Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 82 additions & 3 deletions AffinityPatcher/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,28 @@ private static void PatchAffinity(DirectoryInfo directoryInfo, bool verbose, boo
{
AnsiConsole.MarkupLine("[blue]Starting Affinity patching process...[/]");

// Persona patching
var personaAssembly = FindAssembly("Serif.Interop.Persona.dll", directoryInfo);
if (personaAssembly == null)
throw new FileNotFoundException("Cannot find the required Affinity assembly (Serif.Interop.Persona.dll).");
if (personaAssembly != null)
{
PatchAffinityAssembly(personaAssembly.FullName, verbose: verbose, keepOriginal: keepOriginal);
}
else
{
AnsiConsole.MarkupLine("[yellow]Warning: Serif.Interop.Persona.dll not found. Main patches skipped.[/]");
}

PatchAffinityAssembly(personaAssembly.FullName, verbose: verbose, keepOriginal: keepOriginal);
// Patch CloudServices (fix for Issue #11)
var cloudServicesAssembly = FindAssembly("Affinity.CloudServices.dll", directoryInfo);
if (cloudServicesAssembly != null)
{
PatchCloudServicesAssembly(cloudServicesAssembly.FullName, verbose, keepOriginal);
}
else
{
// If no separate DLL exists, classes may be in Persona (older versions)
AnsiConsole.MarkupLine("[yellow]Affinity.CloudServices.dll not found. Skipping extra analytics patches.[/]");
}
}

private static void PatchDxO(DirectoryInfo directoryInfo, bool verbose, bool keepOriginal)
Expand Down Expand Up @@ -199,6 +216,68 @@ private static void PatchAffinityAssembly(string targetFile, bool verbose, bool
FinalizeAssembly(targetFile, tempOutput);
}

private static void PatchCloudServicesAssembly(string targetFile, bool verbose, bool keepOriginal)
{
AnsiConsole.MarkupLine("[blue]Patching Affinity Cloud Services to block analytics...[/]");

if (keepOriginal)
{
File.Copy(targetFile, targetFile + ".bak", overwrite: true);
AnsiConsole.MarkupLine("[green]Backed up Affinity CloudServices assembly.[/]");
}

var moduleContext = ModuleDef.CreateModuleContext();
var tempOutput = Path.GetTempFileName();
var patchedList = new List<string>();

using (var module = ModuleDefMD.Load(targetFile, moduleContext))
{
// Search for analytics-related types
var analyticsTypes = module.GetTypes().Where(t =>
t.FullName.Contains("Analytics") ||
t.FullName.Contains("Telemetry") ||
t.FullName.Contains("Snowplow"));

foreach (var type in analyticsTypes)
{
// Search for methods that appear to send or initialize data
var methodsToNeutralize = type.Methods.Where(m =>
m.HasBody &&
(m.Name.StartsWith("Upload") ||
m.Name.StartsWith("Send") ||
m.Name.StartsWith("Track") ||
m.Name.StartsWith("Init") ||
m.Name.Contains("Event")));
Comment on lines +244 to +250
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The methods identified for neutralization use name-based heuristics (StartsWith/Contains) which could potentially match unrelated methods. For example, methods named "SendMessage" or "InitializeConfiguration" might be patched even if they're not analytics-related. Consider adding additional validation, such as checking method signatures, parameter types, or verifying that the method actually contains analytics-related code patterns before patching.

Copilot uses AI. Check for mistakes.

foreach (var method in methodsToNeutralize)
{
// If it returns void, use a simple Ret
if (method.ReturnType.ElementType == ElementType.Void)
{
Patcher.PatchWithRetVerbose(method.FullName, method.Body, verbose);
patchedList.Add(method.FullName);
}
// If it returns bool, return false (0)
else if (method.ReturnType.ElementType == ElementType.Boolean)
{
Patcher.PatchWithLdcRetVerbose(method.FullName, method.Body, 0, verbose);
patchedList.Add(method.FullName);
}
Comment thread
PGSCOM marked this conversation as resolved.
// For other return types, do not patch but log when verbose is enabled
else if (verbose)
{
AnsiConsole.MarkupLine(
$"[yellow]Skipping method '{method.FullName}' with unsupported return type '{method.ReturnType.FullName}' (not void/bool).[/]");
}
}
}
Comment on lines +236 to +273
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type-filtering logic searches for types containing "Analytics", "Telemetry", or "Snowplow". However, the code doesn't check if any matching types were actually found before iterating. If no analytics types exist in the assembly, the patching completes silently without any indication that nothing was patched. Consider adding a check after line 239 to log whether any analytics types were found, especially in verbose mode.

Copilot uses AI. Check for mistakes.
Comment on lines +241 to +273
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.

SaveAssembly(module, tempOutput, patchedList, "Affinity.CloudServices");
}

FinalizeAssembly(targetFile, tempOutput);
}

private static void PatchDxOAssembly(string targetFile, bool verbose, bool keepOriginal)
{
if (keepOriginal)
Expand Down
14 changes: 14 additions & 0 deletions AffinityPatcher/Utils/Patcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,18 @@ public static void PatchWithLdcRetVerbose(string methodFullName, CilBody cilBody

PatchWithLdcRet(cilBody, ldcValue);
}
public static void PatchWithRet(CilBody cilBody)
{
cilBody.Instructions.Clear();
cilBody.ExceptionHandlers.Clear();
cilBody.Variables.Clear();
cilBody.Instructions.Add(Instruction.Create(OpCodes.Ret));
}

public static void PatchWithRetVerbose(string methodFullName, CilBody cilBody, bool verbose = false)
{
if (verbose)
AnsiConsole.MarkupLine($"Located [grey]{methodFullName}[/], patching with [grey]\"return;\" (void)[/].");
PatchWithRet(cilBody);
}
}