private void saveToolStripMenuItem_Click(object sender, EventArgs e) { using (SaveFileDialog a = new SaveFileDialog() { DefaultExt = (outfmt == ModelFormat.Chunk ? "sa2" : "sa1") + "mdl", Filter = (outfmt == ModelFormat.Chunk ? "SA2" : "SA1") + "MDL Files|*." + (outfmt == ModelFormat.Chunk ? "sa2" : "sa1") + "mdl|All Files|*.*" }) if (a.ShowDialog(this) == DialogResult.OK) { if (modelFile != null) { modelFile.Tool = "SAMDL"; modelFile.SaveToFile(a.FileName); } else { ModelFile.CreateFile(a.FileName, model, null, null, null, null, "SAMDL", null, outfmt); } } }
static void Main(string[] args) { string filename; if (args.Length > 0) { Console.WriteLine("File: {0}", filename = args[0]); } else { Console.Write("File: "); filename = Console.ReadLine(); } ModelFile model = new ModelFile(filename); string[] mapstr; if (args.Length > 1) { mapstr = args.Skip(1).ToArray(); Console.WriteLine("Remaps: {0}", string.Join(" ", mapstr)); } else { Console.WriteLine("Enter texture mappings (src,dst [src,dst ...]):"); mapstr = Console.ReadLine().Split(' '); } Dictionary <ushort, ushort> maps = new Dictionary <ushort, ushort>(); foreach (string str in mapstr) { string[] b = str.Split(','); maps.Add(ushort.Parse(b[0]), ushort.Parse(b[1])); } foreach (Attach att in model.Model.GetObjects().Where(a => a.Attach != null).Select(a => a.Attach)) { switch (att) { case BasicAttach batt: if (batt.Material != null) { foreach (NJS_MATERIAL mat in batt.Material) { if (maps.ContainsKey((ushort)mat.TextureID)) { mat.TextureID = maps[(ushort)mat.TextureID]; } } } break; case ChunkAttach catt: if (catt.Poly != null) { foreach (PolyChunkTinyTextureID tex in catt.Poly.OfType <PolyChunkTinyTextureID>()) { if (maps.ContainsKey(tex.TextureID)) { tex.TextureID = maps[tex.TextureID]; } } } break; } } model.SaveToFile(filename); }
static void Main(string[] args) { Queue <string> argq = new Queue <string>(args); string mdlfilename; if (argq.Count > 0) { mdlfilename = argq.Dequeue(); Console.WriteLine("Model File: {0}", mdlfilename); } else { Console.Write("Model File: "); mdlfilename = Console.ReadLine(); } ModelFile model = new ModelFile(mdlfilename); NJS_OBJECT[] objects = model.Model.GetObjects(); string repmdlfilename; if (argq.Count > 0) { repmdlfilename = argq.Dequeue(); Console.WriteLine("Replacement Model File: {0}", repmdlfilename); } else { Console.Write("Replacement Model File: "); repmdlfilename = Console.ReadLine(); } ModelFile repmodel = new ModelFile(repmdlfilename); NJS_OBJECT[] repobjects = repmodel.Model.GetObjects(); if (model.Format != repmodel.Format) { Console.WriteLine("Format mismatch between files! Most data will be unable to be relabeled."); } if (objects.Length != repobjects.Length) { Console.WriteLine("Models have different structures, the game may crash."); } for (int i = 0; i < Math.Min(objects.Length, repobjects.Length); i++) { objects[i].Name = repobjects[i].Name; if (objects[i].Attach != null && repobjects[i].Attach != null) { objects[i].Attach.Name = repobjects[i].Attach.Name; if (objects[i].Attach is BasicAttach && repobjects[i].Attach is BasicAttach) { BasicAttach attach = (BasicAttach)objects[i].Attach; BasicAttach repattach = (BasicAttach)repobjects[i].Attach; attach.VertexName = repattach.VertexName; if (repattach.NormalName != null) { attach.NormalName = repattach.NormalName; } if (repattach.MaterialName != null) { attach.MaterialName = repattach.MaterialName; } attach.MeshName = repattach.MeshName; for (int j = 0; j < Math.Min(attach.Mesh.Count, repattach.Mesh.Count); j++) { attach.Mesh[j].PolyName = repattach.Mesh[j].PolyName; if (repattach.Mesh[j].PolyNormalName != null) { attach.Mesh[j].PolyNormalName = repattach.Mesh[j].PolyNormalName; } if (repattach.Mesh[j].UVName != null) { attach.Mesh[j].UVName = repattach.Mesh[j].UVName; } if (repattach.Mesh[j].VColorName != null) { attach.Mesh[j].VColorName = repattach.Mesh[j].VColorName; } } } else if (objects[i].Attach is ChunkAttach && repobjects[i].Attach is ChunkAttach) { ChunkAttach attach = (ChunkAttach)objects[i].Attach; ChunkAttach repattach = (ChunkAttach)repobjects[i].Attach; if (repattach.VertexName != null) { attach.VertexName = repattach.VertexName; } if (repattach.PolyName != null) { attach.PolyName = repattach.PolyName; } } } } model.SaveToFile(repmdlfilename); }
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_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); } }