using LibW4M.Data; using LibW4M.Data.Awards; using LibW4M.Data.Highscores; using LibW4M.Data.InputMapping; using LibW4M.Data.Schemes; using LibW4M.Data.Stats; using LibW4M.Data.Teams; using LibW4M.Data.WeaponFactory; 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; using LibXom.Exceptions; using LibXom.Streams; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using System.Xml.XPath; using static System.Runtime.InteropServices.JavaScript.JSType; namespace LibW4M { public class W4SaveFile { private List unlockableItems; private XomFile xomFile; public DataBank XDataBank; public WeaponsCollective WeaponFactoryCollective; public TeamsCollective TeamDataColective; public SchemesCollective SchemesCollective; public HighscoreCollective HighscoreCollective; public StatsCollective StatsCollective; public TeamStatsCollective TeamStatsCollective; public InputMappingCollective InputMappingCollective; public TeamAwardsData TeamAwardsContainer; public XomString Message; public XomString FEResourceID; public XomFile OriginalXom { get { return this.xomFile; } } public UnlockableItemData[] UnlockableItems { get { return unlockableItems.ToArray(); } } public void Load() { unlockableItems = new List(); XDataBank = new DataBank(this, this.xomFile.GetTypeByName("XDataBank").Containers.First()); foreach(ContainerResourceDetail containerResourceDetail in XDataBank.ContainerResourceDetails) { switch (containerResourceDetail.Name.Value) { case "DATA.Weapons": WeaponFactoryCollective = new WeaponsCollective(this, containerResourceDetail.Value); break; case "DATA.TeamBarracks": TeamDataColective = new TeamsCollective(this, containerResourceDetail.Value); HighscoreCollective = new HighscoreCollective(this, containerResourceDetail.Value); break; case "DATA.Schemes": SchemesCollective = new SchemesCollective(this, containerResourceDetail.Value); break; case "PersistStats": StatsCollective = new StatsCollective(this, containerResourceDetail.Value); TeamStatsCollective = new TeamStatsCollective(this, containerResourceDetail.Value); break; case "Awards": TeamAwardsContainer = new TeamAwardsData(this, containerResourceDetail.Value); break; case "InputMapping": InputMappingCollective = new InputMappingCollective(this, containerResourceDetail.Value); break; default: // stupid hack; the only other option is manually hardcoding every unlockable item in the game // but this way atleast allows for it to be more generic incase for some reason not all (or more) are present. // Team17, why isnt there a collective for this? if (containerResourceDetail.Value.Type.Name.Equals("WXFE_UnlockableItem", StringComparison.InvariantCultureIgnoreCase)) { UnlockableItemData itemData = new UnlockableItemData(this, containerResourceDetail.Value); itemData.nameOverride = containerResourceDetail.Name.Value; unlockableItems.Add(itemData); } else { #if !DEBUG throw new XomException("Unknown container resource: " + containerResourceDetail.Name); #else Console.Error.WriteLine("Unknown container resource: " + containerResourceDetail.Name); #endif } break; } } } internal void saveData() { #if !DEBUG xomFile.ClearAllStrings(); #endif XDataBank.Save(); WeaponFactoryCollective.Save(); // Save Teams, Highscores TeamDataColective.Save(); HighscoreCollective.Save(); SchemesCollective.Save(); // Save Stats, TeamStats StatsCollective.Save(); TeamStatsCollective.Save(); TeamAwardsContainer.Save(); InputMappingCollective.Save(); foreach (UnlockableItemData unlockableItem in UnlockableItems) unlockableItem.Save(); } public void Save(Stream pcSaveStream) { saveData(); using (pcSaveStream) { using (MemoryStream ms = new MemoryStream()) { XomWriter.WriteXom(xomFile, pcSaveStream); ms.Seek(0, SeekOrigin.Begin); ms.CopyTo(pcSaveStream); } } } public void ExtractAllContainers(string path) { foreach(XomType type in xomFile.XomTypes) { string outfolder = Path.Combine(path, type.Name); if (Directory.Exists(outfolder)) Directory.Delete(outfolder, true); Directory.CreateDirectory(outfolder); foreach(XomContainer container in type.Containers) { while (true) { try { File.WriteAllBytes(Path.ChangeExtension(Path.Combine(outfolder, container.Id.ToString("X2")), ".bin"), container.GetData()); break; } catch { continue; } } } } } public XomString[] StringArrayToXomStringArray(string[] strings) { XomString[] xstrings = new XomString[strings.Length]; for (int i = 0; i < xstrings.Length; i++) xstrings[i] = LookupString(strings[i]); return xstrings; } public string[] XomStringArrayToStringArray(XomString[] xstrings) { string[] strings = new string[xstrings.Length]; for (int i = 0; i < strings.Length; i++) strings[i] = xstrings[i].Value; return strings; } public int[] XomStringArrayToIntArray(XomString[] strs) { int[] ids = new int[strs.Length]; for (int i = 0; i < ids.Length; i++) ids[i] = strs[i].Id; return ids; } public XomString[] IntArrayToXomStringArray(int[] stringIds) { XomString[] strings = new XomString[stringIds.Length]; for (int i = 0; i < strings.Length; i++) strings[i] = this.LookupStringFromId(stringIds[i]); return strings; } private static byte[] getXomTypeXBytes(string name) { switch (name) { case "WeaponFactoryContainer": return new byte[0x10] { 0xA5, 0x36, 0x42, 0x20, 0xF4, 0x4D, 0x60, 0x4F, 0x90, 0x97, 0x4F, 0xA8, 0x87, 0xD5, 0xC7, 0x4D }; case "StoreWeaponFactory": return new byte[0x10] { 0xB5, 0x2C, 0x2F, 0xE3, 0x3A, 0x13, 0x1C, 0x4F, 0xB6, 0x4A, 0x81, 0xB8, 0x97, 0x30, 0x2E, 0x82 }; case "WeaponFactoryCollective": return new byte[0x10] { 0xB2, 0x4A, 0x97, 0x29, 0xF8, 0x2E, 0x3D, 0x4E, 0xB7, 0x98, 0xF5, 0x8D, 0x3F, 0x36, 0x07, 0xFF }; case "StoredTeamData": return new byte[0x10] { 0x55, 0xB4, 0x6A, 0x78, 0x13, 0x63, 0xC6, 0x4D, 0xA1, 0xA3, 0xA5, 0x4B, 0x67, 0xD5, 0x2C, 0xDE }; case "HighScoreData": return new byte[0x10] { 0xED, 0xB1, 0x58, 0x90, 0x67, 0x06, 0x31, 0x44, 0xBC, 0x6B, 0x7E, 0x56, 0x6C, 0x8C, 0xBC, 0xE4 }; case "TeamDataColective": return new byte[0x10] { 0x39, 0x11, 0x7C, 0x55, 0xF9, 0xEA, 0xBF, 0x49, 0x99, 0x8D, 0x05, 0xD8, 0x52, 0x59, 0x86, 0x55 }; case "WeaponSettingsData": return new byte[0x10] { 0x16, 0x2E, 0xAC, 0x14, 0x90, 0x16, 0x97, 0x43, 0xBA, 0x75, 0x20, 0x4D, 0xAC, 0x1C, 0x56, 0x3F }; case "SchemeData": return new byte[0x10] { 0x6B, 0xDD, 0x69, 0x39, 0x1A, 0xAF, 0xBF, 0x40, 0xBF, 0x2C, 0x21, 0xE3, 0x7C, 0x4B, 0xFC, 0xFE }; case "SchemeColective": return new byte[0x10] { 0x1E, 0x40, 0x13, 0xDF, 0xC6, 0x8F, 0x50, 0x44, 0x8C, 0x7F, 0x55, 0xC6, 0x72, 0xA7, 0x27, 0x25 }; case "WXFE_UnlockableItem": return new byte[0x10] { 0x72, 0x12, 0xCB, 0x5B, 0x48, 0xBF, 0x9F, 0x4A, 0xA2, 0xD3, 0xD1, 0x21, 0xA9, 0x82, 0xD7, 0x3A }; case "InputDetailsContainer": return new byte[0x10] { 0x66, 0xE0, 0x60, 0xA5, 0xAB, 0x92, 0xC2, 0x43, 0x8E, 0x71, 0x46, 0xE8, 0x34, 0xF5, 0xDF, 0x80 }; case "InputEventMappingContainer": return new byte[0x10] { 0x30, 0x7B, 0x4E, 0x74, 0x00, 0xED, 0x46, 0x4C, 0xA4, 0x5D, 0x87, 0x0C, 0xD3, 0x52, 0x81, 0xD8 }; case "InputMappingDetails": return new byte[0x10] { 0x7B, 0x26, 0x21, 0x39, 0xC6, 0x3E, 0x69, 0x4C, 0xB7, 0xDD, 0x19, 0x9A, 0xED, 0xEB, 0x71, 0x5C }; case "StatsContainer": return new byte[0x10] { 0x13, 0x54, 0xBC, 0xDF, 0x36, 0x80, 0x71, 0x4F, 0x83, 0x71, 0x6F, 0x3B, 0x5D, 0x87, 0x63, 0xED }; case "StoredStatsCollective": return new byte[0x10] { 0x25, 0x1D, 0x56, 0xF7, 0x90, 0xD3, 0xF9, 0x4B, 0xA7, 0xE3, 0xE6, 0x73, 0xD6, 0x75, 0xCE, 0x03 }; case "TeamAwardsContainer": return new byte[0x10] { 0xBB, 0x77, 0x14, 0x26, 0x44, 0xC0, 0xAC, 0x4C, 0xA4, 0x18, 0x90, 0x8F, 0xE9, 0x07, 0x5C, 0xF8 }; case "XResourceDetails": return new byte[0x10] { 0xF2, 0x56, 0x75, 0xE7, 0x95, 0xA4, 0x1A, 0x49, 0x85, 0x63, 0x00, 0x6C, 0xE2, 0x57, 0x72, 0x44 }; case "XIntResourceDetails": return new byte[0x10] { 0x8D, 0x9B, 0x88, 0x7D, 0x14, 0x2C, 0x5E, 0x44, 0x96, 0x1F, 0x5E, 0x0E, 0x55, 0x20, 0x41, 0x6A }; case "XUintResourceDetails": return new byte[0x10] { 0x7C, 0x11, 0xE5, 0x93, 0xDA, 0x8A, 0xFB, 0x4A, 0xB1, 0x7A, 0x66, 0x8D, 0x0B, 0xDD, 0x15, 0xE6 }; case "XStringResourceDetails": return new byte[0x10] { 0x72, 0x58, 0xDC, 0x7C, 0xA3, 0x2C, 0xCC, 0x4F, 0xAE, 0x66, 0x43, 0x10, 0x71, 0xE5, 0xCE, 0x67 }; case "XFloatResourceDetails": return new byte[0x10] { 0x5F, 0x9D, 0x88, 0x08, 0x39, 0x80, 0xEA, 0x4C, 0xBA, 0x5B, 0x9C, 0x05, 0x9A, 0xC0, 0x57, 0x0E }; case "XVectorResourceDetails": return new byte[0x10] { 0x63, 0x45, 0x1F, 0x07, 0xB3, 0x8C, 0x96, 0x49, 0xB7, 0x9F, 0xB0, 0xF5, 0xCA, 0xEC, 0x9D, 0x99 }; case "XContainerResourceDetails": return new byte[0x10] { 0x20, 0xBF, 0xB6, 0xE0, 0x0B, 0xF8, 0x29, 0x43, 0xB6, 0xEB, 0x19, 0x98, 0xED, 0x8A, 0xEC, 0x1F }; case "XDataBank": return new byte[0x10] { 0xAC, 0x61, 0xE6, 0x93, 0x41, 0xD9, 0x2A, 0x44, 0xA1, 0x8E, 0xE9, 0x9B, 0x79, 0x58, 0x79, 0xDC }; case "XContainer": return new byte[0x10] { 0x46, 0xD4, 0x1C, 0x5E, 0xA3, 0x48, 0xFE, 0x44, 0xA5, 0x5A, 0xE2, 0x47, 0xB8, 0xF5, 0xE7, 0x13 }; default: return new byte[0x10]; } } private static int findInStringArray(string str, string[] arr) { for(int i = 0; i < arr.Length; i++) { if (str.Equals(arr[i], StringComparison.InvariantCulture)) return i; } return -1; } private int getXomTypeInsertPos(string typeName) { XomType[] types = xomFile.XomTypes; string[] typesOrder = new string[] { "WeaponFactoryContainer", "StoreWeaponFactory", "WeaponFactoryCollective", "StoredTeamData", "HighScoreData", "TeamDataColective", "WeaponSettingsData", "SchemeData", "SchemeColective", "WXFE_UnlockableItem", "InputDetailsContainer", "InputEventMappingContainer", "InputMappingDetails", "StatsContainer", "StoredStatsCollective", "TeamAwardsContainer", "XResourceDetails", "XIntResourceDetails", "XUintResourceDetails", "XStringResourceDetails", "XFloatResourceDetails", "XVectorResourceDetails", "XContainerResourceDetails", "XDataBank", "XContainer" }; int loc = findInStringArray(typeName, typesOrder); string[] strs = new string[typesOrder.Length]; for (int i = 0; i < types.Length; i++) { int f = findInStringArray(types[i].Name, typesOrder); strs[f] = types[i].Name; } strs[loc] = typeName; int ins = 0; foreach(string str in strs) { if (str == null) continue; if (str == typeName) break; ins++; } return ins - 1; } public void CreateXomType(string typeName) { byte[] xbytes = getXomTypeXBytes(typeName); int insertInto = getXomTypeInsertPos(typeName); xomFile.CreateXomType(xbytes, typeName, insertInto); } public XomContainer CreateContainer(string type) { try { return xomFile.CreateContainer(type); } catch(XomTypeNotFoundException) { this.CreateXomType(type); return this.CreateContainer(type); } } public XomContainer LookupContainerById(int id) { return xomFile.GetContainerById(id); } public XomContainer LookupContainerByUuid(string uuid) { return xomFile.GetContainerByUuid(uuid); } public XomString LookupStringFromId(int id) { return xomFile.GetStringById(id); } public XomString LookupString(string value) { return xomFile.AddOrGetString(value); } public W4SaveFile(XomFile w4Save) { this.xomFile = w4Save; Load(); } } }