static void Main(string[] args) { string filename; if (args.Length > 0) { filename = args[0]; Console.WriteLine("File: {0}", filename); } else { Console.Write("File: "); filename = Console.ReadLine(); } ModelFile model = new ModelFile(filename); switch (model.Format) { case ModelFormat.Basic: foreach (NJS_OBJECT obj in model.Model.GetObjects().Where(obj => obj.Attach is BasicAttach)) { obj.Attach = obj.Attach.ToChunk(); } ModelFile.CreateFile(System.IO.Path.ChangeExtension(filename, "sa2mdl"), model.Model, null, null, null, null, ModelFormat.Chunk); break; case ModelFormat.Chunk: foreach (NJS_OBJECT obj in model.Model.GetObjects().Where(obj => obj.Attach is ChunkAttach)) { obj.Attach = obj.Attach.ToBasic(); } ModelFile.CreateFile(System.IO.Path.ChangeExtension(filename, "sa1mdl"), model.Model, null, null, null, null, ModelFormat.Basic); break; } }
static void Main(string[] args) { Queue <string> argq = new Queue <string>(args); string inifilename; if (argq.Count > 0) { inifilename = argq.Dequeue(); Console.WriteLine("INI File: {0}", inifilename); } else { Console.Write("INI File: "); inifilename = Console.ReadLine(); } Dictionary <int, string> modelnames = IniSerializer.Deserialize <Dictionary <int, string> >(inifilename, inisettings); string mdlfilename; if (argq.Count > 0) { mdlfilename = argq.Dequeue(); Console.WriteLine("Model File: {0}", mdlfilename); } else { Console.Write("Model File: "); mdlfilename = Console.ReadLine(); } ModelFile model = new ModelFile(mdlfilename); NJS_OBJECT[] objects = model.Model.GetObjects(); string repmdlfilename; if (argq.Count > 0) { repmdlfilename = argq.Dequeue(); Console.WriteLine("Replacement Model File: {0}", repmdlfilename); } else { Console.Write("Replacement Model File: "); repmdlfilename = Console.ReadLine(); } ModelFile repmodel = new ModelFile(repmdlfilename); NJS_OBJECT[] repobjects = repmodel.Model.GetObjects(); if (objects.Length != repobjects.Length) { Console.WriteLine("Models have different structures, the game may crash."); } foreach (KeyValuePair <int, string> item in modelnames.ToList()) { if (objects.Any((obj) => obj.Name == item.Value)) { modelnames[item.Key] = repobjects[Array.IndexOf(objects, objects.Single((o) => o.Name == item.Value))].Name; } } ModelFile.CreateFile(mdlfilename, repmodel.Model, null, null, repmodel.Author, repmodel.Description, "replaceMDL", repmodel.Metadata, repmodel.Format); IniSerializer.Serialize(modelnames, inisettings, inifilename); }
private void ExportFolder(string dir) { Directory.CreateDirectory(dir); string fname = Path.GetFileName(dir); IniSerializer.Serialize(meta, Path.Combine(dir, fname + ".ini")); meta.Icon.Save(Path.Combine(dir, fname + ".bmp")); if (meta.TextureData != null && meta.TextureData.Length > 0) { File.WriteAllBytes(Path.Combine(dir, fname + ".pvm"), meta.TextureData); } if (meta.SoundData != null && meta.SoundData.Length > 0) { File.WriteAllBytes(Path.Combine(dir, fname + ".mlt"), meta.SoundData); } if (meta.ModelData != null && meta.ModelData.Length > 0) { byte[] prsdata = FraGag.Compression.Prs.Decompress(meta.ModelData); uint modelpointer = BitConverter.ToUInt32(prsdata, 0) - 0xCCA4000; NJS_OBJECT mdl = new NJS_OBJECT(prsdata, (int)modelpointer, 0xCCA4000, ModelFormat.Basic, null); ModelFile.CreateFile(Path.Combine(dir, fname + ".sa1mdl"), mdl, null, null, null, null, ModelFormat.Basic); if (exportBinaryDataToolStripMenuItem.Checked) { File.WriteAllBytes(Path.Combine(dir, fname + ".prs"), meta.ModelData); File.WriteAllBytes(Path.Combine(dir, fname + ".bin"), prsdata); } } }
public DeathZoneFlags Save(string path, int i) { ModelFile.CreateFile(Path.Combine(path, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl"), Model, null, null, null, LevelData.LevelName + " Death Zone " + i.ToString(NumberFormatInfo.InvariantInfo), "SADXLVL2", null, ModelFormat.Basic); return(new DeathZoneFlags() { Flags = Flags }); }
public DeathZoneFlags Save(string path, string filename) { ModelFile.CreateFile(Path.Combine(path, filename), Model, null, null, LevelData.LevelName + " Death Zone " + filename, null, ModelFormat.Basic); return(new DeathZoneFlags() { Flags = Flags, Filename = filename }); }
public void ExportModel() { using (System.Windows.Forms.SaveFileDialog a = new System.Windows.Forms.SaveFileDialog { DefaultExt = "dae", Filter = "SAModel Files|*.sa1mdl|Collada|*.dae|Wavefront|*.obj" }) { if (a.ShowDialog() == System.Windows.Forms.DialogResult.OK) { string ftype = "collada"; switch (System.IO.Path.GetExtension(a.FileName).ToLowerInvariant()) { case ".sa1mdl": ModelFile.CreateFile(a.FileName, COL.Model, null, null, null, null, COL.Model.GetModelFormat()); return; case ".fbx": ftype = "fbx"; break; case ".obj": ftype = "obj"; break; } Assimp.AssimpContext context = new Assimp.AssimpContext(); Assimp.Scene scene = new Assimp.Scene(); scene.Materials.Add(new Assimp.Material()); Assimp.Node n = new Assimp.Node(); n.Name = "RootNode"; scene.RootNode = n; string rootPath = System.IO.Path.GetDirectoryName(a.FileName); List <string> texturePaths = new List <string>(); int numSteps = 0; if (LevelData.TextureBitmaps != null && LevelData.TextureBitmaps.Count > 0) { numSteps = LevelData.TextureBitmaps[LevelData.leveltexs].Length; } for (int i = 0; i < numSteps; i++) { BMPInfo bmp = LevelData.TextureBitmaps[LevelData.leveltexs][i]; texturePaths.Add(System.IO.Path.Combine(rootPath, bmp.Name + ".png")); bmp.Image.Save(System.IO.Path.Combine(rootPath, bmp.Name + ".png")); } SAEditorCommon.Import.AssimpStuff.AssimpExport(COL.Model, scene, Matrix.Identity, texturePaths.Count > 0 ? texturePaths.ToArray() : null, scene.RootNode); context.ExportFile(scene, a.FileName, ftype, Assimp.PostProcessSteps.ValidateDataStructure | Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.FlipUVs); // } } }
private void buttonSaveModel_Click(object sender, EventArgs e) { using (SaveFileDialog sv = new SaveFileDialog() { FileName = Path.ChangeExtension(Path.GetFileName(currentFilename), ".sa1mdl"), Title = "Save Model", Filter = "SA1MDL Files|*.sa1mdl|All Files|*.*", DefaultExt = "sa1mdl" }) { if (sv.ShowDialog() == DialogResult.OK) { byte[] modeldata = FraGag.Compression.Prs.Decompress(meta.ModelData); uint modelpointer = BitConverter.ToUInt32(modeldata, 0) - 0xCCA4000; NJS_OBJECT mdl = new NJS_OBJECT(modeldata, (int)modelpointer, 0xCCA4000, ModelFormat.Basic, null); ModelFile.CreateFile(sv.FileName, mdl, null, null, null, new System.Collections.Generic.Dictionary <uint, byte[]>(), ModelFormat.Basic); } } }
private void saveToolStripMenuItem_Click(object sender, EventArgs e) { using (SaveFileDialog a = new SaveFileDialog() { DefaultExt = (outfmt == ModelFormat.Chunk ? "sa2" : "sa1") + "mdl", Filter = (outfmt == ModelFormat.Chunk ? "SA2" : "SA1") + "MDL Files|*." + (outfmt == ModelFormat.Chunk ? "sa2" : "sa1") + "mdl|All Files|*.*" }) if (a.ShowDialog(this) == DialogResult.OK) { if (modelFile != null) { modelFile.Tool = "SAMDL"; modelFile.SaveToFile(a.FileName); } else { ModelFile.CreateFile(a.FileName, model, null, null, null, null, "SAMDL", null, outfmt); } } }
public void ExportModel() { using (System.Windows.Forms.SaveFileDialog a = new System.Windows.Forms.SaveFileDialog { DefaultExt = "dae", Filter = "SAModel Files|*.sa1mdl|Collada|*.dae|Wavefront|*.obj" }) { if (a.ShowDialog() == System.Windows.Forms.DialogResult.OK) { string ftype = "collada"; switch (System.IO.Path.GetExtension(a.FileName).ToLowerInvariant()) { case ".sa1mdl": ModelFile.CreateFile(a.FileName, Model, null, null, null, null, Model.GetModelFormat()); return; case ".fbx": ftype = "fbx"; break; case ".obj": ftype = "obj"; break; } Assimp.AssimpContext context = new Assimp.AssimpContext(); Assimp.Scene scene = new Assimp.Scene(); scene.Materials.Add(new Assimp.Material()); Assimp.Node n = new Assimp.Node(); n.Name = "RootNode"; scene.RootNode = n; string rootPath = System.IO.Path.GetDirectoryName(a.FileName); SAEditorCommon.Import.AssimpStuff.AssimpExport(Model, scene, Matrix.Identity, null, scene.RootNode); context.ExportFile(scene, a.FileName, ftype, Assimp.PostProcessSteps.ValidateDataStructure | Assimp.PostProcessSteps.Triangulate | Assimp.PostProcessSteps.FlipUVs); // } } }
private void exportSA2MDLToolStripMenuItem_Click(object sender, EventArgs e) { using (SaveFileDialog dlg = new SaveFileDialog() { DefaultExt = "sa2mdl", Filter = "SA2MDL files|*.sa2mdl" }) if (dlg.ShowDialog(this) == DialogResult.OK) { List <string> anims = new List <string>(); if (selectedObject.Motion != null) { string animname = Path.GetFileNameWithoutExtension(dlg.FileName) + "_sklmtn.saanim"; selectedObject.Motion.Save(Path.Combine(Path.GetDirectoryName(dlg.FileName), animname)); anims.Add(animname); } if (selectedObject.ShapeMotion != null) { string animname = Path.GetFileNameWithoutExtension(dlg.FileName) + "_shpmtn.saanim"; selectedObject.ShapeMotion.Save(Path.Combine(Path.GetDirectoryName(dlg.FileName), animname)); anims.Add(animname); } ModelFile.CreateFile(dlg.FileName, selectedObject.Model, anims.ToArray(), null, null, null, ModelFormat.Chunk); } }
public static void SplitNBFile(string filename, bool extractchunks, string outdir, int verbose = 0, string inifilename = null, bool overwrite = true) { Dictionary <int, string> sectionlist = new Dictionary <int, string>(); Dictionary <int, NJS_OBJECT> modellist = new Dictionary <int, NJS_OBJECT>(); Dictionary <int, NJS_MOTION> animlist = new Dictionary <int, NJS_MOTION>(); byte[] file = File.ReadAllBytes(filename); if (file.Length == 180920) // Patch in unused rotation for E101R, required for rebuilding with correct pointers { file[129896] = 0xE0; file[129897] = 0x0A; file[129904] = 0x02; } if (BitConverter.ToInt32(file, 0) != 0x04424A4E) { Console.WriteLine("Invalid NB file."); return; } if (!Directory.Exists(outdir)) { Directory.CreateDirectory(outdir); } Environment.CurrentDirectory = outdir; int numfiles = BitConverter.ToInt16(file, 4); Dictionary <int, string> splitfilenames = new Dictionary <int, string>(); if (inifilename != null) { inifilename = Path.GetFullPath(inifilename); } if (File.Exists(inifilename)) { splitfilenames = IniSerializer.Deserialize <Dictionary <int, string> >(inifilename); if (verbose > 0) { Console.WriteLine("Split INI: {0}", inifilename); } } else { if (verbose > 0) { Console.WriteLine("Split INI {0} not found!", inifilename); } for (int i = 0; i < numfiles; i++) { splitfilenames[i] = i.ToString("D2"); } } int curaddr = 8; for (int i = 0; i < numfiles; i++) { ushort type = BitConverter.ToUInt16(file, curaddr); byte[] chunk = new byte[BitConverter.ToInt32(file, curaddr + 4)]; Array.Copy(file, curaddr + 8, chunk, 0, chunk.Length); switch (type) { case 0: if (verbose > 0) { Console.WriteLine("\nSection {0} at {1} is empty", i.ToString("D2", NumberFormatInfo.InvariantInfo), curaddr.ToString("X")); } sectionlist.Add(i, "NULL"); break; case 1: if (verbose > 0) { Console.WriteLine("\nSection {0} at {1} is a model", i.ToString("D2", NumberFormatInfo.InvariantInfo), curaddr.ToString("X")); } if (extractchunks) { File.WriteAllBytes(i.ToString("D2", NumberFormatInfo.InvariantInfo) + ".bin", chunk); } NJS_OBJECT mdl = ProcessModel(chunk, verbose, curaddr + 8); //if (extractchunks) File.WriteAllBytes(i.ToString("D2", NumberFormatInfo.InvariantInfo) + "_p.bin", GetSections(mdl)); // Assume there is no description/texture for SAMDL project mode string[] metadata = new string[0]; string outFilename = splitfilenames[i]; if (splitfilenames[i].Contains("|")) { metadata = splitfilenames[i].Split('|'); // Filename|Description|Texture file outFilename = metadata[0]; } string outResult = outFilename + ".sa1mdl"; modellist.Add(i, mdl); if (metadata.Length > 1) { outResult += ("|" + metadata[1]); } if (metadata.Length > 2) { outResult += ("|" + metadata[2]); } sectionlist.Add(i, outResult); break; case 3: if (verbose > 0) { Console.WriteLine("\nSection {0} at {1} is a motion", i.ToString("D2", NumberFormatInfo.InvariantInfo), curaddr.ToString("X")); } if (extractchunks) { File.WriteAllBytes(i.ToString("D2", NumberFormatInfo.InvariantInfo) + ".bin", chunk); } string desc = null; if (splitfilenames[i].Contains("|")) { metadata = splitfilenames[i].Split('|'); // Filename|Description outFilename = metadata[0]; desc = metadata[1]; } else { outFilename = splitfilenames[i]; } NJS_MOTION mot = ProcessMotion(chunk, verbose, curaddr + 8); mot.Description = desc; //if (extractchunks) File.WriteAllBytes(i.ToString("D2", NumberFormatInfo.InvariantInfo) + "_p.bin", GetSections(mot)); animlist.Add(i, mot); sectionlist.Add(i, outFilename + ".saanim"); break; default: if (verbose > 0) { Console.WriteLine("\nSection {0} at {1} is an unknown type", i.ToString("D2", NumberFormatInfo.InvariantInfo), curaddr.ToString("X")); } if (extractchunks) { File.WriteAllBytes(i.ToString("D2", NumberFormatInfo.InvariantInfo) + ".bin", chunk); } sectionlist.Add(i, splitfilenames[i] + ".wtf"); break; } curaddr += chunk.Length + 8; } // Save models and animations foreach (var modelitem in modellist) { // If the filename field contains description/texture, split it string filenameString = splitfilenames[modelitem.Key]; if (splitfilenames[modelitem.Key].Contains("|")) { string[] filenameSplit = filenameString.Split('|'); filenameString = filenameSplit[0]; } List <string> anims = new List <string>(); foreach (var animitem in animlist) { string filenameStringAnim = splitfilenames[animitem.Key]; if (splitfilenames[animitem.Key].Contains("|")) { string[] filenameSplitAnim = filenameStringAnim.Split('|'); filenameStringAnim = filenameSplitAnim[0]; } if (modelitem.Value.CountAnimated() == animitem.Value.ModelParts) { if (File.Exists(Path.Combine(outdir, filenameStringAnim + ".saanim")) && !overwrite) { return; } if (!Directory.Exists(Path.GetDirectoryName(filenameStringAnim + ".saanim")) && Path.GetDirectoryName(filenameStringAnim + ".saanim") != "") { Directory.CreateDirectory(Path.GetDirectoryName(filenameStringAnim + ".saanim")); } animitem.Value.Save(filenameStringAnim + ".saanim"); System.Text.StringBuilder sb = new System.Text.StringBuilder(1024); PathRelativePathTo(sb, Path.GetFullPath(Path.Combine(outdir, filenameStringAnim + ".saanim")), 0, Path.GetFullPath(filenameStringAnim + ".saanim"), 0); anims.Add(sb.ToString()); } } if (File.Exists(Path.Combine(outdir, filenameString + ".sa1mdl")) && !overwrite) { return; } if (!Directory.Exists(Path.GetDirectoryName(filenameString + ".sa1mdl")) && Path.GetDirectoryName(filenameString + ".sa1mdl") != "") { Directory.CreateDirectory(Path.GetDirectoryName(filenameString + ".sa1mdl")); } ModelFile.CreateFile(filenameString + ".sa1mdl", modelitem.Value, anims.ToArray(), null, null, null, ModelFormat.Basic); } string sectionListFilename = Path.GetFileNameWithoutExtension(filename) + ".ini"; if (inifilename != null) { sectionListFilename = Path.GetFileNameWithoutExtension(inifilename) + "_data.ini"; } IniSerializer.Serialize(sectionlist, Path.Combine(outdir, sectionListFilename)); }
static void Main(string[] args) { bool nometa = false; bool nolabel = false; string mode; string fullpath_out; bool bigendian = false; List <string> mdlanimfiles; if (args.Length == 0) { Console.WriteLine("Split any binary files supported by SA Tools.\n"); Console.WriteLine("Usage:\n"); Console.WriteLine("-Splitting binary files with INI data-"); Console.WriteLine("split binary <file> <inifile> [output path] [-nometa] [-nolabel]\n"); Console.WriteLine("-Splitting SA1/SADX NB files-"); Console.WriteLine("split nb <file> [output path] -ini [split INI file]\n"); Console.WriteLine("-Splitting SA2 MDL files-"); Console.WriteLine("split mdl <file> [output path] -anim [animation files]\n"); Console.WriteLine("-Splitting SA2B MDL files-"); Console.WriteLine("split mdl_b <file> [output path] -anim [animation files]\n"); Console.WriteLine("-Splitting dllexport entries from DLL files-"); Console.WriteLine("split dllexport <file> <type> <name> [output path] [-p numparts]\n"); Console.WriteLine("Press ENTER to exit."); Console.ReadLine(); return; } #if DEBUG if (SplitExtensions(args) == true) { return; } #endif for (int u = 2; u < args.Length; u++) { if (args[u] == "-nometa") { nometa = true; } if (args[u] == "-nolabel") { nolabel = true; } } mode = args[0]; switch (mode.ToLowerInvariant()) { case "binary": string fullpath_bin = Path.GetFullPath(args[1]); if (!File.Exists(fullpath_bin)) { Console.WriteLine("File {0} doesn't exist.", fullpath_bin); return; } Console.WriteLine("File: {0}", fullpath_bin); string fullpath_ini = Path.GetFullPath(args[2]); if (!File.Exists(fullpath_ini)) { Console.WriteLine("File {0} doesn't exist.", fullpath_ini); return; } Console.WriteLine("Data mapping: {0}", fullpath_ini); fullpath_out = Path.GetDirectoryName(fullpath_bin); if (args.Length > 3) { fullpath_out = args[3]; if (fullpath_out[fullpath_out.Length - 1] != '/') { fullpath_out = string.Concat(fullpath_out, '/'); } fullpath_out = Path.GetFullPath(fullpath_out); } Console.WriteLine("Output folder: {0}", fullpath_out); if (nometa) { Console.WriteLine("Labels are disabled"); } if (Path.GetExtension(args[1]).ToLowerInvariant() == ".dll") { SA_Tools.SplitDLL.SplitDLL.SplitDLLFile(fullpath_bin, fullpath_ini, fullpath_out, nometa, nolabel); } else { SA_Tools.Split.Split.SplitFile(fullpath_bin, fullpath_ini, fullpath_out, nometa, nolabel); } break; case "nb": case "nb_b": string fullpath_nb = Path.GetFullPath(args[1]); string path_ini = null; if (args[args.Length - 2].ToLowerInvariant() == "-ini") { path_ini = Path.GetFullPath(args[args.Length - 1]); } if (!File.Exists(fullpath_nb)) { Console.WriteLine("File {0} doesn't exist.", fullpath_nb); return; } Console.WriteLine("File: {0}", fullpath_nb); fullpath_out = Path.GetDirectoryName(fullpath_nb); if (args.Length > 2) { fullpath_out = args[2]; if (fullpath_out[fullpath_out.Length - 1] != '/') { fullpath_out = string.Concat(fullpath_out, '/'); } fullpath_out = Path.GetFullPath(fullpath_out); } Console.WriteLine("Output folder: {0}", fullpath_out); SA_Tools.Split.SplitNB.SplitNBFile(fullpath_nb, false, fullpath_out, 1, path_ini); break; case "mdl": case "mdl_b": string fullpath_mdl = Path.GetFullPath(args[1]); if (!File.Exists(fullpath_mdl)) { Console.WriteLine("File {0} doesn't exist.", fullpath_mdl); return; } Console.Write("File: {0}", fullpath_mdl); if (mode == "mdl_b") { bigendian = true; Console.Write(" (Big Endian)\n"); } else { Console.Write(System.Environment.NewLine); } fullpath_out = Path.GetDirectoryName(fullpath_mdl); if (args.Length > 2) { fullpath_out = args[2]; if (fullpath_out[fullpath_out.Length - 1] != '/') { fullpath_out = string.Concat(fullpath_out, '/'); } fullpath_out = Path.GetFullPath(fullpath_out); } Console.WriteLine("Output path: {0}", fullpath_out); if (args.Length > 3) { mdlanimfiles = new List <string>(); Console.WriteLine("Animation files:"); for (int u = 3; u < args.Length; u++) { string animpath = Path.GetFullPath(args[u]); if (File.Exists(animpath)) { mdlanimfiles.Add(animpath); Console.WriteLine(animpath); } else { Console.WriteLine("File {0} doesn't exist.", animpath); } } SA_Tools.SAArc.sa2MDL.Split(bigendian, fullpath_mdl, fullpath_out, mdlanimfiles.ToArray()); } else { SA_Tools.SAArc.sa2MDL.Split(bigendian, fullpath_mdl, fullpath_out, null); } break; case "dllexport": string fullpath_dllex = Path.GetFullPath(args[1]); string type = args[2]; string name = args[3]; string fileOutputPath = ""; if (args.Length > 4) { for (int i = 4; i < args.Length; i++) { if (args[i].Substring(0, 1) != "-" && args[i - 1].Substring(0, 1) != "-") { fileOutputPath = args[i]; } } } if (!File.Exists(fullpath_dllex)) { Console.WriteLine("File {0} doesn't exist.", fullpath_dllex); return; } Console.Write("File: {0}", fullpath_dllex); byte[] datafile = File.ReadAllBytes(fullpath_dllex); uint imageBase = SA_Tools.HelperFunctions.SetupEXE(ref datafile).Value; Dictionary <string, int> exports; Dictionary <int, string> labels = new Dictionary <int, string>(); { int ptr = BitConverter.ToInt32(datafile, BitConverter.ToInt32(datafile, 0x3c) + 4 + 20 + 96); GCHandle handle = GCHandle.Alloc(datafile, GCHandleType.Pinned); IMAGE_EXPORT_DIRECTORY dir = (IMAGE_EXPORT_DIRECTORY)Marshal.PtrToStructure( Marshal.UnsafeAddrOfPinnedArrayElement(datafile, ptr), typeof(IMAGE_EXPORT_DIRECTORY)); handle.Free(); exports = new Dictionary <string, int>(dir.NumberOfFunctions); int nameaddr = dir.AddressOfNames; int ordaddr = dir.AddressOfNameOrdinals; for (int i = 0; i < dir.NumberOfNames; i++) { string namex = datafile.GetCString(BitConverter.ToInt32(datafile, nameaddr), System.Text.Encoding.ASCII); int addr = BitConverter.ToInt32(datafile, dir.AddressOfFunctions + (BitConverter.ToInt16(datafile, ordaddr) * 4)); exports.Add(namex, addr); labels.Add(addr, namex); nameaddr += 4; ordaddr += 2; } Console.Write(" ({0} exports)\n", exports.Count); } if (!exports.ContainsKey(name)) { Console.WriteLine("The export table has no item named {0}", name); return; } int address = exports[name]; Console.WriteLine("{0} {1}:{2}", type, name, address.ToString("X8")); switch (type) { // Landtables case "landtable": case "sa1landtable": case "sadxlandtable": case "sa2landtable": case "sa2blandtable": case "battlelandtable": LandTableFormat landfmt_cur; string landext; switch (type) { case "sa1landtable": landfmt_cur = LandTableFormat.SA1; landext = ".sa1lvl"; break; case "sadxlandtable": landfmt_cur = LandTableFormat.SADX; landext = ".sa1lvl"; break; case "sa2landtable": landfmt_cur = LandTableFormat.SA2; landext = ".sa2lvl"; break; case "sa2blandtable": case "battlelandtable": landfmt_cur = LandTableFormat.SA2B; landext = ".sa2blvl"; break; case "landtable": default: landfmt_cur = LandTableFormat.SADX; landext = ".sa1lvl"; break; } LandTable land = new LandTable(datafile, address, imageBase, landfmt_cur, labels); if (fileOutputPath == "") { fileOutputPath = land.Name + landext; } if (!Directory.Exists(Path.GetDirectoryName(fileOutputPath))) { Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); } land.SaveToFile(fileOutputPath, landfmt_cur, nometa); break; // NJS_OBJECT case "model": case "object": case "basicmodel": case "basicdxmodel": case "chunkmodel": case "gcmodel": { ModelFormat modelfmt_obj; string modelext; switch (type) { case "basicmodel": modelfmt_obj = ModelFormat.Basic; modelext = ".sa1mdl"; break; case "basicdxmodel": modelfmt_obj = ModelFormat.BasicDX; modelext = ".sa1mdl"; break; case "chunkmodel": modelfmt_obj = ModelFormat.Chunk; modelext = ".sa2mdl"; break; case "gcmodel": modelfmt_obj = ModelFormat.GC; modelext = ".sa2bmdl"; break; default: modelfmt_obj = ModelFormat.BasicDX; modelext = ".sa1mdl"; break; } NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt_obj, labels, new Dictionary <int, Attach>()); if (fileOutputPath == "") { fileOutputPath = mdl.Name + modelext; } if (!Directory.Exists(Path.GetDirectoryName(fileOutputPath))) { Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); } ModelFile.CreateFile(fileOutputPath, mdl, null, null, null, null, modelfmt_obj, nometa); } break; // NJS_MOTION case "animation": case "motion": int numparts = 0; for (int a = 3; a < args.Length; a++) { if (args[a] == "-p") { numparts = int.Parse(args[a + 1], System.Globalization.NumberStyles.Integer); } } NJS_MOTION ani = new NJS_MOTION(datafile, address, imageBase, numparts, labels); if (fileOutputPath == "") { fileOutputPath = ani.Name + ".saanim"; } string outpath = Path.GetDirectoryName(Path.GetFullPath(fileOutputPath)); Console.WriteLine("Output file: {0}", Path.GetFullPath(fileOutputPath)); if (!Directory.Exists(outpath)) { Directory.CreateDirectory(outpath); } ani.Save(fileOutputPath, nometa); break; default: Console.WriteLine("Unrecognized export type {0}", type); break; } break; default: Console.WriteLine("Incorrect mode specified. Press ENTER to exit."); Console.ReadLine(); return; } }
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; } }
static void Main(string[] args) { if (args.Length == 0) { Console.Write("Filename: "); args = new string[] { Console.ReadLine().Trim('"') }; } foreach (string filename in args) { Console.WriteLine("Splitting file {0}...", filename); byte[] fc; if (Path.GetExtension(filename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { fc = Prs.Decompress(filename); } else { fc = File.ReadAllBytes(filename); } EventIniData ini = new EventIniData() { Name = Path.GetFileNameWithoutExtension(filename) }; string path = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(filename)), Path.GetFileNameWithoutExtension(filename))).FullName; uint key; if (fc[0] == 0x81) { Console.WriteLine("File is in GC/PC format."); ByteConverter.BigEndian = true; key = 0x8125FE60; ini.Game = Game.SA2B; } else { Console.WriteLine("File is in DC format."); ByteConverter.BigEndian = false; key = 0xC600000; ini.Game = Game.SA2; } List <string> nodenames = new List <string>(); Dictionary <string, KeyValuePair <string, NJS_OBJECT> > modelfiles = new Dictionary <string, KeyValuePair <string, NJS_OBJECT> >(); int ptr = fc.GetPointer(0x20, key); if (ptr != 0) { for (int i = 0; i < (ini.Game == Game.SA2B ? 18 : 16); i++) { UpgradeInfo info = new UpgradeInfo(); info.RootNode = GetModel(fc, ptr, key, Path.Combine(path, $"Upgrade {i + 1} Root.sa2mdl"), nodenames, modelfiles); if (info.RootNode != null) { int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { info.AttachNode1 = $"object_{ptr2:X8}"; } info.Model1 = GetModel(fc, ptr + 8, key, Path.Combine(path, $"Upgrade {i + 1} Model 1.sa2mdl"), nodenames, modelfiles); ptr2 = fc.GetPointer(ptr + 0xC, key); if (ptr2 != 0) { info.AttachNode2 = $"object_{ptr2:X8}"; } info.Model2 = GetModel(fc, ptr + 0x10, key, Path.Combine(path, $"Upgrade {i + 1} Model 2.sa2mdl"), nodenames, modelfiles); } ini.Upgrades.Add(info); ptr += 0x14; } } else { Console.WriteLine("Event contains no character upgrades."); } int gcnt = ByteConverter.ToInt32(fc, 8); ptr = fc.GetPointer(0, key); if (ptr != 0) { Console.WriteLine("Event contains {0} group(s).", gcnt + 1); for (int gn = 0; gn <= gcnt; gn++) { GroupInfo info = new GroupInfo(); int ptr2 = fc.GetPointer(ptr, key); int ecnt = ByteConverter.ToInt32(fc, ptr + 4); if (ptr2 != 0) { Console.WriteLine("Group {0} contains {1} entit{2}.", gn + 1, ecnt, ecnt == 1 ? "y" : "ies"); for (int en = 0; en < ecnt; en++) { string name = GetModel(fc, ptr2, key, Path.Combine(path, $"Group {gn + 1} Entity {en + 1} Model.sa2mdl"), nodenames, modelfiles); if (name != null) { info.Entities.Add(name); } ptr2 += ini.Game == Game.SA2B ? 0x2C : 0x20; } } else { Console.WriteLine("Group {0} contains no entities.", gn + 1); } ptr2 = fc.GetPointer(ptr + 0x18, key); if (ptr2 != 0) { info.Big = GetModel(fc, ptr2, key, Path.Combine(path, $"Group {gn + 1} Big Model.sa2mdl"), nodenames, modelfiles); } ini.Groups.Add(info); ptr += 0x20; } } else { Console.WriteLine("Event contains no groups."); } ptr = fc.GetPointer(0x18, key); if (ptr != 0) { for (int i = 0; i < 93; i++) { string name = GetModel(fc, ptr, key, Path.Combine(path, $"Mech Part {i + 1}.sa2mdl"), nodenames, modelfiles); if (name != null) { ini.MechParts.Add(i, name); } ptr += 4; } } else { Console.WriteLine("Event contains no mech parts."); } ptr = fc.GetPointer(0x1C, key); if (ptr != 0) { ini.TailsTails = GetModel(fc, ptr, key, Path.Combine(path, $"Tails tails.sa2mdl"), nodenames, modelfiles); } else { Console.WriteLine("Event does not contain Tails' tails."); } foreach (var item in modelfiles) { ModelFile.CreateFile(item.Value.Key, item.Value.Value, null, null, null, null, null, ModelFormat.Chunk); ini.Files.Add(Path.GetFileName(item.Value.Key), HelperFunctions.FileHash(item.Value.Key)); } IniSerializer.Serialize(ini, Path.Combine(path, Path.ChangeExtension(Path.GetFileName(filename), ".ini"))); } }
static void Main(string[] args) { string filename; if (args.Length > 0) { filename = args[0]; Console.WriteLine("File: {0}", filename); } else { Console.Write("File: "); filename = Console.ReadLine(); } ModelFile model = new ModelFile(filename); switch (model.Format) { case ModelFormat.Basic: foreach (NJS_OBJECT obj in model.Model.GetObjects().Where(obj => obj.Attach is BasicAttach)) { BasicAttach basatt = (BasicAttach)obj.Attach; ChunkAttach cnkatt = new ChunkAttach(true, true) { Name = basatt.Name + "_cnk", Bounds = basatt.Bounds }; obj.Attach = cnkatt; VertexChunk vcnk; bool hasnormal = basatt.Normal?.Length > 0; bool hasvcolor = basatt.Mesh.Any(a => a.VColor != null); if (hasvcolor) { vcnk = new VertexChunk(ChunkType.Vertex_VertexDiffuse8); } else if (hasnormal) { vcnk = new VertexChunk(ChunkType.Vertex_VertexNormal); } else { vcnk = new VertexChunk(ChunkType.Vertex_Vertex); } List <CachedVertex> cache = new List <CachedVertex>(basatt.Vertex.Length); List <List <Strip> > strips = new List <List <Strip> >(); List <List <List <UV> > > uvs = new List <List <List <UV> > >(); foreach (NJS_MESHSET mesh in basatt.Mesh) { List <Strip> polys = new List <Strip>(); List <List <UV> > us = null; bool hasUV = mesh.UV != null; bool hasVColor = mesh.VColor != null; int currentstriptotal = 0; switch (mesh.PolyType) { case Basic_PolyType.Triangles: { List <ushort> tris = new List <ushort>(); Dictionary <ushort, UV> uvmap = new Dictionary <ushort, UV>(); foreach (Poly poly in mesh.Poly) { for (int i = 0; i < 3; i++) { ushort ind = (ushort)cache.AddUnique(new CachedVertex( basatt.Vertex[poly.Indexes[i]], basatt.Normal[poly.Indexes[i]], hasVColor ? mesh.VColor[currentstriptotal] : Color.White, mesh.UV?[currentstriptotal])); if (hasUV) { uvmap[ind] = mesh.UV[currentstriptotal]; } ++currentstriptotal; tris.Add(ind); } } if (hasUV) { us = new List <List <UV> >(); } nvStripifier.GenerateStrips(tris.ToArray(), out var primitiveGroups); // Add strips for (var i = 0; i < primitiveGroups.Length; i++) { var primitiveGroup = primitiveGroups[i]; System.Diagnostics.Debug.Assert(primitiveGroup.Type == PrimitiveType.TriangleStrip); var stripIndices = new ushort[primitiveGroup.Indices.Length]; List <UV> stripuv = new List <UV>(); for (var j = 0; j < primitiveGroup.Indices.Length; j++) { var vertexIndex = primitiveGroup.Indices[j]; stripIndices[j] = vertexIndex; if (hasUV) { stripuv.Add(uvmap[vertexIndex]); } } polys.Add(new Strip(stripIndices, false)); if (hasUV) { us.Add(stripuv); } } } break; case Basic_PolyType.Quads: { List <ushort> tris = new List <ushort>(); Dictionary <ushort, UV> uvmap = new Dictionary <ushort, UV>(); foreach (Poly poly in mesh.Poly) { ushort[] quad = new ushort[4]; for (int i = 0; i < 4; i++) { ushort ind = (ushort)cache.AddUnique(new CachedVertex( basatt.Vertex[poly.Indexes[i]], basatt.Normal[poly.Indexes[i]], hasVColor ? mesh.VColor[currentstriptotal] : Color.White, mesh.UV?[currentstriptotal])); if (hasUV) { uvmap[ind] = mesh.UV[currentstriptotal]; } ++currentstriptotal; quad[i] = ind; } tris.Add(quad[0]); tris.Add(quad[1]); tris.Add(quad[2]); tris.Add(quad[2]); tris.Add(quad[1]); tris.Add(quad[3]); } if (hasUV) { us = new List <List <UV> >(); } nvStripifier.GenerateStrips(tris.ToArray(), out var primitiveGroups); // Add strips for (var i = 0; i < primitiveGroups.Length; i++) { var primitiveGroup = primitiveGroups[i]; System.Diagnostics.Debug.Assert(primitiveGroup.Type == PrimitiveType.TriangleStrip); var stripIndices = new ushort[primitiveGroup.Indices.Length]; List <UV> stripuv = new List <UV>(); for (var j = 0; j < primitiveGroup.Indices.Length; j++) { var vertexIndex = primitiveGroup.Indices[j]; stripIndices[j] = vertexIndex; if (hasUV) { stripuv.Add(uvmap[vertexIndex]); } } polys.Add(new Strip(stripIndices, false)); if (hasUV) { us.Add(stripuv); } } } break; case Basic_PolyType.NPoly: case Basic_PolyType.Strips: if (hasUV) { us = new List <List <UV> >(); } foreach (Strip poly in mesh.Poly.Cast <Strip>()) { List <UV> stripuv = new List <UV>(); ushort[] inds = (ushort[])poly.Indexes.Clone(); for (int i = 0; i < poly.Indexes.Length; i++) { inds[i] = (ushort)cache.AddUnique(new CachedVertex( basatt.Vertex[poly.Indexes[i]], basatt.Normal[poly.Indexes[i]], hasVColor ? mesh.VColor[currentstriptotal] : Color.White)); if (hasUV) { stripuv.Add(mesh.UV[currentstriptotal]); } ++currentstriptotal; } polys.Add(new Strip(inds, poly.Reversed)); if (hasUV) { us.Add(stripuv); } } break; } strips.Add(polys); uvs.Add(us); } foreach (var item in cache) { vcnk.Vertices.Add(item.vertex); if (hasnormal) { vcnk.Normals.Add(item.normal); } if (hasvcolor) { vcnk.Diffuse.Add(item.color); } } vcnk.VertexCount = (ushort)cache.Count; switch (vcnk.Type) { case ChunkType.Vertex_Vertex: vcnk.Size = (ushort)(vcnk.VertexCount * 3 + 1); break; case ChunkType.Vertex_VertexDiffuse8: vcnk.Size = (ushort)(vcnk.VertexCount * 4 + 1); break; case ChunkType.Vertex_VertexNormal: vcnk.Size = (ushort)(vcnk.VertexCount * 6 + 1); break; case ChunkType.Vertex_VertexNormalDiffuse8: vcnk.Size = (ushort)(vcnk.VertexCount * 7 + 1); break; } cnkatt.Vertex.Add(vcnk); for (int i = 0; i < basatt.Mesh.Count; i++) { NJS_MESHSET mesh = basatt.Mesh[i]; NJS_MATERIAL mat = null; if (basatt.Material != null && mesh.MaterialID < basatt.Material.Count) { mat = basatt.Material[mesh.MaterialID]; cnkatt.Poly.Add(new PolyChunkTinyTextureID() { ClampU = mat.ClampU, ClampV = mat.ClampV, FilterMode = mat.FilterMode, FlipU = mat.FlipU, FlipV = mat.FlipV, SuperSample = mat.SuperSample, TextureID = (ushort)mat.TextureID }); cnkatt.Poly.Add(new PolyChunkMaterial() { SourceAlpha = mat.SourceAlpha, DestinationAlpha = mat.DestinationAlpha, Diffuse = mat.DiffuseColor, Specular = mat.SpecularColor, SpecularExponent = (byte)mat.Exponent }); } PolyChunkStrip strip; if (mesh.UV != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripUVN); } else { strip = new PolyChunkStrip(ChunkType.Strip_Strip); } if (mat != null) { strip.IgnoreLight = mat.IgnoreLighting; strip.IgnoreSpecular = mat.IgnoreSpecular; strip.UseAlpha = mat.UseAlpha; strip.DoubleSide = mat.DoubleSided; strip.FlatShading = mat.FlatShading; strip.EnvironmentMapping = mat.EnvironmentMap; } for (int i1 = 0; i1 < strips[i].Count; i1++) { Strip item = strips[i][i1]; UV[] uv2 = null; if (mesh.UV != null) { uv2 = uvs[i][i1].ToArray(); } strip.Strips.Add(new PolyChunkStrip.Strip(item.Reversed, item.Indexes, uv2, null)); } cnkatt.Poly.Add(strip); } } ModelFile.CreateFile(System.IO.Path.ChangeExtension(filename, "sa2mdl"), model.Model, null, null, null, null, null, ModelFormat.Chunk); break; case ModelFormat.Chunk: Vertex[] VertexBuffer = new Vertex[0]; Vertex[] NormalBuffer = new Vertex[0]; foreach (NJS_OBJECT obj in model.Model.GetObjects().Where(obj => obj.Attach is ChunkAttach)) { ChunkAttach cnkatt = (ChunkAttach)obj.Attach; BasicAttach basatt = new BasicAttach() { Name = cnkatt.Name, Bounds = cnkatt.Bounds }; obj.Attach = basatt; if (cnkatt.Vertex != null) { foreach (VertexChunk chunk in cnkatt.Vertex) { if (VertexBuffer.Length < chunk.IndexOffset + chunk.VertexCount) { Array.Resize(ref VertexBuffer, chunk.IndexOffset + chunk.VertexCount); Array.Resize(ref NormalBuffer, chunk.IndexOffset + chunk.VertexCount); } Array.Copy(chunk.Vertices.ToArray(), 0, VertexBuffer, chunk.IndexOffset, chunk.Vertices.Count); Array.Copy(chunk.Normals.ToArray(), 0, NormalBuffer, chunk.IndexOffset, chunk.Normals.Count); } } NJS_MATERIAL material = new NJS_MATERIAL() { UseTexture = true }; int minVtx = int.MaxValue; int maxVtx = int.MinValue; foreach (PolyChunk chunk in cnkatt.Poly) { switch (chunk.Type) { case ChunkType.Bits_BlendAlpha: { PolyChunkBitsBlendAlpha c2 = (PolyChunkBitsBlendAlpha)chunk; material.SourceAlpha = c2.SourceAlpha; material.DestinationAlpha = c2.DestinationAlpha; } break; case ChunkType.Bits_MipmapDAdjust: break; case ChunkType.Bits_SpecularExponent: material.Exponent = ((PolyChunkBitsSpecularExponent)chunk).SpecularExponent; break; case ChunkType.Tiny_TextureID: case ChunkType.Tiny_TextureID2: { PolyChunkTinyTextureID c2 = (PolyChunkTinyTextureID)chunk; material.ClampU = c2.ClampU; material.ClampV = c2.ClampV; material.FilterMode = c2.FilterMode; material.FlipU = c2.FlipU; material.FlipV = c2.FlipV; material.SuperSample = c2.SuperSample; material.TextureID = c2.TextureID; } break; case ChunkType.Material_Diffuse: case ChunkType.Material_Ambient: case ChunkType.Material_DiffuseAmbient: case ChunkType.Material_Specular: case ChunkType.Material_DiffuseSpecular: case ChunkType.Material_AmbientSpecular: case ChunkType.Material_DiffuseAmbientSpecular: case ChunkType.Material_Diffuse2: case ChunkType.Material_Ambient2: case ChunkType.Material_DiffuseAmbient2: case ChunkType.Material_Specular2: case ChunkType.Material_DiffuseSpecular2: case ChunkType.Material_AmbientSpecular2: case ChunkType.Material_DiffuseAmbientSpecular2: { PolyChunkMaterial c2 = (PolyChunkMaterial)chunk; material.SourceAlpha = c2.SourceAlpha; material.DestinationAlpha = c2.DestinationAlpha; if (c2.Diffuse.HasValue) { material.DiffuseColor = c2.Diffuse.Value; } if (c2.Specular.HasValue) { material.SpecularColor = c2.Specular.Value; material.Exponent = c2.SpecularExponent; } } break; case ChunkType.Strip_Strip: case ChunkType.Strip_StripUVN: case ChunkType.Strip_StripUVH: case ChunkType.Strip_StripNormal: case ChunkType.Strip_StripUVNNormal: case ChunkType.Strip_StripUVHNormal: case ChunkType.Strip_StripColor: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: case ChunkType.Strip_Strip2: case ChunkType.Strip_StripUVN2: case ChunkType.Strip_StripUVH2: { PolyChunkStrip c2 = (PolyChunkStrip)chunk; material.DoubleSided = c2.DoubleSide; material.EnvironmentMap = c2.EnvironmentMapping; material.FlatShading = c2.FlatShading; material.IgnoreLighting = c2.IgnoreLight; material.IgnoreSpecular = c2.IgnoreSpecular; material.UseAlpha = c2.UseAlpha; bool hasVColor = false; switch (chunk.Type) { case ChunkType.Strip_StripColor: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: hasVColor = true; break; } bool hasUV = false; switch (chunk.Type) { case ChunkType.Strip_StripUVN: case ChunkType.Strip_StripUVH: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: case ChunkType.Strip_StripUVN2: case ChunkType.Strip_StripUVH2: hasUV = true; break; } List <Strip> strips = new List <Strip>(c2.StripCount); List <UV> uvs = hasUV ? new List <UV>() : null; List <Color> vcolors = hasVColor ? new List <Color>() : null; foreach (PolyChunkStrip.Strip strip in c2.Strips) { minVtx = Math.Min(minVtx, strip.Indexes.Min()); maxVtx = Math.Max(maxVtx, strip.Indexes.Max()); strips.Add(new Strip((ushort[])strip.Indexes.Clone(), strip.Reversed)); if (hasUV) { uvs.AddRange(strip.UVs); } if (hasVColor) { vcolors.AddRange(strip.VColors); } } NJS_MESHSET mesh = new NJS_MESHSET(strips.ToArray(), false, hasUV, hasVColor); if (hasUV) { uvs.CopyTo(mesh.UV); } if (hasVColor) { vcolors.CopyTo(mesh.VColor); } mesh.MaterialID = (ushort)basatt.Material.Count; basatt.Mesh.Add(mesh); basatt.Material.Add(material); material = new NJS_MATERIAL(material.GetBytes(), 0); } break; } } int numVtx = maxVtx - minVtx + 1; basatt.ResizeVertexes(numVtx); Array.Copy(VertexBuffer, minVtx, basatt.Vertex, 0, numVtx); Array.Copy(NormalBuffer, minVtx, basatt.Normal, 0, numVtx); foreach (NJS_MESHSET mesh in basatt.Mesh) { foreach (Poly poly in mesh.Poly) { for (int i = 0; i < poly.Indexes.Length; i++) { poly.Indexes[i] = (ushort)(poly.Indexes[i] - minVtx); } } } } ModelFile.CreateFile(System.IO.Path.ChangeExtension(filename, "sa1mdl"), model.Model, null, null, null, null, null, ModelFormat.Basic); break; } }
private void button1_Click(object sender, EventArgs e) { bool success = false; uint address = (uint)numericUpDownBinaryAddress.Value; if (checkBoxBinaryMemory.Checked) { address -= (uint)numericUpDownBinaryKey.Value; } LandTableFormat format = (LandTableFormat)comboBoxBinaryFormat.SelectedIndex; LandTableFormat outfmt = format; if (format == LandTableFormat.SADX) { outfmt = LandTableFormat.SA1; } ByteConverter.BigEndian = checkBoxBinaryBigEndian.Checked; Settings.Author = textBoxBinaryAuthor.Text; Settings.Save(); SaveFileDialog sd = new SaveFileDialog(); switch (comboBoxBinaryItemType.SelectedIndex) { //Level case 0: sd = new SaveFileDialog() { DefaultExt = outfmt.ToString().ToLowerInvariant() + "lvl", Filter = outfmt.ToString().ToUpperInvariant() + "LVL Files|*." + outfmt.ToString().ToLowerInvariant() + "lvl|All Files|*.*" }; if (sd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { new LandTable(file, (int)numericUpDownBinaryAddress.Value, (uint)numericUpDownBinaryKey.Value, format) { Author = textBoxBinaryAuthor.Text, Description = textBoxBinaryDescription.Text }.SaveToFile(sd.FileName, outfmt); if (checkBoxBinaryStructs.Checked) { ConvertToText(sd.FileName); } if (!checkBoxBinarySAModel.Checked) { File.Delete(sd.FileName); } success = true; } break; //Model case 1: sd = new SaveFileDialog() { DefaultExt = outfmt.ToString().ToLowerInvariant() + "mdl", Filter = outfmt.ToString().ToUpperInvariant() + "MDL Files|*." + outfmt.ToString().ToLowerInvariant() + "mdl|All Files|*.*" }; if (sd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { NJS_OBJECT tempmodel = new NJS_OBJECT(file, (int)address, (uint)numericUpDownBinaryKey.Value, (ModelFormat)comboBoxBinaryFormat.SelectedIndex, null); ModelFile.CreateFile(sd.FileName, tempmodel, null, textBoxBinaryAuthor.Text, textBoxBinaryDescription.Text, null, (ModelFormat)comboBoxBinaryFormat.SelectedIndex); ConvertToText(sd.FileName, checkBoxBinaryStructs.Checked, checkBoxBinaryNJA.Checked, false); if (!checkBoxBinarySAModel.Checked) { File.Delete(sd.FileName); } success = true; } break; //Action case 2: sd = new SaveFileDialog() { DefaultExt = outfmt.ToString().ToLowerInvariant() + "mdl", Filter = outfmt.ToString().ToUpperInvariant() + "MDL Files|*." + outfmt.ToString().ToLowerInvariant() + "mdl|All Files|*.*" }; if (sd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { //Model NJS_ACTION tempaction = new NJS_ACTION(file, (int)address, (uint)numericUpDownBinaryKey.Value, (ModelFormat)comboBoxBinaryFormat.SelectedIndex, null); NJS_OBJECT tempmodel = tempaction.Model; ModelFile.CreateFile(sd.FileName, tempmodel, null, textBoxBinaryAuthor.Text, textBoxBinaryDescription.Text, null, (ModelFormat)comboBoxBinaryFormat.SelectedIndex); ConvertToText(sd.FileName, checkBoxBinaryStructs.Checked, checkBoxBinaryNJA.Checked, false); if (!checkBoxBinarySAModel.Checked) { File.Delete(sd.FileName); } //Action string saanimPath = Path.Combine(Path.GetDirectoryName(sd.FileName), Path.GetFileNameWithoutExtension(sd.FileName) + ".saanim"); tempaction.Animation.Save(saanimPath); ConvertToText(saanimPath, checkBoxBinaryStructs.Checked, false, checkBoxBinaryJSON.Checked); if (checkBoxBinarySAModel.Checked) { using (TextWriter twmain = File.CreateText(Path.Combine(Path.GetDirectoryName(sd.FileName), Path.GetFileNameWithoutExtension(sd.FileName) + ".action"))) { twmain.WriteLine(Path.GetFileName(saanimPath)); twmain.Flush(); twmain.Close(); } } else { File.Delete(saanimPath); } success = true; } break; } if (success) { MessageBox.Show("Data extracted!", "Binary Data Extractor", MessageBoxButtons.OK, MessageBoxIcon.Information); } }
public static void Split(string filename) { nodenames.Clear(); modelfiles.Clear(); motionfiles.Clear(); Console.WriteLine("Splitting file {0}...", filename); byte[] fc; if (Path.GetExtension(filename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { fc = Prs.Decompress(filename); } else { fc = File.ReadAllBytes(filename); } MiniEventIniData ini = new MiniEventIniData() { Name = Path.GetFileNameWithoutExtension(filename) }; string path = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(filename)), Path.GetFileNameWithoutExtension(filename))).FullName; uint key; List <NJS_MOTION> motions = null; if (fc[4] == 0x81) { Console.WriteLine("File is in GC/PC format."); ByteConverter.BigEndian = true; key = 0x816DFE60; ini.Game = Game.SA2B; } else { Console.WriteLine("File is in DC format."); ByteConverter.BigEndian = false; key = 0xCB00000; ini.Game = Game.SA2; } int ptr = fc.GetPointer(8, key); if (ptr != 0) { Console.WriteLine("Sonic is in this Mini-Event"); Directory.CreateDirectory(Path.Combine(path, "Sonic")); MiniEventChars data = new MiniEventChars(); data.BodyAnims = GetMotion(fc, ptr, key, $"Sonic\\Body.saanim", motions, 62); int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { data.HeadPart = GetModel(fc, ptr + 4, key, $"Sonic\\Head.sa2mdl"); } if (data.HeadPart != null) { data.HeadAnims = GetMotion(fc, ptr + 8, key, $"Sonic\\Head.saanim", motions, modelfiles[data.HeadPart].Model.CountAnimated()); if (data.HeadAnims != null) { modelfiles[data.HeadPart].Motions.Add($"Head.saanim"); } data.HeadShapeMotions = GetMotion(fc, ptr + 0xC, key, $"Sonic\\HeadShape.saanim", motions, modelfiles[data.HeadPart].Model.CountMorph()); if (data.HeadShapeMotions != null) { modelfiles[data.HeadPart].Motions.Add($"HeadShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x10, key); if (ptr2 != 0) { data.MouthPart = GetModel(fc, ptr + 0x10, key, $"Sonic\\Mouth.sa2mdl"); } if (data.MouthPart != null) { data.MouthAnims = GetMotion(fc, ptr + 0x14, key, $"Sonic\\Mouth.saanim", motions, modelfiles[data.MouthPart].Model.CountAnimated()); if (data.MouthAnims != null) { modelfiles[data.MouthPart].Motions.Add($"Mouth.saanim"); } data.MouthShapeMotions = GetMotion(fc, ptr + 0x18, key, $"Sonic\\MouthShape.saanim", motions, modelfiles[data.MouthPart].Model.CountMorph()); if (data.MouthShapeMotions != null) { modelfiles[data.MouthPart].Motions.Add($"MouthShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x1C, key); if (ptr2 != 0) { data.LHandPart = GetModel(fc, ptr + 0x1C, key, $"Sonic\\LeftHand.sa2mdl"); } if (data.LHandPart != null) { data.LHandAnims = GetMotion(fc, ptr + 0x20, key, $"Sonic\\LeftHand.saanim", motions, modelfiles[data.LHandPart].Model.CountAnimated()); if (data.LHandAnims != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHand.saanim"); } data.LHandShapeMotions = GetMotion(fc, ptr + 0x24, key, $"Sonic\\LeftHandShape.saanim", motions, modelfiles[data.LHandPart].Model.CountMorph()); if (data.LHandShapeMotions != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHandShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x28, key); if (ptr2 != 0) { data.RHandPart = GetModel(fc, ptr + 0x28, key, $"Sonic\\RightHand.sa2mdl"); } if (data.RHandPart != null) { data.RHandAnims = GetMotion(fc, ptr + 0x2C, key, $"Sonic\\RightHand.saanim", motions, modelfiles[data.RHandPart].Model.CountAnimated()); if (data.RHandAnims != null) { modelfiles[data.RHandPart].Motions.Add($"RightHand.saanim"); } data.RHandShapeMotions = GetMotion(fc, ptr + 0x30, key, $"Sonic\\RightHandShape.saanim", motions, modelfiles[data.RHandPart].Model.CountMorph()); if (data.RHandShapeMotions != null) { modelfiles[data.RHandPart].Motions.Add($"RightHandShape.saanim"); } } ini.Sonic.Add(data); } ptr = fc.GetPointer(0xC, key); if (ptr != 0) { Console.WriteLine("Shadow is in this Mini-Event"); Directory.CreateDirectory(Path.Combine(path, "Shadow")); MiniEventChars data = new MiniEventChars(); data.BodyAnims = GetMotion(fc, ptr, key, $"Shadow\\Body.saanim", motions, 62); int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { data.HeadPart = GetModel(fc, ptr + 4, key, $"Shadow\\Head.sa2mdl"); } if (data.HeadPart != null) { data.HeadAnims = GetMotion(fc, ptr + 8, key, $"Shadow\\Head.saanim", motions, modelfiles[data.HeadPart].Model.CountAnimated()); if (data.HeadAnims != null) { modelfiles[data.HeadPart].Motions.Add($"Head.saanim"); } data.HeadShapeMotions = GetMotion(fc, ptr + 0xC, key, $"Shadow\\HeadShape.saanim", motions, modelfiles[data.HeadPart].Model.CountMorph()); if (data.HeadShapeMotions != null) { modelfiles[data.HeadPart].Motions.Add($"HeadShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x10, key); if (ptr2 != 0) { data.MouthPart = GetModel(fc, ptr + 0x10, key, $"Shadow\\Mouth.sa2mdl"); } if (data.MouthPart != null) { data.MouthAnims = GetMotion(fc, ptr + 0x14, key, $"Shadow\\Mouth.saanim", motions, modelfiles[data.MouthPart].Model.CountAnimated()); if (data.MouthAnims != null) { modelfiles[data.MouthPart].Motions.Add($"Mouth.saanim"); } data.MouthShapeMotions = GetMotion(fc, ptr + 0x18, key, $"Shadow\\MouthShape.saanim", motions, modelfiles[data.MouthPart].Model.CountMorph()); if (data.MouthShapeMotions != null) { modelfiles[data.MouthPart].Motions.Add($"MouthShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x1C, key); if (ptr2 != 0) { data.LHandPart = GetModel(fc, ptr + 0x1C, key, $"Shadow\\LeftHand.sa2mdl"); } if (data.LHandPart != null) { data.LHandAnims = GetMotion(fc, ptr + 0x20, key, $"Shadow\\LeftHand.saanim", motions, modelfiles[data.LHandPart].Model.CountAnimated()); if (data.LHandAnims != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHand.saanim"); } data.LHandShapeMotions = GetMotion(fc, ptr + 0x24, key, $"Shadow\\LeftHandShape.saanim", motions, modelfiles[data.LHandPart].Model.CountMorph()); if (data.LHandShapeMotions != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHandShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x28, key); if (ptr2 != 0) { data.RHandPart = GetModel(fc, ptr + 0x28, key, $"Shadow\\RightHand.sa2mdl"); } if (data.RHandPart != null) { data.RHandAnims = GetMotion(fc, ptr + 0x2C, key, $"Shadow\\RightHand.saanim", motions, modelfiles[data.RHandPart].Model.CountAnimated()); if (data.RHandAnims != null) { modelfiles[data.RHandPart].Motions.Add($"RightHand.saanim"); } data.RHandShapeMotions = GetMotion(fc, ptr + 0x30, key, $"Shadow\\RightHandShape.saanim", motions, modelfiles[data.RHandPart].Model.CountMorph()); if (data.RHandShapeMotions != null) { modelfiles[data.RHandPart].Motions.Add($"RightHandShape.saanim"); } } ini.Shadow.Add(data); } ptr = fc.GetPointer(0x18, key); if (ptr != 0) { Console.WriteLine("Knuckles is in this Mini-Event"); Directory.CreateDirectory(Path.Combine(path, "Knuckles")); MiniEventChars data = new MiniEventChars(); data.BodyAnims = GetMotion(fc, ptr, key, $"Knuckles\\Body.saanim", motions, 62); int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { data.HeadPart = GetModel(fc, ptr + 4, key, $"Knuckles\\Head.sa2mdl"); } if (data.HeadPart != null) { data.HeadAnims = GetMotion(fc, ptr + 8, key, $"Knuckles\\Head.saanim", motions, modelfiles[data.HeadPart].Model.CountAnimated()); if (data.HeadAnims != null) { modelfiles[data.HeadPart].Motions.Add($"Head.saanim"); } data.HeadShapeMotions = GetMotion(fc, ptr + 0xC, key, $"Knuckles\\HeadShape.saanim", motions, modelfiles[data.HeadPart].Model.CountMorph()); if (data.HeadShapeMotions != null) { modelfiles[data.HeadPart].Motions.Add($"HeadShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x10, key); if (ptr2 != 0) { data.MouthPart = GetModel(fc, ptr + 0x10, key, $"Knuckles\\Mouth.sa2mdl"); } if (data.MouthPart != null) { data.MouthAnims = GetMotion(fc, ptr + 0x14, key, $"Knuckles\\Mouth.saanim", motions, modelfiles[data.MouthPart].Model.CountAnimated()); if (data.MouthAnims != null) { modelfiles[data.MouthPart].Motions.Add($"Mouth.saanim"); } data.MouthShapeMotions = GetMotion(fc, ptr + 0x18, key, $"Knuckles\\MouthShape.saanim", motions, modelfiles[data.MouthPart].Model.CountMorph()); if (data.MouthShapeMotions != null) { modelfiles[data.MouthPart].Motions.Add($"MouthShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x1C, key); if (ptr2 != 0) { data.LHandPart = GetModel(fc, ptr + 0x1C, key, $"Knuckles\\LeftHand.sa2mdl"); } if (data.LHandPart != null) { data.LHandAnims = GetMotion(fc, ptr + 0x20, key, $"Knuckles\\LeftHand.saanim", motions, modelfiles[data.LHandPart].Model.CountAnimated()); if (data.LHandAnims != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHand.saanim"); } data.LHandShapeMotions = GetMotion(fc, ptr + 0x24, key, $"Knuckles\\LeftHandShape.saanim", motions, modelfiles[data.LHandPart].Model.CountMorph()); if (data.LHandShapeMotions != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHandShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x28, key); if (ptr2 != 0) { data.RHandPart = GetModel(fc, ptr + 0x28, key, $"Knuckles\\RightHand.sa2mdl"); } if (data.RHandPart != null) { data.RHandAnims = GetMotion(fc, ptr + 0x2C, key, $"Knuckles\\RightHand.saanim", motions, modelfiles[data.RHandPart].Model.CountAnimated()); if (data.RHandAnims != null) { modelfiles[data.RHandPart].Motions.Add($"RightHand.saanim"); } data.RHandShapeMotions = GetMotion(fc, ptr + 0x30, key, $"Knuckles\\RightHandShape.saanim", motions, modelfiles[data.RHandPart].Model.CountMorph()); if (data.RHandShapeMotions != null) { modelfiles[data.RHandPart].Motions.Add($"RightHandShape.saanim"); } } ini.Knuckles.Add(data); } ptr = fc.GetPointer(0x1C, key); if (ptr != 0) { Console.WriteLine("Rouge is in this Mini-Event"); Directory.CreateDirectory(Path.Combine(path, "Rouge")); MiniEventChars data = new MiniEventChars(); data.BodyAnims = GetMotion(fc, ptr, key, $"Rouge\\Body.saanim", motions, 62); int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { data.HeadPart = GetModel(fc, ptr + 4, key, $"Rouge\\Head.sa2mdl"); } if (data.HeadPart != null) { data.HeadAnims = GetMotion(fc, ptr + 8, key, $"Rouge\\Head.saanim", motions, modelfiles[data.HeadPart].Model.CountAnimated()); if (data.HeadAnims != null) { modelfiles[data.HeadPart].Motions.Add($"Head.saanim"); } data.HeadShapeMotions = GetMotion(fc, ptr + 0xC, key, $"Rouge\\HeadShape.saanim", motions, modelfiles[data.HeadPart].Model.CountMorph()); if (data.HeadShapeMotions != null) { modelfiles[data.HeadPart].Motions.Add($"HeadShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x10, key); if (ptr2 != 0) { data.MouthPart = GetModel(fc, ptr + 0x10, key, $"Rouge\\Mouth.sa2mdl"); } if (data.MouthPart != null) { data.MouthAnims = GetMotion(fc, ptr + 0x14, key, $"Rouge\\Mouth.saanim", motions, modelfiles[data.MouthPart].Model.CountAnimated()); if (data.MouthAnims != null) { modelfiles[data.MouthPart].Motions.Add($"Mouth.saanim"); } data.MouthShapeMotions = GetMotion(fc, ptr + 0x18, key, $"Rouge\\MouthShape.saanim", motions, modelfiles[data.MouthPart].Model.CountMorph()); if (data.MouthShapeMotions != null) { modelfiles[data.MouthPart].Motions.Add($"MouthShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x1C, key); if (ptr2 != 0) { data.LHandPart = GetModel(fc, ptr + 0x1C, key, $"Rouge\\LeftHand.sa2mdl"); } if (data.LHandPart != null) { data.LHandAnims = GetMotion(fc, ptr + 0x20, key, $"Rouge\\LeftHand.saanim", motions, modelfiles[data.LHandPart].Model.CountAnimated()); if (data.LHandAnims != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHand.saanim"); } data.LHandShapeMotions = GetMotion(fc, ptr + 0x24, key, $"Rouge\\LeftHandShape.saanim", motions, modelfiles[data.LHandPart].Model.CountMorph()); if (data.LHandShapeMotions != null) { modelfiles[data.LHandPart].Motions.Add($"LeftHandShape.saanim"); } } ptr2 = fc.GetPointer(ptr + 0x28, key); if (ptr2 != 0) { data.RHandPart = GetModel(fc, ptr + 0x28, key, $"Rouge\\RightHand.sa2mdl"); } if (data.RHandPart != null) { data.RHandAnims = GetMotion(fc, ptr + 0x2C, key, $"Rouge\\RightHand.saanim", motions, modelfiles[data.RHandPart].Model.CountAnimated()); if (data.RHandAnims != null) { modelfiles[data.RHandPart].Motions.Add($"RightHand.saanim"); } data.RHandShapeMotions = GetMotion(fc, ptr + 0x30, key, $"Rouge\\RightHandShape.saanim", motions, modelfiles[data.RHandPart].Model.CountMorph()); if (data.RHandShapeMotions != null) { modelfiles[data.RHandPart].Motions.Add($"RightHandShape.saanim"); } } ini.Rouge.Add(data); } ptr = fc.GetPointer(0x24, key); if (ptr != 0) { Console.WriteLine("Mech Eggman is in this Mini-Event"); Directory.CreateDirectory(Path.Combine(path, "Mech Eggman")); ini.MechEggmanBodyAnims = GetMotion(fc, ptr, key, $"Mech Eggman\\Body.saanim", motions, 33); } ptr = fc.GetPointer(4, key); if (ptr != 0) { ini.Camera = GetMotion(fc, ptr + 0x10, key, $"Camera.saanim", motions, 1); ini.CamFrames = ByteConverter.ToInt32(fc, ptr + 4); } else { Console.WriteLine("Mini-Event does not contain a camera."); } foreach (var item in motionfiles.Values) { string fn = item.Filename; string fp = Path.Combine(path, fn); item.Motion.Save(fp); ini.Files.Add(fn, HelperFunctions.FileHash(fp)); } foreach (var item in modelfiles.Values) { string fp = Path.Combine(path, item.Filename); ModelFile.CreateFile(fp, item.Model, item.Motions.ToArray(), null, null, null, item.Format); ini.Files.Add(item.Filename, HelperFunctions.FileHash(fp)); } JsonSerializer js = new JsonSerializer { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }; using (var tw = File.CreateText(Path.Combine(path, Path.ChangeExtension(Path.GetFileName(filename), ".json")))) js.Serialize(tw, ini); }
static void Main(string[] args) { bool writeall = false; bool noencrypt = false; if (args.Length == 0) { Console.WriteLine("No file/folder specified."); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); return; } string filename = args[0]; string fname = Path.GetFileNameWithoutExtension(filename); string dir = Path.Combine(Environment.CurrentDirectory, fname); for (int a = 0; a < args.Length; a++) { if (args[a] == "-w") { writeall = true; } if (args[a] == "-d") { noencrypt = true; } if (args[a] == "-t") { byte[] zfile = File.ReadAllBytes(filename); DecryptData(ref zfile); File.WriteAllBytes(filename + "_dec.bin", zfile); return; } } if (Directory.Exists(filename)) { byte[] vms = ConvertMetadata(Path.Combine(dir, fname + ".ini")); if (!noencrypt) { ProcessVMS(ref vms); } File.WriteAllBytes(Path.Combine(Environment.CurrentDirectory, fname + "_new.vms"), vms); Console.Write("Output file: {0}", Path.Combine(Environment.CurrentDirectory, fname + "_new.vms")); if (noencrypt) { Console.Write(" (no encryption)"); } Console.Write(System.Environment.NewLine); return; } byte[] file = File.ReadAllBytes(filename); Console.Write("Extracting DLC file: {0}", filename); if (BitConverter.ToUInt32(file, 0x280) != 0x2C0) { Console.Write(" (encrypted)"); ProcessVMS(ref file); } if (writeall) { Console.Write(" with binary data"); } Console.Write(System.Environment.NewLine); Directory.CreateDirectory(dir); MetatadaToINI(file, Path.Combine(dir, fname + ".ini")); SaveIcon(file, Path.Combine(dir, fname + ".bmp")); uint pvm_pointer = BitConverter.ToUInt32(file, 0x290); int pvm_value = BitConverter.ToInt32(file, 0x294); int pvm_count = BitConverter.ToInt32(file, 0x298); if (pvm_value != 0) { Console.WriteLine("PVM at {0}, number of textures {1}", pvm_pointer.ToString("X"), pvm_count); } uint mlt_pointer = BitConverter.ToUInt32(file, 0x29C); int mlt_value = BitConverter.ToInt32(file, 0x2A0); if (mlt_value != 0) { Console.WriteLine("MLT at {0}", mlt_pointer.ToString("X")); } uint prs_pointer = BitConverter.ToUInt32(file, 0x2A4); int prs_value = BitConverter.ToInt32(file, 0x2A8); if (prs_value != 0) { Console.WriteLine("PRS at {0}", prs_pointer.ToString("X")); } //Checksum uint crc = CalculateChecksum(ref file, 0x2C0, file.Length); Console.WriteLine("Checksum file / calculated: {0} ({1}) / {2} ({3})", BitConverter.ToInt32(file, 0x2AC).ToString("X"), BitConverter.ToInt32(file, 0x2AC), crc.ToString("X"), (int)crc); //Save sections int pvm_size = (int)mlt_pointer - (int)pvm_pointer; if (pvm_size > 0) { byte[] pvmdata = new byte[pvm_size]; Array.Copy(file, pvm_pointer, pvmdata, 0, pvm_size); File.WriteAllBytes(Path.Combine(dir, fname + ".pvm"), pvmdata); } int mlt_size = (int)prs_pointer - (int)mlt_pointer; if (mlt_size > 0) { byte[] mltdata = new byte[mlt_size]; Array.Copy(file, mlt_pointer, mltdata, 0, mlt_size); File.WriteAllBytes(Path.Combine(dir, fname + ".mlt"), mltdata); } uint sectionsize = BitConverter.ToUInt32(file, 0x48); int text_count = BitConverter.ToInt32(file, 0x28C); int item_count = BitConverter.ToInt32(file, 0x284); int item_size = (item_count * 30 + 12); //12-byte header do { item_size++; }while (item_size % 16 != 0); int prs_size = file.Length - (int)prs_pointer; Console.WriteLine("Headerless size {0}, item size {1}, text size {2}, PVM size {3}, MLT size {4}, PRS size {5}", sectionsize, item_size, text_count * 64, pvm_size, mlt_size, prs_size); if (prs_size > 0) { byte[] prsdata = new byte[prs_size]; //Console.WriteLine("Copy from array of size {0} from {1} to array size {2}", file.Length, prs_pointer, prsdata.Length); Array.Copy(file, prs_pointer, prsdata, 0, prs_size); if (writeall) { File.WriteAllBytes(Path.Combine(dir, fname + ".prs"), prsdata); } prsdata = FraGag.Compression.Prs.Decompress(prsdata); if (writeall) { File.WriteAllBytes(Path.Combine(dir, fname + ".bin"), prsdata); } //Model pointer uint modelpointer = BitConverter.ToUInt32(prsdata, 0) - 0xCCA4000; Console.WriteLine("Model pointer: {0}", modelpointer.ToString("X")); NJS_OBJECT mdl = new NJS_OBJECT(prsdata, (int)modelpointer, 0xCCA4000, ModelFormat.Basic, null); ModelFile.CreateFile((Path.Combine(dir, fname + ".sa1mdl")), mdl, null, null, null, null, ModelFormat.Basic); } Console.WriteLine("Output folder: {0}", dir); }
static uint FindModel(string filename) { CurrentStep++; CurrentScanData = "models similar to '" + Path.GetFileName(filename) + "'"; ByteConverter.BigEndian = BigEndian; // Basic only for now uint result = 0; ModelFile modelFile = new ModelFile(filename); ModelFormat modelfmt = modelFile.Format; if (modelfmt == ModelFormat.Basic && BasicModelsAreDX) { modelfmt = ModelFormat.BasicDX; } Console.WriteLine("Model format: {0}", modelfmt); NJS_OBJECT originalmodel = modelFile.Model; string model_extension = ".sa1mdl"; if (!SingleOutputFolder) { Directory.CreateDirectory(Path.Combine(OutputFolder, "models")); } for (uint address = StartAddress; address < EndAddress; address += 1) { if (CancelScan) { break; } CurrentAddress = address; string fileOutputPath = Path.Combine(OutputFolder, "models", address.ToString("X8")); if (SingleOutputFolder) { fileOutputPath = Path.Combine(OutputFolder, address.ToString("X8")); } try { if (!CheckModel(address, 0, modelfmt)) { //Console.WriteLine("Not found: {0}", address.ToString("X")); continue; } NJS_OBJECT mdl = new NJS_OBJECT(datafile, (int)address, ImageBase, modelfmt, new Dictionary <int, Attach>()); if (!CompareModels(originalmodel, mdl)) { continue; } NJS_OBJECT[] children1 = originalmodel.Children.ToArray(); NJS_OBJECT[] children2 = mdl.Children.ToArray(); if (children1.Length != children2.Length) { continue; } for (int k = 0; k < children1.Length; k++) { if (!CompareModels(children1[k], children2[k])) { continue; } } ModelFile.CreateFile(fileOutputPath + model_extension, mdl, null, null, null, null, modelfmt, NoMeta); Console.WriteLine("Model at {0} seems to match!", address.ToString("X")); addresslist.Add(address, "NJS_OBJECT"); FoundBasicModels++; } catch (Exception) { continue; } } return(result); }
// Scan for models static void ScanModel(ModelFormat modelfmt) { CurrentStep++; CurrentScanData = "Models " + modelfmt.ToString(); uint scan_end = (EndAddress == 0) ? EndAddress : (uint)datafile.Length - 52; // 52 for NJS_OBJECT ByteConverter.BigEndian = BigEndian; Console.WriteLine("Step {0}: Scanning for {1} models", CurrentStep, modelfmt); string model_extension = ".sa1mdl"; string model_dir = "basicmodels"; string model_type = "NJS_OBJECT"; int count = 0; switch (modelfmt) { case ModelFormat.Basic: default: model_extension = ".sa1mdl"; model_dir = "basicmodels"; model_type = "NJS_OBJECT_OLD"; break; case ModelFormat.BasicDX: model_extension = ".sa1mdl"; model_dir = "basicmodels"; model_type = "NJS_OBJECT"; break; case ModelFormat.Chunk: model_extension = ".sa2mdl"; model_dir = "chunkmodels"; model_type = "NJS_CNK_OBJECT"; break; case ModelFormat.GC: model_extension = ".sa2bmdl"; model_dir = "gcmodels"; model_type = "NJS_GC_OBJECT"; break; } if (!SingleOutputFolder) { Directory.CreateDirectory(Path.Combine(OutputFolder, model_dir)); } for (uint address = StartAddress; address < scan_end; address += 1) { if (CancelScan) { break; } if (ConsoleMode && address % 1000 == 0) { Console.Write("\r{0} ", address.ToString("X8")); } CurrentAddress = address; string fileOutputPath = Path.Combine(OutputFolder, model_dir, address.ToString("X8")); if (SingleOutputFolder) { fileOutputPath = Path.Combine(OutputFolder, address.ToString("X8")); } try { if (!CheckModel(address, 0, modelfmt)) { //Console.WriteLine("Not found: {0}", address.ToString("X")); continue; } //else Console.WriteLine("found: {0}", address.ToString("X")); NJS_OBJECT mdl = new NJS_OBJECT(datafile, (int)address, ImageBase, modelfmt, new Dictionary <int, Attach>()); // Additional checks to prevent false positives with empty nodes if (CheckForModelData(mdl)) { ModelFile.CreateFile(fileOutputPath + model_extension, mdl, null, null, null, null, modelfmt, NoMeta); count++; switch (modelfmt) { case ModelFormat.Basic: case ModelFormat.BasicDX: FoundBasicModels++; break; case ModelFormat.Chunk: FoundChunkModels++; break; case ModelFormat.GC: FoundGCModels++; break; default: break; } addresslist.Add(address, model_type); if (!KeepChildModels) { DeleteChildModels(mdl, model_dir, model_extension); } } } catch (Exception ex) { Console.WriteLine("\rError adding model at {0}: {1}", address.ToString("X"), ex.Message.ToString()); continue; } } Console.WriteLine("\r{0} models found", count); }
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); }
/// <summary> /// Export an a folder with metadata + files for SADX DLC mod /// </summary> /// <param name="dir">Export path. Must be a folder.</param> /// <param name="writeall">Also write raw binary sections as separate files.</param> public void Export(string dir, bool writeall = false) { byte[] file = GetBytes(); Directory.CreateDirectory(dir); string fname = Path.GetFileName(dir); IniSerializer.Serialize(new VMS_DLC(file), Path.Combine(dir, fname + ".ini")); Bitmap bmp = GetIconFromFile(file); bmp.Save(Path.Combine(dir, fname + ".bmp"), ImageFormat.Bmp); uint pvm_pointer = BitConverter.ToUInt32(file, 0x290); uint mlt_pointer = BitConverter.ToUInt32(file, 0x29C); uint prs_pointer = BitConverter.ToUInt32(file, 0x2A4); // Save sections int pvm_size = (int)mlt_pointer - (int)pvm_pointer; if (pvm_size > 0) { byte[] pvmdata = new byte[pvm_size]; Array.Copy(file, pvm_pointer, pvmdata, 0, pvm_size); File.WriteAllBytes(Path.Combine(dir, fname + ".pvm"), pvmdata); } int mlt_size = (int)prs_pointer - (int)mlt_pointer; if (mlt_size > 0) { byte[] mltdata = new byte[mlt_size]; Array.Copy(file, mlt_pointer, mltdata, 0, mlt_size); File.WriteAllBytes(Path.Combine(dir, fname + ".mlt"), mltdata); } uint sectionsize = BitConverter.ToUInt32(file, 0x48); int text_count = BitConverter.ToInt32(file, 0x28C); int item_count = BitConverter.ToInt32(file, 0x284); int item_size = (item_count * 30 + 12); //12-byte header do { item_size++; }while (item_size % 16 != 0); int prs_size = file.Length - (int)prs_pointer; if (prs_size > 0) { byte[] prsdata = new byte[prs_size]; Array.Copy(file, prs_pointer, prsdata, 0, prs_size); if (writeall) { File.WriteAllBytes(Path.Combine(dir, fname + ".prs"), prsdata); } prsdata = FraGag.Compression.Prs.Decompress(prsdata); if (writeall) { File.WriteAllBytes(Path.Combine(dir, fname + ".bin"), prsdata); } // Model pointer uint modelpointer = BitConverter.ToUInt32(prsdata, 0) - 0xCCA4000; //Console.WriteLine("Model pointer: {0}", modelpointer.ToString("X")); NJS_OBJECT mdl = new NJS_OBJECT(prsdata, (int)modelpointer, 0xCCA4000, ModelFormat.Basic, null); ModelFile.CreateFile((Path.Combine(dir, fname + ".sa1mdl")), mdl, null, null, null, null, ModelFormat.Basic); } //Console.WriteLine("Output folder: {0}", dir); }
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); uint imageBase = HelperFunctions.SetupEXE(ref datafile).Value; Dictionary <string, int> exports; { int ptr = BitConverter.ToInt32(datafile, BitConverter.ToInt32(datafile, 0x3c) + 4 + 20 + 96); GCHandle handle = GCHandle.Alloc(datafile, GCHandleType.Pinned); IMAGE_EXPORT_DIRECTORY dir = (IMAGE_EXPORT_DIRECTORY)Marshal.PtrToStructure( Marshal.UnsafeAddrOfPinnedArrayElement(datafile, ptr), typeof(IMAGE_EXPORT_DIRECTORY)); handle.Free(); exports = new Dictionary <string, int>(dir.NumberOfFunctions); int nameaddr = dir.AddressOfNames; int ordaddr = dir.AddressOfNameOrdinals; for (int i = 0; i < dir.NumberOfNames; i++) { string name = HelperFunctions.GetCString(datafile, BitConverter.ToInt32(datafile, nameaddr), System.Text.Encoding.ASCII); int addr = BitConverter.ToInt32(datafile, dir.AddressOfFunctions + (BitConverter.ToInt16(datafile, ordaddr) * 4)); exports.Add(name, addr); nameaddr += 4; ordaddr += 2; } } ModelFormat modelfmt = 0; LandTableFormat landfmt = 0; string modelext = null; string landext = null; switch (inifile.Game) { case Game.SADX: modelfmt = ModelFormat.BasicDX; landfmt = LandTableFormat.SADX; modelext = ".sa1mdl"; landext = ".sa1lvl"; break; case Game.SA2B: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2; modelext = ".sa2mdl"; landext = ".sa2lvl"; break; } int itemcount = 0; List <string> labels = new List <string>(); ModelAnimationsDictionary models = new ModelAnimationsDictionary(); DllIniData output = new DllIniData() { Name = inifile.ModuleName, Game = inifile.Game }; Stopwatch timer = new Stopwatch(); timer.Start(); foreach (KeyValuePair <string, FileInfo> item in inifile.Files) { if (string.IsNullOrEmpty(item.Key)) { continue; } FileInfo data = item.Value; string type = data.Type; string name = item.Key; output.Exports[name] = type; int address = exports[name]; if (data.Filename != null) { Console.WriteLine(name + " -> " + data.Filename); Directory.CreateDirectory(Path.GetDirectoryName(data.Filename)); } else { Console.WriteLine(name); } switch (type) { case "landtable": { LandTable land = new LandTable(datafile, address, imageBase, landfmt) { Description = name, Tool = "splitDLL" }; DllItemInfo info = new DllItemInfo() { Export = name, Label = land.Name }; output.Items.Add(info); if (!labels.Contains(land.Name)) { land.SaveToFile(data.Filename, landfmt); output.Files[data.Filename] = new FileTypeHash("landtable", HelperFunctions.FileHash(data.Filename)); labels.AddRange(land.GetLabels()); } } break; case "landtablearray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; LandTable land = new LandTable(datafile, ptr, imageBase, landfmt) { Description = idx, Tool = "splitDLL" }; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = land.Name }; output.Items.Add(info); if (!labels.Contains(land.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + landext); land.SaveToFile(fn, landfmt); output.Files[fn] = new FileTypeHash("landtable", HelperFunctions.FileHash(fn)); labels.AddRange(land.GetLabels()); } } address += 4; } break; case "model": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, modelfmt)); labels.AddRange(mdl.GetLabels()); } } break; case "morph": { BasicAttach dummy = new BasicAttach(datafile, address, imageBase, modelfmt == ModelFormat.BasicDX); NJS_OBJECT mdl = new NJS_OBJECT() { Attach = dummy }; DllItemInfo info = new DllItemInfo() { Export = name, Label = dummy.Name }; output.Items.Add(info); if (!labels.Contains(dummy.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, modelfmt)); labels.AddRange(mdl.GetLabels()); } } break; case "modelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, modelfmt); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + modelext); models.Add(new ModelAnimations(fn, idx, mdl, modelfmt)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "modelsarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); BasicAttach dummy = new BasicAttach(datafile, ptr, imageBase, modelfmt == ModelFormat.BasicDX); NJS_OBJECT mdl = new NJS_OBJECT() { Attach = dummy }; string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = dummy.Name }; output.Items.Add(info); if (!labels.Contains(dummy.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + modelext); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.BasicDX)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "basicmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.Basic)); labels.AddRange(mdl.GetLabels()); } } break; case "basicmodelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.Basic); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl"); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.Basic)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "basicdxmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.BasicDX)); labels.AddRange(mdl.GetLabels()); } } break; case "basicdxmodelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.BasicDX); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl"); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.BasicDX)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "chunkmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.Chunk)); labels.AddRange(mdl.GetLabels()); } } break; case "chunkmodelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.Chunk); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa2mdl"); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.Chunk)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "actionarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); AnimationHeader ani = new AnimationHeader(datafile, ptr, imageBase, modelfmt); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; ani.Animation.Name = item.Key + "_" + i; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = ani.Animation.Name, Field = "motion" }; output.Items.Add(info); info = new DllItemInfo() { Export = name, Index = i, Label = ani.Model.Name, Field = "object" }; output.Items.Add(info); string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); ani.Animation.Save(fn); output.Files[fn] = new FileTypeHash("animation", HelperFunctions.FileHash(fn)); if (models.Contains(ani.Model.Name)) { ModelAnimations mdl = models[ani.Model.Name]; System.Text.StringBuilder sb = new System.Text.StringBuilder(260); PathRelativePathTo(sb, Path.GetFullPath(mdl.Filename), 0, Path.GetFullPath(fn), 0); mdl.Animations.Add(sb.ToString()); } else { string mfn = Path.ChangeExtension(fn, modelext); ModelFile.CreateFile(mfn, ani.Model, new[] { Path.GetFileName(fn) }, null, null, idx + "->object", "splitDLL", null, modelfmt); output.Files[mfn] = new FileTypeHash("model", HelperFunctions.FileHash(mfn)); } } address += 4; } break; case "texlist": if (output.TexLists == null) { output.TexLists = new TexListContainer(); } output.TexLists.Add((uint)(address + imageBase), new DllTexListInfo(name, null)); break; case "texlistarray": if (output.TexLists == null) { output.TexLists = new TexListContainer(); } for (int i = 0; i < data.Length; i++) { uint ptr = BitConverter.ToUInt32(datafile, address); if (ptr != 0 && !output.TexLists.ContainsKey(ptr)) { output.TexLists.Add(ptr, new DllTexListInfo(name, i)); } address += 4; } break; } itemcount++; } foreach (ModelAnimations item in models) { ModelFile.CreateFile(item.Filename, item.Model, item.Animations.ToArray(), null, null, item.Name, "splitDLL", null, item.Format); string type = "model"; switch (item.Format) { case ModelFormat.Basic: type = "basicmodel"; break; case ModelFormat.BasicDX: type = "basicdxmodel"; break; case ModelFormat.Chunk: type = "chunkmodel"; break; } output.Files[item.Filename] = new FileTypeHash(type, HelperFunctions.FileHash(item.Filename)); } IniSerializer.Serialize(output, Path.Combine(Environment.CurrentDirectory, Path.GetFileNameWithoutExtension(datafilename)) + "_data.ini"); timer.Stop(); Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds."); Console.WriteLine(); }
public static int SplitDLLFile(string datafilename, string inifilename, string projectFolderName) { #if !DEBUG try { #endif byte[] datafile = File.ReadAllBytes(datafilename); IniData inifile = IniSerializer.Deserialize <IniData>(inifilename); uint imageBase = HelperFunctions.SetupEXE(ref datafile).Value; Dictionary <string, int> exports; { int ptr = BitConverter.ToInt32(datafile, BitConverter.ToInt32(datafile, 0x3c) + 4 + 20 + 96); GCHandle handle = GCHandle.Alloc(datafile, GCHandleType.Pinned); IMAGE_EXPORT_DIRECTORY dir = (IMAGE_EXPORT_DIRECTORY)Marshal.PtrToStructure( Marshal.UnsafeAddrOfPinnedArrayElement(datafile, ptr), typeof(IMAGE_EXPORT_DIRECTORY)); handle.Free(); exports = new Dictionary <string, int>(dir.NumberOfFunctions); int nameaddr = dir.AddressOfNames; int ordaddr = dir.AddressOfNameOrdinals; for (int i = 0; i < dir.NumberOfNames; i++) { string name = datafile.GetCString(BitConverter.ToInt32(datafile, nameaddr), System.Text.Encoding.ASCII); int addr = BitConverter.ToInt32(datafile, dir.AddressOfFunctions + (BitConverter.ToInt16(datafile, ordaddr) * 4)); exports.Add(name, addr); nameaddr += 4; ordaddr += 2; } } ModelFormat modelfmt = 0; LandTableFormat landfmt = 0; string modelext = null; string landext = null; switch (inifile.Game) { case Game.SADX: modelfmt = ModelFormat.BasicDX; landfmt = LandTableFormat.SADX; modelext = ".sa1mdl"; landext = ".sa1lvl"; break; case Game.SA2B: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2; modelext = ".sa2mdl"; landext = ".sa2lvl"; break; } int itemcount = 0; List <string> labels = new List <string>(); ModelAnimationsDictionary models = new ModelAnimationsDictionary(); DllIniData output = new DllIniData() { Name = inifile.ModuleName, Game = inifile.Game }; Stopwatch timer = new Stopwatch(); timer.Start(); foreach (KeyValuePair <string, FileInfo> item in inifile.Files) { if (string.IsNullOrEmpty(item.Key)) { continue; } FileInfo data = item.Value; string type = data.Type; string name = item.Key; output.Exports[name] = type; int address = exports[name]; string fileOutputPath = ""; if (data.Filename != null) { fileOutputPath = string.Concat(projectFolderName, data.Filename); Console.WriteLine(name + " -> " + fileOutputPath); Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); } else { Console.WriteLine(name); } switch (type) { case "landtable": { LandTable land = new LandTable(datafile, address, imageBase, landfmt) { Description = name }; DllItemInfo info = new DllItemInfo() { Export = name, Label = land.Name }; output.Items.Add(info); if (!labels.Contains(land.Name)) { land.SaveToFile(fileOutputPath, landfmt); output.Files[data.Filename] = new FileTypeHash("landtable", HelperFunctions.FileHash(fileOutputPath)); labels.AddRange(land.GetLabels()); } } break; case "battlelandtable": { LandTable land = new LandTable(datafile, address, imageBase, LandTableFormat.SA2B) { Description = name }; DllItemInfo info = new DllItemInfo() { Export = name, Label = land.Name }; output.Items.Add(info); if (!labels.Contains(land.Name)) { land.SaveToFile(fileOutputPath, LandTableFormat.SA2B); output.Files[data.Filename] = new FileTypeHash("landtable", HelperFunctions.FileHash(fileOutputPath)); labels.AddRange(land.GetLabels()); } } break; case "landtablearray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; LandTable land = new LandTable(datafile, ptr, imageBase, landfmt) { Description = idx }; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = land.Name }; output.Items.Add(info); if (!labels.Contains(land.Name)) { string outputFN = Path.Combine(fileOutputPath, i.ToString(NumberFormatInfo.InvariantInfo) + landext); string fileName = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + landext); land.SaveToFile(outputFN, landfmt); output.Files[fileName] = new FileTypeHash("landtable", HelperFunctions.FileHash(outputFN)); labels.AddRange(land.GetLabels()); } } address += 4; } break; case "model": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>()); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, modelfmt)); labels.AddRange(mdl.GetLabels()); } } break; case "morph": { BasicAttach dummy = new BasicAttach(datafile, address, imageBase, modelfmt == ModelFormat.BasicDX); NJS_OBJECT mdl = new NJS_OBJECT() { Attach = dummy }; DllItemInfo info = new DllItemInfo() { Export = name, Label = dummy.Name }; output.Items.Add(info); if (!labels.Contains(dummy.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, modelfmt)); labels.AddRange(mdl.GetLabels()); } } break; case "modelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, modelfmt, new Dictionary <int, Attach>()); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + modelext); models.Add(new ModelAnimations(fn, idx, mdl, modelfmt)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "modelsarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); BasicAttach dummy = new BasicAttach(datafile, ptr, imageBase, modelfmt == ModelFormat.BasicDX); NJS_OBJECT mdl = new NJS_OBJECT() { Attach = dummy }; string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = dummy.Name }; output.Items.Add(info); if (!labels.Contains(dummy.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + modelext); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.BasicDX)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "basicmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic, new Dictionary <int, Attach>()); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.Basic)); labels.AddRange(mdl.GetLabels()); } } break; case "basicmodelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.Basic, new Dictionary <int, Attach>()); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl"); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.Basic)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "basicdxmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX, new Dictionary <int, Attach>()); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.BasicDX)); labels.AddRange(mdl.GetLabels()); } } break; case "basicdxmodelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.BasicDX, new Dictionary <int, Attach>()); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa1mdl"); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.BasicDX)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "chunkmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); DllItemInfo info = new DllItemInfo() { Export = name, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { models.Add(new ModelAnimations(data.Filename, name, mdl, ModelFormat.Chunk)); labels.AddRange(mdl.GetLabels()); } } break; case "chunkmodelarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_OBJECT mdl = new NJS_OBJECT(datafile, ptr, imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = mdl.Name }; output.Items.Add(info); if (!labels.Contains(mdl.Name)) { string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".sa2mdl"); models.Add(new ModelAnimations(fn, idx, mdl, ModelFormat.Chunk)); labels.AddRange(mdl.GetLabels()); } } address += 4; } break; case "actionarray": for (int i = 0; i < data.Length; i++) { int ptr = BitConverter.ToInt32(datafile, address); if (ptr != 0) { ptr = (int)(ptr - imageBase); NJS_ACTION ani = new NJS_ACTION(datafile, ptr, imageBase, modelfmt, new Dictionary <int, Attach>()); string idx = name + "[" + i.ToString(NumberFormatInfo.InvariantInfo) + "]"; ani.Animation.Name = item.Key + "_" + i; DllItemInfo info = new DllItemInfo() { Export = name, Index = i, Label = ani.Animation.Name, Field = "motion" }; output.Items.Add(info); info = new DllItemInfo() { Export = name, Index = i, Label = ani.Model.Name, Field = "object" }; output.Items.Add(info); string outputFN = Path.Combine(fileOutputPath, i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); string fn = Path.Combine(data.Filename, i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); ani.Animation.Save(outputFN); output.Files[fn] = new FileTypeHash("animation", HelperFunctions.FileHash(outputFN)); if (models.Contains(ani.Model.Name)) { ModelAnimations mdl = models[ani.Model.Name]; System.Text.StringBuilder sb = new System.Text.StringBuilder(260); PathRelativePathTo(sb, Path.GetFullPath(Path.Combine(projectFolderName, mdl.Filename)), 0, Path.GetFullPath(outputFN), 0); mdl.Animations.Add(sb.ToString()); // this is where the problem is } else { string mfn = Path.ChangeExtension(fn, modelext); string outputmfn = Path.Combine(projectFolderName, mfn); string animationName = Path.GetFileName(outputFN); ModelFile.CreateFile(outputmfn, ani.Model, new[] { animationName }, null, idx + "->object", null, modelfmt); output.Files[mfn] = new FileTypeHash("model", HelperFunctions.FileHash(outputmfn)); } } address += 4; } break; case "texlist": if (output.TexLists == null) { output.TexLists = new TexListContainer(); } output.TexLists.Add((uint)(address + imageBase), new DllTexListInfo(name, null)); break; case "texlistarray": if (output.TexLists == null) { output.TexLists = new TexListContainer(); } for (int i = 0; i < data.Length; i++) { uint ptr = BitConverter.ToUInt32(datafile, address); if (ptr != 0 && !output.TexLists.ContainsKey(ptr)) { output.TexLists.Add(ptr, new DllTexListInfo(name, i)); } address += 4; } 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); } output.DataItems.Add(new DllDataItemInfo() { Type = type, Export = name, Filename = data.Filename, MD5Hash = string.Join("|", hashes.ToArray()) }); } break; case "charaobjectdatalist": { Directory.CreateDirectory(fileOutputPath); List <CharaObjectData> result = new List <CharaObjectData>(); List <string> hashes = new List <string>(); for (int i = 0; i < data.Length; i++) { string chnm = charaobjectnames[i]; CharaObjectData chara = new CharaObjectData(); NJS_OBJECT model = new NJS_OBJECT(datafile, (int)(BitConverter.ToInt32(datafile, address) - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); chara.MainModel = model.Name; NJS_MOTION anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 4) - imageBase), imageBase, model.CountAnimated()); chara.Animation1 = anim.Name; anim.Save(Path.Combine(fileOutputPath, $"{chnm} Anim 1.saanim")); hashes.Add($"{chnm} Anim 1.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Anim 1.saanim"))); anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 8) - imageBase), imageBase, model.CountAnimated()); chara.Animation2 = anim.Name; anim.Save(Path.Combine(fileOutputPath, $"{chnm} Anim 2.saanim")); hashes.Add($"{chnm} Anim 2.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Anim 2.saanim"))); anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 12) - imageBase), imageBase, model.CountAnimated()); chara.Animation3 = anim.Name; anim.Save(Path.Combine(fileOutputPath, $"{chnm} Anim 3.saanim")); hashes.Add($"{chnm} Anim 3.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Anim 3.saanim"))); ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{chnm}.sa2mdl"), model, new[] { $"{chnm} Anim 1.saanim", $"{chnm} Anim 2.saanim", $"{chnm} Anim 3.saanim" }, null, null, null, ModelFormat.Chunk); hashes.Add($"{chnm}.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm}.sa2mdl"))); int ptr = BitConverter.ToInt32(datafile, address + 16); if (ptr != 0) { model = new NJS_OBJECT(datafile, (int)(ptr - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); chara.AccessoryModel = model.Name; chara.AccessoryAttachNode = "object_" + (BitConverter.ToInt32(datafile, address + 20) - imageBase).ToString("X8"); ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{chnm} Accessory.sa2mdl"), model, null, null, null, null, ModelFormat.Chunk); hashes.Add($"{chnm} Accessory.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{chnm} Accessory.sa2mdl"))); } ptr = BitConverter.ToInt32(datafile, address + 24); if (ptr != 0) { model = new NJS_OBJECT(datafile, (int)(ptr - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); chara.SuperModel = model.Name; anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 28) - imageBase), imageBase, model.CountAnimated()); chara.SuperAnimation1 = anim.Name; anim.Save(Path.Combine(fileOutputPath, $"Super {chnm} Anim 1.saanim")); hashes.Add($"Super {chnm} Anim 1.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm} Anim 1.saanim"))); anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 32) - imageBase), imageBase, model.CountAnimated()); chara.SuperAnimation2 = anim.Name; anim.Save(Path.Combine(fileOutputPath, $"Super {chnm} Anim 2.saanim")); hashes.Add($"Super {chnm} Anim 2.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm} Anim 2.saanim"))); anim = new NJS_MOTION(datafile, (int)(BitConverter.ToInt32(datafile, address + 36) - imageBase), imageBase, model.CountAnimated()); chara.SuperAnimation3 = anim.Name; anim.Save(Path.Combine(fileOutputPath, $"Super {chnm} Anim 3.saanim")); hashes.Add($"Super {chnm} Anim 3.saanim:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm} Anim 3.saanim"))); ModelFile.CreateFile(Path.Combine(fileOutputPath, $"Super {chnm}.sa2mdl"), model, new[] { $"Super {chnm} Anim 1.saanim", $"Super {chnm} Anim 2.saanim", $"Super {chnm} Anim 3.saanim" }, null, null, null, ModelFormat.Chunk); hashes.Add($"Super {chnm}.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"Super {chnm}.sa2mdl"))); } chara.Unknown1 = BitConverter.ToInt32(datafile, address + 40); chara.Rating = BitConverter.ToInt32(datafile, address + 44); chara.DescriptionID = BitConverter.ToInt32(datafile, address + 48); chara.TextBackTexture = BitConverter.ToInt32(datafile, address + 52); chara.Unknown5 = BitConverter.ToSingle(datafile, address + 56); result.Add(chara); address += 60; } IniSerializer.Serialize(result, Path.Combine(fileOutputPath, "info.ini")); hashes.Add("info.ini:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, "info.ini"))); output.DataItems.Add(new DllDataItemInfo() { Type = type, Export = name, Filename = data.Filename, MD5Hash = string.Join("|", hashes.ToArray()) }); } break; case "kartspecialinfolist": { Directory.CreateDirectory(fileOutputPath); List <KartSpecialInfo> result = new List <KartSpecialInfo>(); List <string> hashes = new List <string>(); for (int i = 0; i < data.Length; i++) { KartSpecialInfo kart = new KartSpecialInfo { ID = ByteConverter.ToInt32(datafile, address) }; NJS_OBJECT model = new NJS_OBJECT(datafile, (int)(BitConverter.ToInt32(datafile, address + 4) - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); kart.Model = model.Name; ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{i}.sa2mdl"), model, null, null, null, null, ModelFormat.Chunk); hashes.Add($"{i}.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{i}.sa2mdl"))); int ptr = BitConverter.ToInt32(datafile, address + 8); if (ptr != 0) { model = new NJS_OBJECT(datafile, (int)(ptr - imageBase), imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); kart.LowModel = model.Name; ModelFile.CreateFile(Path.Combine(fileOutputPath, $"{i} Low.sa2mdl"), model, null, null, null, null, ModelFormat.Chunk); hashes.Add($"{i} Low.sa2mdl:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, $"{i} Low.sa2mdl"))); } kart.TexList = ByteConverter.ToUInt32(datafile, address + 12); kart.Unknown1 = ByteConverter.ToInt32(datafile, address + 16); kart.Unknown2 = ByteConverter.ToInt32(datafile, address + 20); kart.Unknown3 = ByteConverter.ToInt32(datafile, address + 24); result.Add(kart); address += 0x1C; } IniSerializer.Serialize(result, Path.Combine(fileOutputPath, "info.ini")); hashes.Add("info.ini:" + HelperFunctions.FileHash(Path.Combine(fileOutputPath, "info.ini"))); output.DataItems.Add(new DllDataItemInfo() { Type = type, Export = name, Filename = data.Filename, MD5Hash = string.Join("|", hashes.ToArray()) }); } break; } itemcount++; } foreach (ModelAnimations item in models) { string modelOutputPath = string.Concat(projectFolderName, item.Filename); //string modelOutputPath = item.Filename; ModelFile.CreateFile(modelOutputPath, item.Model, item.Animations.ToArray(), null, item.Name, null, item.Format); string type = "model"; switch (item.Format) { case ModelFormat.Basic: type = "basicmodel"; break; case ModelFormat.BasicDX: type = "basicdxmodel"; break; case ModelFormat.Chunk: type = "chunkmodel"; break; } output.Files[item.Filename] = new FileTypeHash(type, HelperFunctions.FileHash(modelOutputPath)); } IniSerializer.Serialize(output, Path.Combine(projectFolderName, Path.GetFileNameWithoutExtension(datafilename)) + "_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)SA_Tools.Split.SplitERRORVALUE.UnhandledException); } #endif return((int)SA_Tools.Split.SplitERRORVALUE.Success); }
public static void Split(string filename) { nodenames.Clear(); modelfiles.Clear(); motionfiles.Clear(); Console.WriteLine("Splitting file {0}...", filename); byte[] fc; if (Path.GetExtension(filename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { fc = Prs.Decompress(filename); } else { fc = File.ReadAllBytes(filename); } EventIniData ini = new EventIniData() { Name = Path.GetFileNameWithoutExtension(filename) }; string path = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(filename)), Path.GetFileNameWithoutExtension(filename))).FullName; uint key; List <NJS_MOTION> motions = null; bool battle; bool dcbeta; if (fc[0] == 0x81) { if (fc[0x2B] <= 0x01 && fc[0x2A] == 0) { Console.WriteLine("File is in GC/PC format."); ByteConverter.BigEndian = true; key = 0x8125FE60; ini.Game = Game.SA2B; battle = true; dcbeta = false; motions = ReadMotionFile(Path.ChangeExtension(filename, null) + "motion.bin"); ini.Motions = motions.Select(a => a?.Name).ToList(); foreach (var mtn in motions.Where(a => a != null)) { motionfiles[mtn.Name] = new MotionInfo(null, mtn); } } else { Console.WriteLine("File is in GC/PC Beta format."); ByteConverter.BigEndian = true; key = 0x812FFE60; ini.Game = Game.SA2B; battle = false; dcbeta = false; } } else { if ((fc[37] == 0x25) || (fc[38] == 0x22) || ((fc[36] == 0) && ((fc[1] == 0xFE) || (fc[1] == 0xF2) || ((fc[1] == 0x27) && fc[2] == 0x9F)))) { Console.WriteLine("File is in DC Beta format."); ByteConverter.BigEndian = false; key = 0xC600000; ini.Game = Game.SA2; battle = false; dcbeta = true; } else { Console.WriteLine("File is in DC format."); ByteConverter.BigEndian = false; key = 0xC600000; ini.Game = Game.SA2; battle = false; dcbeta = false; } } int ptr = fc.GetPointer(0x20, key); if (ptr != 0) { if (!dcbeta) { for (int i = 0; i < (battle ? 18 : 16); i++) { string upnam = upgradenames[i]; string chnam = upnam; switch (i) { case 0: chnam = "Sonic"; break; case 4: chnam = "Shadow"; break; case 6: chnam = "Knuckles"; break; case 12: chnam = "Rouge"; break; case 16: chnam = "Mech Tails"; break; case 17: chnam = "Mech Eggman"; break; } UpgradeInfo info = new UpgradeInfo(); info.RootNode = GetModel(fc, ptr, key, $"{chnam} Root.sa2mdl"); if (info.RootNode != null) { int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { info.AttachNode1 = $"object_{ptr2:X8}"; } int ptr3 = fc.GetPointer(ptr + 8, key); if (ptr3 != 0) { info.Model1 = GetModel(fc, ptr + 8, key, $"{upnam} Model 1.sa2mdl"); } ptr2 = fc.GetPointer(ptr + 0xC, key); if (ptr2 != 0) { info.AttachNode2 = $"object_{ptr2:X8}"; } ptr3 = fc.GetPointer(ptr + 0x10, key); if (ptr3 != 0) { info.Model2 = GetModel(fc, ptr + 0x10, key, $"{upnam} Model 2.sa2mdl"); } } ini.Upgrades.Add(info); ptr += 0x14; } } else { for (int i = 0; i < 14; i++) { string upnam = upgradebetanames[i]; string chnam = upnam; switch (i) { case 0: chnam = "Sonic"; break; case 4: chnam = "Shadow"; break; case 6: chnam = "Knuckles"; break; case 10: chnam = "Rouge"; break; } UpgradeInfo info = new UpgradeInfo(); info.RootNode = GetModel(fc, ptr, key, $"{chnam} Root.sa2mdl"); if (info.RootNode != null) { int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { info.AttachNode1 = $"object_{ptr2:X8}"; } int ptr3 = fc.GetPointer(ptr + 8, key); if (ptr3 != 0) { info.Model1 = GetModel(fc, ptr + 8, key, $"{upnam} Model 1.sa2mdl"); } ptr2 = fc.GetPointer(ptr + 0xC, key); if (ptr2 != 0) { info.AttachNode2 = $"object_{ptr2:X8}"; } ptr3 = fc.GetPointer(ptr + 0x10, key); if (ptr3 != 0) { info.Model2 = GetModel(fc, ptr + 0x10, key, $"{upnam} Model 2.sa2mdl"); } } ini.Upgrades.Add(info); ptr += 0x14; } } } else { Console.WriteLine("Event contains no character upgrades."); } ptr = fc.GetPointer(0x18, key); if (ptr != 0) { for (int i = 0; i < 93; i++) { string name = $"object_{ptr:X8}"; if (name != null) { ini.MechParts.Add(i, name); } ptr += 4; } } else { Console.WriteLine("Event contains no mech parts."); } int gcnt = ByteConverter.ToInt32(fc, 8); ptr = fc.GetPointer(0, key); if (ptr != 0) { Console.WriteLine("Event contains {0} scene(s).", gcnt + 1); for (int gn = 0; gn <= gcnt; gn++) { Directory.CreateDirectory(Path.Combine(path, $"Scene {gn + 1}")); SceneInfo scn = new SceneInfo(); int ptr2 = fc.GetPointer(ptr, key); int ecnt = ByteConverter.ToInt32(fc, ptr + 4); if (ptr2 != 0) { Console.WriteLine("Scene {0} contains {1} entit{2}.", gn + 1, ecnt, ecnt == 1 ? "y" : "ies"); for (int en = 0; en < ecnt; en++) { EntityInfo ent = new EntityInfo(); ent.Model = GetModel(fc, ptr2, key, $"Scene {gn + 1}\\Entity {en + 1} Model.sa2mdl"); if (ent.Model != null) { ent.Motion = GetMotion(fc, ptr2 + 4, key, $"Scene {gn + 1}\\Entity {en + 1} Motion.saanim", motions, modelfiles[ent.Model].Model.CountAnimated()); if (ent.Motion != null) { modelfiles[ent.Model].Motions.Add(motionfiles[ent.Motion].Filename); } ent.ShapeMotion = GetMotion(fc, ptr2 + 8, key, $"Scene {gn + 1}\\Entity {en + 1} Shape Motion.saanim", motions, modelfiles[ent.Model].Model.CountMorph()); if (ent.ShapeMotion != null) { modelfiles[ent.Model].Motions.Add(motionfiles[ent.ShapeMotion].Filename); } } if (battle) { ent.GCModel = GetGCModel(fc, ptr2 + 12, key, $"Scene {gn + 1}\\Entity {en + 1} GC Model.sa2bmdl"); ent.ShadowModel = GetModel(fc, ptr2 + 16, key, $"Scene {gn + 1}\\Entity {en + 1} Shadow Model.sa2mdl"); ent.Position = new Vertex(fc, ptr2 + 24); ent.Flags = ByteConverter.ToUInt32(fc, ptr2 + 36); ent.Layer = ByteConverter.ToUInt32(fc, ptr2 + 40); } else { ent.Position = new Vertex(fc, ptr2 + 16); ent.Flags = ByteConverter.ToUInt32(fc, ptr2 + 28); } scn.Entities.Add(ent); ptr2 += battle ? 0x2C : 0x20; } } else { Console.WriteLine("Scene {0} contains no entities.", gn + 1); } ptr2 = fc.GetPointer(ptr + 8, key); if (ptr2 != 0) { int cnt = ByteConverter.ToInt32(fc, ptr + 12); for (int i = 0; i < cnt; i++) { scn.CameraMotions.Add(GetMotion(fc, ptr2, key, $"Scene {gn + 1}\\Camera Motion {i + 1}.saanim", motions, 1)); ptr2 += sizeof(int); } } ptr2 = fc.GetPointer(ptr + 0x18, key); if (ptr2 != 0) { BigInfo big = new BigInfo(); big.Model = GetModel(fc, ptr2, key, $"Scene {gn + 1}\\Big Model.sa2mdl"); if (big.Model != null) { int anicnt = modelfiles[big.Model].Model.CountAnimated(); int ptr3 = fc.GetPointer(ptr2 + 4, key); if (ptr3 != 0) { int cnt = ByteConverter.ToInt32(fc, ptr2 + 8); for (int i = 0; i < cnt; i++) { big.Motions.Add(new string[] { GetMotion(fc, ptr3, key, $"Scene {gn + 1}\\Big Motion {i + 1}a.saanim", motions, anicnt), GetMotion(fc, ptr3 + 4, key, $"Scene {gn + 1}\\Big Motion {i + 1}b.saanim", motions, anicnt) }); ptr3 += 8; } } } big.Unknown = ByteConverter.ToInt32(fc, ptr2 + 12); scn.Big = big; } scn.FrameCount = ByteConverter.ToInt32(fc, ptr + 28); ini.Scenes.Add(scn); ptr += 0x20; } } else { Console.WriteLine("Event contains no scenes."); } ptr = fc.GetPointer(0x1C, key); if (ptr != 0) { ini.TailsTails = GetModel(fc, ptr, key, $"Tails' tails.sa2mdl"); } else { Console.WriteLine("Event does not contain Tails' tails."); } ptr = fc.GetPointer(4, key); if (ptr == 0) { Console.WriteLine("Event does not contain an internal texture list."); } ptr = fc.GetPointer(0xC, key); if (ptr == 0) { Console.WriteLine("Event does not contain texture sizes."); } ptr = fc.GetPointer(0x24, key); if (ptr == 0) { Console.WriteLine("Event does not contain texture animation data."); } if (battle && fc[0x2B] == 0) { Console.WriteLine("Event does not use GC shadow maps."); } if (battle && fc[0x2B] == 1) { Console.WriteLine("Event uses GC shadow maps."); } foreach (var item in motionfiles.Values) { string fn = item.Filename ?? $"Unknown Motion {motions.IndexOf(item.Motion)}.saanim"; string fp = Path.Combine(path, fn); item.Motion.Save(fp); ini.Files.Add(fn, HelperFunctions.FileHash(fp)); } foreach (var item in modelfiles.Values) { string fp = Path.Combine(path, item.Filename); ModelFile.CreateFile(fp, item.Model, item.Motions.ToArray(), null, null, null, item.Format); ini.Files.Add(item.Filename, HelperFunctions.FileHash(fp)); } JsonSerializer js = new JsonSerializer { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }; using (var tw = File.CreateText(Path.Combine(path, Path.ChangeExtension(Path.GetFileName(filename), ".json")))) js.Serialize(tw, ini); }
static void Main(string[] args) { bool nometa = false; bool nolabel = false; string mode; string fullpath_out; bool bigendian = false; List <string> mdlanimfiles; if (args.Length == 0) { Console.WriteLine("Split any binary files supported by SA Tools.\n"); Console.WriteLine("-Splitting using an XML template-"); Console.WriteLine("split template <xmlfile> [-data sourcepath] [output path]\n"); Console.WriteLine("-Splitting a binary file with INI data-"); Console.WriteLine("split binary <file> <inifile> [output path]\n"); Console.WriteLine("-Splitting a single item from a binary file without INI data-"); Console.WriteLine("split single <game> <file> <key> <address> <type> [output filename] [-p custom properties] [-name entryName]\n"); Console.WriteLine("-Splitting an NB file-"); Console.WriteLine("split nb <file> [output path] [-ini split ini file]\n"); Console.WriteLine("-Splitting SA2 MDL files-"); Console.WriteLine("split mdl <file> [output path] [-anim animation files]\n"); Console.WriteLine("-Splitting SA2B MDL files-"); Console.WriteLine("split mdl_b <file> [output path] [-anim animation files]\n"); Console.WriteLine("-Splitting dllexport entries from DLL files-"); Console.WriteLine("split dllexport <file> <type> <name> [-id array ID] [output path] [-p numparts]\n"); Console.WriteLine("Common switches: [-nometa], [-nolabel]"); Console.WriteLine("Press ENTER to exit."); Console.ReadLine(); return; } for (int u = 2; u < args.Length; u++) { if (args[u] == "-nometa") { nometa = true; } if (args[u] == "-nolabel") { nolabel = true; } } mode = args[0]; System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); switch (mode.ToLowerInvariant()) { case "binary": string fullpath_bin = Path.GetFullPath(args[1]); if (!File.Exists(fullpath_bin)) { Console.WriteLine("File {0} doesn't exist.", fullpath_bin); return; } Console.WriteLine("File: {0}", fullpath_bin); string fullpath_ini = Path.GetFullPath(args[2]); if (!File.Exists(fullpath_ini)) { Console.WriteLine("File {0} doesn't exist.", fullpath_ini); return; } Console.WriteLine("Data mapping: {0}", fullpath_ini); fullpath_out = Path.GetDirectoryName(fullpath_bin); if (args.Length > 3) { fullpath_out = args[3]; if (fullpath_out[fullpath_out.Length - 1] != '/') { fullpath_out = string.Concat(fullpath_out, '/'); } fullpath_out = Path.GetFullPath(fullpath_out); } Console.WriteLine("Output folder: {0}", fullpath_out); if (nometa) { Console.WriteLine("Labels are disabled"); } if (Path.GetExtension(args[1]).ToLowerInvariant() == ".dll") { SplitTools.SplitDLL.SplitDLL.SplitDLLFile(fullpath_bin, fullpath_ini, fullpath_out, nometa, nolabel); } else { SplitTools.Split.SplitBinary.SplitFile(fullpath_bin, fullpath_ini, fullpath_out, nometa, nolabel); } break; case "template": string dataFolder = ""; fullpath_out = ""; if (args.Length < 2) { Console.WriteLine("Insufficient arguments"); return; } if (!File.Exists(Path.GetFullPath(args[1]))) { Console.WriteLine("File {0} doesn't exist", Path.GetFullPath(args[1])); return; } for (int i = 2; i < args.Length; i++) { if (args[i] == "-nolabel") { nolabel = true; } else if (args[i] == "-nometa") { nometa = true; } else if (args[i] == "-data") { dataFolder = args[i + 1]; i++; } else { fullpath_out = args[i]; } } Templates.SplitTemplate template = ProjectFunctions.openTemplateFile(Path.GetFullPath(args[1])); if (template == null) { Console.WriteLine("Failed to open template: {0}", Path.GetFullPath(args[1])); return; } if (dataFolder == "") { dataFolder = ProjectFunctions.GetGamePath(template.GameInfo.GameName); } Console.WriteLine("Data folder: {0}", dataFolder); string iniFolder = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName), "..\\GameConfig", template.GameInfo.DataFolder)); Console.WriteLine("Splitting using template for {0} located at {1}", template.GameInfo.GameName, Path.GetFullPath(args[1])); if (!Directory.Exists(dataFolder)) { Console.WriteLine("\nData folder does not exist: {0}", Path.GetFullPath(dataFolder)); Console.WriteLine("Put your game files in {0} and run split again.", Path.GetFullPath(dataFolder)); Console.WriteLine("Press ENTER to exit."); Console.ReadLine(); return; } Console.WriteLine("INI folder: {0}", iniFolder); if (fullpath_out == "") { fullpath_out = Path.Combine(Environment.CurrentDirectory, template.GameInfo.GameName); } Console.WriteLine("Output folder: {0}", fullpath_out); foreach (Templates.SplitEntry splitEntry in template.SplitEntries) { if (!File.Exists(Path.Combine(dataFolder, splitEntry.SourceFile))) { Console.WriteLine("Split source file {0} doesn't exist", Path.Combine(dataFolder, splitEntry.SourceFile)); continue; } Console.WriteLine("\n{0}: {1}: {2}", splitEntry.CmnName == null ? "No description" : splitEntry.CmnName, splitEntry.SourceFile, splitEntry.IniFile + ".ini"); ProjectFunctions.SplitTemplateEntry(splitEntry, null, dataFolder, iniFolder, fullpath_out, true, nometa, nolabel); } if (template.SplitMDLEntries != null) { foreach (Templates.SplitEntryMDL splitEntryMDL in template.SplitMDLEntries) { if (!File.Exists(Path.Combine(dataFolder, splitEntryMDL.ModelFile))) { Console.WriteLine("Split MDL source file {0} doesn't exist", Path.Combine(dataFolder, splitEntryMDL.ModelFile)); continue; } Console.Write("\nSplitting MDL file: {0}", splitEntryMDL.ModelFile); ProjectFunctions.SplitTemplateMDLEntry(splitEntryMDL, null, dataFolder, fullpath_out); } } break; case "single": int startoffset = 0; string game = args[1]; string filepath = args[2]; string outPath = ""; uint key = uint.Parse(args[3], System.Globalization.NumberStyles.HexNumber); int eaddress = int.Parse(args[4], System.Globalization.NumberStyles.HexNumber); string entryName = ""; string props = ""; string etype = args[5]; if (args.Length > 6) { for (int a = 6; a < args.Length; a++) { switch (args[a]) { case "-name": entryName = args[a + 1]; a++; break; case "-offset": startoffset = int.Parse(args[a + 1], System.Globalization.NumberStyles.HexNumber); a++; break; case "-p": props = args[a + 1]; a++; break; default: outPath = args[a]; break; } } } // If no output filename is specified if (outPath == "") { outPath = Path.Combine(Environment.CurrentDirectory, eaddress.ToString("X8")); } // If an output name is specified without a path else if (Path.GetDirectoryName(outPath) == "") { outPath = Path.Combine(Environment.CurrentDirectory, outPath); } // If a path is specified without a filename else if (Path.GetFileName(outPath) == "") { outPath = Path.Combine(outPath, eaddress.ToString("X8")); } Console.WriteLine("Splitting from {0} (key: {1}) in {2}: {3} at {4}, offset: {5}", Path.GetFileName(filepath), key.ToString("X"), game.ToUpperInvariant(), etype, eaddress.ToString("X"), startoffset.ToString("X")); Console.WriteLine("Output path: {0}", Path.GetFullPath(outPath)); SplitTools.Split.SplitBinary.SplitManual(game, filepath, key, eaddress, etype, outPath, props, entryName, nometa, nolabel, startoffset); break; case "nb": case "nb_b": string fullpath_nb = Path.GetFullPath(args[1]); string path_ini = null; if (args[args.Length - 2].ToLowerInvariant() == "-ini") { path_ini = Path.GetFullPath(args[args.Length - 1]); } if (!File.Exists(fullpath_nb)) { Console.WriteLine("File {0} doesn't exist.", fullpath_nb); return; } Console.WriteLine("File: {0}", fullpath_nb); fullpath_out = Path.GetDirectoryName(fullpath_nb); if (args.Length > 2) { fullpath_out = args[2]; if (fullpath_out[fullpath_out.Length - 1] != '/') { fullpath_out = string.Concat(fullpath_out, '/'); } fullpath_out = Path.GetFullPath(fullpath_out); } Console.WriteLine("Output folder: {0}", fullpath_out); SplitTools.Split.SplitNB.SplitNBFile(fullpath_nb, false, fullpath_out, 1, path_ini); break; case "mdl": case "mdl_b": string fullpath_mdl = Path.GetFullPath(args[1]); if (!File.Exists(fullpath_mdl)) { Console.WriteLine("File {0} doesn't exist.", fullpath_mdl); return; } Console.Write("File: {0}", fullpath_mdl); if (mode == "mdl_b") { bigendian = true; Console.Write(" (Big Endian)\n"); } else { Console.Write(System.Environment.NewLine); } fullpath_out = Path.GetDirectoryName(fullpath_mdl); if (args.Length > 1) { fullpath_out = args[2]; if (fullpath_out[fullpath_out.Length - 1] != '/') { fullpath_out = string.Concat(fullpath_out, '/'); } fullpath_out = Path.GetFullPath(fullpath_out); } Console.WriteLine("Output path: {0}", fullpath_out); if (args.Length > 2) { mdlanimfiles = new List <string>(); Console.WriteLine("Animation files:"); for (int u = 3; u < args.Length; u++) { string animpath = Path.GetFullPath(args[u]); if (File.Exists(animpath)) { mdlanimfiles.Add(animpath); Console.WriteLine(animpath); } else { Console.WriteLine("File {0} doesn't exist.", animpath); } } SplitTools.SAArc.sa2MDL.Split(bigendian, fullpath_mdl, fullpath_out, mdlanimfiles.ToArray()); } else { SplitTools.SAArc.sa2MDL.Split(bigendian, fullpath_mdl, fullpath_out, null); } break; case "dllexport": int arrayid = -1; string fullpath_dllex = Path.GetFullPath(args[1]); string type = args[2]; string name = args[3]; string fileOutputPath = ""; if (args.Length > 4) { for (int u = 4; u < args.Length; u++) { if (args[u] == "-id") { arrayid = int.Parse(args[u + 1]); u++; } else { fileOutputPath = args[u]; } } } if (!File.Exists(fullpath_dllex)) { Console.WriteLine("File {0} doesn't exist.", fullpath_dllex); return; } Console.Write("File: {0}", fullpath_dllex); byte[] datafile = File.ReadAllBytes(fullpath_dllex); uint imageBase = SplitTools.HelperFunctions.SetupEXE(ref datafile).Value; Dictionary <string, int> exports; Dictionary <int, string> labels = new Dictionary <int, string>(); { int ptr = BitConverter.ToInt32(datafile, BitConverter.ToInt32(datafile, 0x3c) + 4 + 20 + 96); GCHandle handle = GCHandle.Alloc(datafile, GCHandleType.Pinned); IMAGE_EXPORT_DIRECTORY dir = (IMAGE_EXPORT_DIRECTORY)Marshal.PtrToStructure( Marshal.UnsafeAddrOfPinnedArrayElement(datafile, ptr), typeof(IMAGE_EXPORT_DIRECTORY)); handle.Free(); exports = new Dictionary <string, int>(dir.NumberOfFunctions); int nameaddr = dir.AddressOfNames; int ordaddr = dir.AddressOfNameOrdinals; for (int i = 0; i < dir.NumberOfNames; i++) { string namex = datafile.GetCString(BitConverter.ToInt32(datafile, nameaddr), System.Text.Encoding.ASCII); int addr = BitConverter.ToInt32(datafile, dir.AddressOfFunctions + (BitConverter.ToInt16(datafile, ordaddr) * 4)); exports.Add(namex, addr); labels.Add(addr, namex); nameaddr += 4; ordaddr += 2; } Console.Write(" ({0} exports)\n", exports.Count); } if (!exports.ContainsKey(name)) { Console.WriteLine("The export table has no item named {0}", name); return; } int address = exports[name]; // If an array ID is specified, jump to the pointer needed and use it as the address to split if (arrayid != -1) { uint newpointer = ByteConverter.ToUInt32(datafile, address + arrayid * 4); address = (int)(newpointer - imageBase); } Console.WriteLine("{0} {1}:{2}", type, name, address.ToString("X8")); switch (type) { // Landtables case "landtable": case "sa1landtable": case "sadxlandtable": case "sa2landtable": case "sa2blandtable": case "battlelandtable": LandTableFormat landfmt_cur; string landext; switch (type) { case "sa1landtable": landfmt_cur = LandTableFormat.SA1; landext = ".sa1lvl"; break; case "sadxlandtable": landfmt_cur = LandTableFormat.SADX; landext = ".sa1lvl"; break; case "sa2landtable": landfmt_cur = LandTableFormat.SA2; landext = ".sa2lvl"; break; case "sa2blandtable": case "battlelandtable": landfmt_cur = LandTableFormat.SA2B; landext = ".sa2blvl"; break; case "landtable": default: landfmt_cur = LandTableFormat.SADX; landext = ".sa1lvl"; break; } LandTable land = new LandTable(datafile, address, imageBase, landfmt_cur, labels); fileOutputPath = MakePathThatExists(fileOutputPath, land.Name + landext); if (!Directory.Exists(Path.GetDirectoryName(fileOutputPath))) { Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); } land.SaveToFile(fileOutputPath, landfmt_cur, nometa); break; // NJS_OBJECT case "model": case "object": case "basicmodel": case "basicdxmodel": case "chunkmodel": case "gcmodel": { ModelFormat modelfmt_obj; string modelext; switch (type) { case "basicmodel": modelfmt_obj = ModelFormat.Basic; modelext = ".sa1mdl"; break; case "basicdxmodel": modelfmt_obj = ModelFormat.BasicDX; modelext = ".sa1mdl"; break; case "chunkmodel": modelfmt_obj = ModelFormat.Chunk; modelext = ".sa2mdl"; break; case "gcmodel": modelfmt_obj = ModelFormat.GC; modelext = ".sa2bmdl"; break; default: modelfmt_obj = ModelFormat.BasicDX; modelext = ".sa1mdl"; break; } NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt_obj, labels, new Dictionary <int, Attach>()); fileOutputPath = MakePathThatExists(fileOutputPath, mdl.Name + modelext); if (!Directory.Exists(Path.GetDirectoryName(fileOutputPath))) { Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); } ModelFile.CreateFile(fileOutputPath, mdl, null, null, null, null, modelfmt_obj, nometa); } break; // NJS_MOTION case "animation": case "motion": int numparts = 0; for (int a = 3; a < args.Length; a++) { if (args[a] == "-p") { numparts = int.Parse(args[a + 1], System.Globalization.NumberStyles.Integer); } } NJS_MOTION ani = new NJS_MOTION(datafile, address, imageBase, numparts, labels); fileOutputPath = MakePathThatExists(fileOutputPath, ani.Name + "saanim"); string outpath = Path.GetDirectoryName(Path.GetFullPath(fileOutputPath)); Console.WriteLine("Output file: {0}", Path.GetFullPath(fileOutputPath)); if (!Directory.Exists(outpath)) { Directory.CreateDirectory(outpath); } ani.Save(fileOutputPath, nometa); break; // Attach case "attach": case "basicattach": case "basicdxattach": case "chunkattach": case "gcattach": { Attach dummy; ModelFormat modelfmt_att; string modelext = ".sa1mdl"; switch (type) { case "basicattach": modelfmt_att = ModelFormat.Basic; modelext = ".sa1mdl"; dummy = new BasicAttach(datafile, address, imageBase, false); break; case "basicdxattach": modelfmt_att = ModelFormat.BasicDX; modelext = ".sa1mdl"; dummy = new BasicAttach(datafile, address, imageBase, true); break; case "chunkattach": modelfmt_att = ModelFormat.Chunk; modelext = ".sa2mdl"; dummy = new ChunkAttach(datafile, address, imageBase); break; case "gcattach": modelfmt_att = ModelFormat.GC; dummy = new SAModel.GC.GCAttach(datafile, address, imageBase); modelext = ".sa2bmdl"; break; default: modelfmt_att = ModelFormat.BasicDX; modelext = ".sa1mdl"; dummy = new BasicAttach(datafile, address, imageBase, true); break; } NJS_OBJECT mdl = new NJS_OBJECT() { Attach = dummy }; fileOutputPath = MakePathThatExists(fileOutputPath, mdl.Name + modelext); if (!Directory.Exists(Path.GetDirectoryName(fileOutputPath))) { Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); } ModelFile.CreateFile(fileOutputPath, mdl, null, null, null, null, modelfmt_att, nometa); } break; default: Console.WriteLine("Unrecognized export type {0}", type); break; } break; default: Console.WriteLine("Incorrect mode specified. Press ENTER to exit."); Console.ReadLine(); return; } }
static void Main(string[] args) { string dir = Environment.CurrentDirectory; try { Queue <string> argq = new Queue <string>(args); if (argq.Count > 0 && argq.Peek().Equals("/be", StringComparison.OrdinalIgnoreCase)) { ByteConverter.BigEndian = true; argq.Dequeue(); } string mdlfilename; if (argq.Count > 0) { mdlfilename = argq.Dequeue(); Console.WriteLine("File: {0}", mdlfilename); } else { Console.Write("File: "); mdlfilename = Console.ReadLine(); } mdlfilename = Path.GetFullPath(mdlfilename); string[] anifilenames = new string[argq.Count]; for (int j = 0; j < anifilenames.Length; j++) { Console.WriteLine("Animations: {0}", argq.Peek()); anifilenames[j] = Path.GetFullPath(argq.Dequeue()); } Environment.CurrentDirectory = Path.GetDirectoryName(mdlfilename); byte[] mdlfile = File.ReadAllBytes(mdlfilename); if (Path.GetExtension(mdlfilename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { mdlfile = FraGag.Compression.Prs.Decompress(mdlfile); } Directory.CreateDirectory(Path.GetFileNameWithoutExtension(mdlfilename)); int address = 0; int i = ByteConverter.ToInt32(mdlfile, address); SortedDictionary <int, int> modeladdrs = new SortedDictionary <int, int>(); while (i != -1) { modeladdrs[i] = ByteConverter.ToInt32(mdlfile, address + 4); address += 8; i = ByteConverter.ToInt32(mdlfile, address); } Dictionary <int, NJS_OBJECT> models = new Dictionary <int, NJS_OBJECT>(); Dictionary <int, string> modelnames = new Dictionary <int, string>(); List <string> partnames = new List <string>(); foreach (KeyValuePair <int, int> item in modeladdrs) { NJS_OBJECT obj = new NJS_OBJECT(mdlfile, item.Value, 0, ModelFormat.Chunk); modelnames[item.Key] = obj.Name; if (!partnames.Contains(obj.Name)) { List <string> names = new List <string>(obj.GetObjects().Select((o) => o.Name)); foreach (int idx in modelnames.Where(a => names.Contains(a.Value)).Select(a => a.Key)) { models.Remove(idx); } models[item.Key] = obj; partnames.AddRange(names); } } Dictionary <int, string> animfns = new Dictionary <int, string>(); Dictionary <int, Animation> anims = new Dictionary <int, Animation>(); foreach (string anifilename in anifilenames) { byte[] anifile = File.ReadAllBytes(anifilename); if (Path.GetExtension(anifilename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { anifile = FraGag.Compression.Prs.Decompress(anifile); } Directory.CreateDirectory(Path.GetFileNameWithoutExtension(anifilename)); address = 0; i = ByteConverter.ToInt16(anifile, address); while (i != -1) { anims[i] = new Animation(anifile, ByteConverter.ToInt32(anifile, address + 4), 0, ByteConverter.ToInt16(anifile, address + 2)); animfns[i] = Path.Combine(Path.GetFileNameWithoutExtension(anifilename), i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); address += 8; i = ByteConverter.ToInt16(anifile, address); } } foreach (KeyValuePair <int, NJS_OBJECT> model in models) { List <string> animlist = new List <string>(); foreach (KeyValuePair <int, Animation> anim in anims) { if (model.Value.CountAnimated() == anim.Value.ModelParts) { animlist.Add("../" + animfns[anim.Key]); } } ModelFile.CreateFile(Path.Combine(Path.GetFileNameWithoutExtension(mdlfilename), model.Key.ToString(NumberFormatInfo.InvariantInfo) + ".sa2mdl"), model.Value, animlist.ToArray(), null, null, null, "splitMDL", null, ModelFormat.Chunk); } IniSerializer.Serialize(modelnames, new IniCollectionSettings(IniCollectionMode.IndexOnly), Path.Combine(Path.GetFileNameWithoutExtension(mdlfilename), Path.GetFileNameWithoutExtension(mdlfilename) + ".ini")); foreach (KeyValuePair <int, Animation> anim in anims) { anim.Value.Save(animfns[anim.Key]); } } finally { Environment.CurrentDirectory = dir; } }
static void Main(string[] args) { string basemdlname; if (args.Length > 0) { basemdlname = args[0]; } else { Console.Write("Base Model: "); basemdlname = Console.ReadLine().Trim('"'); } string fext = Path.GetExtension(basemdlname); ModelFile modelFile = new ModelFile(basemdlname); ModelFormat fmt = modelFile.Format; NJS_OBJECT basemdl = modelFile.Model; string mtnname; if (args.Length > 1) { mtnname = args[1]; } else { Console.Write("Motion: "); mtnname = Console.ReadLine().Trim('"'); } NJS_MOTION mtn = NJS_MOTION.Load(mtnname, basemdl.CountMorph()); string outdir; if (args.Length > 2) { outdir = args[2]; } else { outdir = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(mtnname)), Path.GetFileNameWithoutExtension(mtnname)); } Directory.CreateDirectory(outdir); MTNInfo inf = new MTNInfo() { ModelFormat = fmt, Name = mtn.Name, Frames = mtn.Frames, InterpolationMode = mtn.InterpolationMode }; IniSerializer.Serialize(inf, Path.Combine(outdir, Path.ChangeExtension(Path.GetFileName(mtnname), ".ini"))); NJS_OBJECT[] objs = basemdl.GetObjects().Where(a => a.Morph).ToArray(); for (int frame = 0; frame < mtn.Frames; frame++) { if (!mtn.Models.Any(a => a.Value.Vertex.ContainsKey(frame))) { continue; } foreach ((int idx, AnimModelData amd) in mtn.Models) { if (amd.Vertex.ContainsKey(frame)) { Vertex[] verts = amd.Vertex[frame]; amd.Normal.TryGetValue(frame, out Vertex[] norms); switch (objs[idx].Attach) { case BasicAttach batt: for (int i = 0; i < Math.Min(verts.Length, batt.Vertex.Length); i++) { batt.Vertex[i] = verts[i]; if (norms != null && batt.Normal != null) { batt.Normal[i] = norms[i]; } } break; case ChunkAttach catt: if (catt.Vertex == null) { continue; } int vcnt = catt.Vertex.Sum(a => a.VertexCount); int ci = 0; int vi = 0; for (int i = 0; i < Math.Min(verts.Length, vcnt); i++) { catt.Vertex[ci].Vertices[vi] = verts[i]; if (norms != null && catt.Vertex[ci].Normals != null) { catt.Vertex[ci].Normals[vi] = norms[i]; } if (++vi >= catt.Vertex[ci].VertexCount) { ++ci; vi = 0; } } break; } } } ModelFile.CreateFile(Path.Combine(outdir, $"{frame}{fext}"), basemdl, null, null, null, null, fmt); } }
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 void Split(bool isBigEndian, string filePath, string outputFolder, string[] animationPaths) { string dir = Environment.CurrentDirectory; try { if (outputFolder[outputFolder.Length - 1] != '/') { outputFolder = string.Concat(outputFolder, "/"); } ByteConverter.BigEndian = isBigEndian; // get file name, read it from the console if nothing string mdlfilename = filePath; mdlfilename = Path.GetFullPath(mdlfilename); // look through the argumetns for animationfiles string[] anifilenames = animationPaths; // load model file Environment.CurrentDirectory = (outputFolder.Length != 0) ? outputFolder : Path.GetDirectoryName(mdlfilename); byte[] mdlfile = File.ReadAllBytes(mdlfilename); if (Path.GetExtension(mdlfilename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { mdlfile = FraGag.Compression.Prs.Decompress(mdlfile); } Directory.CreateDirectory(Path.GetFileNameWithoutExtension(mdlfilename)); // getting model pointers int address = 0; int i = ByteConverter.ToInt32(mdlfile, address); SortedDictionary <int, int> modeladdrs = new SortedDictionary <int, int>(); while (i != -1) { modeladdrs[i] = ByteConverter.ToInt32(mdlfile, address + 4); address += 8; i = ByteConverter.ToInt32(mdlfile, address); } // load models from pointer list Dictionary <int, NJS_OBJECT> models = new Dictionary <int, NJS_OBJECT>(); Dictionary <int, string> modelnames = new Dictionary <int, string>(); List <string> partnames = new List <string>(); foreach (KeyValuePair <int, int> item in modeladdrs) { NJS_OBJECT obj = new NJS_OBJECT(mdlfile, item.Value, 0, ModelFormat.Chunk); modelnames[item.Key] = obj.Name; if (!partnames.Contains(obj.Name)) { List <string> names = new List <string>(obj.GetObjects().Select((o) => o.Name)); foreach (int idx in modelnames.Where(a => names.Contains(a.Value)).Select(a => a.Key)) { models.Remove(idx); } models[item.Key] = obj; partnames.AddRange(names); } } // load animations Dictionary <int, string> animfns = new Dictionary <int, string>(); Dictionary <int, NJS_MOTION> anims = new Dictionary <int, NJS_MOTION>(); foreach (string anifilename in anifilenames) { Dictionary <int, int> processedanims = new Dictionary <int, int>(); Dictionary <int, string> ini = new Dictionary <int, string>(); byte[] anifile = File.ReadAllBytes(anifilename); if (Path.GetExtension(anifilename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { anifile = FraGag.Compression.Prs.Decompress(anifile); } Directory.CreateDirectory(Path.GetFileNameWithoutExtension(anifilename)); address = 0; i = ByteConverter.ToInt16(anifile, address); while (i != -1) { int aniaddr = ByteConverter.ToInt32(anifile, address + 4); if (!processedanims.ContainsKey(aniaddr)) { anims[i] = new NJS_MOTION(anifile, ByteConverter.ToInt32(anifile, address + 4), 0, ByteConverter.ToInt16(anifile, address + 2)); animfns[i] = Path.Combine(Path.GetFileNameWithoutExtension(anifilename), i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); anims[i].Save(animfns[i]); processedanims[aniaddr] = i; } ini[i] = "animation_" + aniaddr.ToString("X8"); address += 8; i = ByteConverter.ToInt16(anifile, address); } IniSerializer.Serialize(ini, new IniCollectionSettings(IniCollectionMode.IndexOnly), Path.Combine(Path.GetFileNameWithoutExtension(anifilename), Path.GetFileNameWithoutExtension(anifilename) + ".ini")); } // save output model files foreach (KeyValuePair <int, NJS_OBJECT> model in models) { List <string> animlist = new List <string>(); foreach (KeyValuePair <int, NJS_MOTION> anim in anims) { if (model.Value.CountAnimated() == anim.Value.ModelParts) { string rel = animfns[anim.Key].Replace(outputFolder, string.Empty); if (rel.Length > 1 && rel[1] != ':') { rel = "../" + rel; } animlist.Add(rel); } } ModelFile.CreateFile(Path.Combine(Path.GetFileNameWithoutExtension(mdlfilename), model.Key.ToString(NumberFormatInfo.InvariantInfo) + ".sa2mdl"), model.Value, animlist.ToArray(), null, null, null, null, ModelFormat.Chunk); } // save ini file IniSerializer.Serialize(modelnames, new IniCollectionSettings(IniCollectionMode.IndexOnly), Path.Combine(Path.GetFileNameWithoutExtension(mdlfilename), Path.GetFileNameWithoutExtension(mdlfilename) + ".ini")); } finally { Environment.CurrentDirectory = dir; } }