Requesting assistance on Delphi sourcecode (Meen IMG2BMP)

Discuss game modding
Post Reply
MartinVole
4-bit nibble
Posts: 18
Joined: August 30th, 2012, 9:13 pm

Requesting assistance on Delphi sourcecode (Meen IMG2BMP)

Post by MartinVole »

In previous post I made introducing information on I.M.Meen which I transferred and expanded on in the Modding Wiki, I am lacking information on masking which was not fully explained in the topic but featured in the tool. Delphi is a language I barely understand. I'm no programmer though, more of a minor code revision/augment is my height. Usually I just do things directly by hex and see what happens.

Code: Select all

(* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   IMG2BMP.DPR (Delphi 7 project)                                *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   I.M.Meen/Chill Manor (1995 PC/DOS) IMG2BMP                    *
 *    Author:   WRS (xentax.com)                                   *
 *    Thread:   http://forum.xentax.com/viewtopic.php?f=18&t=3929  *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *)

program IMG2BMP;
{$APPTYPE CONSOLE}

uses
  Windows,
  Classes,
  SysUtils,
  Graphics;

const
  // image dimentions 
  IMG_MAX_WIDTH  = 64;
  IMG_MAX_HEIGHT = 64;

  // palette from "MeenMapPalette.bin" (formatting was automated!)
  IMG_PALETTE : array[0..767] of Byte = (
    $00,$00,$00, $00,$00,$BC, $00,$A8,$00, $00,$A8,$A8, $A8,$00,$00, $A8,$00,$A8, $A8,$54,$00, $A8,$A8,$A8,
    $54,$54,$54, $54,$54,$FC, $54,$FC,$54, $54,$FC,$FC, $FC,$54,$54, $FC,$54,$FC, $FC,$FC,$54, $FC,$FC,$FC,
    $F8,$54,$54, $F0,$44,$44, $E8,$30,$30, $E4,$20,$20, $DC,$10,$10, $D4,$00,$00, $C8,$00,$00, $BC,$00,$00,
    $AC,$00,$00, $9C,$00,$00, $8C,$00,$00, $7C,$00,$00, $70,$00,$00, $64,$00,$00, $5C,$00,$00, $50,$00,$00,
    $44,$00,$00, $38,$00,$00, $30,$00,$00, $28,$00,$00, $24,$00,$00, $1C,$00,$00, $14,$00,$00, $0C,$00,$00,
    $D8,$70,$34, $C8,$64,$2C, $BC,$5C,$24, $AC,$50,$1C, $9C,$44,$10, $8C,$38,$08, $80,$30,$08, $78,$2C,$04,
    $6C,$24,$04, $64,$20,$04, $58,$18,$00, $50,$10,$00, $4C,$10,$00, $48,$0C,$00, $44,$0C,$00, $40,$08,$00,
    $3C,$08,$00, $38,$04,$00, $30,$04,$00, $28,$04,$00, $20,$04,$00, $18,$04,$00, $10,$00,$00, $0C,$00,$00,
    $C0,$E4,$FC, $B0,$E0,$FC, $A8,$D4,$F0, $9C,$C8,$E4, $90,$BC,$D8, $84,$B0,$CC, $78,$A0,$C0, $6C,$94,$B0,
    $64,$88,$A0, $58,$78,$90, $4C,$6C,$80, $44,$5C,$6C, $38,$4C,$5C, $2C,$40,$4C, $28,$38,$40, $20,$30,$38,
    $1C,$28,$30, $18,$20,$24, $10,$18,$1C, $0C,$10,$14, $08,$0C,$10, $08,$0C,$0C, $04,$08,$0C, $04,$08,$08,
    $F0,$F0,$94, $E4,$E4,$88, $D8,$D8,$78, $CC,$CC,$68, $B8,$B8,$60, $A8,$A8,$58, $98,$98,$50, $88,$88,$44,
    $74,$74,$3C, $64,$64,$34, $58,$58,$2C, $50,$50,$28, $44,$44,$24, $38,$38,$1C, $30,$30,$18, $24,$24,$10,
    $24,$20,$10, $20,$1C,$10, $1C,$18,$0C, $1C,$14,$0C, $14,$10,$08, $10,$0C,$08, $08,$08,$04, $04,$04,$00,
    $F0,$D4,$B8, $E0,$C4,$A4, $D4,$B4,$94, $C4,$A4,$80, $B8,$94,$70, $A8,$84,$5C, $9C,$74,$48, $8C,$60,$38,
    $80,$50,$24, $74,$48,$20, $68,$40,$18, $60,$38,$10, $54,$2C,$0C, $48,$24,$04, $40,$1C,$00, $38,$18,$00,
    $30,$14,$00, $28,$10,$00, $20,$0C,$00, $18,$08,$00, $10,$04,$00, $0C,$04,$00, $0C,$04,$00, $08,$04,$00,
    $E8,$C0,$FC, $E0,$A4,$FC, $D8,$88,$FC, $CC,$6C,$FC, $C4,$50,$FC, $BC,$34,$FC, $B4,$18,$FC, $A8,$00,$FC,
    $9C,$00,$E8, $8C,$00,$D4, $7C,$00,$C0, $70,$00,$AC, $60,$00,$98, $50,$00,$84, $48,$00,$74, $3C,$00,$64,
    $34,$00,$54, $2C,$00,$44, $20,$00,$34, $18,$00,$24, $14,$00,$20, $10,$00,$18, $0C,$00,$14, $08,$00,$0C,
    $FC,$D0,$AC, $F8,$C8,$98, $F8,$BC,$88, $F8,$B4,$74, $F4,$A8,$64, $F4,$A0,$50, $F0,$94,$40, $F0,$88,$2C,
    $EC,$80,$1C, $DC,$74,$14, $CC,$6C,$10, $BC,$60,$0C, $AC,$58,$04, $9C,$4C,$00, $8C,$44,$00, $78,$3C,$00,
    $68,$30,$00, $54,$28,$00, $44,$1C,$00, $30,$14,$00, $28,$10,$00, $20,$0C,$00, $18,$08,$00, $10,$08,$00,
    $E8,$E8,$E8, $DC,$DC,$DC, $D4,$D4,$D4, $C8,$C8,$C8, $BC,$BC,$BC, $B0,$B0,$B0, $B0,$B0,$B0, $A4,$A4,$A4,
    $98,$98,$98, $8C,$8C,$8C, $80,$80,$80, $70,$70,$70, $68,$68,$68, $5C,$5C,$5C, $50,$50,$50, $48,$48,$48,
    $3C,$3C,$3C, $30,$30,$30, $28,$28,$28, $20,$20,$20, $18,$18,$18, $10,$10,$10, $08,$08,$08, $00,$00,$00,
    $F8,$F8,$D0, $F0,$F0,$C4, $EC,$E8,$B4, $E0,$E0,$B0, $D8,$D8,$A4, $CC,$CC,$94, $BC,$B8,$88, $A4,$A0,$74,
    $94,$90,$64, $84,$7C,$54, $74,$6C,$48, $64,$5C,$40, $58,$54,$38, $50,$48,$34, $44,$40,$2C, $38,$34,$24,
    $30,$2C,$20, $24,$20,$18, $1C,$1C,$14, $18,$14,$0C, $10,$0C,$08, $0C,$08,$04, $08,$04,$04, $00,$00,$00,
    $C8,$F8,$AC, $B4,$F4,$98, $A0,$F0,$80, $88,$EC,$6C, $74,$E8,$58, $60,$E4,$44, $50,$D0,$38, $40,$C0,$30,
    $30,$AC,$24, $20,$98,$1C, $10,$84,$10, $00,$70,$08, $00,$68,$08, $00,$5C,$04, $00,$50,$04, $00,$48,$04,
    $00,$3C,$04, $00,$30,$04, $00,$2C,$00, $00,$24,$00, $00,$1C,$00, $00,$14,$00, $00,$0C,$00, $00,$04,$00
  );

type
  {
     Sprite margins (from left side of image)
  }
  IMG_PADDED_HEAD = packed record
    Left,
    Right  : Word;
  end;

  {
     Line margins (from left side of image)

     Line width is (EOL - SOL)
       SOL ---> EOL
       When EOL == 0, there isn't anymore data, and the structure stops
       PIX is the base palette index pointer
  }
  IMG_PADDED_FOOT = packed record
    EOL,
    SOL,
    PIX    : SmallInt;
  end;

var
  IMGF      : TMemoryStream; // file i/o

  HeaderFlag,
  tHeader,
  Headers,
  PaddedHeaders,
  h,i,j     : Word;

  dOffset,
  hCPos,
  pxOrigin  : LongWord;

  px,
  x,
  y         : Byte;

  BMP       : TBitmap;       // writing bitmaps the dull way
  PixelData : array[0..((IMG_MAX_WIDTH * IMG_MAX_HEIGHT) -1)] of Byte;

  pHead     : IMG_PADDED_HEAD;
  Foot      : IMG_PADDED_FOOT;
  pFooter   : Array of SmallInt;
  TickCnt   : LongWord; // speed testing

procedure PrintUsage;
begin
  Write(' > Usage: '+ExtractFileName(ParamStr(0))+' <filename>'#13#10#13#10);
end;

procedure PreExit;
begin
  Write('Press ENTER to exit...'#13#10);
  Readln;
end;

// entry point
begin

  // program info
  Write('I.M.Meen/Chill Manor (1995 PC/DOS) IMG2BMP'#13#10);
  Write(' Author:'#9'WRS (xentax.com)'#13#10);
  Write(' Thread:'#9'http://forum.xentax.com/viewtopic.php?f=18&t=3929'#13#10#13#10);

  // check we have an argument
  if ParamCount() <> 1 then
  begin
    PrintUsage;
    PreExit;
    Exit;
  end;

  // check it's a file
  if FileExists(ParamStr(1)) = false then
  begin
    Write(' > ERROR: Could not load "'+ParamStr(1)+'"'#13#10#13#10);
    PreExit;
    Exit;
  end;

  // load file
  IMGF := TMemoryStream.Create;
  IMGF.LoadFromFile(ParamStr(1));

  // read in the header flag
  IMGF.Read(HeaderFlag, 2);

  {
       if HeaderFlag == 1 then there are no padded images
       else, there are more headers with padding image info
  }

  if HeaderFlag = 1 then
  begin

    IMGF.Read(Headers, 2);
    PaddedHeaders    := 0;

  end
  else
  begin

    Headers := 0;
    IMGF.Read(tHeader, 2); Headers := Headers + tHeader;
    IMGF.Read(tHeader, 2); Headers := Headers + tHeader;

    IMGF.Read(PaddedHeaders, 2);

    // skip other header info (null or otherwise unknown 16-bit integers)
    IMGF.Seek(2 + (HeaderFlag * 2), soBeginning);

  end;

  Write(' > Expecting '+IntToStr(Headers+PaddedHeaders)+' files'#13#10);

  // parse all square (no padding, 64x64) sprites
  for h := 1 to Headers do
  begin
    // read sprite offset
    IMGF.Read(dOffset, 4);

    // save pos and goto sprite data
    hCPos := IMGF.Position;
    IMGF.Seek(dOffset, soBeginning);

    Write(' > Reading sprite @ '+inttostr(IMGF.Position)+#13#10);

    BMP             := TBitmap.Create;
    BMP.Width       := IMG_MAX_WIDTH;
    BMP.Height      := IMG_MAX_HEIGHT;
    BMP.PixelFormat := pf24bit;

    IMGF.Read(PixelData, IMG_MAX_WIDTH * IMG_MAX_HEIGHT);

    for y:=0 to IMG_MAX_HEIGHT-1 do
    begin
      for x:=0 to IMG_MAX_WIDTH-1 do
      begin

        // Get palette color index from pixeldata
        BMP.Canvas.Pixels[x,y] := (IMG_PALETTE[ (PixelData[(x*64)+y] * 3) + 0 ] * $1)
                                 +(IMG_PALETTE[ (PixelData[(x*64)+y] * 3) + 1 ] * $100)
                                 +(IMG_PALETTE[ (PixelData[(x*64)+y] * 3) + 2 ] * $10000);

      end;
    end;

    BMP.SaveToFile(Format('Sprite_%.4d.bmp', [h]));
    BMP.Free;

    // return back to header data
    IMGF.Seek(hCPos, soBeginning);
  end;

  // parse all other sprites (padded, 64x64) sprites
  for h := 1 to PaddedHeaders do
  begin
    // read sprite offset
    IMGF.Read(dOffset, 4);

    // save pos and goto sprite data
    hCPos := IMGF.Position;
    IMGF.Seek(dOffset, soBeginning);

    Write(' > Reading alpha sprite @ '+inttostr(IMGF.Position));
    TickCnt := GetTickCount();


    // read in the sprite margins
    IMGF.Read(pHead, SizeOf(IMG_PADDED_HEAD));

    {
        these values are used to determine how many footers there are
        it may not be completely reliable, so beware
    }

    // read all the footer pointers
    SetLength(pFooter, pHead.Right-pHead.Left+1);


    for i:=0 to (pHead.Right-pHead.Left) do
    begin
      IMGF.Read(tHeader, 2);
      pFooter[i] := tHeader;
    end;

    BMP             := TBitmap.Create;
    BMP.Width       := IMG_MAX_WIDTH;
    BMP.Height      := IMG_MAX_HEIGHT;
    BMP.PixelFormat := pf24bit;

    // fill the canvas with our alpha layer color
    with BMP.Canvas do
    begin
      Pen.Color   := $FF00FF; // magenta chroma key
                              // anything which isn't in the pallete will do
      Brush.Color := Pen.Color;
    end;

    BMP.Canvas.Rectangle(0,0,IMG_MAX_WIDTH,IMG_MAX_HEIGHT);

    // read all footer data
    for i:=0 to (pHead.Right-pHead.Left) do
    begin
      IMGF.Seek(dOffset + pFooter[i], soBeginning);

      // read until EOL == 0
      while true do
      begin

        IMGF.Read(tHeader, 2);
        if tHeader = 0 then break;

        Foot.EOL := tHeader;

        IMGF.Read(tHeader, 2); Foot.SOL := tHeader;
        IMGF.Read(tHeader, 2); Foot.PIX := tHeader;

        // for each pixel in this line
        for j:= Foot.SOL to Foot.EOL -1 do
        begin
          pxOrigin := IMGF.Position;

          // get the palette index

          // position = sprite offset + pixel offset + line pos
          IMGF.Seek(dOffset + Foot.PIX + (j - Foot.SOL), soBeginning);
          IMGF.Read(px, 1);

          BMP.Canvas.Pixels[pHead.Left + i, j] :=

                                  (IMG_PALETTE[ (px * 3) + 0 ] * $1)
                                 +(IMG_PALETTE[ (px * 3) + 1 ] * $100)
                                 +(IMG_PALETTE[ (px * 3) + 2 ] * $10000);


          IMGF.Seek(pxOrigin, soBeginning);

        end;

      end;

    end;


    BMP.SaveToFile(Format('Sprite_%.4d.bmp', [Headers + h]));
    BMP.Free;

    Write(' ('+IntToStr(GetTickCount-TickCnt)+' ms)'#13#10);

    // return back to header data
    IMGF.Seek(hCPos, soBeginning);
  end;

  IMGF.Free;

  Write(' > Done!'#13#10);

  PreExit;
  Exit;

end.
I can't quite gather this (the handling of masking) into an explainable format (the only thing I do not have documented regarding Meen IMGs). The tool obviously too has no handling for images larger than 64x64 and recursive indexes (as those used in Chill Manor), and the usage of a predefined palette array rather than loading from the source makes little sense to me and seems... to be an unnecessarily complicated brute-force method. Then again, do not know if Delphi has binary source read capabilities or a binary-to-array procedure as I've seen with C++/VB. Ah well... in any case, any help is appreciated.
User avatar
DOSGuy
Website Administrator
Posts: 1063
Joined: September 2nd, 2005, 8:28 pm
Contact:

Re: Requesting assistance on Delphi sourcecode (Meen IMG2BMP

Post by DOSGuy »

I'm a Delphi programmer, or I was until about 5 years ago. I can follow what's basically going on here, with an IMG being loaded and R, G and B values being read in one section, then a Bitmap object being created and R, G and B valued being written in another section. That's pretty much how I did it when I manually created BMPs in my Delphi programs back in the day.

I'm not totally sure what your question is, or if I would still (or ever did) understand this code well enough to answer it. With regard to binary stuff, Delphi supports numbers in hex and octal, and also supports inline Assembly. In fact, very modern Delphi supports pretty much every major programming language. Object Pascal is still very readable, but I only program in C-like languages and BASIC (shudder) at work now.
Today entirely the maniac there is no excuse with the article.
MartinVole
4-bit nibble
Posts: 18
Joined: August 30th, 2012, 9:13 pm

Re: Requesting assistance on Delphi sourcecode (Meen IMG2BMP

Post by MartinVole »

C languages I tend to understand more. But what my primary question is understanding the parts handling the "alpha layer" which is a form of compression used by the Meen engine.
User avatar
Hallfiry
7-bit super nerd
Posts: 210
Joined: March 20th, 2012, 10:41 am
Contact:

Re: Requesting assistance on Delphi sourcecode (Meen IMG2BMP

Post by Hallfiry »

First of all, the person who wrote this program is not a pro. Writing the palette as a 0..255 array of inters would have been much easier. Especially accessing the values and writing them on the canvas would be simplified.

What exactly do you want to know about the program or the format that it decodes?
Magazine cover disk catalog:
http://www.kultcds.com/Catalog/
MartinVole
4-bit nibble
Posts: 18
Joined: August 30th, 2012, 9:13 pm

Re: Requesting assistance on Delphi sourcecode (Meen IMG2BMP

Post by MartinVole »

Personally, I'd think it better to have another input that just loads the 768-byte palette and translate it to an array.

As for what I'm trying to document is where it fills the alpha layer in to complete the image. The IMG container has places in it that tells the game to fill in gaps, that is, where transparency is, likely in an effort to reduce file size. This portion of the IMG2BMP code is extremely accurate in that part because it is based on debugging the game itself but was undocumented.

It is exactly that, how it reads and processes the alpha/masking of the images. How it reads, processes, and performs those routines based on what it finds. I understand everything else in the format up until that point. Its the only thing I haven't been able to document. Sometime though I may spend time augmenting the program but Delphi is certainly NOT like C...
User avatar
Hallfiry
7-bit super nerd
Posts: 210
Joined: March 20th, 2012, 10:41 am
Contact:

Re: Requesting assistance on Delphi sourcecode (Meen IMG2BMP

Post by Hallfiry »

I've now roughly read the corresponding code fragment and have to say again that I don't like that programmer.

But I know what he does. There are "lines" defined, which are actually lines of pixels within a line. When the end of a line is defined as 0, the line is terminated. before drawing the pixels for each line, he paints a magenta rectangle on the entire canvas. Then he reads all "lines" for each line and jumps to the starting position within the line on the canvas and draws the pixels. These pictures also have a padding from left and right to save space due to the fixed image size of 64x64

Also, the programmer who invented that format seems to be a little bit crazy. (I've seen a lot of weird formats, but that one almost takes the cake. Only Battlespire can beat it)
Magazine cover disk catalog:
http://www.kultcds.com/Catalog/
MartinVole
4-bit nibble
Posts: 18
Joined: August 30th, 2012, 9:13 pm

Re: Requesting assistance on Delphi sourcecode (Meen IMG2BMP

Post by MartinVole »

Yeah, this is Animation Magic, known more for Faces of Evil and Wand of Gamelon, so their routines are complicated only by the fact they are... very weird. The magenta lines represent the way the game itself processes them filling the gaps on the fly, as the engine reads these fills as the transparency... technically sound, reduces filesize afterall... but when you have redundant copies of the same files spanned across 35 containers it... is a moot point. Added that some processing time is spent performing these adjustments, I'd just have used a solid transparent color... far less painful.

But thanks for the clarification! I'll try to document it.

Edit: I plan to overhaul the program in C++ to support Chill Manor but I'm admittedly a programming goof. One of the changes I'd make would be to have the palette read from external palette itself. That would remove the necessity to make another program that handles Chill Manor exclusively. I think that would involve in.read and transferring the entire processing of the palette to an array of the size of 768.
Post Reply