public static void ExportINI(DllIniData IniData, Dictionary <string, bool> itemsToExport, string fileName) { string dstfol = Path.GetDirectoryName(fileName); DllIniData output = new DllIniData() { Name = IniData.Name, Game = IniData.Game, Exports = IniData.Exports, TexLists = IniData.TexLists }; List <string> labels = new List <string>(); foreach (KeyValuePair <string, FileTypeHash> item in IniData.Files.Where(i => itemsToExport[i.Key])) { Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(dstfol, item.Key))); File.Copy(item.Key, Path.Combine(dstfol, item.Key), true); switch (item.Value.Type) { case "landtable": LandTable tbl = LandTable.LoadFromFile(item.Key); labels.AddRange(tbl.GetLabels()); break; case "model": case "basicmodel": case "chunkmodel": case "gcmodel": case "basicdxmodel": NJS_OBJECT mdl = new ModelFile(item.Key).Model; labels.AddRange(mdl.GetLabels()); break; case "animation": NJS_MOTION ani = NJS_MOTION.Load(item.Key); labels.Add(ani.Name); break; } output.Files.Add(item.Key, new FileTypeHash(item.Value.Type, null)); } output.Items = new List <DllItemInfo>(IniData.Items.Where(a => labels.Contains(a.Label))); foreach (var item in IniData.DataItems.Where(i => itemsToExport[i.Filename])) { Directory.CreateDirectory(Path.Combine(dstfol, item.Filename)); CopyDirectory(new DirectoryInfo(item.Filename), Path.Combine(dstfol, item.Filename)); output.DataItems.Add(item); } IniSerializer.Serialize(output, fileName); }
private void button6_Click(object sender, EventArgs e) { using (SaveFileDialog fd = new SaveFileDialog() { DefaultExt = "ini", Filter = "INI files|*.ini", InitialDirectory = Environment.CurrentDirectory, RestoreDirectory = true }) if (fd.ShowDialog(this) == DialogResult.OK) { string dstfol = Path.GetDirectoryName(fd.FileName); DllIniData output = new DllIniData() { Name = IniData.Name, Game = IniData.Game, Exports = IniData.Exports, TexLists = IniData.TexLists, Files = new DictionaryContainer <FileTypeHash>() }; List <string> labels = new List <string>(); foreach (KeyValuePair <string, FileTypeHash> item in IniData.Files.Where((a, i) => listView1.CheckedIndices.Contains(i))) { Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(dstfol, item.Key))); File.Copy(item.Key, Path.Combine(dstfol, item.Key), true); switch (item.Value.Type) { case "landtable": LandTable tbl = LandTable.LoadFromFile(item.Key); labels.AddRange(tbl.GetLabels()); break; case "model": case "basicmodel": case "chunkmodel": case "basicdxmodel": NJS_OBJECT mdl = new ModelFile(item.Key).Model; labels.AddRange(mdl.GetLabels()); break; case "animation": Animation ani = Animation.Load(item.Key); labels.Add(ani.Name); break; } output.Files.Add(item.Key, new FileTypeHash(item.Value.Type, null)); } output.Items = new List <DllItemInfo>(IniData.Items.Where(a => labels.Contains(a.Label))); IniSerializer.Serialize(output, fd.FileName); } }
/// <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 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; } } }
private void LoadFile(string filename) { loaded = false; UseWaitCursor = true; Enabled = false; LevelData.leveltexs = null; cam = new EditorCamera(EditorOptions.RenderDrawDistance); if (LandTable.CheckLevelFile(filename)) { LevelData.geo = LandTable.LoadFromFile(filename); } else { byte[] file = File.ReadAllBytes(filename); if (Path.GetExtension(filename).Equals(".prs", StringComparison.OrdinalIgnoreCase)) { file = FraGag.Compression.Prs.Decompress(file); } using (LevelFileDialog dlg = new LevelFileDialog()) { dlg.ShowDialog(this); LevelData.geo = new LandTable(file, (int)dlg.NumericUpDown1.Value, (uint)dlg.numericUpDown2.Value, (LandTableFormat)dlg.comboBox2.SelectedIndex); } } LevelData.ClearLevelItems(); for (int i = 0; i < LevelData.geo.COL.Count; i++) { LevelData.AddLevelItem((new LevelItem(LevelData.geo.COL[i], i, selectedItems))); } LevelData.TextureBitmaps = new Dictionary <string, BMPInfo[]>(); LevelData.Textures = new Dictionary <string, Texture[]>(); using (OpenFileDialog a = new OpenFileDialog() { DefaultExt = "pvm", Filter = "Texture Files|*.pvm;*.gvm;*.prs" }) { if (!string.IsNullOrEmpty(LevelData.geo.TextureFileName)) { a.FileName = LevelData.geo.TextureFileName + ".pvm"; } else { a.FileName = string.Empty; } if (a.ShowDialog(this) == DialogResult.OK) { BMPInfo[] TexBmps = TextureArchive.GetTextures(a.FileName); Texture[] texs = new Texture[TexBmps.Length]; for (int j = 0; j < TexBmps.Length; j++) { texs[j] = TexBmps[j].Image.ToTexture(d3ddevice); } string texname = Path.GetFileNameWithoutExtension(a.FileName); if (!LevelData.TextureBitmaps.ContainsKey(texname)) { LevelData.TextureBitmaps.Add(texname, TexBmps); } if (!LevelData.Textures.ContainsKey(texname)) { LevelData.Textures.Add(texname, texs); } LevelData.leveltexs = texname; } } loaded = true; transformGizmo = new TransformGizmo(); gizmoSpaceComboBox.Enabled = false; gizmoSpaceComboBox.SelectedIndex = 0; clearLevelToolStripMenuItem.Enabled = LevelData.geo != null; calculateAllBoundsToolStripMenuItem.Enabled = LevelData.geo != null; statsToolStripMenuItem.Enabled = LevelData.geo != null; selectedItems.SelectionChanged += SelectionChanged; UseWaitCursor = false; Enabled = editInfoToolStripMenuItem.Enabled = saveToolStripMenuItem.Enabled = exportToolStripMenuItem.Enabled = importToolStripMenuItem.Enabled = true; DrawLevel(); }
static void Main(string[] args) { string filename; if (args.Length > 0) { filename = args[0]; Console.WriteLine("File: {0}", filename); } else { Console.Write("File: "); filename = Console.ReadLine(); } LandTable level = LandTable.LoadFromFile(filename); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { if ((col.SurfaceFlags & SurfaceFlags.Visible) == SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SurfaceFlags.Visible; newcol.Model = new NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; string newname = basatt.Name + "_cnk"; if (visitedAttaches.ContainsKey(newname)) { newcol.Model.Attach = visitedAttaches[newname]; } else { ChunkAttach cnkatt = new ChunkAttach(true, true) { Name = basatt.Name + "_cnk", Bounds = basatt.Bounds }; visitedAttaches[newname] = cnkatt; newcol.Model.Attach = cnkatt; VertexChunk vcnk; bool hasnormal = basatt.Normal?.Length > 0; bool hasvcolor = basatt.Mesh.Any(a => a.VColor != null); if (hasvcolor) { vcnk = new VertexChunk(ChunkType.Vertex_VertexDiffuse8); } else if (hasnormal) { vcnk = new VertexChunk(ChunkType.Vertex_VertexNormal); } else { vcnk = new VertexChunk(ChunkType.Vertex_Vertex); } List <CachedVertex> cache = new List <CachedVertex>(basatt.Vertex.Length); List <List <Strip> > strips = new List <List <Strip> >(); List <List <List <UV> > > uvs = new List <List <List <UV> > >(); foreach (NJS_MESHSET mesh in basatt.Mesh) { List <Strip> polys = new List <Strip>(); List <List <UV> > us = null; bool hasUV = mesh.UV != null; bool hasVColor = mesh.VColor != null; int currentstriptotal = 0; switch (mesh.PolyType) { case Basic_PolyType.Triangles: { List <ushort> tris = new List <ushort>(); Dictionary <ushort, UV> uvmap = new Dictionary <ushort, UV>(); foreach (Poly poly in mesh.Poly) { for (int i = 0; i < 3; i++) { ushort ind = (ushort)cache.AddUnique(new CachedVertex( basatt.Vertex[poly.Indexes[i]], basatt.Normal[poly.Indexes[i]], hasVColor ? mesh.VColor[currentstriptotal] : Color.White, mesh.UV?[currentstriptotal])); if (hasUV) { uvmap[ind] = mesh.UV[currentstriptotal]; } ++currentstriptotal; tris.Add(ind); } } if (hasUV) { us = new List <List <UV> >(); } nvStripifier.GenerateStrips(tris.ToArray(), out var primitiveGroups); // Add strips for (var i = 0; i < primitiveGroups.Length; i++) { var primitiveGroup = primitiveGroups[i]; System.Diagnostics.Debug.Assert(primitiveGroup.Type == PrimitiveType.TriangleStrip); var stripIndices = new ushort[primitiveGroup.Indices.Length]; List <UV> stripuv = new List <UV>(); for (var j = 0; j < primitiveGroup.Indices.Length; j++) { var vertexIndex = primitiveGroup.Indices[j]; stripIndices[j] = vertexIndex; if (hasUV) { stripuv.Add(uvmap[vertexIndex]); } } polys.Add(new Strip(stripIndices, false)); if (hasUV) { us.Add(stripuv); } } } break; case Basic_PolyType.Quads: { List <ushort> tris = new List <ushort>(); Dictionary <ushort, UV> uvmap = new Dictionary <ushort, UV>(); foreach (Poly poly in mesh.Poly) { ushort[] quad = new ushort[4]; for (int i = 0; i < 4; i++) { ushort ind = (ushort)cache.AddUnique(new CachedVertex( basatt.Vertex[poly.Indexes[i]], basatt.Normal[poly.Indexes[i]], hasVColor ? mesh.VColor[currentstriptotal] : Color.White, mesh.UV?[currentstriptotal])); if (hasUV) { uvmap[ind] = mesh.UV[currentstriptotal]; } ++currentstriptotal; quad[i] = ind; } tris.Add(quad[0]); tris.Add(quad[1]); tris.Add(quad[2]); tris.Add(quad[2]); tris.Add(quad[1]); tris.Add(quad[3]); } if (hasUV) { us = new List <List <UV> >(); } nvStripifier.GenerateStrips(tris.ToArray(), out var primitiveGroups); // Add strips for (var i = 0; i < primitiveGroups.Length; i++) { var primitiveGroup = primitiveGroups[i]; System.Diagnostics.Debug.Assert(primitiveGroup.Type == PrimitiveType.TriangleStrip); var stripIndices = new ushort[primitiveGroup.Indices.Length]; List <UV> stripuv = new List <UV>(); for (var j = 0; j < primitiveGroup.Indices.Length; j++) { var vertexIndex = primitiveGroup.Indices[j]; stripIndices[j] = vertexIndex; if (hasUV) { stripuv.Add(uvmap[vertexIndex]); } } polys.Add(new Strip(stripIndices, false)); if (hasUV) { us.Add(stripuv); } } } break; case Basic_PolyType.NPoly: case Basic_PolyType.Strips: if (hasUV) { us = new List <List <UV> >(); } foreach (Strip poly in mesh.Poly.Cast <Strip>()) { List <UV> stripuv = new List <UV>(); ushort[] inds = (ushort[])poly.Indexes.Clone(); for (int i = 0; i < poly.Indexes.Length; i++) { inds[i] = (ushort)cache.AddUnique(new CachedVertex( basatt.Vertex[poly.Indexes[i]], basatt.Normal[poly.Indexes[i]], hasVColor ? mesh.VColor[currentstriptotal] : Color.White)); if (hasUV) { stripuv.Add(mesh.UV[currentstriptotal]); } ++currentstriptotal; } polys.Add(new Strip(inds, poly.Reversed)); if (hasUV) { us.Add(stripuv); } } break; } strips.Add(polys); uvs.Add(us); } foreach (var item in cache) { vcnk.Vertices.Add(item.vertex); if (hasnormal) { vcnk.Normals.Add(item.normal); } if (hasvcolor) { vcnk.Diffuse.Add(item.color); } } vcnk.VertexCount = (ushort)cache.Count; switch (vcnk.Type) { case ChunkType.Vertex_Vertex: vcnk.Size = (ushort)(vcnk.VertexCount * 3 + 1); break; case ChunkType.Vertex_VertexDiffuse8: vcnk.Size = (ushort)(vcnk.VertexCount * 4 + 1); break; case ChunkType.Vertex_VertexNormal: vcnk.Size = (ushort)(vcnk.VertexCount * 6 + 1); break; case ChunkType.Vertex_VertexNormalDiffuse8: vcnk.Size = (ushort)(vcnk.VertexCount * 7 + 1); break; } cnkatt.Vertex.Add(vcnk); for (int i = 0; i < basatt.Mesh.Count; i++) { NJS_MESHSET mesh = basatt.Mesh[i]; NJS_MATERIAL mat = null; if (basatt.Material != null && mesh.MaterialID < basatt.Material.Count) { mat = basatt.Material[mesh.MaterialID]; cnkatt.Poly.Add(new PolyChunkTinyTextureID() { ClampU = mat.ClampU, ClampV = mat.ClampV, FilterMode = mat.FilterMode, FlipU = mat.FlipU, FlipV = mat.FlipV, SuperSample = mat.SuperSample, TextureID = (ushort)mat.TextureID }); cnkatt.Poly.Add(new PolyChunkMaterial() { SourceAlpha = mat.SourceAlpha, DestinationAlpha = mat.DestinationAlpha, Diffuse = mat.DiffuseColor, Specular = mat.SpecularColor, SpecularExponent = (byte)mat.Exponent }); } PolyChunkStrip strip; if (mesh.UV != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripUVN); } else { strip = new PolyChunkStrip(ChunkType.Strip_Strip); } if (mat != null) { strip.IgnoreLight = mat.IgnoreLighting; strip.IgnoreSpecular = mat.IgnoreSpecular; strip.UseAlpha = mat.UseAlpha; strip.DoubleSide = mat.DoubleSided; strip.FlatShading = mat.FlatShading; strip.EnvironmentMapping = mat.EnvironmentMap; } for (int i1 = 0; i1 < strips[i].Count; i1++) { Strip item = strips[i][i1]; UV[] uv2 = null; if (mesh.UV != null) { uv2 = uvs[i][i1].ToArray(); } strip.Strips.Add(new PolyChunkStrip.Strip(item.Reversed, item.Indexes, uv2, null)); } cnkatt.Poly.Add(strip); } } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: Vertex[] VertexBuffer = new Vertex[0]; Vertex[] NormalBuffer = new Vertex[0]; Color?[] ColorBuffer = new Color?[0]; foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { ChunkAttach cnkatt = (ChunkAttach)col.Model.Attach; BasicAttach basatt = new BasicAttach() { Name = cnkatt.Name, Bounds = cnkatt.Bounds }; if (cnkatt.Vertex != null) { foreach (VertexChunk chunk in cnkatt.Vertex) { if (VertexBuffer.Length < chunk.IndexOffset + chunk.VertexCount) { Array.Resize(ref VertexBuffer, chunk.IndexOffset + chunk.VertexCount); Array.Resize(ref NormalBuffer, chunk.IndexOffset + chunk.VertexCount); Array.Resize(ref ColorBuffer, chunk.IndexOffset + chunk.VertexCount); } Array.Copy(chunk.Vertices.ToArray(), 0, VertexBuffer, chunk.IndexOffset, chunk.Vertices.Count); Array.Copy(chunk.Normals.ToArray(), 0, NormalBuffer, chunk.IndexOffset, chunk.Normals.Count); if (chunk.Diffuse.Count > 0) { Array.Copy(chunk.Diffuse.Cast <Color?>().ToArray(), 0, ColorBuffer, chunk.IndexOffset, chunk.Diffuse.Count); } } } NJS_MATERIAL material = new NJS_MATERIAL() { UseTexture = true }; int minVtx = int.MaxValue; int maxVtx = int.MinValue; foreach (PolyChunk chunk in cnkatt.Poly) { switch (chunk.Type) { case ChunkType.Bits_BlendAlpha: { PolyChunkBitsBlendAlpha c2 = (PolyChunkBitsBlendAlpha)chunk; material.SourceAlpha = c2.SourceAlpha; material.DestinationAlpha = c2.DestinationAlpha; } break; case ChunkType.Bits_MipmapDAdjust: break; case ChunkType.Bits_SpecularExponent: material.Exponent = ((PolyChunkBitsSpecularExponent)chunk).SpecularExponent; break; case ChunkType.Tiny_TextureID: case ChunkType.Tiny_TextureID2: { PolyChunkTinyTextureID c2 = (PolyChunkTinyTextureID)chunk; material.ClampU = c2.ClampU; material.ClampV = c2.ClampV; material.FilterMode = c2.FilterMode; material.FlipU = c2.FlipU; material.FlipV = c2.FlipV; material.SuperSample = c2.SuperSample; material.TextureID = c2.TextureID; } break; case ChunkType.Material_Diffuse: case ChunkType.Material_Ambient: case ChunkType.Material_DiffuseAmbient: case ChunkType.Material_Specular: case ChunkType.Material_DiffuseSpecular: case ChunkType.Material_AmbientSpecular: case ChunkType.Material_DiffuseAmbientSpecular: case ChunkType.Material_Diffuse2: case ChunkType.Material_Ambient2: case ChunkType.Material_DiffuseAmbient2: case ChunkType.Material_Specular2: case ChunkType.Material_DiffuseSpecular2: case ChunkType.Material_AmbientSpecular2: case ChunkType.Material_DiffuseAmbientSpecular2: { PolyChunkMaterial c2 = (PolyChunkMaterial)chunk; material.SourceAlpha = c2.SourceAlpha; material.DestinationAlpha = c2.DestinationAlpha; if (c2.Diffuse.HasValue) { material.DiffuseColor = c2.Diffuse.Value; } if (c2.Specular.HasValue) { material.SpecularColor = c2.Specular.Value; material.Exponent = c2.SpecularExponent; } } break; case ChunkType.Strip_Strip: case ChunkType.Strip_StripUVN: case ChunkType.Strip_StripUVH: case ChunkType.Strip_StripNormal: case ChunkType.Strip_StripUVNNormal: case ChunkType.Strip_StripUVHNormal: case ChunkType.Strip_StripColor: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: case ChunkType.Strip_Strip2: case ChunkType.Strip_StripUVN2: case ChunkType.Strip_StripUVH2: { PolyChunkStrip c2 = (PolyChunkStrip)chunk; material.DoubleSided = c2.DoubleSide; material.EnvironmentMap = c2.EnvironmentMapping; material.FlatShading = c2.FlatShading; material.IgnoreLighting = c2.IgnoreLight; material.IgnoreSpecular = c2.IgnoreSpecular; material.UseAlpha = c2.UseAlpha; bool hasVColor = false; switch (chunk.Type) { case ChunkType.Strip_StripColor: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: hasVColor = true; break; } bool hasUV = false; switch (chunk.Type) { case ChunkType.Strip_StripUVN: case ChunkType.Strip_StripUVH: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: case ChunkType.Strip_StripUVN2: case ChunkType.Strip_StripUVH2: hasUV = true; break; } bool hasVertVColor = false; if (!hasVColor && c2.Strips.All(a => a.Indexes.All(b => ColorBuffer[b].HasValue))) { hasVertVColor = true; } List <Strip> strips = new List <Strip>(c2.StripCount); List <UV> uvs = hasUV ? new List <UV>() : null; List <Color> vcolors = hasVColor || hasVertVColor ? new List <Color>() : null; foreach (PolyChunkStrip.Strip strip in c2.Strips) { minVtx = Math.Min(minVtx, strip.Indexes.Min()); maxVtx = Math.Max(maxVtx, strip.Indexes.Max()); strips.Add(new Strip((ushort[])strip.Indexes.Clone(), strip.Reversed)); if (hasUV) { uvs.AddRange(strip.UVs); } if (hasVColor) { vcolors.AddRange(strip.VColors); } else if (hasVertVColor) { foreach (short i in strip.Indexes) { vcolors.Add(ColorBuffer[i].Value); } } } NJS_MESHSET mesh = new NJS_MESHSET(strips.ToArray(), false, hasUV, hasVColor || hasVertVColor); if (hasUV) { uvs.CopyTo(mesh.UV); } if (hasVColor || hasVertVColor) { vcolors.CopyTo(mesh.VColor); } mesh.MaterialID = (ushort)basatt.Material.Count; basatt.Mesh.Add(mesh); basatt.Material.Add(material); material = new NJS_MATERIAL(material.GetBytes(), 0); } break; } } int numVtx = maxVtx - minVtx + 1; basatt.ResizeVertexes(numVtx); Array.Copy(VertexBuffer, minVtx, basatt.Vertex, 0, numVtx); Array.Copy(NormalBuffer, minVtx, basatt.Normal, 0, numVtx); foreach (NJS_MESHSET mesh in basatt.Mesh) { foreach (Poly poly in mesh.Poly) { for (int i = 0; i < poly.Indexes.Length; i++) { poly.Indexes[i] = (ushort)(poly.Indexes[i] - minVtx); } } } col.Model.Attach = basatt; } level.Anim = new List <GeoAnimData>(); level.Flags = 8; // set LandTable to use PVM/GVM level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
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"); } }
static void Main(string[] args) { Environment.CurrentDirectory = @"C:\SONICADVENTUREDX\Projects\ECPort"; List <BMPInfo> textures = new List <BMPInfo>(TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\BEACH01.PVM")); LandTable landTable = LandTable.LoadFromFile(@"Levels\Emerald Coast\Act 1\LandTable.sa1lvl"); texmap = new Dictionary <int, int>(); BMPInfo[] newtexs = TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\BEACH03.PVM"); for (int i = 0; i < newtexs.Length; i++) { BMPInfo found = textures.FirstOrDefault(a => a.Name.Equals(newtexs[i].Name)); if (found == null) { texmap[i] = textures.Count; textures.Add(newtexs[i]); } else { texmap[i] = textures.IndexOf(found); } } foreach (COL col in LandTable.LoadFromFile(@"Levels\Emerald Coast\Act 3\LandTable.sa1lvl").COL) { foreach (NJS_MATERIAL mat in ((BasicAttach)col.Model.Attach).Material) { mat.TextureID = texmap[mat.TextureID]; } landTable.COL.Add(col); } texmap = new Dictionary <int, int>(); newtexs = TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\BEACH02.PVM"); for (int i = 0; i < newtexs.Length; i++) { BMPInfo found = textures.FirstOrDefault(a => a.Name.Equals(newtexs[i].Name)); if (found == null) { texmap[i] = textures.Count; textures.Add(newtexs[i]); } else { texmap[i] = textures.IndexOf(found); } } foreach (COL col in LandTable.LoadFromFile(@"Levels\Emerald Coast\Act 2\LandTable.sa1lvl").COL) { col.Bounds.Center.Z -= 2000; col.Model.Position.Z -= 2000; foreach (NJS_MATERIAL mat in ((BasicAttach)col.Model.Attach).Material) { mat.TextureID = texmap[mat.TextureID]; } landTable.COL.Add(col); } texmap = new Dictionary <int, int>(); newtexs = TextureArchive.GetTextures(@"C:\SONICADVENTUREDX\system\OBJ_BEACH.PVM"); for (int i = 0; i < newtexs.Length; i++) { BMPInfo found = textures.FirstOrDefault(a => a.Name.Equals(newtexs[i].Name)); if (found == null) { texmap[i] = textures.Count; textures.Add(newtexs[i]); } else { texmap[i] = textures.IndexOf(found); } } PAKFile pak = new PAKFile(); List <byte> inf = new List <byte>(); string filenoext = "beach01"; string longdir = "..\\..\\..\\sonic2\\resource\\gd_pc\\prs\\" + filenoext; using (System.Windows.Forms.Panel panel = new System.Windows.Forms.Panel()) using (Direct3D d3d = new Direct3D()) using (Device dev = new Device(d3d, 0, DeviceType.Hardware, panel.Handle, CreateFlags.HardwareVertexProcessing, new PresentParameters(640, 480))) { for (int i = 0; i < textures.Count; i++) { using (Texture tex = textures[i].Image.ToTexture(dev)) using (DataStream str = Surface.ToStream(tex.GetSurfaceLevel(0), ImageFileFormat.Dds)) using (MemoryStream ms = new MemoryStream()) { str.CopyTo(ms); pak.Files.Add(new PAKFile.File(filenoext + '\\' + Path.ChangeExtension(textures[i].Name, ".dds"), longdir + '\\' + Path.ChangeExtension(textures[i].Name, ".dds"), ms.ToArray())); } int infsz = inf.Count; inf.AddRange(Encoding.ASCII.GetBytes(Path.ChangeExtension(textures[i].Name, null))); inf.AddRange(new byte[0x1C - (inf.Count - infsz)]); inf.AddRange(BitConverter.GetBytes(i + 200)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(textures[i].Image.Width)); inf.AddRange(BitConverter.GetBytes(textures[i].Image.Height)); inf.AddRange(BitConverter.GetBytes(0)); inf.AddRange(BitConverter.GetBytes(0x80000000)); } } pak.Files.Insert(0, new PAKFile.File(filenoext + '\\' + filenoext + ".inf", longdir + '\\' + filenoext + ".inf", inf.ToArray())); pak.Save(@"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\gd_PC\PRS\beach01.pak"); List <COL> newcollist = new List <COL>(); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); foreach (COL col in landTable.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { ConvertCOL(newcollist, visitedAttaches, col); } landTable.COL = newcollist; Console.WriteLine("Loading Object Definitions:"); Console.WriteLine("Parsing..."); LevelData.ObjDefs = new List <ObjectDefinition>(); Dictionary <string, ObjectData> objdefini = IniSerializer.Deserialize <Dictionary <string, ObjectData> >("objdefs.ini"); List <ObjectData> objectErrors = new List <ObjectData>(); ObjectListEntry[] objlstini = ObjectList.Load(@"Levels\Emerald Coast\Object List.ini", false); Directory.CreateDirectory("dllcache").Attributes |= FileAttributes.Hidden; List <KeyValuePair <string, string> > compileErrors = new List <KeyValuePair <string, string> >(); for (int ID = 0; ID < objlstini.Length; ID++) { string codeaddr = objlstini[ID].CodeString; if (!objdefini.ContainsKey(codeaddr)) { codeaddr = "0"; } ObjectData defgroup = objdefini[codeaddr]; ObjectDefinition def; if (!string.IsNullOrEmpty(defgroup.CodeFile)) { Console.WriteLine("Compiling: " + defgroup.CodeFile); def = CompileObjectDefinition(defgroup, out bool errorOccured, out string errorText); if (errorOccured) { KeyValuePair <string, string> errorValue = new KeyValuePair <string, string>( defgroup.CodeFile, errorText); compileErrors.Add(errorValue); } } else { def = new DefaultObjectDefinition(); } LevelData.ObjDefs.Add(def); // The only reason .Model is checked for null is for objects that don't yet have any // models defined for them. It would be annoying seeing that error all the time! if (string.IsNullOrEmpty(defgroup.CodeFile) && !string.IsNullOrEmpty(defgroup.Model)) { Console.WriteLine("Loading: " + defgroup.Model); // Otherwise, if the model file doesn't exist and/or no texture file is defined, // load the "default object" instead ("?"). if (!File.Exists(defgroup.Model)) { ObjectData error = new ObjectData { Name = defgroup.Name, Model = defgroup.Model, Texture = defgroup.Texture }; objectErrors.Add(error); defgroup.Model = null; } } def.Init(defgroup, objlstini[ID].Name); def.SetInternalName(objlstini[ID].Name); } // Checks if there have been any errors added to the error list and does its thing // This thing is a mess. If anyone can think of a cleaner way to do this, be my guest. if (objectErrors.Count > 0) { int count = objectErrors.Count; List <string> errorStrings = new List <string> { "The following objects failed to load:" }; foreach (ObjectData o in objectErrors) { bool texEmpty = string.IsNullOrEmpty(o.Texture); bool texExists = (!string.IsNullOrEmpty(o.Texture) && LevelData.Textures.ContainsKey(o.Texture)); errorStrings.Add(""); errorStrings.Add("Object:\t\t" + o.Name); errorStrings.Add("\tModel:"); errorStrings.Add("\t\tName:\t" + o.Model); errorStrings.Add("\t\tExists:\t" + File.Exists(o.Model)); errorStrings.Add("\tTexture:"); errorStrings.Add("\t\tName:\t" + ((texEmpty) ? "(N/A)" : o.Texture)); errorStrings.Add("\t\tExists:\t" + texExists); } // TODO: Proper logging. Who knows where this file may end up File.WriteAllLines("SADXLVL2.log", errorStrings.ToArray()); } // Loading SET Layout Console.WriteLine("Loading SET items", "Initializing..."); List <SETItem> setlist = new List <SETItem>(); SonicRetro.SAModel.SAEditorCommon.UI.EditorItemSelection selection = new SonicRetro.SAModel.SAEditorCommon.UI.EditorItemSelection(); if (LevelData.ObjDefs.Count > 0) { string setstr = @"C:\SONICADVENTUREDX\Projects\ECPort\system\SET0100S.BIN"; if (File.Exists(setstr)) { Console.WriteLine("SET: " + setstr.Replace(Environment.CurrentDirectory, "")); setlist = SETItem.Load(setstr, selection); } setstr = @"C:\SONICADVENTUREDX\Projects\ECPort\system\SET0102B.BIN"; if (File.Exists(setstr)) { Console.WriteLine("SET: " + setstr.Replace(Environment.CurrentDirectory, "")); setlist.AddRange(SETItem.Load(setstr, selection)); } setstr = @"C:\SONICADVENTUREDX\Projects\ECPort\system\SET0101S.BIN"; if (File.Exists(setstr)) { Console.WriteLine("SET: " + setstr.Replace(Environment.CurrentDirectory, "")); List <SETItem> newlist = SETItem.Load(setstr, selection); foreach (SETItem item in newlist) { item.Position.Z -= 2000; } setlist.AddRange(newlist); } } MatrixStack transform = new MatrixStack(); List <SETItem> add = new List <SETItem>(); List <SETItem> del = new List <SETItem>(); List <PalmtreeData> trees = new List <PalmtreeData>(); foreach (SETItem item in setlist) { switch (item.ID) { case 0xD: // item box item.ID = 0xA; item.Scale.X = itemboxmap[(int)item.Scale.X]; break; case 0x15: // ring group to rings for (int i = 0; i < Math.Min(item.Scale.X + 1, 8); i++) { if (item.Scale.Z == 1) // circle { double v4 = i * 360.0; Vector3 v7 = new Vector3( ObjectHelper.NJSin((int)(v4 / item.Scale.X * 65536.0 * 0.002777777777777778)) * item.Scale.Y, 0, ObjectHelper.NJCos((int)(v4 / item.Scale.X * 65536.0 * 0.002777777777777778)) * item.Scale.Y); transform.Push(); transform.NJTranslate(item.Position); transform.NJRotateObject(item.Rotation); Vector3 pos = Vector3.TransformCoordinate(v7, transform.Top); transform.Pop(); add.Add(new SETItem(0, selection) { Position = pos.ToVertex() }); } else // line { transform.Push(); transform.NJTranslate(item.Position); transform.NJRotateObject(item.Rotation); double v5; if (i % 2 == 1) { v5 = i * item.Scale.Y * -0.5; } else { v5 = Math.Ceiling(i * 0.5) * item.Scale.Y; } Vector3 pos = Vector3.TransformCoordinate(new Vector3(0, 0, (float)v5), transform.Top); transform.Pop(); add.Add(new SETItem(0, selection) { Position = pos.ToVertex() }); } } del.Add(item); break; case 0x1A: // tikal -> omochao item.ID = 0x19; item.Position.Y += 3; break; case 0x1D: // kiki item.ID = 0x5B; item.Rotation = new Rotation(); item.Scale = new Vertex(); break; case 0x1F: // sweep ->beetle item.ID = 0x38; item.Rotation = new Rotation(); item.Scale = new Vertex(1, 0, 0); break; case 0x28: // launch ramp item.ID = 6; item.Scale.X /= 2.75f; item.Scale.Z = 0.799999952316284f; break; case 0x4F: // updraft item.ID = 0x35; item.Scale.X = Math.Max(Math.Min(item.Scale.X, 200), 10) / 2; item.Scale.Y = Math.Max(Math.Min(item.Scale.Y, 200), 10) / 2; item.Scale.Z = Math.Max(Math.Min(item.Scale.Z, 200), 10) / 2; break; case 0x52: // item box air item.ID = 0xB; item.Scale.X = itemboxmap[(int)item.Scale.X]; break; // palm trees case 32: case 33: case 34: case 35: trees.Add(new PalmtreeData((byte)(item.ID - 32), item.Position, item.Rotation)); del.Add(item); break; // nonsolid objects case 47: case 48: case 49: case 50: case 51: case 52: case 59: case 62: case 63: case 64: case 70: ConvertSETItem(newcollist, item, false, setlist.IndexOf(item)); del.Add(item); break; // solid objects case 36: case 37: case 39: case 41: case 42: case 43: case 44: case 45: case 46: case 54: case 58: case 66: case 71: case 72: case 73: case 74: ConvertSETItem(newcollist, item, true, setlist.IndexOf(item)); del.Add(item); break; case 81: // goal item.ID = 0xE; item.Position.Y += 30; break; default: if (idmap.ContainsKey(item.ID)) { item.ID = idmap[item.ID]; } else { del.Add(item); } break; } } setlist.AddRange(add); foreach (SETItem item in del) { setlist.Remove(item); } setlist.Add(new SETItem(0x55, selection) { Position = new Vertex(6158.6f, -88f, 2384.97f), Scale = new Vertex(3, 0, 0) }); { COL col = new COL() { Model = new ModelFile(@"E:\Bridge Model.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Visible }; col.Model.Position = new Vertex(2803, -1, 365); foreach (NJS_MATERIAL mat in ((BasicAttach)col.Model.Attach).Material) { mat.TextureID = texmap[mat.TextureID]; } col.Model.ProcessVertexData(); col.CalculateBounds(); ConvertCOL(newcollist, new Dictionary <string, Attach>(), col); col = new COL() { Model = new ModelFile(@"E:\Bridge Model COL.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.Position = new Vertex(2803, -1, 365); col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment0.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment1.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment2.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); col = new COL() { Model = new ModelFile(@"E:\BridgeSegment3.sa1mdl").Model, SurfaceFlags = SurfaceFlags.Solid }; col.Model.ProcessVertexData(); col.CalculateBounds(); newcollist.Add(col); } landTable.SaveToFile(@"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\LandTable.sa2lvl", LandTableFormat.SA2); ByteConverter.BigEndian = true; SETItem.Save(setlist, @"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\gd_PC\set0013_s.bin"); for (int i = 0; i < 4; i++) { ModelFile modelFile = new ModelFile($@"C:\SONICADVENTUREDX\Projects\Test\Objects\Levels\Emerald Coast\YASI{i}.sa1mdl"); foreach (BasicAttach attach in modelFile.Model.GetObjects().Where(a => a.Attach != null).Select(a => a.Attach)) { foreach (NJS_MATERIAL mat in attach.Material) { mat.TextureID = texmap[mat.TextureID]; } } modelFile.SaveToFile($@"C:\Program Files (x86)\Steam\steamapps\common\Sonic Adventure 2\mods\Emerald Coast\YASI{i}.sa1mdl"); } using (StreamWriter sw = File.CreateText(@"E:\Documents\Visual Studio 2017\Projects\LevelTest\LevelTest\pt.c")) sw.WriteLine(string.Join(",\r\n", trees)); }
static void Main(string[] args) { string filename; if (args.Length > 0) { filename = args[0]; Console.WriteLine("File: {0}", filename); } else { Console.Write("File: "); filename = Console.ReadLine().Trim('"'); } LandTable level = LandTable.LoadFromFile(filename); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { //fix flags differences if ((col.SurfaceFlags & SA1SurfaceFlags.UseSkyDrawDistance) == SA1SurfaceFlags.UseSkyDrawDistance) { col.SurfaceFlags &= ~SA1SurfaceFlags.UseSkyDrawDistance; } if ((col.SurfaceFlags & SA1SurfaceFlags.Diggable) == SA1SurfaceFlags.Diggable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Diggable; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Diggable; } if ((col.SurfaceFlags & SA1SurfaceFlags.Stairs) == SA1SurfaceFlags.Stairs) { col.SurfaceFlags &= ~SA1SurfaceFlags.Stairs; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Stairs; } if ((col.SurfaceFlags & SA1SurfaceFlags.Waterfall) == SA1SurfaceFlags.Waterfall) { col.SurfaceFlags &= ~SA1SurfaceFlags.Waterfall; } if ((col.SurfaceFlags & SA1SurfaceFlags.Hurt) == SA1SurfaceFlags.Hurt) { col.SurfaceFlags &= ~SA1SurfaceFlags.Hurt; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Hurt; } if ((col.SurfaceFlags & SA1SurfaceFlags.Unclimbable) == SA1SurfaceFlags.Unclimbable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Unclimbable; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.Unclimbable; } if ((col.SurfaceFlags & SA1SurfaceFlags.CannotLand) == SA1SurfaceFlags.CannotLand) { col.SurfaceFlags &= ~SA1SurfaceFlags.CannotLand; col.SurfaceFlags |= (SA1SurfaceFlags)SA2SurfaceFlags.CannotLand; } if ((col.SurfaceFlags & SA1SurfaceFlags.LowDepth) == SA1SurfaceFlags.LowDepth) { col.SurfaceFlags &= ~SA1SurfaceFlags.LowDepth; } if ((col.SurfaceFlags & SA1SurfaceFlags.Visible) == SA1SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SA1SurfaceFlags.Visible; newcol.Model = new NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; string newname = basatt.Name + "_cnk"; if (visitedAttaches.ContainsKey(newname)) { newcol.Model.Attach = visitedAttaches[newname]; } else { ChunkAttach cnkatt = basatt.ToChunk(); visitedAttaches[newname] = cnkatt; newcol.Model.Attach = cnkatt; } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SA1SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SA1SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { col.Model.Attach = col.Model.Attach.ToBasic(); } foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { //fix flags differences if ((col.SurfaceFlags & SA1SurfaceFlags.Diggable) == SA1SurfaceFlags.Diggable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Diggable; col.SurfaceFlags |= SA1SurfaceFlags.Stairs; } if ((col.SurfaceFlags & SA1SurfaceFlags.UseSkyDrawDistance) == SA1SurfaceFlags.UseSkyDrawDistance) { col.SurfaceFlags &= ~SA1SurfaceFlags.UseSkyDrawDistance; col.SurfaceFlags |= SA1SurfaceFlags.Diggable; } if ((col.SurfaceFlags & SA1SurfaceFlags.Unclimbable) == SA1SurfaceFlags.Unclimbable) { col.SurfaceFlags &= ~SA1SurfaceFlags.Unclimbable; col.SurfaceFlags |= SA1SurfaceFlags.CannotLand; } if ((col.SurfaceFlags & SA1SurfaceFlags.IncreasedAcceleration) == SA1SurfaceFlags.IncreasedAcceleration) { col.SurfaceFlags &= ~SA1SurfaceFlags.IncreasedAcceleration; col.SurfaceFlags |= SA1SurfaceFlags.Unclimbable; } if ((col.SurfaceFlags & SA1SurfaceFlags.Waterfall) == SA1SurfaceFlags.Waterfall) { col.SurfaceFlags &= ~SA1SurfaceFlags.Waterfall; col.SurfaceFlags |= SA1SurfaceFlags.Hurt; } } level.Anim = new List <GeoAnimData>(); level.Attributes = SA1LandtableAttributes.LoadTextureFile; // set LandTable to use PVM/GVM level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
static void Main(string[] args) { string filename; if (args.Length > 0) { filename = args[0]; Console.WriteLine("File: {0}", filename); } else { Console.Write("File: "); filename = Console.ReadLine(); } LandTable level = LandTable.LoadFromFile(filename); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { if ((col.SurfaceFlags & SurfaceFlags.Visible) == SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SurfaceFlags.Visible; newcol.Model = new SonicRetro.SAModel.NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; ChunkAttach cnkatt = new ChunkAttach(true, true) { Name = basatt.Name + "_cnk", Bounds = basatt.Bounds }; newcol.Model.Attach = cnkatt; VertexChunk vcnk; if (basatt.Normal != null && basatt.Normal.Length > 0) { vcnk = new VertexChunk(ChunkType.Vertex_VertexNormal); } else { vcnk = new VertexChunk(ChunkType.Vertex_Vertex); } vcnk.Vertices = new List <Vertex>(basatt.Vertex); if (basatt.Normal != null) { vcnk.Normals = new List <Vertex>(basatt.Normal); } vcnk.VertexCount = (ushort)basatt.Vertex.Length; vcnk.Size = (ushort)((vcnk.Type == ChunkType.Vertex_VertexNormal ? vcnk.VertexCount * 6 : vcnk.VertexCount * 3) + 1); cnkatt.Vertex.Add(vcnk); foreach (NJS_MESHSET mesh in basatt.Mesh) { if (mesh.PolyType != Basic_PolyType.Strips) { Console.WriteLine("Warning: Skipping non-strip mesh in {0} ({1}).", basatt.MeshName, mesh.PolyType); continue; } NJS_MATERIAL mat = null; if (basatt.Material != null && mesh.MaterialID < basatt.Material.Count) { mat = basatt.Material[mesh.MaterialID]; cnkatt.Poly.Add(new PolyChunkBitsBlendAlpha() { SourceAlpha = mat.SourceAlpha, DestinationAlpha = mat.DestinationAlpha }); cnkatt.Poly.Add(new PolyChunkTinyTextureID() { ClampU = mat.ClampU, ClampV = mat.ClampV, FilterMode = mat.FilterMode, FlipU = mat.FlipU, FlipV = mat.FlipV, SuperSample = mat.SuperSample, TextureID = (ushort)mat.TextureID }); cnkatt.Poly.Add(new PolyChunkMaterial() { Diffuse = mat.DiffuseColor, Specular = mat.SpecularColor, SpecularExponent = (byte)mat.Exponent }); } PolyChunkStrip strip; if (mesh.UV != null & mesh.VColor != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripUVNColor); } else if (mesh.UV != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripUVN); } else if (mesh.VColor != null) { strip = new PolyChunkStrip(ChunkType.Strip_StripColor); } else { strip = new PolyChunkStrip(ChunkType.Strip_Strip); } if (mat != null) { strip.IgnoreLight = mat.IgnoreLighting; strip.IgnoreSpecular = mat.IgnoreSpecular; strip.UseAlpha = mat.UseAlpha; strip.DoubleSide = mat.DoubleSided; strip.FlatShading = mat.FlatShading; strip.EnvironmentMapping = mat.EnvironmentMap; } int striptotal = 0; foreach (Strip item in mesh.Poly.Cast <Strip>()) { UV[] uvs = null; if (mesh.UV != null) { uvs = new UV[item.Indexes.Length]; Array.Copy(mesh.UV, striptotal, uvs, 0, item.Indexes.Length); } Color[] vcolors = null; if (mesh.VColor != null) { vcolors = new Color[item.Indexes.Length]; Array.Copy(mesh.VColor, striptotal, vcolors, 0, item.Indexes.Length); } strip.Strips.Add(new PolyChunkStrip.Strip(item.Reversed, item.Indexes, uvs, vcolors)); striptotal += item.Indexes.Length; } cnkatt.Poly.Add(strip); } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.Tool = "SA Tools Level Converter"; level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: Vertex[] VertexBuffer = new Vertex[0]; Vertex[] NormalBuffer = new Vertex[0]; foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { ChunkAttach cnkatt = (ChunkAttach)col.Model.Attach; BasicAttach basatt = new BasicAttach() { Name = cnkatt.Name, Bounds = cnkatt.Bounds }; if (cnkatt.Vertex != null) { foreach (VertexChunk chunk in cnkatt.Vertex) { if (VertexBuffer.Length < chunk.IndexOffset + chunk.VertexCount) { Array.Resize(ref VertexBuffer, chunk.IndexOffset + chunk.VertexCount); Array.Resize(ref NormalBuffer, chunk.IndexOffset + chunk.VertexCount); } Array.Copy(chunk.Vertices.ToArray(), 0, VertexBuffer, chunk.IndexOffset, chunk.Vertices.Count); Array.Copy(chunk.Normals.ToArray(), 0, NormalBuffer, chunk.IndexOffset, chunk.Normals.Count); } } NJS_MATERIAL material = new NJS_MATERIAL() { UseTexture = true }; int minVtx = int.MaxValue; int maxVtx = int.MinValue; foreach (PolyChunk chunk in cnkatt.Poly) { switch (chunk.Type) { case ChunkType.Bits_BlendAlpha: { PolyChunkBitsBlendAlpha c2 = (PolyChunkBitsBlendAlpha)chunk; material.SourceAlpha = c2.SourceAlpha; material.DestinationAlpha = c2.DestinationAlpha; } break; case ChunkType.Bits_MipmapDAdjust: break; case ChunkType.Bits_SpecularExponent: material.Exponent = ((PolyChunkBitsSpecularExponent)chunk).SpecularExponent; break; case ChunkType.Tiny_TextureID: case ChunkType.Tiny_TextureID2: { PolyChunkTinyTextureID c2 = (PolyChunkTinyTextureID)chunk; material.ClampU = c2.ClampU; material.ClampV = c2.ClampV; material.FilterMode = c2.FilterMode; material.FlipU = c2.FlipU; material.FlipV = c2.FlipV; material.SuperSample = c2.SuperSample; material.TextureID = c2.TextureID; } break; case ChunkType.Material_Diffuse: case ChunkType.Material_Ambient: case ChunkType.Material_DiffuseAmbient: case ChunkType.Material_Specular: case ChunkType.Material_DiffuseSpecular: case ChunkType.Material_AmbientSpecular: case ChunkType.Material_DiffuseAmbientSpecular: case ChunkType.Material_Diffuse2: case ChunkType.Material_Ambient2: case ChunkType.Material_DiffuseAmbient2: case ChunkType.Material_Specular2: case ChunkType.Material_DiffuseSpecular2: case ChunkType.Material_AmbientSpecular2: case ChunkType.Material_DiffuseAmbientSpecular2: { PolyChunkMaterial c2 = (PolyChunkMaterial)chunk; if (c2.Diffuse.HasValue) { material.DiffuseColor = c2.Diffuse.Value; } if (c2.Specular.HasValue) { material.SpecularColor = c2.Specular.Value; material.Exponent = c2.SpecularExponent; } } break; case ChunkType.Strip_Strip: case ChunkType.Strip_StripUVN: case ChunkType.Strip_StripUVH: case ChunkType.Strip_StripNormal: case ChunkType.Strip_StripUVNNormal: case ChunkType.Strip_StripUVHNormal: case ChunkType.Strip_StripColor: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: case ChunkType.Strip_Strip2: case ChunkType.Strip_StripUVN2: case ChunkType.Strip_StripUVH2: { PolyChunkStrip c2 = (PolyChunkStrip)chunk; material.DoubleSided = c2.DoubleSide; material.EnvironmentMap = c2.EnvironmentMapping; material.FlatShading = c2.FlatShading; material.IgnoreLighting = c2.IgnoreLight; material.IgnoreSpecular = c2.IgnoreSpecular; material.UseAlpha = c2.UseAlpha; bool hasVColor = false; switch (chunk.Type) { case ChunkType.Strip_StripColor: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: hasVColor = true; break; } bool hasUV = false; switch (chunk.Type) { case ChunkType.Strip_StripUVN: case ChunkType.Strip_StripUVH: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: case ChunkType.Strip_StripUVN2: case ChunkType.Strip_StripUVH2: hasUV = true; break; } List <Strip> strips = new List <Strip>(c2.StripCount); List <UV> uvs = hasUV ? new List <UV>() : null; List <Color> vcolors = hasVColor ? new List <Color>() : null; foreach (PolyChunkStrip.Strip strip in c2.Strips) { minVtx = Math.Min(minVtx, strip.Indexes.Min()); maxVtx = Math.Max(maxVtx, strip.Indexes.Max()); strips.Add(new Strip(strip.Indexes, strip.Reversed)); if (hasUV) { uvs.AddRange(strip.UVs); } if (hasVColor) { vcolors.AddRange(strip.VColors); } } NJS_MESHSET mesh = new NJS_MESHSET(strips.ToArray(), false, hasUV, hasVColor); if (hasUV) { uvs.CopyTo(mesh.UV); } if (hasVColor) { vcolors.CopyTo(mesh.VColor); } mesh.MaterialID = (ushort)basatt.Material.Count; basatt.Mesh.Add(mesh); basatt.Material.Add(material); material = new NJS_MATERIAL(material.GetBytes(), 0); } break; } } int numVtx = maxVtx - minVtx + 1; basatt.ResizeVertexes(numVtx); Array.Copy(VertexBuffer, minVtx, basatt.Vertex, 0, numVtx); Array.Copy(NormalBuffer, minVtx, basatt.Normal, 0, numVtx); foreach (NJS_MESHSET mesh in basatt.Mesh) { foreach (Poly poly in mesh.Poly) { for (int i = 0; i < poly.Indexes.Length; i++) { poly.Indexes[i] = (ushort)(poly.Indexes[i] - minVtx); } } } col.Model.Attach = basatt; } level.Anim = new List <GeoAnimData>(); level.Tool = "SA Tools Level Converter"; level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
static void Main(string[] args) { string filename_src; string filename_dst; if (args.Length == 1) { string[] filenames = File.ReadAllLines(args[0]); for (int f = 0; f < filenames.Length; f++) { Console.WriteLine("Sorting file {0}", filenames[f]); ModelFile model = new ModelFile(filenames[f]); SortModel(model.Model, true); model.SaveToFile(filenames[f]); } return; } if (args.Length > 1) { string filename_out = "Result.ini"; for (int a = 0; a < args.Length; a++) { if (args[a] == "-f") { Console.WriteLine("Folder mode"); DoFolderStuff(args); return; } if (args[a] == "-s") { savediff = true; } if (args[a] == "-a") { overwrite = false; } if (args[a] == "-o") { filename_out = Path.GetFullPath(args[a + 1]); } } filename_src = Path.GetFullPath(args[0]); filename_dst = Path.GetFullPath(args[1]); Console.WriteLine("Source file: {0}", filename_src); Console.WriteLine("Destination file: {0}", filename_dst); if (savediff) { Console.Write("Output file: {0}, ", filename_out); Console.Write("mode: " + (overwrite ? "Overwrite" : "Append") + "\n"); } } else { Console.WriteLine("This tool compares two levels or models and outputs a list of differences between them.\n"); Console.WriteLine("Usage:"); Console.WriteLine("CompareTool <file1> <file2> [-s] [-a] [-o outputfile]\n"); Console.WriteLine("Arguments:"); Console.WriteLine("file1: Source level or model"); Console.WriteLine("file2: Destination level or model"); //Console.WriteLine("-s: Save the list of differences to an INI file"); Console.WriteLine("-a: Append to the list of differences instead of overwriting it"); Console.WriteLine("-o: Output filename (default is Result.ini)\n"); Console.WriteLine("Example:"); Console.WriteLine("CompareTool Level_PC.sa1lvl Level_Gamecube.sa1lvl\n"); Console.WriteLine("Press ENTER to exit."); Console.ReadLine(); return; } if (!File.Exists(filename_src) || !File.Exists(filename_dst)) { Console.WriteLine("File {0} or {1} doesn't exist.", filename_src, filename_dst); Console.ReadLine(); return; } string ext = Path.GetExtension(filename_src).ToLowerInvariant(); //biglist = new Dictionary<int, List<DiffData>>(); switch (ext) { case ".sa1lvl": LandTable land_src = LandTable.LoadFromFile(filename_src); LandTable land_dst = LandTable.LoadFromFile(filename_dst); COL[] arr_src = land_src.COL.ToArray(); COL[] arr_dst = land_dst.COL.ToArray(); bool same = true; for (int co = 0; co < arr_src.Length; co++) { if (same && !CompareCOL(arr_src[co], arr_dst[co])) { Console.WriteLine("COL order different at item {0} ({1} / {2} / {3})! Trying manual match.", co, arr_src[co].Bounds.Center.X, arr_src[co].Bounds.Center.Y, arr_src[co].Bounds.Center.Z); same = false; } } //Compare using identical order if (same) { for (int c = 0; c < arr_src.Length; c++) { if (arr_src[c].Model.Attach != null) { CompareAttach((BasicAttach)arr_src[c].Model.Attach, (BasicAttach)arr_dst[c].Model.Attach); } } } //Compare using different order else { if (arr_dst.Length != arr_src.Length) { Console.WriteLine("COL count different: {0} vs {1}", arr_src.Length, arr_dst.Length); } Dictionary <int, int> matches = new Dictionary <int, int>(); for (int c1 = 0; c1 < arr_dst.Length; c1++) { bool found = false; for (int c2 = 0; c2 < arr_dst.Length; c2++) { if (arr_src[c1].Model.Attach != null && CompareCOL(arr_src[c1], arr_dst[c2], 0)) { if (!matches.ContainsKey(c2) && !matches.ContainsValue(c1)) { matches.Add(c2, c1); found = true; Console.WriteLine("COL item {0} matched with {1}", c1, c2); CompareAttach((BasicAttach)arr_src[c1].Model.Attach, (BasicAttach)arr_dst[c2].Model.Attach); } } } //Try again but less strict if (!found) { for (int c2 = 0; c2 < arr_dst.Length; c2++) { if (arr_src[c1].Model.Attach != null && CompareCOL(arr_src[c1], arr_dst[c2], 1)) { if (!matches.ContainsKey(c2) && !matches.ContainsValue(c1)) { matches.Add(c2, c1); Console.WriteLine("COL item {0} partially matched with {1}", c1, c2); CompareAttach((BasicAttach)arr_src[c1].Model.Attach, (BasicAttach)arr_dst[c2].Model.Attach); } } } } } Console.WriteLine("Total COL items in landtables: {0} vs {1}, matches: {2}", arr_src.Length, arr_dst.Length, matches.Count); } //if (savediff) SerializeDiffList(filename_out); break; case ".sa1mdl": NJS_OBJECT mdl_src = new ModelFile(filename_src).Model; NJS_OBJECT mdl_dst = new ModelFile(filename_dst).Model; CompareModel(mdl_src, mdl_dst); //if (savediff) SerializeDiffList(filename_out); break; default: break; } if (savediff) { Console.WriteLine("Total UV array differences: {0}", uvcount); } }
// Scans a folder for .sa1lvl, .sa1mdl and .saanim files and creates a sorted split INI file based on their labels and filenames static void SplitLD(string[] args) { string dirpath = args[0]; string[] modelfiles = System.IO.Directory.GetFiles(dirpath, "*.sa1mdl", SearchOption.AllDirectories); string[] levelfiles = System.IO.Directory.GetFiles(dirpath, "*.sa1lvl", SearchOption.AllDirectories); string[] animfiles = System.IO.Directory.GetFiles(dirpath, "*.saanim", SearchOption.AllDirectories); Dictionary <string, SA_Tools.FileInfo> files_ini = new Dictionary <string, SA_Tools.FileInfo>(); for (int u = 0; u < modelfiles.Length; u++) { ModelFile mdl = new ModelFile(modelfiles[u]); SA_Tools.FileInfo fileinfo = new SA_Tools.FileInfo(); fileinfo.Type = "model"; fileinfo.Filename = modelfiles[u]; string mdlname = mdl.Model.Name; fileinfo.Address = int.Parse(mdlname.Substring(mdlname.Length - 8, 8), System.Globalization.NumberStyles.AllowHexSpecifier); //Console.WriteLine("{0}={1}", fileinfo.Address.ToString("X8"), modelfiles[u]); if (!files_ini.ContainsKey(mdlname)) { files_ini.Add(mdlname, fileinfo); } else { Console.WriteLine("Duplicate of {0}", mdlname); } } for (int u = 0; u < levelfiles.Length; u++) { LandTable lvl = LandTable.LoadFromFile(levelfiles[u]); SA_Tools.FileInfo fileinfo = new SA_Tools.FileInfo(); fileinfo.Type = "landtable"; fileinfo.Filename = levelfiles[u]; string lvlname = lvl.Name; fileinfo.Address = int.Parse(lvlname.Substring(lvlname.Length - 8, 8), System.Globalization.NumberStyles.AllowHexSpecifier); //Console.WriteLine("{0}={1}", fileinfo.Address.ToString("X8"), levelfiles[u]); if (!files_ini.ContainsKey(lvlname)) { files_ini.Add(lvlname, fileinfo); } else { Console.WriteLine("Duplicate of {0}", lvlname); } } for (int u = 0; u < animfiles.Length; u++) { NJS_MOTION mtn = NJS_MOTION.Load(animfiles[u]); SA_Tools.FileInfo fileinfo = new SA_Tools.FileInfo(); fileinfo.Type = "animation"; fileinfo.Filename = animfiles[u]; string mtnname = mtn.Name; fileinfo.Address = int.Parse(mtnname.Substring(mtnname.Length - 8, 8), System.Globalization.NumberStyles.AllowHexSpecifier); //Console.WriteLine("{0}={1}", fileinfo.Address.ToString("X8"), animfiles[u]); if (!files_ini.ContainsKey(mtnname)) { files_ini.Add(mtnname, fileinfo); } else { Console.WriteLine("Duplicate of {0}", mtnname); } } IniData inidata_new = new SA_Tools.IniData(); inidata_new.Files = SortIniData(files_ini); IniSerializer.Serialize(inidata_new, dirpath + "_sorted.ini"); }
public static void ExportCPP(DllIniData IniData, Dictionary <string, bool> itemsToExport, string fileName) { using (TextWriter writer = File.CreateText(fileName)) { bool SA2 = IniData.Game == 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": 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": case "animindex": NJS_MOTION ani = NJS_MOTION.Load(item.Key); ani.ToStructVariables(writer); labels.Add(ani.Name); break; } writer.WriteLine(); } 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))) { writer.WriteLine("\t{0} = &{1};", item.ToString(), item.Label); } 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"); } }
static void Main(string[] args) { string filename; if (args.Length > 0) { filename = args[0]; Console.WriteLine("File: {0}", filename); } else { Console.Write("File: "); filename = Console.ReadLine().Trim('"'); } LandTable level = LandTable.LoadFromFile(filename); Dictionary <string, Attach> visitedAttaches = new Dictionary <string, Attach>(); switch (level.Format) { case LandTableFormat.SA1: { List <COL> newcollist = new List <COL>(); foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach != null)) { if ((col.SurfaceFlags & SA1SurfaceFlags.Visible) == SA1SurfaceFlags.Visible) { COL newcol = new COL() { Bounds = col.Bounds }; newcol.SurfaceFlags = SA1SurfaceFlags.Visible; newcol.Model = new NJS_OBJECT() { Name = col.Model.Name + "_cnk" }; newcol.Model.Position = col.Model.Position; newcol.Model.Rotation = col.Model.Rotation; newcol.Model.Scale = col.Model.Scale; BasicAttach basatt = (BasicAttach)col.Model.Attach; string newname = basatt.Name + "_cnk"; if (visitedAttaches.ContainsKey(newname)) { newcol.Model.Attach = visitedAttaches[newname]; } else { ChunkAttach cnkatt = basatt.ToChunk(); visitedAttaches[newname] = cnkatt; newcol.Model.Attach = cnkatt; } newcollist.Add(newcol); } if ((col.SurfaceFlags & ~SA1SurfaceFlags.Visible) != 0) { col.SurfaceFlags &= ~SA1SurfaceFlags.Visible; newcollist.Add(col); } } level.COL = newcollist; } level.Anim = new List <GeoAnimData>(); level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa2lvl"), LandTableFormat.SA2); break; case LandTableFormat.SA2: foreach (COL col in level.COL.Where((col) => col.Model != null && col.Model.Attach is ChunkAttach)) { col.Model.Attach = col.Model.Attach.ToBasic(); } level.Anim = new List <GeoAnimData>(); level.Flags = 8; // set LandTable to use PVM/GVM level.SaveToFile(System.IO.Path.ChangeExtension(filename, "sa1lvl"), LandTableFormat.SA1); break; } }
private void button5_Click(object sender, EventArgs e) { using (SaveFileDialog fd = new SaveFileDialog() { DefaultExt = "cpp", Filter = "C++ source files|*.cpp", InitialDirectory = Environment.CurrentDirectory, RestoreDirectory = true }) if (fd.ShowDialog(this) == DialogResult.OK) { using (TextWriter writer = File.CreateText(fd.FileName)) { bool SA2 = IniData.Game == 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((a, i) => listView1.CheckedIndices.Contains(i))) { 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": 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": Animation ani = Animation.Load(item.Key); ani.ToStructVariables(writer); labels.Add(ani.Name); break; } writer.WriteLine(); } 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))) { writer.WriteLine("\t{0} = &{1};", item.ToString(), item.Label); } 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"); } } }