static void Main(string[] args) { if (args.Length == 0) { Console.Write("Filename: "); args = new string[] { Console.ReadLine().Trim('"') }; } foreach (string filename in args) { Console.WriteLine("Splitting file {0}...", filename); byte[] fc; if (Path.GetExtension(filename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { fc = Prs.Decompress(filename); } else { fc = File.ReadAllBytes(filename); } EventIniData ini = new EventIniData() { Name = Path.GetFileNameWithoutExtension(filename) }; string path = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(filename)), Path.GetFileNameWithoutExtension(filename))).FullName; uint key; if (fc[0] == 0x81) { Console.WriteLine("File is in GC/PC format."); ByteConverter.BigEndian = true; key = 0x8125FE60; ini.Game = Game.SA2B; } else { Console.WriteLine("File is in DC format."); ByteConverter.BigEndian = false; key = 0xC600000; ini.Game = Game.SA2; } List <string> nodenames = new List <string>(); Dictionary <string, KeyValuePair <string, NJS_OBJECT> > modelfiles = new Dictionary <string, KeyValuePair <string, NJS_OBJECT> >(); int ptr = fc.GetPointer(0x20, key); if (ptr != 0) { for (int i = 0; i < (ini.Game == Game.SA2B ? 18 : 16); i++) { UpgradeInfo info = new UpgradeInfo(); info.RootNode = GetModel(fc, ptr, key, Path.Combine(path, $"Upgrade {i + 1} Root.sa2mdl"), nodenames, modelfiles); if (info.RootNode != null) { int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { info.AttachNode1 = $"object_{ptr2:X8}"; } info.Model1 = GetModel(fc, ptr + 8, key, Path.Combine(path, $"Upgrade {i + 1} Model 1.sa2mdl"), nodenames, modelfiles); ptr2 = fc.GetPointer(ptr + 0xC, key); if (ptr2 != 0) { info.AttachNode2 = $"object_{ptr2:X8}"; } info.Model2 = GetModel(fc, ptr + 0x10, key, Path.Combine(path, $"Upgrade {i + 1} Model 2.sa2mdl"), nodenames, modelfiles); } ini.Upgrades.Add(info); ptr += 0x14; } } else { Console.WriteLine("Event contains no character upgrades."); } int gcnt = ByteConverter.ToInt32(fc, 8); ptr = fc.GetPointer(0, key); if (ptr != 0) { Console.WriteLine("Event contains {0} group(s).", gcnt + 1); for (int gn = 0; gn <= gcnt; gn++) { GroupInfo info = new GroupInfo(); int ptr2 = fc.GetPointer(ptr, key); int ecnt = ByteConverter.ToInt32(fc, ptr + 4); if (ptr2 != 0) { Console.WriteLine("Group {0} contains {1} entit{2}.", gn + 1, ecnt, ecnt == 1 ? "y" : "ies"); for (int en = 0; en < ecnt; en++) { string name = GetModel(fc, ptr2, key, Path.Combine(path, $"Group {gn + 1} Entity {en + 1} Model.sa2mdl"), nodenames, modelfiles); if (name != null) { info.Entities.Add(name); } ptr2 += ini.Game == Game.SA2B ? 0x2C : 0x20; } } else { Console.WriteLine("Group {0} contains no entities.", gn + 1); } ptr2 = fc.GetPointer(ptr + 0x18, key); if (ptr2 != 0) { info.Big = GetModel(fc, ptr2, key, Path.Combine(path, $"Group {gn + 1} Big Model.sa2mdl"), nodenames, modelfiles); } ini.Groups.Add(info); ptr += 0x20; } } else { Console.WriteLine("Event contains no groups."); } ptr = fc.GetPointer(0x18, key); if (ptr != 0) { for (int i = 0; i < 93; i++) { string name = GetModel(fc, ptr, key, Path.Combine(path, $"Mech Part {i + 1}.sa2mdl"), nodenames, modelfiles); if (name != null) { ini.MechParts.Add(i, name); } ptr += 4; } } else { Console.WriteLine("Event contains no mech parts."); } ptr = fc.GetPointer(0x1C, key); if (ptr != 0) { ini.TailsTails = GetModel(fc, ptr, key, Path.Combine(path, $"Tails tails.sa2mdl"), nodenames, modelfiles); } else { Console.WriteLine("Event does not contain Tails' tails."); } foreach (var item in modelfiles) { ModelFile.CreateFile(item.Value.Key, item.Value.Value, null, null, null, null, null, ModelFormat.Chunk); ini.Files.Add(Path.GetFileName(item.Value.Key), HelperFunctions.FileHash(item.Value.Key)); } IniSerializer.Serialize(ini, Path.Combine(path, Path.ChangeExtension(Path.GetFileName(filename), ".ini"))); } }
static void Main(string[] args) { if (args.Length == 0) { Console.Write("Filename: "); args = new string[] { Console.ReadLine().Trim('"') }; } foreach (string filename in args) { Console.WriteLine("Splitting file {0}...", filename); byte[] fc; if (Path.GetExtension(filename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { fc = Prs.Decompress(filename); } else { fc = File.ReadAllBytes(filename); } EventIniData ini = new EventIniData() { Name = Path.GetFileNameWithoutExtension(filename) }; string path = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(filename)), Path.GetFileNameWithoutExtension(filename))).FullName; uint key; List <NJS_MOTION> motions = null; bool battle; if (fc[0] == 0x81) { Console.WriteLine("File is in GC/PC format."); ByteConverter.BigEndian = true; key = 0x8125FE60; ini.Game = Game.SA2B; battle = true; motions = ReadMotionFile(Path.ChangeExtension(filename, null) + "motion.bin"); ini.Motions = motions.Select(a => a?.Name).ToList(); foreach (var mtn in motions.Where(a => a != null)) { motionfiles[mtn.Name] = new MotionInfo(null, mtn); } } else { Console.WriteLine("File is in DC format."); ByteConverter.BigEndian = false; key = 0xC600000; ini.Game = Game.SA2; battle = false; } int ptr = fc.GetPointer(0x20, key); if (ptr != 0) { for (int i = 0; i < (battle ? 18 : 16); i++) { string upnam = upgradenames[i]; string chnam = upnam; switch (i) { case 0: chnam = "Sonic"; break; case 4: chnam = "Shadow"; break; case 6: chnam = "Knuckles"; break; case 12: chnam = "Rouge"; break; case 16: chnam = "Mech Tails"; break; case 17: chnam = "Mech Eggman"; break; } UpgradeInfo info = new UpgradeInfo(); info.RootNode = GetModel(fc, ptr, key, $"{chnam} Root.sa2mdl"); if (info.RootNode != null) { int ptr2 = fc.GetPointer(ptr + 4, key); if (ptr2 != 0) { info.AttachNode1 = $"object_{ptr2:X8}"; } info.Model1 = GetModel(fc, ptr + 8, key, $"{upnam} Model 1.sa2mdl"); ptr2 = fc.GetPointer(ptr + 0xC, key); if (ptr2 != 0) { info.AttachNode2 = $"object_{ptr2:X8}"; } info.Model2 = GetModel(fc, ptr + 0x10, key, $"{upnam} Model 2.sa2mdl"); } ini.Upgrades.Add(info); ptr += 0x14; } } else { Console.WriteLine("Event contains no character upgrades."); } ptr = fc.GetPointer(0x18, key); if (ptr != 0) { for (int i = 0; i < 93; i++) { string name = GetModel(fc, ptr, key, $"Mech Part {i + 1}.sa2mdl"); if (name != null) { ini.MechParts.Add(i, name); } ptr += 4; } } else { Console.WriteLine("Event contains no mech parts."); } int gcnt = ByteConverter.ToInt32(fc, 8); ptr = fc.GetPointer(0, key); if (ptr != 0) { Console.WriteLine("Event contains {0} scene(s).", gcnt + 1); for (int gn = 0; gn <= gcnt; gn++) { Directory.CreateDirectory(Path.Combine(path, $"Scene {gn + 1}")); SceneInfo scn = new SceneInfo(); int ptr2 = fc.GetPointer(ptr, key); int ecnt = ByteConverter.ToInt32(fc, ptr + 4); if (ptr2 != 0) { Console.WriteLine("Scene {0} contains {1} entit{2}.", gn + 1, ecnt, ecnt == 1 ? "y" : "ies"); for (int en = 0; en < ecnt; en++) { EntityInfo ent = new EntityInfo(); ent.Model = GetModel(fc, ptr2, key, $"Scene {gn + 1}\\Entity {en + 1} Model.sa2mdl"); if (ent.Model != null) { ent.Motion = GetMotion(fc, ptr2 + 4, key, $"Scene {gn + 1}\\Entity {en + 1} Motion.saanim", motions, modelfiles[ent.Model].Model.CountAnimated()); if (ent.Motion != null) { modelfiles[ent.Model].Motions.Add(motionfiles[ent.Motion].Filename); } ent.ShapeMotion = GetMotion(fc, ptr2 + 8, key, $"Scene {gn + 1}\\Entity {en + 1} Shape Motion.saanim", motions, modelfiles[ent.Model].Model.CountMorph()); if (ent.ShapeMotion != null) { modelfiles[ent.Model].Motions.Add(motionfiles[ent.ShapeMotion].Filename); } } if (battle) { ent.GCModel = GetGCModel(fc, ptr2 + 12, key, $"Scene {gn + 1}\\Entity {en + 1} GC Model.sa2bmdl"); ent.ShadowModel = GetModel(fc, ptr2 + 16, key, $"Scene {gn + 1}\\Entity {en + 1} Shadow Model.sa2mdl"); ent.Position = new Vertex(fc, ptr2 + 24); ent.Flags = ByteConverter.ToUInt32(fc, ptr2 + 40); } else { ent.Position = new Vertex(fc, ptr2 + 16); ent.Flags = ByteConverter.ToUInt32(fc, ptr2 + 28); } scn.Entities.Add(ent); ptr2 += ini.Game == Game.SA2B ? 0x2C : 0x20; } } else { Console.WriteLine("Scene {0} contains no entities.", gn + 1); } ptr2 = fc.GetPointer(ptr + 8, key); if (ptr2 != 0) { int cnt = ByteConverter.ToInt32(fc, ptr + 12); for (int i = 0; i < cnt; i++) { scn.CameraMotions.Add(GetMotion(fc, ptr2, key, $"Scene {gn + 1}\\Camera Motion {i + 1}.saanim", motions, 1)); ptr2 += sizeof(int); } } ptr2 = fc.GetPointer(ptr + 0x18, key); if (ptr2 != 0) { BigInfo big = new BigInfo(); big.Model = GetModel(fc, ptr2, key, $"Scene {gn + 1}\\Big Model.sa2mdl"); if (big.Model != null) { int anicnt = modelfiles[big.Model].Model.CountAnimated(); int ptr3 = fc.GetPointer(ptr2 + 4, key); if (ptr3 != 0) { int cnt = ByteConverter.ToInt32(fc, ptr2 + 8); for (int i = 0; i < cnt; i++) { big.Motions.Add(new string[] { GetMotion(fc, ptr3, key, $"Scene {gn + 1}\\Big Motion {i + 1}a.saanim", motions, anicnt), GetMotion(fc, ptr3 + 4, key, $"Scene {gn + 1}\\Big Motion {i + 1}b.saanim", motions, anicnt) }); ptr3 += 8; } } } big.Unknown = ByteConverter.ToInt32(fc, ptr2 + 12); scn.Big = big; } scn.FrameCount = ByteConverter.ToInt32(fc, ptr + 28); ini.Scenes.Add(scn); ptr += 0x20; } } else { Console.WriteLine("Event contains no scenes."); } ptr = fc.GetPointer(0x1C, key); if (ptr != 0) { ini.TailsTails = GetModel(fc, ptr, key, $"Tails' tails.sa2mdl"); } else { Console.WriteLine("Event does not contain Tails' tails."); } foreach (var item in motionfiles.Values) { string fn = item.Filename ?? $"Unknown Motion {motions.IndexOf(item.Motion)}.saanim"; string fp = Path.Combine(path, fn); item.Motion.Save(fp); ini.Files.Add(fn, HelperFunctions.FileHash(fp)); } foreach (var item in modelfiles.Values) { string fp = Path.Combine(path, item.Filename); ModelFile.CreateFile(fp, item.Model, item.Motions.ToArray(), null, null, null, item.Format); ini.Files.Add(item.Filename, HelperFunctions.FileHash(fp)); } JsonSerializer js = new JsonSerializer { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }; using (var tw = File.CreateText(Path.Combine(path, Path.ChangeExtension(Path.GetFileName(filename), ".json")))) js.Serialize(tw, ini); } }