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