using LibW4M.Data.Stats; using LibW4M.PS2; using LibXom; using LibXom.Streams; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Security.AccessControl; 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[] GetXboxSigningKeyForRegion(XboxRegion region) { switch (region) { case XboxRegion.EU: return new byte[0x10] { 0x78, 0x9F, 0x93, 0x67, 0xFD, 0xC8, 0x70, 0xAC, 0xCF, 0x9D, 0x46, 0xB0, 0x5E, 0x51, 0x36, 0x43 }; case XboxRegion.USA: default: return new byte[0x10] { 0x20, 0x23, 0x8B, 0x22, 0xED, 0x13, 0xB7, 0xC1, 0x71, 0x5F, 0xC5, 0xB3, 0x72, 0x07, 0xC0, 0x24 }; } } public static void CreateSaveFile(W4SaveFile save, Stream output, XboxRegion region) { 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, region); ms.Seek(0, SeekOrigin.Begin); ms.CopyTo(output); } } public static void CreateSaveZipFile(W4SaveFile save, Stream outStream, XboxRegion region) { using(MemoryStream ms = new MemoryStream()) { using(ZipArchive zip = new ZipArchive(ms, ZipArchiveMode.Create, true)) { string titleId = (region == XboxRegion.USA) ? "4d4a0017" : "434d0052"; string userId = (region == XboxRegion.USA) ? "474BBB580245" : "474BBB580245"; zip.CreateEntry(titleId + "/", CompressionLevel.SmallestSize); zip.CreateEntry(titleId + "/" + userId + "/", CompressionLevel.SmallestSize); using (Stream stream = zip.CreateEntry(titleId + "/SaveImage.xbx", CompressionLevel.SmallestSize).Open()) { stream.Write(SaveResources.SaveImage, 0, SaveResources.SaveImage.Length); } using (Stream stream = zip.CreateEntry(titleId + "/TitleImage.xbx", CompressionLevel.SmallestSize).Open()) { stream.Write(SaveResources.TitleImage, 0, SaveResources.TitleImage.Length); } using (Stream stream = zip.CreateEntry(titleId + "/TitleMeta.xbx", CompressionLevel.SmallestSize).Open()) { stream.Write(SaveResources.TitleMeta, 0, SaveResources.TitleMeta.Length); } using (Stream stream = zip.CreateEntry(titleId + "/" + userId + "/SaveMeta.xbx", CompressionLevel.SmallestSize).Open()) { stream.Write(SaveResources.SaveMeta, 0, SaveResources.SaveMeta.Length); } using (Stream stream = zip.CreateEntry(titleId + "/" + userId + "/data", CompressionLevel.SmallestSize).Open()) { CreateSaveFile(save, stream, region); } } ms.Seek(0, SeekOrigin.Begin); ms.CopyTo(outStream); } } 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, XboxRegion region) { using (SHA1 sha = SHA1.Create()) { using (MemoryStream ms = new MemoryStream()) { byte[] signHeader = new byte[0x40]; Array.Copy(GetXboxSigningKeyForRegion(region), signHeader, 0x10); // hash save data ms.Write(xor(signHeader, 0x36), 0, signHeader.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(signHeader, 0x5C), 0, signHeader.Length); ms.Write(hash, 0, hash.Length); sha.Initialize(); return sha.ComputeHash(ms.ToArray()); } } } private static MemoryStream readSaveData(Stream stream) { MemoryStream ms = new MemoryStream(); stream.Seek(SHA1_SIZE, SeekOrigin.Begin); stream.CopyTo(ms); ms.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin); return ms; } public static W4SaveFile ReadXboxSave(string filename, ref XboxRegion outRegion) { using (FileStream fs = File.OpenRead(filename)) { using (ZipArchive zip = new ZipArchive(fs, ZipArchiveMode.Read, true)) { ZipArchiveEntry? usaDataEntry = zip.GetEntry("4d4a0017/474BBB580245/data"); if (usaDataEntry is not null) { outRegion = XboxRegion.USA; return XboxSave.readXboxMainSave(usaDataEntry.Open()); } ZipArchiveEntry? eurDataEntry = zip.GetEntry("434d0052/474BBB580245/data"); if (eurDataEntry is not null) { outRegion = XboxRegion.EU; return XboxSave.readXboxMainSave(eurDataEntry.Open()); } } } throw new FileNotFoundException("Could not find '4d4a0017/474BBB580245/data' or '434d0052/474BBB580245/data' inside the zip?\nis it not an Worms 4 Mayhem XBOX save file?"); } private static W4SaveFile readXboxMainSave(Stream stream) { using (stream) { using(MemoryStream streamCopy = new MemoryStream()) { stream.CopyTo(streamCopy); streamCopy.Seek(0, SeekOrigin.Begin); using (MemoryStream xomStream = readSaveData(streamCopy)) { return new W4SaveFile(XomReader.ReadXomFile(xomStream)); } } } } public static void CalcAndWriteSignature(Stream s, XboxRegion region) { byte[] data = new byte[XBOX_MAX_SZ]; s.Read(data, 0, XBOX_MAX_SZ); byte[] signature = calcSignature(data, region); 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); } } } }