hostile-takeover/SpiffLib/PdbPacker.cs
2014-07-06 17:47:28 -07:00

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);
}
}
}
}
}