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; } }
/// <summary> /// Saves changes made to the currently loaded stage. /// </summary> /// <param name="autoCloseDialog">Defines whether or not the progress dialog should close on completion.</param> private void SaveStage(bool autoCloseDialog) { if (!isStageLoaded) { return; } if (isSA2LVL()) { SaveSA2Data(autoCloseDialog); return; } ProgressDialog progress = new ProgressDialog("Saving stage: " + levelName, 6, true, autoCloseDialog); progress.Show(this); Application.DoEvents(); IniLevelData level = salvlini.Levels[levelID]; Directory.CreateDirectory(modSystemFolder); SA1LevelAct levelact = new SA1LevelAct(level.LevelID); progress.SetTaskAndStep("Saving:", "Geometry..."); if (LevelData.geo != null) { LevelData.geo.SaveToFile(level.LevelGeometry, LandTableFormat.SA1); } progress.StepProgress(); progress.Step = "Start positions..."; Application.DoEvents(); for (int i = 0; i < LevelData.StartPositions.Length; i++) { if (!File.Exists(salvlini.Characters[LevelData.Characters[i]].StartPositions)) { log.Add("Error saving start positions for character " + i.ToString()); osd.AddMessage("Error saving start positions for character " + i.ToString(), 180); break; } Dictionary <SA1LevelAct, SA1StartPosInfo> posini = SA1StartPosList.Load(salvlini.Characters[LevelData.Characters[i]].StartPositions); if (posini.ContainsKey(levelact)) { posini.Remove(levelact); } if (LevelData.StartPositions[i].Position.X != 0 || LevelData.StartPositions[i].Position.Y != 0 || LevelData.StartPositions[i].Position.Z != 0 || LevelData.StartPositions[i].Rotation.Y != 0) { posini.Add(levelact, new SA1StartPosInfo() { Position = LevelData.StartPositions[i].Position, YRotation = LevelData.StartPositions[i].Rotation.Y }); } posini.Save(salvlini.Characters[LevelData.Characters[i]].StartPositions); } progress.StepProgress(); progress.Step = "Death zones..."; Application.DoEvents(); if (LevelData.DeathZones != null) { DeathZoneFlags[] dzini = new DeathZoneFlags[LevelData.DeathZones.Count]; string path = Path.GetDirectoryName(level.DeathZones); for (int i = 0; i < LevelData.DeathZones.Count; i++) { dzini[i] = LevelData.DeathZones[i].Save(path, i.ToString(System.Globalization.NumberFormatInfo.InvariantInfo) + ".sa1mdl"); } dzini.Save(level.DeathZones); } progress.StepProgress(); #region Saving SET Items progress.Step = "SET items..."; Application.DoEvents(); if (!LevelData.SETItemsIsNull()) { for (int i = 0; i < LevelData.SETChars.Length; i++) { string setstr = Path.Combine(modSystemFolder, "SET" + LevelData.SETName + LevelData.SETChars[i] + ".bin"); // blank the set file if (File.Exists(setstr) || LevelData.GetSetItemCount(i) == 0) { byte[] emptyBytes = new byte[0x20]; File.WriteAllBytes(setstr, emptyBytes); } List <byte> file = new List <byte>(LevelData.GetSetItemCount(i) * 0x20 + 0x20); file.AddRange(BitConverter.GetBytes(LevelData.GetSetItemCount(i))); file.Align(0x20); foreach (SETItem item in LevelData.SETItems(i)) { file.AddRange(item.GetBytes()); } File.WriteAllBytes(setstr, file.ToArray()); } } progress.StepProgress(); #endregion #region Saving CAM Items progress.Step = "CAM items..."; Application.DoEvents(); if (LevelData.CAMItems != null) { for (int i = 0; i < LevelData.CAMItems.Length; i++) { string camString = Path.Combine(modSystemFolder, "CAM" + LevelData.SETName + LevelData.SETChars[i] + ".bin"); /*if (modpath != null) * camString = Path.Combine(modpath, camString);*/ // TODO: Handle this differently. File stream? If the user is using a symbolic link for example, we defeat the purpose by deleting it. if (File.Exists(camString)) { File.Delete(camString); } if (LevelData.CAMItems[i].Count == 0) { continue; } List <byte> file = new List <byte>(LevelData.CAMItems[i].Count * 0x40 + 0x40); // setting up file size and header file.AddRange(BitConverter.GetBytes(LevelData.CAMItems[i].Count)); file.Align(0x40); foreach (CAMItem item in LevelData.CAMItems[i]) // outputting individual components { file.AddRange(item.GetBytes()); } File.WriteAllBytes(camString, file.ToArray()); } } #endregion #region Saving Mission SET Items progress.Step = "Mission SET items..."; Application.DoEvents(); if (LevelData.MissionSETItems != null) { for (int i = 0; i < LevelData.MissionSETItems.Length; i++) { string setstr = Path.Combine(modSystemFolder, "SETMI" + level.LevelID + LevelData.SETChars[i] + ".bin"); string prmstr = Path.Combine(modSystemFolder, "PRMMI" + level.LevelID + LevelData.SETChars[i] + ".bin"); /*if (modpath != nullmodSystemPath * { * setstr = Path.Combine(modpath, setstr); * prmstr = Path.Combine(modpath, prmstr); * }*/ // TODO: Consider simply blanking the SET file instead of deleting it. // Completely deleting it might be undesirable since Sonic's layout will be loaded // in place of the missing file. And where mods are concerned, you could have conflicts // with other mods if the file is deleted. if (File.Exists(setstr)) { File.Delete(setstr); } if (File.Exists(prmstr)) { File.Delete(prmstr); } if (LevelData.MissionSETItems[i].Count == 0) { continue; } List <byte> setfile = new List <byte>(LevelData.MissionSETItems[i].Count * 0x20 + 0x20); setfile.AddRange(BitConverter.GetBytes(LevelData.MissionSETItems[i].Count)); setfile.Align(0x20); List <byte> prmfile = new List <byte>(LevelData.MissionSETItems[i].Count * 0xC + 0x20); prmfile.AddRange(new byte[] { 0, 0, 0x30, 0x56 }); prmfile.Align(0x20); foreach (MissionSETItem item in LevelData.MissionSETItems[i]) { setfile.AddRange(item.GetBytes()); prmfile.AddRange(item.GetPRMBytes()); } File.WriteAllBytes(setstr, setfile.ToArray()); File.WriteAllBytes(prmstr, prmfile.ToArray()); } } progress.StepProgress(); #endregion #region Saving Splines progress.Step = "Splines..."; Application.DoEvents(); if (LevelData.LevelSplines != null) { string splineDirectory = Path.Combine(Path.Combine(modFolder, salvlini.Paths), levelact.ToString()); int s = 0; foreach (SplineData spline in LevelData.LevelSplines) { string path = Path.Combine(splineDirectory, string.Format("{0}.ini", s)); spline.Save(path); s++; } } #endregion #region Saving Lights progress.Step = "Lights..."; Application.DoEvents(); if (stageLightList != null) { string stageLightPath = Path.Combine(modFolder, "Misc", "Stage Lights.ini"); IniSerializer.Serialize(stageLightList, stageLightPath); } if (characterLightList != null) { string charLightPath = Path.Combine(modFolder, "Misc", "Character Lights.ini"); IniSerializer.Serialize(characterLightList, charLightPath); } #endregion #region Saving Fog progress.Step = "Fog..."; Application.DoEvents(); if (stageFogList != null && salvlini.LevelFogFiles.FogEntries != null && salvlini.LevelFogFiles.FogEntries.ContainsKey(levelact.Level)) { string fogFilePath = salvlini.LevelFogFiles.FogEntries[levelact.Level]; IniSerializer.Serialize(stageFogList, fogFilePath); } #endregion progress.StepProgress(); progress.SetTaskAndStep("Save complete!"); unsaved = false; Application.DoEvents(); }
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); }
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); }
private void ExportCPP(TextWriter writer, bool SA2) { Dictionary<uint, string> pointers = new Dictionary<uint, string>(); uint imagebase = IniData.ImageBase ?? 0x400000; ModelFormat modelfmt = 0; LandTableFormat landfmt = 0; switch (IniData.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; } writer.WriteLine("// Generated by SA Tools Struct Converter"); writer.WriteLine(); if (SA2) writer.WriteLine("#include \"SA2ModLoader.h\""); else writer.WriteLine("#include \"SADXModLoader.h\""); writer.WriteLine(); Dictionary<string, string> models = new Dictionary<string, string>(); foreach (KeyValuePair<string, SA_Tools.FileInfo> item in IniData.Files.Where((a, i) => listView1.CheckedIndices.Contains(i))) { string name = item.Key.MakeIdentifier(); SA_Tools.FileInfo data = item.Value; switch (data.Type) { case "landtable": LandTable tbl = LandTable.LoadFromFile(data.Filename); name = tbl.Name; writer.WriteLine(tbl.ToStructVariables(landfmt, new List<string>())); break; case "model": SonicRetro.SAModel.NJS_OBJECT mdl = new ModelFile(data.Filename).Model; name = mdl.Name; writer.WriteLine(mdl.ToStructVariables(modelfmt == ModelFormat.BasicDX, new List<string>())); models.Add(item.Key, mdl.Name); break; case "basicmodel": mdl = new SonicRetro.SAModel.ModelFile(data.Filename).Model; name = mdl.Name; writer.WriteLine(mdl.ToStructVariables(false, new List<string>())); models.Add(item.Key, mdl.Name); break; case "basicdxmodel": mdl = new SonicRetro.SAModel.ModelFile(data.Filename).Model; name = mdl.Name; writer.WriteLine(mdl.ToStructVariables(true, new List<string>())); models.Add(item.Key, mdl.Name); break; case "chunkmodel": mdl = new SonicRetro.SAModel.ModelFile(data.Filename).Model; name = mdl.Name; writer.WriteLine(mdl.ToStructVariables(false, new List<string>())); models.Add(item.Key, mdl.Name); break; case "action": Animation ani = Animation.Load(data.Filename); name = "action_" + ani.Name.MakeIdentifier(); writer.WriteLine(ani.ToStructVariables()); writer.WriteLine("NJS_ACTION {0} = {{ &{1}, &{2} }};", name, models[data.CustomProperties["model"]], ani.Name.MakeIdentifier()); break; case "animation": ani = Animation.Load(data.Filename); name = ani.Name.MakeIdentifier(); writer.WriteLine(ani.ToStructVariables()); break; case "objlist": { ObjectListEntry[] list = ObjectList.Load(data.Filename, SA2); writer.WriteLine("ObjectListEntry {0}_list[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (ObjectListEntry obj in list) objs.Add(obj.ToStruct() + " " + obj.Name.ToComment()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); writer.WriteLine("ObjectList {0} = {{ arraylengthandptr({0}_list) }};", name); } break; case "startpos": if (SA2) { Dictionary<SA2LevelIDs, SA2StartPosInfo> list = SA2StartPosList.Load(data.Filename); writer.WriteLine("StartPosition {0}[] = {{", name); List<string> objs = new List<string>(list.Count + 1); foreach (KeyValuePair<SA2LevelIDs, SA2StartPosInfo> obj in list) objs.Add(obj.ToStruct()); objs.Add("{ LevelIDs_Invalid }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } else { Dictionary<SA1LevelAct, SA1StartPosInfo> list = SA1StartPosList.Load(data.Filename); writer.WriteLine("StartPosition {0}[] = {{", name); List<string> objs = new List<string>(list.Count + 1); foreach (KeyValuePair<SA1LevelAct, SA1StartPosInfo> obj in list) objs.Add(obj.ToStruct()); objs.Add("{ LevelIDs_Invalid }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "texlist": { TextureListEntry[] list = TextureList.Load(data.Filename); writer.WriteLine("PVMEntry {0}[] = {{", name); List<string> objs = new List<string>(list.Length + 1); foreach (TextureListEntry obj in list) objs.Add(obj.ToStruct()); objs.Add("{ 0 }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "leveltexlist": { LevelTextureList list = LevelTextureList.Load(data.Filename); writer.WriteLine("PVMEntry {0}_list[] = {{", name); List<string> objs = new List<string>(list.TextureList.Length); foreach (TextureListEntry obj in list.TextureList) objs.Add(obj.ToStruct()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); writer.WriteLine("LevelPVMList {0} = {{ {1}, arraylengthandptr({0}_list) }};", name, list.Level.ToC()); } break; case "triallevellist": { SA1LevelAct[] list = TrialLevelList.Load(data.Filename); writer.WriteLine("TrialLevelListEntry {0}_list[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (SA1LevelAct obj in list) objs.Add(string.Format("{{ {0}, {1} }}", obj.Level.ToC("LevelIDs"), obj.Act)); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); writer.WriteLine("TrialLevelList {0} = {{ arrayptrandlength({0}_list) }};", name); } break; case "bosslevellist": { SA1LevelAct[] list = BossLevelList.Load(data.Filename); writer.WriteLine("__int16 {0}[] = {{", name); List<string> objs = new List<string>(list.Length + 1); foreach (SA1LevelAct obj in list) objs.Add(obj.ToC()); objs.Add("levelact(LevelIDs_Invalid, 0)"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "fieldstartpos": { Dictionary<SA1LevelIDs, FieldStartPosInfo> list = FieldStartPosList.Load(data.Filename); writer.WriteLine("FieldStartPosition {0}[] = {{", name); List<string> objs = new List<string>(list.Count + 1); foreach (KeyValuePair<SA1LevelIDs, FieldStartPosInfo> obj in list) objs.Add(obj.ToStruct()); objs.Add("{ LevelIDs_Invalid }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "soundtestlist": { SoundTestListEntry[] list = SoundTestList.Load(data.Filename); writer.WriteLine("SoundTestEntry {0}_list[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (SoundTestListEntry obj in list) objs.Add(obj.ToStruct() + " " + obj.Title.ToComment()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); writer.WriteLine("SoundTestCategory {0} = {{ arrayptrandlength({0}_list) }};", name); } break; case "musiclist": { MusicListEntry[] list = MusicList.Load(data.Filename); writer.WriteLine("MusicInfo {0}[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (MusicListEntry obj in list) objs.Add(obj.ToStruct()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "soundlist": { SoundListEntry[] list = SoundList.Load(data.Filename); writer.WriteLine("SoundFileInfo {0}_list[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (SoundListEntry obj in list) objs.Add(obj.ToStruct()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); writer.WriteLine("SoundList {0} = {{ arraylengthandptr({0}_list) }};", name); } break; case "stringarray": { string[] strs = StringArray.Load(data.Filename); Languages lang = Languages.Japanese; if (data.CustomProperties.ContainsKey("language")) lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true); writer.WriteLine("char *{0}[] = {{", name); List<string> objs = new List<string>(strs.Length); foreach (string obj in strs) objs.Add(obj.ToC(lang) + " " + obj.ToComment()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "nextlevellist": { NextLevelListEntry[] list = NextLevelList.Load(data.Filename); writer.WriteLine("NextLevelData {0}[] = {{", name); List<string> objs = new List<string>(list.Length + 1); foreach (NextLevelListEntry obj in list) objs.Add(obj.ToStruct()); objs.Add("{ 0, -1 }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "cutscenetext": { CutsceneText texts = new CutsceneText(data.Filename); uint addr = (uint)(data.Address + imagebase); for (int j = 0; j < 5; j++) { string[] strs = texts.Text[j]; Languages lang = (Languages)j; writer.WriteLine("char *{0}_{1}[] = {{", name, lang); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", strs.Select((a) => a.ToC(lang) + " " + a.ToComment()).ToArray())); writer.WriteLine("};"); writer.WriteLine(); pointers.Add(addr, string.Format("{0}_{1}", name, lang)); addr += 4; } } break; case "recapscreen": { uint addr = (uint)(data.Address + imagebase); RecapScreen[][] texts = RecapScreenList.Load(data.Filename, int.Parse(data.CustomProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo)); for (int l = 0; l < 5; l++) for (int j = 0; j < texts.Length; j++) { writer.WriteLine("char *{0}_{1}_{2}_Text[] = {{", name, (Languages)l, j); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", texts[j][l].Text.Split('\n').Select((a) => a.ToC((Languages)l) + " " + a.ToComment()).ToArray())); writer.WriteLine("};"); writer.WriteLine(); } for (int l = 0; l < 5; l++) { writer.WriteLine("RecapScreen {0}_{1}[] = {{", name, (Languages)l); List<string> objs = new List<string>(texts.Length); for (int j = 0; j < texts.Length; j++) { RecapScreen scr = texts[j][l]; objs.Add(string.Format("{{ {0}, arraylengthandptr({1}_{2}_{3}_Text) }}", SA_Tools.HelperFunctions.ToC(scr.Speed), name, (Languages)l, j)); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); pointers.Add(addr, string.Format("{0}_{1}", name, (Languages)l)); addr += 4; } } break; case "npctext": { NPCText[][] texts = NPCTextList.Load(data.Filename, int.Parse(data.CustomProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo)); uint headaddr = (uint)(data.Address + imagebase); for (int l = 0; l < 5; l++) { for (int j = 0; j < texts[l].Length; j++) { if (texts[l][j].Groups.Count == 0) continue; if (texts[l][j].HasControl) { writer.WriteLine("__int16 {0}_{1}_{2}_Control[] = {{", name, (Languages)l, j); bool first = true; List<string> objs = new List<string>(); foreach (NPCTextGroup group in texts[l][j].Groups) { if (!first) objs.Add(NPCTextControl.NewGroup.ToC()); else first = false; foreach (ushort flag in group.EventFlags) { objs.Add(NPCTextControl.EventFlag.ToC()); objs.Add(flag.ToCHex()); } foreach (ushort flag in group.NPCFlags) { objs.Add(NPCTextControl.NPCFlag.ToC()); objs.Add(flag.ToCHex()); } if (group.Character != (SA1CharacterFlags)0xFF) { objs.Add(NPCTextControl.Character.ToC()); objs.Add(group.Character.ToC("CharacterFlags")); } if (group.Voice.HasValue) { objs.Add(NPCTextControl.Voice.ToC()); objs.Add(group.Voice.Value.ToString()); } if (group.SetEventFlag.HasValue) { objs.Add(NPCTextControl.SetEventFlag.ToC()); objs.Add(group.SetEventFlag.Value.ToCHex()); } } objs.Add(NPCTextControl.End.ToC()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); } if (texts[l][j].HasText) { writer.WriteLine("HintText_Text {0}_{1}_{2}_Text[] = {{", name, (Languages)l, j); List<string> objs = new List<string>(); foreach (NPCTextGroup group in texts[l][j].Groups) { foreach (NPCTextLine line in group.Lines) objs.Add(line.ToStruct((Languages)l) + " " + line.Line.ToComment()); objs.Add("{ 0 }"); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); } } } for (int l = 0; l < 5; l++) { if (l > 0) writer.WriteLine(); writer.WriteLine("HintText_Entry {0}_{1}[] = {{", name, (Languages)l); List<string> objs = new List<string>(); for (int j = 0; j < texts[l].Length; j++) { if (texts[l][j].Groups.Count == 0) { objs.Add("{ 0 }"); continue; } StringBuilder line = new StringBuilder("{ "); if (texts[l][j].HasControl) line.AppendFormat("{0}_{1}_{2}_Control", name, (Languages)l, j); else line.Append("NULL"); line.Append(", "); if (texts[l][j].HasText) line.AppendFormat("{0}_{1}_{2}_Text", name, (Languages)l, j); else line.Append("NULL"); line.Append(" }"); objs.Add(line.ToString()); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); pointers.Add(headaddr, string.Format("{0}_{1}", name, (Languages)l)); headaddr += 4; } } break; case "levelclearflags": { LevelClearFlag[] list = LevelClearFlagList.Load(data.Filename); writer.WriteLine("LevelClearFlagData {0}[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (LevelClearFlag obj in list) objs.Add(obj.ToStruct()); objs.Add("{ -1 }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "deathzone": { DeathZoneFlags[] list = DeathZoneFlagsList.Load(data.Filename); string path = Path.GetDirectoryName(data.Filename); List<string> mdls = new List<string>(list.Length); List<string> objs = new List<string>(); for (int j = 0; j < list.Length; j++) { SonicRetro.SAModel.NJS_OBJECT obj = new ModelFile(Path.Combine(path, j.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl")).Model; writer.WriteLine(obj.ToStructVariables(modelfmt == ModelFormat.BasicDX, objs)); mdls.Add(obj.Name); objs.Clear(); } writer.WriteLine("DeathZone {0}[] = {{", name); for (int j = 0; j < list.Length; j++) objs.Add(string.Format("{{ {0}, &{1} }}", list[j].Flags.ToC("CharacterFlags"), mdls[j])); objs.Add("{ 0 }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "skyboxscale": { uint headaddr = (uint)(data.Address + imagebase); int cnt = int.Parse(data.CustomProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SkyboxScale[] sclini = SkyboxScaleList.Load(data.Filename); for (int j = 0; j < cnt; j++) { writer.WriteLine("SkyboxScale {0}_{1} = {2};", name, j, sclini[j].ToStruct()); pointers.Add(headaddr, string.Format("{0}_{1}", name, j)); headaddr += 4; } } break; case "stageselectlist": { StageSelectLevel[] list = StageSelectLevelList.Load(data.Filename); writer.WriteLine("StageSelectLevel {0}[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (StageSelectLevel obj in list) objs.Add(obj.ToStruct()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "levelrankscores": { Dictionary<SA2LevelIDs, LevelRankScores> list = LevelRankScoresList.Load(data.Filename); writer.WriteLine("LevelRankScores {0}[] = {{", name); List<string> objs = new List<string>(list.Count); foreach (KeyValuePair<SA2LevelIDs, LevelRankScores> obj in list) objs.Add(obj.ToStruct()); objs.Add("{ LevelIDs_Invalid }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "levelranktimes": { Dictionary<SA2LevelIDs, LevelRankTimes> list = LevelRankTimesList.Load(data.Filename); writer.WriteLine("LevelRankTimes {0}[] = {{", name); List<string> objs = new List<string>(list.Count); foreach (KeyValuePair<SA2LevelIDs, LevelRankTimes> obj in list) objs.Add(obj.ToStruct()); objs.Add("{ LevelIDs_Invalid }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "endpos": { Dictionary<SA2LevelIDs, SA2EndPosInfo> list = SA2EndPosList.Load(data.Filename); writer.WriteLine("LevelEndPosition {0}[] = {{", name); List<string> objs = new List<string>(list.Count); foreach (KeyValuePair<SA2LevelIDs, SA2EndPosInfo> obj in list) objs.Add(obj.ToStruct()); objs.Add("{ LevelIDs_Invalid }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "animationlist": { SA2AnimationInfo[] list = SA2AnimationInfoList.Load(data.Filename); writer.WriteLine("AnimationInfo {0}[] = {{", name); List<string> objs = new List<string>(list.Length); foreach (SA2AnimationInfo obj in list) objs.Add(obj.ToStruct()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "levelpathlist": { List<SA1LevelAct> levels = new List<SA1LevelAct>(); foreach (string dir in Directory.GetDirectories(data.Filename)) { SA1LevelAct level; try { level = new SA1LevelAct(new DirectoryInfo(dir).Name); } catch { continue; } levels.Add(level); List<PathData> paths = PathList.Load(dir); for (int i = 0; i < paths.Count; i++) { writer.WriteLine("Loop {0}_{1}_{2}_Entries[] = {{", name, level.ToString().MakeIdentifier(), i); List<string> objs = new List<string>(paths[i].Path.Count); foreach (PathDataEntry entry in paths[i].Path) objs.Add(entry.ToStruct()); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); writer.WriteLine("LoopHead {0}_{1}_{2} = {{ {3}, LengthOfArray({0}_{1}_{2}_Entries), {4}, {0}_{1}_{2}_Entries, (ObjectFuncPtr){5} }};", name, level.ToString().MakeIdentifier(), i, paths[i].Unknown, HelperFunctions.ToC(paths[i].TotalDistance), HelperFunctions.ToCHex(paths[i].Code)); writer.WriteLine(); } writer.WriteLine("LoopHead *{0}_{1}[] = {{", name, level.ToString().MakeIdentifier()); for (int i = 0; i < paths.Count; i++) writer.WriteLine("\t&{0}_{1}_{2},", name, level.ToString().MakeIdentifier(), i); writer.WriteLine("\tNULL"); writer.WriteLine("};"); writer.WriteLine(); } writer.WriteLine("PathDataPtr {0}[] = {{", name); foreach (SA1LevelAct level in levels) writer.WriteLine("\t{{ {0}, {1}_{2} }},", level.ToC(), name, level.ToString().MakeIdentifier()); writer.WriteLine("\t{ 0xFFFF }"); writer.WriteLine("};"); writer.WriteLine(); } break; case "stagelightdatalist": { List<SA1StageLightData> list = SA1StageLightDataList.Load(data.Filename); writer.WriteLine("StageLightData {0}[] = {{", name); List<string> objs = new List<string>(list.Count + 1); foreach (SA1StageLightData obj in list) objs.Add(obj.ToStruct()); objs.Add("{ 0xFFu }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; } writer.WriteLine(); if (data.PointerList != null && data.PointerList.Length > 0) foreach (int ptr in data.PointerList) pointers.Add((uint)(ptr + imagebase), name); } writer.WriteLine("PointerInfo pointers[] = {"); List<string> ptrs = new List<string>(pointers.Count); foreach (KeyValuePair<uint, string> ptr in pointers) ptrs.Add(string.Format("ptrdecl({0}, &{1})", HelperFunctions.ToCHex(ptr.Key), ptr.Value)); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", ptrs.ToArray())); writer.WriteLine("};"); writer.WriteLine(); }
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { #if !DEBUG try { #endif int steps = 10; if (d3ddevice == null) ++steps; toolStrip1.Enabled = false; // HACK: Fixes Twinkle Circuit's geometry lingering if loaded before Sky Chase. // I'm sure the real problem is somewhere below, but this is sort of an all around cleanup. if (isStageLoaded) LevelData.Clear(); isStageLoaded = false; using (ProgressDialog progress = new ProgressDialog("Loading stage: " + levelName, steps)) { IniLevelData level = ini.Levels[levelID]; string syspath = Path.Combine(Environment.CurrentDirectory, ini.SystemPath); string modpath = ini.ModPath; SA1LevelAct levelact = new SA1LevelAct(level.LevelID); LevelData.leveltexs = null; cam = new EditorCamera(EditorOptions.RenderDrawDistance); Invoke((Action<IWin32Window>)progress.Show, this); if (d3ddevice == null) { progress.SetTask("Initializing Direct3D..."); Invoke((Action)InitializeDirect3D); progress.StepProgress(); } progress.SetTaskAndStep("Loading level data:", "Geometry"); if (string.IsNullOrEmpty(level.LevelGeometry)) LevelData.geo = null; else { LevelData.geo = LandTable.LoadFromFile(level.LevelGeometry); LevelData.LevelItems = new List<LevelItem>(); for (int i = 0; i < LevelData.geo.COL.Count; i++) LevelData.LevelItems.Add(new LevelItem(LevelData.geo.COL[i], d3ddevice, i, selectedItems)); } progress.StepProgress(); progress.SetStep("Textures"); LevelData.TextureBitmaps = new Dictionary<string, BMPInfo[]>(); LevelData.Textures = new Dictionary<string, Texture[]>(); if (LevelData.geo != null && !string.IsNullOrEmpty(LevelData.geo.TextureFileName)) { BMPInfo[] TexBmps = TextureArchive.GetTextures(Path.Combine(syspath, LevelData.geo.TextureFileName) + ".PVM"); Texture[] texs = new Texture[TexBmps.Length]; for (int j = 0; j < TexBmps.Length; j++) texs[j] = new Texture(d3ddevice, TexBmps[j].Image, Usage.None, Pool.Managed); if (!LevelData.TextureBitmaps.ContainsKey(LevelData.geo.TextureFileName)) LevelData.TextureBitmaps.Add(LevelData.geo.TextureFileName, TexBmps); if (!LevelData.Textures.ContainsKey(LevelData.geo.TextureFileName)) LevelData.Textures.Add(LevelData.geo.TextureFileName, texs); LevelData.leveltexs = LevelData.geo.TextureFileName; } progress.StepProgress(); #region Start Positions progress.SetTaskAndStep("Setting up start positions..."); LevelData.StartPositions = new StartPosItem[LevelData.Characters.Length]; for (int i = 0; i < LevelData.StartPositions.Length; i++) { progress.SetStep(string.Format("{0}/{1}", (i + 1), LevelData.StartPositions.Length)); IniCharInfo character; if (i == 0 && levelact.Level == SA1LevelIDs.PerfectChaos) character = ini.Characters["SuperSonic"]; else character = ini.Characters[LevelData.Characters[i]]; Dictionary<SA1LevelAct, SA1StartPosInfo> posini = SA1StartPosList.Load(character.StartPositions); Vertex pos = new Vertex(); int rot = 0; if (posini.ContainsKey(levelact)) { pos = posini[levelact].Position; rot = posini[levelact].YRotation; } LevelData.StartPositions[i] = new StartPosItem(new ModelFile(character.Model).Model, character.Textures, character.Height, pos, rot, d3ddevice, selectedItems); LoadTextureList(character.TextureList, syspath); } progress.StepProgress(); #endregion #region Death Zones progress.SetTaskAndStep("Death Zones:", "Initializing..."); if (string.IsNullOrEmpty(level.DeathZones)) LevelData.DeathZones = null; else { LevelData.DeathZones = new List<DeathZoneItem>(); DeathZoneFlags[] dzini = DeathZoneFlagsList.Load(level.DeathZones); string path = Path.GetDirectoryName(level.DeathZones); for (int i = 0; i < dzini.Length; i++) { progress.SetStep(String.Format("Loading model {0}/{1}", (i + 1), dzini.Length)); LevelData.DeathZones.Add(new DeathZoneItem( new ModelFile(Path.Combine(path, i.ToString(System.Globalization.NumberFormatInfo.InvariantInfo) + ".sa1mdl")) .Model, dzini[i].Flags, d3ddevice, selectedItems)); } } progress.StepProgress(); #endregion #region Textures and Texture Lists progress.SetTaskAndStep("Loading textures for:"); progress.SetStep("Common objects"); // Loads common object textures (e.g OBJ_REGULAR) LoadTextureList(ini.ObjectTextureList, syspath); progress.SetTaskAndStep("Loading stage texture lists..."); // Loads the textures in the texture list for this stage (e.g BEACH01) foreach (string file in Directory.GetFiles(ini.LevelTextureLists)) { LevelTextureList texini = LevelTextureList.Load(file); if (texini.Level != levelact) continue; LoadTextureList(texini.TextureList, syspath); } progress.SetTaskAndStep("Loading textures for:", "Objects"); // Object texture list(s) LoadTextureList(level.ObjectTextureList, syspath); progress.SetStep("Stage"); // The stage textures... again? "Extra"? if (level.Textures != null && level.Textures.Length > 0) foreach (string tex in level.Textures) { LoadPVM(tex, syspath); if (string.IsNullOrEmpty(LevelData.leveltexs)) LevelData.leveltexs = tex; } progress.StepProgress(); #endregion #region Object Definitions / SET Layout progress.SetTaskAndStep("Loading Object Definitions:", "Parsing..."); LevelData.ObjDefs = new List<ObjectDefinition>(); Dictionary<string, ObjectData> objdefini = IniSerializer.Deserialize<Dictionary<string, ObjectData>>(ini.ObjectDefinitions); if (!string.IsNullOrEmpty(level.ObjectList) && File.Exists(level.ObjectList)) { List<ObjectData> objectErrors = new List<ObjectData>(); ObjectListEntry[] objlstini = ObjectList.Load(level.ObjectList, false); Directory.CreateDirectory("dllcache").Attributes |= FileAttributes.Hidden; for (int ID = 0; ID < objlstini.Length; ID++) { string codeaddr = objlstini[ID].CodeString; if (!objdefini.ContainsKey(codeaddr)) codeaddr = "0"; ObjectData defgroup = objdefini[codeaddr]; ObjectDefinition def; if (!string.IsNullOrEmpty(defgroup.CodeFile)) { progress.SetStep("Compiling: " + defgroup.CodeFile); // TODO: Split this out to a function #region Compile object code files string ty = defgroup.CodeType; string dllfile = Path.Combine("dllcache", ty + ".dll"); DateTime modDate = DateTime.MinValue; if (File.Exists(dllfile)) modDate = File.GetLastWriteTime(dllfile); string fp = defgroup.CodeFile.Replace('/', Path.DirectorySeparatorChar); if (modDate >= File.GetLastWriteTime(fp) && modDate > File.GetLastWriteTime(Application.ExecutablePath)) def = (ObjectDefinition) Activator.CreateInstance( Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, dllfile)) .GetType(ty)); else { string ext = Path.GetExtension(fp); CodeDomProvider pr = null; switch (ext.ToLowerInvariant()) { case ".cs": pr = new Microsoft.CSharp.CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } }); break; case ".vb": pr = new Microsoft.VisualBasic.VBCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } }); break; } if (pr != null) { CompilerParameters para = new CompilerParameters(new string[] { "System.dll", "System.Core.dll", "System.Drawing.dll", Assembly.GetAssembly(typeof (Vector3)).Location, Assembly.GetAssembly(typeof (Texture)).Location, Assembly.GetAssembly(typeof (D3DX)).Location, Assembly.GetExecutingAssembly().Location, Assembly.GetAssembly(typeof (LandTable)).Location, Assembly.GetAssembly(typeof (EditorCamera)).Location, Assembly.GetAssembly(typeof (SA1LevelAct)).Location, Assembly.GetAssembly(typeof (ObjectDefinition)).Location }) { GenerateExecutable = false, GenerateInMemory = false, IncludeDebugInformation = true, OutputAssembly = Path.Combine(Environment.CurrentDirectory, dllfile) }; CompilerResults res = pr.CompileAssemblyFromFile(para, fp); if (res.Errors.HasErrors) { // TODO: Merge with existing object error handler. I add too many ToDos. string errors = null; foreach (CompilerError item in res.Errors) errors += String.Format("\n\n{0}, {1}: {2}", item.Line, item.Column, item.ErrorText); MessageBox.Show("Failed to compile object code file:\n" + defgroup.CodeFile + errors, "Object compilation failure", MessageBoxButtons.OK, MessageBoxIcon.Error); def = new DefaultObjectDefinition(); } else { def = (ObjectDefinition)Activator.CreateInstance(res.CompiledAssembly.GetType(ty)); } } else def = new DefaultObjectDefinition(); } #endregion } else { def = new DefaultObjectDefinition(); } LevelData.ObjDefs.Add(def); // The only reason .Model is checked for null is for objects that don't yet have any // models defined for them. It would be annoying seeing that error all the time! if (string.IsNullOrEmpty(defgroup.CodeFile) && !string.IsNullOrEmpty(defgroup.Model)) { progress.SetStep("Loading: " + defgroup.Model); // Otherwise, if the model file doesn't exist and/or no texture file is defined, // load the "default object" instead ("?"). if (!File.Exists(defgroup.Model) || string.IsNullOrEmpty(defgroup.Texture) || !LevelData.Textures.ContainsKey(defgroup.Texture)) { ObjectData error = new ObjectData { Name = defgroup.Name, Model = defgroup.Model, Texture = defgroup.Texture }; objectErrors.Add(error); defgroup.Model = null; } } def.Init(defgroup, objlstini[ID].Name, d3ddevice); def.SetInternalName(objlstini[ID].Name); } // Loading SET Layout progress.SetTaskAndStep("Loading SET items", "Initializing..."); if (LevelData.ObjDefs.Count > 0) { LevelData.SETName = level.SETName ?? level.LevelID; string setstr = Path.Combine(syspath, "SET" + LevelData.SETName + "{0}.bin"); LevelData.SETItems = new List<SETItem>[LevelData.SETChars.Length]; for (int i = 0; i < LevelData.SETChars.Length; i++) { List<SETItem> list = new List<SETItem>(); byte[] setfile = null; string formatted = string.Format(setstr, LevelData.SETChars[i]); if (modpath != null && File.Exists(Path.Combine(modpath, formatted))) setfile = File.ReadAllBytes(Path.Combine(modpath, formatted)); else if (File.Exists(formatted)) setfile = File.ReadAllBytes(formatted); if (setfile != null) { progress.SetTask("SET: " + formatted.Replace(Environment.CurrentDirectory, "")); int count = BitConverter.ToInt32(setfile, 0); int address = 0x20; for (int j = 0; j < count; j++) { progress.SetStep(string.Format("{0}/{1}", (j + 1), count)); SETItem ent = new SETItem(setfile, address, selectedItems); list.Add(ent); address += 0x20; } } LevelData.SETItems[i] = list; } } else { LevelData.SETItems = null; } // Checks if there have been any errors added to the error list and does its thing // This thing is a mess. If anyone can think of a cleaner way to do this, be my guest. if (objectErrors.Count > 0) { int count = objectErrors.Count; List<string> errorStrings = new List<string> { "The following objects failed to load:" }; foreach (ObjectData o in objectErrors) { bool texEmpty = string.IsNullOrEmpty(o.Texture); bool texExists = (!string.IsNullOrEmpty(o.Texture) && LevelData.Textures.ContainsKey(o.Texture)); errorStrings.Add(""); errorStrings.Add("Object:\t\t" + o.Name); errorStrings.Add("\tModel:"); errorStrings.Add("\t\tName:\t" + o.Model); errorStrings.Add("\t\tExists:\t" + File.Exists(o.Model)); errorStrings.Add("\tTexture:"); errorStrings.Add("\t\tName:\t" + ((texEmpty) ? "(N/A)" : o.Texture)); errorStrings.Add("\t\tExists:\t" + texExists); } // TODO: Proper logging. Who knows where this file may end up File.WriteAllLines("SADXLVL2.log", errorStrings.ToArray()); MessageBox.Show(count + ((count == 1) ? " object" : " objects") + " failed to load their model(s).\n" + "\nThe level will still display, but the objects in question will not display their proper models." + "\n\nPlease check the log for details.", "Error loading models", MessageBoxButtons.OK, MessageBoxIcon.Information); } } else { LevelData.SETItems = null; } progress.StepProgress(); #endregion #region CAM Layout progress.SetTaskAndStep("Loading CAM items", "Initializing..."); string camstr = Path.Combine(syspath, "CAM" + LevelData.SETName + "{0}.bin"); LevelData.CAMItems = new List<CAMItem>[LevelData.SETChars.Length]; for (int i = 0; i < LevelData.SETChars.Length; i++) { List<CAMItem> list = new List<CAMItem>(); byte[] camfile = null; string formatted = string.Format(camstr, LevelData.SETChars[i]); if (modpath != null && File.Exists(Path.Combine(modpath, formatted))) camfile = File.ReadAllBytes(Path.Combine(modpath, formatted)); else if (File.Exists(formatted)) camfile = File.ReadAllBytes(formatted); if (camfile != null) { progress.SetTask("CAM: " + formatted.Replace(Environment.CurrentDirectory, "")); int count = BitConverter.ToInt32(camfile, 0); int address = 0x40; for (int j = 0; j < count; j++) { progress.SetStep(string.Format("{0}/{1}", (j + 1), count)); CAMItem ent = new CAMItem(camfile, address, selectedItems); list.Add(ent); address += 0x40; } } LevelData.CAMItems[i] = list; } CAMItem.Init(d3ddevice); progress.StepProgress(); #endregion #region Loading Level Effects LevelData.leveleff = null; if (!string.IsNullOrEmpty(level.Effects)) { progress.SetTaskAndStep("Loading Level Effects..."); LevelDefinition def = null; string ty = "SADXObjectDefinitions.Level_Effects." + Path.GetFileNameWithoutExtension(level.Effects); string dllfile = Path.Combine("dllcache", ty + ".dll"); DateTime modDate = DateTime.MinValue; if (File.Exists(dllfile)) modDate = File.GetLastWriteTime(dllfile); string fp = level.Effects.Replace('/', Path.DirectorySeparatorChar); if (modDate >= File.GetLastWriteTime(fp) && modDate > File.GetLastWriteTime(Application.ExecutablePath)) { def = (LevelDefinition) Activator.CreateInstance( Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, dllfile)).GetType(ty)); } else { string ext = Path.GetExtension(fp); CodeDomProvider pr = null; switch (ext.ToLowerInvariant()) { case ".cs": pr = new Microsoft.CSharp.CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } }); break; case ".vb": pr = new Microsoft.VisualBasic.VBCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } }); break; } if (pr != null) { CompilerParameters para = new CompilerParameters(new string[] { "System.dll", "System.Core.dll", "System.Drawing.dll", Assembly.GetAssembly(typeof (Vector3)).Location, Assembly.GetAssembly(typeof (Texture)).Location, Assembly.GetAssembly(typeof (D3DX)).Location, Assembly.GetExecutingAssembly().Location, Assembly.GetAssembly(typeof (LandTable)).Location, Assembly.GetAssembly(typeof (EditorCamera)).Location, Assembly.GetAssembly(typeof (SA1LevelAct)).Location, Assembly.GetAssembly(typeof (Item)).Location }) { GenerateExecutable = false, GenerateInMemory = false, IncludeDebugInformation = true, OutputAssembly = Path.Combine(Environment.CurrentDirectory, dllfile) }; CompilerResults res = pr.CompileAssemblyFromFile(para, fp); if (!res.Errors.HasErrors) def = (LevelDefinition)Activator.CreateInstance(res.CompiledAssembly.GetType(ty)); } } if (def != null) def.Init(level, levelact.Act, d3ddevice); LevelData.leveleff = def; } progress.StepProgress(); #endregion #region Loading Splines LevelData.LevelSplines = new List<SplineData>(); SplineData.Init(); if (!string.IsNullOrEmpty(ini.Paths)) { progress.SetTaskAndStep("Reticulating splines..."); String splineDirectory = Path.Combine(Path.Combine(Environment.CurrentDirectory, ini.Paths), levelact.ToString()); if (Directory.Exists(splineDirectory)) { List<string> pathFiles = new List<string>(); for (int i = 0; i < int.MaxValue; i++) { string path = string.Concat(splineDirectory, string.Format("/{0}.ini", i)); if (File.Exists(path)) { pathFiles.Add(path); } else break; } foreach (string pathFile in pathFiles) // looping through path files { SplineData newSpline = new SplineData(PathData.Load(pathFile), selectedItems); newSpline.RebuildMesh(d3ddevice); LevelData.LevelSplines.Add(newSpline); } } } progress.StepProgress(); #endregion #region Stage Lights progress.SetTaskAndStep("Loading lights..."); if ((stageLightList != null) && (stageLightList.Count > 0)) { List<SA1StageLightData> lightList = new List<SA1StageLightData>(); foreach (SA1StageLightData lightData in stageLightList) { if ((lightData.Level == levelact.Level) && (lightData.Act == levelact.Act)) lightList.Add(lightData); } if (lightList.Count > 0) { for (int i = 0; i < d3ddevice.Lights.Count; i++) // clear all default lights { d3ddevice.Lights[i].Enabled = false; } for (int i = 0; i < lightList.Count; i++) { SA1StageLightData lightData = lightList[i]; d3ddevice.Lights[i].Enabled = true; d3ddevice.Lights[i].Type = (lightData.UseDirection) ? LightType.Directional : LightType.Point; d3ddevice.Lights[i].Diffuse = lightData.RGB.ToColor(); d3ddevice.Lights[i].DiffuseColor = new ColorValue(lightData.RGB.X, lightData.RGB.Y, lightData.RGB.Z, 1.0f); d3ddevice.Lights[i].Ambient = lightData.AmbientRGB.ToColor(); d3ddevice.Lights[i].Specular = Color.Black; d3ddevice.Lights[i].Direction = lightData.Direction.ToVector3(); d3ddevice.Lights[i].Range = lightData.Dif; // guessing here } } else { MessageBox.Show("No lights were found for this stage. Using default lights instead.", "No lights found", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } progress.StepProgress(); #endregion transformGizmo = new TransformGizmo(); Invoke((Action)progress.Close); } #if !DEBUG } catch (Exception ex) { MessageBox.Show( ex.GetType().Name + ": " + ex.Message + "\nLog file has been saved to " + Path.Combine(Environment.CurrentDirectory, "SADXLVL2.log") + ".\nSend this to MainMemory on the Sonic Retro forums.", "SADXLVL2 Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Error); File.WriteAllText("SADXLVL2.log", ex.ToString()); initerror = true; } #endif }
static void Main(string[] args) { string datafilename, inifilename; if (args.Length > 0) { datafilename = args[0]; Console.WriteLine("File: {0}", datafilename); } else { Console.Write("File: "); datafilename = Console.ReadLine(); } if (args.Length > 1) { inifilename = args[1]; Console.WriteLine("INI File: {0}", inifilename); } else { Console.Write("INI File: "); inifilename = Console.ReadLine(); } byte[] datafile = File.ReadAllBytes(datafilename); IniData inifile = IniSerializer.Deserialize<IniData>(inifilename); SA_Tools.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; Console.WriteLine(item.Key + ": " + data.Address.ToString("X") + " → " + data.Filename); Directory.CreateDirectory(Path.GetDirectoryName(data.Filename)); switch (type) { case "landtable": new LandTable(datafile, address, imageBase, landfmt) { Description = item.Key, Tool = "split" }.SaveToFile(data.Filename, landfmt); break; case "model": { SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.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(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, modelfmt); } break; case "basicmodel": { SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.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(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, ModelFormat.Basic); } break; case "basicdxmodel": { SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.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(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, ModelFormat.BasicDX); } break; case "chunkmodel": { SonicRetro.SAModel.NJS_OBJECT mdl = new SonicRetro.SAModel.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(data.Filename, mdl, mdlanis, mdlmorphs, null, item.Key, "split", null, ModelFormat.Chunk); } break; case "action": { AnimationHeader ani = new AnimationHeader(datafile, address, imageBase, modelfmt); ani.Animation.Name = filedesc; ani.Animation.Save(data.Filename); } break; case "animation": new Animation(datafile, address, imageBase, int.Parse(customProperties["numparts"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo)) { Name = filedesc } .Save(data.Filename); 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(data.Filename); } break; case "startpos": if (SA2) SA2StartPosList.Load(datafile, address).Save(data.Filename); else SA1StartPosList.Load(datafile, address).Save(data.Filename); break; case "texlist": TextureList.Load(datafile, address, imageBase).Save(data.Filename); break; case "leveltexlist": new LevelTextureList(datafile, address, imageBase).Save(data.Filename); break; case "triallevellist": TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), data.Filename); break; case "bosslevellist": BossLevelList.Save(BossLevelList.Load(datafile, address), data.Filename); break; case "fieldstartpos": FieldStartPosList.Load(datafile, address).Save(data.Filename); break; case "soundtestlist": SoundTestList.Load(datafile, address, imageBase).Save(data.Filename); break; case "musiclist": { int muscnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); MusicList.Load(datafile, address, imageBase, muscnt).Save(data.Filename); } break; case "soundlist": SoundList.Load(datafile, address, imageBase).Save(data.Filename); 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(data.Filename); } break; case "nextlevellist": NextLevelList.Load(datafile, address).Save(data.Filename); break; case "cutscenetext": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); string[] hashes; new CutsceneText(datafile, address, imageBase, cnt).Save(data.Filename, out hashes); data.MD5Hash = string.Join(",", hashes); nohash = true; } break; case "recapscreen": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); string[][] hashes; RecapScreenList.Load(datafile, address, imageBase, cnt).Save(data.Filename, out 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); string[][] hashes; NPCTextList.Load(datafile, address, imageBase, cnt).Save(data.Filename, out 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), data.Filename); break; case "deathzone": { List<DeathZoneFlags> flags = new List<DeathZoneFlags>(); string path = Path.GetDirectoryName(data.Filename); List<string> hashes = new List<string>(); int num = 0; while (SA_Tools.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 SonicRetro.SAModel.NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt), null, null, null, null, "split", null, modelfmt); hashes.Add(HelperFunctions.FileHash(file)); address += 8; } flags.ToArray().Save(data.Filename); hashes.Insert(0, HelperFunctions.FileHash(data.Filename)); 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(data.Filename); } break; case "stageselectlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); StageSelectLevelList.Load(datafile, address, cnt).Save(data.Filename); } break; case "levelrankscores": LevelRankScoresList.Load(datafile, address).Save(data.Filename); break; case "levelranktimes": LevelRankTimesList.Load(datafile, address).Save(data.Filename); break; case "endpos": SA2EndPosList.Load(datafile, address).Save(data.Filename); break; case "animationlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA2AnimationInfoList.Load(datafile, address, cnt).Save(data.Filename); } break; case "levelpathlist": { List<string> hashes = new List<string>(); ushort lvlnum = (ushort)SA_Tools.ByteConverter.ToUInt32(datafile, address); while (lvlnum != 0xFFFF) { int ptr = SA_Tools.ByteConverter.ToInt32(datafile, address + 4); if (ptr != 0) { ptr = (int)((uint)ptr - imageBase); SA1LevelAct level = new SA1LevelAct(lvlnum); string lvldir = Path.Combine(data.Filename, level.ToString()); string[] lvlhashes; PathList.Load(datafile, ptr, imageBase).Save(lvldir, out lvlhashes); hashes.Add(level.ToString() + ":" + string.Join(",", lvlhashes)); } address += 8; lvlnum = (ushort)SA_Tools.ByteConverter.ToUInt32(datafile, address); } data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "stagelightdatalist": SA1StageLightDataList.Load(datafile, address).Save(data.Filename); break; default: // raw binary { byte[] bin = new byte[int.Parse(customProperties["size"], NumberStyles.HexNumber)]; Array.Copy(datafile, address, bin, 0, bin.Length); File.WriteAllBytes(data.Filename, bin); } break; } if (!nohash) data.MD5Hash = HelperFunctions.FileHash(data.Filename); 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(); } IniSerializer.Serialize(masterobjlist, inifile.MasterObjectList); } IniSerializer.Serialize(inifile, Path.Combine(Environment.CurrentDirectory, Path.GetFileNameWithoutExtension(datafilename)) + "_data.ini"); timer.Stop(); Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds."); Console.WriteLine(); }