1+ using Dalamud . Plugin . Services ;
12using Mono . Cecil ;
23using Mono . Cecil . Cil ;
34
@@ -7,11 +8,13 @@ internal class Patcher
78{
89 private Version PluginVersion { get ; }
910 private string WorkPath { get ; }
11+ private IPluginLog PluginLog { get ; }
1012
11- public Patcher ( Version version , string workPath )
13+ public Patcher ( Version version , string workPath , IPluginLog pluginLog )
1214 {
1315 PluginVersion = version ;
1416 WorkPath = workPath ;
17+ PluginLog = pluginLog ;
1518 }
1619
1720 public void MainPlugin ( )
@@ -196,4 +199,112 @@ void UseMarshallCopyBuffer(MethodDefinition method)
196199
197200 memory . WriteOut ( ) ;
198201 }
202+
203+ public void MachinaFFXIV ( )
204+ {
205+ var machinaDalamud = new TargetAssembly ( Path . Combine ( WorkPath , "Machina.FFXIV.Dalamud.dll" ) ) ;
206+
207+ var machina = new TargetAssembly ( Path . Combine ( WorkPath , "Machina.FFXIV.dll" ) ) ;
208+ machina . RemoveStrongNaming ( ) ;
209+
210+ // For the Machina library, we want to replace all usages of `DeucalionClient` with our reimplemented
211+ // `NotDeucalionClient`. That class is used only in `Machina.FFXIV.FFXIVNetworkMonitor` class. So we need
212+ // to do the following modifications:
213+ //
214+ // 1. Replace its private member `_deucalionClient`'s type.
215+ // 2. In its every method:
216+ // - Replace all its `ldfld` and `stfld` instructions of the current class's `_deucalionClient` member.
217+ // - Replace all its `ldfld` and `stfld` instructions of `DeucalionClient`'s members with `NotDeucalionClient`'s
218+ // corresponding members. Thay have the same names and types, so we can just replace the type references.
219+ // - Replace all its `newobj` and `call*` instructions of `DeucalionClient`'s methods with `NotDeucalionClient`'s
220+ // corresponding methods.
221+ // - To prevent the Deucalion library from being really injected, replace all its `call*` instructions that to
222+ // class `DeucalionInjector` with `NotDeucalionInjector`.
223+ // 3. Safely remove the `DeucalionClient` and `DeucalionInjector` classes from the Machina library.
224+
225+ var oldClientFullName = "Machina.FFXIV.Deucalion.DeucalionClient" ;
226+ var newClientFullName = "Machina.FFXIV.Dalamud.NotDeucalionClient" ;
227+ var oldClientDefiniton = machina . Assembly . MainModule . Types . First ( type => type . FullName == oldClientFullName ) ;
228+ var newClientDefiniton = machinaDalamud . Assembly . MainModule . Types . First ( type => type . FullName == newClientFullName ) ;
229+
230+ var oldInjectorFullName = "Machina.FFXIV.Deucalion.DeucalionInjector" ;
231+ var newInjectorFullName = "Machina.FFXIV.Dalamud.NotDeucalionInjector" ;
232+ var oldInjectorDefiniton = machina . Assembly . MainModule . Types . First ( type => type . FullName == oldInjectorFullName ) ;
233+ var newInjectorDefiniton = machinaDalamud . Assembly . MainModule . Types . First ( type => type . FullName == newInjectorFullName ) ;
234+
235+ // For method replacements, also replace for the nested types of `DeucalionClient`.
236+ // (i.e. `MessageReceivedHandler` / `MessageSentHandler` delegates)
237+ var typeReplacements = new Dictionary < string , TypeDefinition >
238+ {
239+ { oldClientFullName , newClientDefiniton } ,
240+ { oldInjectorFullName , newInjectorDefiniton } ,
241+ } ;
242+ foreach ( var nestedType in oldClientDefiniton . NestedTypes )
243+ {
244+ var target = newClientDefiniton . NestedTypes . FirstOrDefault (
245+ type => type . Name == nestedType . Name ) ;
246+ if ( target != null )
247+ typeReplacements [ nestedType . FullName ] = target ;
248+ }
249+
250+ // Get the `FFXIVNetworkMonitor` class.
251+ var ffxivNetworkMonitor = machina . Assembly . MainModule . Types . First ( type =>
252+ type . FullName == "Machina.FFXIV.FFXIVNetworkMonitor" ) ;
253+
254+ {
255+ // Step 1 - Replace its private member `_deucalionClient`'s type.
256+ var deucalionClientField = ffxivNetworkMonitor . Fields . First ( field => field . Name == "_deucalionClient" ) ;
257+ deucalionClientField . FieldType = machina . Assembly . MainModule . ImportReference ( newClientDefiniton ) ;
258+ }
259+ {
260+ // Step 2
261+ foreach ( var method in ffxivNetworkMonitor . Methods )
262+ {
263+ foreach ( var instruction in method . Body . Instructions )
264+ {
265+ if ( instruction . OpCode == OpCodes . Newobj ||
266+ instruction . OpCode == OpCodes . Call ||
267+ instruction . OpCode == OpCodes . Calli ||
268+ instruction . OpCode == OpCodes . Callvirt )
269+ {
270+ var methodReference = ( MethodReference ) instruction . Operand ;
271+ foreach ( var ( oldType , newType ) in typeReplacements )
272+ {
273+ if ( methodReference . DeclaringType . FullName == oldType )
274+ {
275+ var newMethodDefinition = newType . Methods . FirstOrDefault ( m =>
276+ m . Name == methodReference . Name && m . Parameters . Count == methodReference . Parameters . Count ) ;
277+ if ( newMethodDefinition != null )
278+ instruction . Operand = machina . Assembly . MainModule . ImportReference ( newMethodDefinition ) ;
279+ else
280+ {
281+ PluginLog . Error ( $ "[Patcher] Could not find method { methodReference . Name } in { newType . FullName } ") ;
282+ }
283+ }
284+ }
285+ }
286+ else if ( instruction . OpCode == OpCodes . Ldfld || instruction . OpCode == OpCodes . Stfld )
287+ {
288+ var fieldReference = ( FieldReference ) instruction . Operand ;
289+ if ( fieldReference . DeclaringType . FullName == ffxivNetworkMonitor . FullName &&
290+ fieldReference . FieldType . FullName == oldClientFullName )
291+ {
292+ fieldReference . FieldType = machina . Assembly . MainModule . ImportReference ( newClientDefiniton ) ;
293+ }
294+ else if ( fieldReference . DeclaringType . FullName == oldClientFullName )
295+ {
296+ var newFieldDefiniton = newClientDefiniton . Fields . First ( f => f . Name == fieldReference . Name ) ;
297+ instruction . Operand = machina . Assembly . MainModule . ImportReference ( newFieldDefiniton ) ;
298+ }
299+ }
300+ }
301+ }
302+ }
303+
304+ // Step 3 - Remove the unused classes.
305+ machina . Assembly . MainModule . Types . Remove ( oldClientDefiniton ) ;
306+ machina . Assembly . MainModule . Types . Remove ( oldInjectorDefiniton ) ;
307+
308+ machina . WriteOut ( ) ;
309+ }
199310}
0 commit comments