1+ using System . Diagnostics ;
12using Serilog ;
23using UndertaleModLib ;
34using UndertaleModLib . Models ;
4- using DiffMatchPatch ;
5- using UndertaleModLib . Decompiler ;
6- using System . Runtime ;
7- using UndertaleModLib . Util ;
85
96namespace ModShardDiff ;
107internal 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-
9648internal 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