From 9fcf29c8416bc7f326ef92f4bd33efdf4a40ff12 Mon Sep 17 00:00:00 2001
From: Li
Date: Sat, 4 Mar 2023 13:24:02 +1300
Subject: [PATCH] Support XBOX Save
---
LibW4M/PS2/Ps2Save.cs | 5 +
LibW4M/W4SaveFile.cs | 14 +++
LibW4M/XBOX/XboxSave.cs | 105 ++++++++++++++++++
W4Gui/Main.cs | 48 ++++----
.../PublishProfiles/FolderProfile.pubxml.user | 2 +-
5 files changed, 149 insertions(+), 25 deletions(-)
create mode 100644 LibW4M/XBOX/XboxSave.cs
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