public static int SplitFile(string datafilename, string inifilename, string projectFolderName) { try { byte[] datafile = File.ReadAllBytes(datafilename); IniData inifile = IniSerializer.Deserialize <IniData>(inifilename); if (inifile.MD5 != null && inifile.MD5.Count > 0) { string datahash = HelperFunctions.FileHash(datafile); if (!inifile.MD5.Any(h => h.Equals(datahash, StringComparison.OrdinalIgnoreCase))) { Console.WriteLine("The file {0} is not valid for use with the INI {1}.", datafilename, inifilename); return((int)ERRORVALUE.InvalidDataMapping); } } ByteConverter.BigEndian = SonicRetro.SAModel.ByteConverter.BigEndian = inifile.BigEndian; Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(datafilename)); if (inifile.Compressed) { datafile = FraGag.Compression.Prs.Decompress(datafile); } uint imageBase = HelperFunctions.SetupEXE(ref datafile) ?? inifile.ImageBase.Value; if (Path.GetExtension(datafilename).Equals(".rel", StringComparison.OrdinalIgnoreCase)) { HelperFunctions.FixRELPointers(datafile); } bool SA2 = inifile.Game == Game.SA2 | inifile.Game == Game.SA2B; ModelFormat modelfmt = 0; LandTableFormat landfmt = 0; switch (inifile.Game) { case Game.SA1: modelfmt = ModelFormat.Basic; landfmt = LandTableFormat.SA1; break; case Game.SADX: modelfmt = ModelFormat.BasicDX; landfmt = LandTableFormat.SADX; break; case Game.SA2: case Game.SA2B: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2; break; } int itemcount = 0; Dictionary <string, MasterObjectListEntry> masterobjlist = new Dictionary <string, MasterObjectListEntry>(); Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >(); Stopwatch timer = new Stopwatch(); timer.Start(); foreach (KeyValuePair <string, SA_Tools.FileInfo> item in inifile.Files) { if (string.IsNullOrEmpty(item.Key)) { continue; } string filedesc = item.Key; SA_Tools.FileInfo data = item.Value; Dictionary <string, string> customProperties = data.CustomProperties; string type = data.Type; int address = data.Address; bool nohash = false; string fileOutputPath = string.Concat(projectFolderName, data.Filename); Console.WriteLine(item.Key + ": " + data.Address.ToString("X") + " → " + fileOutputPath); Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); switch (type) { case "landtable": new LandTable(datafile, address, imageBase, landfmt) { Description = item.Key }.SaveToFile(fileOutputPath, landfmt); break; case "model": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, modelfmt); } break; case "basicmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.Basic); } break; case "basicdxmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.BasicDX); } break; case "chunkmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.Chunk); } break; case "action": { NJS_ACTION ani = new NJS_ACTION(datafile, address, imageBase, modelfmt); ani.Animation.Name = filedesc; ani.Animation.Save(fileOutputPath); } break; case "animation": new NJS_MOTION(datafile, address, imageBase, int.Parse(customProperties["numparts"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo)) { Name = filedesc } .Save(fileOutputPath); break; case "objlist": { ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2); if (inifile.MasterObjectList != null) { foreach (ObjectListEntry obj in objs) { if (!masterobjlist.ContainsKey(obj.CodeString)) { masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj)); } if (!objnamecounts.ContainsKey(obj.CodeString)) { objnamecounts.Add(obj.CodeString, new Dictionary <string, int>() { { obj.Name, 1 } }); } else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name)) { objnamecounts[obj.CodeString].Add(obj.Name, 1); } else { objnamecounts[obj.CodeString][obj.Name]++; } } } objs.Save(fileOutputPath); } break; case "startpos": if (SA2) { SA2StartPosList.Load(datafile, address).Save(fileOutputPath); } else { SA1StartPosList.Load(datafile, address).Save(fileOutputPath); } break; case "texlist": TextureList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "leveltexlist": new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath); break; case "triallevellist": TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath); break; case "bosslevellist": BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath); break; case "fieldstartpos": FieldStartPosList.Load(datafile, address).Save(fileOutputPath); break; case "soundtestlist": SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "musiclist": { int muscnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath); } break; case "soundlist": SoundList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "stringarray": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); Languages lang = Languages.Japanese; if (data.CustomProperties.ContainsKey("language")) { lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true); } StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath); } break; case "nextlevellist": NextLevelList.Load(datafile, address).Save(fileOutputPath); break; case "cutscenetext": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[] hashes); data.MD5Hash = string.Join(",", hashes); nohash = true; } break; case "recapscreen": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes); string[] hash2 = new string[hashes.Length]; for (int i = 0; i < hashes.Length; i++) { hash2[i] = string.Join(",", hashes[i]); } data.MD5Hash = string.Join(":", hash2); nohash = true; } break; case "npctext": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes); string[] hash2 = new string[hashes.Length]; for (int i = 0; i < hashes.Length; i++) { hash2[i] = string.Join(",", hashes[i]); } data.MD5Hash = string.Join(":", hash2); nohash = true; } break; case "levelclearflags": LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath); break; case "deathzone": { List <DeathZoneFlags> flags = new List <DeathZoneFlags>(); string path = Path.GetDirectoryName(fileOutputPath); List <string> hashes = new List <string>(); int num = 0; while (ByteConverter.ToUInt32(datafile, address + 4) != 0) { flags.Add(new DeathZoneFlags(datafile, address)); string file = Path.Combine(path, num++.ToString(NumberFormatInfo.InvariantInfo) + (modelfmt == ModelFormat.Chunk ? ".sa2mdl" : ".sa1mdl")); ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt), null, null, null, null, null, modelfmt); hashes.Add(HelperFunctions.FileHash(file)); address += 8; } flags.ToArray().Save(fileOutputPath); hashes.Insert(0, HelperFunctions.FileHash(fileOutputPath)); data.MD5Hash = string.Join(",", hashes.ToArray()); nohash = true; } break; case "skyboxscale": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath); } break; case "stageselectlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath); } break; case "levelrankscores": LevelRankScoresList.Load(datafile, address).Save(fileOutputPath); break; case "levelranktimes": LevelRankTimesList.Load(datafile, address).Save(fileOutputPath); break; case "endpos": SA2EndPosList.Load(datafile, address).Save(fileOutputPath); break; case "animationlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath); } break; case "levelpathlist": { List <string> hashes = new List <string>(); ushort lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); while (lvlnum != 0xFFFF) { int ptr = ByteConverter.ToInt32(datafile, address + 4); if (ptr != 0) { ptr = (int)((uint)ptr - imageBase); SA1LevelAct level = new SA1LevelAct(lvlnum); string lvldir = Path.Combine(fileOutputPath, level.ToString()); PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes); hashes.Add(level.ToString() + ":" + string.Join(",", lvlhashes)); } address += 8; lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); } data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "pathlist": { PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes); data.MD5Hash = string.Join(",", hashes.ToArray()); nohash = true; } break; case "stagelightdatalist": SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath); break; case "weldlist": WeldList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "bmitemattrlist": BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "creditstextlist": CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "animindexlist": { Directory.CreateDirectory(fileOutputPath); List <string> hashes = new List <string>(); int i = ByteConverter.ToInt16(datafile, address); while (i != -1) { new NJS_MOTION(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, ByteConverter.ToInt16(datafile, address + 2)) .Save(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); hashes.Add(i.ToString(NumberFormatInfo.InvariantInfo) + ":" + HelperFunctions.FileHash(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim")); address += 8; i = ByteConverter.ToInt16(datafile, address); } data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "storysequence": SA2StoryList.Load(datafile, address).Save(fileOutputPath); break; default: // raw binary { byte[] bin = new byte[int.Parse(customProperties["size"], NumberStyles.HexNumber)]; Array.Copy(datafile, address, bin, 0, bin.Length); File.WriteAllBytes(fileOutputPath, bin); } break; } if (!nohash) { data.MD5Hash = HelperFunctions.FileHash(fileOutputPath); } itemcount++; } if (inifile.MasterObjectList != null) { foreach (KeyValuePair <string, MasterObjectListEntry> obj in masterobjlist) { KeyValuePair <string, int> name = new KeyValuePair <string, int>(); foreach (KeyValuePair <string, int> it in objnamecounts[obj.Key]) { if (it.Value > name.Value) { name = it; } } obj.Value.Name = name.Key; obj.Value.Names = objnamecounts[obj.Key].Select((it) => it.Key).ToArray(); } string masterObjectListOutputPath = string.Concat(projectFolderName, inifile.MasterObjectList); IniSerializer.Serialize(masterobjlist, masterObjectListOutputPath); } IniSerializer.Serialize(inifile, Path.Combine(projectFolderName, Path.GetFileNameWithoutExtension(datafilename) + "_data.ini")); timer.Stop(); Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds."); Console.WriteLine(); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); Console.WriteLine("Press any key to exit."); Console.ReadLine(); return((int)ERRORVALUE.UnhandledException); } return((int)ERRORVALUE.Success); }
static void Main(string[] args) { string[] arguments = Environment.GetCommandLineArgs(); Game game; string filename; string dir = Environment.CurrentDirectory; bool bigendian = false; if (args.Length == 0) { Console.WriteLine("USplit is a tool that lets you extract any data supported by SA Tools from any binary file."); Console.WriteLine("Usage: usplit <GAME> <FILENAME> <KEY> <TYPE> <ADDRESS> <PARAMETER1> <PARAMETER2> [-name <NAME>]\n"); Console.WriteLine("Argument description:"); Console.WriteLine("<GAME>: SA1, SADX, SA2, SA2B. Add '_b' (e.g. SADX_b) to switch to Big Endian.\n"); Console.WriteLine("<FILENAME>: The name of the binary file, e.g. sonic.exe.\n"); Console.WriteLine("<KEY>: Binary key, e.g. 400000 for sonic.exe or C900000 for SA1 STG file.\n"); Console.WriteLine("<TYPE>: One of the following:\n" + "binary <length> [hex],\nlandtable, model, basicmodel, basicdxmodel, chunkmodel, gcmodel, action, animation <NJS_OBJECT address> [shortrot],\n" + "objlist, startpos, texlist, leveltexlist, triallevellist, bosslevellist, fieldstartpos, soundlist, soundtestlist,\nnextlevellist, " + "levelclearflags, deathzone, levelrankscores, levelranktimes, endpos, levelpathlist, pathlist,\nstagelightdatalist, weldlist" + "bmitemattrlist, creditstextlist, animindexlist, storysequence, musiclist <count>,\n" + "stringarray <count> [language], skyboxscale <count>, stageselectlist <count>, animationlist <count>,\n" + "masterstringlist <count>, cutscenetext <count>, recapscreen <count>, npctext <count>\n"); Console.WriteLine("<ADDRESS>: The location of data in the file.\n"); Console.WriteLine("<PARAMETER1>: length, count, secondary address etc. depending on data type\n"); Console.WriteLine("<PARAMETER2>: 'hex' for binary to read length as hexadecimal, 'shortrot' for animation to read rotation as short\n"); Console.WriteLine("<NAME>: Output file name (optional)\n"); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); return; } //Args list: game, filename, key, type, address, [address2/count], [language], [name] switch (args[0]) { case "SA1": game = Game.SA1; break; case "SA1_b": game = Game.SA1; bigendian = true; break; case "SADX": game = Game.SADX; break; case "SADX_b": game = Game.SADX; bigendian = true; break; case "SA2": game = Game.SA2; break; case "SA2_b": game = Game.SA2; bigendian = true; break; case "SA2B": game = Game.SA2B; break; case "SA2B_b": game = Game.SA2B; bigendian = true; break; default: return; } string model_extension = ".sa1mdl"; string landtable_extension = ".sa1lvl"; ByteConverter.BigEndian = SonicRetro.SAModel.ByteConverter.BigEndian = bigendian; filename = args[1]; byte[] datafile = File.ReadAllBytes(filename); if (Path.GetExtension(filename).ToLowerInvariant() == ".prs") { datafile = FraGag.Compression.Prs.Decompress(datafile); } Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(filename)); uint imageBase = uint.Parse(args[2], NumberStyles.AllowHexSpecifier); string type = args[3]; int address = int.Parse(args[4], NumberStyles.AllowHexSpecifier); bool SA2 = game == Game.SA2 | game == Game.SA2B; ModelFormat modelfmt = ModelFormat.BasicDX; LandTableFormat landfmt = LandTableFormat.SADX; switch (game) { case Game.SA1: modelfmt = ModelFormat.Basic; landfmt = LandTableFormat.SA1; model_extension = ".sa1mdl"; landtable_extension = ".sa1lvl"; break; case Game.SADX: modelfmt = ModelFormat.BasicDX; landfmt = LandTableFormat.SADX; model_extension = ".sa1mdl"; landtable_extension = ".sa1lvl"; break; case Game.SA2: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2; model_extension = ".sa2mdl"; landtable_extension = ".sa2lvl"; break; case Game.SA2B: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2B; model_extension = ".sa2mdl"; landtable_extension = ".sa2blvl"; break; } Dictionary <string, MasterObjectListEntry> masterobjlist = new Dictionary <string, MasterObjectListEntry>(); Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >(); string fileOutputPath = dir + "\\" + address.ToString("X"); Console.WriteLine("Game: {0}, file: {1}, key: 0x{2}, splitting {3} at 0x{4}", game.ToString(), filename, imageBase.ToString("X"), type, address.ToString("X")); if (args[args.Length - 2] == "-name") { fileOutputPath = dir + "\\" + args[args.Length - 1]; Console.WriteLine("Name: {0}", args[args.Length - 1]); } switch (type) { case "landtable": new LandTable(datafile, address, imageBase, landfmt).SaveToFile(fileOutputPath + landtable_extension, landfmt); break; case "model": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + model_extension, mdl, null, null, null, null, modelfmt); } break; case "basicmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa1mdl", mdl, null, null, null, null, ModelFormat.Basic); } break; case "basicdxmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa1mdl", mdl, null, null, null, null, ModelFormat.BasicDX); } break; case "chunkmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa2mdl", mdl, null, null, null, null, ModelFormat.Chunk); } break; case "gcmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.GC, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa2mdl", mdl, null, null, null, null, ModelFormat.GC); } break; case "action": { NJS_ACTION ani = new NJS_ACTION(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>()); ani.Animation.Save(fileOutputPath + ".saanim"); string[] mdlanis = new string[0]; NJS_OBJECT mdl = ani.Model; mdlanis = (fileOutputPath + ".saanim").Split(','); ModelFile.CreateFile(fileOutputPath + "_model" + model_extension, mdl, mdlanis, null, null, null, modelfmt); } break; case "animation": { bool shortrot_enabled = false; if (args.Length > 6 && args[6] == "shortrot") { shortrot_enabled = true; } NJS_OBJECT mdl = new NJS_OBJECT(datafile, int.Parse(args[5], NumberStyles.AllowHexSpecifier), imageBase, modelfmt, new Dictionary <int, Attach>()); new NJS_MOTION(datafile, address, imageBase, mdl.CountAnimated(), shortrot: shortrot_enabled).Save(fileOutputPath + ".saanim"); string[] mdlanis = new string[0]; mdlanis = (fileOutputPath + ".saanim").Split(','); ModelFile.CreateFile(fileOutputPath + "_model" + model_extension, mdl, mdlanis, null, null, null, modelfmt); } break; case "objlist": { ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2); foreach (ObjectListEntry obj in objs) { if (!masterobjlist.ContainsKey(obj.CodeString)) { masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj)); } if (!objnamecounts.ContainsKey(obj.CodeString)) { objnamecounts.Add(obj.CodeString, new Dictionary <string, int>() { { obj.Name, 1 } }); } else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name)) { objnamecounts[obj.CodeString].Add(obj.Name, 1); } else { objnamecounts[obj.CodeString][obj.Name]++; } } objs.Save(fileOutputPath + ".ini"); } break; case "startpos": if (SA2) { SA2StartPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); } else { SA1StartPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); } break; case "texlist": TextureList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "leveltexlist": new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "triallevellist": TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath + ".ini"); break; case "bosslevellist": BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath + ".ini"); break; case "fieldstartpos": FieldStartPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "soundtestlist": SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "musiclist": { int muscnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath + ".ini"); } break; case "soundlist": SoundList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "stringarray": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); Languages lang = Languages.Japanese; if (args.Length > 6) { lang = (Languages)Enum.Parse(typeof(Languages), args[6], true); } StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath + ".txt"); } break; case "nextlevellist": NextLevelList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "cutscenetext": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[] hashes); } break; case "recapscreen": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[][] hashes); } break; case "npctext": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[][] hashes); } break; case "levelclearflags": LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath + ".ini"); break; case "deathzone": { List <DeathZoneFlags> flags = new List <DeathZoneFlags>(); string path = Path.GetDirectoryName(fileOutputPath); List <string> hashes = new List <string>(); int num = 0; while (ByteConverter.ToUInt32(datafile, address + 4) != 0) { flags.Add(new DeathZoneFlags(datafile, address)); string file = Path.Combine(path, num++.ToString(NumberFormatInfo.InvariantInfo) + (modelfmt == ModelFormat.Chunk ? ".sa2mdl" : ".sa1mdl")); ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt, new Dictionary <int, Attach>()), null, null, null, null, modelfmt); address += 8; } flags.ToArray().Save(fileOutputPath + ".ini"); } break; case "skyboxscale": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".ini"); } break; case "stageselectlist": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath + ".ini"); } break; case "levelrankscores": LevelRankScoresList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "levelranktimes": LevelRankTimesList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "endpos": SA2EndPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "animationlist": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath + ".ini"); } break; case "levelpathlist": { ushort lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); while (lvlnum != 0xFFFF) { int ptr = ByteConverter.ToInt32(datafile, address + 4); if (ptr != 0) { ptr = (int)((uint)ptr - imageBase); SA1LevelAct level = new SA1LevelAct(lvlnum); string lvldir = Path.Combine(fileOutputPath, level.ToString()); PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes); } address += 8; lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); } } break; case "pathlist": { PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes); } break; case "stagelightdatalist": SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath); break; case "weldlist": WeldList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "bmitemattrlist": BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "creditstextlist": CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "animindexlist": { Directory.CreateDirectory(fileOutputPath); List <string> hashes = new List <string>(); int i = ByteConverter.ToInt16(datafile, address); while (i != -1) { new NJS_MOTION(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, ByteConverter.ToInt16(datafile, address + 2)) .Save(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); hashes.Add(i.ToString(NumberFormatInfo.InvariantInfo) + ":" + HelperFunctions.FileHash(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim")); address += 8; i = ByteConverter.ToInt16(datafile, address); } } break; case "storysequence": SA2StoryList.Load(datafile, address).Save(fileOutputPath); break; case "masterstringlist": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); for (int l = 0; l < 5; l++) { Languages lng = (Languages)l; System.Text.Encoding enc = HelperFunctions.GetEncoding(game, lng); string ld = Path.Combine(fileOutputPath, lng.ToString()); Directory.CreateDirectory(ld); int ptr = datafile.GetPointer(address, imageBase); for (int i = 0; i < cnt; i++) { int ptr2 = datafile.GetPointer(ptr, imageBase); if (ptr2 != 0) { string fn = Path.Combine(ld, $"{i}.txt"); File.WriteAllText(fn, datafile.GetCString(ptr2, enc).Replace("\n", "\r\n")); } ptr += 4; } address += 4; } } break; case "binary": { int length; if (args.Length > 6 && args[6] == "hex") { length = int.Parse(args[5], NumberStyles.AllowHexSpecifier); } else { length = int.Parse(args[5]); } byte[] bin = new byte[length]; Array.Copy(datafile, address, bin, 0, bin.Length); File.WriteAllBytes(fileOutputPath + ".bin", bin); Console.WriteLine("Length: {0} (0x{1}) bytes", length.ToString(), length.ToString("X")); } break; default: break; } }
public static int SplitFile(string datafilename, string inifilename, string projectFolderName, bool nometa = false, bool nolabel = false) { #if !DEBUG try #endif { byte[] datafile; byte[] datafile_temp = File.ReadAllBytes(datafilename); IniData inifile = IniSerializer.Deserialize <IniData>(inifilename); string listfile = Path.Combine(Path.GetDirectoryName(inifilename), Path.GetFileNameWithoutExtension(datafilename) + "_labels.txt"); Dictionary <int, string> labels = new Dictionary <int, string>(); if (File.Exists(listfile) && !nolabel) { labels = IniSerializer.Deserialize <Dictionary <int, string> >(listfile); } if (inifile.StartOffset != 0) { byte[] datafile_new = new byte[inifile.StartOffset + datafile_temp.Length]; datafile_temp.CopyTo(datafile_new, inifile.StartOffset); datafile = datafile_new; } else { datafile = datafile_temp; } if (inifile.MD5 != null && inifile.MD5.Count > 0) { string datahash = HelperFunctions.FileHash(datafile); if (!inifile.MD5.Any(h => h.Equals(datahash, StringComparison.OrdinalIgnoreCase))) { Console.WriteLine("The file {0} is not valid for use with the INI {1}.", datafilename, inifilename); return((int)SplitERRORVALUE.InvalidDataMapping); } } ByteConverter.BigEndian = SonicRetro.SAModel.ByteConverter.BigEndian = inifile.BigEndian; ByteConverter.Reverse = SonicRetro.SAModel.ByteConverter.Reverse = inifile.Reverse; Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(datafilename)); if (Path.GetExtension(datafilename).ToLowerInvariant() == ".prs" || (inifile.Compressed && Path.GetExtension(datafilename).ToLowerInvariant() != ".bin")) { datafile = FraGag.Compression.Prs.Decompress(datafile); } uint imageBase = HelperFunctions.SetupEXE(ref datafile) ?? inifile.ImageBase.Value; if (Path.GetExtension(datafilename).Equals(".rel", StringComparison.OrdinalIgnoreCase)) { datafile = HelperFunctions.DecompressREL(datafile); HelperFunctions.FixRELPointers(datafile, imageBase); } bool SA2 = inifile.Game == Game.SA2 | inifile.Game == Game.SA2B; ModelFormat modelfmt_def = 0; LandTableFormat landfmt_def = 0; switch (inifile.Game) { case Game.SA1: modelfmt_def = ModelFormat.Basic; landfmt_def = LandTableFormat.SA1; break; case Game.SADX: modelfmt_def = ModelFormat.BasicDX; landfmt_def = LandTableFormat.SADX; break; case Game.SA2: modelfmt_def = ModelFormat.Chunk; landfmt_def = LandTableFormat.SA2; break; case Game.SA2B: modelfmt_def = ModelFormat.GC; landfmt_def = LandTableFormat.SA2B; break; } int itemcount = 0; Dictionary <string, MasterObjectListEntry> masterobjlist = new Dictionary <string, MasterObjectListEntry>(); Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >(); Stopwatch timer = new Stopwatch(); timer.Start(); foreach (KeyValuePair <string, SA_Tools.FileInfo> item in new List <KeyValuePair <string, SA_Tools.FileInfo> >(inifile.Files)) { if (string.IsNullOrEmpty(item.Key)) { continue; } string filedesc = item.Key; SA_Tools.FileInfo data = item.Value; Dictionary <string, string> customProperties = data.CustomProperties; string type = data.Type; int address = data.Address; bool nohash = false; string fileOutputPath = Path.Combine(projectFolderName, data.Filename); Console.WriteLine(item.Key + ": " + data.Address.ToString("X") + " → " + fileOutputPath); Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); switch (type) { case "landtable": LandTableFormat format = data.CustomProperties.ContainsKey("format") ? (LandTableFormat)Enum.Parse(typeof(LandTableFormat), data.CustomProperties["format"]) : landfmt_def; new LandTable(datafile, address, imageBase, format, labels) { Description = item.Key }.SaveToFile(fileOutputPath, landfmt_def, nometa); break; case "model": case "basicmodel": case "basicdxmodel": case "chunkmodel": case "gcmodel": { ModelFormat mdlformat; switch (type) { case "basicmodel": mdlformat = ModelFormat.Basic; break; case "basicdxmodel": mdlformat = ModelFormat.BasicDX; break; case "chunkmodel": mdlformat = ModelFormat.Chunk; break; case "gcmodel": mdlformat = ModelFormat.GC; break; case "model": default: mdlformat = modelfmt_def; break; } if (data.CustomProperties.ContainsKey("format")) { mdlformat = (ModelFormat)Enum.Parse(typeof(ModelFormat), data.CustomProperties["format"]); } NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, mdlformat, labels, new Dictionary <int, Attach>()); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, null, item.Key, null, mdlformat, nometa); } break; case "action": { ModelFormat modelfmt_act = data.CustomProperties.ContainsKey("format") ? (ModelFormat)Enum.Parse(typeof(ModelFormat), data.CustomProperties["format"]) : modelfmt_def; NJS_ACTION ani = new NJS_ACTION(datafile, address, imageBase, modelfmt_act, labels, new Dictionary <int, Attach>()); if (!labels.ContainsValue(ani.Name) && !nolabel) { ani.Name = filedesc; } if (customProperties.ContainsKey("numparts")) { ani.Animation.ModelParts = int.Parse(customProperties["numparts"]); } if (ani.Animation.ModelParts == 0) { Console.WriteLine("Action {0} has no model data!", ani.Name); continue; } ani.Animation.Save(fileOutputPath, nometa); } break; case "animation": case "motion": int numparts = 0; if (customProperties.ContainsKey("numparts")) { numparts = int.Parse(customProperties["numparts"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo); } else { Console.WriteLine("Number of parts not specified for {0}", filedesc); } if (customProperties.ContainsKey("shortrot")) { NJS_MOTION mot = new NJS_MOTION(datafile, address, imageBase, numparts, labels, bool.Parse(customProperties["shortrot"])); if (!labels.ContainsKey(address) && !nolabel) { mot.Name = filedesc; } mot.Save(fileOutputPath, nometa); } else { NJS_MOTION mot = new NJS_MOTION(datafile, address, imageBase, numparts, labels); if (!labels.ContainsKey(address) && !nolabel) { mot.Name = filedesc; } mot.Save(fileOutputPath, nometa); } break; case "objlist": { ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2); if (inifile.MasterObjectList != null) { foreach (ObjectListEntry obj in objs) { if (!masterobjlist.ContainsKey(obj.CodeString)) { masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj)); } if (!objnamecounts.ContainsKey(obj.CodeString)) { objnamecounts.Add(obj.CodeString, new Dictionary <string, int>() { { obj.Name, 1 } }); } else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name)) { objnamecounts[obj.CodeString].Add(obj.Name, 1); } else { objnamecounts[obj.CodeString][obj.Name]++; } } } objs.Save(fileOutputPath); } break; case "startpos": if (SA2) { SA2StartPosList.Load(datafile, address).Save(fileOutputPath); } else { SA1StartPosList.Load(datafile, address).Save(fileOutputPath); } break; case "texlist": TextureList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "texnamearray": TexnameArray texnames = new TexnameArray(datafile, address, imageBase); texnames.Save(fileOutputPath); break; case "texlistarray": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); for (int i = 0; i < cnt; i++) { uint ptr = BitConverter.ToUInt32(datafile, address); if (data.Filename != null && ptr != 0) { ptr -= imageBase; TexnameArray texarr = new TexnameArray(datafile, (int)ptr, imageBase); string fn = Path.Combine(fileOutputPath, i.ToString("D3", NumberFormatInfo.InvariantInfo) + ".txt"); if (data.CustomProperties.ContainsKey("filename" + i.ToString())) { fn = Path.Combine(fileOutputPath, data.CustomProperties["filename" + i.ToString()] + ".txt"); } if (!Directory.Exists(Path.GetDirectoryName(fn))) { Directory.CreateDirectory(Path.GetDirectoryName(fn)); } texarr.Save(fn); } address += 4; } nohash = true; } break; case "leveltexlist": new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath); break; case "triallevellist": TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath); break; case "bosslevellist": BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath); break; case "fieldstartpos": FieldStartPosList.Load(datafile, address).Save(fileOutputPath); break; case "soundtestlist": SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "musiclist": { int muscnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath); } break; case "soundlist": SoundList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "stringarray": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); Languages lang = Languages.Japanese; if (data.CustomProperties.ContainsKey("language")) { lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true); } StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath); } break; case "nextlevellist": NextLevelList.Load(datafile, address).Save(fileOutputPath); break; case "cutscenetext": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[] hashes); data.MD5Hash = string.Join(",", hashes); nohash = true; } break; case "recapscreen": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes); string[] hash2 = new string[hashes.Length]; for (int i = 0; i < hashes.Length; i++) { hash2[i] = string.Join(",", hashes[i]); } data.MD5Hash = string.Join(":", hash2); nohash = true; } break; case "npctext": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes); string[] hash2 = new string[hashes.Length]; for (int i = 0; i < hashes.Length; i++) { hash2[i] = string.Join(",", hashes[i]); } data.MD5Hash = string.Join(":", hash2); nohash = true; } break; case "levelclearflags": LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath); break; case "deathzone": { List <DeathZoneFlags> flags = new List <DeathZoneFlags>(); string path = Path.GetDirectoryName(fileOutputPath); List <string> hashes = new List <string>(); int num = 0; while (ByteConverter.ToUInt32(datafile, address + 4) != 0) { string file_tosave; if (customProperties.ContainsKey("filename" + num.ToString())) { file_tosave = customProperties["filename" + num++.ToString()]; } else { file_tosave = num++.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl"; } string file = Path.Combine(path, file_tosave); flags.Add(new DeathZoneFlags(datafile, address, file_tosave)); ModelFormat modelfmt_death = inifile.Game == Game.SADX ? ModelFormat.BasicDX : ModelFormat.Basic; // Death zones in all games except SADXPC use Basic non-DX models ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt_death, new Dictionary <int, Attach>()), null, null, null, null, modelfmt_death, nometa); hashes.Add(HelperFunctions.FileHash(file)); address += 8; } flags.ToArray().Save(fileOutputPath); hashes.Insert(0, HelperFunctions.FileHash(fileOutputPath)); data.MD5Hash = string.Join(",", hashes.ToArray()); nohash = true; } break; case "skyboxscale": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath); } break; case "stageselectlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath); } break; case "levelrankscores": LevelRankScoresList.Load(datafile, address).Save(fileOutputPath); break; case "levelranktimes": LevelRankTimesList.Load(datafile, address).Save(fileOutputPath); break; case "endpos": SA2EndPosList.Load(datafile, address).Save(fileOutputPath); break; case "animationlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath); } break; case "enemyanimationlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA2EnemyAnimInfoList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath); } break; case "sa1actionlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA1ActionInfoList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath); } break; case "motiontable": { Directory.CreateDirectory(fileOutputPath); List <MotionTableEntry> result = new List <MotionTableEntry>(); List <string> hashes = new List <string>(); bool shortrot = false; if (customProperties.ContainsKey("shortrot")) { shortrot = bool.Parse(customProperties["shortrot"]); } int nodeCount = int.Parse(data.CustomProperties["nodecount"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo); int Length = int.Parse(data.CustomProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); Dictionary <int, string> mtns = new Dictionary <int, string>(); for (int i = 0; i < Length; i++) { MotionTableEntry bmte = new MotionTableEntry(); int mtnaddr = (int)(ByteConverter.ToUInt32(datafile, address) - imageBase); if (!mtns.ContainsKey(mtnaddr)) { NJS_MOTION motion = new NJS_MOTION(datafile, mtnaddr, imageBase, nodeCount, null, shortrot); bmte.Motion = motion.Name; mtns.Add(mtnaddr, motion.Name); motion.Save(Path.Combine(fileOutputPath, $"{i}.saanim"), nometa); hashes.Add($"{i}.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{i}.saanim"))); } else { bmte.Motion = mtns[mtnaddr]; } bmte.LoopProperty = ByteConverter.ToUInt16(datafile, address + 4); bmte.Pose = ByteConverter.ToUInt16(datafile, address + 6); bmte.NextAnimation = ByteConverter.ToInt32(datafile, address + 8); bmte.TransitionSpeed = ByteConverter.ToUInt32(datafile, address + 0xC); bmte.StartFrame = ByteConverter.ToSingle(datafile, address + 0x10); bmte.EndFrame = ByteConverter.ToSingle(datafile, address + 0x14); bmte.PlaySpeed = ByteConverter.ToSingle(datafile, address + 0x18); result.Add(bmte); address += 0x1C; } IniSerializer.Serialize(result, Path.Combine(fileOutputPath, "info.ini")); hashes.Add("info.ini:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, "info.ini"))); data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "levelpathlist": { List <string> hashes = new List <string>(); ushort lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); while (lvlnum != 0xFFFF) { int ptr = ByteConverter.ToInt32(datafile, address + 4); if (ptr != 0) { ptr = (int)((uint)ptr - imageBase); SA1LevelAct level = new SA1LevelAct(lvlnum); string lvldir = Path.Combine(fileOutputPath, level.ToString()); PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes); hashes.Add(level.ToString() + ":" + string.Join(",", lvlhashes)); } address += 8; lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); } data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "pathlist": { PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes); data.MD5Hash = string.Join(",", hashes.ToArray()); nohash = true; } break; case "stagelightdatalist": SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath); break; case "weldlist": WeldList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "bmitemattrlist": BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "creditstextlist": CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "animindexlist": { Directory.CreateDirectory(fileOutputPath); List <string> hashes = new List <string>(); int i = ByteConverter.ToInt16(datafile, address); while (i != -1) { new NJS_MOTION(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, ByteConverter.ToInt16(datafile, address + 2)) .Save(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim", nometa); hashes.Add(i.ToString(NumberFormatInfo.InvariantInfo) + ":" + HelperFunctions.FileHash(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim")); address += 8; i = ByteConverter.ToInt16(datafile, address); } data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "storysequence": SA2StoryList.Load(datafile, address).Save(fileOutputPath); break; case "masterstringlist": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); for (int l = 0; l < 5; l++) { Languages lng = (Languages)l; System.Text.Encoding enc = HelperFunctions.GetEncoding(inifile.Game, lng); string ld = Path.Combine(fileOutputPath, lng.ToString()); Directory.CreateDirectory(ld); int ptr = datafile.GetPointer(address, imageBase); for (int i = 0; i < cnt; i++) { int ptr2 = datafile.GetPointer(ptr, imageBase); if (ptr2 != 0) { string fn = Path.Combine(ld, $"{i}.txt"); File.WriteAllText(fn, datafile.GetCString(ptr2, enc).Replace("\n", "\r\n")); inifile.Files.Add($"{filedesc} {lng} {i}", new FileInfo() { Type = "string", Filename = fn, PointerList = new int[] { ptr }, MD5Hash = HelperFunctions.FileHash(fn), CustomProperties = new Dictionary <string, string>() { { "language", lng.ToString() } } }); } ptr += 4; } address += 4; } inifile.Files.Remove(filedesc); nohash = true; } break; case "camera": NinjaCamera cam = new NinjaCamera(datafile, address); cam.Save(fileOutputPath); break; case "fogdatatable": int fcnt = 3; if (customProperties.ContainsKey("count")) { fcnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); } FogDataTable fga = new FogDataTable(datafile, address, imageBase, fcnt); fga.Save(fileOutputPath); break; case "palettelightlist": int count = 255; if (customProperties.ContainsKey("count")) { count = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); } PaletteLightList pllist = new PaletteLightList(datafile, address, count); pllist.Save(fileOutputPath); break; case "physicsdata": PlayerParameter plpm = new PlayerParameter(datafile, address); plpm.Save(fileOutputPath); break; default: // raw binary { byte[] bin = new byte[int.Parse(customProperties["size"], NumberStyles.HexNumber)]; Array.Copy(datafile, address, bin, 0, bin.Length); File.WriteAllBytes(fileOutputPath, bin); } break; } if (!nohash) { data.MD5Hash = HelperFunctions.FileHash(fileOutputPath); } itemcount++; } if (inifile.MasterObjectList != null) { foreach (KeyValuePair <string, MasterObjectListEntry> obj in masterobjlist) { KeyValuePair <string, int> name = new KeyValuePair <string, int>(); foreach (KeyValuePair <string, int> it in objnamecounts[obj.Key]) { if (it.Value > name.Value) { name = it; } } obj.Value.Name = name.Key; obj.Value.Names = objnamecounts[obj.Key].Select((it) => it.Key).ToArray(); } string masterObjectListOutputPath = Path.Combine(projectFolderName, inifile.MasterObjectList); IniSerializer.Serialize(masterobjlist, masterObjectListOutputPath); } IniSerializer.Serialize(inifile, Path.Combine(projectFolderName, Path.GetFileNameWithoutExtension(inifilename) + "_data.ini")); timer.Stop(); Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds."); Console.WriteLine(); } #if !DEBUG catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); Console.WriteLine("Press any key to exit."); Console.ReadLine(); return((int)SplitERRORVALUE.UnhandledException); } #endif return((int)SplitERRORVALUE.Success); }