mirror of
https://github.com/AndnixSH/APKToolGUI.git
synced 2026-05-04 11:02:27 +00:00
Implement split apk merging. Use own temp directory
Apk merging is based on https://github.com/shadow578/ApksMerger
This commit is contained in:
@@ -267,6 +267,24 @@
|
||||
<Reference Include="WindowsFormsIntegration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AndroidRes\AndroidResourceMerger.cs" />
|
||||
<Compile Include="AndroidRes\AndroidResources.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidAttribute.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidBool.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidInteger.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidPlural.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidPublic.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidString.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidStyle.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidStyleable.cs" />
|
||||
<Compile Include="AndroidRes\Model\AndroidTypedItem.cs" />
|
||||
<Compile Include="AndroidRes\Model\GenericArrayTypes.cs" />
|
||||
<Compile Include="AndroidRes\Model\GenericTypes.cs" />
|
||||
<Compile Include="AndroidRes\Model\Generic\AndroidGeneric.cs" />
|
||||
<Compile Include="AndroidRes\Model\Generic\AndroidGenericArray.cs" />
|
||||
<Compile Include="AndroidRes\Model\Generic\AndroidResource.cs" />
|
||||
<Compile Include="AndroidRes\Util\ClassExtensions.cs" />
|
||||
<Compile Include="AndroidRes\Util\Log.cs" />
|
||||
<Compile Include="ApkTool\AaptParser.cs" />
|
||||
<Compile Include="ApkTool\ApkFixer.cs" />
|
||||
<Compile Include="ApkTool\Apktool.cs">
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
//https://github.com/shadow578/ApksMerger
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using APKSMerger.AndroidRes.Model;
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using APKSMerger.Util;
|
||||
using APKToolGUI.Utils;
|
||||
|
||||
namespace APKSMerger.AndroidRes
|
||||
{
|
||||
/// <summary>
|
||||
/// merges android resource files
|
||||
/// </summary>
|
||||
public sealed class AndroidMerger
|
||||
{
|
||||
/// <summary>
|
||||
/// check capabilities of the base and splits, warn if (common) libs are missing
|
||||
/// </summary>
|
||||
/// <param name="locales">list of supported locales; key is locale, value is name of dir that first included it</param>
|
||||
/// <param name="abis">list of supported abis; key is abi, value is name of dir that first included it</param>
|
||||
/// <param name="baseDir">base project dir</param>
|
||||
/// <param name="splits">split dirs</param>
|
||||
public void CollectCapabilities(out Dictionary<string, string> locales, out Dictionary<string, string> abis,
|
||||
DirectoryInfo baseDir, params DirectoryInfo[] splits)
|
||||
{
|
||||
//init dicts
|
||||
Log.i("collecting info about splits...");
|
||||
locales = new Dictionary<string, string>();
|
||||
abis = new Dictionary<string, string>();
|
||||
|
||||
//combine base and splits into one list
|
||||
List<DirectoryInfo> allDir = new List<DirectoryInfo>();
|
||||
allDir.Add(baseDir);
|
||||
allDir.AddRange(splits);
|
||||
|
||||
//check all dirs, collect infos about them
|
||||
foreach (DirectoryInfo d in allDir)
|
||||
{
|
||||
//check exists
|
||||
if (!d.Exists)
|
||||
{
|
||||
Directory.CreateDirectory(baseDir.FullName);
|
||||
Log.w($"Create baseDir {baseDir.FullName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
//get all library archs included in this dir
|
||||
//a decompiled apk dir may have a lib directory that contains native libraries for all archs supported by that apk (or split)
|
||||
//the archs are splitted into their own directories, depending on the arch they're for
|
||||
string libsDir = Path.Combine(d.FullName, "lib");
|
||||
if (Directory.Exists(libsDir))
|
||||
{
|
||||
foreach (string arch in Directory.EnumerateDirectories(libsDir))
|
||||
{
|
||||
//get name of arch
|
||||
string archName = Path.GetFileName(arch);
|
||||
|
||||
//add arch to lists of abis
|
||||
if (!abis.ContainsKey(archName))
|
||||
{
|
||||
Log.v($"{d.Name} includes abi {archName}");
|
||||
abis.Add(archName, d.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
//double arch?
|
||||
Log.w($"arch {archName} already included by {abis[archName]} - in {d.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.v($"{d.Name} does not include abis");
|
||||
}
|
||||
|
||||
//get all locales included in this dir
|
||||
//extra locales are defined in strings.xml files in directories named values-<LOCALE_NAME>
|
||||
//locale name seems to be formatted as ISO 639, but with an extra r (so en-GB == en-rGB)
|
||||
string resDir = Path.Combine(d.FullName, "res");
|
||||
if (Directory.Exists(resDir))
|
||||
{
|
||||
//add all dirs matching pattern (like values-en-rGB)
|
||||
foreach (string lang in Directory.EnumerateDirectories(resDir, @"values-*"))
|
||||
{
|
||||
//check directory contains a strings.xml
|
||||
if (!File.Exists(Path.Combine(lang, "strings.xml")))
|
||||
continue;
|
||||
|
||||
//get name of lang
|
||||
string langName = Path.GetFileName(lang).ReplaceFirst("values-", "");
|
||||
|
||||
//add lang to list of locales
|
||||
if (!locales.ContainsKey(langName))
|
||||
{
|
||||
Log.v($"{d.Name} included locale {langName}");
|
||||
locales.Add(langName, d.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
//double lang?
|
||||
Log.w($"locale {langName} already included by {locales[langName]} - in {d.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.v($"{d.Name} does not include locales");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// merge all splits into the base project dir
|
||||
/// </summary>
|
||||
/// <param name="baseDir">base project dir</param>
|
||||
/// <param name="splits">split dirs to merge</param>
|
||||
public void MergeSplits(DirectoryInfo baseDir, params DirectoryInfo[] splits)
|
||||
{
|
||||
//Log.v($"Base dir: {baseDir.FullName}");
|
||||
//check all dirs exists
|
||||
if (!baseDir.Exists)
|
||||
{
|
||||
Directory.CreateDirectory(baseDir.FullName);
|
||||
Log.w($"Create baseDir {baseDir.FullName}");
|
||||
//return;
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo dir in splits)
|
||||
{
|
||||
//Debug.WriteLine(dir);
|
||||
if (!dir.Exists)
|
||||
{
|
||||
Log.e($"split dir {dir.FullName} dos not exist!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<string> assetPacks = new List<string>();
|
||||
//enumarate all splitted files
|
||||
Dictionary</*original*/string, /*replacement*/string> globalNameReplacements = new Dictionary<string, string>();
|
||||
foreach (DirectoryInfo split in splits)
|
||||
{
|
||||
//Log.v($"Split dir: {split.FullName}");
|
||||
split.EnumerateAllFiles("*.*", true, (FileInfo splittedFile) =>
|
||||
{
|
||||
if (splittedFile.FullName.Contains("AndroidManifest.xml"))
|
||||
{
|
||||
string manifest = File.ReadAllText(splittedFile.FullName);
|
||||
string splitModule = StringExt.Regex(@"(?<= split=\"")(.*?)(?=\"")", manifest);
|
||||
|
||||
if (!String.IsNullOrEmpty(splitModule) && manifest.Contains("dist:type=\"asset-pack\""))
|
||||
{
|
||||
Log.v($"Add module: {splitModule}");
|
||||
assetPacks.Add(splitModule);
|
||||
}
|
||||
}
|
||||
|
||||
//Debug.WriteLine($"Splited file: {splittedFile.FullName}");
|
||||
//check if should process
|
||||
string splitRel = PathUtils.GetRelativePath(split.FullName, splittedFile.FullName);
|
||||
if (!ShouldProcess(splittedFile, split))
|
||||
{
|
||||
//Log.v($"skip excluded split file {splitRel}");
|
||||
return;
|
||||
}
|
||||
|
||||
//Debug.WriteLine($"Split rel dir: {splitRel}");
|
||||
//Debug.WriteLine($"base Dir: {baseDir.FullName}");
|
||||
List<string> splitList = splitRel.Split('\\').ToList();
|
||||
splitList.RemoveAt(0);
|
||||
string outputString = string.Join("\\", splitList);
|
||||
|
||||
//get file path for base dir
|
||||
FileInfo baseFile = new FileInfo(Path.Combine(baseDir.FullName, outputString));
|
||||
//Debug.WriteLine($"Base file: {baseFile}");
|
||||
//Log.v($"Base file: {baseFile}");
|
||||
|
||||
//create target dir in base if needed
|
||||
string baseFileDir = Path.GetDirectoryName(baseFile.FullName);
|
||||
//Log.v($"Base file´dir: {baseFileDir}");
|
||||
//Debug.WriteLine($"Base file dir: {baseFileDir}");
|
||||
if (!Directory.Exists(baseFileDir))
|
||||
{
|
||||
Directory.CreateDirectory(baseFileDir);
|
||||
}
|
||||
|
||||
//check file exists in base and is resource xml
|
||||
if (!IsResourceXml(baseFile))
|
||||
{
|
||||
//nothing to merge, just copy
|
||||
Log.v($"Move split file {splitRel} to {baseFile}");
|
||||
if (File.Exists(baseFile.FullName))
|
||||
File.Delete(baseFile.FullName);
|
||||
splittedFile.MoveTo(baseFile.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
//already exists, merge
|
||||
//Debug.WriteLine($"Merge split file {splitRel} with {baseFile}");
|
||||
|
||||
//skip if files are equal
|
||||
if (baseFile.HasSameHash(splittedFile))
|
||||
{
|
||||
Log.vv($"base and split of {splitRel} have same hash, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
//check base and split are both resource xmls, if not skip
|
||||
if (/*!IsResourceXml(baseFile) ||*/ !IsResourceXml(splittedFile))
|
||||
{
|
||||
Log.vv($"split of {splitRel} is not resource xml, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (splittedFile.FullName.Contains("styles.xml"))
|
||||
{
|
||||
Debug.WriteLine("Break");
|
||||
}
|
||||
|
||||
//merge
|
||||
MergeResourceXML(baseFile, splittedFile, globalNameReplacements);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//skip replacement if no global name replacements are available
|
||||
if (globalNameReplacements.Count <= 0)
|
||||
{
|
||||
Log.d("skip global name replacements: count is 0");
|
||||
}
|
||||
|
||||
//replace names globally (in xml only)
|
||||
Log.d($"process {globalNameReplacements.Count} global name replacements...");
|
||||
foreach (string org in globalNameReplacements.Keys)
|
||||
{
|
||||
Log.v($"Replace {org} with {globalNameReplacements[org]}");
|
||||
//Debug.Write($"replace {org} with {globalNameReplacements[org]}");
|
||||
}
|
||||
|
||||
baseDir.EnumerateAllFiles("*.xml", true, (FileInfo file) =>
|
||||
{
|
||||
//Debug.WriteLine($"Name replace in {file.FullName}");
|
||||
|
||||
//create temp file
|
||||
FileInfo temp = new FileInfo(Path.GetTempFileName());
|
||||
|
||||
//copy from input to temp, replace everything on replace list
|
||||
using (StreamReader inp = file.OpenText())
|
||||
using (StreamWriter oup = temp.CreateText())
|
||||
{
|
||||
|
||||
string ln;
|
||||
while ((ln = inp.ReadLine()) != null)
|
||||
{
|
||||
//replace all
|
||||
foreach (string org in globalNameReplacements.Keys)
|
||||
{
|
||||
string dummy = StringExt.Regex(@"APKTOOL_DUMMY_([A-Za-z0-9])\w", ln);
|
||||
|
||||
if (ln.Contains(dummy))
|
||||
{
|
||||
//To avoid replacing wrong dummies. Don't know if there is better way
|
||||
ln = ln.Replace(org + "<", globalNameReplacements[org] + "<");
|
||||
ln = ln.Replace(org + "\"", globalNameReplacements[org] + "\"");
|
||||
//Debug.WriteLine($"Replaced {org} with {globalNameReplacements[org]} in {file.FullName}");
|
||||
}
|
||||
//if (ln.Contains(org))
|
||||
// ln = Regex.Replace(ln, @"APKTOOL_DUMMY_([A-Za-z0-9])\w", globalNameReplacements[org]);
|
||||
}
|
||||
|
||||
//write back
|
||||
oup.WriteLine(ln);
|
||||
}
|
||||
}
|
||||
|
||||
//move temp to input and delete temp if still exists
|
||||
string tempPath = temp.FullName;
|
||||
|
||||
if (File.Exists(file.FullName))
|
||||
{
|
||||
File.Delete(file.FullName);
|
||||
}
|
||||
|
||||
temp.MoveTo(file.FullName);
|
||||
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
});
|
||||
|
||||
//remove splits from android manifest
|
||||
FileInfo baseManifest = new FileInfo(Path.Combine(baseDir.FullName, "AndroidManifest.xml"));
|
||||
PatchManifest(baseManifest, assetPacks);
|
||||
|
||||
FileInfo baseYml = new FileInfo(Path.Combine(baseDir.FullName, "apktool.yml"));
|
||||
PatchYml(baseYml, assetPacks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch the AndroidManifest.xml to not use splits
|
||||
/// </summary>
|
||||
/// <param name="manifest">the manifest xml to patch</param>
|
||||
void PatchManifest(FileInfo manifest, List<string> assetPacks)
|
||||
{
|
||||
Log.d($"patching manifest {manifest.FullName}...");
|
||||
|
||||
//check the file exists
|
||||
if (!manifest.Exists)
|
||||
{
|
||||
Log.e("manifest to patch does not exist!");
|
||||
return;
|
||||
}
|
||||
|
||||
string modules = null;
|
||||
if (assetPacks.Count != 0)
|
||||
{
|
||||
foreach (string asset in assetPacks)
|
||||
{
|
||||
modules += "," + asset;
|
||||
}
|
||||
}
|
||||
|
||||
//prepare targets to remove
|
||||
string[] replaceTargets = { @"android:isSplitRequired=""true""" };
|
||||
|
||||
List<string> removeTargets = new List<string> { @"meta-data android:name=""com.android.stamp.source""",
|
||||
@"meta-data android:name=""com.android.vending.derived.apk.id""",
|
||||
@"meta-data android:name=""com.android.vending.splits.required""",
|
||||
@"meta-data android:name=""com.android.vending.splits"""};
|
||||
|
||||
//create temp file
|
||||
FileInfo temp = new FileInfo(Path.GetTempFileName());
|
||||
|
||||
//copy from input to temp, replace everything on replace list
|
||||
using (StreamReader inp = manifest.OpenText())
|
||||
using (StreamWriter oup = temp.CreateText())
|
||||
{
|
||||
string ln;
|
||||
while ((ln = inp.ReadLine()) != null)
|
||||
{
|
||||
//remove all
|
||||
foreach (string target in replaceTargets)
|
||||
{
|
||||
ln = ln.Replace(target, "");
|
||||
}
|
||||
|
||||
if (removeTargets.Any(w => ln.Contains(w)))
|
||||
continue;
|
||||
|
||||
if (ln.Contains("STAMP_TYPE_DISTRIBUTION_APK"))
|
||||
ln = ln.Replace("STAMP_TYPE_DISTRIBUTION_APK", "STAMP_TYPE_STANDALONE_APK");
|
||||
|
||||
if (ln.Contains("</application>") && !String.IsNullOrEmpty(modules))
|
||||
{
|
||||
oup.WriteLine(@" <meta-data android:name=""com.android.dynamic.apk.fused.modules"" android:value=""base" + modules + @"""/>");
|
||||
}
|
||||
|
||||
//write back
|
||||
oup.WriteLine(ln);
|
||||
}
|
||||
}
|
||||
|
||||
//move temp to input and delete temp if still exists
|
||||
string tempPath = temp.FullName;
|
||||
|
||||
if (File.Exists(manifest.FullName))
|
||||
{
|
||||
File.Delete(manifest.FullName);
|
||||
}
|
||||
|
||||
temp.MoveTo(manifest.FullName);
|
||||
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchYml(FileInfo yml, List<string> assetPacks)
|
||||
{
|
||||
Log.d($"patching apktool.yml {yml.FullName}...");
|
||||
|
||||
//check the file exists
|
||||
if (!yml.Exists)
|
||||
{
|
||||
Log.e("manifest to patch does not exist!");
|
||||
return;
|
||||
}
|
||||
|
||||
//create temp file
|
||||
FileInfo temp = new FileInfo(Path.GetTempFileName());
|
||||
|
||||
//copy from input to temp, replace everything on replace list
|
||||
using (StreamReader inp = yml.OpenText())
|
||||
using (StreamWriter oup = temp.CreateText())
|
||||
{
|
||||
string ln;
|
||||
while ((ln = inp.ReadLine()) != null)
|
||||
{
|
||||
if (ln.Contains("doNotCompress:") && assetPacks.Count != 0)
|
||||
{
|
||||
oup.WriteLine(ln);
|
||||
foreach (string asset in assetPacks)
|
||||
{
|
||||
oup.WriteLine("- assets/assetpack/" + asset);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//write back
|
||||
oup.WriteLine(ln);
|
||||
}
|
||||
}
|
||||
|
||||
//move temp to input and delete temp if still exists
|
||||
string tempPath = temp.FullName;
|
||||
|
||||
if (File.Exists(yml.FullName))
|
||||
{
|
||||
File.Delete(yml.FullName);
|
||||
}
|
||||
|
||||
temp.MoveTo(yml.FullName);
|
||||
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// merge two splitted resource xmls, overwrite a with merged
|
||||
/// </summary>
|
||||
/// <param name="a">file a to merge</param>
|
||||
/// <param name="b">file b to merge</param>
|
||||
/// <param name="globalNameReplacements">dictionary that can be used to replace names of resources globally</param>
|
||||
void MergeResourceXML(FileInfo a, FileInfo b, Dictionary</*original*/string, /*replacement*/string> globalNameReplacements)
|
||||
{
|
||||
//deserialize both
|
||||
AndroidResources resBase = AndroidResources.FromFile(a.FullName);
|
||||
AndroidResources resSplit = AndroidResources.FromFile(b.FullName);
|
||||
|
||||
//merge resources to resA
|
||||
foreach (AndroidResource res in resSplit.Values)
|
||||
{
|
||||
if (res is AndroidPublic splitP)
|
||||
{
|
||||
//entry of public.xml, special merge (Id has to be unique)
|
||||
//try to find public with same id in base apk
|
||||
AndroidPublic baseP = resBase.FindPublicWithId(splitP.Id);
|
||||
if (baseP == null || !baseP.Type.Equals(splitP.Type))
|
||||
{
|
||||
//id not found or wrong type, add from split
|
||||
resBase.Values.Add(splitP);
|
||||
}
|
||||
else
|
||||
{
|
||||
//id with correct type found in base,
|
||||
//check if name of base is apktool dummy and name of split is not
|
||||
if (baseP.Name.StartsWith("APKTOOL_DUMMY") && !splitP.Name.StartsWith("APKTOOL_DUMMY"))
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.v($"Replace {baseP.Name} with {splitP.Name}...");
|
||||
globalNameReplacements.Add(baseP.Name, splitP.Name);
|
||||
baseP.Name = splitP.Name;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.v($"Error replacing {baseP.Name} with {splitP.Name}...");
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//normal resource entry (string / color / ...)
|
||||
if (!resBase.Values.Contains(res))
|
||||
{
|
||||
|
||||
resBase.Values.Add(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//serialize back to a
|
||||
resBase.ToFile(a.FullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check if the xml file contains the resources xml tag
|
||||
/// </summary>
|
||||
/// <param name="xml">the xml to check</param>
|
||||
/// <returns>does the xml contain the tag?</returns>
|
||||
bool IsResourceXml(FileInfo f)
|
||||
{
|
||||
//check file exists
|
||||
if (!f.Exists) return false;
|
||||
|
||||
try
|
||||
{
|
||||
//Net reactor cause error
|
||||
//check xml root
|
||||
XmlDocument xml = new XmlDocument();
|
||||
|
||||
xml.Load(f.FullName);
|
||||
//Log.v($"IsResourceXml 5");
|
||||
return xml.DocumentElement.Name.Equals("resources", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//probably bad xml
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// should the file be processed?
|
||||
/// Example for files to exclude from processing are AndroidManifest.xml, apktool.yml, and META-INF/*
|
||||
/// </summary>
|
||||
/// <param name="file">the file to check</param>
|
||||
/// <param name="projDir">the project dir the file is in</param>
|
||||
/// <returns>process the file?</returns>
|
||||
bool ShouldProcess(FileInfo file, DirectoryInfo projDir)
|
||||
{
|
||||
//get relative path
|
||||
string filePathRel = PathUtils.GetRelativePath(projDir.FullName, file.FullName).TrimStart('/').TrimStart('\\');
|
||||
|
||||
//check if in META-INF (exclude all)
|
||||
//if (filePathRel.StartsWith("META-INF", StringComparison.OrdinalIgnoreCase))
|
||||
// return false;
|
||||
|
||||
//check if in original (exlude all)
|
||||
if (filePathRel.StartsWith("original", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
//check if AndroidManifest.xml OR apktool.yml
|
||||
if (file.Name.Equals("androidmanifest.xml", StringComparison.OrdinalIgnoreCase)
|
||||
|| file.Name.Equals("apktool.yml", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
//check if AndroidManifest.xml OR apktool.yml
|
||||
if (file.Name.Equals("resources.arsc", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
//check if drawables.yml
|
||||
//if (file.Name.Equals("drawables.xml", StringComparison.OrdinalIgnoreCase))
|
||||
// return false;
|
||||
|
||||
//all ok, include
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//https://github.com/shadow578/ApksMerger
|
||||
|
||||
using APKSMerger.AndroidRes.Model;
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes
|
||||
{
|
||||
[XmlRoot("resources")]
|
||||
public sealed class AndroidResources
|
||||
{
|
||||
//basic
|
||||
[XmlElement("bool", Type = typeof(AndroidBool))]
|
||||
[XmlElement("integer", Type = typeof(AndroidInteger))]
|
||||
[XmlElement("dimen", Type = typeof(AndroidDimension))]
|
||||
[XmlElement("drawable", Type = typeof(AndroidDrawable))]
|
||||
[XmlElement("color", Type = typeof(AndroidColor))]
|
||||
[XmlElement("fraction", Type = typeof(AndroidFraction))]
|
||||
|
||||
//extended
|
||||
[XmlElement("attr", Type = typeof(AndroidAttribute))]
|
||||
[XmlElement("string", Type = typeof(AndroidString))]
|
||||
[XmlElement("item", Type = typeof(AndroidTypedItem))]
|
||||
[XmlElement("public", Type = typeof(AndroidPublic))]
|
||||
|
||||
//complex
|
||||
[XmlElement("style", Type = typeof(AndroidStyle))]
|
||||
[XmlElement("plurals", Type = typeof(AndroidPlural))]
|
||||
[XmlElement("string-array", Type = typeof(AndroidStringArray))]
|
||||
[XmlElement("integer-array", Type = typeof(AndroidIntegerArray))]
|
||||
[XmlElement("array", Type = typeof(AndroidGenericArray))]
|
||||
[XmlElement("declare-styleable", Type = typeof(AndroidStyleable))]
|
||||
public List<AndroidResource> Values { get; set; } = new List<AndroidResource>();
|
||||
|
||||
/// <summary>
|
||||
/// Find a AndroidPublic with matching id
|
||||
/// </summary>
|
||||
/// <param name="id">the id to find</param>
|
||||
/// <returns>matching public, or null if not found</returns>
|
||||
public AndroidPublic FindPublicWithId(string id)
|
||||
{
|
||||
foreach(AndroidResource res in Values)
|
||||
{
|
||||
if((res is AndroidPublic pub) && pub.Id.Equals(id))
|
||||
{
|
||||
return pub;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize a file into a object
|
||||
/// </summary>
|
||||
/// <param name="file">the file to deserialize</param>
|
||||
/// <returns>the object</returns>
|
||||
public static AndroidResources FromFile(string file)
|
||||
{
|
||||
//check file
|
||||
if (!File.Exists(file)) return null;
|
||||
|
||||
//deserialize
|
||||
try
|
||||
{
|
||||
XmlSerializer ser = new XmlSerializer(typeof(AndroidResources));
|
||||
using (StreamReader reader = File.OpenText(file))
|
||||
{
|
||||
return ser.Deserialize(reader) as AndroidResources;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// serialize into a file
|
||||
/// </summary>
|
||||
/// <param name="file">the file to serialize to, will be overwritten if exists</param>
|
||||
/// <returns>write file ok?</returns>
|
||||
public bool ToFile(string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
XmlSerializer ser = new XmlSerializer(typeof(AndroidResources));
|
||||
using (StreamWriter writer = File.CreateText(file))
|
||||
{
|
||||
ser.Serialize(writer, this, new XmlSerializerNamespaces());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidAttribute : AndroidResource
|
||||
{
|
||||
[XmlAttribute("format")]
|
||||
public string Format { get; set; }
|
||||
|
||||
//[XmlText]
|
||||
//[XmlAttribute("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidBool : AndroidResource
|
||||
{
|
||||
//[XmlText]
|
||||
//[XmlAttribute("value")]
|
||||
public bool Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidInteger : AndroidResource
|
||||
{
|
||||
//[XmlText]
|
||||
//[XmlAttribute("value")]
|
||||
public int Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidPlural : AndroidResource
|
||||
{
|
||||
public sealed class Plural
|
||||
{
|
||||
[XmlAttribute("quantitiy")]
|
||||
public string Quantity { get; set; }
|
||||
|
||||
[XmlText]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
[XmlElement("item", Type = typeof(Plural))]
|
||||
public List<Plural> Values { get; set; } = new List<Plural>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidPublic : AndroidResource
|
||||
{
|
||||
[XmlAttribute("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[XmlAttribute("id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidString : AndroidResource
|
||||
{
|
||||
//[XmlAttribute("formatted")]
|
||||
//public bool Formatted { get; set; }
|
||||
|
||||
//[XmlAttribute("translatable")]
|
||||
//public bool Translateable { get; set; }
|
||||
|
||||
// [XmlText]
|
||||
//[XmlAttribute("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidStyle : AndroidResource
|
||||
{
|
||||
[XmlAttribute("parent")]
|
||||
public string Parent { get; set; }
|
||||
|
||||
[XmlElement("item", Type = typeof(AndroidGeneric))]
|
||||
public List<AndroidGeneric> Items { get; set; } = new List<AndroidGeneric>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidStyleable : AndroidResource
|
||||
{
|
||||
[XmlElement("attr", Type = typeof(AndroidAttribute))]
|
||||
public List<AndroidAttribute> Values { get; set; } = new List<AndroidAttribute>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidTypedItem : AndroidResource
|
||||
{
|
||||
[XmlAttribute("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
//[XmlText]
|
||||
//[XmlAttribute("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model.Generic
|
||||
{
|
||||
public class AndroidGeneric : AndroidResource
|
||||
{
|
||||
[XmlText]
|
||||
//[XmlAttribute("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model.Generic
|
||||
{
|
||||
public class AndroidGenericArray : AndroidResource
|
||||
{
|
||||
public sealed class Item
|
||||
{
|
||||
// [XmlText]
|
||||
//[XmlAttribute("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
[XmlElement("item", Type = typeof(Item))]
|
||||
public List<Item> Values { get; set; } = new List<Item>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model.Generic
|
||||
{
|
||||
public class AndroidResource
|
||||
{
|
||||
[XmlAttribute(AttributeName = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
//check other object is of correct type, otherwise not equal
|
||||
if (!(obj is AndroidResource other)) return false;
|
||||
|
||||
//check if name is equal
|
||||
//Debug.WriteLine("Xml name: " + other.Name);
|
||||
return Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidStringArray : AndroidGenericArray { }
|
||||
|
||||
public sealed class AndroidIntegerArray : AndroidGenericArray { }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using APKSMerger.AndroidRes.Model.Generic;
|
||||
|
||||
namespace APKSMerger.AndroidRes.Model
|
||||
{
|
||||
public sealed class AndroidDimension : AndroidGeneric { }
|
||||
|
||||
public sealed class AndroidDrawable : AndroidGeneric { }
|
||||
|
||||
public sealed class AndroidColor : AndroidGeneric { }
|
||||
|
||||
public sealed class AndroidFraction : AndroidGeneric { }
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace APKSMerger.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// extension methods
|
||||
/// </summary>
|
||||
public static class ClassExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// replaces the first occurance of the pattern with the replacement
|
||||
/// </summary>
|
||||
/// <param name="s">the string to replace in</param>
|
||||
/// <param name="pattern">the pattern to replace</param>
|
||||
/// <param name="replacement">the replacement for the pattern</param>
|
||||
/// <returns>a string in wich the first occurance of the pattern was replaced</returns>
|
||||
public static string ReplaceFirst(this string s, string pattern, string replacement)
|
||||
{
|
||||
int pos = s.IndexOf(pattern);
|
||||
if (pos < 0)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
return s.Substring(0, pos) + replacement + s.Substring(pos + pattern.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// does the array contain the string a, ignoring case?
|
||||
/// </summary>
|
||||
/// <param name="s">the array to check</param>
|
||||
/// <param name="a">the string to check for</param>
|
||||
/// <returns>contains it?</returns>
|
||||
public static bool ContainsIgnoreCase(this string[] s, string a)
|
||||
{
|
||||
foreach (string sa in s)
|
||||
{
|
||||
if (sa.Equals(a, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// enumerates all files in the directory (and subdirs if enabled)
|
||||
/// </summary>
|
||||
/// <param name="dir">the directory to enumerate in</param>
|
||||
/// <param name="pattern">the pattern to filter with, eg. *.* or *.txt</param>
|
||||
/// <param name="includeSubDirs">should files in subdirs be included?</param>
|
||||
/// <param name="action">the action to execute for all files</param>
|
||||
public static void EnumerateAllFiles(this DirectoryInfo dir, string pattern, bool includeSubDirs, Action<FileInfo> action)
|
||||
{
|
||||
foreach (FileInfo file in dir.EnumerateFiles(pattern, includeSubDirs ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
action.Invoke(file);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// enumerates all files in the directory (and subdirs if enabled) in parallel
|
||||
/// </summary>
|
||||
/// <param name="dir">the directory to enumerate in</param>
|
||||
/// <param name="pattern">the pattern to filter with, eg. *.* or *.txt</param>
|
||||
/// <param name="includeSubDirs">should files in subdirs be included?</param>
|
||||
/// <param name="action">the action to execute for all files</param>
|
||||
public static void EnumerateAllFilesParallel(this DirectoryInfo dir, string pattern, bool includeSubDirs, Action<FileInfo> action)
|
||||
{
|
||||
Parallel.ForEach(dir.EnumerateFiles(pattern, includeSubDirs ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly), action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// checks if the two files have the same hash (MD5)
|
||||
/// </summary>
|
||||
/// <param name="a">the first file</param>
|
||||
/// <param name="b">the file to compare</param>
|
||||
/// <returns>do they have the same hash?</returns>
|
||||
public static bool HasSameHash(this FileInfo a, FileInfo b)
|
||||
{
|
||||
return a.GetMD5().Equals(b.GetMD5());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the md5 of the file
|
||||
/// </summary>
|
||||
/// <param name="f">the file to get md5 of</param>
|
||||
/// <returns>md5 string of the file</returns>
|
||||
public static string GetMD5(this FileInfo f)
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
using (FileStream stream = f.OpenRead())
|
||||
{
|
||||
return BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// repeat the char n times
|
||||
/// </summary>
|
||||
/// <param name="c">char to repeat</param>
|
||||
/// <param name="n">how often to repeat</param>
|
||||
/// <returns>string with n time c</returns>
|
||||
public static string Repeat(this char c, int n)
|
||||
{
|
||||
string s = "";
|
||||
for (int i = 0; i < n; i++)
|
||||
s += c;
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
using APKToolGUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace APKSMerger.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// simple logging wrapper class
|
||||
/// </summary>
|
||||
public static class Log
|
||||
{
|
||||
/// <summary>
|
||||
/// should very verbose logs (Log.vv) be written?
|
||||
/// </summary>
|
||||
public static bool LogVeryVerbose { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// should verbose logs (Log.v) be written?
|
||||
/// </summary>
|
||||
public static bool LogVerbose { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// should debug logs (Log.d) be written?
|
||||
/// </summary>
|
||||
public static bool LogDebug { get; set; } = true;
|
||||
|
||||
#region direct logs
|
||||
/// <summary>
|
||||
/// log message with level VERY VERBOSE (may be disabled)
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public static void vv(string s)
|
||||
{
|
||||
if (!LogVeryVerbose) return;
|
||||
FormMain.Instance.ToLog(ApktoolEventType.None, s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level VERBOSE (may be disabled)
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public static void v(string s)
|
||||
{
|
||||
if (!LogVerbose) return;
|
||||
FormMain.Instance.ToLog(ApktoolEventType.None, s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level DEBUG (may be disabled)
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public static void d(string s)
|
||||
{
|
||||
if (!LogDebug) return;
|
||||
FormMain.Instance.ToLog(ApktoolEventType.None, s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level INFO
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public static void i(string s)
|
||||
{
|
||||
FormMain.Instance.ToLog(ApktoolEventType.Infomation, s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level WARNING
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public static void w(string s)
|
||||
{
|
||||
FormMain.Instance.ToLog(ApktoolEventType.Warning, s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level ERROR
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public static void e(string s)
|
||||
{
|
||||
FormMain.Instance.ToLog(ApktoolEventType.Error, s);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// start a new async log session
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static AsyncLogSession StartAsync()
|
||||
{
|
||||
return new AsyncLogSession();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// writes a direct log message
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
/// <param name="color">color to log in, null is default</param>
|
||||
static void WriteLogDirect(string s, ConsoleColor? color = null)
|
||||
{
|
||||
//set color
|
||||
ConsoleColor iColor = Console.ForegroundColor;
|
||||
if (color.HasValue)
|
||||
{
|
||||
Console.ForegroundColor = color.Value;
|
||||
}
|
||||
|
||||
//write log
|
||||
Console.WriteLine(s);
|
||||
|
||||
//restore color
|
||||
if (color.HasValue)
|
||||
{
|
||||
Console.ForegroundColor = iColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// async log sesssion
|
||||
/// </summary>
|
||||
public class AsyncLogSession : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// lock object to ensure only one object commits at a time
|
||||
/// </summary>
|
||||
static readonly object _CommitLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Tag to include when logging
|
||||
/// </summary>
|
||||
Stack<string> tags = new Stack<string>();
|
||||
|
||||
/// <summary>
|
||||
/// contains all pending log entries
|
||||
/// </summary>
|
||||
Queue<string> pending = new Queue<string>();
|
||||
|
||||
#region log functions
|
||||
/// <summary>
|
||||
/// log message with level VERY VERBOSE (may be disabled)
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public void vv(string s)
|
||||
{
|
||||
if (!LogVeryVerbose) return;
|
||||
|
||||
EnqueueMessage($"[VV]{s}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level VERBOSE (may be disabled)
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public void v(string s)
|
||||
{
|
||||
if (!LogVerbose) return;
|
||||
|
||||
EnqueueMessage($"[V]{s}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level DEBUG (may be disabled)
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public void d(string s)
|
||||
{
|
||||
if (!LogDebug) return;
|
||||
|
||||
EnqueueMessage($"[D]{s}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level INFO
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public void i(string s)
|
||||
{
|
||||
EnqueueMessage($"[I]{s}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level WARNING
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public void w(string s)
|
||||
{
|
||||
EnqueueMessage($"[W]{s}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// log message with level ERROR
|
||||
/// </summary>
|
||||
/// <param name="s">the string to log</param>
|
||||
public void e(string s)
|
||||
{
|
||||
EnqueueMessage($"[E]{s}");
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// enqueue a message in the message queue
|
||||
/// </summary>
|
||||
/// <param name="s">the message to enqueue</param>
|
||||
void EnqueueMessage(string s)
|
||||
{
|
||||
pending.Enqueue($"{GetTag()}:{s}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// push a tag onto the tags stack
|
||||
/// </summary>
|
||||
/// <param name="t">the tag to push</param>
|
||||
public void PushTag(string t)
|
||||
{
|
||||
tags.Push(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// pop the last tag of the tags stack
|
||||
/// </summary>
|
||||
public void PopTag()
|
||||
{
|
||||
// tags.TryPop(out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get the current tag
|
||||
/// </summary>
|
||||
/// <returns>current tag, or string.Empty if no tag</returns>
|
||||
public string GetTag()
|
||||
{
|
||||
string tag;
|
||||
// if (!tags.TryPeek(out tag))
|
||||
tag = string.Empty;
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// commit all pending log entries
|
||||
/// </summary>
|
||||
public void Commit()
|
||||
{
|
||||
lock (_CommitLock)
|
||||
{
|
||||
while (pending.Count > 0)
|
||||
{
|
||||
WriteLogDirect(pending.Dequeue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// same as calling .Commit(). used for using() syntax
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+185
-15
@@ -14,7 +14,8 @@ using System.Collections.Generic;
|
||||
using APKToolGUI.Handlers;
|
||||
using Microsoft.WindowsAPICodePack.Taskbar;
|
||||
using System.Media;
|
||||
|
||||
using APKSMerger.AndroidRes;
|
||||
using Ionic.Zip;
|
||||
|
||||
namespace APKToolGUI
|
||||
{
|
||||
@@ -34,8 +35,12 @@ namespace APKToolGUI
|
||||
private Stopwatch stopwatch;
|
||||
private string lastStartedDate;
|
||||
|
||||
internal static FormMain Instance { get; private set; }
|
||||
|
||||
public FormMain()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
Program.SetLanguage();
|
||||
InitializeComponent();
|
||||
this.Text += " - v" + ProductVersion;
|
||||
@@ -113,6 +118,8 @@ namespace APKToolGUI
|
||||
else
|
||||
ToLog(ApktoolEventType.None, Language.DragDropSupported);
|
||||
|
||||
ToLog(ApktoolEventType.None, String.Format(Language.TempDirectory, Program.TempDirectory()));
|
||||
|
||||
TimeSpan updateInterval = DateTime.Now - Settings.Default.LastUpdateCheck;
|
||||
if (updateInterval.Days > 0 && Settings.Default.CheckForUpdateAtStartup)
|
||||
updateCheker.CheckAsync(true);
|
||||
@@ -140,8 +147,16 @@ namespace APKToolGUI
|
||||
switch (Environment.GetCommandLineArgs()[1])
|
||||
{
|
||||
case "decapk":
|
||||
if (await Decompile(file) == 0)
|
||||
Close();
|
||||
if (file.ContainsAny(".xapk", ".zip", ".apks", ".apkm"))
|
||||
{
|
||||
if (await MergeAPK(file) == 0)
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await Decompile(file) == 0)
|
||||
Close();
|
||||
}
|
||||
break;
|
||||
case "comapk":
|
||||
if (await Build(file) == 0)
|
||||
@@ -232,6 +247,49 @@ namespace APKToolGUI
|
||||
|
||||
try
|
||||
{
|
||||
string splitPath = Path.Combine(Program.TempDirectory(), "SplitInfo");
|
||||
string arch = null;
|
||||
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
DirectoryUtils.Delete(splitPath);
|
||||
if (file.ContainsAny(".xapk", ".zip", ".apks", ".apkm"))
|
||||
{
|
||||
Directory.CreateDirectory(splitPath);
|
||||
|
||||
using (ZipFile zipDest = ZipFile.Read(file))
|
||||
{
|
||||
bool mainApkFound = false;
|
||||
foreach (ZipEntry entry in zipDest.Entries)
|
||||
{
|
||||
if (!mainApkFound && !entry.FileName.Contains("config.") && entry.FileName.EndsWith(".apk"))
|
||||
{
|
||||
Debug.WriteLine("Found main APK" + entry.FileName);
|
||||
entry.Extract(splitPath, ExtractExistingFileAction.OverwriteSilently);
|
||||
file = Path.Combine(splitPath, entry.FileName);
|
||||
mainApkFound = true;
|
||||
}
|
||||
if (entry.FileName.Contains("lib/armeabi-v7a"))
|
||||
{
|
||||
arch += "armeabi-v7a, ";
|
||||
}
|
||||
if (entry.FileName.Contains("lib/arm64-v8a"))
|
||||
{
|
||||
arch += "arm64-v8a, ";
|
||||
}
|
||||
if (entry.FileName.Contains("lib/x86"))
|
||||
{
|
||||
arch += "x86, ";
|
||||
}
|
||||
if (entry.FileName.Contains("lib/x86_64"))
|
||||
{
|
||||
arch += "x86_64, ";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bool parsed = false;
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
@@ -258,21 +316,26 @@ namespace APKToolGUI
|
||||
permTxtBox.Text = aapt.Permissions;
|
||||
localsTxtBox.Text = aapt.Locales;
|
||||
fullInfoTextBox.Text = aapt.FullInfo;
|
||||
archSdkTxtBox.Text = aapt.NativeCode;
|
||||
if (!String.IsNullOrEmpty(aapt.NativeCode))
|
||||
archSdkTxtBox.Text = aapt.NativeCode;
|
||||
else
|
||||
archSdkTxtBox.Text = arch.RemoveLast(", ");
|
||||
launchActivityTxtBox.Text = aapt.LaunchableActivity;
|
||||
|
||||
if (aapt.AppIcon != null)
|
||||
{
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
ZipUtils.ExtractFile(file, aapt.AppIcon, Path.Combine(Program.TEMP_PATH, aapt.PackageName));
|
||||
ZipUtils.ExtractFile(file, aapt.AppIcon, Path.Combine(Program.TempDirectory(), aapt.PackageName));
|
||||
});
|
||||
string icon = Path.Combine(Program.TEMP_PATH, aapt.PackageName, Path.GetFileName(aapt.AppIcon));
|
||||
string icon = Path.Combine(Program.TempDirectory(), aapt.PackageName, Path.GetFileName(aapt.AppIcon));
|
||||
if (File.Exists(icon))
|
||||
{
|
||||
apkIconPicBox.Image = BitmapUtils.LoadBitmap(icon);
|
||||
}
|
||||
}
|
||||
|
||||
DirectoryUtils.Delete(splitPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -283,9 +346,8 @@ namespace APKToolGUI
|
||||
ToLog(ApktoolEventType.Warning, Language.ErrorGettingApkInfo);
|
||||
#endif
|
||||
}
|
||||
|
||||
ToLog(ApktoolEventType.Done, Language.Done);
|
||||
Done();
|
||||
ToStatus(Language.Done, Resources.done);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -434,6 +496,106 @@ namespace APKToolGUI
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Merge APK
|
||||
internal async Task<int> MergeAPK(string inputSplitApk)
|
||||
{
|
||||
int code = 0;
|
||||
|
||||
string apkFileName = Path.GetFileName(inputSplitApk);
|
||||
|
||||
string tempApk = Path.Combine(Program.TempDirectory(), "dec.apk");
|
||||
|
||||
string extractedSplitDir = Path.Combine(Program.TempDirectory(), "SplitApk");
|
||||
string decompileDir = Path.Combine(Program.TempDirectory(), "Decompiled");
|
||||
string mergedDir = Path.Combine(Program.TempDirectory(), "Merged");
|
||||
|
||||
string outputDir = PathUtils.GetDirectoryNameWithoutExtension(inputSplitApk);
|
||||
if (Settings.Default.Decode_UseOutputDir && !IgnoreOutputDirContextMenu)
|
||||
outputDir = Path.Combine(Settings.Default.Decode_OutputDir, Path.GetFileNameWithoutExtension(inputSplitApk));
|
||||
|
||||
try
|
||||
{
|
||||
Running();
|
||||
|
||||
DirectoryUtils.Delete(extractedSplitDir);
|
||||
Directory.CreateDirectory(extractedSplitDir);
|
||||
DirectoryUtils.Delete(mergedDir);
|
||||
Directory.CreateDirectory(mergedDir);
|
||||
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
if (Settings.Default.Framework_ClearBeforeDecode)
|
||||
{
|
||||
if (ClearFramework() == 0)
|
||||
ToLog(ApktoolEventType.None, Language.FrameworkCacheCleared);
|
||||
else
|
||||
ToLog(ApktoolEventType.Error, Language.ErrorClearingFw);
|
||||
}
|
||||
|
||||
ToLog(ApktoolEventType.Infomation, "=====[ " + Language.MergingApk + " ]=====");
|
||||
ToLog(ApktoolEventType.None, String.Format(Language.InputFile, inputSplitApk));
|
||||
ToStatus(String.Format(Language.MergingApk + " \"{0}\"...", Path.GetFileName(inputSplitApk)), Resources.waiting);
|
||||
|
||||
//Extract all apk files
|
||||
ToLog(ApktoolEventType.None, Language.ExtractingAllApkFiles);
|
||||
ZipUtils.ExtractAll(inputSplitApk, extractedSplitDir, true);
|
||||
|
||||
//Decompile all apk files
|
||||
ToLog(ApktoolEventType.None, Language.DecompilingAllApkFiles);
|
||||
|
||||
List<DirectoryInfo> splitDirs = new List<DirectoryInfo>();
|
||||
var apkfiles = Directory.EnumerateFiles(extractedSplitDir, "*.apk");
|
||||
|
||||
foreach (string apk in apkfiles)
|
||||
{
|
||||
string output = Path.Combine(decompileDir, Path.GetFileNameWithoutExtension(apk));
|
||||
|
||||
code = apktool.Decompile(apk, output);
|
||||
if (code != 0)
|
||||
{
|
||||
ToLog(ApktoolEventType.Error, Language.ErrorDecompiling);
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(output, "smali")) || File.Exists(Path.Combine(output, "classes.dex")))
|
||||
{
|
||||
ToLog(ApktoolEventType.Infomation, String.Format(Language.DetectedAsBase, apk));
|
||||
|
||||
ToLog(ApktoolEventType.None, String.Format(Language.MovingBasedirectory, decompileDir));
|
||||
DirectoryUtils.Move(output, mergedDir);
|
||||
continue;
|
||||
}
|
||||
|
||||
DirectoryInfo splitI = new DirectoryInfo(output);
|
||||
ToLog(ApktoolEventType.Infomation, String.Format(Language.DetectedAsSplit, apk));
|
||||
splitDirs.Add(splitI);
|
||||
}
|
||||
|
||||
AndroidMerger merger = new AndroidMerger();
|
||||
DirectoryInfo baseDir = new DirectoryInfo(mergedDir);
|
||||
|
||||
Dictionary<string, string> locales, abis;
|
||||
|
||||
ToLog(ApktoolEventType.None, Language.MergingApk);
|
||||
merger.CollectCapabilities(out locales, out abis, baseDir, splitDirs.ToArray());
|
||||
merger.MergeSplits(baseDir, splitDirs.ToArray());
|
||||
|
||||
ToLog(ApktoolEventType.None, Language.MergeFinishedMoveDir);
|
||||
DirectoryUtils.Move(mergedDir, outputDir);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
code = 1;
|
||||
ToLog(ApktoolEventType.Error, ex.ToString());
|
||||
}
|
||||
|
||||
Done(printTimer: true);
|
||||
|
||||
return code;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Apktool
|
||||
private void InitializeAPKTool()
|
||||
{
|
||||
@@ -464,7 +626,7 @@ namespace APKToolGUI
|
||||
if (Settings.Default.Decode_UseOutputDir && !IgnoreOutputDirContextMenu)
|
||||
outputDir = Path.Combine(Settings.Default.Decode_OutputDir, Path.GetFileNameWithoutExtension(inputApk));
|
||||
|
||||
string tempApk = Path.Combine(Program.TEMP_PATH, "dec.apk");
|
||||
string tempApk = Path.Combine(Program.TempDirectory(), "dec.apk");
|
||||
string outputTempDir = tempApk.Replace(".apk", "");
|
||||
|
||||
try
|
||||
@@ -584,7 +746,7 @@ namespace APKToolGUI
|
||||
}
|
||||
string outputCompiledApkFile = outputFile;
|
||||
|
||||
string tempDecApkFolder = Path.Combine(Program.TEMP_PATH, "dec");
|
||||
string tempDecApkFolder = Path.Combine(Program.TempDirectory(), "dec");
|
||||
string outputTempApk = tempDecApkFolder + ".apk";
|
||||
|
||||
if (Settings.Default.Utf8FilenameSupport)
|
||||
@@ -823,7 +985,7 @@ namespace APKToolGUI
|
||||
ToStatus(String.Format(Language.Aligning + " \"{0}\"...", Path.GetFileName(input)), Resources.waiting);
|
||||
}));
|
||||
|
||||
string tempApk = Path.Combine(Program.TEMP_PATH, "tempapk.apk");
|
||||
string tempApk = Path.Combine(Program.TempDirectory(), "tempapk.apk");
|
||||
string outputApkFile = output;
|
||||
|
||||
if (Settings.Default.Utf8FilenameSupport)
|
||||
@@ -870,7 +1032,7 @@ namespace APKToolGUI
|
||||
ToLog(ApktoolEventType.None, String.Format(Language.InputFile, input));
|
||||
ToStatus(String.Format(Language.Signing + " \"{0}\"...", Path.GetFileName(input)), Resources.waiting);
|
||||
}));
|
||||
string tempApk = Path.Combine(Program.TEMP_PATH, "tempapk.apk");
|
||||
string tempApk = Path.Combine(Program.TempDirectory(), "tempapk.apk");
|
||||
string outputApkFile = output;
|
||||
|
||||
if (Settings.Default.Utf8FilenameSupport)
|
||||
@@ -921,7 +1083,14 @@ namespace APKToolGUI
|
||||
|
||||
private void openTempFolderToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
Process.Start(Program.TEMP_PATH);
|
||||
try
|
||||
{
|
||||
Process.Start(Program.TempDirectory());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ToLog(ApktoolEventType.Error, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void menuItemCheckUpdate_Click(object sender, EventArgs e)
|
||||
@@ -1058,9 +1227,10 @@ namespace APKToolGUI
|
||||
|
||||
private void Application_ApplicationExit(object sender, EventArgs e)
|
||||
{
|
||||
//Clear temp folder
|
||||
DirectoryUtils.Delete(Program.TEMP_PATH);
|
||||
Save();
|
||||
|
||||
//Clear temp folder
|
||||
DirectoryUtils.Delete(Program.TempDirectory());
|
||||
}
|
||||
|
||||
private bool ActionButtonsEnabled
|
||||
|
||||
@@ -835,13 +835,13 @@
|
||||
<value>3, 3</value>
|
||||
</data>
|
||||
<data name="label1.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>50, 13</value>
|
||||
<value>160, 13</value>
|
||||
</data>
|
||||
<data name="label1.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>12</value>
|
||||
</data>
|
||||
<data name="label1.Text" xml:space="preserve">
|
||||
<value>APK File:</value>
|
||||
<value>APK/XAPK/APKS/ZIP/APKM File:</value>
|
||||
</data>
|
||||
<data name=">>label1.Name" xml:space="preserve">
|
||||
<value>label1</value>
|
||||
|
||||
@@ -169,10 +169,10 @@ namespace APKToolGUI
|
||||
{
|
||||
customTempLocationTxtBox.Text = fbd.SelectedPath;
|
||||
//Clear temp folder
|
||||
DirectoryUtils.Delete(Program.TEMP_PATH);
|
||||
DirectoryUtils.Delete(Program.TempDirectory());
|
||||
|
||||
//Create new temp folder
|
||||
Program.TEMP_PATH = Program.TempDir();
|
||||
Directory.CreateDirectory(Program.TempDirectory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ namespace APKToolGUI.Handlers
|
||||
|
||||
internal async void button_DECODE_Decode_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (File.Exists(main.textBox_DECODE_InputAppPath.Text))
|
||||
string inputFile = main.textBox_DECODE_InputAppPath.Text;
|
||||
if (File.Exists(inputFile))
|
||||
{
|
||||
if (main.checkBox_DECODE_UseFramework.Checked && !Directory.Exists(main.textBox_DECODE_FrameDir.Text))
|
||||
{
|
||||
@@ -92,7 +93,10 @@ namespace APKToolGUI.Handlers
|
||||
}
|
||||
}
|
||||
|
||||
await main.Decompile(main.textBox_DECODE_InputAppPath.Text);
|
||||
if (inputFile.ContainsAny(".xapk", ".zip", ".apks", ".apkm"))
|
||||
await main.MergeAPK(inputFile);
|
||||
else
|
||||
await main.Decompile(inputFile);
|
||||
}
|
||||
else
|
||||
MessageBox.Show(Language.WarningFileForDecodingNotSelected, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
@@ -107,7 +111,7 @@ namespace APKToolGUI.Handlers
|
||||
main.ToLog(ApktoolEventType.Error, Language.ErrorSelectedFileNotExist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void decOutOpenDirBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (Directory.Exists(Settings.Default.Decode_OutputDir))
|
||||
|
||||
@@ -16,45 +16,48 @@ namespace APKToolGUI.Handlers
|
||||
class DragDropHandlers
|
||||
{
|
||||
private static FormMain main;
|
||||
|
||||
string[] apks = { ".apk", ".xapk", ".zip", ".apks", ".apkm" };
|
||||
|
||||
public DragDropHandlers(FormMain Main)
|
||||
{
|
||||
main = Main;
|
||||
|
||||
//Decode
|
||||
DragEventHandler decEventHandler = new DragEventHandler((sender, e) => { DropApkToDec(e); });
|
||||
Register(main.decPanel, ".apk", decEventHandler);
|
||||
Register(main.textBox_DECODE_InputAppPath, ".apk", decEventHandler, main.decPanel);
|
||||
Register(main.button_DECODE_Decode, ".apk", decEventHandler, main.decPanel);
|
||||
Register(main.decPanel, null, decEventHandler, apks);
|
||||
Register(main.textBox_DECODE_InputAppPath, main.decPanel, decEventHandler, apks);
|
||||
Register(main.button_DECODE_Decode, main.decPanel, decEventHandler, apks);
|
||||
|
||||
DragEventHandler comEventHandler = new DragEventHandler((sender, e) => { DropDirToCom(e); });
|
||||
Register(main.comPanel, "", comEventHandler);
|
||||
Register(main.textBox_BUILD_InputProjectDir, "", comEventHandler, main.comPanel);
|
||||
Register(main.button_BUILD_Build, "", comEventHandler, main.comPanel);
|
||||
Register(main.comPanel, null, comEventHandler, null);
|
||||
Register(main.textBox_BUILD_InputProjectDir, main.comPanel, comEventHandler, null);
|
||||
Register(main.button_BUILD_Build, main.comPanel, comEventHandler, null);
|
||||
|
||||
DragEventHandler alignEventHandler = new DragEventHandler((sender, e) => { DropApkToAlign(e); });
|
||||
Register(main.zipalignPanel, ".apk", alignEventHandler);
|
||||
Register(main.textBox_ZIPALIGN_InputFile, ".apk", alignEventHandler, main.zipalignPanel);
|
||||
Register(main.button_ZIPALIGN_Align, ".apk", alignEventHandler, main.zipalignPanel);
|
||||
Register(main.zipalignPanel, null, alignEventHandler, apks);
|
||||
Register(main.textBox_ZIPALIGN_InputFile, main.zipalignPanel, alignEventHandler, apks);
|
||||
Register(main.button_ZIPALIGN_Align, main.zipalignPanel, alignEventHandler, apks);
|
||||
|
||||
DragEventHandler signEventHandler = new DragEventHandler((sender, e) => { DropApkToSign(e); });
|
||||
Register(main.signPanel, ".apk", signEventHandler);
|
||||
Register(main.textBox_SIGN_InputFile, ".apk", signEventHandler, main.signPanel);
|
||||
Register(main.button_SIGN_Sign, ".apk", signEventHandler, main.signPanel);
|
||||
Register(main.signPanel, null, signEventHandler, apks);
|
||||
Register(main.textBox_SIGN_InputFile, main.signPanel, signEventHandler, apks);
|
||||
Register(main.button_SIGN_Sign, main.signPanel, signEventHandler, apks);
|
||||
|
||||
DragEventHandler baksmaliEventHandler = new DragEventHandler((sender, e) => { DropDexToBaksmali(e); });
|
||||
Register(main.bakSmaliGroupBox, ".dex", baksmaliEventHandler);
|
||||
Register(main.bakSmaliGroupBox, null, baksmaliEventHandler, new string[] { ".dex" });
|
||||
main.bakSmaliGroupBox.AllowDrop = true;
|
||||
|
||||
DragEventHandler smaliEventHandler = new DragEventHandler((sender, e) => { DropDirToSmali(e); });
|
||||
Register(main.smaliGroupBox, "", smaliEventHandler);
|
||||
Register(main.smaliGroupBox, null, smaliEventHandler, null);
|
||||
main.smaliGroupBox.AllowDrop = true;
|
||||
|
||||
DragEventHandler apkInfoEventHandler = new DragEventHandler((sender, e) => { DropApkToGetInfo(e); });
|
||||
Register(main.basicInfoTabPage, ".apk", apkInfoEventHandler);
|
||||
Register(main.fileTxtBox, ".apk", apkInfoEventHandler);
|
||||
Register(main.basicInfoTabPage, null, apkInfoEventHandler, apks);
|
||||
Register(main.fileTxtBox, null, apkInfoEventHandler, apks);
|
||||
}
|
||||
|
||||
void Register(Control ctrl, string extension, DragEventHandler dragHandler, Control extCtrl = null)
|
||||
void Register(Control ctrl, Control extCtrl, DragEventHandler dragHandler, string[] extension)
|
||||
{
|
||||
if (extCtrl == null)
|
||||
extCtrl = ctrl;
|
||||
@@ -67,20 +70,24 @@ namespace APKToolGUI.Handlers
|
||||
private async void DropApkToDec(DragEventArgs e)
|
||||
{
|
||||
string apkFile = null;
|
||||
if (e.DropOneByEnd(".apk", file => apkFile = file))
|
||||
if (e.DropOneByEnd(file => apkFile = file, apks))
|
||||
{
|
||||
await main.GetApkInfo(apkFile);
|
||||
main.textBox_DECODE_InputAppPath.Text = apkFile;
|
||||
main.decPanel.BackColor = Color.White;
|
||||
|
||||
await main.Decompile(apkFile);
|
||||
await main.GetApkInfo(apkFile);
|
||||
|
||||
if (apkFile.ContainsAny(".xapk", ".zip", ".apks", ".apkm"))
|
||||
await main.MergeAPK(apkFile);
|
||||
else
|
||||
await main.Decompile(apkFile);
|
||||
}
|
||||
}
|
||||
|
||||
private async void DropDirToCom(DragEventArgs e)
|
||||
{
|
||||
string folder = null;
|
||||
if (e.DropOneByEnd("", file => folder = file))
|
||||
if (e.DropOneByEnd(file => folder = file, null))
|
||||
{
|
||||
if (File.Exists(Path.Combine(folder, "AndroidManifest.xml")))
|
||||
{
|
||||
@@ -96,7 +103,7 @@ namespace APKToolGUI.Handlers
|
||||
private async void DropApkToAlign(DragEventArgs e)
|
||||
{
|
||||
string apkFile = null;
|
||||
if (e.DropOneByEnd(".apk", file => apkFile = file))
|
||||
if (e.DropOneByEnd(file => apkFile = file, apks))
|
||||
{
|
||||
main.textBox_ZIPALIGN_InputFile.Text = apkFile;
|
||||
main.zipalignPanel.BackColor = Color.White;
|
||||
@@ -131,7 +138,7 @@ namespace APKToolGUI.Handlers
|
||||
private async void DropApkToSign(DragEventArgs e)
|
||||
{
|
||||
string apkFile = null;
|
||||
if (e.DropOneByEnd(".apk", file => apkFile = file))
|
||||
if (e.DropOneByEnd(file => apkFile = file, apks))
|
||||
{
|
||||
main.textBox_SIGN_InputFile.Text = apkFile;
|
||||
main.signPanel.BackColor = Color.White;
|
||||
@@ -177,7 +184,7 @@ namespace APKToolGUI.Handlers
|
||||
private async void DropDexToBaksmali(DragEventArgs e)
|
||||
{
|
||||
string apkFile = null;
|
||||
if (e.DropOneByEnd(".dex", file => apkFile = file))
|
||||
if (e.DropOneByEnd(file => apkFile = file, ".dex"))
|
||||
{
|
||||
main.baksmaliBrowseInputDexTxtBox.Text = apkFile;
|
||||
main.bakSmaliGroupBox.BackColor = Color.White;
|
||||
@@ -188,7 +195,7 @@ namespace APKToolGUI.Handlers
|
||||
private async void DropDirToSmali(DragEventArgs e)
|
||||
{
|
||||
string dir = null;
|
||||
if (e.DropOneByEnd("", file => dir = file))
|
||||
if (e.DropOneByEnd(file => dir = file, null))
|
||||
{
|
||||
main.smaliBrowseInputDirTxtBox.Text = dir;
|
||||
main.smaliGroupBox.BackColor = Color.White;
|
||||
@@ -199,7 +206,7 @@ namespace APKToolGUI.Handlers
|
||||
private void DropApkToGetInfo(DragEventArgs e)
|
||||
{
|
||||
string apkFile = null;
|
||||
if (e.DropOneByEnd(".apk", file => apkFile = file))
|
||||
if (e.DropOneByEnd(file => apkFile = file, apks))
|
||||
{
|
||||
main.smaliBrowseInputDirTxtBox.Text = apkFile;
|
||||
main.basicInfoTabPage.BackColor = Color.White;
|
||||
|
||||
+81
@@ -321,6 +321,15 @@ namespace APKToolGUI.Languages {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Decompiling all APK files.
|
||||
/// </summary>
|
||||
internal static string DecompilingAllApkFiles {
|
||||
get {
|
||||
return ResourceManager.GetString("DecompilingAllApkFiles", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Decompiling dex.
|
||||
/// </summary>
|
||||
@@ -348,6 +357,33 @@ namespace APKToolGUI.Languages {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} detected as base.
|
||||
/// </summary>
|
||||
internal static string DetectedAsBase {
|
||||
get {
|
||||
return ResourceManager.GetString("DetectedAsBase", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} detected as split.
|
||||
/// </summary>
|
||||
internal static string DetectedAsSplit {
|
||||
get {
|
||||
return ResourceManager.GetString("DetectedAsSplit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Directory {0} does not exist.
|
||||
/// </summary>
|
||||
internal static string DirNotExist {
|
||||
get {
|
||||
return ResourceManager.GetString("DirNotExist", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Done.
|
||||
/// </summary>
|
||||
@@ -582,6 +618,15 @@ namespace APKToolGUI.Languages {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Extracting all APK files.
|
||||
/// </summary>
|
||||
internal static string ExtractingAllApkFiles {
|
||||
get {
|
||||
return ResourceManager.GetString("ExtractingAllApkFiles", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to File.
|
||||
/// </summary>
|
||||
@@ -726,6 +771,24 @@ namespace APKToolGUI.Languages {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Merge finished. Moving directory to {0}.
|
||||
/// </summary>
|
||||
internal static string MergeFinishedMoveDir {
|
||||
get {
|
||||
return ResourceManager.GetString("MergeFinishedMoveDir", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Merging APK.
|
||||
/// </summary>
|
||||
internal static string MergingApk {
|
||||
get {
|
||||
return ResourceManager.GetString("MergingApk", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to META-INF folder does not exist. Skipped.
|
||||
/// </summary>
|
||||
@@ -753,6 +816,15 @@ namespace APKToolGUI.Languages {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Moving base directory to {0}.
|
||||
/// </summary>
|
||||
internal static string MovingBasedirectory {
|
||||
get {
|
||||
return ResourceManager.GetString("MovingBasedirectory", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to For the changes to take effect you must restart the program. You want to do it now?.
|
||||
/// </summary>
|
||||
@@ -906,6 +978,15 @@ namespace APKToolGUI.Languages {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Temp directory: {0}.
|
||||
/// </summary>
|
||||
internal static string TempDirectory {
|
||||
get {
|
||||
return ResourceManager.GetString("TempDirectory", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Text file.
|
||||
/// </summary>
|
||||
|
||||
@@ -432,4 +432,31 @@
|
||||
<data name="SetLanguageRestartApplication" xml:space="preserve">
|
||||
<value>The language is set. Do you want to restart the application?</value>
|
||||
</data>
|
||||
<data name="DecompilingAllApkFiles" xml:space="preserve">
|
||||
<value>Decompiling all APK files</value>
|
||||
</data>
|
||||
<data name="DetectedAsBase" xml:space="preserve">
|
||||
<value>{0} detected as base</value>
|
||||
</data>
|
||||
<data name="DetectedAsSplit" xml:space="preserve">
|
||||
<value>{0} detected as split</value>
|
||||
</data>
|
||||
<data name="ExtractingAllApkFiles" xml:space="preserve">
|
||||
<value>Extracting all APK files</value>
|
||||
</data>
|
||||
<data name="MergeFinishedMoveDir" xml:space="preserve">
|
||||
<value>Merge finished. Moving directory to {0}</value>
|
||||
</data>
|
||||
<data name="MergingApk" xml:space="preserve">
|
||||
<value>Merging APK</value>
|
||||
</data>
|
||||
<data name="MovingBasedirectory" xml:space="preserve">
|
||||
<value>Moving base directory to {0}</value>
|
||||
</data>
|
||||
<data name="DirNotExist" xml:space="preserve">
|
||||
<value>Directory {0} does not exist</value>
|
||||
</data>
|
||||
<data name="TempDirectory" xml:space="preserve">
|
||||
<value>Temp directory: {0}</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -62,7 +62,7 @@ namespace APKToolGUI
|
||||
}
|
||||
if (FilesCheck() == true)
|
||||
{
|
||||
Directory.CreateDirectory(TEMP_PATH);
|
||||
Directory.CreateDirectory(TempDirectory());
|
||||
PortableSettingsProvider.SettingsFileName = "config.xml";
|
||||
PortableSettingsProvider.ApplyProvider(Settings.Default);
|
||||
Application.Run(new FormMain());
|
||||
@@ -165,18 +165,17 @@ namespace APKToolGUI
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string TempDir()
|
||||
public static string TempDirectory()
|
||||
{
|
||||
//Generate new every new instance to avoid conflict
|
||||
//We want to keep obfuscated path short as possible to prevent long path error
|
||||
if (Settings.Default.UseCustomTempDir)
|
||||
return Path.Combine(Settings.Default.TempDir, StringExt.RandStrWithCaps(5));
|
||||
else
|
||||
return Path.Combine(Path.GetTempPath(), StringExt.RandStrWithCaps(5));
|
||||
return Path.Combine(LOCAL_APPDATA_PATH, ASSEMBLY_NAME, StringExt.RandStrWithCaps(5));
|
||||
}
|
||||
|
||||
public static string LOCAL_APPDATA_PATH = Environment.GetEnvironmentVariable("LocalAppData");
|
||||
public static string TEMP_PATH = TempDir();
|
||||
public static string APP_PATH = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
public static string RES_PATH = Path.Combine(APP_PATH, "Resources");
|
||||
public static string ASSEMBLY_NAME = AssemblyName.GetAssemblyName(Assembly.GetExecutingAssembly().Location).Name;
|
||||
|
||||
@@ -52,19 +52,12 @@ namespace APKToolGUI.Utils
|
||||
Directory.CreateDirectory(targetFolder);
|
||||
foreach (var file in folder)
|
||||
{
|
||||
//try
|
||||
//{
|
||||
//Debug.WriteLine("Move file: " + file);
|
||||
var targetFile = Path.Combine(targetFolder, Path.GetFileName(file));
|
||||
File.Copy(file, targetFile, true);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// Debug.WriteLine("Error moving file: " + file + " (" + ex.Message + ")");
|
||||
//}
|
||||
if (File.Exists(targetFile))
|
||||
File.Delete(targetFile);
|
||||
File.Move(file, targetFile);
|
||||
}
|
||||
}
|
||||
Directory.Delete(source, true);
|
||||
}
|
||||
|
||||
public static void ReplaceinFiles(
|
||||
|
||||
@@ -32,53 +32,27 @@ namespace SaveToGameWpf.Logic.Utils
|
||||
|
||||
return filter == null ? items : items.Where(filter).ToArray();
|
||||
}
|
||||
public static void CheckDragEnter(this DragEventArgs e, string extensions)
|
||||
{
|
||||
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
|
||||
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
foreach (var file in files)
|
||||
{
|
||||
var ext = Path.GetExtension(file);
|
||||
if (!String.IsNullOrEmpty(extensions) && ext.Equals(extensions))
|
||||
{
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
return;
|
||||
}
|
||||
else if (String.IsNullOrEmpty(extensions))
|
||||
{
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.Effect = DragDropEffects.None;
|
||||
}
|
||||
|
||||
public static bool CheckDragOver(this DragEventArgs e, string extensions)
|
||||
{
|
||||
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return false;
|
||||
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
foreach (var file in files)
|
||||
{
|
||||
var ext = Path.GetExtension(file);
|
||||
if (!String.IsNullOrEmpty(extensions) && ext.Equals(extensions))
|
||||
{
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
return true;
|
||||
}
|
||||
//else if (String.IsNullOrEmpty(extensions) && File.Exists(Path.Combine(file, "AndroidManifest.xml")))
|
||||
else if (String.IsNullOrEmpty(extensions))
|
||||
{
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool CheckManyDragOver(this DragEventArgs e, params string[] extensions)
|
||||
public static void CheckDragEnter(this DragEventArgs e, params string[] extensions)
|
||||
{
|
||||
string[] files = e.GetFilesDrop();
|
||||
if (extensions.Any(ext => files[0].EndsWith(ext, StringComparison.Ordinal)))
|
||||
if (extensions == null && Directory.Exists(files[0]))
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
else if (extensions.Any(ext => files[0].EndsWith(ext, StringComparison.Ordinal)))
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
else
|
||||
e.Effect = DragDropEffects.None;
|
||||
}
|
||||
|
||||
public static bool CheckDragOver(this DragEventArgs e, params string[] extensions)
|
||||
{
|
||||
string[] files = e.GetFilesDrop();
|
||||
if (extensions == null && Directory.Exists(files[0]))
|
||||
{
|
||||
e.Effect = DragDropEffects.Move;
|
||||
return true;
|
||||
}
|
||||
else if (files.Length == 1 && extensions.Any(ext => files[0].EndsWith(ext, StringComparison.Ordinal)))
|
||||
{
|
||||
e.Effect = DragDropEffects.Move;
|
||||
return true;
|
||||
@@ -88,46 +62,41 @@ namespace SaveToGameWpf.Logic.Utils
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool DropOneByEnd(this DragEventArgs e, string ext, Action<string> onSuccess)
|
||||
{
|
||||
string[] files = e.GetFilesDrop(ext);
|
||||
|
||||
if (files.Length == 1)
|
||||
public static bool CheckManyDragOver(this DragEventArgs e, params string[] extensions)
|
||||
{
|
||||
string[] files = e.GetFilesDrop();
|
||||
|
||||
if (extensions == null && Directory.Exists(files[0]))
|
||||
{
|
||||
e.Effect = DragDropEffects.Move;
|
||||
return true;
|
||||
}
|
||||
else if (extensions.Any(ext => files[0].EndsWith(ext, StringComparison.Ordinal)))
|
||||
{
|
||||
e.Effect = DragDropEffects.Move;
|
||||
return true;
|
||||
}
|
||||
e.Effect = DragDropEffects.None;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool DropOneByEnd(this DragEventArgs e, Action<string> onSuccess, params string[] extensions)
|
||||
{
|
||||
string[] files = e.GetFilesDrop();
|
||||
if (extensions == null && Directory.Exists(files[0]))
|
||||
{
|
||||
onSuccess(files[0]);
|
||||
return true;
|
||||
}
|
||||
else if (extensions.Any(ext => files[0].EndsWith(ext, StringComparison.Ordinal)))
|
||||
{
|
||||
onSuccess(files[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string DropOneByEnd(this DragEventArgs e, string ext)
|
||||
{
|
||||
string[] files = e.GetFilesDrop(ext);
|
||||
|
||||
if (files.Length == 1)
|
||||
return files[0];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DropManyByEnd(this DragEventArgs e, string ext, Action<string[]> onSuccess)
|
||||
{
|
||||
string[] files = e.GetFilesDrop(ext);
|
||||
|
||||
if (files.Length > 0)
|
||||
onSuccess(files);
|
||||
}
|
||||
|
||||
public static string[] DropManyByEnd(this DragEventArgs e, string ext)
|
||||
{
|
||||
string[] files = e.GetFilesDrop(ext);
|
||||
|
||||
if (files.Length > 0)
|
||||
return files;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,5 +32,16 @@ namespace APKToolGUI.Utils
|
||||
{
|
||||
return Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path));
|
||||
}
|
||||
|
||||
public static string GetRelativePath(string relativeTo, string path)
|
||||
{
|
||||
var uri = new Uri(relativeTo);
|
||||
var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false)
|
||||
{
|
||||
rel = $".{Path.DirectorySeparatorChar}{rel}";
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,5 +69,16 @@ namespace APKToolGUI.Utils
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ContainsAny(this string haystack, params string[] needles)
|
||||
{
|
||||
foreach (string needle in needles)
|
||||
{
|
||||
if (haystack.Contains(needle))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,15 @@ namespace APKToolGUI.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExtractAll(string path, string destination, bool flattenFoldersOnExtract = false)
|
||||
{
|
||||
using (ZipFile zip = ZipFile.Read(path))
|
||||
{
|
||||
zip.FlattenFoldersOnExtract = flattenFoldersOnExtract;
|
||||
zip.ExtractAll(destination, ExtractExistingFileAction.OverwriteSilently);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddDirectory(string path, string fileName, string directoryPathInArchive = "")
|
||||
{
|
||||
ZipFile zip = new ZipFile();
|
||||
|
||||
Reference in New Issue
Block a user