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
+
+
+
+
+
+
+