diff --git a/.github/workflows/dll.yaml b/.github/workflows/dll.yaml new file mode 100644 index 0000000..aacf315 --- /dev/null +++ b/.github/workflows/dll.yaml @@ -0,0 +1,52 @@ +name: ACVPatcher Release + +on: + release: + types: + - created + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.204 + - name: Restore dependencies + run: dotnet restore + - name: Set version + if: ${{ github.event_name == 'release' }} + run: '"${{ github.event.release.tag_name }}" | Out-File -NoNewline -FilePath ./VERSION' + - name: Build ACVPatcher + run: dotnet build -c Release + - name: Publish ACVPatcher DLL + run: dotnet publish acvpatcher.sln -c Release /p:DebugType=None /p:DebugSymbols=false + - name: Copy license + run: cp LICENSE ACVPatcher/bin/Release/net8.0/publish/LICENSE.txt + - name: Copy notice + run: cp NOTICE ACVPatcher/bin/Release/net8.0//publish/NOTICE.txt + - name: Artifact Upload DLL + if: ${{ github.event_name != 'release' }} + uses: actions/upload-artifact@v4 + with: + name: DLL + path: ACVPatcher/bin/Release/net8.0/publish/ + - name: Compress Release Artifact + if: ${{ github.event_name == 'release' }} + run: 'Compress-Archive ACVPatcher/bin/Release/net8.0/publish/* ./release.zip' + - name: Upload Release Asset + if: ${{ github.event_name == 'release' }} + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./release.zip + asset_name: DLL.zip + asset_content_type: application/zip \ No newline at end of file diff --git a/.gitignore b/.gitignore index 31e6745..61c347b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ macos-dmg/QuestPatcher.dmg .idea/ # Bug fixing info/crash reports kept locally -diagnosis/ \ No newline at end of file +diagnosis/ +publish/ diff --git a/ACVPatcher/PatchingManager.cs b/ACVPatcher/PatchingManager.cs index e69de29..66aeffc 100644 --- a/ACVPatcher/PatchingManager.cs +++ b/ACVPatcher/PatchingManager.cs @@ -0,0 +1,233 @@ +using System; +using System.IO.Compression; +using CommandLine; +using CommandLine.Text; +using QuestPatcher.Axml; +using QuestPatcher.Zip; + +namespace ACVPatcher +{ + public class PatchingManager + { + private string ApkPath { get; set; } + private IEnumerable? ClassPath { get; set; } + private IEnumerable? Permission { get; set; } + private string? Instrumentation { get; set; } + private IEnumerable? Receivers { get; set; } + + public PatchingManager(string apkpath, IEnumerable? classpaths, IEnumerable? permissions, string? instrumentationTag, IEnumerable? receivers) + { + ApkPath = apkpath; + ClassPath = classpaths; + Permission = permissions; + Instrumentation = instrumentationTag; + Receivers = receivers; + } + + public async Task Run() + { + using var apkStream = File.Open(ApkPath, FileMode.Open); + var apk = await ApkZip.OpenAsync(apkStream); + + if (ClassPath != null) + { + foreach (var classPath in ClassPath) + { + await AddClassToApk(apk, classPath); + } + } + if (Permission != null || Instrumentation != null || Receivers != null) + { + await PatchManifest(apk); + } + + await apk.DisposeAsync(); + } + + private async Task PatchManifest(ApkZip apk) + { + bool modified = false; + using var ms = new MemoryStream(); + using (var stream = await apk.OpenReaderAsync("AndroidManifest.xml")) + { + await stream.CopyToAsync(ms); + } + + ms.Position = 0; + var manifest = AxmlLoader.LoadDocument(ms); + string package = AxmlManager.GetPackage(manifest); + if (Permission != null) + { + var existingPermissions = AxmlManager.GetExistingChildren(manifest, "uses-permission"); + foreach (var permission in Permission) + { + if (existingPermissions.Contains(permission)) { continue; } // Do not add existing permissions + AddPermissionToManifest(manifest, permission); + modified = true; + } + } + if (Instrumentation != null) + { + AddInstrumentationToManifest(manifest, Instrumentation, package); + modified = true; + } + if (Receivers != null) + { + var appElement = manifest.Children.Single(child => child.Name == "application"); + // var existingReceivers = AxmlManager.GetExistingChildren(appElement, "receiver"); + var existingReceiverElements = GetExistingReceiverElements(appElement); + var receiverActions = ParseReceiverActions(Receivers); + foreach (var receiverAction in receiverActions) + { + var receiverName = receiverAction.Key; + List actions = receiverAction.Value; + var receiverElement = existingReceiverElements.ContainsKey(receiverName) ? existingReceiverElements[receiverName] : null; + if (receiverElement == null) + { + receiverElement = AddReceiverToManifest(appElement, receiverName); + existingReceiverElements[receiverName] = receiverElement; + modified = true; + } + + var receiverIntentFilter = receiverElement.Children.Any(ch => ch.Name == "intent-filter") ? receiverElement.Children.Single(ch => ch.Name == "intent-filter") : null; + if (receiverIntentFilter == null) + { + receiverIntentFilter = new AxmlElement("intent-filter"); + receiverElement.Children.Add(receiverIntentFilter); + } + List existingActions = receiverIntentFilter.Children + .Where(ch => ch.Name == "action").Select(ch => ch.Attributes.Single(attr => attr.Name == "name")?.Value as string) + .ToList(); + var newActions = actions.Where(action => action != null).Except(existingActions).ToList(); + + foreach (var action in newActions) + { + AddIntentAction(receiverIntentFilter, action!); + modified = true; + } + } + } + if (modified) + { + ms.SetLength(0); + ms.Position = 0; + AxmlSaver.SaveDocument(ms, manifest); + ms.Position = 0; + await apk.AddFileAsync("AndroidManifest.xml", ms, CompressionLevel.Optimal); + } + } + + private Dictionary GetExistingReceiverElements(AxmlElement appElement) + { + var receiverElements = new Dictionary(); + + foreach (var receiver in appElement.Children) + { + if (receiver.Name != "receiver") { continue; } + + var receiverName = receiver.Attributes.Single(attr => attr.Name == "name")?.Value as string; + if (receiverName != null) + { + receiverElements[receiverName] = receiver; + } + } + return receiverElements; + } + + private Dictionary> GetExistingReceiverActions(AxmlElement appElement) + { + var receiverActions = new Dictionary>(); + + foreach (var receiver in appElement.Children) + { + if (receiver.Name != "receiver") { continue; } + + var receiverName = receiver.Attributes.Single(attr => attr.Name == "name")?.Value as string; + if (receiverName == null) { continue; } + + var intentFilters = receiver.Children.Where(child => child.Name == "intent-filter").ToList(); + if (intentFilters.Count == 0) { continue; } + + var actions = new List(); + foreach (var intentFilter in intentFilters) + { + foreach (var action in intentFilter.Children) + { + if (action.Name != "action") { continue; } + + var actionName = action.Attributes.Single(attr => attr.Name == "name")?.Value as string; + if (actionName != null) + { + actions.Add(actionName); + } + } + } + receiverActions[receiverName] = actions; + } + return receiverActions; + } + + private Dictionary> ParseReceiverActions(IEnumerable receiverArgs) + { + var receiverActions = new Dictionary>(); + + foreach (string receiverArg in receiverArgs) + { + // Split the receiverArg string into two separate variables + var receiverParts = receiverArg.Split(':'); + var receiverClassName = receiverParts[0]; + var actionName = receiverParts[1]; + + if (receiverActions.ContainsKey(receiverClassName)) + { + receiverActions[receiverClassName].Add(actionName); + } + else + { + receiverActions[receiverClassName] = new List { actionName }; + } + } + + return receiverActions; + } + + private AxmlElement AddReceiverToManifest(AxmlElement appElement, string receiver) + { + AxmlElement receiverElement = new("receiver"); + AxmlManager.AddNameAttribute(receiverElement, receiver); + AxmlManager.AddExportedAttribute(receiverElement, true); + AxmlManager.AddEnabledAttribute(receiverElement, true); + appElement.Children.Add(receiverElement); + return receiverElement; + } + + private void AddIntentAction(AxmlElement intentFilterElement, string actionName) + { + AxmlElement actionElement = new("action"); + AxmlManager.AddNameAttribute(actionElement, actionName); + intentFilterElement.Children.Add(actionElement); + } + + private void AddInstrumentationToManifest(AxmlElement manifest, string instrumentationName, string package) + { + AxmlElement instrElement = new("instrumentation"); + AxmlManager.AddNameAttribute(instrElement, instrumentationName); + AxmlManager.AddTargetPackageAttribute(instrElement, package); + manifest.Children.Add(instrElement); + } + + private static void AddPermissionToManifest(AxmlElement manifest, string permission) + { + AxmlElement permElement = new("uses-permission"); + AxmlManager.AddNameAttribute(permElement, permission); + manifest.Children.Add(permElement); + } + + private async Task AddClassToApk(ApkZip apk, string classPath) + { + var fileName = Path.GetFileName(classPath); + using var dexStream = File.OpenRead(classPath); + await apk.AddFileAsync(fileName, dexStream, CompressionLevel.Optimal); + } + } +} \ No newline at end of file diff --git a/ACVPatcher/Program.cs b/ACVPatcher/Program.cs index 43ce905..2355bec 100644 --- a/ACVPatcher/Program.cs +++ b/ACVPatcher/Program.cs @@ -5,8 +5,6 @@ using ACVPatcher; using CommandLine; using CommandLine.Text; -using QuestPatcher.Axml; -using QuestPatcher.Zip; class Options { @@ -30,7 +28,6 @@ class Program { static async Task Main(string[] args) { - var patchingManager = new PatchingManager(); // args = "-p android.permission.WRITE_EXTERNAL_STORAGE -i tool.acv.AcvInstrumentation -r tool.acv.AcvReceiver:tool.acv.calculate -a /Users/ap/projects/dblt/apks/debloatapp/base.apk".Split(' '); Console.WriteLine(string.Join(" ", args)); var options = await Parser.Default.ParseArguments(args).WithParsedAsync(async options => @@ -40,221 +37,8 @@ static async Task Main(string[] args) Console.WriteLine($"Instrumentation: {options.Instrumentation}"); Console.WriteLine($"Receivers: {(options.Receivers != null ? string.Join(", ", options.Receivers) : string.Empty)}"); Console.WriteLine($"APK Path: {options.ApkPath}"); - await patchingManager.Run(options); + var patchingManager = new PatchingManager(options.ApkPath, options.ClassPath, options.Permission, options.Instrumentation, options.Receivers); + await patchingManager.Run(); }); } } - -internal class PatchingManager -{ - public PatchingManager() - { - } - - public async Task Run(Options options) - { - using var apkStream = File.Open(options.ApkPath, FileMode.Open); - var apk = await ApkZip.OpenAsync(apkStream); - - if (options.ClassPath != null) - { - foreach (var classPath in options.ClassPath) - { - await AddClassToApk(apk, classPath); - } - } - if (options.Permission != null || options.Instrumentation != null || options.Receivers != null) - { - await PatchManifest(apk, options); - } - - await apk.DisposeAsync(); - } - - private async Task PatchManifest(ApkZip apk, Options options) - { - bool modified = false; - using var ms = new MemoryStream(); - using (var stream = await apk.OpenReaderAsync("AndroidManifest.xml")) - { - await stream.CopyToAsync(ms); - } - - ms.Position = 0; - var manifest = AxmlLoader.LoadDocument(ms); - string package = AxmlManager.GetPackage(manifest); - if (options.Permission != null) - { - var existingPermissions = AxmlManager.GetExistingChildren(manifest, "uses-permission"); - foreach (var permission in options.Permission) - { - if (existingPermissions.Contains(permission)) { continue; } // Do not add existing permissions - AddPermissionToManifest(manifest, permission); - modified = true; - } - } - if (options.Instrumentation != null) - { - AddInstrumentationToManifest(manifest, options.Instrumentation, package); - modified = true; - } - if (options.Receivers != null) - { - var appElement = manifest.Children.Single(child => child.Name == "application"); - // var existingReceivers = AxmlManager.GetExistingChildren(appElement, "receiver"); - var existingReceiverElements = GetExistingReceiverElements(appElement); - var receiverActions = ParseReceiverActions(options.Receivers); - foreach (var receiverAction in receiverActions) - { - var receiverName = receiverAction.Key; - List actions = receiverAction.Value; - var receiverElement = existingReceiverElements.ContainsKey(receiverName) ? existingReceiverElements[receiverName] : null; - if (receiverElement == null) - { - receiverElement = AddReceiverToManifest(appElement, receiverName); - existingReceiverElements[receiverName] = receiverElement; - modified = true; - } - - var receiverIntentFilter = receiverElement.Children.Any(ch => ch.Name == "intent-filter") ? receiverElement.Children.Single(ch => ch.Name == "intent-filter") : null; - if (receiverIntentFilter == null) - { - receiverIntentFilter = new AxmlElement("intent-filter"); - receiverElement.Children.Add(receiverIntentFilter); - } - List existingActions = receiverIntentFilter.Children - .Where(ch => ch.Name == "action").Select(ch => ch.Attributes.Single(attr => attr.Name == "name")?.Value as string) - .ToList(); - var newActions = actions.Where(action => action != null).Except(existingActions).ToList(); - - foreach (var action in newActions) - { - AddIntentAction(receiverIntentFilter, action!); - modified = true; - } - } - } - if (modified) - { - ms.SetLength(0); - ms.Position = 0; - AxmlSaver.SaveDocument(ms, manifest); - ms.Position = 0; - await apk.AddFileAsync("AndroidManifest.xml", ms, CompressionLevel.Optimal); - } - } - - private Dictionary GetExistingReceiverElements(AxmlElement appElement) - { - var receiverElements = new Dictionary(); - - foreach (var receiver in appElement.Children) - { - if (receiver.Name != "receiver") { continue; } - - var receiverName = receiver.Attributes.Single(attr => attr.Name == "name")?.Value as string; - if (receiverName != null) - { - receiverElements[receiverName] = receiver; - } - } - return receiverElements; - } - - private Dictionary> GetExistingReceiverActions(AxmlElement appElement) - { - var receiverActions = new Dictionary>(); - - foreach (var receiver in appElement.Children) - { - if (receiver.Name != "receiver") { continue; } - - var receiverName = receiver.Attributes.Single(attr => attr.Name == "name")?.Value as string; - if (receiverName == null) { continue; } - - var intentFilters = receiver.Children.Where(child => child.Name == "intent-filter").ToList(); - if (intentFilters.Count == 0) { continue; } - - var actions = new List(); - foreach (var intentFilter in intentFilters) - { - foreach (var action in intentFilter.Children) - { - if (action.Name != "action") { continue; } - - var actionName = action.Attributes.Single(attr => attr.Name == "name")?.Value as string; - if (actionName != null) - { - actions.Add(actionName); - } - } - } - receiverActions[receiverName] = actions; - } - return receiverActions; - } - - private Dictionary> ParseReceiverActions(IEnumerable receiverArgs) - { - var receiverActions = new Dictionary>(); - - foreach (string receiverArg in receiverArgs) - { - // Split the receiverArg string into two separate variables - var receiverParts = receiverArg.Split(':'); - var receiverClassName = receiverParts[0]; - var actionName = receiverParts[1]; - - if (receiverActions.ContainsKey(receiverClassName)) - { - receiverActions[receiverClassName].Add(actionName); - } - else - { - receiverActions[receiverClassName] = new List { actionName }; - } - } - - return receiverActions; - } - - private AxmlElement AddReceiverToManifest(AxmlElement appElement, string receiver) - { - AxmlElement receiverElement = new("receiver"); - AxmlManager.AddNameAttribute(receiverElement, receiver); - AxmlManager.AddExportedAttribute(receiverElement, true); - AxmlManager.AddEnabledAttribute(receiverElement, true); - appElement.Children.Add(receiverElement); - return receiverElement; - } - - private void AddIntentAction(AxmlElement intentFilterElement, string actionName) - { - AxmlElement actionElement = new("action"); - AxmlManager.AddNameAttribute(actionElement, actionName); - intentFilterElement.Children.Add(actionElement); - } - - private void AddInstrumentationToManifest(AxmlElement manifest, string instrumentationName, string package) - { - AxmlElement instrElement = new("instrumentation"); - AxmlManager.AddNameAttribute(instrElement, instrumentationName); - AxmlManager.AddTargetPackageAttribute(instrElement, package); - manifest.Children.Add(instrElement); - } - - private static void AddPermissionToManifest(AxmlElement manifest, string permission) - { - AxmlElement permElement = new("uses-permission"); - AxmlManager.AddNameAttribute(permElement, permission); - manifest.Children.Add(permElement); - } - - private async Task AddClassToApk(ApkZip apk, string classPath) - { - var fileName = Path.GetFileName(classPath); - using var dexStream = File.OpenRead(classPath); - await apk.AddFileAsync(fileName, dexStream, CompressionLevel.Optimal); - } - -}