11using System . CommandLine ;
2- using System . CommandLine . Invocation ;
3- using System . Security . Cryptography ;
2+ using System . Runtime . InteropServices ;
43using System . Text ;
4+ using AffinityPatcher . Enums ;
5+ using AffinityPatcher . Utils ;
56using dnlib . DotNet ;
67using dnlib . DotNet . Emit ;
78using dnlib . DotNet . Writer ;
@@ -12,81 +13,109 @@ namespace AffinityPatcher
1213{
1314 internal class Program
1415 {
15- // Token: 0x02000502 RID: 1282
16- public enum CrashReportUploadPolicy
17- {
18- // Token: 0x04009C1F RID: 39967
19- User ,
20-
21- // Token: 0x04009C20 RID: 39968
22- Always ,
23-
24- // Token: 0x04009C21 RID: 39969
25- Never
26- }
27-
2816 static async Task < int > Main ( string [ ] args )
2917 {
3018 var rootCommand =
31- new RootCommand ( "Simple application for patching license activation amongst Affinity v2.x/v1.x products." ) ;
19+ new RootCommand ( "Universal application patcher for Affinity v2.x/v1.x products and DxO PhotoLab ." ) ;
3220
3321 var inputOptions = new Option < DirectoryInfo ? > ( "--input" ,
34- description : "Target Affinity directory (i.e., path containing Photo/Designer.exe)." )
35- { IsRequired = true } ;
22+ description : "Target application directory (i.e., path containing the main executable)." )
23+ { IsRequired = true } ;
24+
25+ var modeOptions = new Option < PatcherMode > ( "--mode" ,
26+ description : "Select which application to patch." )
27+ { IsRequired = false } ;
28+ modeOptions . SetDefaultValue ( PatcherMode . Affinity ) ;
29+
3630 var verboseOptions = new Option < bool > ( "--verbose" , description : "Enable verbose logging." ) ;
3731 var backupOptions = new Option < bool > ( "--keep" , description : "Backup original assembly." ) ;
3832
3933 rootCommand . AddOption ( inputOptions ) ;
34+ rootCommand . AddOption ( modeOptions ) ;
4035 rootCommand . AddOption ( verboseOptions ) ;
4136 rootCommand . AddOption ( backupOptions ) ;
4237
43- rootCommand . SetHandler ( ( di , shouldVerbose , shouldBackup ) =>
38+ rootCommand . SetHandler ( ( di , mode , shouldVerbose , shouldBackup ) =>
4439 {
4540 if ( di is not { Exists : true } )
46- {
47- throw new DirectoryNotFoundException ( "Cannot find the target Affinity directory." ) ;
48- }
41+ throw new DirectoryNotFoundException ( $ "Cannot find the target { mode } directory.") ;
42+
43+ bool hasWriteAccess = DirectoryAccessGeneric . HasWriteAccess ( di . FullName ) ;
44+ if ( shouldVerbose )
45+ AnsiConsole . MarkupLine ( $ "[grey]Checking write access to { di . FullName } : { hasWriteAccess } [/]") ;
46+ if ( ! hasWriteAccess )
47+ throw new AccessViolationException ( "Target directory does not have write access. You may need to re-run this application as administrator." ) ;
4948
50- var personaAssembly = FindPersonaAssembly ( di ) ;
51- if ( personaAssembly == null )
49+ AnsiConsole . MarkupLine (
50+ $ "[grey]Patching product \" { mode } \" - if this is incorrect, make sure you have selected the desired product via the \" --mode\" switch.[/]") ;
51+ switch ( mode )
5252 {
53- throw new FileNotFoundException ( "Cannot find the required assembly." ) ;
53+ case PatcherMode . Affinity :
54+ PatchAffinity ( di , shouldVerbose , shouldBackup ) ;
55+ break ;
56+ case PatcherMode . DxO :
57+ PatchDxO ( di , shouldVerbose , shouldBackup ) ;
58+ break ;
59+ default :
60+ throw new ArgumentException ( "Invalid patcher mode specified." ) ;
5461 }
62+ } , inputOptions , modeOptions , verboseOptions , backupOptions ) ;
5563
56- PatchPersonaAssembly ( personaAssembly . FullName , verbose : shouldVerbose , keepOriginal : shouldBackup ) ;
57- } , inputOptions , verboseOptions , backupOptions ) ;
64+ return await rootCommand . InvokeAsync ( args ) ;
65+ }
5866
67+ private static void PatchAffinity ( DirectoryInfo directoryInfo , bool verbose , bool keepOriginal )
68+ {
69+ AnsiConsole . MarkupLine ( "[blue]Starting Affinity patching process...[/]" ) ;
5970
60- return await rootCommand . InvokeAsync ( args ) ;
71+ var personaAssembly = FindAssembly ( "Serif.Interop.Persona.dll" , directoryInfo ) ;
72+ if ( personaAssembly == null )
73+ throw new FileNotFoundException ( "Cannot find the required Affinity assembly (Serif.Interop.Persona.dll)." ) ;
74+
75+ PatchAffinityAssembly ( personaAssembly . FullName , verbose : verbose , keepOriginal : keepOriginal ) ;
76+ }
77+
78+ private static void PatchDxO ( DirectoryInfo directoryInfo , bool verbose , bool keepOriginal )
79+ {
80+ AnsiConsole . MarkupLine ( "[blue]Starting DxO PhotoLab patching process...[/]" ) ;
81+
82+ var activationAssembly = FindAssembly ( "DxO.PhotoLab.Activation.dll" , directoryInfo ) ;
83+ var activationInteropAssembly = FindAssembly ( "DxO.PhotoLab.Activation.Interop.dll" , directoryInfo ) ;
84+
85+ if ( activationAssembly == null || activationInteropAssembly == null )
86+ throw new FileNotFoundException ( "Cannot find the required DxO assemblies." ) ;
87+
88+ PatchDxOAssembly ( activationAssembly . FullName , verbose : verbose , keepOriginal : keepOriginal ) ;
89+ PatchDxOAssembly ( activationInteropAssembly . FullName , verbose : verbose , keepOriginal : keepOriginal ) ;
6190 }
6291
63- static FileInfo ? FindPersonaAssembly ( DirectoryInfo ? directoryInfo )
92+ private static FileInfo ? FindAssembly ( string dllName , DirectoryInfo ? directoryInfo )
6493 {
65- var targetPath = Path . Join ( directoryInfo ? . FullName , "Serif.Interop.Persona.dll" ) ;
66- var fi = new FileInfo ( targetPath ) ;
67- if ( fi . Exists ) return fi ;
6894 // use the backup file if one exists
69- targetPath = Path . Join ( directoryInfo ? . FullName , "Serif.Interop.Persona.dll.bak" ) ;
70- fi = new FileInfo ( targetPath ) ;
71- return fi . Exists ? fi : null ;
95+ var expectedPath = Path . Join ( directoryInfo ? . FullName , dllName ) ;
96+ var expectedFi = new FileInfo ( expectedPath ) ;
97+ return expectedFi . Exists ? expectedFi : null ;
7298 }
7399
74- static void PatchPersonaAssembly ( string targetFile , bool verbose , bool keepOriginal )
100+ private static void PatchAffinityAssembly ( string targetFile , bool verbose , bool keepOriginal )
75101 {
76102 if ( keepOriginal )
77103 {
78104 File . Copy ( targetFile , targetFile + ".bak" , overwrite : true ) ;
79- AnsiConsole . MarkupLine ( "[green]Backed up original assembly.[/]" ) ;
105+ AnsiConsole . MarkupLine ( "[green]Backed up original Affinity assembly.[/]" ) ;
80106 }
81107
82108 var moduleContext = ModuleDef . CreateModuleContext ( ) ;
83109 var tempOutput = Path . GetTempFileName ( ) ;
110+
84111 using ( var module = ModuleDefMD . Load ( targetFile , moduleContext ) )
85112 {
86113 var patchedList = new List < string > ( ) ;
87114 var application = module . Types . FirstOrDefault ( x => x . FullName == "Serif.Interop.Persona.Application" ) ;
115+
88116 var methodsToPatchToTrue = application ? . Methods . Where ( x =>
89117 x . Name == "HasEntitlementToRun" || x . Name == "CheckEula" || x . Name == "CheckAnalytics" ) ;
118+
90119 if ( methodsToPatchToTrue != null )
91120 {
92121 foreach ( var method in methodsToPatchToTrue )
@@ -97,15 +126,14 @@ static void PatchPersonaAssembly(string targetFile, bool verbose, bool keepOrigi
97126 $ "Located [grey]{ method . FullName } [/], patching with [grey]\" return true\" [/].") ;
98127 }
99128
100- PatchWithLdcRet ( method . Body , 1 ) ;
129+ Patcher . PatchWithLdcRet ( method . Body , 1 ) ;
101130 patchedList . Add ( method . FullName ) ;
102131 }
103132 }
104133
105134 var methodsToPatchToFalse = application ? . Methods . Where ( x => x . Name == "get_AllowsOptInAnalytics" ) ;
106135 if ( methodsToPatchToFalse != null )
107136 {
108-
109137 foreach ( var method in methodsToPatchToFalse )
110138 {
111139 if ( verbose )
@@ -114,7 +142,7 @@ static void PatchPersonaAssembly(string targetFile, bool verbose, bool keepOrigi
114142 $ "Located [grey]{ method . FullName } [/], patching with [grey]\" return false\" [/].") ;
115143 }
116144
117- PatchWithLdcRet ( method . Body , 0 ) ;
145+ Patcher . PatchWithLdcRet ( method . Body , 0 ) ;
118146 patchedList . Add ( method . FullName ) ;
119147 }
120148 }
@@ -128,53 +156,128 @@ static void PatchPersonaAssembly(string targetFile, bool verbose, bool keepOrigi
128156 $ "Located [grey]{ crashPolicy . FullName } [/], patching as [grey]{ CrashReportUploadPolicy . Never . Humanize ( ) } .[/]") ;
129157 }
130158
131- PatchWithLdcRet ( crashPolicy . Body , ( int ) CrashReportUploadPolicy . Never ) ;
159+ Patcher . PatchWithLdcRet ( crashPolicy . Body , ( int ) CrashReportUploadPolicy . Never ) ;
132160 patchedList . Add ( crashPolicy . FullName ) ;
133161 }
134162
135- AnsiConsole . Status ( ) . Spinner ( Spinner . Known . Aesthetic )
136- . Start ( "Saving assembly..." , x =>
163+ SaveAssembly ( module , tempOutput , patchedList , "Affinity" ) ;
164+ }
165+
166+ FinalizeAssembly ( targetFile , tempOutput ) ;
167+ }
168+
169+ private static void PatchDxOAssembly ( string targetFile , bool verbose , bool keepOriginal )
170+ {
171+ if ( keepOriginal )
172+ {
173+ var backupPath = targetFile + ".bak" ;
174+ File . Copy ( targetFile , backupPath , overwrite : true ) ;
175+ AnsiConsole . MarkupLine ( $ "[green]Backed up original DxO assembly as \" { backupPath } \" .[/]") ;
176+ }
177+
178+ var moduleContext = ModuleDef . CreateModuleContext ( ) ;
179+ var tempOutput = Path . GetTempFileName ( ) ;
180+
181+ using ( var module = ModuleDefMD . Load ( targetFile , moduleContext ) )
182+ {
183+ var patchedList = new List < string > ( ) ;
184+ var features = module . Types . Where ( x => x . FullName . Contains ( "DxO.PhotoLab.Activation.Feature" ) || x . FullName . Contains ( "DxOActivation" ) ) ;
185+
186+ foreach ( var feature in features )
187+ {
188+ var methodsToPatchToTrue = feature ? . Methods . Where ( x => x . HasBody ) . Where ( x =>
189+ x . Name . EndsWith ( "IsValid" ) || x . Name . EndsWith ( "HasAnyLicense" ) ||
190+ x . Name . EndsWith ( "IsActivated" ) || x . Name . EndsWith ( "IsElite" ) ) ;
191+
192+ if ( methodsToPatchToTrue != null )
137193 {
138- if ( module . IsILOnly )
194+ foreach ( var method in methodsToPatchToTrue )
139195 {
140- module . Write ( tempOutput ) ;
196+ Patcher . PatchWithLdcRetVerbose ( method . FullName , method . Body , 1 , verbose ) ;
197+ patchedList . Add ( method . FullName ) ;
141198 }
142- else
199+ }
200+
201+ var methodsToPatchToFalse = feature ? . Methods . Where ( x => x . HasBody ) . Where ( x =>
202+ x . Name . EndsWith ( "IsExpired" ) || x . Name . EndsWith ( "IsDemo" ) ||
203+ x . Name . EndsWith ( "IsTemporary" ) || x . Name == "Check" ) ;
204+
205+ if ( methodsToPatchToFalse != null )
206+ {
207+ foreach ( var method in methodsToPatchToFalse )
143208 {
144- var writerOptions = new NativeModuleWriterOptions ( module , false ) ;
145- module . NativeWrite ( tempOutput , writerOptions ) ;
209+ Patcher . PatchWithLdcRetVerbose ( method . FullName , method . Body , 0 , verbose ) ;
210+ patchedList . Add ( method . FullName ) ;
146211 }
147- } ) ;
212+ }
148213
149- var sb = new StringBuilder ( ) ;
150- foreach ( var patched in patchedList )
151- {
152- sb . AppendLine ( $ "- [green]{ patched } [/]") ;
214+ var methodsToPatchToTwo = feature ? . Methods . Where ( x => x . HasBody ) . Where ( x => x . Name . EndsWith ( "DemoType" ) ) ;
215+ if ( methodsToPatchToTwo != null )
216+ {
217+ foreach ( var method in methodsToPatchToTwo )
218+ {
219+ Patcher . PatchWithLdcRetVerbose ( method . FullName , method . Body , 2 , verbose ) ;
220+ patchedList . Add ( method . FullName ) ;
221+ }
222+ }
223+
224+ var methodsToPatchToSpecifiedAmount = feature ? . Methods . Where ( x => x . HasBody ) . Where ( x =>
225+ x . Name . EndsWith ( "RemainingDays" ) || x . Name == "RemainingOfflineDays" ) ;
226+
227+ if ( methodsToPatchToSpecifiedAmount != null )
228+ {
229+ foreach ( var method in methodsToPatchToSpecifiedAmount )
230+ {
231+ Patcher . PatchWithLdcRetVerbose ( method . FullName , method . Body , 99 , verbose ) ;
232+ patchedList . Add ( method . FullName ) ;
233+ }
234+ }
153235 }
154236
155- var panel = new Panel ( sb . ToString ( ) ) ;
156- panel . Padding = new Padding ( 1 , 1 ) ;
157- panel . Header ( "Patched" ) ;
158- AnsiConsole . Write ( panel ) ;
237+ SaveAssembly ( module , tempOutput , patchedList , "DxO" ) ;
159238 }
160239
161- if ( targetFile . EndsWith ( ".bak" ) )
240+ FinalizeAssembly ( targetFile , tempOutput ) ;
241+ }
242+
243+ private static void SaveAssembly ( ModuleDefMD module , string tempOutput , List < string > patchedList , string appName )
244+ {
245+ AnsiConsole . Status ( ) . Spinner ( Spinner . Known . Aesthetic )
246+ . Start ( $ "Saving { appName } assembly...", _ =>
247+ {
248+ if ( module . IsILOnly )
249+ {
250+ module . Write ( tempOutput ) ;
251+ }
252+ else
253+ {
254+ var writerOptions = new NativeModuleWriterOptions ( module , false ) ;
255+ module . NativeWrite ( tempOutput , writerOptions ) ;
256+ }
257+ } ) ;
258+
259+ var sb = new StringBuilder ( ) ;
260+ foreach ( var patched in patchedList )
162261 {
163- targetFile = targetFile . Replace ( ".bak" , " ") ;
262+ sb . AppendLine ( $ "- [green] { patched } [/] ") ;
164263 }
165- File . Move ( tempOutput , targetFile , overwrite : true ) ;
166264
167- AnsiConsole . MarkupLine (
168- $ "[green]Assembly saved as { targetFile } [/] with file size [bold]{ new FileInfo ( targetFile ) . Length . Bytes ( ) . Humanize ( ) } [/].") ;
265+ var panel = new Panel ( sb . ToString ( ) )
266+ {
267+ Padding = new Padding ( 1 , 1 )
268+ } ;
269+ panel . Header ( $ "Patched ({ appName } )") ;
270+ AnsiConsole . Write ( panel ) ;
169271 }
170272
171- static void PatchWithLdcRet ( CilBody cilBody , int ldcValue )
273+ private static void FinalizeAssembly ( string targetFile , string tempOutput )
172274 {
173- cilBody . Instructions . Clear ( ) ;
174- cilBody . ExceptionHandlers . Clear ( ) ;
175- cilBody . Variables . Clear ( ) ;
176- cilBody . Instructions . Add ( Instruction . CreateLdcI4 ( ldcValue ) ) ;
177- cilBody . Instructions . Add ( Instruction . Create ( OpCodes . Ret ) ) ;
275+ if ( targetFile . EndsWith ( ".bak" ) ) targetFile = targetFile . Replace ( ".bak" , "" ) ;
276+
277+ File . Move ( tempOutput , targetFile , overwrite : true ) ;
278+
279+ AnsiConsole . MarkupLine (
280+ $ "[green]Assembly saved as { targetFile } [/] with file size [bold]{ new FileInfo ( targetFile ) . Length . Bytes ( ) . Humanize ( ) } [/].") ;
178281 }
179282 }
180- }
283+ }
0 commit comments