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; } }
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); }
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(); } LandTable level = LandTable.LoadFromFile(filename); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { if ((col.SurfaceFlags & SurfaceFlags.Visible) == SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SurfaceFlags.Visible; newcol.Model = new NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; string newname = basatt.Name + "_cnk"; if (visitedAttaches.ContainsKey(newname)) { newcol.Model.Attach = visitedAttaches[newname]; } else { ChunkAttach cnkatt = new ChunkAttach(true, true) { Name = basatt.Name + "_cnk", Bounds = basatt.Bounds }; visitedAttaches[newname] = cnkatt; newcol.Model.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); } } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: Vertex[] VertexBuffer = new Vertex[0]; Vertex[] NormalBuffer = new Vertex[0]; Color?[] ColorBuffer = new Color?[0]; foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { ChunkAttach cnkatt = (ChunkAttach)col.Model.Attach; BasicAttach basatt = new BasicAttach() { Name = cnkatt.Name, Bounds = cnkatt.Bounds }; 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.Resize(ref ColorBuffer, 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); if (chunk.Diffuse.Count > 0) { Array.Copy(chunk.Diffuse.Cast <Color?>().ToArray(), 0, ColorBuffer, chunk.IndexOffset, chunk.Diffuse.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; } bool hasVertVColor = false; if (!hasVColor && c2.Strips.All(a => a.Indexes.All(b => ColorBuffer[b].HasValue))) { hasVertVColor = true; } List <Strip> strips = new List <Strip>(c2.StripCount); List <UV> uvs = hasUV ? new List <UV>() : null; List <Color> vcolors = hasVColor || hasVertVColor ? 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); } else if (hasVertVColor) { foreach (short i in strip.Indexes) { vcolors.Add(ColorBuffer[i].Value); } } } NJS_MESHSET mesh = new NJS_MESHSET(strips.ToArray(), false, hasUV, hasVColor || hasVertVColor); if (hasUV) { uvs.CopyTo(mesh.UV); } if (hasVColor || hasVertVColor) { 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); } } } col.Model.Attach = basatt; } level.Anim = new List <GeoAnimData>(); level.Flags = 8; // set LandTable to use PVM/GVM level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
// Scan for Landtables static void ScanLandtable(LandTableFormat landfmt) { CurrentStep++; CurrentScanData = "Landtables " + landfmt.ToString(); ByteConverter.BigEndian = BigEndian; Console.WriteLine("Step {0}: Scanning for {1} landtables", CurrentStep, landfmt.ToString()); string landtable_extension = ".sa1lvl"; int count = 0; switch (landfmt) { case LandTableFormat.SA1: landtable_extension = ".sa1lvl"; break; case LandTableFormat.SADX: default: landtable_extension = ".sa1lvl"; break; case LandTableFormat.SA2: landtable_extension = ".sa2lvl"; break; case LandTableFormat.SA2B: landtable_extension = ".sa2blvl"; break; } if (!SingleOutputFolder) { Directory.CreateDirectory(Path.Combine(OutputFolder, "levels")); } for (uint address = StartAddress; address < EndAddress; address += 1) { if (CancelScan) { break; } if (ConsoleMode && address % 1000 == 0) { Console.Write("\r{0} ", address.ToString("X8")); } CurrentAddress = address; string fileOutputPath = Path.Combine(OutputFolder, "levels", address.ToString("X8")); if (SingleOutputFolder) { fileOutputPath = Path.Combine(OutputFolder, address.ToString("X8")); } if (!CheckLandTable(address, landfmt)) { continue; } try { //Console.WriteLine("Try {0}", address.ToString("X")); LandTable land = new LandTable(datafile, (int)address, ImageBase, landfmt); if (land.COL.Count > 3) { land.SaveToFile(fileOutputPath + landtable_extension, landfmt, NoMeta); count++; switch (landfmt) { case LandTableFormat.SA1: FoundSA1Landtables++; break; case LandTableFormat.SADX: default: FoundSADXLandtables++; break; case LandTableFormat.SA2: FoundSA2Landtables++; break; case LandTableFormat.SA2B: FoundSA2BLandtables++; break; } landtablelist.Add(address); Console.WriteLine("\rLandtable {0} at {1}", landfmt.ToString(), address.ToString("X8")); addresslist.Add(address, "landtable_" + landfmt.ToString()); address += (uint)LandTable.Size(landfmt) - 1; } } catch (Exception) { continue; } } Console.WriteLine("\r{0} landtables found", count); }
static void Main(string[] args) { Environment.CurrentDirectory = @"C:\SONICADVENTUREDX\Projects\ECPort"; List <BMPInfo> textures = new List <BMPInfo>(TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\BEACH01.PVM")); LandTable landTable = LandTable.LoadFromFile(@"Levels\Emerald Coast\Act 1\LandTable.sa1lvl"); texmap = new Dictionary <int, int>(); BMPInfo[] newtexs = TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\BEACH03.PVM"); for (int i = 0; i < newtexs.Length; i++) { BMPInfo found = textures.FirstOrDefault(a => a.Name.Equals(newtexs[i].Name)); if (found == null) { texmap[i] = textures.Count; textures.Add(newtexs[i]); } else { texmap[i] = textures.IndexOf(found); } } foreach (COL col in LandTable.LoadFromFile(@"Levels\Emerald Coast\Act 3\LandTable.sa1lvl").COL) { foreach (NJS_MATERIAL mat in ((BasicAttach)col.Model.Attach).Material) { mat.TextureID = texmap[mat.TextureID]; } landTable.COL.Add(col); } texmap = new Dictionary <int, int>(); newtexs = TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\BEACH02.PVM"); for (int i = 0; i < newtexs.Length; i++) { BMPInfo found = textures.FirstOrDefault(a => a.Name.Equals(newtexs[i].Name)); if (found == null) { texmap[i] = textures.Count; textures.Add(newtexs[i]); } else { texmap[i] = textures.IndexOf(found); } } foreach (COL col in LandTable.LoadFromFile(@"Levels\Emerald Coast\Act 2\LandTable.sa1lvl").COL) { col.Bounds.Center.Z -= 2000; col.Model.Position.Z -= 2000; foreach (NJS_MATERIAL mat in ((BasicAttach)col.Model.Attach).Material) { mat.TextureID = texmap[mat.TextureID]; } landTable.COL.Add(col); } texmap = new Dictionary <int, int>(); newtexs = TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\OBJ_BEACH.PVM"); for (int i = 0; i < newtexs.Length; i++) { BMPInfo found = textures.FirstOrDefault(a => a.Name.Equals(newtexs[i].Name)); if (found == null) { texmap[i] = textures.Count; textures.Add(newtexs[i]); } else { texmap[i] = textures.IndexOf(found); } } PAKFile pak = new PAKFile(); List <byte> inf = new List <byte>(); string filenoext = "beach01"; string longdir = "..\\..\\..\\sonic2\\resource\\gd_pc\\prs\\" + filenoext; using (System.Windows.Forms.Panel panel = new System.Windows.Forms.Panel()) using (Direct3D d3d = new Direct3D()) using (Device dev = new Device(d3d, 0, DeviceType.Hardware, panel.Handle, CreateFlags.HardwareVertexProcessing, new PresentParameters(640, 480))) { for (int i = 0; i < textures.Count; i++) { using (Texture tex = textures[i].Image.ToTexture(dev)) using (DataStream str = Surface.ToStream(tex.GetSurfaceLevel(0), ImageFileFormat.Dds)) using (MemoryStream ms = new MemoryStream()) { str.CopyTo(ms); pak.Files.Add(new PAKFile.File(filenoext + '\\' + Path.ChangeExtension(textures[i].Name, ".dds"), longdir + '\\' + Path.ChangeExtension(textures[i].Name, ".dds"), ms.ToArray())); } int infsz = inf.Count; inf.AddRange(Encoding.ASCII.GetBytes(Path.ChangeExtension(textures[i].Name, null))); inf.AddRange(new byte[0x1C - (inf.Count - infsz)]); inf.AddRange(BitConverter.GetBytes(i + 200)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(textures[i].Image.Width)); inf.AddRange(BitConverter.GetBytes(textures[i].Image.Height)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(0x80000000)); } } pak.Files.Insert(0, new PAKFile.File(filenoext + '\\' + filenoext + ".inf", longdir + '\\' + filenoext + ".inf", inf.ToArray())); pak.Save(@"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\gd_PC\PRS\beach01.pak"); List <COL> newcollist = new List <COL>(); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); foreach (COL col in landTable.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { ConvertCOL(newcollist, visitedAttaches, col); } landTable.COL = newcollist; Console.WriteLine("Loading Object Definitions:"); Console.WriteLine("Parsing..."); LevelData.ObjDefs = new List <ObjectDefinition>(); Dictionary <string, ObjectData> objdefini = IniSerializer.Deserialize <Dictionary <string, ObjectData> >("objdefs.ini"); List <ObjectData> objectErrors = new List <ObjectData>(); ObjectListEntry[] objlstini = ObjectList.Load(@"Levels\Emerald Coast\Object List.ini", false); Directory.CreateDirectory("dllcache").Attributes |= FileAttributes.Hidden; List <KeyValuePair <string, string> > compileErrors = new List <KeyValuePair <string, string> >(); for (int ID = 0; ID < objlstini.Length; ID++) { string codeaddr = objlstini[ID].CodeString; if (!objdefini.ContainsKey(codeaddr)) { codeaddr = "0"; } ObjectData defgroup = objdefini[codeaddr]; ObjectDefinition def; if (!string.IsNullOrEmpty(defgroup.CodeFile)) { Console.WriteLine("Compiling: " + defgroup.CodeFile); def = CompileObjectDefinition(defgroup, out bool errorOccured, out string errorText); if (errorOccured) { KeyValuePair <string, string> errorValue = new KeyValuePair <string, string>( defgroup.CodeFile, errorText); compileErrors.Add(errorValue); } } else { def = new DefaultObjectDefinition(); } LevelData.ObjDefs.Add(def); // The only reason .Model is checked for null is for objects that don't yet have any // models defined for them. It would be annoying seeing that error all the time! if (string.IsNullOrEmpty(defgroup.CodeFile) && !string.IsNullOrEmpty(defgroup.Model)) { Console.WriteLine("Loading: " + defgroup.Model); // Otherwise, if the model file doesn't exist and/or no texture file is defined, // load the "default object" instead ("?"). if (!File.Exists(defgroup.Model)) { ObjectData error = new ObjectData { Name = defgroup.Name, Model = defgroup.Model, Texture = defgroup.Texture }; objectErrors.Add(error); defgroup.Model = null; } } def.Init(defgroup, objlstini[ID].Name); def.SetInternalName(objlstini[ID].Name); } // Checks if there have been any errors added to the error list and does its thing // This thing is a mess. If anyone can think of a cleaner way to do this, be my guest. if (objectErrors.Count > 0) { int count = objectErrors.Count; List <string> errorStrings = new List <string> { "The following objects failed to load:" }; foreach (ObjectData o in objectErrors) { bool texEmpty = string.IsNullOrEmpty(o.Texture); bool texExists = (!string.IsNullOrEmpty(o.Texture) && LevelData.Textures.ContainsKey(o.Texture)); errorStrings.Add(""); errorStrings.Add("Object:\t\t" + o.Name); errorStrings.Add("\tModel:"); errorStrings.Add("\t\tName:\t" + o.Model); errorStrings.Add("\t\tExists:\t" + File.Exists(o.Model)); errorStrings.Add("\tTexture:"); errorStrings.Add("\t\tName:\t" + ((texEmpty) ? "(N/A)" : o.Texture)); errorStrings.Add("\t\tExists:\t" + texExists); } // TODO: Proper logging. Who knows where this file may end up File.WriteAllLines("SADXLVL2.log", errorStrings.ToArray()); } // Loading SET Layout Console.WriteLine("Loading SET items", "Initializing..."); List <SETItem> setlist = new List <SETItem>(); SonicRetro.SAModel.SAEditorCommon.UI.EditorItemSelection selection = new SonicRetro.SAModel.SAEditorCommon.UI.EditorItemSelection(); if (LevelData.ObjDefs.Count > 0) { string setstr = @"C:\SONICADVENTUREDX\Projects\ECPort\system\SET0100S.BIN"; if (File.Exists(setstr)) { Console.WriteLine("SET: " + setstr.Replace(Environment.CurrentDirectory, "")); setlist = SETItem.Load(setstr, selection); } setstr = @"C:\SONICADVENTUREDX\Projects\ECPort\system\SET0102B.BIN"; if (File.Exists(setstr)) { Console.WriteLine("SET: " + setstr.Replace(Environment.CurrentDirectory, "")); setlist.AddRange(SETItem.Load(setstr, selection)); } setstr = @"C:\SONICADVENTUREDX\Projects\ECPort\system\SET0101S.BIN"; if (File.Exists(setstr)) { Console.WriteLine("SET: " + setstr.Replace(Environment.CurrentDirectory, "")); List <SETItem> newlist = SETItem.Load(setstr, selection); foreach (SETItem item in newlist) { item.Position.Z -= 2000; } setlist.AddRange(newlist); } } MatrixStack transform = new MatrixStack(); List <SETItem> add = new List <SETItem>(); List <SETItem> del = new List <SETItem>(); List <PalmtreeData> trees = new List <PalmtreeData>(); foreach (SETItem item in setlist) { switch (item.ID) { case 0xD: // item box item.ID = 0xA; item.Scale.X = itemboxmap[(int)item.Scale.X]; break; case 0x15: // ring group to rings for (int i = 0; i < Math.Min(item.Scale.X + 1, 8); i++) { if (item.Scale.Z == 1) // circle { double v4 = i * 360.0; Vector3 v7 = new Vector3( ObjectHelper.NJSin((int)(v4 / item.Scale.X * 65536.0 * 0.002777777777777778)) * item.Scale.Y, 0, ObjectHelper.NJCos((int)(v4 / item.Scale.X * 65536.0 * 0.002777777777777778)) * item.Scale.Y); transform.Push(); transform.NJTranslate(item.Position); transform.NJRotateObject(item.Rotation); Vector3 pos = Vector3.TransformCoordinate(v7, transform.Top); transform.Pop(); add.Add(new SETItem(0, selection) { Position = pos.ToVertex() }); } else // line { transform.Push(); transform.NJTranslate(item.Position); transform.NJRotateObject(item.Rotation); double v5; if (i % 2 == 1) { v5 = i * item.Scale.Y * -0.5; } else { v5 = Math.Ceiling(i * 0.5) * item.Scale.Y; } Vector3 pos = Vector3.TransformCoordinate(new Vector3(0, 0, (float)v5), transform.Top); transform.Pop(); add.Add(new SETItem(0, selection) { Position = pos.ToVertex() }); } } del.Add(item); break; case 0x1A: // tikal -> omochao item.ID = 0x19; item.Position.Y += 3; break; case 0x1D: // kiki item.ID = 0x5B; item.Rotation = new Rotation(); item.Scale = new Vertex(); break; case 0x1F: // sweep ->beetle item.ID = 0x38; item.Rotation = new Rotation(); item.Scale = new Vertex(1, 0, 0); break; case 0x28: // launch ramp item.ID = 6; item.Scale.X /= 2.75f; item.Scale.Z = 0.799999952316284f; break; case 0x4F: // updraft item.ID = 0x35; item.Scale.X = Math.Max(Math.Min(item.Scale.X, 200), 10) / 2; item.Scale.Y = Math.Max(Math.Min(item.Scale.Y, 200), 10) / 2; item.Scale.Z = Math.Max(Math.Min(item.Scale.Z, 200), 10) / 2; break; case 0x52: // item box air item.ID = 0xB; item.Scale.X = itemboxmap[(int)item.Scale.X]; break; // palm trees case 32: case 33: case 34: case 35: trees.Add(new PalmtreeData((byte)(item.ID - 32), item.Position, item.Rotation)); del.Add(item); break; // nonsolid objects case 47: case 48: case 49: case 50: case 51: case 52: case 59: case 62: case 63: case 64: case 70: ConvertSETItem(newcollist, item, false, setlist.IndexOf(item)); del.Add(item); break; // solid objects case 36: case 37: case 39: case 41: case 42: case 43: case 44: case 45: case 46: case 54: case 58: case 66: case 71: case 72: case 73: case 74: ConvertSETItem(newcollist, item, true, setlist.IndexOf(item)); del.Add(item); break; case 81: // goal item.ID = 0xE; item.Position.Y += 30; break; default: if (idmap.ContainsKey(item.ID)) { item.ID = idmap[item.ID]; } else { del.Add(item); } break; } } setlist.AddRange(add); foreach (SETItem item in del) { setlist.Remove(item); } setlist.Add(new SETItem(0x55, selection) { Position = new Vertex(6158.6f, -88f, 2384.97f), Scale = new Vertex(3, 0, 0) }); { COL col = new COL() { Model = new ModelFile(@"E:\Bridge Model.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Visible }; col.Model.Position = new Vertex(2803, -1, 365); foreach (NJS_MATERIAL mat in ((BasicAttach)col.Model.Attach).Material) { mat.TextureID = texmap[mat.TextureID]; } col.Model.ProcessVertexData(); col.CalculateBounds(); ConvertCOL(newcollist, new Dictionary <string, Attach>(), col); col = new COL() { Model = new ModelFile(@"E:\Bridge Model COL.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.Position = new Vertex(2803, -1, 365); col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment0.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment1.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment2.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment3.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); } landTable.SaveToFile(@"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\LandTable.sa2lvl", LandTableFormat.SA2); ByteConverter.BigEndian = true; SETItem.Save(setlist, @"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\gd_PC\set0013_s.bin"); for (int i = 0; i < 4; i++) { ModelFile modelFile = new ModelFile($@"C:\SONICADVENTUREDX\Projects\Test\Objects\Levels\Emerald Coast\YASI{i}.sa1mdl"); foreach (BasicAttach attach in modelFile.Model.GetObjects().Where(a => a.Attach != null).Select(a => a.Attach)) { foreach (NJS_MATERIAL mat in attach.Material) { mat.TextureID = texmap[mat.TextureID]; } } modelFile.SaveToFile($@"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\YASI{i}.sa1mdl"); } using (StreamWriter sw = File.CreateText(@"E:\Documents\Visual Studio 2017\Projects\LevelTest\LevelTest\pt.c")) sw.WriteLine(string.Join(",\r\n", trees)); }
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().Trim('"'); } LandTable level = LandTable.LoadFromFile(filename); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { //fix flags differences if ((col.SurfaceFlags & SA1SurfaceFlags.UseSkyDrawDistance) == SA1SurfaceFlags.UseSkyDrawDistance) { col.SurfaceFlags &= ~SA1SurfaceFlags.UseSkyDrawDistance; } if ((col.SurfaceFlags & SA1SurfaceFlags.Diggable) == SA1SurfaceFlags.Diggable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Diggable; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Diggable; } if ((col.SurfaceFlags & SA1SurfaceFlags.Stairs) == SA1SurfaceFlags.Stairs) { col.SurfaceFlags &= ~SA1SurfaceFlags.Stairs; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Stairs; } if ((col.SurfaceFlags & SA1SurfaceFlags.Waterfall) == SA1SurfaceFlags.Waterfall) { col.SurfaceFlags &= ~SA1SurfaceFlags.Waterfall; } if ((col.SurfaceFlags & SA1SurfaceFlags.Hurt) == SA1SurfaceFlags.Hurt) { col.SurfaceFlags &= ~SA1SurfaceFlags.Hurt; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Hurt; } if ((col.SurfaceFlags & SA1SurfaceFlags.Unclimbable) == SA1SurfaceFlags.Unclimbable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Unclimbable; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Unclimbable; } if ((col.SurfaceFlags & SA1SurfaceFlags.CannotLand) == SA1SurfaceFlags.CannotLand) { col.SurfaceFlags &= ~SA1SurfaceFlags.CannotLand; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.CannotLand; } if ((col.SurfaceFlags & SA1SurfaceFlags.LowDepth) == SA1SurfaceFlags.LowDepth) { col.SurfaceFlags &= ~SA1SurfaceFlags.LowDepth; } if ((col.SurfaceFlags & SA1SurfaceFlags.Visible) == SA1SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SA1SurfaceFlags.Visible; newcol.Model = new NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; string newname = basatt.Name + "_cnk"; if (visitedAttaches.ContainsKey(newname)) { newcol.Model.Attach = visitedAttaches[newname]; } else { ChunkAttach cnkatt = basatt.ToChunk(); visitedAttaches[newname] = cnkatt; newcol.Model.Attach = cnkatt; } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SA1SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SA1SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { col.Model.Attach = col.Model.Attach.ToBasic(); } foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { //fix flags differences if ((col.SurfaceFlags & SA1SurfaceFlags.Diggable) == SA1SurfaceFlags.Diggable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Diggable; col.SurfaceFlags |= SA1SurfaceFlags.Stairs; } if ((col.SurfaceFlags & SA1SurfaceFlags.UseSkyDrawDistance) == SA1SurfaceFlags.UseSkyDrawDistance) { col.SurfaceFlags &= ~SA1SurfaceFlags.UseSkyDrawDistance; col.SurfaceFlags |= SA1SurfaceFlags.Diggable; } if ((col.SurfaceFlags & SA1SurfaceFlags.Unclimbable) == SA1SurfaceFlags.Unclimbable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Unclimbable; col.SurfaceFlags |= SA1SurfaceFlags.CannotLand; } if ((col.SurfaceFlags & SA1SurfaceFlags.IncreasedAcceleration) == SA1SurfaceFlags.IncreasedAcceleration) { col.SurfaceFlags &= ~SA1SurfaceFlags.IncreasedAcceleration; col.SurfaceFlags |= SA1SurfaceFlags.Unclimbable; } if ((col.SurfaceFlags & SA1SurfaceFlags.Waterfall) == SA1SurfaceFlags.Waterfall) { col.SurfaceFlags &= ~SA1SurfaceFlags.Waterfall; col.SurfaceFlags |= SA1SurfaceFlags.Hurt; } } level.Anim = new List <GeoAnimData>(); level.Attributes = SA1LandtableAttributes.LoadTextureFile; // set LandTable to use PVM/GVM level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
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(); } LandTable level = LandTable.LoadFromFile(filename); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { if ((col.SurfaceFlags & SurfaceFlags.Visible) == SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SurfaceFlags.Visible; newcol.Model = new SonicRetro.SAModel.NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; ChunkAttach cnkatt = new ChunkAttach(true, true) { Name = basatt.Name + "_cnk", Bounds = basatt.Bounds }; newcol.Model.Attach = cnkatt; VertexChunk vcnk; if (basatt.Normal != null && basatt.Normal.Length > 0) { vcnk = new VertexChunk(ChunkType.Vertex_VertexNormal); } else { vcnk = new VertexChunk(ChunkType.Vertex_Vertex); } vcnk.Vertices = new List <Vertex>(basatt.Vertex); if (basatt.Normal != null) { vcnk.Normals = new List <Vertex>(basatt.Normal); } vcnk.VertexCount = (ushort)basatt.Vertex.Length; vcnk.Size = (ushort)((vcnk.Type == ChunkType.Vertex_VertexNormal ? vcnk.VertexCount * 6 : vcnk.VertexCount * 3) + 1); cnkatt.Vertex.Add(vcnk); foreach (NJS_MESHSET mesh in basatt.Mesh) { if (mesh.PolyType != Basic_PolyType.Strips) { Console.WriteLine("Warning: Skipping non-strip mesh in {0} ({1}).", basatt.MeshName, mesh.PolyType); continue; } NJS_MATERIAL mat = null; if (basatt.Material != null && mesh.MaterialID < basatt.Material.Count) { mat = basatt.Material[mesh.MaterialID]; cnkatt.Poly.Add(new PolyChunkBitsBlendAlpha() { SourceAlpha = mat.SourceAlpha, DestinationAlpha = mat.DestinationAlpha }); 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() { Diffuse = mat.DiffuseColor, Specular = mat.SpecularColor, SpecularExponent = (byte)mat.Exponent }); } PolyChunkStrip strip; if (mesh.UV != null & mesh.VColor != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripUVNColor); } else if (mesh.UV != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripUVN); } else if (mesh.VColor != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripColor); } 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; } int striptotal = 0; foreach (Strip item in mesh.Poly.Cast <Strip>()) { UV[] uvs = null; if (mesh.UV != null) { uvs = new UV[item.Indexes.Length]; Array.Copy(mesh.UV, striptotal, uvs, 0, item.Indexes.Length); } Color[] vcolors = null; if (mesh.VColor != null) { vcolors = new Color[item.Indexes.Length]; Array.Copy(mesh.VColor, striptotal, vcolors, 0, item.Indexes.Length); } strip.Strips.Add(new PolyChunkStrip.Strip(item.Reversed, item.Indexes, uvs, vcolors)); striptotal += item.Indexes.Length; } cnkatt.Poly.Add(strip); } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.Tool = "SA Tools Level Converter"; level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: Vertex[] VertexBuffer = new Vertex[0]; Vertex[] NormalBuffer = new Vertex[0]; foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { ChunkAttach cnkatt = (ChunkAttach)col.Model.Attach; BasicAttach basatt = new BasicAttach() { Name = cnkatt.Name, Bounds = cnkatt.Bounds }; 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; 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(strip.Indexes, 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); } } } col.Model.Attach = basatt; } level.Anim = new List <GeoAnimData>(); level.Tool = "SA Tools Level Converter"; level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
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 filename; if (args.Length > 0) { filename = args[0]; Console.WriteLine("File: {0}", filename); } else { Console.Write("File: "); filename = Console.ReadLine().Trim('"'); } LandTable level = LandTable.LoadFromFile(filename); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { if ((col.SurfaceFlags & SA1SurfaceFlags.Visible) == SA1SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SA1SurfaceFlags.Visible; newcol.Model = new NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; string newname = basatt.Name + "_cnk"; if (visitedAttaches.ContainsKey(newname)) { newcol.Model.Attach = visitedAttaches[newname]; } else { ChunkAttach cnkatt = basatt.ToChunk(); visitedAttaches[newname] = cnkatt; newcol.Model.Attach = cnkatt; } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SA1SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SA1SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { col.Model.Attach = col.Model.Attach.ToBasic(); } level.Anim = new List <GeoAnimData>(); level.Flags = 8; // set LandTable to use PVM/GVM level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
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(); }