From b271d979835b3d79106f554d8972945da31f0c49 Mon Sep 17 00:00:00 2001 From: Li Date: Sat, 7 Jan 2023 01:36:13 -0800 Subject: [PATCH] Upload inital code --- .gitignore | 4 + LibXom/Blocks/CtnrBlock.cs | 26 +++ LibXom/Blocks/GuidBlock.cs | 48 ++++ LibXom/Blocks/MoikBlock.cs | 48 ++++ LibXom/Blocks/SchmBlock.cs | 45 ++++ LibXom/Blocks/StrsBlock.cs | 55 +++++ LibXom/Blocks/TypeBlock.cs | 44 ++++ LibXom/Blocks/XomBlock.cs | 21 ++ LibXom/Blocks/XomBlockHandler.cs | 26 +++ LibXom/Data/XomCompressor.cs | 145 ++++++++++++ LibXom/Data/XomContainer.cs | 55 +++++ LibXom/Data/XomFile.cs | 143 ++++++++++++ LibXom/Data/XomFileComponent.cs | 23 ++ LibXom/Data/XomString.cs | 43 ++++ LibXom/Data/XomType.cs | 74 +++++++ .../Exceptions/XomBlockNotFoundException.cs | 16 ++ .../XomContainerNotFoundException.cs | 16 ++ LibXom/Exceptions/XomException.cs | 16 ++ .../XomFileComponentNotFoundException.cs | 16 ++ LibXom/Exceptions/XomTypeNotFoundException.cs | 16 ++ LibXom/LibXom.csproj | 9 + LibXom/XomReader.cs | 206 ++++++++++++++++++ LibXom/XomWriter.cs | 162 ++++++++++++++ Worms4Editor.sln | 31 +++ Worms4Editor/Program.cs | 36 +++ Worms4Editor/Worms4Editor.csproj | 14 ++ 26 files changed, 1338 insertions(+) create mode 100644 .gitignore create mode 100644 LibXom/Blocks/CtnrBlock.cs create mode 100644 LibXom/Blocks/GuidBlock.cs create mode 100644 LibXom/Blocks/MoikBlock.cs create mode 100644 LibXom/Blocks/SchmBlock.cs create mode 100644 LibXom/Blocks/StrsBlock.cs create mode 100644 LibXom/Blocks/TypeBlock.cs create mode 100644 LibXom/Blocks/XomBlock.cs create mode 100644 LibXom/Blocks/XomBlockHandler.cs create mode 100644 LibXom/Data/XomCompressor.cs create mode 100644 LibXom/Data/XomContainer.cs create mode 100644 LibXom/Data/XomFile.cs create mode 100644 LibXom/Data/XomFileComponent.cs create mode 100644 LibXom/Data/XomString.cs create mode 100644 LibXom/Data/XomType.cs create mode 100644 LibXom/Exceptions/XomBlockNotFoundException.cs create mode 100644 LibXom/Exceptions/XomContainerNotFoundException.cs create mode 100644 LibXom/Exceptions/XomException.cs create mode 100644 LibXom/Exceptions/XomFileComponentNotFoundException.cs create mode 100644 LibXom/Exceptions/XomTypeNotFoundException.cs create mode 100644 LibXom/LibXom.csproj create mode 100644 LibXom/XomReader.cs create mode 100644 LibXom/XomWriter.cs create mode 100644 Worms4Editor.sln create mode 100644 Worms4Editor/Program.cs create mode 100644 Worms4Editor/Worms4Editor.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83f617d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +Worms4Editor/obj/* +Worms4Editor/bin/* +LibXom/obj/* +LibXom/bin/* \ No newline at end of file diff --git a/LibXom/Blocks/CtnrBlock.cs b/LibXom/Blocks/CtnrBlock.cs new file mode 100644 index 0000000..ad080c1 --- /dev/null +++ b/LibXom/Blocks/CtnrBlock.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Blocks +{ + public class CtnrBlock : XomBlock + { + private byte[] data; + public byte[] Data + { + get + { + return data; + } + } + + internal CtnrBlock(byte[] data) + { + this.name = "CTNR"; + this.data = data; + } + } +} diff --git a/LibXom/Blocks/GuidBlock.cs b/LibXom/Blocks/GuidBlock.cs new file mode 100644 index 0000000..6a08bf3 --- /dev/null +++ b/LibXom/Blocks/GuidBlock.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Blocks +{ + public class GuidBlock : XomBlock + { + + private int unk0; + private int unk1; + private int unk2; + + public int Unk0 + { + get + { + return unk0; + } + } + public int Unk1 + { + get + { + return unk1; + } + } + + public int Unk2 + { + get + { + return unk2; + } + } + + + internal GuidBlock(int unk0, int unk1, int unk2) + { + this.name = "GUID"; + this.unk0 = unk0; + this.unk1 = unk1; + this.unk2 = unk2; + } + } +} diff --git a/LibXom/Blocks/MoikBlock.cs b/LibXom/Blocks/MoikBlock.cs new file mode 100644 index 0000000..c846cd2 --- /dev/null +++ b/LibXom/Blocks/MoikBlock.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Blocks +{ + public class MoikBlock : XomBlock + { + + private int version; + private int numCtnr; + private int numTypes; + + public int Version + { + get + { + return version; + } + } + + public int NumCtnr + { + get + { + return numCtnr; + } + } + + public int NumTypes + { + get + { + return numTypes; + } + } + + internal MoikBlock(int version, int numCtnr, int numTypes) + { + this.name = "MOIK"; + this.version = version; + this.numCtnr = numCtnr; + this.numTypes = numTypes; + } + } +} diff --git a/LibXom/Blocks/SchmBlock.cs b/LibXom/Blocks/SchmBlock.cs new file mode 100644 index 0000000..c869701 --- /dev/null +++ b/LibXom/Blocks/SchmBlock.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Blocks +{ + public class SchmBlock : XomBlock + { + private int unk0; + private int unk1; + private int unk2; + + public int Unk0 + { + get + { + return unk0; + } + } + public int Unk1 + { + get + { + return unk1; + } + } + + public int Unk2 + { + get + { + return unk2; + } + } + internal SchmBlock(int unk0, int unk1, int unk2) + { + this.name = "SCHM"; + this.unk0 = unk0; + this.unk1 = unk1; + this.unk2 = unk2; + } + } +} diff --git a/LibXom/Blocks/StrsBlock.cs b/LibXom/Blocks/StrsBlock.cs new file mode 100644 index 0000000..f3cc30e --- /dev/null +++ b/LibXom/Blocks/StrsBlock.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Blocks +{ + public class StrsBlock : XomBlock + { + private int strsBufferSz; + private int numStrs; + private List offsetList = new List(); + private List stringList = new List(); + + public int StringsSectionSz + { + get + { + return strsBufferSz; + } + } + public int NumStrs + { + get + { + return numStrs; + } + } + public int[] OffsetList + { + get + { + return offsetList.ToArray(); + } + } + public string[] StringList + { + get + { + return stringList.ToArray(); + } + } + + internal StrsBlock(int numStrs, int strsBufferSz, int[] offsetList, string[] stringList) + { + this.name = "STRS"; + this.numStrs = numStrs; + this.strsBufferSz = strsBufferSz; + + this.offsetList.AddRange(offsetList); + this.stringList.AddRange(stringList); + } + } +} diff --git a/LibXom/Blocks/TypeBlock.cs b/LibXom/Blocks/TypeBlock.cs new file mode 100644 index 0000000..6c1c92d --- /dev/null +++ b/LibXom/Blocks/TypeBlock.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Blocks +{ + public class TypeBlock : XomBlock + { + private int numCtnr; + private byte[] md5; + private string typeName; + public int NumCtnr + { + get + { + return numCtnr; + } + } + public byte[] Md5 + { + get + { + return md5; + } + } + public string TypeName + { + get + { + return typeName; + } + } + + internal TypeBlock(int numCtnr, byte[] md5, string typeName) + { + this.name = "TYPE"; + this.numCtnr = numCtnr; + this.md5 = md5; + this.typeName = typeName; + } + } +} diff --git a/LibXom/Blocks/XomBlock.cs b/LibXom/Blocks/XomBlock.cs new file mode 100644 index 0000000..5a3c63e --- /dev/null +++ b/LibXom/Blocks/XomBlock.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Blocks +{ + public class XomBlock + { + internal string? name = null; + internal string Name + { + get + { + if (name == null) return ""; + return name; + } + } + } +} diff --git a/LibXom/Blocks/XomBlockHandler.cs b/LibXom/Blocks/XomBlockHandler.cs new file mode 100644 index 0000000..a61574f --- /dev/null +++ b/LibXom/Blocks/XomBlockHandler.cs @@ -0,0 +1,26 @@ +using LibXom.Data; + +namespace LibXom.Blocks +{ + internal class XomBlockHandler + { + public static XomBlock[] GetBlocksByName(XomBlock[] xomBlocks, string name) + { + List sortedXomBlocksList = new List(); + foreach (XomBlock xomBlock in xomBlocks) + { + if (xomBlock.Name.ToLower().Equals(name.ToLower())) + { + sortedXomBlocksList.Add(xomBlock); + } + } + return sortedXomBlocksList.ToArray(); + } + public static XomBlock? GetBlockByName(XomBlock[] xomBlocks, string name) + { + XomBlock[] blocks = GetBlocksByName(xomBlocks, name); + if (blocks.Length <= 0) return null; + return blocks[0]; + } + } +} diff --git a/LibXom/Data/XomCompressor.cs b/LibXom/Data/XomCompressor.cs new file mode 100644 index 0000000..013229d --- /dev/null +++ b/LibXom/Data/XomCompressor.cs @@ -0,0 +1,145 @@ +using LibXom.Exceptions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Data +{ + public class XomCompressor + { + + internal static int readCompressedIntFromStream(MemoryStream ms) + { + byte[] buffer = new byte[4]; + + for (int i = 0; i < 4; i++) + { + int b = ms.ReadByte(); + buffer[i] = Convert.ToByte(b); + if (b > 0x7F) continue; + else break; + } + + return DecompressInt(BitConverter.ToInt32(buffer)); + } + public static int[] Decompress(byte[] input) + { + List decompressedData = new List(); + + using (MemoryStream inputStream = new MemoryStream(input)) + { + while (inputStream.Position < inputStream.Length) + { + int n = readCompressedIntFromStream(inputStream); + decompressedData.Add(n); + } + + } + 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 decompressInt16(int compressedInt) + { + int mul = (compressedInt >> 8); + int add = (compressedInt & 0xFF); + + return (mul * 0x80) + (add % 0x80); + } + private static int decompressInt24(int compressedInt) + { + int mul = (compressedInt >> 8) & 0xFF; + int mul2 = (compressedInt >> 16); + + int add = (compressedInt & 0xFF); + + return ((mul2 * 0x80) * mul) + (add % 0x80); + } + + private static int decompressInt32(int compressedInt) + { + int mul = (compressedInt >> 8) & 0xFF; + int mul2 = (compressedInt >> 16) & 0xFF; + int mul3 = (compressedInt >> 24); + + int add = (compressedInt & 0xFF); + + return (((mul3 * 0x80) * mul2 * 0x80) * mul * 0x80) + (add % 0x80); + } + + public static int DecompressInt(int compressedInt) + { + switch (getNumberByteCount(compressedInt)) + { + case 1: + return compressedInt; + case 2: + return decompressInt16(compressedInt); + case 3: + return decompressInt24(compressedInt); + case 4: + return decompressInt32(compressedInt); + default: + throw new XomException("Number is too large or too small."); + } + } + public static int NextCompressedInterger(int compressedInt) + { + // Some parts of XOM follow a particular pattern, + + // 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; + } + } +} diff --git a/LibXom/Data/XomContainer.cs b/LibXom/Data/XomContainer.cs new file mode 100644 index 0000000..c0dbabf --- /dev/null +++ b/LibXom/Data/XomContainer.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Data +{ + public class XomContainer : XomFileComponent + { + + private string typeBelongs; + internal byte[] data; + + public XomType Type + { + get + { + return this.fileBelongs.GetTypeByName(typeBelongs); + } + } + public override int Id + { + get + { + return this.fileBelongs.calculateIdForXomFileComponent(this.uuid, fileBelongs.XomContainers); + } + } + public byte[] Data + { + get + { + return data; + } + set + { + this.Type.ReplaceContainerData(this, value); + } + } + + public int[] Decompress() + { + byte[] compressedData = new byte[Data.Length - 3]; + Array.ConstrainedCopy(Data, 3, compressedData, 0, compressedData.Length); + return XomCompressor.Decompress(compressedData); + } + + internal XomContainer(XomFile fromFile, string fromType, byte[] data) + { + fileBelongs = fromFile; + typeBelongs = fromType; + this.data = data; + } + } +} diff --git a/LibXom/Data/XomFile.cs b/LibXom/Data/XomFile.cs new file mode 100644 index 0000000..f9c11be --- /dev/null +++ b/LibXom/Data/XomFile.cs @@ -0,0 +1,143 @@ +using LibXom.Blocks; +using LibXom.Exceptions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Data +{ + public class XomFile + { + private int version; + private int unk0; + private List xomStrings = new List(); + private List xomTypes = new List(); + + public XomString[] XomStrings + { + get + { + return xomStrings.ToArray(); + } + } + + public XomType[] XomTypes + { + get + { + return xomTypes.ToArray(); + } + } + + public XomContainer[] XomContainers + { + get + { + List xomContainers = new List(); + foreach(XomType type in xomTypes) + foreach(XomContainer container in type.Containers) + xomContainers.Add(container); + return xomContainers.ToArray(); + } + } + + public XomContainer GetContainerById(int containerId) + { + foreach(XomContainer container in XomContainers) + if (container.Id.Equals(containerId)) return container; + throw new XomContainerNotFoundException("No container with id " + containerId + " was found."); + } + public XomType GetTypeByName(string typeName) + { + foreach(XomType type in XomTypes) + if (type.Name.Equals(typeName, StringComparison.InvariantCulture)) return type; + + throw new XomTypeNotFoundException("Type \"" + typeName + "\" was not found in XOM."); + } + internal int calculateIdForXomFileComponent(string searchUuid, XomFileComponent[] components) + { + int id = 0; + foreach(XomFileComponent component in components) + { + if (component.uuid.Equals(searchUuid, StringComparison.InvariantCultureIgnoreCase)) return id; + id = XomCompressor.NextCompressedInterger(id); + } + return id; + } + internal XomFile(XomBlock[] xomBlocks) + { + MoikBlock? moikBlock = XomBlockHandler.GetBlockByName(xomBlocks, "MOIK") as MoikBlock; + SchmBlock? schemeBlock = XomBlockHandler.GetBlockByName(xomBlocks, "SCHM") as SchmBlock; + StrsBlock? stringBlock = XomBlockHandler.GetBlockByName(xomBlocks, "STRS") as StrsBlock; + + // Get Types + XomBlock[] typeBlocks = XomBlockHandler.GetBlocksByName(xomBlocks, "TYPE"); + + // Get Containers + XomBlock[] containerBlocks = XomBlockHandler.GetBlocksByName(xomBlocks, "CTNR"); + + + if (moikBlock is not MoikBlock) throw new XomBlockNotFoundException("XOM contained no MOIK block!, Is it corrupted?"); + if (schemeBlock is not SchmBlock) throw new XomBlockNotFoundException("XOM contained no SCHM block!, Is it corrupted?"); + if (stringBlock is not StrsBlock) throw new XomBlockNotFoundException("XOM contained no STRS block!, Is it corrupted?"); + + version = moikBlock.Version; + unk0 = schemeBlock.Unk0; + + + /// Read Strings + + // Create a list of all strings and the offset they would appear in the strs block. + Dictionary stringOffsets = new Dictionary(); + int loc = 0; + foreach (string str in stringBlock.StringList) + { + stringOffsets.Add(loc, str); + loc += str.Length + 1; // str length, + \0 for terminator. + } + + // Now create the list of XomStrings ... + for (int i = 0; i < stringBlock.NumStrs; i++) + { + int offset = stringBlock.OffsetList[i]; + string value = stringOffsets[offset]; + // Were storing the offset here so that i can figure out how to best order the values again + // when reconstructing the value (eg make it as close to the actual game) + // ideally this wouldnt be needed. + // TODO: Figure out how strings are ordered, so i can ditch the offset variable. + xomStrings.Add(new XomString(this, offset, value)); + + } + + /// Read Types + // keep a global count of how many container blocks have been processed + int cnt = 0; + foreach (TypeBlock type in typeBlocks) + { + string typeName = type.TypeName; + byte[] md5 = type.Md5; + int numContainers = type.NumCtnr; + // Create a list of containers, inside this type. + List xomContainers = new List(); + // Read numContainers many containers from the container blocks list, + // and then create XomContainer from it. + for (int i = 0; i < numContainers; i++) + { + // Get the container block from the global count .. + CtnrBlock? containerBlock = containerBlocks[cnt] as CtnrBlock; + if (containerBlock is not CtnrBlock) continue; + + // Add the new container to the list + xomContainers.Add(new XomContainer(this, typeName, containerBlock.Data)); + + cnt++; // Increment the global container block count. + } + + this.xomTypes.Add(new XomType(this, md5, typeName, xomContainers.ToArray())); + } + + } + } +} diff --git a/LibXom/Data/XomFileComponent.cs b/LibXom/Data/XomFileComponent.cs new file mode 100644 index 0000000..4e8c041 --- /dev/null +++ b/LibXom/Data/XomFileComponent.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Data +{ + public class XomFileComponent + { + internal Guid guid = Guid.NewGuid(); + internal XomFile fileBelongs; + public virtual int Id { get; } + internal string uuid + { + get + { + return guid.ToString(); + } + } + + } +} diff --git a/LibXom/Data/XomString.cs b/LibXom/Data/XomString.cs new file mode 100644 index 0000000..e841dbe --- /dev/null +++ b/LibXom/Data/XomString.cs @@ -0,0 +1,43 @@ +using LibXom.Exceptions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Data +{ + public class XomString : XomFileComponent + { + private int offset; + private string value; + + public override int Id + { + get + { + return this.fileBelongs.calculateIdForXomFileComponent(this.uuid, fileBelongs.XomStrings); + } + } + public int Offset + { + get + { + return offset; + } + } + public string Value + { + get + { + return value; + } + } + internal XomString(XomFile fromFile, int offset, string value) + { + this.fileBelongs = fromFile; + this.offset = offset; + this.value = value; + } + } +} diff --git a/LibXom/Data/XomType.cs b/LibXom/Data/XomType.cs new file mode 100644 index 0000000..d2aa0ad --- /dev/null +++ b/LibXom/Data/XomType.cs @@ -0,0 +1,74 @@ +using LibXom.Exceptions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Data +{ + public class XomType : XomFileComponent + { + private byte[] md5; + private string name; + private List xomContainers = new List(); + public override int Id + { + get + { + return this.fileBelongs.calculateIdForXomFileComponent(this.uuid, fileBelongs.XomTypes); + } + } + + public byte[] Md5 + { + get + { + return md5; + } + } + public string Name + { + get + { + return name; + } + } + public XomContainer[] Containers + { + get + { + return xomContainers.ToArray(); + } + } + private int getContainerIndex(XomContainer container) + { + for (int i = 0; i < xomContainers.Count; i++) + { + if (xomContainers[i].uuid.Equals(container.uuid, StringComparison.InvariantCulture)) + { + return i; + } + } + throw new XomContainerNotFoundException("Could not find Xom Container with uuid: " + container.uuid + " in type: " + this.Name); + } + public void ReplaceContainerData(XomContainer container, byte[] newData) + { + int indx = this.getContainerIndex(container); + this.xomContainers[indx].data = newData; + } + public void DeleteContainer(XomContainer container) + { + int indx = this.getContainerIndex(container); + this.xomContainers.RemoveAt(indx); + } + + internal XomType(XomFile fileFrom, byte[] md5, string name, XomContainer[] xomContainers) + { + fileBelongs = fileFrom; + this.md5 = md5; + this.name = name; + this.xomContainers.AddRange(xomContainers); + } + } +} diff --git a/LibXom/Exceptions/XomBlockNotFoundException.cs b/LibXom/Exceptions/XomBlockNotFoundException.cs new file mode 100644 index 0000000..420e1b0 --- /dev/null +++ b/LibXom/Exceptions/XomBlockNotFoundException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Exceptions +{ + public class XomBlockNotFoundException : XomException + { + internal XomBlockNotFoundException(string message) : base(message) + { + + } + } +} diff --git a/LibXom/Exceptions/XomContainerNotFoundException.cs b/LibXom/Exceptions/XomContainerNotFoundException.cs new file mode 100644 index 0000000..afbd50b --- /dev/null +++ b/LibXom/Exceptions/XomContainerNotFoundException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Exceptions +{ + public class XomContainerNotFoundException : XomException + { + internal XomContainerNotFoundException(string message) : base(message) + { + + } + } +} diff --git a/LibXom/Exceptions/XomException.cs b/LibXom/Exceptions/XomException.cs new file mode 100644 index 0000000..8bd4bc2 --- /dev/null +++ b/LibXom/Exceptions/XomException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Exceptions +{ + public class XomException : Exception + { + internal XomException(string message) + { + + } + } +} diff --git a/LibXom/Exceptions/XomFileComponentNotFoundException.cs b/LibXom/Exceptions/XomFileComponentNotFoundException.cs new file mode 100644 index 0000000..f6b76f9 --- /dev/null +++ b/LibXom/Exceptions/XomFileComponentNotFoundException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Exceptions +{ + public class XomFileComponentNotFoundException : XomException + { + internal XomFileComponentNotFoundException(string message) : base(message) + { + + } + } +} diff --git a/LibXom/Exceptions/XomTypeNotFoundException.cs b/LibXom/Exceptions/XomTypeNotFoundException.cs new file mode 100644 index 0000000..8d721bb --- /dev/null +++ b/LibXom/Exceptions/XomTypeNotFoundException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibXom.Exceptions +{ + public class XomTypeNotFoundException : XomException + { + internal XomTypeNotFoundException(string message) : base(message) + { + + } + } +} diff --git a/LibXom/LibXom.csproj b/LibXom/LibXom.csproj new file mode 100644 index 0000000..a1ed5b3 --- /dev/null +++ b/LibXom/LibXom.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/LibXom/XomReader.cs b/LibXom/XomReader.cs new file mode 100644 index 0000000..265d7c1 --- /dev/null +++ b/LibXom/XomReader.cs @@ -0,0 +1,206 @@ +using LibXom.Blocks; +using LibXom.Data; +using System.Text; + +namespace LibXom +{ + public class XomReader + { + private Stream xomStream; + private byte[] readBytes(int amt) + { + byte[] buffer = new byte[amt]; + xomStream.Read(buffer, 0, amt); + return buffer; + } + private byte readByte() + { + return Convert.ToByte(xomStream.ReadByte()); + } + private string readStrLen(int len) + { + byte[] buf = readBytes(len); + + int rlen = 0; + for (rlen = 0; rlen < len; rlen++) + if (buf[rlen] == 0) break; + + return Encoding.UTF8.GetString(buf, 0, rlen); + } + private string readCStr() + { + StringBuilder cstr = new StringBuilder(); + + while (true) + { + char c = (char)readByte(); + if (c == 0) break; + cstr.Append(c); + } + + return cstr.ToString(); + } + private int readInt32BE() + { + byte[] buffer = readBytes(0x4); + buffer.Reverse(); + return BitConverter.ToInt32(buffer); + } + private void skip(int amt) + { + xomStream.Seek(amt, SeekOrigin.Current); + } + private int readInt32() + { + return BitConverter.ToInt32(readBytes(0x4)); + } + + public bool bufferEndsWith(List buffer, byte[] search) + { + int len = search.Length; + + if (buffer.Count < len) return false; + + byte[] lastSection = new byte[len]; + + int ii = 0; + for (int i = 0; i < len; i++) + { + int pos = (buffer.Count - len) + i; + lastSection[ii] = buffer[pos]; + ii++; + } + + if (lastSection.SequenceEqual(search)) + return true; + else + return false; + } + public CtnrBlock readCtnr() + { + List buffer = new List(); + while (xomStream.Position < xomStream.Length) + { + buffer.Add(readByte()); + if (bufferEndsWith(buffer, Encoding.UTF8.GetBytes("CTNR"))) + { + skip(-4); + int i = buffer.Count - 1; + int endAt = i - 4; + for (; i != endAt; i--) + buffer.RemoveAt(i); + return new CtnrBlock(buffer.ToArray()); + } + } + return new CtnrBlock(buffer.ToArray()); + } + + public MoikBlock readMoik() + { + int version = readInt32BE(); + skip(0x10); + int numTypes = readInt32(); + int numCtnr = readInt32(); + int numCtnr2 = readInt32(); + skip(0x1C); + return new MoikBlock(version, numCtnr, numTypes); + } + public TypeBlock readType() + { + skip(0x4); + int numCtnr = readInt32(); + skip(0x4); + byte[] md5 = readBytes(0x10); + string typeName = readStrLen(0x20); + return new TypeBlock(numCtnr, md5, typeName); + } + public SchmBlock readSchm() + { + int unk0 = readInt32(); + int unk1 = readInt32(); + int unk2 = readInt32(); + return new SchmBlock(unk0, unk1, unk2); + } + public GuidBlock readGuid() + { + int unk0 = readInt32(); + int unk1 = readInt32(); + int unk2 = readInt32(); + return new GuidBlock(unk0, unk1, unk2); + } + public StrsBlock readStrs() + { + int numStrs = readInt32(); + int strsSz = readInt32(); + + int[] offsets = new int[numStrs]; + string[] strings = new string[numStrs]; + + for (int i = 0; i < numStrs; i++) + { + offsets[i] = readInt32(); + } + for (int i = 0; i < numStrs; i++) + { + strings[i] = readCStr(); + } + return new StrsBlock(numStrs, strsSz, offsets, strings); ; + } + public XomBlock? readBlock() + { + string hdr = readStrLen(0x4); + + switch (hdr) + { + case "MOIK": + return readMoik(); + case "TYPE": + return readType(); + case "SCHM": + return readSchm(); + case "GUID": + return readGuid(); + case "STRS": + return readStrs(); + case "CTNR": + return readCtnr(); + } + + return null; + } + private XomBlock[] readAllBlocks() + { + List xomBlocks = new List(); + while (xomStream.Position < xomStream.Length) + { + XomBlock? block = readBlock(); + if (block == null) break; + xomBlocks.Add(block); + } + return xomBlocks.ToArray(); + } + public static XomFile ReadXomFile(string xomFilename) + { + using(MemoryStream ms = new MemoryStream()) + { + using(FileStream fs = File.OpenRead(xomFilename)) + fs.CopyTo(ms); + + ms.Seek(0, SeekOrigin.Begin); + return ReadXomFile(ms); + } + } + public static XomFile ReadXomFile(Stream xomStream) + { + XomReader reader = new XomReader(xomStream); + // Read all blocks + XomBlock[] xomBlocks = reader.readAllBlocks(); + // Create the file object + return new XomFile(xomBlocks); + } + internal XomReader(Stream xom) + { + this.xomStream = xom; + } + } +} diff --git a/LibXom/XomWriter.cs b/LibXom/XomWriter.cs new file mode 100644 index 0000000..d32371d --- /dev/null +++ b/LibXom/XomWriter.cs @@ -0,0 +1,162 @@ +using LibXom.Blocks; +using LibXom.Data; +using System.Text; + +namespace LibXom +{ + public class XomWriter + { + private Stream xomStream; + private XomFile xomFile; + + private void writeByte(byte b) + { + xomStream.WriteByte(b); + } + private int pos() + { + return Convert.ToInt32(xomStream.Position); + } + private void rewind(int amt) + { + xomStream.Seek(-amt, SeekOrigin.Current); + } + private void skip(int amt) + { + int cpos = pos(); + int len = Convert.ToInt32(xomStream.Length); + int remain = len - cpos; + + if(amt > remain) + { + xomStream.Seek(remain, SeekOrigin.Current); + amt -= remain; + writePadding(0, amt); + } + else + { + xomStream.Seek(amt, SeekOrigin.Current); + } + } + private void writePadding(byte pad, int len) + { + byte[] buf = new byte[len]; + if(pad != 0) + for(int i = 0; i < len; i++) + buf[i] = pad; + writeBytes(buf); + } + private void writeBytes(byte[] bytes) + { + xomStream.Write(bytes, 0, bytes.Length); + } + private void writeInt32(int value) + { + byte[] buffer = BitConverter.GetBytes(value); + writeBytes(buffer); + } + private void writeInt32BE(int value) + { + byte[] buffer = BitConverter.GetBytes(value); + buffer.Reverse(); + writeBytes(buffer); + } + + private void writeStrLen(string str, int len) + { + writeStr(str); + + int padLen = (len - str.Length); + if (padLen > 0) + skip(padLen); + } + + private void writeStr(string str) + { + byte[] buffer = Encoding.UTF8.GetBytes(str); + writeBytes(buffer); + } + + private void writeCStr(string str) + { + writeStr(str); + writeByte(0); + } + private void writeMoik(MoikBlock moikBlock) + { + writeStr(moikBlock.Name); + writeInt32BE(moikBlock.Version); + skip(0x10); + writeInt32(moikBlock.NumTypes); + writeInt32(moikBlock.NumCtnr); + writeInt32(moikBlock.NumCtnr); + skip(0x1C); + } + + private void writeType(TypeBlock typeBlock) + { + writeStr(typeBlock.Name); + skip(0x4); + writeInt32(typeBlock.NumCtnr); + skip(0x4); + writeBytes(typeBlock.Md5); + writeStrLen(typeBlock.TypeName, 0x20); + } + + + private void writeGuid(GuidBlock guidBlock) + { + writeStr(guidBlock.Name); + writeInt32(guidBlock.Unk0); + writeInt32(guidBlock.Unk1); + writeInt32(guidBlock.Unk2); + } + private void writeSchm(SchmBlock schmBlock) + { + writeStr(schmBlock.Name); + writeInt32(schmBlock.Unk0); + writeInt32(schmBlock.Unk1); + writeInt32(schmBlock.Unk2); + } + private void writeStrs(StrsBlock strsBlock) + { + writeStr(strsBlock.Name); + writeInt32(strsBlock.NumStrs); + writeInt32(strsBlock.StringsSectionSz); + + foreach (int offset in strsBlock.OffsetList) + writeInt32(offset); + foreach (string str in strsBlock.StringList) + writeCStr(str); + } + + private void writeCtnr(CtnrBlock ctnrBlock) + { + writeStr(ctnrBlock.Name); + writeBytes(ctnrBlock.Data); + } + + private void writeBlocks(XomBlock[] blocks) + { + foreach(XomBlock block in blocks) + { + if (block is MoikBlock) writeMoik(block as MoikBlock); + if (block is GuidBlock) writeGuid(block as GuidBlock); + if (block is SchmBlock) writeSchm(block as SchmBlock); + if (block is StrsBlock) writeStrs(block as StrsBlock); + if (block is TypeBlock) writeType(block as TypeBlock); + if (block is CtnrBlock) writeCtnr(block as CtnrBlock); + + } + } + + public void WriteXom() + { + } + internal XomWriter(Stream xomStream, XomFile xomFile) + { + this.xomStream = xomStream; + this.xomFile = xomFile; + } + } +} diff --git a/Worms4Editor.sln b/Worms4Editor.sln new file mode 100644 index 0000000..adba5bb --- /dev/null +++ b/Worms4Editor.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33205.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worms4Editor", "Worms4Editor\Worms4Editor.csproj", "{FBAA43A5-824F-4C9A-97BC-7B18A42413B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibXom", "LibXom\LibXom.csproj", "{7B60E17C-780E-44D3-BF02-9F5712DD3AE2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FBAA43A5-824F-4C9A-97BC-7B18A42413B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBAA43A5-824F-4C9A-97BC-7B18A42413B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBAA43A5-824F-4C9A-97BC-7B18A42413B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBAA43A5-824F-4C9A-97BC-7B18A42413B9}.Release|Any CPU.Build.0 = Release|Any CPU + {7B60E17C-780E-44D3-BF02-9F5712DD3AE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B60E17C-780E-44D3-BF02-9F5712DD3AE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B60E17C-780E-44D3-BF02-9F5712DD3AE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B60E17C-780E-44D3-BF02-9F5712DD3AE2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C7DC1F46-D074-4C13-B78C-AC959EB35C29} + EndGlobalSection +EndGlobal diff --git a/Worms4Editor/Program.cs b/Worms4Editor/Program.cs new file mode 100644 index 0000000..06b3ad8 --- /dev/null +++ b/Worms4Editor/Program.cs @@ -0,0 +1,36 @@ +using LibXom; +using LibXom.Blocks; +using LibXom.Data; +using System.Security.Cryptography; + +namespace Worms4Editor +{ + internal class Program + { + static void Main(string[] args) + { + Console.WriteLine(XomCompressor.DecompressInt(32767).ToString("X")); + Console.WriteLine(XomCompressor.DecompressInt(98432).ToString("X")); + Console.WriteLine(XomCompressor.DecompressInt(98433).ToString("X")); + Console.WriteLine(XomCompressor.DecompressInt(8388607).ToString("X")); + int id = 0; + for (int i = 0; i < 0x9000; 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); + } + XomFile xfile = XomReader.ReadXomFile(@"SaveGame.xom"); + XomFile ps2file = XomReader.ReadXomFile(@"ps2.xom"); + + XomType type = xfile.GetTypeByName("StoredStatsCollective"); + File.WriteAllBytes("StoredStatsCollective.bin", type.Containers.First().Data); + + /*foreach(int d in data) + { + Console.WriteLine(d + ": "+ BitConverter.ToString(ps2file.GetContainerById(d).Data).Replace("-", " ")); + }*/ + + } + } +} \ No newline at end of file diff --git a/Worms4Editor/Worms4Editor.csproj b/Worms4Editor/Worms4Editor.csproj new file mode 100644 index 0000000..ea5db42 --- /dev/null +++ b/Worms4Editor/Worms4Editor.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + +