Skip to content

Commit 363c019

Browse files
committed
[Major] using serialization to quickly differentiate codes (thanks to Pong), bug fix for Texture comparison
1 parent 5cb457c commit 363c019

3 files changed

Lines changed: 303 additions & 226 deletions

File tree

Diff.cs

Lines changed: 28 additions & 216 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1+
using System.Diagnostics;
12
using Serilog;
23
using UndertaleModLib;
34
using UndertaleModLib.Models;
4-
using DiffMatchPatch;
5-
using UndertaleModLib.Decompiler;
6-
using System.Runtime;
7-
using UndertaleModLib.Util;
85

96
namespace ModShardDiff;
107
internal static class MainOperations
@@ -20,12 +17,25 @@ public static async Task MainCommand(string name, string reference, string? outp
2017
Log.Logger = logger.CreateLogger();
2118

2219
Console.WriteLine($"Exporting differences between {name} and {reference} in {outputFolder}.");
23-
20+
21+
Stopwatch stopWatch = new();
22+
stopWatch.Start();
2423
Task<bool> task = FileReader.Diff(name, reference, outputFolder);
25-
await task;
24+
try
25+
{
26+
await task;
27+
}
28+
catch(Exception ex)
29+
{
30+
Log.Error(ex, "Something went wrong");
31+
}
32+
2633
if (task.Result)
2734
{
28-
Console.WriteLine($"Process failed successfully.");
35+
stopWatch.Stop();
36+
TimeSpan ts = stopWatch.Elapsed;
37+
string elapsedTime = string.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
38+
Console.WriteLine($"Process failed successfully in {elapsedTime}.");
2939
}
3040
else
3141
{
@@ -35,229 +45,32 @@ public static async Task MainCommand(string name, string reference, string? outp
3545
await Log.CloseAndFlushAsync();
3646
}
3747
}
38-
39-
class UndertaleCodeNameComparer : IEqualityComparer<UndertaleCode>
40-
{
41-
public bool Equals(UndertaleCode? x, UndertaleCode? y)
42-
{
43-
if (x == null || y == null) return false;
44-
return x.Name.Content == y.Name.Content;
45-
}
46-
47-
// If Equals() returns true for a pair of objects
48-
// then GetHashCode() must return the same value for these objects.
49-
50-
public int GetHashCode(UndertaleCode x)
51-
{
52-
//Check whether the object is null
53-
if (x == null) return 0;
54-
return x.Name.Content.GetHashCode();
55-
}
56-
}
57-
58-
class UndertaleSpriteNameComparer : IEqualityComparer<UndertaleSprite>
59-
{
60-
public bool Equals(UndertaleSprite? x, UndertaleSprite? y)
61-
{
62-
if (x == null || y == null) return false;
63-
return x.Name.Content == y.Name.Content;
64-
}
65-
66-
// If Equals() returns true for a pair of objects
67-
// then GetHashCode() must return the same value for these objects.
68-
69-
public int GetHashCode(UndertaleSprite x)
70-
{
71-
//Check whether the object is null
72-
if (x == null) return 0;
73-
return x.Name.Content.GetHashCode();
74-
}
75-
}
76-
77-
class UnderTaleInstructionComparer : IEqualityComparer<UndertaleInstruction>
78-
{
79-
public bool Equals(UndertaleInstruction? x, UndertaleInstruction? y)
80-
{
81-
if (x == null || y == null) return false;
82-
return x.Address == y.Address && x.Kind == y.Kind && x.Type1 == y.Type1 && x.Type2 == y.Type2 && x.TypeInst == y.TypeInst && x.ComparisonKind == y.ComparisonKind;
83-
}
84-
85-
// If Equals() returns true for a pair of objects
86-
// then GetHashCode() must return the same value for these objects.
87-
88-
public int GetHashCode(UndertaleInstruction x)
89-
{
90-
//Check whether the object is null
91-
if (x == null) return 0;
92-
return x.Address.GetHashCode() ^ x.Kind.GetHashCode() ^ x.Type1.GetHashCode() ^ x.Type2.GetHashCode() ^ x.TypeInst.GetHashCode() ^ x.ComparisonKind.GetHashCode();
93-
}
94-
}
95-
9648
internal static class FileReader
9749
{
98-
private static void ExportDiffs(IEnumerable<string> added, IEnumerable<string> removed, string name, DirectoryInfo outputFolder)
99-
{
100-
File.WriteAllLines(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), $"added{name}.txt"), added);
101-
File.WriteAllLines(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), $"removed{name}.txt"), removed);
102-
}
103-
private static void DiffCodes(UndertaleData name, UndertaleData reference, DirectoryInfo outputFolder)
104-
{
105-
GlobalDecompileContext contextName = new(name, false);
106-
GlobalDecompileContext contextRef = new(reference, false);
107-
DirectoryInfo dirAddedCode = new(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), "AddedCodes"));
108-
dirAddedCode.Create();
109-
DirectoryInfo dirModifiedCode = new(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), "ModifiedCodes"));
110-
dirModifiedCode.Create();
111-
112-
IEnumerable<UndertaleCode> added = name.Code.Except(reference.Code, new UndertaleCodeNameComparer());
113-
IEnumerable<UndertaleCode> removed = reference.Code.Except(name.Code, new UndertaleCodeNameComparer());
114-
115-
using (StreamWriter sw = new(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), $"addedCodes.txt")))
116-
{
117-
foreach(UndertaleCode code in added)
118-
{
119-
sw.WriteLine(code.Name.Content);
120-
string strCode = "";
121-
try
122-
{
123-
strCode = Decompiler.Decompile(code, contextName);
124-
File.WriteAllText(Path.Join(dirAddedCode.FullName, Path.DirectorySeparatorChar.ToString(), $"{code.Name.Content}.gml"), strCode);
125-
}
126-
catch
127-
{
128-
// pass if failed
129-
}
130-
}
131-
}
132-
File.WriteAllLines(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), $"removedCodes.txt"), removed.Select(x => x.Name.Content));
133-
134-
IEnumerable<UndertaleCode> common = name.Code.Intersect(reference.Code, new UndertaleCodeNameComparer());
135-
diff_match_patch dmp = new();
136-
137-
foreach(UndertaleCode code in common)
138-
{
139-
UndertaleCode codeRef = reference.Code.First(t => t.Name.Content == code.Name.Content);
140-
if (codeRef.Length == code.Length && codeRef.Instructions.SequenceEqual(code.Instructions, new UnderTaleInstructionComparer())) continue;
141-
Console.WriteLine($"{code.Name.Content} modified.");
142-
143-
string strName = "";
144-
string strRef = "";
145-
try
146-
{
147-
strName = Decompiler.Decompile(code, contextName);
148-
strRef = Decompiler.Decompile(codeRef, contextRef);
149-
}
150-
catch(Exception ex)
151-
{
152-
if (!ex.Message.Contains("This code block represents a function nested inside"))
153-
{
154-
try
155-
{
156-
strName = code.Disassemble(name.Variables, name.CodeLocals.For(code));
157-
strRef = codeRef.Disassemble(reference.Variables, reference.CodeLocals.For(codeRef));
158-
}
159-
catch (Exception ex2)
160-
{
161-
Console.WriteLine($@"{ex2.GetType()}: {ex2.Message}");
162-
}
163-
}
164-
}
165-
List<Diff> diff = dmp.diff_main(strRef, strName);
166-
if (diff.Count <= 1) continue;
167-
168-
string report = dmp.diff_prettyHtml(diff);
169-
File.WriteAllText(Path.Join(dirModifiedCode.FullName, Path.DirectorySeparatorChar.ToString(), $"{code.Name.Content}.html"), report);
170-
}
171-
172-
}
173-
private static void DiffObjects(UndertaleData name, UndertaleData reference, DirectoryInfo outputFolder)
174-
{
175-
IEnumerable<string> added = name.GameObjects.Select(x => x.Name.Content).Except(reference.GameObjects.Select(x => x.Name.Content));
176-
IEnumerable<string> removed = reference.GameObjects.Select(x => x.Name.Content).Except(name.GameObjects.Select(x => x.Name.Content));
177-
ExportDiffs(added, removed, "GameObjects", outputFolder);
178-
}
179-
private static void DiffRooms(UndertaleData name, UndertaleData reference, DirectoryInfo outputFolder)
180-
{
181-
IEnumerable<string> added = name.Rooms.Select(x => x.Name.Content).Except(reference.Rooms.Select(x => x.Name.Content));
182-
IEnumerable<string> removed = reference.Rooms.Select(x => x.Name.Content).Except(name.Rooms.Select(x => x.Name.Content));
183-
ExportDiffs(added, removed, "Rooms", outputFolder);
184-
}
185-
private static void DiffSounds(UndertaleData name, UndertaleData reference, DirectoryInfo outputFolder)
186-
{
187-
IEnumerable<string> added = name.Sounds.Select(x => x.Name.Content).Except(reference.Sounds.Select(x => x.Name.Content));
188-
IEnumerable<string> removed = reference.Sounds.Select(x => x.Name.Content).Except(name.Sounds.Select(x => x.Name.Content));
189-
ExportDiffs(added, removed, "Sounds", outputFolder);
190-
}
191-
private static void DiffSprites(UndertaleData name, UndertaleData reference, DirectoryInfo outputFolder)
192-
{
193-
TextureWorker worker = new();
194-
DirectoryInfo dirAddedSprite = new(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), "AddedSprites"));
195-
dirAddedSprite.Create();
196-
DirectoryInfo dirModifiedSprite = new(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), "ModifiedSprites"));
197-
dirModifiedSprite.Create();
198-
199-
IEnumerable<UndertaleSprite> added = name.Sprites.Except(reference.Sprites, new UndertaleSpriteNameComparer());
200-
IEnumerable<UndertaleSprite> removed = reference.Sprites.Except(name.Sprites, new UndertaleSpriteNameComparer());
201-
using (StreamWriter sw = new(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), $"addedSprites.txt")))
202-
{
203-
foreach(UndertaleSprite sprite in added)
204-
{
205-
sw.WriteLine(sprite.Name.Content);
206-
for (int i = 0; i < sprite.Textures.Count; i++)
207-
{
208-
if (sprite.Textures[i]?.Texture is not null)
209-
{
210-
worker.ExportAsPNG(sprite.Textures[i].Texture, Path.Combine(dirAddedSprite.FullName , sprite.Name.Content + "_" + i + ".png"), null, true);
211-
}
212-
}
213-
}
214-
}
215-
File.WriteAllLines(Path.Join(outputFolder.FullName, Path.DirectorySeparatorChar.ToString(), $"removedSprites.txt"), removed.Select(x => x.Name.Content));
216-
217-
IEnumerable<UndertaleSprite> common = name.Sprites.Intersect(reference.Sprites, new UndertaleSpriteNameComparer());
218-
bool equalTexture = true;
219-
foreach(UndertaleSprite sprite in common)
220-
{
221-
UndertaleSprite spriteRef = reference.Sprites.First(t => t.Name.Content == sprite.Name.Content);
222-
for (int i = 0; i < sprite.Textures.Count; i++)
223-
{
224-
if (sprite.Textures[i]?.Texture is not null)
225-
{
226-
equalTexture = sprite.Textures[i].Texture.TexturePage.TextureData.TextureBlob.SequenceEqual(spriteRef.Textures[i].Texture.TexturePage.TextureData.TextureBlob);
227-
if(equalTexture) continue;
228-
worker.ExportAsPNG(sprite.Textures[i].Texture, Path.Combine(dirModifiedSprite.FullName , sprite.Name.Content + "_" + i + ".png"), null, true);
229-
}
230-
}
231-
}
232-
}
233-
private static void DiffTexturePageItems(UndertaleData name, UndertaleData reference, DirectoryInfo outputFolder)
234-
{
235-
IEnumerable<string> added = name.TexturePageItems.Select(x => x.Name.Content).Except(reference.TexturePageItems.Select(x => x.Name.Content));
236-
IEnumerable<string> removed = reference.TexturePageItems.Select(x => x.Name.Content).Except(name.TexturePageItems.Select(x => x.Name.Content));
237-
ExportDiffs(added, removed, "TexturePageItems", outputFolder);
238-
}
23950
public static async Task<bool> Diff(string name, string reference, string outputFolder)
24051
{
24152
DirectoryInfo dir = new(outputFolder);
24253
if (dir.Exists) dir.Delete(true);
24354
dir.Create();
55+
56+
if (!File.Exists(name)) throw new FileNotFoundException($"File {name} does not exist.");
57+
if (!File.Exists(reference)) throw new FileNotFoundException($"File {reference} does not exist.");
24458

24559
Task<UndertaleData?> taskName = LoadFile(name);
246-
await taskName;
60+
await taskName;
24761
Task<UndertaleData?> taskRef = LoadFile(reference);
24862
await taskRef;
24963

250-
if (taskName.Result == null || taskRef.Result == null) return false;
64+
if (taskName.Result == null || taskRef.Result == null) throw new FormatException($"Cannot load {name} and {outputFolder}.");
25165

252-
DiffCodes(taskName.Result, taskRef.Result, dir);
253-
DiffObjects(taskName.Result, taskRef.Result, dir);
254-
DiffRooms(taskName.Result, taskRef.Result, dir);
255-
DiffSounds(taskName.Result, taskRef.Result, dir);
256-
DiffSprites(taskName.Result, taskRef.Result, dir);
257-
DiffTexturePageItems(taskName.Result, taskRef.Result, dir);
66+
DiffUtils.DiffCodes(taskName.Result, taskRef.Result, dir);
67+
DiffUtils.DiffObjects(taskName.Result, taskRef.Result, dir);
68+
DiffUtils.DiffRooms(taskName.Result, taskRef.Result, dir);
69+
DiffUtils.DiffSounds(taskName.Result, taskRef.Result, dir);
70+
DiffUtils.DiffSprites(taskName.Result, taskRef.Result, dir);
71+
DiffUtils.DiffTexturePageItems(taskName.Result, taskRef.Result, dir);
25872
return true;
25973
}
260-
26174
private static UndertaleData? LoadUmt(string filename)
26275
{
26376
UndertaleData? data = null;
@@ -271,7 +84,6 @@ public static async Task<bool> Diff(string name, string reference, string output
27184

27285
return data;
27386
}
274-
27587
public static async Task<UndertaleData?> LoadFile(string filename)
27688
{
27789
UndertaleData? data = null;

0 commit comments

Comments
 (0)