From 53ffd4eeea1518b5c7cbc2cce8580c7a4189c3bf Mon Sep 17 00:00:00 2001 From: Li Date: Sun, 8 Jan 2023 18:54:06 -0800 Subject: [PATCH] Implement Compression --- LibXom/Data/XomCompressor.cs | 127 ++++++++++++++++++----------------- LibXom/Data/XomContainer.cs | 67 +++++++++++++----- Worms4Editor/Program.cs | 8 +-- 3 files changed, 114 insertions(+), 88 deletions(-) diff --git a/LibXom/Data/XomCompressor.cs b/LibXom/Data/XomCompressor.cs index 1b142ee..f1fb11b 100644 --- a/LibXom/Data/XomCompressor.cs +++ b/LibXom/Data/XomCompressor.cs @@ -1,7 +1,9 @@ using LibXom.Exceptions; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Runtime.Intrinsics; using System.Text; using System.Threading.Tasks; @@ -9,6 +11,15 @@ namespace LibXom.Data { public class XomCompressor { + private static int getNumberByteCount(int num) + { + uint unum = Convert.ToUInt32(num); + if (unum <= 0xFF) return 1; + if (unum <= 0xFFFF) return 2; + if (unum <= 0xFFFFFF) return 3; + if (unum <= 0xFFFFFFFF) return 4; + throw new XomException("Number is too large or too small."); + } internal static int readCompressedIntFromStream(MemoryStream ms) { @@ -24,6 +35,21 @@ namespace LibXom.Data return DecompressInt(BitConverter.ToInt32(buffer)); } + internal static byte[] compressBuffer(int[] input) + { + using(MemoryStream ms = new MemoryStream()) + { + foreach(int enc in input) + { + int c = CompressInt(enc); + int sz = getNumberByteCount(c); + byte[] buffer = BitConverter.GetBytes(c); + ms.Write(buffer, 0x00, sz); + } + ms.Seek(0, SeekOrigin.Begin); + return ms.ToArray(); + } + } internal static int[] decompressBuffer(byte[] input) { List decompressedData = new List(); @@ -39,78 +65,53 @@ namespace LibXom.Data } return decompressedData.ToArray(); } - private static int getNumberByteCount(int num) - { - uint unum = Convert.ToUInt32(num); - if (unum <= 0xFF) return 1; - if (unum <= 0xFFFF) return 2; - if (unum <= 0xFFFFFF) return 3; - if (unum <= 0xFFFFFFFF) return 4; - throw new XomException("Number is too large or too small."); - } private static int pow(int val, int pow) { return Convert.ToInt32(Math.Pow(val, pow)); } - // TODO: Write the inverse of this; "CompressInt()" - public static int DecompressInt(int compressedInt) + private static int floor(double val) { - int b1 = (compressedInt & 0xFF); - int b2 = (compressedInt >> 8) & 0xFF; - int b3 = (compressedInt >> 16) & 0xFF; - int b4 = (compressedInt >> 24) & 0xFF; - - return (b4 * pow(0x80, 3)) + ((b3 % 0x80) * pow(0x80, 0x2)) + ((b2 % 0x80) * pow(0x80, 1)) + (b1 % 0x80); + return Convert.ToInt32(Math.Floor(val)); } - public static int NextCompressedInterger(int compressedInt) + private static int minusButNot0(int val, int minus) { - // Some parts of XOM follow a particular pattern, + int nval = val - minus; + if (nval < 0) nval = 0; + return nval; + } + public static int CompressInt(int decompressedInt) + { + int val = decompressedInt; + // Calculate each byte + int b4 = floor(val / pow(0x80, 3)); + val -= minusButNot0(b4, 1) * pow(0x80, 3); + int b3 = floor(val / pow(0x80, 2)); + val -= minusButNot0(b3, 1) * pow(0x80, 2); + int b2 = floor(val / pow(0x80, 1)); + val -= minusButNot0(b2, 1) * pow(0x80, 1); + int b1 = val; + + // rebuild int from bytes + int compressedInt = 0; + compressedInt = (compressedInt << 8) | b4; + compressedInt = (compressedInt << 8) | b3; + compressedInt = (compressedInt << 8) | b2; + compressedInt = (compressedInt << 8) | b1; + + return compressedInt; + } + + public static int DecompressInt(int compressedInt) + { + // Extract each byte from the int + int b1 = (compressedInt >> 0) & 0xFF; + int b2 = (compressedInt >> 8) & 0xFF; + int b3 = (compressedInt >> 16) & 0xFF; + int b4 = (compressedInt >> 24) & 0xFF; - // 0x7b, 0x7d, 0x7e, 0x7f, 0x180 - // 0x1fb, 0x1fd, 0x1fe, 0x1ff 0x280 - // 0x2fb, 0x2fd, 0x2fe, 0x2ff 0x380 - - // This function will take any given int, - // and give you the next following this sequence. - - // This is done by XOM to "compress" intergers, - // you can read a int and, if its > 0x7f you know theres another byte to be read - // otherwise you've read the entire interger, and can read the next one - - // add 1 to current int - int nextId = compressedInt + 1; - int byteCount = getNumberByteCount(nextId); - - // if its 0, just return 0 - if (nextId <= 0) return nextId; - - // if its a multiple of 0x80.. - if (nextId % 0x8000 == 0) - { - int div = nextId / 0x8000; - // check if that divisor is also divisible by 2 - if (div % 0x2 == 0) - nextId += (0x8000); // add 0x8000 - } - if (nextId % 0x80 == 0) - { - int div = nextId / 0x80; - // check if that divisor is also divisible by 2 - if (div % 0x2 == 0) - nextId += (0x80); // add 0x80 - } - - byte[] buffer = BitConverter.GetBytes(nextId); - for(int i = 0; i < 4; i++) - { - if (buffer[i] > 0x7F) continue; - if (buffer[i] == 0) buffer[i]++; - break; - } - nextId = BitConverter.ToInt32(buffer); - - return nextId; + // calculate full number from bytes + return (b4 * pow(0x80, 3)) + ((b3 % 0x80) * pow(0x80, 0x2)) + ((b2 % 0x80) * pow(0x80, 0x1)) + (b1 % 0x80); } } } diff --git a/LibXom/Data/XomContainer.cs b/LibXom/Data/XomContainer.cs index e900d7f..5ac5ac5 100644 --- a/LibXom/Data/XomContainer.cs +++ b/LibXom/Data/XomContainer.cs @@ -12,6 +12,13 @@ namespace LibXom.Data private string typeBelongs; internal byte[] data; + internal XomContainer(XomFile fromFile, string fromType, byte[] data) + { + fileBelongs = fromFile; + typeBelongs = fromType; + this.data = data; + } + public XomType Type { get @@ -34,6 +41,48 @@ namespace LibXom.Data { this.Type.ReplaceContainerData(this, data); } + + // Compress method, convert bytes to int array, then to compressed byte array. + public byte[] Compress(byte[] bytes) + { + return this.Compress(byteArrayToIntArray(bytes)); + } + // Compress method, convert nums to compressed byte array. + public byte[] Compress(int[] nums) + { + byte[] buffer = XomCompressor.compressBuffer(nums); + byte[] compressedData = new byte[buffer.Length + 3]; + Array.ConstrainedCopy(buffer, 0, compressedData, 3, compressedData.Length); + return compressedData; + } + public void CompressAndUpdate(byte[] bytes) + { + this.SetData(this.Compress(bytes)); + } + public void CompressAndUpdate(int[] nums) + { + this.SetData(this.Compress(nums)); + } + // Decompress methods, decompress all data, (eg for WeaponFactoryCollective, or StoredStatsCollective) + public int[] Decompress() + { + byte[] compressedData = new byte[data.Length - 3]; + Array.ConstrainedCopy(data, 3, compressedData, 0, compressedData.Length); + return XomCompressor.decompressBuffer(compressedData); + } + // Decompress to a byte array. + public byte[] DecompressToBytes() + { + return intArrayToByteArray(this.Decompress()); + } + private int[] byteArrayToIntArray(byte[] byteArray) + { + int[] intArray = new int[byteArray.Length / 4]; + for (int i = 0; i < intArray.Length; i++) + intArray[i] = BitConverter.ToInt32(byteArray, i * 4); + return intArray; + } + private byte[] intArrayToByteArray(int[] intArray) { using (MemoryStream ms = new MemoryStream()) @@ -48,23 +97,5 @@ namespace LibXom.Data return ms.ToArray(); } } - - public int[] Decompress() - { - byte[] compressedData = new byte[data.Length - 3]; - Array.ConstrainedCopy(data, 3, compressedData, 0, compressedData.Length); - return XomCompressor.decompressBuffer(compressedData); - } - - public byte[] DecompressToBytes() - { - return intArrayToByteArray(this.Decompress()); - } - internal XomContainer(XomFile fromFile, string fromType, byte[] data) - { - fileBelongs = fromFile; - typeBelongs = fromType; - this.data = data; - } } } diff --git a/Worms4Editor/Program.cs b/Worms4Editor/Program.cs index d08a8a7..67f4abc 100644 --- a/Worms4Editor/Program.cs +++ b/Worms4Editor/Program.cs @@ -10,13 +10,7 @@ namespace Worms4Editor { static void Main(string[] args) { - /* int id = 0; - for (int i = 0; i < 0x7FFFFF; i++) { - int d = XomCompressor.DecompressInt(id); - if (d != i) Console.WriteLine("FAIL; " + i.ToString("X") + " id " + id.ToString("X") + " d " + d.ToString("X")); - else Console.WriteLine("PASS; " + i.ToString("X") + " id " + id.ToString("X") + " d " + d.ToString("X")); - id = XomCompressor.NextCompressedInterger(id); - } */ + int id = 0; XomFile xfile = XomReader.ReadXomFile(@"Original.xom"); //XomFile ps2file = XomReader.ReadXomFile(@"ps2.xom");