public byte[] GetBytes(uint imageBase, uint modelptr, LandTableFormat format) { List <byte> result = new List <byte>(); result.AddRange(Bounds.GetBytes()); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: result.AddRange(ByteConverter.GetBytes(WidthY)); result.AddRange(ByteConverter.GetBytes(WidthZ)); result.AddRange(ByteConverter.GetBytes(modelptr)); result.AddRange(ByteConverter.GetBytes(BlockBits)); break; case LandTableFormat.SA2: case LandTableFormat.SA2B: result.AddRange(ByteConverter.GetBytes(modelptr)); result.AddRange(ByteConverter.GetBytes(WidthZ)); result.AddRange(ByteConverter.GetBytes(BlockBits)); break; } result.AddRange(ByteConverter.GetBytes(Flags)); return(result.ToArray()); }
public GeoAnimData(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, string> labels, Dictionary <int, Attach> attaches) { ModelFormat mfmt = 0; switch (format) { case LandTableFormat.SA1: mfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: mfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: mfmt = ModelFormat.Chunk; break; } AnimationFrame = ByteConverter.ToSingle(file, address); AnimationSpeed = ByteConverter.ToSingle(file, address + 4); MaxFrame = ByteConverter.ToSingle(file, address + 8); Model = new NJS_OBJECT(file, (int)(ByteConverter.ToUInt32(file, address + 0xC) - imageBase), imageBase, mfmt, labels, attaches); int actionaddr = (int)(ByteConverter.ToUInt32(file, address + 0x10) - imageBase); TexlistPointer = ByteConverter.ToUInt32(file, address + 0x14); NJS_ACTION action = new NJS_ACTION(file, actionaddr, imageBase, mfmt, labels, attaches); Animation = action.Animation; }
public string ToStruct(LandTableFormat format) { StringBuilder result = new StringBuilder("{ "); result.Append(Bounds.ToStruct()); result.Append(", "); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: result.Append(WidthY.ToC()); result.Append(", "); result.Append(WidthZ.ToC()); result.Append(", "); result.Append(Model != null ? "&" + Model.Name : "NULL"); result.Append(", "); result.AppendFormat(BlockBits.ToCHex()); break; case LandTableFormat.SA2: result.Append(Model != null ? "&" + Model.Name : "NULL"); result.Append(", "); result.Append(WidthZ.ToC()); result.Append(", "); result.Append(BlockBits.ToCHex()); break; } result.Append(", "); result.AppendFormat(Flags.ToCHex()); result.Append(" }"); return(result.ToString()); }
public GeoAnimData(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, string> labels, Dictionary <int, Attach> attaches) { ModelFormat mfmt = 0; switch (format) { case LandTableFormat.SA1: mfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: mfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: mfmt = ModelFormat.Chunk; break; } Unknown1 = ByteConverter.ToInt32(file, address); Unknown2 = ByteConverter.ToSingle(file, address + 4); Unknown3 = ByteConverter.ToSingle(file, address + 8); Model = new NJS_OBJECT(file, (int)(ByteConverter.ToUInt32(file, address + 0xC) - imageBase), imageBase, mfmt, labels, attaches); Animation = NJS_MOTION.ReadHeader(file, (int)(ByteConverter.ToUInt32(file, address + 0x10) - imageBase), imageBase, mfmt, labels, attaches); Unknown4 = ByteConverter.ToInt32(file, address + 0x14); }
public string ToStructVariables(LandTableFormat format, List <string> labels, string[] textures = null) { using (StringWriter sw = new StringWriter()) { ToStructVariables(sw, format, labels, textures); return(sw.ToString()); } }
private void WriteStructMetadata(StreamWriter sw, bool level, LandTableFormat fmt, string[] texnames = null) { sw.Write("/* Sonic Adventure "); switch (fmt) { case LandTableFormat.SA1: sw.Write("1"); break; case LandTableFormat.SADX: sw.Write("DX"); break; case LandTableFormat.SA2: sw.Write("2"); break; case LandTableFormat.SA2B: sw.Write("2: Battle"); break; } if (level) { sw.WriteLine(" LandTable"); } else { sw.WriteLine(" Model"); } sw.WriteLine(" * "); sw.WriteLine(" * Generated by SALVL"); sw.WriteLine(" * "); if (!string.IsNullOrEmpty(LevelData.geo.Description)) { sw.Write(" * Description: "); sw.WriteLine(LevelData.geo.Description); sw.WriteLine(" * "); } if (!string.IsNullOrEmpty(LevelData.geo.Author)) { sw.Write(" * Author: "); sw.WriteLine(LevelData.geo.Author); sw.WriteLine(" * "); } sw.WriteLine(" */"); sw.WriteLine(); if (texnames != null) { sw.Write("enum {0}TexName", LevelData.leveltexs); sw.WriteLine(); sw.WriteLine("{"); sw.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", texnames)); sw.WriteLine("};"); sw.WriteLine(); } }
public COL(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, string> labels, bool?forceBasic) { Bounds = new BoundingSphere(file, address); ModelFormat mfmt = 0; switch (format) { case LandTableFormat.SA1: mfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: mfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: if (forceBasic.HasValue && forceBasic.Value) { mfmt = ModelFormat.Basic; } else { mfmt = ModelFormat.Chunk; } break; } switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: Unknown1 = ByteConverter.ToInt32(file, address + 0x10); Unknown2 = ByteConverter.ToInt32(file, address + 0x14); uint tmpaddr = ByteConverter.ToUInt32(file, address + 0x18) - imageBase; Model = new NJS_OBJECT(file, (int)tmpaddr, imageBase, mfmt, labels); Unknown3 = ByteConverter.ToInt32(file, address + 0x1C); Flags = ByteConverter.ToInt32(file, address + 0x20); break; case LandTableFormat.SA2: Flags = ByteConverter.ToInt32(file, address + 0x1C); if (!forceBasic.HasValue) { mfmt = Flags < 0 ? ModelFormat.Chunk : ModelFormat.Basic; } tmpaddr = ByteConverter.ToUInt32(file, address + 0x10) - imageBase; Model = new NJS_OBJECT(file, (int)tmpaddr, imageBase, mfmt, labels); Unknown2 = ByteConverter.ToInt32(file, address + 0x14); Unknown3 = ByteConverter.ToInt32(file, address + 0x18); break; } }
private void button1_Click(object sender, EventArgs e) { LandTableFormat format = (LandTableFormat)comboBox2.SelectedIndex; LandTableFormat outfmt = format; if (format == LandTableFormat.SADX) outfmt = LandTableFormat.SA1; ByteConverter.BigEndian = checkBox1.Checked; using (SaveFileDialog sd = new SaveFileDialog() { DefaultExt = outfmt.ToString().ToLowerInvariant() + "lvl", Filter = outfmt.ToString().ToUpperInvariant() + "LVL Files|*." + outfmt.ToString().ToLowerInvariant() + "lvl|All Files|*.*" }) if (sd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { new LandTable(file, (int)NumericUpDown1.Value, (uint)numericUpDown2.Value, format) { Author = author.Text, Description = description.Text }.SaveToFile(sd.FileName, outfmt); Settings.Author = author.Text; Settings.Save(); } }
public static int Size(LandTableFormat format) { switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: return(0x24); case LandTableFormat.SA2: return(0x20); default: throw new ArgumentOutOfRangeException("format"); } }
private void saveToolStripMenuItem_Click(object sender, EventArgs e) { LandTableFormat outfmt = LevelData.geo.Format; if (outfmt == LandTableFormat.SADX) { outfmt = LandTableFormat.SA1; } using (SaveFileDialog a = new SaveFileDialog() { DefaultExt = outfmt.ToString().ToLowerInvariant() + "lvl", Filter = outfmt.ToString().ToUpperInvariant() + "LVL Files|*." + outfmt.ToString().ToLowerInvariant() + "lvl|All Files|*.*" }) if (a.ShowDialog(this) == DialogResult.OK) { LevelData.geo.SaveToFile(a.FileName, outfmt); } }
public COL(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary<int, string> labels, bool? forceBasic) { Bounds = new BoundingSphere(file, address); ModelFormat mfmt = 0; switch (format) { case LandTableFormat.SA1: mfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: mfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: if (forceBasic.HasValue && forceBasic.Value) mfmt = ModelFormat.Basic; else mfmt = ModelFormat.Chunk; break; } switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: Unknown1 = ByteConverter.ToInt32(file, address + 0x10); Unknown2 = ByteConverter.ToInt32(file, address + 0x14); uint tmpaddr = ByteConverter.ToUInt32(file, address + 0x18) - imageBase; Model = new NJS_OBJECT(file, (int)tmpaddr, imageBase, mfmt, labels); Unknown3 = ByteConverter.ToInt32(file, address + 0x1C); Flags = ByteConverter.ToInt32(file, address + 0x20); break; case LandTableFormat.SA2: Flags = ByteConverter.ToInt32(file, address + 0x1C); if (!forceBasic.HasValue) mfmt = Flags < 0 ? ModelFormat.Chunk : ModelFormat.Basic; tmpaddr = ByteConverter.ToUInt32(file, address + 0x10) - imageBase; Model = new NJS_OBJECT(file, (int)tmpaddr, imageBase, mfmt, labels); Unknown2 = ByteConverter.ToInt32(file, address + 0x14); Unknown3 = ByteConverter.ToInt32(file, address + 0x18); break; } }
public GeoAnimData(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary<int, string> labels) { ModelFormat mfmt = 0; switch (format) { case LandTableFormat.SA1: mfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: mfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: mfmt = ModelFormat.Chunk; break; } Unknown1 = ByteConverter.ToInt32(file, address); Unknown2 = ByteConverter.ToSingle(file, address + 4); Unknown3 = ByteConverter.ToSingle(file, address + 8); Model = new NJS_OBJECT(file, (int)(ByteConverter.ToUInt32(file, address + 0xC) - imageBase), imageBase, mfmt); Animation = Animation.ReadHeader(file, (int)(ByteConverter.ToUInt32(file, address + 0x10) - imageBase), imageBase, mfmt, labels); Unknown4 = ByteConverter.ToInt32(file, address + 0x14); }
public GeoAnimData(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, string> labels, Dictionary <int, Attach> attaches) { ModelFormat mfmt = 0; switch (format) { case LandTableFormat.SA1: mfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: mfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: mfmt = ModelFormat.Chunk; break; } Unknown1 = ByteConverter.ToInt32(file, address); Unknown2 = ByteConverter.ToSingle(file, address + 4); Unknown3 = ByteConverter.ToSingle(file, address + 8); Model = new NJS_OBJECT(file, (int)(ByteConverter.ToUInt32(file, address + 0xC) - imageBase), imageBase, mfmt, labels, attaches); int actionaddr = (int)(ByteConverter.ToUInt32(file, address + 0x10) - imageBase); int motionaddr = (int)(ByteConverter.ToUInt32(file, actionaddr + 4) - imageBase); Animation = NJS_MOTION.ReadDirect(file, Model.CountAnimated(), motionaddr, imageBase, labels, attaches); Unknown4 = ByteConverter.ToInt32(file, address + 0x14); if (labels.ContainsKey(actionaddr)) { ActionName = labels[actionaddr]; } else { NJS_ACTION action = new NJS_ACTION(file, actionaddr, imageBase, mfmt, labels, attaches); ActionName = action.Name; labels.Add(actionaddr + (int)imageBase, ActionName); } }
public byte[] GetBytes(uint imageBase, LandTableFormat format, out uint address) { return GetBytes(imageBase, format, new Dictionary<string, uint>(), out address); }
public void SaveToFile(string filename, LandTableFormat format) { bool be = ByteConverter.BigEndian; ByteConverter.BigEndian = false; if (format == LandTableFormat.SADX) { format = LandTableFormat.SA1; } List <byte> file = new List <byte>(); ulong magic; switch (format) { case LandTableFormat.SA1: magic = SA1LVLVer; break; case LandTableFormat.SA2: magic = SA2LVLVer; break; default: throw new ArgumentException("Cannot save " + format + " format levels to file!", "format"); } file.AddRange(ByteConverter.GetBytes(magic)); Dictionary <string, uint> labels = new Dictionary <string, uint>(); byte[] lvl = GetBytes(0x10, format, labels, out uint addr); file.AddRange(ByteConverter.GetBytes(addr + 0x10)); file.AddRange(ByteConverter.GetBytes(lvl.Length + 0x10)); file.AddRange(lvl); if (labels.Count > 0) { List <byte> chunk = new List <byte>(labels.Count * 8); int straddr = (labels.Count * 8) + 8; List <byte> strbytes = new List <byte>(); foreach (KeyValuePair <string, uint> label in labels) { chunk.AddRange(ByteConverter.GetBytes(label.Value)); chunk.AddRange(ByteConverter.GetBytes(straddr + strbytes.Count)); strbytes.AddRange(Encoding.UTF8.GetBytes(label.Key)); strbytes.Add(0); strbytes.Align(4); } chunk.AddRange(ByteConverter.GetBytes(-1L)); chunk.AddRange(strbytes); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Label)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } if (!string.IsNullOrEmpty(Author)) { List <byte> chunk = new List <byte>(Author.Length + 1); chunk.AddRange(Encoding.UTF8.GetBytes(Author)); chunk.Add(0); chunk.Align(4); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Author)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } if (!string.IsNullOrEmpty(Description)) { List <byte> chunk = new List <byte>(Description.Length + 1); chunk.AddRange(Encoding.UTF8.GetBytes(Description)); chunk.Add(0); chunk.Align(4); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Description)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } if (!string.IsNullOrEmpty(Tool)) { List <byte> chunk = new List <byte>(Tool.Length + 1); chunk.AddRange(Encoding.UTF8.GetBytes(Tool)); chunk.Add(0); chunk.Align(4); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Tool)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } foreach (KeyValuePair <uint, byte[]> item in Metadata) { file.AddRange(ByteConverter.GetBytes(item.Key)); file.AddRange(ByteConverter.GetBytes(item.Value.Length)); file.AddRange(item.Value); } file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.End)); file.AddRange(new byte[4]); File.WriteAllBytes(filename, file.ToArray()); ByteConverter.BigEndian = be; }
public void ToStructVariables(TextWriter writer, LandTableFormat format, List <string> labels, string[] textures = null) { List <COL> cnk = new List <COL>(); List <COL> bas = new List <COL>(); foreach (COL item in COL) { if (item.Model.Attach is BasicAttach) { bas.Add(item); } else { cnk.Add(item); } } COL.Clear(); COL.AddRange(cnk); COL.AddRange(bas); for (int i = 0; i < COL.Count; i++) { if (!labels.Contains(COL[i].Model.Name)) { labels.Add(COL[i].Model.Name); COL[i].Model.ToStructVariables(writer, format == LandTableFormat.SADX, labels, textures); } } for (int i = 0; i < Anim.Count; i++) { string aniid = Anim[i].Animation.Name.MakeIdentifier(); if (!labels.Contains(Anim[i].Model.Name)) { labels.Add(Anim[i].Model.Name); Anim[i].Model.ToStructVariables(writer, format == LandTableFormat.SADX, labels, textures); } if (!labels.Contains(aniid)) { labels.Add(aniid); Anim[i].Animation.ToStructVariables(writer); writer.Write("NJS_ACTION action_"); writer.Write(aniid); writer.Write(" = { &"); writer.Write(Anim[i].Model.Name); writer.Write(", &"); writer.Write(aniid); writer.WriteLine(" };"); writer.WriteLine(); } } if (!labels.Contains(COLName)) { labels.Add(COLName); writer.Write("COL "); writer.Write(COLName); writer.WriteLine("[] = {"); List <string> lines = new List <string>(COL.Count); foreach (COL item in COL) { lines.Add(item.ToStruct(format)); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", lines.ToArray())); writer.WriteLine("};"); writer.WriteLine(); } if (Anim.Count > 0 && !labels.Contains(AnimName)) { labels.Add(AnimName); writer.Write("GeoAnimData "); writer.Write(AnimName); writer.WriteLine("[] = {"); List <string> lines = new List <string>(Anim.Count); foreach (GeoAnimData item in Anim) { lines.Add(item.ToStruct()); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", lines.ToArray())); writer.WriteLine("};"); writer.WriteLine(); } writer.Write("LandTable "); writer.Write(Name); writer.Write(" = { LengthOfArray<int16_t>("); writer.Write(COLName); writer.Write("), "); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: writer.Write(Anim.Count > 0 ? "LengthOfArray<int16_t>(" + AnimName + ")" : "0"); writer.Write(", "); writer.Write(Flags.ToCHex()); writer.Write(", "); writer.Write(Unknown1.ToC()); writer.Write(", "); writer.Write(COLName); writer.Write(", "); writer.Write(Anim.Count > 0 ? AnimName : "NULL"); writer.Write(", "); writer.Write(TextureFileName.ToC()); writer.Write(", (NJS_TEXLIST *)"); writer.Write(TextureList.ToCHex()); writer.Write(", "); writer.Write(Unknown2.ToCHex()); writer.Write(", "); writer.Write(Unknown3.ToCHex()); break; case LandTableFormat.SA2: writer.Write(cnk.Count); writer.Write(", 0, 0, 0, 0, "); writer.Write(Unknown1.ToC()); writer.Write(", "); writer.Write(COLName); writer.Write(", "); writer.Write(Anim.Count > 0 ? AnimName : "NULL"); writer.Write(", "); writer.Write(TextureFileName.ToC()); writer.Write(", (NJS_TEXLIST *)"); writer.Write(TextureList.ToCHex()); break; } writer.WriteLine(" };"); }
public byte[] GetBytes(uint imageBase, LandTableFormat format, out uint address) { return(GetBytes(imageBase, format, new Dictionary <string, uint>(), out address)); }
static void Main(string[] args) { string[] arguments = Environment.GetCommandLineArgs(); Game game; string filename; string dir = Environment.CurrentDirectory; bool bigendian = false; if (args.Length == 0) { Console.WriteLine("USplit is a tool that lets you extract any data supported by SA Tools from any binary file."); Console.WriteLine("Usage: usplit <GAME> <FILENAME> <KEY> <TYPE> <ADDRESS> <PARAMETER1> <PARAMETER2> [-name <NAME>]\n"); Console.WriteLine("Argument description:"); Console.WriteLine("<GAME>: SA1, SADX, SA2, SA2B. Add '_b' (e.g. SADX_b) to switch to Big Endian.\n"); Console.WriteLine("<FILENAME>: The name of the binary file, e.g. sonic.exe.\n"); Console.WriteLine("<KEY>: Binary key, e.g. 400000 for sonic.exe or C900000 for SA1 STG file.\n"); Console.WriteLine("<TYPE>: One of the following:\n" + "binary <length> [hex],\nlandtable, model, basicmodel, basicdxmodel, chunkmodel, gcmodel, action, animation <NJS_OBJECT address> [shortrot],\n" + "objlist, startpos, texlist, leveltexlist, triallevellist, bosslevellist, fieldstartpos, soundlist, soundtestlist,\nnextlevellist, " + "levelclearflags, deathzone, levelrankscores, levelranktimes, endpos, levelpathlist, pathlist,\nstagelightdatalist, weldlist" + "bmitemattrlist, creditstextlist, animindexlist, storysequence, musiclist <count>,\n" + "stringarray <count> [language], skyboxscale <count>, stageselectlist <count>, animationlist <count>,\n" + "masterstringlist <count>, cutscenetext <count>, recapscreen <count>, npctext <count>\n"); Console.WriteLine("<ADDRESS>: The location of data in the file.\n"); Console.WriteLine("<PARAMETER1>: length, count, secondary address etc. depending on data type\n"); Console.WriteLine("<PARAMETER2>: 'hex' for binary to read length as hexadecimal, 'shortrot' for animation to read rotation as short\n"); Console.WriteLine("<NAME>: Output file name (optional)\n"); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); return; } //Args list: game, filename, key, type, address, [address2/count], [language], [name] switch (args[0]) { case "SA1": game = Game.SA1; break; case "SA1_b": game = Game.SA1; bigendian = true; break; case "SADX": game = Game.SADX; break; case "SADX_b": game = Game.SADX; bigendian = true; break; case "SA2": game = Game.SA2; break; case "SA2_b": game = Game.SA2; bigendian = true; break; case "SA2B": game = Game.SA2B; break; case "SA2B_b": game = Game.SA2B; bigendian = true; break; default: return; } string model_extension = ".sa1mdl"; string landtable_extension = ".sa1lvl"; ByteConverter.BigEndian = SonicRetro.SAModel.ByteConverter.BigEndian = bigendian; filename = args[1]; byte[] datafile = File.ReadAllBytes(filename); if (Path.GetExtension(filename).ToLowerInvariant() == ".prs") { datafile = FraGag.Compression.Prs.Decompress(datafile); } Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(filename)); uint imageBase = uint.Parse(args[2], NumberStyles.AllowHexSpecifier); string type = args[3]; int address = int.Parse(args[4], NumberStyles.AllowHexSpecifier); bool SA2 = game == Game.SA2 | game == Game.SA2B; ModelFormat modelfmt = ModelFormat.BasicDX; LandTableFormat landfmt = LandTableFormat.SADX; switch (game) { case Game.SA1: modelfmt = ModelFormat.Basic; landfmt = LandTableFormat.SA1; model_extension = ".sa1mdl"; landtable_extension = ".sa1lvl"; break; case Game.SADX: modelfmt = ModelFormat.BasicDX; landfmt = LandTableFormat.SADX; model_extension = ".sa1mdl"; landtable_extension = ".sa1lvl"; break; case Game.SA2: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2; model_extension = ".sa2mdl"; landtable_extension = ".sa2lvl"; break; case Game.SA2B: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2B; model_extension = ".sa2mdl"; landtable_extension = ".sa2blvl"; break; } Dictionary <string, MasterObjectListEntry> masterobjlist = new Dictionary <string, MasterObjectListEntry>(); Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >(); string fileOutputPath = dir + "\\" + address.ToString("X"); Console.WriteLine("Game: {0}, file: {1}, key: 0x{2}, splitting {3} at 0x{4}", game.ToString(), filename, imageBase.ToString("X"), type, address.ToString("X")); if (args[args.Length - 2] == "-name") { fileOutputPath = dir + "\\" + args[args.Length - 1]; Console.WriteLine("Name: {0}", args[args.Length - 1]); } switch (type) { case "landtable": new LandTable(datafile, address, imageBase, landfmt).SaveToFile(fileOutputPath + landtable_extension, landfmt); break; case "model": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + model_extension, mdl, null, null, null, null, modelfmt); } break; case "basicmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa1mdl", mdl, null, null, null, null, ModelFormat.Basic); } break; case "basicdxmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa1mdl", mdl, null, null, null, null, ModelFormat.BasicDX); } break; case "chunkmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa2mdl", mdl, null, null, null, null, ModelFormat.Chunk); } break; case "gcmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.GC, new Dictionary <int, Attach>()); ModelFile.CreateFile(fileOutputPath + ".sa2mdl", mdl, null, null, null, null, ModelFormat.GC); } break; case "action": { NJS_ACTION ani = new NJS_ACTION(datafile, address, imageBase, modelfmt, new Dictionary <int, Attach>()); ani.Animation.Save(fileOutputPath + ".saanim"); string[] mdlanis = new string[0]; NJS_OBJECT mdl = ani.Model; mdlanis = (fileOutputPath + ".saanim").Split(','); ModelFile.CreateFile(fileOutputPath + "_model" + model_extension, mdl, mdlanis, null, null, null, modelfmt); } break; case "animation": { bool shortrot_enabled = false; if (args.Length > 6 && args[6] == "shortrot") { shortrot_enabled = true; } NJS_OBJECT mdl = new NJS_OBJECT(datafile, int.Parse(args[5], NumberStyles.AllowHexSpecifier), imageBase, modelfmt, new Dictionary <int, Attach>()); new NJS_MOTION(datafile, address, imageBase, mdl.CountAnimated(), shortrot: shortrot_enabled).Save(fileOutputPath + ".saanim"); string[] mdlanis = new string[0]; mdlanis = (fileOutputPath + ".saanim").Split(','); ModelFile.CreateFile(fileOutputPath + "_model" + model_extension, mdl, mdlanis, null, null, null, modelfmt); } break; case "objlist": { ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2); foreach (ObjectListEntry obj in objs) { if (!masterobjlist.ContainsKey(obj.CodeString)) { masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj)); } if (!objnamecounts.ContainsKey(obj.CodeString)) { objnamecounts.Add(obj.CodeString, new Dictionary <string, int>() { { obj.Name, 1 } }); } else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name)) { objnamecounts[obj.CodeString].Add(obj.Name, 1); } else { objnamecounts[obj.CodeString][obj.Name]++; } } objs.Save(fileOutputPath + ".ini"); } break; case "startpos": if (SA2) { SA2StartPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); } else { SA1StartPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); } break; case "texlist": TextureList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "leveltexlist": new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "triallevellist": TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath + ".ini"); break; case "bosslevellist": BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath + ".ini"); break; case "fieldstartpos": FieldStartPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "soundtestlist": SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "musiclist": { int muscnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath + ".ini"); } break; case "soundlist": SoundList.Load(datafile, address, imageBase).Save(fileOutputPath + ".ini"); break; case "stringarray": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); Languages lang = Languages.Japanese; if (args.Length > 6) { lang = (Languages)Enum.Parse(typeof(Languages), args[6], true); } StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath + ".txt"); } break; case "nextlevellist": NextLevelList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "cutscenetext": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[] hashes); } break; case "recapscreen": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[][] hashes); } break; case "npctext": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".txt", out string[][] hashes); } break; case "levelclearflags": LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath + ".ini"); break; case "deathzone": { List <DeathZoneFlags> flags = new List <DeathZoneFlags>(); string path = Path.GetDirectoryName(fileOutputPath); List <string> hashes = new List <string>(); int num = 0; while (ByteConverter.ToUInt32(datafile, address + 4) != 0) { flags.Add(new DeathZoneFlags(datafile, address)); string file = Path.Combine(path, num++.ToString(NumberFormatInfo.InvariantInfo) + (modelfmt == ModelFormat.Chunk ? ".sa2mdl" : ".sa1mdl")); ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt, new Dictionary <int, Attach>()), null, null, null, null, modelfmt); address += 8; } flags.ToArray().Save(fileOutputPath + ".ini"); } break; case "skyboxscale": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath + ".ini"); } break; case "stageselectlist": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath + ".ini"); } break; case "levelrankscores": LevelRankScoresList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "levelranktimes": LevelRankTimesList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "endpos": SA2EndPosList.Load(datafile, address).Save(fileOutputPath + ".ini"); break; case "animationlist": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath + ".ini"); } break; case "levelpathlist": { ushort lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); while (lvlnum != 0xFFFF) { int ptr = ByteConverter.ToInt32(datafile, address + 4); if (ptr != 0) { ptr = (int)((uint)ptr - imageBase); SA1LevelAct level = new SA1LevelAct(lvlnum); string lvldir = Path.Combine(fileOutputPath, level.ToString()); PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes); } address += 8; lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); } } break; case "pathlist": { PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes); } break; case "stagelightdatalist": SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath); break; case "weldlist": WeldList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "bmitemattrlist": BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "creditstextlist": CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "animindexlist": { Directory.CreateDirectory(fileOutputPath); List <string> hashes = new List <string>(); int i = ByteConverter.ToInt16(datafile, address); while (i != -1) { new NJS_MOTION(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, ByteConverter.ToInt16(datafile, address + 2)) .Save(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); hashes.Add(i.ToString(NumberFormatInfo.InvariantInfo) + ":" + HelperFunctions.FileHash(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim")); address += 8; i = ByteConverter.ToInt16(datafile, address); } } break; case "storysequence": SA2StoryList.Load(datafile, address).Save(fileOutputPath); break; case "masterstringlist": { int cnt = int.Parse(args[5], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); for (int l = 0; l < 5; l++) { Languages lng = (Languages)l; System.Text.Encoding enc = HelperFunctions.GetEncoding(game, lng); string ld = Path.Combine(fileOutputPath, lng.ToString()); Directory.CreateDirectory(ld); int ptr = datafile.GetPointer(address, imageBase); for (int i = 0; i < cnt; i++) { int ptr2 = datafile.GetPointer(ptr, imageBase); if (ptr2 != 0) { string fn = Path.Combine(ld, $"{i}.txt"); File.WriteAllText(fn, datafile.GetCString(ptr2, enc).Replace("\n", "\r\n")); } ptr += 4; } address += 4; } } break; case "binary": { int length; if (args.Length > 6 && args[6] == "hex") { length = int.Parse(args[5], NumberStyles.AllowHexSpecifier); } else { length = int.Parse(args[5]); } byte[] bin = new byte[length]; Array.Copy(datafile, address, bin, 0, bin.Length); File.WriteAllBytes(fileOutputPath + ".bin", bin); Console.WriteLine("Length: {0} (0x{1}) bytes", length.ToString(), length.ToString("X")); } break; default: break; } }
/// <summary> /// Exports a single level, model or animation file as text. /// </summary> /// <param name="source">Source pathname.</param> /// <param name="type">Type of text conversion.</param> /// <param name="destination">Destination pathname. Leave blank to export in the same folder with a swapped extension.</param> /// <param name="basicDX">Use the SADX2004 format for Basic models.</param> public static void ConvertFileToText(string source, TextType type, string destination = "", bool basicDX = true, bool overwrite = true) { string outext = ".c"; string extension = Path.GetExtension(source); switch (extension.ToLowerInvariant()) { case ".sa2lvl": case ".sa1lvl": if (type == TextType.CStructs) { if (destination == "") { destination = Path.Combine(Path.GetDirectoryName(source), Path.GetFileNameWithoutExtension(source) + outext); } if (!overwrite && File.Exists(destination)) { while (File.Exists(destination)) { destination = destination = Path.Combine(Path.GetDirectoryName(destination), Path.GetFileNameWithoutExtension(destination) + "_" + outext); } } LandTable land = LandTable.LoadFromFile(source); List <string> labels = new List <string>() { land.Name }; using (StreamWriter sw = File.CreateText(destination)) { sw.Write("/* Sonic Adventure "); LandTableFormat fmt = land.Format; switch (land.Format) { case LandTableFormat.SA1: case LandTableFormat.SADX: if (basicDX) { sw.Write("DX"); fmt = LandTableFormat.SADX; } else { sw.Write("1"); fmt = LandTableFormat.SA1; } break; case LandTableFormat.SA2: sw.Write("2"); fmt = LandTableFormat.SA2; break; case LandTableFormat.SA2B: sw.Write("2 Battle"); fmt = LandTableFormat.SA2B; break; } sw.WriteLine(" LandTable"); sw.WriteLine(" * "); sw.WriteLine(" * Generated by DataToolbox"); sw.WriteLine(" * "); if (!string.IsNullOrEmpty(land.Description)) { sw.Write(" * Description: "); sw.WriteLine(land.Description); sw.WriteLine(" * "); } if (!string.IsNullOrEmpty(land.Author)) { sw.Write(" * Author: "); sw.WriteLine(land.Author); sw.WriteLine(" * "); } sw.WriteLine(" */"); sw.WriteLine(); land.ToStructVariables(sw, fmt, labels, null); } } break; case ".sa1mdl": case ".sa2mdl": ModelFile modelFile = new ModelFile(source); NJS_OBJECT model = modelFile.Model; List <NJS_MOTION> animations = new List <NJS_MOTION>(modelFile.Animations); if (type == TextType.CStructs) { outext = ".c"; if (destination == "") { destination = Path.Combine(Path.GetDirectoryName(source), Path.GetFileNameWithoutExtension(source) + outext); } if (!overwrite && File.Exists(destination)) { while (File.Exists(destination)) { destination = destination = Path.Combine(Path.GetDirectoryName(destination), Path.GetFileNameWithoutExtension(destination) + "_" + outext); } } using (StreamWriter sw = File.CreateText(destination)) { sw.Write("/* NINJA "); switch (modelFile.Format) { case ModelFormat.Basic: case ModelFormat.BasicDX: if (basicDX) { sw.Write("Basic (with Sonic Adventure DX additions)"); } else { sw.Write("Basic"); } break; case ModelFormat.Chunk: sw.Write("Chunk"); break; case ModelFormat.GC: sw.Write("GC"); break; } sw.WriteLine(" model"); sw.WriteLine(" * "); sw.WriteLine(" * Generated by DataToolbox"); sw.WriteLine(" * "); if (modelFile != null) { if (!string.IsNullOrEmpty(modelFile.Description)) { sw.Write(" * Description: "); sw.WriteLine(modelFile.Description); sw.WriteLine(" * "); } if (!string.IsNullOrEmpty(modelFile.Author)) { sw.Write(" * Author: "); sw.WriteLine(modelFile.Author); sw.WriteLine(" * "); } } sw.WriteLine(" */"); sw.WriteLine(); List <string> labels_m = new List <string>() { model.Name }; model.ToStructVariables(sw, basicDX, labels_m, null); foreach (NJS_MOTION anim in animations) { anim.ToStructVariables(sw); } } } else if (type == TextType.NJA) { outext = ".nja"; if (destination == "") { destination = Path.Combine(Path.GetDirectoryName(source), Path.GetFileNameWithoutExtension(source) + outext); } if (!overwrite && File.Exists(destination)) { while (File.Exists(destination)) { destination = destination = Path.Combine(Path.GetDirectoryName(destination), Path.GetFileNameWithoutExtension(destination) + "_" + outext); } } using (StreamWriter sw2 = File.CreateText(destination)) { List <string> labels_nj = new List <string>() { model.Name }; model.ToNJA(sw2, basicDX, labels_nj, null); } } break; case ".saanim": NJS_MOTION animation = NJS_MOTION.Load(source); if (type == TextType.CStructs) { outext = ".c"; if (destination == "") { destination = Path.Combine(Path.GetDirectoryName(source), Path.GetFileNameWithoutExtension(source) + outext); } if (!overwrite && File.Exists(destination)) { while (File.Exists(destination)) { destination = destination = Path.Combine(Path.GetDirectoryName(destination), Path.GetFileNameWithoutExtension(destination) + "_" + outext); } } using (StreamWriter sw = File.CreateText(destination)) { sw.WriteLine("/* NINJA Motion"); sw.WriteLine(" * "); sw.WriteLine(" * Generated by DataToolbox"); sw.WriteLine(" * "); sw.WriteLine(" */"); sw.WriteLine(); animation.ToStructVariables(sw); } } else if (type == TextType.JSON) { outext = ".json"; if (destination == "") { destination = Path.Combine(Path.GetDirectoryName(source), Path.GetFileNameWithoutExtension(source) + outext); } if (!overwrite && File.Exists(destination)) { while (File.Exists(destination)) { destination = destination = Path.Combine(Path.GetDirectoryName(destination), Path.GetFileNameWithoutExtension(destination) + "_" + outext); } } JsonSerializer js = new JsonSerializer() { Culture = System.Globalization.CultureInfo.InvariantCulture }; using (TextWriter tw = File.CreateText(destination)) using (JsonTextWriter jtw = new JsonTextWriter(tw) { Formatting = Formatting.Indented }) js.Serialize(jtw, animation); } break; } }
static bool CheckLandTable(uint address, LandTableFormat landfmt) { ByteConverter.BigEndian = BigEndian; if (address > (uint)datafile.Length - 52) { return(false); } short COLCount; short AnimCount; short ChunkCount; ushort Unknown1; uint COLAddress; uint AnimPointer; uint Texlist; uint Buffer; int ObjAddrPointer; uint ObjAddr; ModelFormat modelfmt = ModelFormat.Basic; switch (landfmt) { case LandTableFormat.SA1: modelfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: modelfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: modelfmt = ModelFormat.Chunk; break; case LandTableFormat.SA2B: modelfmt = ModelFormat.GC; break; } switch (landfmt) { case LandTableFormat.SA1: case LandTableFormat.SADX: COLCount = ByteConverter.ToInt16(datafile, (int)address); if (COLCount <= 0 || COLCount > 2048) { return(false); } AnimCount = ByteConverter.ToInt16(datafile, (int)address + 2); if (AnimCount < 0 || AnimCount > 2048) { return(false); } COLAddress = ByteConverter.ToUInt32(datafile, (int)address + 0xC); if (COLAddress < ImageBase || COLAddress == 0) { return(false); } if (COLAddress > datafile.Length - 32 + ImageBase) { return(false); } AnimPointer = ByteConverter.ToUInt32(datafile, (int)address + 0x10); if (AnimPointer != 0 && AnimPointer < ImageBase) { return(false); } if (AnimPointer > datafile.Length - 32 + ImageBase) { return(false); } Texlist = ByteConverter.ToUInt32(datafile, (int)address + 0x18); if (Texlist != 0 && Texlist < ImageBase) { return(false); } if (Texlist > datafile.Length - 32 + ImageBase) { return(false); } ObjAddrPointer = (int)(COLAddress - ImageBase) + 0x18; ObjAddr = ByteConverter.ToUInt32(datafile, ObjAddrPointer); if (ObjAddr < ImageBase) { return(false); } if (!CheckModel(ObjAddr - ImageBase, -1, modelfmt, true)) { return(false); } break; case LandTableFormat.SA2: case LandTableFormat.SA2B: COLCount = ByteConverter.ToInt16(datafile, (int)address); if (COLCount < 0) { return(false); } ChunkCount = ByteConverter.ToInt16(datafile, (int)address + 2); if (ChunkCount < -1) { return(false); } Unknown1 = ByteConverter.ToUInt16(datafile, (int)address + 4); if (Unknown1 != 65535) { return(false); } COLAddress = ByteConverter.ToUInt32(datafile, (int)address + 0x10); if (COLAddress < ImageBase) { return(false); } if (COLAddress > datafile.Length - 32 + ImageBase) { return(false); } Buffer = ByteConverter.ToUInt32(datafile, (int)address + 0x14); if (Buffer != 0) { return(false); } AnimPointer = ByteConverter.ToUInt32(datafile, (int)address + 0x18); if (AnimPointer != 0 && AnimPointer < ImageBase) { return(false); } if (AnimPointer > datafile.Length - 32 + ImageBase) { return(false); } Texlist = ByteConverter.ToUInt32(datafile, (int)address + 0x1C); if (Texlist > datafile.Length - 32 + ImageBase) { return(false); } if (Texlist == 0 || Texlist < ImageBase) { return(false); } break; } return(true); }
public static int Size(LandTableFormat format) { switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: return 0x24; case LandTableFormat.SA2: return 0x20; default: throw new ArgumentOutOfRangeException("format"); } }
public string ToStructVariables(LandTableFormat format, List<string> labels) { return ToStructVariables(format, labels, null); }
// 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); }
public string ToStructVariables(LandTableFormat format, List<string> labels, string[] textures) { StringBuilder result = new StringBuilder(); List<COL> cnk = new List<COL>(); List<COL> bas = new List<COL>(); foreach (COL item in COL) { if (item.Model.Attach is BasicAttach) bas.Add(item); else cnk.Add(item); } COL.Clear(); COL.AddRange(cnk); COL.AddRange(bas); for (int i = 0; i < COL.Count; i++) { if (!labels.Contains(COL[i].Model.Name)) { labels.Add(COL[i].Model.Name); result.AppendLine(COL[i].Model.ToStructVariables(format == LandTableFormat.SADX, labels, textures)); } } for (int i = 0; i < Anim.Count; i++) { string aniid = Anim[i].Animation.Name.MakeIdentifier(); if (!labels.Contains(Anim[i].Model.Name)) { labels.Add(Anim[i].Model.Name); result.AppendLine(Anim[i].Model.ToStructVariables(format == LandTableFormat.SADX, labels, textures)); } if (labels.Contains(aniid)) { labels.Add(aniid); result.AppendLine(Anim[i].Animation.ToStructVariables()); result.Append("NJS_ACTION action_"); result.Append(aniid); result.Append(" = { &"); result.Append(Anim[i].Model.Name); result.Append(", &"); result.Append(aniid); result.AppendLine(" };"); result.AppendLine(); } } if (!labels.Contains(COLName)) { labels.Add(COLName); result.Append("COL "); result.Append(COLName); result.AppendLine("[] = {"); List<string> lines = new List<string>(COL.Count); foreach (COL item in COL) lines.Add(item.ToStruct(format)); result.AppendLine("\t" + string.Join("," + Environment.NewLine + "\t", lines.ToArray())); result.AppendLine("};"); result.AppendLine(); } if (Anim.Count > 0 && !labels.Contains(COLName)) { labels.Add(COLName); result.Append("GeoAnimData "); result.Append(AnimName); result.AppendLine("[] = {"); List<string> lines = new List<string>(Anim.Count); foreach (GeoAnimData item in Anim) lines.Add(item.ToStruct()); result.AppendLine("\t" + string.Join("," + Environment.NewLine + "\t", lines.ToArray())); result.AppendLine("};"); result.AppendLine(); } result.Append("LandTable "); result.Append(Name); result.Append(" = { LengthOfArray("); result.Append(COLName); result.Append("), "); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: result.Append(Anim.Count > 0 ? "LengthOfArray(" + AnimName + ")" : "0"); result.Append(", "); result.Append(Flags.ToCHex()); result.Append(", "); result.Append(Unknown1.ToC()); result.Append(", "); result.Append(COLName); result.Append(", "); result.Append(Anim.Count > 0 ? AnimName : "NULL"); result.Append(", "); result.Append(TextureFileName.ToC()); result.Append(", (NJS_TEXLIST *)"); result.Append(TextureList.ToCHex()); result.Append(", "); result.Append(Unknown2.ToCHex()); result.Append(", "); result.Append(Unknown3.ToCHex()); break; case LandTableFormat.SA2: result.Append(cnk.Count); result.Append(", 0, 0, 0, 0, "); result.Append(Unknown1.ToC()); result.Append(", "); result.Append(COLName); result.Append(", "); result.Append(Anim.Count > 0 ? AnimName : "NULL"); result.Append(", "); result.Append(TextureFileName.ToC()); result.Append(", (NJS_TEXLIST *)"); result.Append(TextureList.ToCHex()); break; } result.AppendLine(" };"); return result.ToString(); }
public void SaveToFile(string filename, LandTableFormat format) { bool be = ByteConverter.BigEndian; ByteConverter.BigEndian = false; if (format == LandTableFormat.SADX) format = LandTableFormat.SA1; List<byte> file = new List<byte>(); ulong magic; switch (format) { case LandTableFormat.SA1: magic = SA1LVLVer; break; case LandTableFormat.SA2: magic = SA2LVLVer; break; default: throw new ArgumentException("Cannot save " + format + " format levels to file!", "format"); } file.AddRange(ByteConverter.GetBytes(magic)); uint addr; Dictionary<string, uint> labels = new Dictionary<string, uint>(); byte[] lvl = GetBytes(0x10, format, labels, out addr); file.AddRange(ByteConverter.GetBytes(addr + 0x10)); file.AddRange(ByteConverter.GetBytes(lvl.Length + 0x10)); file.AddRange(lvl); if (labels.Count > 0) { List<byte> chunk = new List<byte>(labels.Count * 8); int straddr = (labels.Count * 8) + 8; List<byte> strbytes = new List<byte>(); foreach (KeyValuePair<string, uint> label in labels) { chunk.AddRange(ByteConverter.GetBytes(label.Value)); chunk.AddRange(ByteConverter.GetBytes(straddr + strbytes.Count)); strbytes.AddRange(Encoding.UTF8.GetBytes(label.Key)); strbytes.Add(0); strbytes.Align(4); } chunk.AddRange(ByteConverter.GetBytes(-1L)); chunk.AddRange(strbytes); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Label)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } if (!string.IsNullOrEmpty(Author)) { List<byte> chunk = new List<byte>(Author.Length + 1); chunk.AddRange(Encoding.UTF8.GetBytes(Author)); chunk.Add(0); chunk.Align(4); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Author)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } if (!string.IsNullOrEmpty(Description)) { List<byte> chunk = new List<byte>(Description.Length + 1); chunk.AddRange(Encoding.UTF8.GetBytes(Description)); chunk.Add(0); chunk.Align(4); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Description)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } if (!string.IsNullOrEmpty(Tool)) { List<byte> chunk = new List<byte>(Tool.Length + 1); chunk.AddRange(Encoding.UTF8.GetBytes(Tool)); chunk.Add(0); chunk.Align(4); file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.Tool)); file.AddRange(ByteConverter.GetBytes(chunk.Count)); file.AddRange(chunk); } foreach (KeyValuePair<uint, byte[]> item in Metadata) { file.AddRange(ByteConverter.GetBytes(item.Key)); file.AddRange(ByteConverter.GetBytes(item.Value.Length)); file.AddRange(item.Value); } file.AddRange(ByteConverter.GetBytes((uint)ChunkTypes.End)); file.AddRange(new byte[4]); File.WriteAllBytes(filename, file.ToArray()); ByteConverter.BigEndian = be; }
public byte[] GetBytes(uint imageBase, LandTableFormat format) { uint address; return GetBytes(imageBase, format, out address); }
private void cStructsToolStripMenuItem_Click(object sender, EventArgs e) { using (SaveFileDialog sd = new SaveFileDialog() { DefaultExt = "c", Filter = "C Files|*.c" }) if (sd.ShowDialog(this) == DialogResult.OK) { LandTableFormat fmt = LevelData.geo.Format; switch (fmt) { case LandTableFormat.SA1: case LandTableFormat.SADX: using (StructExportDialog ed = new StructExportDialog() { Format = LevelData.geo.Format }) if (ed.ShowDialog(this) == DialogResult.OK) { fmt = ed.Format; } else { return; } break; } List <string> labels = new List <string>() { LevelData.geo.Name }; using (StreamWriter sw = File.CreateText(sd.FileName)) { sw.Write("/* Sonic Adventure "); switch (fmt) { case LandTableFormat.SA1: sw.Write("1"); break; case LandTableFormat.SADX: sw.Write("DX"); break; case LandTableFormat.SA2: sw.Write("2"); break; } sw.WriteLine(" LandTable"); sw.WriteLine(" * "); sw.WriteLine(" * Generated by SALVL"); sw.WriteLine(" * "); if (!string.IsNullOrEmpty(LevelData.geo.Description)) { sw.Write(" * Description: "); sw.WriteLine(LevelData.geo.Description); sw.WriteLine(" * "); } if (!string.IsNullOrEmpty(LevelData.geo.Author)) { sw.Write(" * Author: "); sw.WriteLine(LevelData.geo.Author); sw.WriteLine(" * "); } sw.WriteLine(" */"); sw.WriteLine(); string[] texnames = null; if (LevelData.leveltexs != null) { texnames = new string[LevelData.TextureBitmaps[LevelData.leveltexs].Length]; for (int i = 0; i < LevelData.TextureBitmaps[LevelData.leveltexs].Length; i++) { texnames[i] = string.Format("{0}TexName_{1}", LevelData.leveltexs, LevelData.TextureBitmaps[LevelData.leveltexs][i].Name); } sw.Write("enum {0}TexName", LevelData.leveltexs); sw.WriteLine(); sw.WriteLine("{"); sw.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", texnames)); sw.WriteLine("};"); sw.WriteLine(); } LevelData.geo.ToStructVariables(sw, fmt, labels, texnames); } } }
public LandTable(byte[] file, int address, uint imageBase, LandTableFormat format) : this(file, address, imageBase, format, new Dictionary <int, string>()) { }
public string ToStruct(LandTableFormat format) { StringBuilder result = new StringBuilder("{ "); result.Append(Bounds.ToStruct()); result.Append(", "); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: result.Append(Unknown1.ToCHex()); result.Append(", "); result.Append(Unknown2.ToCHex()); result.Append(", "); result.Append(Model != null ? "&" + Model.Name : "NULL"); result.Append(", "); result.AppendFormat(Unknown3.ToCHex()); break; case LandTableFormat.SA2: result.Append(Model != null ? "&" + Model.Name : "NULL"); result.Append(", "); result.Append(Unknown2.ToCHex()); result.Append(", "); result.Append(Unknown3.ToCHex()); break; } result.Append(", "); result.AppendFormat(Flags.ToCHex()); result.Append(" }"); return result.ToString(); }
public LandTable(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary<int, string> labels) { Format = format; if (labels.ContainsKey(address)) Name = labels[address]; else Name = "landtable_" + address.ToString("X8"); short colcnt = ByteConverter.ToInt16(file, address); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: short anicnt = ByteConverter.ToInt16(file, address + 2); Flags = ByteConverter.ToInt32(file, address + 4); Unknown1 = ByteConverter.ToSingle(file, address + 8); COL = new List<COL>(); int tmpaddr = ByteConverter.ToInt32(file, address + 0xC); if (tmpaddr != 0) { tmpaddr = (int)unchecked((uint)tmpaddr - imageBase); if (labels.ContainsKey(tmpaddr)) COLName = labels[tmpaddr]; else COLName = "collist_" + tmpaddr.ToString("X8"); for (int i = 0; i < colcnt; i++) { COL.Add(new COL(file, tmpaddr, imageBase, format, labels)); tmpaddr += SAModel.COL.Size(format); } } else COLName = "collist_" + Extensions.GenerateIdentifier(); Anim = new List<GeoAnimData>(); tmpaddr = ByteConverter.ToInt32(file, address + 0x10); if (tmpaddr != 0) { tmpaddr = (int)unchecked((uint)tmpaddr - imageBase); if (labels.ContainsKey(tmpaddr)) AnimName = labels[tmpaddr]; else AnimName = "animlist_" + tmpaddr.ToString("X8"); for (int i = 0; i < anicnt; i++) { Anim.Add(new GeoAnimData(file, tmpaddr, imageBase, format, labels)); tmpaddr += GeoAnimData.Size; } } else AnimName = "animlist_" + Extensions.GenerateIdentifier(); tmpaddr = ByteConverter.ToInt32(file, address + 0x14); if (tmpaddr != 0) { tmpaddr = (int)unchecked((uint)tmpaddr - imageBase); TextureFileName = file.GetCString(tmpaddr, Encoding.ASCII); } TextureList = ByteConverter.ToUInt32(file, address + 0x18); Unknown2 = ByteConverter.ToInt32(file, address + 0x1C); Unknown3 = ByteConverter.ToInt32(file, address + 0x20); break; case LandTableFormat.SA2: short cnkcnt = ByteConverter.ToInt16(file, address + 2); Unknown1 = ByteConverter.ToSingle(file, address + 0xC); COL = new List<COL>(); tmpaddr = ByteConverter.ToInt32(file, address + 0x10); if (tmpaddr != 0) { tmpaddr = (int)unchecked((uint)tmpaddr - imageBase); if (labels.ContainsKey(tmpaddr)) COLName = labels[tmpaddr]; else COLName = "collist_" + tmpaddr.ToString("X8"); for (int i = 0; i < colcnt; i++) { COL.Add(new COL(file, tmpaddr, imageBase, format, labels, cnkcnt < 0 ? null : (bool?)(i >= cnkcnt))); tmpaddr += SAModel.COL.Size(format); } } else COLName = "collist_" + Extensions.GenerateIdentifier(); Anim = new List<GeoAnimData>(); AnimName = "animlist_" + Extensions.GenerateIdentifier(); tmpaddr = ByteConverter.ToInt32(file, address + 0x18); if (tmpaddr != 0) { tmpaddr = (int)unchecked((uint)tmpaddr - imageBase); TextureFileName = file.GetCString(tmpaddr, Encoding.ASCII); } TextureList = ByteConverter.ToUInt32(file, address + 0x1C); break; } Metadata = new Dictionary<uint, byte[]>(); }
static void Main(string[] args) { string[] arguments = Environment.GetCommandLineArgs(); string filename; bool dx = true; string outputfile; string extension; string dir = Environment.CurrentDirectory; if (args.Length == 0) { Console.WriteLine("Struct Exporter is a tool that lets you convert level, model and animation files to C structs."); Console.WriteLine("Usage: StructExporter <filename> [output path] [-nodx]\n"); Console.WriteLine("Arguments: -nodx to output Basic models without SADX additions\n"); Console.WriteLine("Supported file types: sa1lvl, sa2lvl, sa1mdl, sa2mdl, saanim\n"); Console.WriteLine("Examples:"); Console.WriteLine("StructExporter mylevel.sa1lvl"); Console.WriteLine("StructExporter mylevel.sa1lvl D:\\mylevel.c"); Console.WriteLine("StructExporter mymodel.sa1mdl D:\\mymodel.c -nodx\n"); Console.WriteLine("You can also drag your file onto StructExporter.exe to get it converted."); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); return; } //Args list: game, filename, key, type, address, [address2/count], [language], [name] filename = args[0]; outputfile = Path.GetFileNameWithoutExtension(filename) + ".c"; if (args.Length > 1) { if (args[args.Length - 1] == "-nodx") { dx = false; } if (args[1] != "-nodx") { outputfile = args[1]; } } byte[] file = File.ReadAllBytes(filename); extension = Path.GetExtension(filename); switch (extension) { case ".sa2lvl": case ".sa1lvl": LandTable land = LandTable.LoadFromFile(filename); List <string> labels = new List <string>() { land.Name }; using (StreamWriter sw = File.CreateText(outputfile)) { sw.Write("/* Sonic Adventure "); LandTableFormat fmt = land.Format; switch (land.Format) { case LandTableFormat.SA1: case LandTableFormat.SADX: if (dx) { sw.Write("DX"); fmt = LandTableFormat.SADX; } else { sw.Write("1"); fmt = LandTableFormat.SA1; } break; case LandTableFormat.SA2: sw.Write("2"); fmt = LandTableFormat.SA2; break; case LandTableFormat.SA2B: sw.Write("2 Battle"); fmt = LandTableFormat.SA2B; break; } sw.WriteLine(" LandTable"); sw.WriteLine(" * "); sw.WriteLine(" * Generated by StructExporter"); sw.WriteLine(" * "); if (!string.IsNullOrEmpty(land.Description)) { sw.Write(" * Description: "); sw.WriteLine(land.Description); sw.WriteLine(" * "); } if (!string.IsNullOrEmpty(land.Author)) { sw.Write(" * Author: "); sw.WriteLine(land.Author); sw.WriteLine(" * "); } sw.WriteLine(" */"); sw.WriteLine(); land.ToStructVariables(sw, fmt, labels, null); } break; case ".sa1mdl": case ".sa2mdl": ModelFile modelFile = new ModelFile(filename); NJS_OBJECT model = modelFile.Model; List <NJS_MOTION> animations = new List <NJS_MOTION>(modelFile.Animations); using (StreamWriter sw = File.CreateText(outputfile)) { sw.Write("/* NINJA "); switch (modelFile.Format) { case ModelFormat.Basic: case ModelFormat.BasicDX: if (dx) { sw.Write("Basic (with Sonic Adventure DX additions)"); } else { sw.Write("Basic"); } break; case ModelFormat.Chunk: sw.Write("Chunk"); break; case ModelFormat.GC: sw.Write("GC"); break; } sw.WriteLine(" model"); sw.WriteLine(" * "); sw.WriteLine(" * Generated by StructExporter"); sw.WriteLine(" * "); if (modelFile != null) { if (!string.IsNullOrEmpty(modelFile.Description)) { sw.Write(" * Description: "); sw.WriteLine(modelFile.Description); sw.WriteLine(" * "); } if (!string.IsNullOrEmpty(modelFile.Author)) { sw.Write(" * Author: "); sw.WriteLine(modelFile.Author); sw.WriteLine(" * "); } } sw.WriteLine(" */"); sw.WriteLine(); List <string> labels_m = new List <string>() { model.Name }; model.ToStructVariables(sw, dx, labels_m, null); foreach (NJS_MOTION anim in animations) { anim.ToStructVariables(sw); } } break; case ".saanim": NJS_MOTION animation = NJS_MOTION.Load(filename); using (StreamWriter sw = File.CreateText(outputfile)) { sw.WriteLine("/* NINJA Motion"); sw.WriteLine(" * "); sw.WriteLine(" * Generated by StructExporter"); sw.WriteLine(" * "); sw.WriteLine(" */"); sw.WriteLine(); animation.ToStructVariables(sw); break; } } }
public static void ExportCPP(DllIniData IniData, Dictionary <string, bool> itemsToExport, string fileName) { using (TextWriter writer = File.CreateText(fileName)) { bool SA2 = IniData.Game == SA_Tools.SplitDLL.Game.SA2B; ModelFormat modelfmt = SA2 ? ModelFormat.Chunk : ModelFormat.BasicDX; LandTableFormat landfmt = SA2 ? LandTableFormat.SA2 : LandTableFormat.SADX; writer.WriteLine("// Generated by SA Tools DLL Mod Generator"); writer.WriteLine(); if (SA2) { writer.WriteLine("#include \"SA2ModLoader.h\""); } else { writer.WriteLine("#include \"SADXModLoader.h\""); } writer.WriteLine(); List <string> labels = new List <string>(); Dictionary <string, uint> texlists = new Dictionary <string, uint>(); foreach (KeyValuePair <string, FileTypeHash> item in IniData.Files.Where(i => itemsToExport[i.Key])) { switch (item.Value.Type) { case "landtable": LandTable tbl = LandTable.LoadFromFile(item.Key); texlists.Add(tbl.Name, tbl.TextureList); tbl.ToStructVariables(writer, landfmt, new List <string>()); labels.AddRange(tbl.GetLabels()); break; case "model": NJS_OBJECT mdl = new ModelFile(item.Key).Model; mdl.ToStructVariables(writer, modelfmt == ModelFormat.BasicDX, new List <string>()); labels.AddRange(mdl.GetLabels()); break; case "basicmodel": case "chunkmodel": case "gcmodel": mdl = new ModelFile(item.Key).Model; mdl.ToStructVariables(writer, false, new List <string>()); labels.AddRange(mdl.GetLabels()); break; case "basicdxmodel": mdl = new ModelFile(item.Key).Model; mdl.ToStructVariables(writer, true, new List <string>()); labels.AddRange(mdl.GetLabels()); break; case "animation": NJS_MOTION ani = NJS_MOTION.Load(item.Key); ani.ToStructVariables(writer); labels.Add(ani.Name); break; } writer.WriteLine(); } foreach (var item in IniData.DataItems.Where(i => itemsToExport[i.Filename])) { switch (item.Type) { case "animindexlist": { SortedDictionary <short, NJS_MOTION> anims = new SortedDictionary <short, NJS_MOTION>(); foreach (string file in Directory.GetFiles(item.Filename, "*.saanim")) { if (short.TryParse(Path.GetFileNameWithoutExtension(file), NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out short i)) { anims.Add(i, NJS_MOTION.Load(file)); } } foreach (KeyValuePair <short, NJS_MOTION> obj in anims) { obj.Value.ToStructVariables(writer); writer.WriteLine(); } writer.WriteLine("AnimationIndex {0}[] = {{", item.Export); List <string> objs = new List <string>(anims.Count); foreach (KeyValuePair <short, NJS_MOTION> obj in anims) { objs.Add($"{{ {obj.Key}, {obj.Value.ModelParts}, &{obj.Value.Name} }}"); } objs.Add("{ -1 }"); writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "charaobjectdatalist": { foreach (string file in Directory.GetFiles(item.Filename, "*.sa2mdl")) { new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>()); writer.WriteLine(); } foreach (string file in Directory.GetFiles(item.Filename, "*.saanim")) { NJS_MOTION.Load(file).ToStructVariables(writer); writer.WriteLine(); } var data = IniSerializer.Deserialize <CharaObjectData[]>(Path.Combine(item.Filename, "info.ini")); writer.WriteLine("CharaObjectData {0}[] = {{", item.Export); List <string> objs = new List <string>(data.Length); foreach (var obj in data) { objs.Add(obj.ToStruct()); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "kartspecialinfolist": { foreach (string file in Directory.GetFiles(item.Filename, "*.sa2mdl")) { new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>()); writer.WriteLine(); } var data = IniSerializer.Deserialize <KartSpecialInfo[]>(Path.Combine(item.Filename, "info.ini")); writer.WriteLine("KartSpecialInfo {0}[] = {{", item.Export); List <string> objs = new List <string>(data.Length); for (int i = 0; i < data.Length; i++) { KartSpecialInfo obj = data[i]; objs.Add(obj.ToStruct()); texlists.Add($"{item.Export}[{i}]", obj.TexList); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "kartmodelsarray": { foreach (string file in Directory.GetFiles(item.Filename, "*.sa2bmdl")) { new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>()); writer.WriteLine(); } foreach (string file in Directory.GetFiles(item.Filename, "*.sa1mdl")) { new ModelFile(file).Model.ToStructVariables(writer, false, new List <string>()); writer.WriteLine(); } var data = IniSerializer.Deserialize <CharaObjectData[]>(Path.Combine(item.Filename, "info.ini")); writer.WriteLine("KartModelsArray {0}[] = {{", item.Export); List <string> objs = new List <string>(data.Length); foreach (var obj in data) { objs.Add(obj.ToStruct()); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; case "motiontable": { foreach (string file in Directory.GetFiles(item.Filename, "*.saanim")) { NJS_MOTION.Load(file).ToStructVariables(writer); writer.WriteLine(); } var data = IniSerializer.Deserialize <MotionTableEntry[]>(Path.Combine(item.Filename, "info.ini")); writer.WriteLine("MotionTableEntry {0}[] = {{", item.Export); List <string> objs = new List <string>(data.Length); foreach (var obj in data) { objs.Add(obj.ToStruct()); } writer.WriteLine("\t" + string.Join("," + Environment.NewLine + "\t", objs.ToArray())); writer.WriteLine("};"); } break; } } writer.WriteLine("extern \"C\" __declspec(dllexport) void __cdecl Init(const char *path, const HelperFunctions &helperFunctions)"); writer.WriteLine("{"); writer.WriteLine("\tHMODULE handle = GetModuleHandle(L\"{0}\");", IniData.Name); List <string> exports = new List <string>(IniData.Items.Where(item => labels.Contains(item.Label)).Select(item => item.Export).Distinct()); foreach (KeyValuePair <string, string> item in IniData.Exports.Where(item => exports.Contains(item.Key))) { writer.WriteLine("\t{0}{1} = ({0})GetProcAddress(handle, \"{1}\");", typemap[item.Value], item.Key); } foreach (DllItemInfo item in IniData.Items.Where(item => labels.Contains(item.Label))) { if (typemap[IniData.Exports[item.Export]].EndsWith("**")) { writer.WriteLine("\t{0} = &{1};", item.ToString(), item.Label); } else { writer.WriteLine("\t*{0} = {1};", item.ToString(), item.Label); } } foreach (var item in IniData.DataItems.Where(item => itemsToExport[item.Filename])) { switch (item.Type) { case "animindexlist": case "charaobjectdatalist": case "kartspecialinfolist": case "kartmodelsarray": writer.WriteLine("\tHookExport(handle, \"{0}\", {0});", item.Export); break; default: writer.WriteLine("\t{0}{1}_exp = ({0})GetProcAddress(handle, \"{1}\");", typemap[item.Type], item.Export); writer.WriteLine("\t*{0}_exp = {0};", item.Export); break; } } if (texlists.Count > 0 && IniData.TexLists != null && IniData.TexLists.Items != null) { exports = new List <string>(IniData.TexLists.Where(item => texlists.Values.Contains(item.Key)).Select(item => item.Value.Export).Distinct()); foreach (KeyValuePair <string, string> item in IniData.Exports.Where(item => exports.Contains(item.Key))) { writer.WriteLine("\t{0}{1} = ({0})GetProcAddress(handle, \"{1}\");", typemap[item.Value], item.Key); } foreach (KeyValuePair <string, uint> item in texlists.Where(item => IniData.TexLists.ContainsKey(item.Value))) { DllTexListInfo tex = IniData.TexLists[item.Value]; string str; if (tex.Index.HasValue) { str = $"{tex.Export}[{tex.Index.Value}]"; } else { str = tex.Export; } writer.WriteLine("\t{0}.TexList = {1};", item.Key, str); } } writer.WriteLine("}"); writer.WriteLine(); writer.WriteLine("extern \"C\" __declspec(dllexport) const ModInfo {0}ModInfo = {{ ModLoaderVer }};", SA2 ? "SA2" : "SADX"); } }
public GeoAnimData(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, Attach> attaches) : this(file, address, imageBase, format, new Dictionary <int, string>(), attaches) { }
public LandTable(byte[] file, int address, uint imageBase, LandTableFormat format) : this(file, address, imageBase, format, new Dictionary<int, string>()) { }
public byte[] GetBytes(uint imageBase, LandTableFormat format, Dictionary <string, uint> labels, out uint address) { List <byte> result = new List <byte>(); byte[] tmpbyte; uint[] colmdladdrs = new uint[COL.Count]; uint tmpaddr; List <COL> cnk = new List <COL>(); List <COL> bas = new List <COL>(); foreach (COL item in COL) { if (item.Model.Attach is BasicAttach) { bas.Add(item); } else { cnk.Add(item); } } COL.Clear(); COL.AddRange(cnk); COL.AddRange(bas); for (int i = 0; i < COL.Count; i++) { if (labels.ContainsKey(COL[i].Model.Name)) { colmdladdrs[i] = labels[COL[i].Model.Name]; } else { result.Align(4); tmpbyte = COL[i].Model.GetBytes(imageBase + (uint)result.Count, format == LandTableFormat.SADX, labels, out tmpaddr); colmdladdrs[i] = tmpaddr + (uint)result.Count + imageBase; result.AddRange(tmpbyte); } } uint[] animmdladdrs = new uint[Anim.Count]; uint[] animaniaddrs = new uint[Anim.Count]; for (int i = 0; i < Anim.Count; i++) { if (labels.ContainsKey(Anim[i].Model.Name)) { animmdladdrs[i] = labels[Anim[i].Model.Name]; } else { result.Align(4); tmpbyte = Anim[i].Model.GetBytes(imageBase + (uint)result.Count, format == LandTableFormat.SADX, labels, out tmpaddr); animmdladdrs[i] = tmpaddr + (uint)result.Count + imageBase; result.AddRange(tmpbyte); } if (labels.ContainsKey(Anim[i].Animation.Name)) { animaniaddrs[i] = labels[Anim[i].Animation.Name]; } else { result.Align(4); tmpbyte = Anim[i].Animation.WriteHeader(imageBase + (uint)result.Count, animmdladdrs[i], labels, out tmpaddr); animaniaddrs[i] = tmpaddr + (uint)result.Count + imageBase; result.AddRange(tmpbyte); } } uint coladdr; if (COL.Count > 0) { if (labels.ContainsKey(COLName)) { coladdr = labels[COLName]; } else { coladdr = imageBase + (uint)result.Count; labels.Add(COLName, coladdr); for (int i = 0; i < COL.Count; i++) { result.Align(4); result.AddRange(COL[i].GetBytes(imageBase + (uint)result.Count, colmdladdrs[i], format)); } } } else { coladdr = 0; } uint animaddr; if (Anim.Count > 0) { if (labels.ContainsKey(AnimName)) { animaddr = labels[AnimName]; } else { animaddr = imageBase + (uint)result.Count; labels.Add(AnimName, animaddr); for (int i = 0; i < Anim.Count; i++) { result.Align(4); result.AddRange(Anim[i].GetBytes(imageBase + (uint)result.Count, animmdladdrs[i], animaniaddrs[i])); } } } else { animaddr = 0; } result.Align(4); uint texnameaddr = 0; if (TextureFileName != null) { texnameaddr = imageBase + (uint)result.Count; result.AddRange(Encoding.ASCII.GetBytes(TextureFileName)); result.Add(0); } result.Align(4); address = (uint)result.Count; result.AddRange(ByteConverter.GetBytes((ushort)COL.Count)); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: result.AddRange(ByteConverter.GetBytes((ushort)Anim.Count)); result.AddRange(ByteConverter.GetBytes(Flags)); result.AddRange(ByteConverter.GetBytes(Unknown1)); result.AddRange(ByteConverter.GetBytes(coladdr)); result.AddRange(ByteConverter.GetBytes(animaddr)); result.AddRange(ByteConverter.GetBytes(texnameaddr)); result.AddRange(ByteConverter.GetBytes(TextureList)); result.AddRange(ByteConverter.GetBytes(Unknown2)); result.AddRange(ByteConverter.GetBytes(Unknown3)); break; case LandTableFormat.SA2: result.AddRange(ByteConverter.GetBytes((ushort)cnk.Count)); result.AddRange(new byte[8]); // TODO: figure out what these do result.AddRange(ByteConverter.GetBytes(Unknown1)); result.AddRange(ByteConverter.GetBytes(coladdr)); result.AddRange(ByteConverter.GetBytes(animaddr)); result.AddRange(ByteConverter.GetBytes(texnameaddr)); result.AddRange(ByteConverter.GetBytes(TextureList)); break; } labels.Add(Name, address + imageBase); return(result.ToArray()); }
public COL(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary<int, string> labels) : this(file, address, imageBase, format, labels, false) { }
public byte[] GetBytes(uint imageBase, LandTableFormat format) { return(GetBytes(imageBase, format, out uint address)); }
public COL(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, string> labels, Dictionary <int, Attach> attaches) : this(file, address, imageBase, format, labels, false, attaches) { }
public byte[] GetBytes(uint imageBase, uint modelptr, LandTableFormat format) { List<byte> result = new List<byte>(); result.AddRange(Bounds.GetBytes()); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: result.AddRange(ByteConverter.GetBytes(Unknown1)); result.AddRange(ByteConverter.GetBytes(Unknown2)); result.AddRange(ByteConverter.GetBytes(modelptr)); result.AddRange(ByteConverter.GetBytes(Unknown3)); break; case LandTableFormat.SA2: result.AddRange(ByteConverter.GetBytes(modelptr)); result.AddRange(ByteConverter.GetBytes(Unknown2)); result.AddRange(ByteConverter.GetBytes(Unknown3)); break; } result.AddRange(ByteConverter.GetBytes(Flags)); return result.ToArray(); }
public COL(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, string> labels, bool?forceBasic, Dictionary <int, Attach> attaches) { Bounds = new BoundingSphere(file, address); ModelFormat mfmt = 0; switch (format) { case LandTableFormat.SA1: mfmt = ModelFormat.Basic; break; case LandTableFormat.SADX: mfmt = ModelFormat.BasicDX; break; case LandTableFormat.SA2: if (forceBasic.HasValue && forceBasic.Value) { mfmt = ModelFormat.Basic; } else { mfmt = ModelFormat.Chunk; } break; case LandTableFormat.SA2B: if (forceBasic.HasValue && forceBasic.Value) { mfmt = ModelFormat.Basic; } else { mfmt = ModelFormat.GC; } break; } switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: WidthY = ByteConverter.ToSingle(file, address + 0x10); WidthZ = ByteConverter.ToSingle(file, address + 0x14); uint tmpaddr = ByteConverter.ToUInt32(file, address + 0x18) - imageBase; Model = new NJS_OBJECT(file, (int)tmpaddr, imageBase, mfmt, labels, attaches); BlockBits = ByteConverter.ToInt32(file, address + 0x1C); Flags = ByteConverter.ToInt32(file, address + 0x20); break; case LandTableFormat.SA2: case LandTableFormat.SA2B: Flags = ByteConverter.ToInt32(file, address + 0x1C); if (!forceBasic.HasValue && Flags >= 0) { mfmt = ModelFormat.Basic; } tmpaddr = ByteConverter.ToUInt32(file, address + 0x10) - imageBase; Model = new NJS_OBJECT(file, (int)tmpaddr, imageBase, mfmt, labels, attaches); WidthZ = ByteConverter.ToInt32(file, address + 0x14); BlockBits = ByteConverter.ToInt32(file, address + 0x18); break; } }
public static int SplitFile(string datafilename, string inifilename, string projectFolderName) { try { byte[] datafile = File.ReadAllBytes(datafilename); IniData inifile = IniSerializer.Deserialize <IniData>(inifilename); if (inifile.MD5 != null && inifile.MD5.Count > 0) { string datahash = HelperFunctions.FileHash(datafile); if (!inifile.MD5.Any(h => h.Equals(datahash, StringComparison.OrdinalIgnoreCase))) { Console.WriteLine("The file {0} is not valid for use with the INI {1}.", datafilename, inifilename); return((int)ERRORVALUE.InvalidDataMapping); } } ByteConverter.BigEndian = SonicRetro.SAModel.ByteConverter.BigEndian = inifile.BigEndian; Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(datafilename)); if (inifile.Compressed) { datafile = FraGag.Compression.Prs.Decompress(datafile); } uint imageBase = HelperFunctions.SetupEXE(ref datafile) ?? inifile.ImageBase.Value; if (Path.GetExtension(datafilename).Equals(".rel", StringComparison.OrdinalIgnoreCase)) { HelperFunctions.FixRELPointers(datafile); } bool SA2 = inifile.Game == Game.SA2 | inifile.Game == Game.SA2B; ModelFormat modelfmt = 0; LandTableFormat landfmt = 0; switch (inifile.Game) { case Game.SA1: modelfmt = ModelFormat.Basic; landfmt = LandTableFormat.SA1; break; case Game.SADX: modelfmt = ModelFormat.BasicDX; landfmt = LandTableFormat.SADX; break; case Game.SA2: case Game.SA2B: modelfmt = ModelFormat.Chunk; landfmt = LandTableFormat.SA2; break; } int itemcount = 0; Dictionary <string, MasterObjectListEntry> masterobjlist = new Dictionary <string, MasterObjectListEntry>(); Dictionary <string, Dictionary <string, int> > objnamecounts = new Dictionary <string, Dictionary <string, int> >(); Stopwatch timer = new Stopwatch(); timer.Start(); foreach (KeyValuePair <string, SA_Tools.FileInfo> item in inifile.Files) { if (string.IsNullOrEmpty(item.Key)) { continue; } string filedesc = item.Key; SA_Tools.FileInfo data = item.Value; Dictionary <string, string> customProperties = data.CustomProperties; string type = data.Type; int address = data.Address; bool nohash = false; string fileOutputPath = string.Concat(projectFolderName, data.Filename); Console.WriteLine(item.Key + ": " + data.Address.ToString("X") + " → " + fileOutputPath); Directory.CreateDirectory(Path.GetDirectoryName(fileOutputPath)); switch (type) { case "landtable": new LandTable(datafile, address, imageBase, landfmt) { Description = item.Key }.SaveToFile(fileOutputPath, landfmt); break; case "model": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, modelfmt); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, modelfmt); } break; case "basicmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Basic); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.Basic); } break; case "basicdxmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.BasicDX); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.BasicDX); } break; case "chunkmodel": { NJS_OBJECT mdl = new NJS_OBJECT(datafile, address, imageBase, ModelFormat.Chunk); string[] mdlanis = new string[0]; if (customProperties.ContainsKey("animations")) { mdlanis = customProperties["animations"].Split(','); } string[] mdlmorphs = new string[0]; if (customProperties.ContainsKey("morphs")) { mdlmorphs = customProperties["morphs"].Split(','); } ModelFile.CreateFile(fileOutputPath, mdl, mdlanis, mdlmorphs, null, item.Key, null, ModelFormat.Chunk); } break; case "action": { NJS_ACTION ani = new NJS_ACTION(datafile, address, imageBase, modelfmt); ani.Animation.Name = filedesc; ani.Animation.Save(fileOutputPath); } break; case "animation": new NJS_MOTION(datafile, address, imageBase, int.Parse(customProperties["numparts"], NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo)) { Name = filedesc } .Save(fileOutputPath); break; case "objlist": { ObjectListEntry[] objs = ObjectList.Load(datafile, address, imageBase, SA2); if (inifile.MasterObjectList != null) { foreach (ObjectListEntry obj in objs) { if (!masterobjlist.ContainsKey(obj.CodeString)) { masterobjlist.Add(obj.CodeString, new MasterObjectListEntry(obj)); } if (!objnamecounts.ContainsKey(obj.CodeString)) { objnamecounts.Add(obj.CodeString, new Dictionary <string, int>() { { obj.Name, 1 } }); } else if (!objnamecounts[obj.CodeString].ContainsKey(obj.Name)) { objnamecounts[obj.CodeString].Add(obj.Name, 1); } else { objnamecounts[obj.CodeString][obj.Name]++; } } } objs.Save(fileOutputPath); } break; case "startpos": if (SA2) { SA2StartPosList.Load(datafile, address).Save(fileOutputPath); } else { SA1StartPosList.Load(datafile, address).Save(fileOutputPath); } break; case "texlist": TextureList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "leveltexlist": new LevelTextureList(datafile, address, imageBase).Save(fileOutputPath); break; case "triallevellist": TrialLevelList.Save(TrialLevelList.Load(datafile, address, imageBase), fileOutputPath); break; case "bosslevellist": BossLevelList.Save(BossLevelList.Load(datafile, address), fileOutputPath); break; case "fieldstartpos": FieldStartPosList.Load(datafile, address).Save(fileOutputPath); break; case "soundtestlist": SoundTestList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "musiclist": { int muscnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); MusicList.Load(datafile, address, imageBase, muscnt).Save(fileOutputPath); } break; case "soundlist": SoundList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "stringarray": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); Languages lang = Languages.Japanese; if (data.CustomProperties.ContainsKey("language")) { lang = (Languages)Enum.Parse(typeof(Languages), data.CustomProperties["language"], true); } StringArray.Load(datafile, address, imageBase, cnt, lang).Save(fileOutputPath); } break; case "nextlevellist": NextLevelList.Load(datafile, address).Save(fileOutputPath); break; case "cutscenetext": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); new CutsceneText(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[] hashes); data.MD5Hash = string.Join(",", hashes); nohash = true; } break; case "recapscreen": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); RecapScreenList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes); string[] hash2 = new string[hashes.Length]; for (int i = 0; i < hashes.Length; i++) { hash2[i] = string.Join(",", hashes[i]); } data.MD5Hash = string.Join(":", hash2); nohash = true; } break; case "npctext": { int cnt = int.Parse(customProperties["length"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); NPCTextList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath, out string[][] hashes); string[] hash2 = new string[hashes.Length]; for (int i = 0; i < hashes.Length; i++) { hash2[i] = string.Join(",", hashes[i]); } data.MD5Hash = string.Join(":", hash2); nohash = true; } break; case "levelclearflags": LevelClearFlagList.Save(LevelClearFlagList.Load(datafile, address), fileOutputPath); break; case "deathzone": { List <DeathZoneFlags> flags = new List <DeathZoneFlags>(); string path = Path.GetDirectoryName(fileOutputPath); List <string> hashes = new List <string>(); int num = 0; while (ByteConverter.ToUInt32(datafile, address + 4) != 0) { flags.Add(new DeathZoneFlags(datafile, address)); string file = Path.Combine(path, num++.ToString(NumberFormatInfo.InvariantInfo) + (modelfmt == ModelFormat.Chunk ? ".sa2mdl" : ".sa1mdl")); ModelFile.CreateFile(file, new NJS_OBJECT(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, modelfmt), null, null, null, null, null, modelfmt); hashes.Add(HelperFunctions.FileHash(file)); address += 8; } flags.ToArray().Save(fileOutputPath); hashes.Insert(0, HelperFunctions.FileHash(fileOutputPath)); data.MD5Hash = string.Join(",", hashes.ToArray()); nohash = true; } break; case "skyboxscale": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SkyboxScaleList.Load(datafile, address, imageBase, cnt).Save(fileOutputPath); } break; case "stageselectlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); StageSelectLevelList.Load(datafile, address, cnt).Save(fileOutputPath); } break; case "levelrankscores": LevelRankScoresList.Load(datafile, address).Save(fileOutputPath); break; case "levelranktimes": LevelRankTimesList.Load(datafile, address).Save(fileOutputPath); break; case "endpos": SA2EndPosList.Load(datafile, address).Save(fileOutputPath); break; case "animationlist": { int cnt = int.Parse(customProperties["count"], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); SA2AnimationInfoList.Load(datafile, address, cnt).Save(fileOutputPath); } break; case "levelpathlist": { List <string> hashes = new List <string>(); ushort lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); while (lvlnum != 0xFFFF) { int ptr = ByteConverter.ToInt32(datafile, address + 4); if (ptr != 0) { ptr = (int)((uint)ptr - imageBase); SA1LevelAct level = new SA1LevelAct(lvlnum); string lvldir = Path.Combine(fileOutputPath, level.ToString()); PathList.Load(datafile, ptr, imageBase).Save(lvldir, out string[] lvlhashes); hashes.Add(level.ToString() + ":" + string.Join(",", lvlhashes)); } address += 8; lvlnum = (ushort)ByteConverter.ToUInt32(datafile, address); } data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "pathlist": { PathList.Load(datafile, address, imageBase).Save(fileOutputPath, out string[] hashes); data.MD5Hash = string.Join(",", hashes.ToArray()); nohash = true; } break; case "stagelightdatalist": SA1StageLightDataList.Load(datafile, address).Save(fileOutputPath); break; case "weldlist": WeldList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "bmitemattrlist": BlackMarketItemAttributesList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "creditstextlist": CreditsTextList.Load(datafile, address, imageBase).Save(fileOutputPath); break; case "animindexlist": { Directory.CreateDirectory(fileOutputPath); List <string> hashes = new List <string>(); int i = ByteConverter.ToInt16(datafile, address); while (i != -1) { new NJS_MOTION(datafile, datafile.GetPointer(address + 4, imageBase), imageBase, ByteConverter.ToInt16(datafile, address + 2)) .Save(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim"); hashes.Add(i.ToString(NumberFormatInfo.InvariantInfo) + ":" + HelperFunctions.FileHash(fileOutputPath + "/" + i.ToString(NumberFormatInfo.InvariantInfo) + ".saanim")); address += 8; i = ByteConverter.ToInt16(datafile, address); } data.MD5Hash = string.Join("|", hashes.ToArray()); nohash = true; } break; case "storysequence": SA2StoryList.Load(datafile, address).Save(fileOutputPath); break; default: // raw binary { byte[] bin = new byte[int.Parse(customProperties["size"], NumberStyles.HexNumber)]; Array.Copy(datafile, address, bin, 0, bin.Length); File.WriteAllBytes(fileOutputPath, bin); } break; } if (!nohash) { data.MD5Hash = HelperFunctions.FileHash(fileOutputPath); } itemcount++; } if (inifile.MasterObjectList != null) { foreach (KeyValuePair <string, MasterObjectListEntry> obj in masterobjlist) { KeyValuePair <string, int> name = new KeyValuePair <string, int>(); foreach (KeyValuePair <string, int> it in objnamecounts[obj.Key]) { if (it.Value > name.Value) { name = it; } } obj.Value.Name = name.Key; obj.Value.Names = objnamecounts[obj.Key].Select((it) => it.Key).ToArray(); } string masterObjectListOutputPath = string.Concat(projectFolderName, inifile.MasterObjectList); IniSerializer.Serialize(masterobjlist, masterObjectListOutputPath); } IniSerializer.Serialize(inifile, Path.Combine(projectFolderName, Path.GetFileNameWithoutExtension(datafilename) + "_data.ini")); timer.Stop(); Console.WriteLine("Split " + itemcount + " items in " + timer.Elapsed.TotalSeconds + " seconds."); Console.WriteLine(); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); Console.WriteLine("Press any key to exit."); Console.ReadLine(); return((int)ERRORVALUE.UnhandledException); } return((int)ERRORVALUE.Success); }
public byte[] GetBytes(uint imageBase, LandTableFormat format, Dictionary<string, uint> labels, out uint address) { List<byte> result = new List<byte>(); byte[] tmpbyte; uint[] colmdladdrs = new uint[COL.Count]; uint tmpaddr; List<COL> cnk = new List<COL>(); List<COL> bas = new List<COL>(); foreach (COL item in COL) { if (item.Model.Attach is BasicAttach) bas.Add(item); else cnk.Add(item); } COL.Clear(); COL.AddRange(cnk); COL.AddRange(bas); for (int i = 0; i < COL.Count; i++) { if (labels.ContainsKey(COL[i].Model.Name)) colmdladdrs[i] = labels[COL[i].Model.Name]; else { result.Align(4); tmpbyte = COL[i].Model.GetBytes(imageBase + (uint)result.Count, format == LandTableFormat.SADX, labels, out tmpaddr); colmdladdrs[i] = tmpaddr + (uint)result.Count + imageBase; result.AddRange(tmpbyte); } } uint[] animmdladdrs = new uint[Anim.Count]; uint[] animaniaddrs = new uint[Anim.Count]; for (int i = 0; i < Anim.Count; i++) { if (labels.ContainsKey(Anim[i].Model.Name)) animmdladdrs[i] = labels[Anim[i].Model.Name]; else { result.Align(4); tmpbyte = Anim[i].Model.GetBytes(imageBase + (uint)result.Count, format == LandTableFormat.SADX, labels, out tmpaddr); animmdladdrs[i] = tmpaddr + (uint)result.Count + imageBase; result.AddRange(tmpbyte); } if (labels.ContainsKey(Anim[i].Animation.Name)) animaniaddrs[i] = labels[Anim[i].Animation.Name]; else { result.Align(4); tmpbyte = Anim[i].Animation.WriteHeader(imageBase + (uint)result.Count, animmdladdrs[i], labels, out tmpaddr); animaniaddrs[i] = tmpaddr + (uint)result.Count + imageBase; result.AddRange(tmpbyte); } } uint coladdr; if (COL.Count > 0) { if (labels.ContainsKey(COLName)) coladdr = labels[COLName]; else { coladdr = imageBase + (uint)result.Count; labels.Add(COLName, coladdr); for (int i = 0; i < COL.Count; i++) { result.Align(4); result.AddRange(COL[i].GetBytes(imageBase + (uint)result.Count, colmdladdrs[i], format)); } } } else coladdr = 0; uint animaddr; if (Anim.Count > 0) { if (labels.ContainsKey(AnimName)) animaddr = labels[AnimName]; else { animaddr = imageBase + (uint)result.Count; labels.Add(AnimName, animaddr); for (int i = 0; i < Anim.Count; i++) { result.Align(4); result.AddRange(Anim[i].GetBytes(imageBase + (uint)result.Count, animmdladdrs[i], animaniaddrs[i])); } } } else animaddr = 0; result.Align(4); uint texnameaddr = 0; if (TextureFileName != null) { texnameaddr = imageBase + (uint)result.Count; result.AddRange(Encoding.ASCII.GetBytes(TextureFileName)); result.Add(0); } result.Align(4); address = (uint)result.Count; result.AddRange(ByteConverter.GetBytes((ushort)COL.Count)); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: result.AddRange(ByteConverter.GetBytes((ushort)Anim.Count)); result.AddRange(ByteConverter.GetBytes(Flags)); result.AddRange(ByteConverter.GetBytes(Unknown1)); result.AddRange(ByteConverter.GetBytes(coladdr)); result.AddRange(ByteConverter.GetBytes(animaddr)); result.AddRange(ByteConverter.GetBytes(texnameaddr)); result.AddRange(ByteConverter.GetBytes(TextureList)); result.AddRange(ByteConverter.GetBytes(Unknown2)); result.AddRange(ByteConverter.GetBytes(Unknown3)); break; case LandTableFormat.SA2: result.AddRange(ByteConverter.GetBytes((ushort)cnk.Count)); result.AddRange(new byte[8]); // TODO: figure out what these do result.AddRange(ByteConverter.GetBytes(Unknown1)); result.AddRange(ByteConverter.GetBytes(coladdr)); result.AddRange(ByteConverter.GetBytes(animaddr)); result.AddRange(ByteConverter.GetBytes(texnameaddr)); result.AddRange(ByteConverter.GetBytes(TextureList)); break; } labels.Add(Name, address + imageBase); return result.ToArray(); }
public LandTable(byte[] file, int address, uint imageBase, LandTableFormat format, Dictionary <int, string> labels) { Format = format; if (labels.ContainsKey(address)) { Name = labels[address]; } else { Name = "landtable_" + address.ToString("X8"); } short colcnt = ByteConverter.ToInt16(file, address); switch (format) { case LandTableFormat.SA1: case LandTableFormat.SADX: short anicnt = ByteConverter.ToInt16(file, address + 2); Flags = ByteConverter.ToInt32(file, address + 4); Unknown1 = ByteConverter.ToSingle(file, address + 8); COL = new List <COL>(); int tmpaddr = ByteConverter.ToInt32(file, address + 0xC); if (tmpaddr != 0) { tmpaddr = (int)unchecked ((uint)tmpaddr - imageBase); if (labels.ContainsKey(tmpaddr)) { COLName = labels[tmpaddr]; } else { COLName = "collist_" + tmpaddr.ToString("X8"); } for (int i = 0; i < colcnt; i++) { COL.Add(new COL(file, tmpaddr, imageBase, format, labels)); tmpaddr += SAModel.COL.Size(format); } } else { COLName = "collist_" + Extensions.GenerateIdentifier(); } Anim = new List <GeoAnimData>(); tmpaddr = ByteConverter.ToInt32(file, address + 0x10); if (tmpaddr != 0) { tmpaddr = (int)unchecked ((uint)tmpaddr - imageBase); if (labels.ContainsKey(tmpaddr)) { AnimName = labels[tmpaddr]; } else { AnimName = "animlist_" + tmpaddr.ToString("X8"); } for (int i = 0; i < anicnt; i++) { Anim.Add(new GeoAnimData(file, tmpaddr, imageBase, format, labels)); tmpaddr += GeoAnimData.Size; } } else { AnimName = "animlist_" + Extensions.GenerateIdentifier(); } tmpaddr = ByteConverter.ToInt32(file, address + 0x14); if (tmpaddr != 0) { tmpaddr = (int)unchecked ((uint)tmpaddr - imageBase); TextureFileName = file.GetCString(tmpaddr, Encoding.ASCII); } TextureList = ByteConverter.ToUInt32(file, address + 0x18); Unknown2 = ByteConverter.ToInt32(file, address + 0x1C); Unknown3 = ByteConverter.ToInt32(file, address + 0x20); break; case LandTableFormat.SA2: short cnkcnt = ByteConverter.ToInt16(file, address + 2); Unknown1 = ByteConverter.ToSingle(file, address + 0xC); COL = new List <COL>(); tmpaddr = ByteConverter.ToInt32(file, address + 0x10); if (tmpaddr != 0) { tmpaddr = (int)unchecked ((uint)tmpaddr - imageBase); if (labels.ContainsKey(tmpaddr)) { COLName = labels[tmpaddr]; } else { COLName = "collist_" + tmpaddr.ToString("X8"); } for (int i = 0; i < colcnt; i++) { COL.Add(new COL(file, tmpaddr, imageBase, format, labels, cnkcnt < 0 ? null : (bool?)(i >= cnkcnt))); tmpaddr += SAModel.COL.Size(format); } } else { COLName = "collist_" + Extensions.GenerateIdentifier(); } Anim = new List <GeoAnimData>(); AnimName = "animlist_" + Extensions.GenerateIdentifier(); tmpaddr = ByteConverter.ToInt32(file, address + 0x18); if (tmpaddr != 0) { tmpaddr = (int)unchecked ((uint)tmpaddr - imageBase); TextureFileName = file.GetCString(tmpaddr, Encoding.ASCII); } TextureList = ByteConverter.ToUInt32(file, address + 0x1C); break; } Metadata = new Dictionary <uint, byte[]>(); }
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); }