2

I have the following two Codes which make up a DOS Game Graphics Image Extracting Program, written in C# :-

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;


namespace Unpac {
class Program {

    //_____________________________________________________________________
    static string       input_filename_no_ext;
    static int          current_output_index;
    static string       output_directory;
    static List<byte>   output_buffer;
    static int          image_height;
    static int          line_min_length;
    static int          line_max_length;

    //_____________________________________________________________________
    static void Main(string[] args) {

        int?        forced_image_width;
        string      input_fullpath;
        string      input_directory;
        string      input_filename;
        byte[]      input_data;
        long        input_max_valid_position;
        long        current_input_position;
        byte        input_byte;
        byte        RLE_sumador;
        int         count;
        byte        b;
        int         n;
        bool        RLE_FC_swap;
        byte        b2;
        int         width_current;


        if(args.Length<2) {
            Console.WriteLine("Use: unpac.exe <InputFile.pac> <OutputDir> [ImageWidth]");
            return;
        }

        if(args.Length>2) {
            forced_image_width = int.Parse(args[2]);
        } else {
            forced_image_width = null;
        }
        
        try {
            input_fullpath      = Path.GetFullPath(args[0]);
            input_directory     = Path.GetDirectoryName(input_fullpath);
            input_filename      = Path.GetFileName(input_fullpath);

            input_fullpath      = Path.Combine(input_directory, input_filename);
            if(!File.Exists(input_fullpath)) throw new Exception();

            int p = input_filename.LastIndexOf('.');
            if(p==-1) {
                input_filename_no_ext = input_filename;
            } else {
                input_filename_no_ext = input_filename.Substring(0, p);
            }

        } catch {
            Console.WriteLine("ERROR: invalid input file");
            return;
        }

        try {
            output_directory = Path.GetFullPath(args[1]);
            if(!Directory.Exists(output_directory)) {
                Directory.CreateDirectory(output_directory);
            }
        } catch {
            Console.WriteLine("ERROR: invalid output directory");
            return;
        }

        try {
            input_data = File.ReadAllBytes(input_fullpath);
        } catch {
            Console.WriteLine("ERROR: cannot read from input file");
            return;
        }

        Console.WriteLine($"{input_filename}");

        input_max_valid_position    = input_data.Length - 1;
        current_input_position      = 0;
        current_output_index        = 1;
        output_buffer               = new List<byte>();

        RLE_sumador                 = 0;
        line_min_length             = int.MaxValue;
        line_max_length             = 0;
        width_current               = 0;
        image_height                = 0;

        while(current_input_position <= input_max_valid_position) {

            input_byte = input_data[current_input_position];
            current_input_position++;

            if(input_byte == 0xFF) {
                //==============
                // Fin de chunk
                //==============
                Save();
                current_output_index++;

                RLE_sumador         = 0;
                line_min_length     = int.MaxValue;
                line_max_length     = 0;
                width_current       = 0;
                image_height        = 0;

            } else if(input_byte == 0xFE) {
                //=================
                // Siguiente linea
                //=================
                if(width_current < line_min_length) line_min_length = width_current;
                if(width_current > line_max_length) line_max_length = width_current;

                if(forced_image_width!=null) {
                    count = forced_image_width.Value - width_current;
                    if(count > 0) {
                        for(n=0; n<count; n++) {
                            output_buffer.Add(0);
                        }
                    }
                }

                image_height++;
                width_current = 0;

            } else if(input_byte == 0xFD) {
                count = (int) (input_data[current_input_position]) + 1;
                current_input_position++;

                b = input_data[current_input_position];
                current_input_position++;

                for(n=0; n<count; n++) {
                    output_buffer.Add(b);
                }

                width_current += count;

            } else if(input_byte == 0xFC) {
                b = input_data[current_input_position];
                current_input_position++;

                b2 = (byte)(b + 1);

                count = (int) (input_data[current_input_position]) + 1;
                current_input_position++;

                RLE_FC_swap = false;
                for(n=0; n<count; n++) {
                    output_buffer.Add(
                        RLE_FC_swap ? b2 : b
                    );
                    RLE_FC_swap = !RLE_FC_swap;
                }

                width_current += count;

            } else if(input_byte == 0xFB) {
                RLE_sumador = input_data[current_input_position];
                current_input_position++;

            } else {
                b = (byte)(input_byte >> 2);
                b += RLE_sumador;
                count = (input_byte & 3) + 1;

                for(n=0; n<count; n++) {
                    output_buffer.Add(b);
                }

                width_current += count;
            }
        }

        Save();
    }

    //_____________________________________________________________________
    static void Save() {

        if(output_buffer.Count == 0) return;

        string  output_filename;
        string  output_fullpath;
        byte[]  output_array;
        int     output_length;
        bool    is_valid_image;


        output_filename = $"{input_filename_no_ext}.{current_output_index:D3}";
        output_fullpath = Path.Combine(output_directory, output_filename);
        output_array    = output_buffer.ToArray();
        output_length   = output_array.Length;
        is_valid_image  = false;


        if(image_height > 0) {
            if(line_min_length != line_max_length) {
                Console.WriteLine($" -> {output_filename} ({output_length}) (min width:{line_min_length}) (max width:{line_max_length}) (height:{image_height})");
            } else {
                Console.WriteLine($" -> {output_filename} ({output_length}) (width:{line_min_length}) (height:{image_height})");
                is_valid_image = true;
            }
        } else {
            Console.WriteLine($" -> {output_filename} ({output_length})");
        }

        try {
            if(is_valid_image) {
                Bitmap.Save(output_fullpath + ".bmp", output_array, line_min_length, image_height);
            } else {
                File.WriteAllBytes(output_fullpath, output_array);
            }
        } catch {
            Console.WriteLine("ERROR: cannot save to output file");
        }

        output_buffer.Clear();
    }

}
}

And :-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace Unpac {
public static class Bitmap {

    private static byte[]           Header;
    private readonly static byte[]  Paleta;

    static Bitmap() {
        Header = new byte[0x436];
        Header[0] = 0x42;
        Header[1] = 0x4D;
        Header[0xA] = 0x36;
        Header[0xB] = 0x04;
        Header[0xE] = 0x28;
        Header[0x1A] = 1;
        Header[0x1C] = 8;
        Header[0x26] = 0x12;
        Header[0x27] = 0x0B;
        Header[0x2A] = 0x12;
        Header[0x2B] = 0x0B;

        
        // Grayscale palette
        int dst_pos = 0x36;
        int color;
        for(color=0; color<0x100; color++) {
            Header[dst_pos++] = (byte) (color << 2);
            Header[dst_pos++] = (byte) (color << 2);
            Header[dst_pos++] = (byte) (color << 2);
            dst_pos++;
        }

        
        /*Paleta = new byte[] {
            0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x05, 0x05, 0x05, 0x08, 0x08, 0x08, 0x0A, 0x0A, 0x0A, 0x0D,
            0x0D, 0x0D, 0x10, 0x10, 0x10, 0x13, 0x13, 0x13, 0x16, 0x16, 0x16, 0x19, 0x19, 0x19, 0x1C, 0x1C,
            0x1C, 0x1F, 0x1F, 0x1F, 0x22, 0x22, 0x22, 0x25, 0x25, 0x25, 0x27, 0x27, 0x27, 0x2A, 0x2A, 0x2A,
            0x2D, 0x2D, 0x2D, 0x30, 0x30, 0x30, 0x33, 0x33, 0x33, 0x36, 0x36, 0x36, 0x39, 0x39, 0x39, 0x3C,
            0x3C, 0x3C, 0x3F, 0x3F, 0x3F, 0x19, 0x3F, 0x19, 0x0F, 0x32, 0x0F, 0x08, 0x25, 0x08, 0x03, 0x19,
            0x03, 0x39, 0x0C, 0x0C, 0x2E, 0x06, 0x06, 0x23, 0x01, 0x01, 0x18, 0x00, 0x00, 0x15, 0x3F, 0x3F,
            0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x2C, 0x00, 0x00, 0x00, 0x17, 0x00, 0x3F, 0x38, 0x1F, 0x3C,
            0x34, 0x1A, 0x39, 0x30, 0x16, 0x36, 0x2C, 0x12, 0x33, 0x28, 0x0E, 0x31, 0x24, 0x0B, 0x2E, 0x20,
            0x08, 0x2B, 0x1D, 0x05, 0x28, 0x19, 0x03, 0x25, 0x16, 0x01, 0x23, 0x13, 0x00, 0x19, 0x27, 0x2E,
            0x18, 0x18, 0x18, 0x1E, 0x1E, 0x1E, 0x0B, 0x0B, 0x0B, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A,
            0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00,
            0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x2A,
            0x1E, 0x20, 0x20, 0x12, 0x12, 0x12, 0x10, 0x10, 0x10, 0x0F, 0x0F, 0x0F, 0x0E, 0x0E, 0x0E, 0x0D,
            0x0D, 0x0D, 0x0C, 0x0C, 0x0C, 0x0B, 0x0B, 0x0B, 0x00, 0x0C, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x08,
            0x00, 0x00, 0x06, 0x00, 0x0B, 0x0C, 0x06, 0x0A, 0x0B, 0x06, 0x09, 0x0A, 0x06, 0x06, 0x06, 0x00,
            0x09, 0x0A, 0x0A, 0x0D, 0x0D, 0x0D, 0x12, 0x12, 0x12, 0x19, 0x19, 0x19, 0x2B, 0x02, 0x00, 0x33,
            0x1A, 0x06, 0x39, 0x2B, 0x0C, 0x3C, 0x33, 0x0F, 0x02, 0x11, 0x03, 0x07, 0x16, 0x06, 0x0F, 0x1B,
            0x0D, 0x2F, 0x19, 0x12, 0x31, 0x31, 0x31, 0x35, 0x35, 0x35, 0x3A, 0x3A, 0x3A, 0x3F, 0x3F, 0x3F,
            0x31, 0x3D, 0x3D, 0x2E, 0x3D, 0x3D, 0x2B, 0x3D, 0x3D, 0x28, 0x3D, 0x3D, 0x22, 0x3D, 0x3D, 0x1A,
            0x3D, 0x3D, 0x17, 0x3B, 0x3D, 0x17, 0x39, 0x3D, 0x16, 0x38, 0x3D, 0x15, 0x37, 0x3D, 0x14, 0x36,
            0x3D, 0x12, 0x34, 0x3D, 0x14, 0x28, 0x12, 0x00, 0x22, 0x0A, 0x00, 0x20, 0x09, 0x00, 0x1F, 0x08,
            0x00, 0x1E, 0x07, 0x00, 0x1D, 0x06, 0x00, 0x1C, 0x05, 0x00, 0x1B, 0x04, 0x0E, 0x18, 0x2D, 0x00,
            0x12, 0x22, 0x00, 0x11, 0x21, 0x00, 0x10, 0x20, 0x00, 0x0F, 0x1F, 0x00, 0x0E, 0x1E, 0x00, 0x0D,
            0x1D, 0x00, 0x0C, 0x1C, 0x0F, 0x23, 0x0E, 0x01, 0x1A, 0x05, 0x00, 0x19, 0x04, 0x00, 0x18, 0x03,
            0x00, 0x17, 0x02, 0x00, 0x16, 0x01, 0x00, 0x15, 0x00, 0x00, 0x14, 0x00, 0x13, 0x27, 0x15, 0x00,
            0x1F, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1A,
            0x00, 0x00, 0x19, 0x00, 0x22, 0x22, 0x22, 0x18, 0x18, 0x17, 0x16, 0x16, 0x16, 0x15, 0x15, 0x15,
            0x14, 0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, 0x11, 0x11, 0x11, 0x11, 0x25, 0x13, 0x00,
            0x1D, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x19, 0x00, 0x00, 0x18,
            0x00, 0x00, 0x17, 0x00, 0x0A, 0x06, 0x00, 0x0E, 0x09, 0x00, 0x0F, 0x0C, 0x00, 0x13, 0x0F, 0x00,
            0x19, 0x14, 0x00, 0x1A, 0x16, 0x00, 0x22, 0x1C, 0x12, 0x26, 0x22, 0x18, 0x2A, 0x2B, 0x1D, 0x20,
            0x21, 0x0A, 0x1E, 0x1F, 0x08, 0x1D, 0x1E, 0x07, 0x1C, 0x1D, 0x06, 0x1B, 0x1C, 0x06, 0x1A, 0x1B,
            0x06, 0x19, 0x1A, 0x06, 0x1B, 0x00, 0x00, 0x15, 0x00, 0x00, 0x20, 0x0A, 0x00, 0x1C, 0x08, 0x00,
            0x04, 0x16, 0x09, 0x06, 0x1A, 0x0B, 0x09, 0x1C, 0x0D, 0x0A, 0x21, 0x2D, 0x06, 0x1C, 0x2E, 0x2B,
            0x29, 0x35, 0x26, 0x25, 0x34, 0x2F, 0x2D, 0x36, 0x00, 0x12, 0x09, 0x38, 0x38, 0x38, 0x00, 0x0E,
            0x08, 0x0A, 0x21, 0x22, 0x06, 0x1C, 0x1C, 0x2B, 0x29, 0x35, 0x26, 0x25, 0x34, 0x2F, 0x2D, 0x36,
            0x00, 0x0E, 0x00, 0x04, 0x15, 0x04, 0x00, 0x18, 0x00, 0x08, 0x1D, 0x08, 0x1E, 0x20, 0x32, 0x2B,
            0x29, 0x35, 0x3F, 0x15, 0x3F, 0x32, 0x00, 0x32, 0x08, 0x0E, 0x00, 0x0A, 0x11, 0x04, 0x11, 0x13,
            0x00, 0x16, 0x18, 0x00, 0x23, 0x23, 0x1D, 0x2A, 0x29, 0x24, 0x3F, 0x15, 0x3F, 0x32, 0x00, 0x32,
            0x03, 0x13, 0x07, 0x05, 0x17, 0x0A, 0x08, 0x1A, 0x0C, 0x0A, 0x1D, 0x0E, 0x1A, 0x2C, 0x3F, 0x14,
            0x28, 0x3F, 0x00, 0x26, 0x3F, 0x3F, 0x15, 0x3F, 0x05, 0x0B, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x12,
            0x00, 0x00, 0x15, 0x00, 0x1D, 0x23, 0x23, 0x1A, 0x1F, 0x1F, 0x16, 0x1A, 0x19, 0x3F, 0x15, 0x3F,
            0x10, 0x14, 0x00, 0x12, 0x18, 0x00, 0x0F, 0x11, 0x00, 0x25, 0x25, 0x2A, 0x21, 0x21, 0x27, 0x21,
            0x21, 0x26, 0x06, 0x0D, 0x00, 0x0B, 0x0F, 0x00, 0x30, 0x30, 0x36, 0x2A, 0x2C, 0x30, 0x27, 0x2A,
            0x2D, 0x29, 0x29, 0x2E, 0x25, 0x25, 0x2B, 0x26, 0x26, 0x2B, 0x30, 0x32, 0x3C, 0x34, 0x35, 0x3F,
            0x00, 0x00, 0x00, 0x00, 0x0C, 0x20, 0x00, 0x20, 0x00, 0x0E, 0x0E, 0x0E, 0x28, 0x00, 0x00, 0x10,
            0x14, 0x00, 0x0A, 0x0A, 0x0A, 0x29, 0x29, 0x29, 0x0F, 0x0F, 0x0F, 0x00, 0x22, 0x2E, 0x00, 0x2C,
            0x00, 0x00, 0x39, 0x39, 0x39, 0x00, 0x00, 0x38, 0x28, 0x00, 0x3F, 0x34, 0x00, 0x3F, 0x3F, 0x3F
        };

        
        int src_pos = 0;
        int dst_pos = 0x36;
        int color;
        for(color=0; color<0x100; color++) {
            Header[dst_pos++] = (byte) (Paleta[src_pos++] << 2);
            Header[dst_pos++] = (byte) (Paleta[src_pos++] << 2);
            Header[dst_pos++] = (byte) (Paleta[src_pos++] << 2);
            dst_pos++;
        }*/

        /*Paleta2 = new byte[] {
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03,
            0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08,
            0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E,
            0x0E, 0x0E, 0x0F, 0x0E, 0x0E, 0x10, 0x0F, 0x0F, 0x11, 0x10, 0x10, 0x12, 0x11, 0x11, 0x13, 0x12,
            0x12, 0x14, 0x13, 0x13, 0x15, 0x14, 0x14, 0x16, 0x15, 0x15, 0x17, 0x16, 0x16, 0x18, 0x17, 0x17,
            0x19, 0x18, 0x18, 0x1A, 0x18, 0x18, 0x1B, 0x19, 0x19, 0x1C, 0x1A, 0x1A, 0x1D, 0x1B, 0x1B, 0x1E,
            0x1C, 0x1C, 0x1F, 0x1D, 0x1D, 0x20, 0x1E, 0x1E, 0x21, 0x1F, 0x1F, 0x22, 0x20, 0x20, 0x23, 0x21,
            0x21, 0x24, 0x22, 0x22, 0x25, 0x23, 0x23, 0x26, 0x24, 0x24, 0x27, 0x24, 0x24, 0x28, 0x25, 0x25,
            0x29, 0x26, 0x26, 0x2A, 0x27, 0x27, 0x2B, 0x28, 0x28, 0x2C, 0x29, 0x29, 0x2D, 0x2A, 0x2A, 0x2E,
            0x2B, 0x2B, 0x2F, 0x2C, 0x2C, 0x30, 0x2D, 0x2D, 0x31, 0x2E, 0x2E, 0x32, 0x2E, 0x2E, 0x33, 0x30,
            0x30, 0x35, 0x30, 0x30, 0x36, 0x31, 0x31, 0x37, 0x32, 0x32, 0x38, 0x33, 0x33, 0x39, 0x34, 0x34,
            0x3A, 0x35, 0x35, 0x3B, 0x36, 0x36, 0x3C, 0x37, 0x37, 0x3D, 0x38, 0x38, 0x3E, 0x39, 0x39, 0x3F
        };

        src_pos = 0;
        dst_pos = 0x36;
        for(color=0; color<64; color++) {
            Header[dst_pos++] = (byte) (Paleta2[src_pos++] << 2);
            Header[dst_pos++] = (byte) (Paleta2[src_pos++] << 2);
            Header[dst_pos++] = (byte) (Paleta2[src_pos++] << 2);
            dst_pos++;
        }*/
    }

    public static bool Save(string filename, byte[] image_data, int width, int height) {
        
        byte[]  output_array;
        int     image_length;
        int     file_size;
        int     width_dword_align_padding;
        int     width_virtual;
        bool    fix_alignment;
        int     n;
        int     pos_src, pos_dst;
        

        width_dword_align_padding = 4 - (width & 3);
        width_virtual = width;
        if(width_dword_align_padding != 4) width_virtual += width_dword_align_padding;
        fix_alignment = width != width_virtual;

        try {
            image_length    = width_virtual * height;
            file_size       = 0x436 + image_length;
            output_array    = new byte[file_size];
        
            // Header
            System.Buffer.BlockCopy(Header, 0, output_array, 0, 0x436);
            // Header File size
            System.Buffer.BlockCopy(BitConverter.GetBytes(file_size), 0, output_array, 2, 4);
            // Header image width
            System.Buffer.BlockCopy(BitConverter.GetBytes(width), 0, output_array, 0x12, 4);
            // Header image height
            System.Buffer.BlockCopy(BitConverter.GetBytes(0 - height), 0, output_array, 0x16, 4);

            if(fix_alignment) {
                pos_src = 0;
                pos_dst = 0x436;
                for(n=0; n<height; n++) {
                    System.Buffer.BlockCopy(image_data, pos_src, output_array, pos_dst, width);
                    pos_src += width;
                    pos_dst += width_virtual;
                }

            } else {
                System.Buffer.BlockCopy(image_data, 0, output_array, 0x436, image_length);
            }

            System.IO.File.WriteAllBytes(filename, output_array);
        } catch {
            return false;
        }

        return true;
    }

}

}

The Codes were written by someone else for me, and I have permission from him, to post them here. The DOS Game uses custom RLE Graphics Encoding. He was unable, to fully explain how the Codes work.

The first Code Program.cs written in C# extracts the Graphics Image, from a Graphics File, and the Bitmap.cs Code i.e. the second one saves the image as a .bmp bitmap File so it can be viewed. According to the person who wrote both Codes, the image width and height is guessed, rather than being known.

However I tried the Codes i.e. unpac Program on a game the previous one made, which also uses Custom RLE Graphics encoding and the Graphics images from .PAC Files don't extract properly. i.e. the Aircraft Cockpit view Image.

What I have tried :-

I tried changing the input bytes in the Program.cs Code, i.e. 0xFF to 0xFE for example in the first part based on what I saw, after opening a File in a Hex Editor its the

input_byte == 0x

parts of the code, which I think need amending.

But nothing useful is extracted, when I use the Program, on the relevant Files from the forerunner game, after making that change. Could someone look at the Program.cs Code for me, i.e. the first one posted by me here ? and suggest what I could change in the Code, to achieve an extracted image, i.e. what I should be looking for when I open relevant Files in a Hex Editor ?

I will post Google Drive links to one File that the cockpit view image that Extracts properly and one that doesn't from the forerunner game. Could someone look at those Files in a Hex editor For me, and suggest what amendments I could make to the Program.cs Code ?

Here are two links the first File extracts properly giving a cockpit view Image, the second one doesn't :-

https://drive.google.com/file/d/1Cr3IV6DkQPYBfPC0HdT6xloFQdimUW12/view?usp=sharing

https://drive.google.com/file/d/15EJUGYYoHPk9VJzNRDgL04LaNZ3XnCEj/view?usp=sharing

I believe it is this part, of the Program.cs Code, that needs changing, i.e. where the problem lies, with unsuccessful extraction of Files, to .bmp format. Could someone look at the File that doesn't extract properly and the one that does, and suggest what possible amendments, I need to make to the two codes I posted, i.e. things to try ? can anyone understand the logic ?

Any help would be much appreciated

Regards

Eddie Winch

2
  • DOS is an 16 bit OS and I don't think there is a .Net or Mono runtime available for DOS. Most likely you wanted to say it is a text-mode application which looks like the DOS command line in Windows 9x but that doesn't make it a "DOS game".
    – Robert
    Oct 23, 2022 at 20:08
  • Hi Robert, the WW2 Aircraft games, were originally made for DOS, many thanks for your comment. Eddie Winch Oct 23, 2022 at 21:10

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.