Skip to content

Instantly share code, notes, and snippets.

@donnaken15
Created February 24, 2024 07:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donnaken15/47f2a31f8c8b9b7743a2e184a9694a37 to your computer and use it in GitHub Desktop.
Save donnaken15/47f2a31f8c8b9b7743a2e184a9694a37 to your computer and use it in GitHub Desktop.
pakdir, before hardcore EXE optimization
/*
* Created by SharpDevelop.
* User: Wesley
* Date: 2/21/2024
* Time: 3:55 PM
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
using System;
using System.IO;
static class Program
{
public static byte[] STH(string hex)
{
if ((hex.Length & 1) == 1)
throw new ArgumentException("Uneven hex string: "+hex);
byte[] test = new byte[hex.Length >> 1];
for (int i = 0; i < test.Length; i++)
test[i] = byte.Parse(hex.Substring(i << 1, 2), System.Globalization.NumberStyles.HexNumber);
return test;
}
public static uint[] crctable = new uint[256];
public static void initCRC32()
{
uint crc, poly = 0xEDB88320;
for (uint i = 0; i < 256; i++)
{
crc = i;
for (int j = 0; j < 8; j++)
{
if ((crc & 1) == 1)
crc = crc >> 1 ^ poly;
else
crc >>= 1;
}
crctable[i] = crc;
}
}
public static uint crc32(string text)
{
text = text.Replace('/', '\\').ToLower();
if (text.Length == 8)
try {
byte[] alreadykey = STH(text);
Array.Reverse(alreadykey);
return BitConverter.ToUInt32(alreadykey, 0);
}
catch { }
uint crc = 0xFFFFFFFF;
for (int i = 0; i < text.Length; i++)
{
crc = crc >> 8 & 0x00FFFFFF ^ crctable[(crc ^ text[i]) & 0xFF];
}
return crc;
}
static readonly string[] exts = {
"", ".xen", ".ps3", ".ps2", ".ngc"
};
static int iOZ(string[] arr, string path)
// test for strings in array
{
int ii = -1;
for (int i = 0; i < exts.Length; i++)
if ((ii = Array.IndexOf(arr, path + exts[i])) != -1)
return ii;
return ii;
}
static uint Eswap(uint value)
{
return ((value & 0xFF) << 24) |
((value & 0xFF00) << 8) |
((value & 0xFF0000) >> 8) |
((value & 0xFF000000) >> 24);
}
static string ext(bool b)
{
return ".pa" + (b ? 'b' : 'k') + ".xen";
}
static uint align(uint a, uint b)
{
b = (uint)unchecked(1<<(int)b);
a = a&(uint)(b-1);
return (a!=0) ? (b-a) : 0;
}
static bool platext(string i)
{
string e = Path.GetExtension(i);
return e == ".xen" ||
e == ".ps3" ||
e == ".ps2" ||
e == ".ngc";
}
// main data boundary is 4096 = 1 << 12
// file data aligns to 16 = 1 << 4
static void AlignFile(BinaryWriter w, uint b)
{
b = (uint)unchecked(1<<(int)b);
uint a = (uint)w.BaseStream.Position & (uint)(b-1);
if (a != 0)
{
byte[] pad = new byte[b-a];
w.Write(pad);
}
}
public static int Main(string[] args)
{
if (args.Length <= 0 && args.Length > 3)
{
//Console.WriteLine("GH3 PAK BUILDER");
Console.WriteLine("usage: pakdir [input folder] [output name] [-b|-z]");
Console.WriteLine(" -b | split output to PAK/PAB");
Console.WriteLine(" -z | build zones, requires TEX/SCN and implies -b");
return 2;
}
if (!Directory.Exists(args[0]))
{
Console.WriteLine("Input folder not found: "+Directory.GetCurrentDirectory()+'\\'+args[0]);
return 1;
}
string output = Path.GetFullPath(args[1]);
string name = Path.GetFileName(args[1]);
Directory.SetCurrentDirectory(args[0]);
bool c_pab = false, c_zone = false;
const string zstr = "zones\\global\\global_gfx";
if (args.Length == 3)
{
string _switch = args[2];
if (_switch.Length != 2 || _switch[0] != '-')
{
// WHY CAN'T I GOTO
Console.WriteLine("Invalid switch string: "+_switch);
return 1;
}
switch (_switch[1])
{
case 's':
c_pab = true;
break;
case 'z':
c_zone = true;
break;
default:
Console.WriteLine("Invalid switch string: "+_switch);
return 1;
}
}
initCRC32();
string[] dir = Directory.GetFiles(".", "*", SearchOption.AllDirectories);
uint filecount = (uint)dir.Length;
uint wadstart = (uint)((filecount + 2) * 0x20);
uint wadoff = wadstart;
//wadstart = wadstart + align(wadstart, 12);
//Console.WriteLine(wadstart.ToString("X8"));
//return;
int scn = -1, tex = -1;
if (c_zone)
{
scn = iOZ(dir, ".\\" + zstr + ".scn");
tex = iOZ(dir, ".\\" + zstr + ".tex");
if (scn < 0 || tex < 0)
{
Console.WriteLine("Matching TEX/SCN cannot be found. It is required for building zone PAKs.");
return 1;
}
if (scn != 1 && tex != 1)
{
if (tex > scn)
{
string swapval = dir[tex];
dir[tex] = dir[scn];
dir[scn] = swapval;
}
// apparently TEX has to come first
}
}
Stream pak = File.Create(output + ext(false));
Stream pab = null;
if (c_zone)
c_pab = true;
if (c_pab)
{
pab = File.Create(output + ext(true));
}
BinaryWriter hed = new BinaryWriter(pak);
BinaryWriter wad = new BinaryWriter(c_pab ? pab : pak);
for (uint i = 0; i < filecount; i++)
{
//Console.WriteLine(dir[i]);
FileInfo f = new FileInfo(dir[i].Substring(2));
bool subf = Path.GetDirectoryName(f.ToString()) != "";
bool _platext = platext(f.ToString());
string name_noplat = Path.GetFileNameWithoutExtension(f.ToString());
string _ext = _platext ?
Path.GetExtension(name_noplat) : f.Extension;
string fn = _platext ? name_noplat : f.Name;
hed.Write(Eswap(crc32(_ext)));
hed.Write(Eswap(wadoff));
hed.Write(Eswap((uint)f.Length));
hed.Write((uint)0);
if (!(_ext == ".tex" || _ext == ".scn")) // wtf
fn = Path.GetFileNameWithoutExtension(fn);
string realpath = (subf ? (Path.GetDirectoryName(f.ToString()) + '\\') : "") + fn;
//Console.WriteLine(realpath);
//Console.WriteLine(fn);
hed.Write(Eswap(crc32(realpath)));
hed.Write(Eswap(crc32(fn)));
if (c_zone && i == tex)
hed.Write(Eswap(crc32(zstr + ".tex")));
else
hed.Write((uint)0);
hed.Write(Eswap((uint)((i == tex || i == scn) ? 4 : 0)));
wadoff += (uint)f.Length + align((uint)f.Length, 4) - 0x20;
}
hed.Write(Eswap(crc32(".last")));
hed.Write(Eswap(wadoff));
hed.Write(Eswap(4));
hed.Write((uint)0);
hed.Write(Eswap(0x897ABB4A));
hed.Write(Eswap(0x6AF98ED1));
hed.Write((uint)0);
hed.Write((uint)0);
for (byte i = 0; i < 8; i++)
hed.Write(0);
for (uint i = 0; i < filecount; i++)
{
wad.Write(File.ReadAllBytes(dir[i]));
AlignFile(wad, 4);
}
wad.Write(0xABABABAB);
AlignFile(wad, 4);
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment