diff --git a/SWBF2Admin/Maps/AddmeReader.cs b/SWBF2Admin/Maps/AddmeReader.cs new file mode 100644 index 0000000..a8e3f40 --- /dev/null +++ b/SWBF2Admin/Maps/AddmeReader.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; +using System.Text; +using SWBF2Admin.Maps.Lua; + +namespace SWBF2Admin.Maps +{ + class AddmeReader + { + private const string UCFB_HEADER = "ucfb"; + + private BinaryReader reader; + + private uint size; + private string name; + private string info; + private uint bodySize; + + public AddmeReader(Stream fs) + { + reader = new BinaryReader(fs); + if (ReadChunkIdentifier() != UCFB_HEADER) + { + throw new Exception("File header mismatch"); + } + size = reader.ReadUInt32(); + + while (NextChunk()) ; + } + + private bool NextChunk() + { + string ci = ReadChunkIdentifier(); + switch (ci) + { + case "scr_": + uint scr_ = reader.ReadUInt32(); //todo + break; + case "NAME": + name = ReadString(); + break; + case "INFO": + info = ReadString(); + break; + case "BODY": + ReadBody(); + break; + } + + return true; + } + + private void Align() + { + while (reader.BaseStream.Position % 4 != 0) + { + long toPad = reader.BaseStream.Position % 4; + reader.BaseStream.Seek(toPad, SeekOrigin.Current); + } + } + + private string ReadString() + { + int len = (int)reader.ReadUInt32(); + string r = DecodeString(reader.ReadBytes(len)); + Align(); + return r; + } + + private string ReadChunkIdentifier() + { + return DecodeString(reader.ReadBytes(4)); + } + + private string DecodeString(byte[] b) + { + return Encoding.ASCII.GetString(b); + } + + private void ReadBody() + { + bodySize = reader.ReadUInt32(); + var l = new LuaVM(reader.BaseStream); + } + + ~AddmeReader() + { + reader.Close(); + } + } +} \ No newline at end of file diff --git a/SWBF2Admin/Maps/Lua/LuaFunction.cs b/SWBF2Admin/Maps/Lua/LuaFunction.cs new file mode 100644 index 0000000..ff88c21 --- /dev/null +++ b/SWBF2Admin/Maps/Lua/LuaFunction.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SWBF2Admin.Maps.Lua +{ + class LuaFunction + { + private List luaAssembly = new List(); + + public LuaFunction(string name, int lineDefined, byte nups, byte numParams, byte variadic, byte maxStackSz) + { + Console.WriteLine("function: n:{0} l:{1} u:{2} p:{3} v:{4} s:{5}", name, lineDefined, nups, numParams, variadic, maxStackSz); + } + + public void PushLine(int line) + { + Console.WriteLine("line: l:{0}", line); + } + + public void PushLocale(string name, int startpc, int endpc) + { + Console.WriteLine("locale: n:{0} s:{1} e:{2} p:{3} v:{4} s:{5}", name, startpc, endpc); + } + + public void PushNup(string name) + { + Console.WriteLine("nup: n:{0}", name); + } + + public void PushConst() + { + Console.WriteLine("const: nil"); + } + + public void PushConst(string str) + { + Console.WriteLine("const: s:{0}", str); + } + + public void PushConst(float f) + { + Console.WriteLine("const: f:{0}", f); + } + + public void PushNested(LuaFunction f) + { + + } + + public void PushCode(byte[] code) + { + luaAssembly.Add(new LuaInstruction(code)); + } + } +} diff --git a/SWBF2Admin/Maps/Lua/LuaInstruction.cs b/SWBF2Admin/Maps/Lua/LuaInstruction.cs new file mode 100644 index 0000000..cd627ab --- /dev/null +++ b/SWBF2Admin/Maps/Lua/LuaInstruction.cs @@ -0,0 +1,110 @@ +using System; + +namespace SWBF2Admin.Maps.Lua +{ + enum LuaOpcode : byte + { + MOVE, + LOADK, + LOADBOOL, + LOADNIL, + GETUPVAL, + GETGLOBAL, + GETTABLE, + SETGLOBAL, + SETTABLE, + NEWTABLE, + SELF, + ADD, + SUB, + MUL, + DIV, + POW, + UNM, + NOT, + CONCAT, + JMP, + EQ, + LT, + LE, + TEST, + CALL, + TAILCALL, + RETURN, + FORLOOP, + TFORLOOP, + TFORPREP, + SETLIST, + SETLISTO, + CLOSE, + CLOSURE + } + class LuaInstruction + { + public LuaOpcode OpCode { get; set; } + public int A { get; set; } + public int B { get; set; } + public int C { get; set; } + + public LuaInstruction(byte[] instr) + { + OpCode = (LuaOpcode)extract(instr, 0, 6); + A = extract(instr, 6, 8); + C = 0; + + switch (OpCode) + { + //iABx + case LuaOpcode.LOADK: + case LuaOpcode.GETGLOBAL: + case LuaOpcode.SETGLOBAL: + case LuaOpcode.SETLIST: + case LuaOpcode.SETLISTO: + case LuaOpcode.CLOSURE: + B = extract(instr, 14, 18); + break; + + //iAsBx + case LuaOpcode.JMP: + case LuaOpcode.FORLOOP: + case LuaOpcode.TFORPREP: + B = EvaluateLuaSignBit(extract(instr, 14, 18)); + break; + + //iABC + default: + C = EvaluateLuaSignBit(extract(instr, 14, 9)); + B = EvaluateLuaSignBit(extract(instr, 23, 9)); + + break; + } + Console.WriteLine("{0}\tA:{1} B:{2} C:{3}", OpCode, A, B, C); + } + + private static int EvaluateLuaSignBit(int i) + { + if ((i & (1 << 9)) != 0) + { + i ^= (1 << 9); + i *= -1; + } + return i; + } + + private int extract(byte[] instr, int pos, int sz) + { + int res = 0; + int i = pos; + int ctx = 0; + + while (i < pos + sz) + { + bool bit = (instr[i / 8] & (1 << (i % 8))) > 0; + res |= ((int)(bit ? 1 : 0) << ctx); + ctx++; + i++; + } + return res; + } + } +} diff --git a/SWBF2Admin/Maps/Lua/LuaVM.cs b/SWBF2Admin/Maps/Lua/LuaVM.cs new file mode 100644 index 0000000..882cfd8 --- /dev/null +++ b/SWBF2Admin/Maps/Lua/LuaVM.cs @@ -0,0 +1,184 @@ +using System; +using System.IO; +using System.Text; + + +namespace SWBF2Admin.Maps.Lua +{ + struct LuaVMConfig + { + public byte + littleEndian, + intSz, + sizeTSz, + instrSz, + opSz, + opASz, + opBSz, + opCSz, + luaNumSz; + } + + enum LUA_T : byte + { + LUA_TNIL = 0, + LUA_TNUMBER = 3, + LUA_TSTRING = 4 + } + + + class LuaVM + { + private byte[] LUA_SIGNATURE = new byte[] { 0x1b, 0x4c, 0x75, 0x61 }; + private const float LUA_TEST_NUM = 3.14159265358979323846E7f; + + private BinaryReader reader; + private LuaVMConfig config; + + public LuaVM(Stream fs) + { + reader = new BinaryReader(fs); + + byte[] signature = reader.ReadBytes(LUA_SIGNATURE.Length); + for (int i = 0; i < signature.Length; i++) + { + if (signature[i] != LUA_SIGNATURE[i]) throw new Exception("lua magic mismatch"); + } + + byte version = reader.ReadByte(); + if (version != 0x50) throw new Exception("lua version mismatch"); + + config = ReadLuaVMConfig(); + //todo: validate remote vm config against local config + + if (ReadLuaNumber() != LUA_TEST_NUM) + { + throw new Exception("lua number mismatch"); + } + + + LoadFunction(); + } + + private LuaVMConfig ReadLuaVMConfig() + { + LuaVMConfig config = new LuaVMConfig() + { + littleEndian = ReadByte(), + intSz = ReadByte(), + sizeTSz = ReadByte(), + instrSz = ReadByte(), + opSz = ReadByte(), + //register sizes for A,B,C + opASz = ReadByte(), + opBSz = ReadByte(), + opCSz = ReadByte(), + luaNumSz = ReadByte(), + }; + return config; + } + + private LuaFunction LoadFunction() + { + LuaFunction def = new LuaFunction( + LoadString(), + ReadInt(), + ReadByte(), + ReadByte(), + ReadByte(), + ReadByte() + ); + + //lines + int nLines = ReadInt(); + for (int i = 0; i < nLines; i++) + { + def.PushLine(ReadInt()); + } + + //locals + int nLocals = ReadInt(); + for (int i = 0; i < nLocals; i++) + { + def.PushLocale(LoadString(), ReadInt(), ReadInt()); + } + + //upvalues + int nUpvalues = ReadInt(); + for (int i = 0; i < nUpvalues; i++) + { + def.PushNup(LoadString()); + } + + //consants + int nConst = ReadInt(); + for (int i = 0; i < nConst; i++) + { + LUA_T type = (LUA_T)ReadByte(); + switch (type) + { + case LUA_T.LUA_TNUMBER: + def.PushConst(ReadLuaNumber()); + break; + case LUA_T.LUA_TSTRING: + def.PushConst(LoadString()); + break; + case LUA_T.LUA_TNIL: + def.PushConst(); + break; + default: + throw new Exception("invalid const type"); + } + } + + int nNested = ReadInt(); + for (int i = 0; i < nNested; i++) + { + def.PushNested(LoadFunction()); + } + + int nCode = ReadInt(); + for (int i = 0; i < nCode; i++) + { + def.PushCode(ReadBlock(config.instrSz)); + } + + return def; + } + + private byte[] ReadBlock(int sz) + { + return reader.ReadBytes(sz); + } + + private float ReadLuaNumber() + { + //lua 5.0 standard would use double instead of float + byte[] number = ReadBlock(config.luaNumSz); + return BitConverter.ToSingle(number, 0); + } + + private byte ReadByte() + { + return ReadBlock(1)[0]; + } + + private int ReadSize() + { + byte[] sz = ReadBlock(config.sizeTSz); + return BitConverter.ToInt32(sz, 0); + } + + private int ReadInt() + { + byte[] sz = ReadBlock(config.intSz); + return BitConverter.ToInt32(sz, 0); + } + private string LoadString() + { + int strLen = ReadSize(); + byte[] str = ReadBlock(strLen); + return Encoding.ASCII.GetString(str); + } + } +} \ No newline at end of file diff --git a/SWBF2Admin/SWBF2Admin.csproj b/SWBF2Admin/SWBF2Admin.csproj index dfc477f..b97a3b0 100644 --- a/SWBF2Admin/SWBF2Admin.csproj +++ b/SWBF2Admin/SWBF2Admin.csproj @@ -132,6 +132,10 @@ + + + +