Thread: Help Needed With Finding Image Data In DOS Game File

  1. #1
    Registered User
    Join Date
    Jan 2023
    Posts
    39

    Help Needed With Finding Image Data In DOS Game File

    I have been analysing, an File called FW190AS.PAC from a 16 Bit Game called Secret Weapons Of The Luftwaffe, in a Hex Editor, the Bytes are displayed in Little Endian format. My aim with this game, is to eventually change The Planes shapes in the game for the outside View, i.e. they are basic Bitmap images, to other shaped Planes. There is another File called FW190A.PAC which also contains images for different views of the Plane and other info. My feeling the 'S' at the end of the name of the File, refers to Shape.

    Through trial and error and narrowing down byte sections in the FW190AS.PAC File, I have noticed for example, that when I change byte values, at certain positions at offset 1030 shown in the Hex Editor, one of the views at an angle of the FW190 plane i.e. Focke Wolf 190,

    A line attached to the plane, on the right side appears, when I look out the rear view mirror view, changing the bytes produces different coloured sections on the line, each time I change the values. Also when I Change Bytes For Offsets 1000 to 10F0, with Bytes from the Corresponding Offsets in BF109GS.PAC, gives a certain angle view of the BF109G, not facing forwards but Northeast for the FW190A. When I change bytes in offset 1720 in FW190AS.PAC the Upwards View looks different.

    Also If I do the same with the bytes, from offsets 1100 to 1400 in the BF109GS.PAC File, to offsets 1100 to 1400 in the FW190AS.PAC File I get the BF109G shown in the opening movie in the game instead of the FW190A.

    I also believe that the different angle Views, for the outside views of the Planes in the game, are at different offsets in the shape Files. I am convinced too, that the Bytes for the different, outside angle views of the Planes, are at different offsets for the different planes' Shape Files, as the BF109GS.PAC bytes for the offsets 1000 to 10F0, give a different view for the BF109G, to the bytes from 1000 to 10F0 Offsets in FW190AS.PAC, which is a forward-facing view i.e. for the FW190A. This would make sense, as the total number of offsets, is different in each plane's shape file.

    I have attached links to Screenshots of the FW190, and you will notice the colour change, in sections of the line out of the FW190A, also In the climbing Plane View changes, when changing bytes in offset 1720, changing the first byte to 07, puts a black line on the left wing, near where it is attached to the fuselage, and also a bitmap, of the original view of the FW190A.
    the SWOTL Plane Shape Test 19 and SWOTL Plane Shape Test 20 Screenshot Files I link to amongst others correspond to the Files I named FW190AS (19).PAC and FW190AS (20).PAC respectively, those will need to change back to FW190AS.PAC to be used in the game, choose the Scramble from Brandis Mission, in Historical Missions for the FW190A, and look out of the rearview mirror, to see the new view, it sometimes takes a few seconds, to change to that, bytes were changed at offset 1030.

    Here is a C Program, very kindly written by john.c on this forum for me, that extracts the images from Secret Weapons of the Luftwaffe Files, and saves them as a bitmap, some are in colour most output Greyscale images:-
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <limits.h>
    
    #define MAX_W   600
    #define MAX_H   300
    
    #define HEADER_SIZE     54
    #define PALETTE_ENTRIES 256
    #define PALETTE_SIZE    (PALETTE_ENTRIES * 4)   // 4 bytes per entry
    #define DATA_OFFSET     (HEADER_SIZE + PALETTE_SIZE)
    
    typedef unsigned char Byte;
    typedef enum Mode { V1, V2 } Mode; // V1 for older files, V2 for newer
    
    typedef struct Info {
        Byte image[MAX_H][MAX_W];
        int  min_width, max_width;
        int  width, height;
        int  adder;
        int  output_file_index;
        int  n_palette;
        const char *filename;
    } Info;
    
    Info info = {
        .min_width = INT_MAX,
        .n_palette = -1,
        .filename  = NULL
    };
    Mode ProgramMode = V1;
    
    void reset() {
        memset(info.image, 0, MAX_H * MAX_W);
        info.min_width = INT_MAX;
        info.max_width = 0;
        info.width = 0;
        info.height = 0;
        info.adder = 0;
    }
    
    void default_palette(Byte *pal) {
        const int shift = (ProgramMode == V1 ? 3 : 2);
        for (int color = 0; color < PALETTE_ENTRIES; color++) {
            *pal++ = color << shift;
            *pal++ = color << shift;
            *pal++ = color << shift;
            *pal++ = 0;
        }
    }
    
    void fill_palette(Byte *pal, const char *infilename, int n_palette) {
        if (n_palette == -1) {
            default_palette(pal);
            return;
        }
        FILE *f = fopen(infilename, "rb");
        int byte, cnt = 0;
        const int shift = (ProgramMode == V1 ?  3 :  2);
        const int size  = (ProgramMode == V1 ? 32 : 64);
        while ((byte = fgetc(f)) != EOF) {
            if (byte == 0xFF && ++cnt == n_palette) {
                for (int i = 0; i < size; ++i) {
                    int r = fgetc(f), g = fgetc(f), b = fgetc(f);
                    pal[i * 4    ] = b << shift;
                    pal[i * 4 + 1] = g << shift;
                    pal[i * 4 + 2] = r << shift;
                }
                break;
            }
        }
        fclose(f);
    }
    
    void save_bmp() { 
        Byte header[HEADER_SIZE] = {
            0x42, 0x4D,                  //  0: BM signature
              -1,   -1,   -1,   -1,      //  2: filesize
               0,    0,    0,    0,      //  6: reserved
              -2,   -2,   -2,   -2,      // 10: offset to pixel array
            0x28,    0,    0,    0,      // 14: DIB header size
              -3,   -3,   -3,   -3,      // 18: width
              -4,   -4,   -4,   -4,      // 22: height
            0x01,    0,                  // 26: planes
            0x08,    0                   // 28: bits per pixel
        };
    
        int pad = (4 - info.max_width % 4) % 4;
        int file_size = DATA_OFFSET + (info.max_width + pad) * info.height;
        Byte *output = malloc(file_size);
        memcpy(output, header, HEADER_SIZE);
        *(int*)(output +  2) =  file_size;
        *(int*)(output + 10) =  DATA_OFFSET;
        *(int*)(output + 18) =  info.max_width;
        *(int*)(output + 22) = -info.height;
    
        Byte *pal = output + HEADER_SIZE;
        if (info.n_palette == -1)
            default_palette(pal);
        else
            fill_palette(pal, info.filename, info.n_palette);
    
        for (int h = 0, pos = DATA_OFFSET; h < info.height; h++) {
            for (int w = 0; w < info.max_width; w++)
                output[pos++] = info.image[h][w];
            for (int p = 0; p < pad; p++)
                output[pos++] = 0;
        }
    
        // Output filenames are: filename-outN.bmp where N is 0,1,...
        char outfilename[100];
        strcpy(outfilename, info.filename);
        char *p = strrchr(outfilename, '.');
        if (p) *p = '\0';
        sprintf(outfilename + strlen(outfilename), "-out%d.bmp",
            info.output_file_index);
    
        FILE *f = fopen(outfilename, "wb");
        fwrite(output, 1, file_size, f);
        fclose(f);
    
        free(output);
    }
    
    void add(int value) {
        if (info.height < MAX_H && info.width < MAX_W)
            info.image[info.height][info.width++] = value;
    }
    
    void process(FILE *fp) {
        for (int byte, prev = 0xFF; (byte = fgetc(fp)) != EOF; prev = byte) {
            int count, value;
            if (prev == 0xFF)
                printf("Chunk: %d\n", info.output_file_index);
            switch (byte) {
            case 0xFF: // End of image
                if (prev == 0xFE) {
                    if (info.min_width != info.max_width)
                        printf("  Error: min (%d) and max (%d) widths do not match\n",
                            info.min_width, info.max_width);
                    printf("  Width: %d  Height: %d\n", info.max_width, info.height);
                    save_bmp();
                }
                reset();
                ++info.output_file_index;
                break;
            case 0xFE: // End of image line
                if (info.width < info.min_width) info.min_width = info.width;
                if (info.width > info.max_width) info.max_width = info.width;
                ++info.height;
                info.width = 0;
                break;
            case 0xFD: // next two bytes are count-1 and value
                count = fgetc(fp) + 1;
                value = fgetc(fp);
                for (int i = 0; i < count; ++i) add(value);
                break;
            case 0xFC: // next two bytes are count-1 and value
                value = fgetc(fp);  // orig. code had +1 here (but why?)
                count = fgetc(fp) + 1;
                for (int i = 0; i < count; ++i) // draw dithered line
                    add(i % 2 == 0 ? value : value + 1);
                break;
            case 0xFB: // Set adder used in default case
                info.adder = fgetc(fp);
                break;
            default:
                if (ProgramMode == V1) {
                    value = (byte >> 3) + info.adder; // High 5 bits are value
                    count = (byte & 7) + 1;           // Low 3 bits are count-1
                }
                else {
                    value = (byte >> 2) + info.adder; // High 6 bits are value
                    count = (byte & 3) + 1;           // Low 2 bits are count-1
                }
                for (int i = 0; i < count; ++i) add(value);
            }
        }
    }
    
    int main(int argc, char *argv[]) {
        if (argc < 2 || argc > 5) {
            printf("Usage: %s [-n] [-p N] INPUT_FILE\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        while (**++argv == '-') {
            if (strcmp(*argv, "-n") == 0)
                ProgramMode = V2;
            else if (strncmp(*argv, "-p", 2) == 0) {
                if (argv[0][2])
                    info.n_palette = atoi(&argv[0][2]);
                else {
                    if (*++argv == NULL) {
                        fprintf(stderr, "Error: Missing parameters\n");
                        exit(EXIT_FAILURE);
                    }
                    info.n_palette = atoi(*argv);
                }
            }
            else {
                fprintf(stderr, "Error: Unknown switch: %s\n", *argv);
                exit(EXIT_FAILURE);
            }
        }
    
        if (!*argv) {
            fprintf(stderr, "Error: No filename\n");
            exit(EXIT_FAILURE);
        }
        info.filename = *argv;
        FILE *fp = fopen(info.filename, "rb");
        if (!fp) {
            perror(info.filename);
            exit(EXIT_FAILURE);
        }
    
        process(fp);
    
        return 0;
    }
    Could someone look at the code, and tell me any changes that could be made to the Code i.e. adapt it ?, to extract the bitmap views from the S.PAC Files i.e. the Plane Shape Files, and save them as bitmaps? in the game. The .PAC Files are from the FE Folder in the game, and the .PNL Files from the CP Folder. the S.PAC Files are from the AC, i.e. Aircraft Folder in the game. and I have linked to some, of the output bitmap images, of.PAC and .PNL Files.
    I watched the following Youtube Video, and particularly looked, at the Fonts Section of the video :-

    Reverse Engineering a Classic Video Game (BioForge) - YouTube


    I noticed that when I opened the BF109GS.PAC and FW190AS.PAC Files in HxD Hex Editor there were repeating Bytes at the top few Offsets, i.e. 01 04 0D 31 01 04 0D 31 01 04 0D 31, on the first offset in the FW190AS.PAC File, then some more repeats in the BF109GS.PAC File, i.e. 01 07 0C 11 01 07 0C 11 01 06 0C 12 01 06 0C 12 01 06 0C 12 01 06 0D 11 01 06 0D, could these repeats be akin, to what the man in the video says they could be, for the Bioforge Game ? And what sections of code i.e. offsets do they correspond to here, are they saying, where the offsets and bytes, for each of the angle views are in the File?
    Here is a link to the game, where you can download it from:-

    Download Secret Weapons of the Luftwaffe (DOS) game - Abandonware DOS


    Here is a link, to the Files I uploaded :-

    https://www.dropbox.com/t/oglKujqxi6BrFVGJ


    The pacswotl and pnlswotl Folders amongst other files, contain the batch Files and compiled C program.

    Any help and info would be very much appreciated.
    Last edited by Salem; 07-06-2023 at 08:55 PM. Reason: Fixed formatting

  2. #2
    Registered User rstanley's Avatar
    Join Date
    Jun 2014
    Location
    New York, NY
    Posts
    1,111
    The first thing to do is to choose and consistently use an indentation style to make the code more readable.

  3. #3
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    The first thing to do is to put the code in code tags so the indentation is retained.
    The second thing to do is to paste the text without extra markup (copy and/or paste as plain text).
    Copying the code from the page source and removing markup (and translating &gt;, &lt; &quot; &amp; ) yields the following.
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <limits.h>
     
    #define MAX_W   600
    #define MAX_H   300
     
    #define HEADER_SIZE     54
    #define PALETTE_ENTRIES 256
    #define PALETTE_SIZE    (PALETTE_ENTRIES * 4)   // 4 bytes per entry
    #define DATA_OFFSET     (HEADER_SIZE + PALETTE_SIZE)
     
    typedef unsigned char Byte;
    typedef enum Mode { V1, V2 } Mode; // V1 for older files, V2 for newer
     
    typedef struct Info {
        Byte image[MAX_H][MAX_W];
        int  min_width, max_width;
        int  width, height;
        int  adder;
        int  output_file_index;
        int  n_palette;
        const char *filename;
    } Info;
     
    Info info = {
        .min_width = INT_MAX,
        .n_palette = -1,
        .filename  = NULL
    };
    Mode ProgramMode = V1;
     
    void reset() {
        memset(info.image, 0, MAX_H * MAX_W);
        info.min_width = INT_MAX;
        info.max_width = 0;
        info.width = 0;
        info.height = 0;
        info.adder = 0;
    }
     
    void default_palette(Byte *pal) {
        const int shift = (ProgramMode == V1 ? 3 : 2);
        for (int color = 0; color < PALETTE_ENTRIES; color++) {
            *pal++ = color << shift;
            *pal++ = color << shift;
            *pal++ = color << shift;
            *pal++ = 0;
        }
    }
     
    void fill_palette(Byte *pal, const char *infilename, int n_palette) {
        if (n_palette == -1) {
            default_palette(pal);
            return;
        }
        FILE *f = fopen(infilename, "rb");
        int byte, cnt = 0;
        const int shift = (ProgramMode == V1 ?  3 :  2);
        const int size  = (ProgramMode == V1 ? 32 : 64);
        while ((byte = fgetc(f)) != EOF) {
            if (byte == 0xFF && ++cnt == n_palette) {
                for (int i = 0; i < size; ++i) {
                    int r = fgetc(f), g = fgetc(f), b = fgetc(f);
                    pal[i * 4    ] = b << shift;
                    pal[i * 4 + 1] = g << shift;
                    pal[i * 4 + 2] = r << shift;
                }
                break;
            }
        }
        fclose(f);
    }
     
    void save_bmp() { 
        Byte header[HEADER_SIZE] = {
            0x42, 0x4D,                  //  0: BM signature
              -1,   -1,   -1,   -1,      //  2: filesize
               0,    0,    0,    0,      //  6: reserved
              -2,   -2,   -2,   -2,      // 10: offset to pixel array
            0x28,    0,    0,    0,      // 14: DIB header size
              -3,   -3,   -3,   -3,      // 18: width
              -4,   -4,   -4,   -4,      // 22: height
            0x01,    0,                  // 26: planes
            0x08,    0                   // 28: bits per pixel
        };
     
        int pad = (4 - info.max_width % 4) % 4;
        int file_size = DATA_OFFSET + (info.max_width + pad) * info.height;
        Byte *output = malloc(file_size);
        memcpy(output, header, HEADER_SIZE);
        *(int*)(output +  2) =  file_size;
        *(int*)(output + 10) =  DATA_OFFSET;
        *(int*)(output + 18) =  info.max_width;
        *(int*)(output + 22) = -info.height;
     
        Byte *pal = output + HEADER_SIZE;
        if (info.n_palette == -1)
            default_palette(pal);
        else
            fill_palette(pal, info.filename, info.n_palette);
     
        for (int h = 0, pos = DATA_OFFSET; h < info.height; h++) {
            for (int w = 0; w < info.max_width; w++)
                output[pos++] = info.image[h][w];
            for (int p = 0; p < pad; p++)
                output[pos++] = 0;
        }
     
        // Output filenames are: filename-outN.bmp where N is 0,1,...
        char outfilename[100];
        strcpy(outfilename, info.filename);
        char *p = strrchr(outfilename, '.');
        if (p) *p = '\0';
        sprintf(outfilename + strlen(outfilename), "-out%d.bmp",
            info.output_file_index);
     
        FILE *f = fopen(outfilename, "wb");
        fwrite(output, 1, file_size, f);
        fclose(f);
     
        free(output);
    }
     
    void add(int value) {
        if (info.height < MAX_H && info.width < MAX_W)
            info.image[info.height][info.width++] = value;
    }
     
    void process(FILE *fp) {
        for (int byte, prev = 0xFF; (byte = fgetc(fp)) != EOF; prev = byte) {
            int count, value;
            if (prev == 0xFF)
                printf("Chunk: %d\n", info.output_file_index);
            switch (byte) {
            case 0xFF: // End of image
                if (prev == 0xFE) {
                    if (info.min_width != info.max_width)
                        printf("  Error: min (%d) and max (%d) widths do not match\n",
                            info.min_width, info.max_width);
                    printf("  Width: %d  Height: %d\n", info.max_width, info.height);
                    save_bmp();
                }
                reset();
                ++info.output_file_index;
                break;
            case 0xFE: // End of image line
                if (info.width < info.min_width) info.min_width = info.width;
                if (info.width > info.max_width) info.max_width = info.width;
                ++info.height;
                info.width = 0;
                break;
            case 0xFD: // next two bytes are count-1 and value
                count = fgetc(fp) + 1;
                value = fgetc(fp);
                for (int i = 0; i < count; ++i) add(value);
                break;
            case 0xFC: // next two bytes are count-1 and value
                value = fgetc(fp);  // orig. code had +1 here (but why?)
                count = fgetc(fp) + 1;
                for (int i = 0; i < count; ++i) // draw dithered line
                    add(i % 2 == 0 ? value : value + 1);
                break;
            case 0xFB: // Set adder used in default case
                info.adder = fgetc(fp);
                break;
            default:
                if (ProgramMode == V1) {
                    value = (byte >> 3) + info.adder; // High 5 bits are value
                    count = (byte & 7) + 1;           // Low 3 bits are count-1
                }
                else {
                    value = (byte >> 2) + info.adder; // High 6 bits are value
                    count = (byte & 3) + 1;           // Low 2 bits are count-1
                }
                for (int i = 0; i < count; ++i) add(value);
            }
        }
    }
     
    int main(int argc, char *argv[]) {
        if (argc < 2 || argc > 5) {
            printf("Usage: %s [-n] [-p N] INPUT_FILE\n", argv[0]);
            exit(EXIT_FAILURE);
        }
     
        while (**++argv == '-') {
            if (strcmp(*argv, "-n") == 0)
                ProgramMode = V2;
            else if (strncmp(*argv, "-p", 2) == 0) {
                if (argv[0][2])
                    info.n_palette = atoi(&argv[0][2]);
                else {
                    if (*++argv == NULL) {
                        fprintf(stderr, "Error: Missing parameters\n");
                        exit(EXIT_FAILURE);
                    }
                    info.n_palette = atoi(*argv);
                }
            }
            else {
                fprintf(stderr, "Error: Unknown switch: %s\n", *argv);
                exit(EXIT_FAILURE);
            }
        }
     
        if (!*argv) {
            fprintf(stderr, "Error: No filename\n");
            exit(EXIT_FAILURE);
        }
        info.filename = *argv;
        FILE *fp = fopen(info.filename, "rb");
        if (!fp) {
            perror(info.filename);
            exit(EXIT_FAILURE);
        }
     
        process(fp);
     
        return 0;
    }
    Last edited by john.c; 07-06-2023 at 08:13 AM.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  4. #4
    Registered User
    Join Date
    Jan 2023
    Posts
    39
    Many thanks for editing the C Code for me john.c . Could a moderator, edit it in my original post please ?

  5. #5
    Registered User
    Join Date
    Jan 2023
    Posts
    39
    Hi john.c Could you help me with this ? If that is okay with you ? Yes any help you could give me, would be much appreciated. Regards Eddie Winch ))

  6. #6
    Registered User
    Join Date
    Jan 2023
    Posts
    39
    Hi john.c Could you help me with this ? If that is okay with you ? Yes any help you could give me, would be much appreciated. Regards Eddie Winch ))

  7. #7
    Registered User
    Join Date
    Jan 2023
    Posts
    39
    john.c ?

  8. #8
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    Sorry, Eddy, but I don't feel like working on this.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  9. #9
    Registered User
    Join Date
    Jan 2023
    Posts
    39
    I understand john.c thankyou for your reply. Do you know anyone on this forum, that possibly would ?

  10. #10
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    I think there's only so much you can do with staring at data files, modifying random bytes and observing the effects.

    To make real progress, you'll probably have to disassemble the code.

    This is less daunting than it used to be.
    1. There are more tools to choose from
    2. They're better than they used to be.
    3. You don't have to shell out $$$ to get something decent.
    tools - Is there any disassembler to rival IDA Pro? - Reverse Engineering Stack Exchange

    One thing you'll need to know about is DOS API - Wikipedia
    Like for example int 21 with AH=0Fh is "open file".

    Once you've found a place where it seems to be opening say PAC files, you can dig deeper to see how the files might actually be used and decoded.

    The RBIL is an essential piece of information when it comes to DOS era archaeology.
    Ralf Brown's Files
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  11. #11
    Registered User
    Join Date
    Jan 2023
    Posts
    39
    Hi Salem,

    Many thanks for your very helpful and informative post, it is very much appreciated. ))

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. can't write .bmp header or image data to output file
    By MWJack in forum C Programming
    Replies: 0
    Last Post: 12-12-2015, 02:05 AM
  2. Replies: 5
    Last Post: 02-10-2014, 10:49 PM
  3. c programming problems. reading data file and finding max
    By drewdude92 in forum C Programming
    Replies: 1
    Last Post: 02-21-2013, 10:34 PM
  4. Finding and Replacing Data in a File
    By DrC in forum C Programming
    Replies: 6
    Last Post: 03-20-2011, 10:09 AM
  5. How to write image data to binary PGM file format(P5)?
    By tommy_chai in forum C Programming
    Replies: 6
    Last Post: 11-03-2007, 10:52 PM

Tags for this Thread