mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-23 06:57:23 +00:00
338 lines
8.9 KiB
C#
338 lines
8.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
|
|
namespace SpiffLib {
|
|
public class PdbPacker {
|
|
static int s_cbRecordMax = 32000;
|
|
static int s_cbFilenameMax = 29;
|
|
ArrayList m_alsFiles = new ArrayList();
|
|
|
|
public class File {
|
|
public string str;
|
|
public byte[] ab;
|
|
public bool fCompress;
|
|
int m_cbCompressed;
|
|
|
|
public File() {
|
|
}
|
|
|
|
public File(string strT, byte[] abT, bool fCompressT, int cbCompressed) {
|
|
str = strT;
|
|
ab = abT;
|
|
fCompress = fCompressT;
|
|
m_cbCompressed = cbCompressed;
|
|
}
|
|
|
|
public File(string strT, byte[] abT, bool fCompressT) {
|
|
str = strT;
|
|
ab = abT;
|
|
fCompress = fCompressT;
|
|
}
|
|
|
|
public File(string strT, byte[] abT) {
|
|
str = strT;
|
|
ab = abT;
|
|
fCompress = true;
|
|
}
|
|
|
|
public int GetCompressedSize() {
|
|
return m_cbCompressed;
|
|
}
|
|
}
|
|
|
|
public PdbPacker() {
|
|
}
|
|
|
|
public PdbPacker(string strFile) {
|
|
Load(strFile);
|
|
}
|
|
|
|
public int Count {
|
|
get {
|
|
return m_alsFiles.Count;
|
|
}
|
|
}
|
|
|
|
public void Add(File file) {
|
|
m_alsFiles.Add(file);
|
|
}
|
|
|
|
public File this[string strFile] {
|
|
get {
|
|
foreach (File file in m_alsFiles) {
|
|
if (file.str.ToLower() == strFile.ToLower())
|
|
return file;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public File this[int iFile] {
|
|
get {
|
|
return (File)m_alsFiles[iFile];
|
|
}
|
|
}
|
|
|
|
struct DirEntry {
|
|
public string strFile;
|
|
public byte crec;
|
|
public ushort irec;
|
|
};
|
|
|
|
public void Load(string strFile) {
|
|
// Open palm database
|
|
|
|
PalmDatabase pdb = new PalmDatabase();
|
|
pdb.Load(strFile);
|
|
|
|
// Load directory entries
|
|
|
|
ArrayList alsRecordData = pdb.GetRecordData();
|
|
CompressionHeader coh;
|
|
DirEntry[] ade = ParseDirectoryRecord(UnpackRecord((byte[])alsRecordData[0], out coh));
|
|
|
|
// Read in the file data
|
|
|
|
m_alsFiles.Clear();
|
|
foreach (DirEntry de in ade) {
|
|
// Decompress and write out bytes
|
|
|
|
ArrayList alsFileBytes = new ArrayList();
|
|
for (int i = 0; i < de.crec; i++) {
|
|
byte[] ab = UnpackRecord((byte[])alsRecordData[i + de.irec], out coh);
|
|
alsFileBytes.AddRange(ab);
|
|
}
|
|
|
|
File file = new File(de.strFile, (byte[])alsFileBytes.ToArray(typeof(byte)), coh.fCompressed, coh.cbCompressed);
|
|
m_alsFiles.Add(file);
|
|
}
|
|
}
|
|
|
|
// Straight ascii order comparer, since at runtime we use a strcmp based binary search
|
|
|
|
public class FilenameComparer : IComparer {
|
|
public int Compare(object objA, object objB) {
|
|
string strA = (string)objA;
|
|
string strB = (string)objB;
|
|
int cch = Math.Min(strA.Length, strB.Length);
|
|
for (int ich = 0; ich < cch; ich++) {
|
|
if (strA[ich] < strB[ich])
|
|
return -1;
|
|
if (strA[ich] > strB[ich])
|
|
return 1;
|
|
}
|
|
|
|
if (strA.Length < strB.Length)
|
|
return -1;
|
|
if (strA.Length > strB.Length)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public void Save(string strFilePdb, string strCreatorId, string strTypeId) {
|
|
// First sort all the files by name
|
|
|
|
string[] astrFileNames = new string[m_alsFiles.Count];
|
|
for (int iFile = 0; iFile < m_alsFiles.Count; iFile++) {
|
|
File file = (File)m_alsFiles[iFile];
|
|
astrFileNames[iFile] = file.str.ToLower();
|
|
}
|
|
File[] afile = (File[])m_alsFiles.ToArray(typeof(File));
|
|
Array.Sort(astrFileNames, afile, new FilenameComparer());
|
|
|
|
// Create the record bytes
|
|
|
|
ArrayList alsDirEntries = new ArrayList();
|
|
ArrayList alsRecords = new ArrayList();
|
|
ushort irec = 1;
|
|
foreach (File file in afile) {
|
|
// Get filename only, check length
|
|
|
|
string strFile = file.str.ToLower();
|
|
if (strFile.Length >= s_cbFilenameMax)
|
|
throw new Exception("The file " + strFile + " is too long. Must be " + (s_cbFilenameMax - 1) + "chars max.");
|
|
|
|
// Read the file into chunks
|
|
|
|
BinaryReader brdr = new BinaryReader(new MemoryStream(file.ab));
|
|
byte crec = 0;
|
|
while (brdr.BaseStream.Position < brdr.BaseStream.Length) {
|
|
int cbLeft = (int)(brdr.BaseStream.Length - brdr.BaseStream.Position);
|
|
int cbRead = cbLeft < s_cbRecordMax ? cbLeft : s_cbRecordMax;
|
|
byte[] abChunk = brdr.ReadBytes(cbRead);
|
|
alsRecords.Add(PackRecord(abChunk, file.fCompress));
|
|
crec++;
|
|
}
|
|
brdr.Close();
|
|
|
|
// Make a directory entry for this file
|
|
|
|
DirEntry de = new DirEntry();
|
|
de.strFile = strFile;
|
|
de.irec = irec;
|
|
de.crec = crec;
|
|
alsDirEntries.Add(de);
|
|
irec += de.crec;
|
|
|
|
#if false
|
|
Console.WriteLine(de.strFile + ", " + de.crec + " recs");
|
|
#endif
|
|
}
|
|
|
|
// Insert the record for the directory entries
|
|
|
|
alsRecords.Insert(0, PackRecord(MakeDirectoryRecord(alsDirEntries), false));
|
|
|
|
// Create and save a .pdb around these records
|
|
|
|
PalmDatabase pdb = new PalmDatabase();
|
|
pdb.SetRecordData(alsRecords);
|
|
pdb.Name = Path.GetFileName(Path.GetFullPath(strFilePdb));
|
|
pdb.CreatorId = IdFromString(strCreatorId);
|
|
pdb.TypeId = IdFromString(strTypeId);
|
|
pdb.Save(strFilePdb);
|
|
}
|
|
|
|
static byte[] PackRecord(byte[] abChunk, bool fCompress) {
|
|
ArrayList alsRecordBytes = new ArrayList();
|
|
byte[] abCompressed = null;
|
|
if (fCompress) {
|
|
abCompressed = Compressor.CompressChunk(abChunk);
|
|
if (abCompressed.Length >= abChunk.Length)
|
|
abCompressed = null;
|
|
}
|
|
if (abCompressed != null) {
|
|
alsRecordBytes.Add((byte)0);
|
|
alsRecordBytes.Add((byte)1);
|
|
alsRecordBytes.Add((byte)((abChunk.Length >> 8) & 0xff));
|
|
alsRecordBytes.Add((byte)(abChunk.Length & 0xff));
|
|
alsRecordBytes.Add((byte)((abCompressed.Length >> 8) & 0xff));
|
|
alsRecordBytes.Add((byte)(abCompressed.Length & 0xff));
|
|
alsRecordBytes.AddRange(abCompressed);
|
|
} else {
|
|
alsRecordBytes.Add((byte)0);
|
|
alsRecordBytes.Add((byte)0);
|
|
alsRecordBytes.Add((byte)((abChunk.Length >> 8) & 0xff));
|
|
alsRecordBytes.Add((byte)(abChunk.Length & 0xff));
|
|
alsRecordBytes.Add((byte)((abChunk.Length >> 8) & 0xff));
|
|
alsRecordBytes.Add((byte)(abChunk.Length & 0xff));
|
|
alsRecordBytes.AddRange(abChunk);
|
|
}
|
|
return (byte[])alsRecordBytes.ToArray(typeof(byte));
|
|
}
|
|
|
|
struct CompressionHeader {
|
|
public bool fCompressed;
|
|
public ushort cbUncompressed;
|
|
public ushort cbCompressed;
|
|
}
|
|
|
|
static byte[] UnpackRecord(byte[] ab, out CompressionHeader coh) {
|
|
coh.fCompressed = (ab[1] == 1);
|
|
coh.cbUncompressed = (ushort)((ab[2] << 8) + ab[3]);
|
|
coh.cbCompressed = (ushort)((ab[4] << 8) + ab[5]);
|
|
|
|
byte[] abT = new byte[ab.Length - 6];
|
|
Array.Copy(ab, 6, abT, 0, abT.Length);
|
|
if (coh.fCompressed) {
|
|
ab = Compressor.DecompressChunk(abT);
|
|
} else {
|
|
ab = abT;
|
|
}
|
|
|
|
return ab;
|
|
}
|
|
|
|
static uint IdFromString(string str) {
|
|
str.PadLeft(4, ' ');
|
|
uint ui = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
ui <<= 8;
|
|
ui |= (uint)str[i];
|
|
}
|
|
return ui;
|
|
}
|
|
|
|
static byte[] MakeDirectoryRecord(ArrayList alsDirEntries) {
|
|
// Each entry is 32 bytes. Serialize into a byte array.
|
|
//
|
|
// struct DirEntry {
|
|
// char szFn[kcbFilename];
|
|
// byte crec;
|
|
// word irec;
|
|
// };
|
|
|
|
ArrayList alsPdbDirEntries = new ArrayList();
|
|
foreach (DirEntry de in alsDirEntries) {
|
|
byte[] abFilename = new byte[s_cbFilenameMax];
|
|
for (int ich = 0; ich < de.strFile.Length; ich++)
|
|
abFilename[ich] = (byte)de.strFile[ich];
|
|
alsPdbDirEntries.AddRange(abFilename);
|
|
alsPdbDirEntries.Add((byte)de.crec);
|
|
alsPdbDirEntries.Add((byte)((de.irec >> 8) & 0xff));
|
|
alsPdbDirEntries.Add((byte)(de.irec & 0xff));
|
|
}
|
|
return (byte[])alsPdbDirEntries.ToArray(typeof(byte));
|
|
}
|
|
|
|
static DirEntry[] ParseDirectoryRecord(byte[] ab) {
|
|
ArrayList alsPdbDirEntries = new ArrayList();
|
|
for (int ib = 0; ib < ab.Length; ib += 32) {
|
|
DirEntry de;
|
|
de.strFile = "";
|
|
for (int ich = 0; ich < s_cbFilenameMax; ich++) {
|
|
char ch = (char)ab[ib + ich];
|
|
if (ch == 0)
|
|
break;
|
|
de.strFile += ch;
|
|
}
|
|
de.crec = (byte)ab[ib + s_cbFilenameMax];
|
|
de.irec = (ushort)((ab[ib + s_cbFilenameMax + 1] << 8) + ab[ib + s_cbFilenameMax + 2]);
|
|
alsPdbDirEntries.Add(de);
|
|
}
|
|
return (DirEntry[])alsPdbDirEntries.ToArray(typeof(DirEntry));
|
|
}
|
|
|
|
public static void MergePdbs(PalmDatabase pdb, PalmDatabase [] apdb)
|
|
{
|
|
// Create directory entry.
|
|
|
|
int irec = pdb.Count + 1; // plus one for directory
|
|
|
|
ArrayList alsPdbDirEntries = new ArrayList();
|
|
for (int ipdb = 0; ipdb < apdb.Length; ipdb++) {
|
|
|
|
byte [] abFilename = new byte[28];
|
|
for (int ich = 0; ich < apdb[ipdb].Name.Length; ich++)
|
|
abFilename[ich] = (byte)apdb[ipdb].Name[ich];
|
|
|
|
alsPdbDirEntries.AddRange(abFilename);
|
|
alsPdbDirEntries.Add((byte)((irec >> 8) & 0xff));
|
|
alsPdbDirEntries.Add((byte)(irec & 0xff));
|
|
alsPdbDirEntries.Add((byte)((apdb[ipdb].Count >> 8) & 0xff));
|
|
alsPdbDirEntries.Add((byte)(apdb[ipdb].Count & 0xff));
|
|
|
|
irec += apdb[ipdb].Count;
|
|
}
|
|
|
|
alsPdbDirEntries.Add((byte)0); // end of directory marker
|
|
|
|
uint uiType = 0x5041434b; // PACK
|
|
|
|
irec = 0;
|
|
pdb.Add((byte[])alsPdbDirEntries.ToArray(typeof(byte)), (ushort)irec, uiType);
|
|
|
|
for (int ipdb = 0; ipdb < apdb.Length; ipdb++) {
|
|
for (int irecAdd = 0; irecAdd < apdb[ipdb].Count; irecAdd++) {
|
|
irec += 1;
|
|
pdb.Add(apdb[ipdb][irecAdd].Data, (ushort)irec, uiType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|