diff --git a/LibW4M/PS2/Ps2Save.cs b/LibW4M/PS2/Ps2Save.cs index ea15452..dae311a 100644 --- a/LibW4M/PS2/Ps2Save.cs +++ b/LibW4M/PS2/Ps2Save.cs @@ -50,6 +50,11 @@ namespace LibW4M.PS2 s.Seek(0, SeekOrigin.Begin); } + public static W4SaveFile ReadPS2Save(string filename) + { + return new W4SaveFile(XomReader.ReadXomFile(PsuFile.ReadPSU(filename).GetFileByName("BESLES-53096W4MA").FileData)); + } + public static void CreatePSU(W4SaveFile save, Stream output) { PsuFile psuFile = new PsuFile(); diff --git a/LibW4M/W4SaveFile.cs b/LibW4M/W4SaveFile.cs index 57b0118..6c8f870 100644 --- a/LibW4M/W4SaveFile.cs +++ b/LibW4M/W4SaveFile.cs @@ -10,6 +10,7 @@ using LibW4M.Data.WXFE; using LibW4M.Data.WXFE.UnlockableItem; using LibW4M.Data.X; using LibW4M.PS2; +using LibW4M.XBOX; using LibXom; using LibXom.Blocks; using LibXom.Data; @@ -170,6 +171,14 @@ namespace LibW4M } } + public void SaveXBOX(Stream xboxSaveStream) + { + using (xboxSaveStream) + { + XboxSave.CreateSaveFile(this, xboxSaveStream); + } + } + public void SavePS2(Stream ps2SaveStream) { using (ps2SaveStream) @@ -177,6 +186,11 @@ namespace LibW4M Ps2Save.CreateSaveFile(this, ps2SaveStream); } } + + public void SaveXBOX(string newXom) + { + SaveXBOX(File.OpenWrite(newXom)); + } public void SavePS2(string newXom) { SavePS2(File.OpenWrite(newXom)); diff --git a/LibW4M/XBOX/XboxSave.cs b/LibW4M/XBOX/XboxSave.cs new file mode 100644 index 0000000..18acba3 --- /dev/null +++ b/LibW4M/XBOX/XboxSave.cs @@ -0,0 +1,105 @@ +using LibW4M.Data.Stats; +using LibW4M.PS2; +using LibXom; +using LibXom.Streams; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace LibW4M.XBOX +{ + public static class XboxSave + { + private const int SHA1_SIZE = 0x14; + private const int XBOX_MAX_SZ = 0x64000; + + public static byte[] XboxSigningKey = new byte[0x40] { 0x20, 0x23, 0x8B, 0x22, 0xED, 0x13, 0xB7, 0xC1, 0x71, 0x5F, 0xC5, 0xB3, 0x72, 0x07, 0xC0, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + public static void CreateSaveFile(W4SaveFile save, Stream output) + { + using(MemoryStream ms = new MemoryStream()) + { + // Remove all stats ... + foreach (StatsContainerData stat in save.StatsCollective.ToArray()) + save.StatsCollective.Delete(stat); + + // Save changes + save.saveData(); + + XomWriter.WriteXom(save.OriginalXom, ms); + ms.Seek(0, SeekOrigin.Begin); + + XboxSave.CalcAndWriteSignature(ms); + + ms.Seek(0, SeekOrigin.Begin); + ms.CopyTo(output); + } + } + + private static byte[] xor(byte[] data, byte key) + { + byte[] outdata = new byte[data.Length]; + for (int i = 0; i < data.Length; i++) + outdata[i] = Convert.ToByte(data[i] ^ key); + return outdata; + } + + private static byte[] calcSignature(byte[] data) + { + using (SHA1 sha = SHA1.Create()) + { + using (MemoryStream ms = new MemoryStream()) + { + // hash save data + ms.Write(xor(XboxSigningKey, 0x36), 0, XboxSigningKey.Length); + ms.Write(data); + + ms.Seek(0, SeekOrigin.Begin); + + byte[] hash = sha.ComputeHash(ms.ToArray()); + + // hash that hash + ms.Seek(0, SeekOrigin.Begin); + ms.SetLength(0); + + ms.Write(xor(XboxSigningKey, 0x5C), 0, XboxSigningKey.Length); + ms.Write(hash, 0, hash.Length); + + sha.Initialize(); + + return sha.ComputeHash(ms.ToArray()); + } + } + } + + public static W4SaveFile ReadXboxSave(string filepath) + { + using (FileStream fs = File.OpenRead(filepath)) + { + fs.Seek(SHA1_SIZE, SeekOrigin.Begin); + return new W4SaveFile(XomReader.ReadXomFile(fs)); + } + } + + public static void CalcAndWriteSignature(Stream s) + { + byte[] data = new byte[XBOX_MAX_SZ]; + s.Read(data, 0, XBOX_MAX_SZ); + byte[] signature = calcSignature(data); + using(MemoryStream ms = new MemoryStream()) + { + ms.Write(signature, 0, SHA1_SIZE); + ms.Write(data, 0, XBOX_MAX_SZ); + + ms.Seek(0, SeekOrigin.Begin); + s.Seek(0, SeekOrigin.Begin); + s.SetLength(ms.Length); + ms.CopyTo(s); + } + } + + } +} diff --git a/W4Gui/Main.cs b/W4Gui/Main.cs index b9d0749..7cf3f06 100644 --- a/W4Gui/Main.cs +++ b/W4Gui/Main.cs @@ -1,6 +1,7 @@ using LibW4M; using LibW4M.Data.WeaponFactory; using LibW4M.PS2; +using LibW4M.XBOX; using LibXom; using LibXom.Data; using System; @@ -30,41 +31,33 @@ namespace W4Gui } - private SaveType getTypeFromName(string filename) - { - switch (Path.GetExtension(filename)) - { - - case ".psu": - return SaveType.PS2; - case ".xom": - default: - return SaveType.PC; - } - } private void openToolStripMenuItem_Click(object sender, EventArgs e) { OpenFileDialog fd = new OpenFileDialog(); - fd.Filter = "Worms 4: Mayhem Save Files|*.xom;*.psu|Worms 4: Mayhem PC Save File|*.xom|Worms 4: Mayhem PS2 Save File|*.psu"; + fd.Filter = "Worms 4: Mayhem PC Save File|*.xom|Worms 4: Mayhem PS2 Save File|*.psu|Worms 4: Mayhem XBOX Save File|data"; fd.Title = "Open Worms 4 Save File"; if(fd.ShowDialog() == DialogResult.OK) { DataManager.LoadedSavePath = fd.FileName; - DataManager.LoadedSaveType = getTypeFromName(DataManager.LoadedSavePath); + DataManager.LoadedSaveType = (SaveType)(fd.FilterIndex - 1); switch (DataManager.LoadedSaveType) { - case SaveType.PS2: - DataManager.SaveFile = new W4SaveFile(XomReader.ReadXomFile(PsuFile.ReadPSU(fd.FileName).GetFileByName("BESLES-53096W4MA").FileData)); - break; case SaveType.PC: default: DataManager.SaveFile = new W4SaveFile(XomReader.ReadXomFile(fd.FileName)); break; + case SaveType.PS2: + DataManager.SaveFile = Ps2Save.ReadPS2Save(fd.FileName); + break; + case SaveType.XBOX: + DataManager.SaveFile = XboxSave.ReadXboxSave(fd.FileName); + break; + } - - + + this.mainTabControl.Enabled = true; this.saveToolStripMenuItem.Enabled = true; this.saveAsToolStripMenuItem.Enabled = true; @@ -89,6 +82,7 @@ namespace W4Gui DataManager.SaveFile.SavePS2PSU(DataManager.LoadedSavePath); break; case SaveType.XBOX: + DataManager.SaveFile.SaveXBOX(DataManager.LoadedSavePath); break; } mainTabControl.Enabled = true; @@ -166,7 +160,7 @@ namespace W4Gui private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) { SaveFileDialog fd = new SaveFileDialog(); - fd.Filter = "Worms 4: Mayhem Save Files|*.xom;*.psu|Worms 4: Mayhem PC Save File|*.xom|Worms 4: Mayhem PS2 Single Save File|*.psu"; + fd.Filter = "Worms 4: Mayhem PC Save File|*.xom|Worms 4: Mayhem PS2 Single Save File|*.psu|Worms 4: Mayhem XBOX Save File|data"; fd.Title = "Save Worms 4 Save File"; fd.FileName = Path.GetFileName(DataManager.LoadedSavePath); if (fd.ShowDialog() == DialogResult.OK) @@ -174,18 +168,24 @@ namespace W4Gui this.mainTabControl.Enabled = false; DataManager.SaveAll(); - SaveType saveType = getTypeFromName(fd.FileName); + SaveType saveType = (SaveType)(fd.FilterIndex-1); switch (saveType) { - case SaveType.PS2: - DataManager.SaveFile.SavePS2PSU(fd.FileName); - break; case SaveType.PC: DataManager.SaveFile.SavePC(fd.FileName); break; + case SaveType.PS2: + DataManager.SaveFile.SavePS2PSU(fd.FileName); + break; + case SaveType.XBOX: + DataManager.SaveFile.SaveXBOX(fd.FileName); + break; } + DataManager.LoadedSavePath = fd.FileName; + DataManager.LoadedSaveType = saveType; + MessageBox.Show("File saved to: " + fd.FileName, "Save Complete!", MessageBoxButtons.OK, MessageBoxIcon.Information); this.mainTabControl.Enabled = true; } diff --git a/W4Gui/Properties/PublishProfiles/FolderProfile.pubxml.user b/W4Gui/Properties/PublishProfiles/FolderProfile.pubxml.user index 74e488b..731bca3 100644 --- a/W4Gui/Properties/PublishProfiles/FolderProfile.pubxml.user +++ b/W4Gui/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - True|2023-03-03T15:40:06.7859106Z;True|2023-03-01T11:45:37.3479871+13:00;True|2023-03-01T09:19:31.4651141+13:00;True|2023-02-25T21:53:35.8769435+13:00;True|2023-02-20T23:18:32.2496478+13:00;True|2023-02-19T06:40:54.2502643+13:00;False|2023-02-16T17:57:46.9563146+13:00;True|2023-01-19T20:41:26.7371208+13:00;True|2023-01-15T17:06:35.5919106+13:00;True|2023-01-14T13:57:56.0824690+13:00;True|2023-01-11T22:22:28.8737310+13:00;True|2023-01-11T22:16:55.3469226+13:00; + True|2023-03-03T16:17:31.1862857Z;True|2023-03-04T04:40:06.7859106+13:00;True|2023-03-01T11:45:37.3479871+13:00;True|2023-03-01T09:19:31.4651141+13:00;True|2023-02-25T21:53:35.8769435+13:00;True|2023-02-20T23:18:32.2496478+13:00;True|2023-02-19T06:40:54.2502643+13:00;False|2023-02-16T17:57:46.9563146+13:00;True|2023-01-19T20:41:26.7371208+13:00;True|2023-01-15T17:06:35.5919106+13:00;True|2023-01-14T13:57:56.0824690+13:00;True|2023-01-11T22:22:28.8737310+13:00;True|2023-01-11T22:16:55.3469226+13:00; \ No newline at end of file