diff --git a/ApkManager/AdbWindow.xaml b/ApkManager/AdbWindow.xaml new file mode 100644 index 0000000..fb57b76 --- /dev/null +++ b/ApkManager/AdbWindow.xaml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApkManager/AdbWindow.xaml.cs b/ApkManager/AdbWindow.xaml.cs new file mode 100644 index 0000000..2f58676 --- /dev/null +++ b/ApkManager/AdbWindow.xaml.cs @@ -0,0 +1,299 @@ +using ApkManager.Lib; +using MahApps.Metro.Controls; +using System.ComponentModel; +using System.IO; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Kind = MahApps.Metro.IconPacks.PackIconMaterialKind; + +namespace ApkManager +{ + /// + /// Interaction logic for AdbWindow.xaml + /// + public partial class AdbWindow : MetroWindow + { + private Config cfg; + private Adb adb; + private Apk apk; + private bool isLoading = false; + + public enum ShowMenu { Main, Install, Uninstall } + + #region WINDOW + public AdbWindow(Apk apk) + { + InitializeComponent(); + + // make all flyout width same as root + foreach (Flyout flayout in Flyouts.Items) + flayout.Width = this.Width; + + // set switch in settings + cfg = new Config(); + swClose.IsChecked = cfg.AutoClose(); + + // define adb + adb = new Adb(); + adb.OnProcess += (value) => ShowLoading(value); + adb.OutputDataReceived += (msg) => CommandOutput_Insert(msg, false); + adb.ErrorDataReceived += (msg) => CommandOutput_Insert(msg, true); + + this.apk = apk; + } + + private void ShowLoading(bool state = true) + { + Dispatcher.Invoke(() => { + isLoading = state; + PanelLoading.Visibility = state ? Visibility.Visible : Visibility.Collapsed; + }); + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + // refresh list + ButtonRefresh_Click(sender, e); + } + + private void OnClosing(object sender, CancelEventArgs e) + { + e.Cancel = isLoading; + } + #endregion + + #region TOPBAR + private async void WindowCommand_Click(object sender, RoutedEventArgs e) + { + if (!(sender is Button btn)) return; + switch ((string)btn.Tag) + { + case "reconnect": + await adb.Reconnect(); + ButtonRefresh_Click(null, null); + break; + case "settings": + menuSettings.IsOpen = !menuSettings.IsOpen; + break; + } + } + + private void SwitchSettings_Click(object sender, RoutedEventArgs e) + { + if (!(sender is ToggleSwitch ts)) return; + + var tag = ts.Tag as string; + if (tag == "Close") + cfg.AutoClose(ts.IsChecked == true); + } + #endregion + + #region DEVICES + private async void ComboDevices_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!(sender is ComboBox combo)) return; + var address = combo.SelectedItem as string; + + txtDevice.Text = ". . . ."; + txtAndroid.Text = ". . . ."; + txtArch.Text = ". . . ."; + txtSdk.Text = ". . . ."; + btnMenuInstall.IsEnabled = false; + + if (!string.IsNullOrWhiteSpace(address)) + { + var device = await adb.GetDevice(address); + var supported = apk.SdkVersion <= device.Sdk; + + txtDevice.Text = device.Name; + txtAndroid.Text = device.Android; + txtArch.Text = device.Arch; + txtSdk.Text = device.Sdk.ToString(); + txtSdk.Foreground = supported ? txtArch.Foreground : Brushes.Red; + btnMenuInstall.IsEnabled = supported; + } + } + + private async void ButtonRefresh_Click(object sender, RoutedEventArgs e) + { + cbDevices.Items.Clear(); + + var devices = await adb.GetDevices(); + foreach(var device in devices) + { + cbDevices.Items.Add(device); + } + + cbDevices.SelectedIndex = 0; + } + + private void ButtonWireless_Click(object sender, RoutedEventArgs e) + { + var selected = cbDevices.Text; + var last = cfg.LastAddress(); + if (selected.IsValidIPAddress()) + txtAddress.Text = selected; + else if (!string.IsNullOrWhiteSpace(last)) + txtAddress.Text = last; + + menuWifi.IsOpen = true; + } + + private async void ButtonConnect_Click(object sender, RoutedEventArgs e) + { + if (string.IsNullOrWhiteSpace(txtAddress.Text)) return; + + menuWifi.IsOpen = false; + + var result = await adb.Connect(txtAddress.Text); + if (result) + { + if (cfg.LastAddress() != txtAddress.Text) + cfg.LastAddress(txtAddress.Text); + + ButtonRefresh_Click(null, null); + } + else menuWifi.IsOpen = true; + } + + private async void ButtonDisconnect_Click(object sender, RoutedEventArgs e) + { + if (string.IsNullOrWhiteSpace(txtAddress.Text)) return; + + menuWifi.IsOpen = false; + + var result = await adb.Disconnect(txtAddress.Text); + if (result) ButtonRefresh_Click(null, null); + else menuWifi.IsOpen = true; + } + + private void TextAddress_TextChanged(object sender, TextChangedEventArgs e) + { + if (!(sender is TextBox tb)) return; + if (!string.IsNullOrWhiteSpace(tb.Text)) + { + var isValid = tb.Text.IsValidIPAddress(); + IconAddress.Kind = isValid ? Kind.Wifi : Kind.WifiOff; + ButtonConnect.IsEnabled = isValid; + ButtonDisconnect.IsEnabled = isValid; + } + else + { + IconAddress.Kind = Kind.WifiOff; + ButtonConnect.IsEnabled = false; + ButtonDisconnect.IsEnabled = false; + } + } + #endregion + + #region OUTPUT + private void CommandOutput_Reset() + { + Dispatcher.Invoke(() => lbOutput.Items.Clear()); + } + + private void CommandOutput_Insert(string message, bool error = false) + { + Dispatcher.Invoke(() => + { + var color = Brushes.WhiteSmoke; + if (error || message.ToLower().Contains("failed")) + color = Brushes.IndianRed; + else if (message.ToLower().Contains("success")) + color = Brushes.LightBlue; + + var item = new ListBoxItem() + { + Content = message.Trim(), + Background = color + }; + lbOutput.Items.Add(item); + lbOutput.ScrollIntoView(item); + }); + } + #endregion + + #region ACTION + private void ShowActionMenu(ShowMenu menu) + { + if (menu == ShowMenu.Install) + { + wcReconnect.Visibility = Visibility.Collapsed; + + actionMain.Visibility = Visibility.Collapsed; + actionInstall.Visibility = Visibility.Visible; + actionUninstall.Visibility = Visibility.Collapsed; + + CommandOutput_Reset(); + CommandOutput_Insert($"File APK : {Path.GetFileName(apk.FilePath)}"); + + gbTarget.IsEnabled = false; + gbAction.Header = "Action : Install"; + gbCommand.Visibility = Visibility.Visible; + } + else if (menu == ShowMenu.Uninstall) + { + wcReconnect.Visibility = Visibility.Collapsed; + + actionMain.Visibility = Visibility.Collapsed; + actionInstall.Visibility = Visibility.Collapsed; + actionUninstall.Visibility = Visibility.Visible; + + CommandOutput_Reset(); + CommandOutput_Insert($"Package : {apk.PackageName}"); + + gbTarget.IsEnabled = false; + gbAction.Header = "Action : Uninstall"; + gbCommand.Visibility = Visibility.Visible; + } + else + { + wcReconnect.Visibility = Visibility.Visible; + + actionMain.Visibility = Visibility.Visible; + actionInstall.Visibility = Visibility.Collapsed; + actionUninstall.Visibility = Visibility.Collapsed; + + gbTarget.IsEnabled = true; + gbAction.Header = "Select Action"; + gbCommand.Visibility = Visibility.Collapsed; + } + } + + private void ButtonMenuInstall_Click(object sender, RoutedEventArgs e) + { + ShowActionMenu(ShowMenu.Install); + } + + private void ButtonMenuUninstall_Click(object sender, RoutedEventArgs e) + { + ShowActionMenu(ShowMenu.Uninstall); + } + + private void ButtonMenuBack_Click(object sender, RoutedEventArgs e) + { + ShowActionMenu(ShowMenu.Main); + } + + private async void ButtonActionInstall_Click(object sender, RoutedEventArgs e) + { + var result = await adb.Install(cbDevices.Text, apk.FilePath); + if (result && cfg.AutoClose()) + this.Close(); + } + + private async void ButtonActionUninstall_Click(object sender, RoutedEventArgs e) + { + CommandOutput_Insert("Force remove app and data...."); + await adb.Uninstall(cbDevices.Text, apk.PackageName); + } + + private async void ButtonActionUninstallKeep_Click(object sender, RoutedEventArgs e) + { + CommandOutput_Insert("Remove app but keep the data...."); + await adb.Uninstall(cbDevices.Text, apk.PackageName, true); + } + #endregion + } +} diff --git a/ApkManager/ApkManager.csproj b/ApkManager/ApkManager.csproj index 0453f4b..605ce1c 100644 --- a/ApkManager/ApkManager.csproj +++ b/ApkManager/ApkManager.csproj @@ -33,9 +33,28 @@ prompt 4 + + Resources\Playstore.ico + + + ..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll + + + ..\packages\MahApps.Metro.1.6.5\lib\net45\MahApps.Metro.dll + + + ..\packages\MahApps.Metro.IconPacks.Material.3.5.0\lib\net45\MahApps.Metro.IconPacks.Core.dll + + + ..\packages\MahApps.Metro.IconPacks.Material.3.5.0\lib\net45\MahApps.Metro.IconPacks.Material.dll + + + + ..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll + @@ -48,24 +67,48 @@ + + ..\packages\ZipStorer.3.6.0\lib\net20\ZipStorer.dll + MSBuild:Compile Designer + + + RenamerWindow.xaml + + + Designer + MSBuild:Compile + MSBuild:Compile Designer + + AdbWindow.xaml + + App.xaml Code + + + + + MainWindow.xaml Code + + Designer + MSBuild:Compile + @@ -85,13 +128,33 @@ ResXFileCodeGenerator Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs - + + Designer + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + \ No newline at end of file diff --git a/ApkManager/App.config b/ApkManager/App.config index 8e15646..e1f3697 100644 --- a/ApkManager/App.config +++ b/ApkManager/App.config @@ -1,6 +1,23 @@  - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApkManager/App.xaml b/ApkManager/App.xaml index 75e6e41..ec7bd23 100644 --- a/ApkManager/App.xaml +++ b/ApkManager/App.xaml @@ -2,8 +2,18 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ApkManager" - StartupUri="MainWindow.xaml"> + Startup="Application_Startup"> - + + + + + + + + + + + diff --git a/ApkManager/App.xaml.cs b/ApkManager/App.xaml.cs index 681df86..70a05ad 100644 --- a/ApkManager/App.xaml.cs +++ b/ApkManager/App.xaml.cs @@ -1,10 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Configuration; +using ApkManager.Lib; +using System; using System.Data; +using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; +using System.Reflection; +using System.Security.Principal; +using System.Threading; using System.Windows; +using System.Windows.Media.Imaging; namespace ApkManager { @@ -13,5 +16,47 @@ namespace ApkManager /// public partial class App : Application { + private void Application_Startup(object sender, StartupEventArgs e) + { + var cfg = new Config(); + using (var mutex = new Mutex(true, "{BB8C82B6-7851-46A0-A902-48446B59CAD6}", out bool isFirstInstance)) + { + if (!isFirstInstance && cfg.SingleInstance()) return; + + var apks = e.Args.Where(s => s.ToLower().EndsWith(".apk")).ToArray(); + + if (apks.Count() <= 1) + new MainWindow(apks.FirstOrDefault()).ShowDialog(); + } + } + + public static bool IsAdministrator() + { + using (var identity = WindowsIdentity.GetCurrent()) + { + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + + public static BitmapImage GetImageFromResources(string pathResource) + { + try + { + var assembly = Assembly.GetExecutingAssembly().GetName().Name; + var packUri = string.Format("pack://application:,,,/{0};component/{1}", assembly, pathResource); + return new BitmapImage(new Uri(packUri)); + } + catch (Exception e) + { + Debug.Print("App.GetImageResource: {0}", e.Message); + return null; + } + } + + public static BitmapImage GetPlaystoreImageFromResources() + { + return GetImageFromResources("Resources/Playstore.png"); + } } } diff --git a/ApkManager/Lib/Aapt.cs b/ApkManager/Lib/Aapt.cs new file mode 100644 index 0000000..d3967e4 --- /dev/null +++ b/ApkManager/Lib/Aapt.cs @@ -0,0 +1,173 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace ApkManager.Lib +{ + class Aapt + { + public class Result + { + public Apk Apk { get; set; } + public bool Success { get; set; } + public string Message { get; set; } + } + + private static BitmapImage GetIconFrom(string pathApk, string pathIcon) + { + try + { + using (var stream = new MemoryStream()) + using (var zip = ZipStorer.Open(pathApk, FileAccess.Read)) + { + if (!pathIcon.EndsWith(".xml")) + { + var fileEntry = zip.ReadCentralDir().Where(f => f.FilenameInZip.Equals(pathIcon)).SingleOrDefault(); + zip.ExtractFile(fileEntry, stream); + } + else + { + var icon = Path.GetFileNameWithoutExtension(pathIcon) + ".png"; + var fileEntry = zip.ReadCentralDir().Where(f => f.FilenameInZip.EndsWith(icon) && f.FilenameInZip.Contains("hdpi")).LastOrDefault(); + zip.ExtractFile(fileEntry, stream); + } + + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.StreamSource = stream; + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); + + return bitmap; + } + } + catch (Exception e) + { + Debug.Print("Apk.GetIcon: {0}", e.Message); + return App.GetPlaystoreImageFromResources(); + } + } + + public static async Task RunAsync(string command, params object[] args) + { + return await Task.Run(() => + { + using (var p = new Process()) + { + p.StartInfo = new ProcessStartInfo() + { + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + WindowStyle = ProcessWindowStyle.Hidden, + FileName = Path.Combine("Lib", "aapt2.exe"), + Arguments = string.Format(command, args) + }; + + p.Start(); + + Debug.Print("Aapt.Command: aapt2 {0}", string.Format(command, args)); + + var output = p.StandardOutput.ReadToEnd(); + var error = p.StandardError.ReadToEnd(); + + p.WaitForExit(); + + if (p.ExitCode != 0) throw new Exception(error); + return output; + } + }); + } + + public static async Task DumbBadging(string pathApk) + { + var output = string.Empty; + + try + { + output = await RunAsync("dump badging \"{0}\"", pathApk); + } + catch (Exception e) + { + Debug.Print("Aapt.DumbBadging: {0}", e.Message); + return new Result() + { + Success = false, + Message = e.Message + }; + } + + var apk = new Apk() { FilePath = pathApk }; + + //package + var match = Regex.Match(output, "package: name='(.+?)'"); + if (match.Success) apk.PackageName = match.Groups[1].Value; + + //versionCode + match = Regex.Match(output, "package.+versionCode='(.+?)'"); + if (match.Success) + { + double.TryParse(match.Groups[1].Value, out double versionCode); + apk.VersionCode = versionCode; + } + + //versionName + match = Regex.Match(output, "package.+versionName='(.+?)'"); + if (match.Success) apk.VersionName = match.Groups[1].Value; + + //label + match = Regex.Match(output, "application.+label='(.+?)'"); + if (match.Success) apk.Label = match.Groups[1].Value; + + //icon + match = Regex.Match(output, "application.+icon='(.+?)'"); + if (match.Success) apk.Icon = GetIconFrom(pathApk, match.Groups[1].Value); + + //sdkVersion + match = Regex.Match(output, @"sdkVersion:'(\d+?)'"); + if (match.Success) + { + int.TryParse(match.Groups[1].Value, out int sdkVersion); + apk.SdkVersion = sdkVersion; + } + + //targetSdkVersion + match = Regex.Match(output, @"targetSdkVersion:'(\d+?)'"); + if (match.Success) + { + int.TryParse(match.Groups[1].Value, out int targetSdkVersion); + apk.TargetSdkVersion = targetSdkVersion; + } + + //permission + var matches = Regex.Matches(output, "uses-permission: name='(.+?)'"); + foreach (Match m in matches) apk.Permissions.Add(m.Groups[1].Value); + + //native-code + match = Regex.Match(output, "native-code: '(.+)'"); + if (match.Success) + { + apk.AbiList = match.Groups[1].Value.Replace("' '",", "); + apk.Platforms = match.Groups[1].Value.Replace("' '", " ").Split(' ').ToList(); + } + else + { + apk.AbiList = "any"; + apk.Platforms.Add("any"); + } + + return new Result() + { + Apk = apk, + Success = true + }; + } + } +} diff --git a/ApkManager/Lib/Adb.cs b/ApkManager/Lib/Adb.cs new file mode 100644 index 0000000..dc4f7cf --- /dev/null +++ b/ApkManager/Lib/Adb.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace ApkManager +{ + internal class Adb + { + public class Device + { + public string Name { get; set; } + public string Android { get; set; } + public string Arch { get; set; } + public int Sdk { get; set; } + + public override string ToString() + { + return Name.ToString(); + } + } + + private static bool OVERRIDE_ONPROCESSEVENT = false; + + public delegate void ProcessEventHandler(bool value); + + public event ProcessEventHandler OnProcess; + + public delegate void OutputEventHander(string message); + + public event OutputEventHander OutputDataReceived; + + public delegate void ErrorEventHandler(string message); + + public event ErrorEventHandler ErrorDataReceived; + + public Adb() + { + } + + private async Task RunAsync(string command, params object[] args) + { + return await Task.Run(() => + { + using (var p = new Process()) + { + var OutputMessage = string.Empty; + p.OutputDataReceived += (s, e) => { + if (string.IsNullOrWhiteSpace(e.Data)) return; + OutputMessage += e.Data + Environment.NewLine; + OutputDataReceived?.Invoke(e.Data); + }; + + var ErrorMessage = string.Empty; + p.ErrorDataReceived += (s, e) => { + if (string.IsNullOrWhiteSpace(e.Data)) return; + ErrorMessage += e.Data + Environment.NewLine; + ErrorDataReceived?.Invoke(e.Data); + }; + + p.StartInfo = new ProcessStartInfo() + { + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + WindowStyle = ProcessWindowStyle.Hidden, + FileName = Path.Combine("Lib", "adb.exe"), + Arguments = string.Format(command, args) + }; + + p.Start(); + + Debug.Print("Adb.Command: adb {0}", string.Format(command, args)); + + if (!OVERRIDE_ONPROCESSEVENT) + OnProcess?.Invoke(true); + + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + + p.WaitForExit(); + + if (!OVERRIDE_ONPROCESSEVENT) + OnProcess?.Invoke(false); + + if (p.ExitCode != 0) throw new Exception(ErrorMessage); + + Debug.Print("Adb.Output: {0}", OutputMessage); + return OutputMessage; + }; + }); + } + + public async Task StartServer() + { + try + { + await RunAsync("start-server"); + return true; + } + catch (Exception e) + { + Debug.Print("Adb.StartServer: {0}", e.Message); + return false; + } + } + + public async Task Reconnect() + { + try + { + await RunAsync("reconnect"); + return true; + } + catch (Exception e) + { + Debug.Print("Adb.Reconnect: {0}", e.Message); + return false; + } + } + + public async Task Connect(string address) + { + try + { + if (!address.Contains(":")) + address += ":5555"; + + var result = await RunAsync("connect {0}", address); + return result.Contains("connected"); + } + catch (Exception e) + { + Debug.Print("Adb.Connect: {0}", e.Message); + return false; + } + } + + public async Task Disconnect(string address) + { + try + { + if (!address.Contains(":")) + address += ":5555"; + + var result = await RunAsync("disconnect {0}", address); + return result.Contains("disconnected") || result.Contains("no such device"); + } + catch (Exception e) + { + Debug.Print("Adb.Disconnect: {0}", e.Message); + return false; + } + } + + public async Task> GetDevices() + { + var devices = new List(); + + try + { + var result = await RunAsync("devices"); + var matches = Regex.Matches(result, @"(.+?)\tdevice"); + foreach (Match match in matches) + { + devices.Add(match.Groups[1].Value); + } + } + catch (Exception e) + { + Debug.Print("Adb.GetDevices: {0}", e.Message); + } + + return devices; + } + + public async Task Install(string device, string pathApk) + { + try + { + var result = await RunAsync("-s {0} install -r \"{1}\"", device, pathApk); + return result.Contains("Success"); + } + catch (Exception e) + { + Debug.Print("Adb.Install: {0}", e.Message); + return false; + } + } + + public async Task Uninstall(string device, string package, bool keepData = false) + { + try + { + var command = keepData ? $"-s {device} shell pm uninstall -k {package}" : $"-s {device} uninstall {package}"; + var result = await RunAsync(command); + return result.Contains("Success"); + } + catch (Exception e) + { + Debug.Print("Adb.Uninstall: {0}", e.Message); + return false; + } + } + + public async Task GetProp(string device, string prop) + { + var result = await RunAsync("-s {0} shell getprop {1}", device, prop); + return string.IsNullOrWhiteSpace(result) ? "Unknown" : result.Trim(); + } + + public async Task GetDevice(string address) + { + OVERRIDE_ONPROCESSEVENT = true; + OnProcess?.Invoke(true); + + var name = await GetProp(address, "ro.product.model"); + if (string.IsNullOrWhiteSpace(name)) + name = await GetProp(address, "ro.product.brand"); + if (string.IsNullOrWhiteSpace(name)) + name = await GetProp(address, "ro.product.device"); + if (string.IsNullOrWhiteSpace(name)) + name = "Unknown"; + + var android = await GetProp(address, "ro.build.version.release"); + if (string.IsNullOrWhiteSpace(android)) + android = "Unknown"; + + var sdk = await GetProp(address, "ro.build.version.sdk"); + int.TryParse(sdk, out int _sdk); + + var arch = await GetProp(address, "ro.product.cpu.abi"); + + OnProcess?.Invoke(false); + OVERRIDE_ONPROCESSEVENT = false; + + return new Device() + { + Name = name, + Android = android, + Sdk = _sdk, + Arch = arch + }; + } + } +} \ No newline at end of file diff --git a/ApkManager/Lib/AdbWinApi.dll b/ApkManager/Lib/AdbWinApi.dll new file mode 100644 index 0000000..7abe26c Binary files /dev/null and b/ApkManager/Lib/AdbWinApi.dll differ diff --git a/ApkManager/Lib/AdbWinUsbApi.dll b/ApkManager/Lib/AdbWinUsbApi.dll new file mode 100644 index 0000000..e7a6de1 Binary files /dev/null and b/ApkManager/Lib/AdbWinUsbApi.dll differ diff --git a/ApkManager/Lib/Apk.cs b/ApkManager/Lib/Apk.cs new file mode 100644 index 0000000..1390b7d --- /dev/null +++ b/ApkManager/Lib/Apk.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Windows.Media.Imaging; + +namespace ApkManager.Lib +{ + public class Apk + { + public string PackageName { get; set; } + public string Label { get; set; } + public string VersionName { get; set; } + public double VersionCode { get; set; } + public int SdkVersion { get; set; } + public int TargetSdkVersion { get; set; } + public BitmapImage Icon { get; set; } + public IList Permissions { get; set; } + public IList Platforms { get; set; } + public string AbiList { get; set; } + public string FilePath { get; set; } + + public Apk() + { + Permissions = new List(); + Platforms = new List(); + } + } +} diff --git a/ApkManager/Lib/Config.cs b/ApkManager/Lib/Config.cs new file mode 100644 index 0000000..fbaae5a --- /dev/null +++ b/ApkManager/Lib/Config.cs @@ -0,0 +1,197 @@ +using System; +using System.Configuration; +using System.Diagnostics; + +namespace ApkManager.Lib +{ + class Config + { + private Configuration config; + + #region KEY STRING + private static readonly string KEY_APPSETTINGS = "appSettings"; + // Window + private static readonly string KEY_SINGLEINSTANCE = "SingleInstance"; + private static readonly string KEY_SAVEWINDOWPOSITION = "SaveWindowPosition"; + private static readonly string KEY_WINDOWMAIN = "WindowMain"; + // Last used + private static readonly string KEY_LASTADDRESS = "LastAddress"; + private static readonly string KEY_AUTOCLOSE = "AutoClose"; + // Usage + private static readonly string KEY_PATTERN = "UsePattern"; + private static readonly string KEY_LABEL = "UseLabel"; + private static readonly string KEY_PACKAGE = "UsePackage"; + private static readonly string KEY_VERSION = "UseVersion"; + private static readonly string KEY_BUILD = "UseBuild"; + private static readonly string KEY_SUFFIXENCLOSURE = "UseSuffixEnclosure"; + private static readonly string KEY_SEPARATOR = "Separator"; + #endregion + + #region SET VALUE + private bool SetValue(string key, string value) + { + try + { + config.AppSettings.Settings[key].Value = value; + config.Save(ConfigurationSaveMode.Modified); + + ConfigurationManager.RefreshSection(KEY_APPSETTINGS); + return true; + } + catch (Exception e) + { + Debug.Print(e.Message); + return false; + } + } + + private bool SetValue(string key, bool value) + { + return SetValue(key, value.ToString()); + } + + private bool SetValue(string key, int value) + { + return SetValue(key, value.ToString()); + } + + private bool SetValue(string key, double value) + { + return SetValue(key, value.ToString()); + } + #endregion + + #region GET VALUE + private string GetString(string key) + { + try + { + var value = ConfigurationManager.AppSettings[key]; + if (string.IsNullOrWhiteSpace(value)) + throw new Exception($"Config: key \"{key}\" is not found!"); + else + return value; + } + catch (Exception e) + { + Debug.Print(e.Message); + return string.Empty; + } + } + + private bool GetBoolean(string key) + { + return GetString(key).ToLower() == "true"; + } + + private int GetInteger(string key) + { + var value = GetString(key); + int.TryParse(value, out int num); + return num; + } + + private double GetDouble(string key) + { + var value = GetString(key); + double.TryParse(value, out double num); + return num; + } + #endregion + + #region GET-SET CONFIG + public Config() + { + config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + } + + public bool SingleInstance() + { + return GetBoolean(KEY_SINGLEINSTANCE); + } + + public bool SingleInstance(bool value) + { + return SetValue(KEY_SINGLEINSTANCE, value); + } + + public WindowPosition WindowPostition() + { + try + { + var value = GetString(KEY_WINDOWMAIN).Split(','); + double.TryParse(value[0], out double top); + double.TryParse(value[1], out double left); + return new WindowPosition() { Top = top, Left = left }; + } + catch (Exception e) + { + Debug.Print("Config.WindowPostition: {0}", e.Message); + return new WindowPosition() { Top = 0, Left = 0 }; + } + } + + public bool WindowPostition(WindowPosition position) + { + var window = string.Format("{0},{1}", position.Top, position.Left); + return SetValue(KEY_WINDOWMAIN, window); + } + + public bool GetWindowPostition() + { + return GetBoolean(KEY_SAVEWINDOWPOSITION); + } + + public bool SetWindowPostition(bool value) + { + return SetValue(KEY_SAVEWINDOWPOSITION, value); + } + + public string LastAddress() + { + return GetString(KEY_LASTADDRESS); + } + + public bool LastAddress(string address) + { + return SetValue(KEY_LASTADDRESS, address); + } + + public bool AutoClose() + { + return GetBoolean(KEY_AUTOCLOSE); + } + + public bool AutoClose(bool value) + { + return SetValue(KEY_AUTOCLOSE, value); + } + + public NameFormat GetNameFormat() + { + return new NameFormat + { + UsePattern = GetString(KEY_PATTERN), + UseLabel = GetBoolean(KEY_LABEL), + UsePackage = GetBoolean(KEY_PACKAGE), + UseVersion = GetBoolean(KEY_VERSION), + UseBuild = GetBoolean(KEY_BUILD), + UseSuffixEnclosure = GetBoolean(KEY_SUFFIXENCLOSURE), + Separator = (Separator)GetInteger(KEY_SEPARATOR) + }; + } + + public bool SetNameFormat(NameFormat nf) + { + var pattern = SetValue(KEY_PATTERN, nf.UsePattern); + var label = SetValue(KEY_LABEL, nf.UseLabel); + var package = SetValue(KEY_PACKAGE, nf.UsePackage); + var version = SetValue(KEY_VERSION, nf.UseVersion); + var build = SetValue(KEY_BUILD, nf.UseBuild); + var enclosure = SetValue(KEY_SUFFIXENCLOSURE, nf.UseSuffixEnclosure); + var separator = SetValue(KEY_SEPARATOR, (int)nf.Separator); + return pattern && label && package && version && build && enclosure && separator; + } + #endregion + } +} diff --git a/ApkManager/Lib/FileAssociation.cs b/ApkManager/Lib/FileAssociation.cs new file mode 100644 index 0000000..c5179b5 --- /dev/null +++ b/ApkManager/Lib/FileAssociation.cs @@ -0,0 +1,80 @@ +using Microsoft.Win32; +using System; +using System.Diagnostics; +using System.IO; + +namespace ApkManager +{ + class FileAssociation + { + private static readonly string APK_EXTENSION = ".apk"; + private static readonly string APK_FILE = "apkfile"; + private static readonly string APK_DESCRIPTION = "Android Application"; + private static readonly RegistryKey Root = Registry.ClassesRoot; + private static readonly string ExePath = Path.GetFullPath(System.Reflection.Assembly.GetEntryAssembly().Location); + + public static bool SetAsDefault(bool value) + { + try + { + if (value) + { + Root.CreateSubKey(APK_EXTENSION).SetValue("", APK_FILE); + using (var key = Root.CreateSubKey(APK_FILE)) + { + key.SetValue("", APK_DESCRIPTION); + key.CreateSubKey("DefaultIcon").SetValue("", $"\"{ExePath}\""); + key.CreateSubKey("Shell\\Open\\Command").SetValue("", $"\"{ExePath}\" /i \"%1\""); + } + } + else + { + Root.DeleteSubKeyTree(APK_EXTENSION); + Root.DeleteSubKeyTree(APK_FILE); + } + return true; + } + catch (Exception e) + { + Debug.Print("Registry: {0}", e.Message); + return false; + } + } + + public static bool IsDefault() + { + try + { + using (var key = Root.OpenSubKey(APK_EXTENSION, false)) + { + if (key == null) + return false; + + if (key.GetValue("").ToString() != APK_FILE) + return false; + } + + using (var key = Root.OpenSubKey(APK_FILE, false)) + { + if (key == null) + return false; + + var regValue = key.OpenSubKey("Shell\\Open\\Command")?.GetValue(""); + + if (regValue == null) + return false; + + if (regValue.ToString() != $"\"{ExePath}\" /i \"%1\"") + return false; + } + + return true; + } + catch (Exception e) + { + Debug.Print("Registry: {0}", e.Message); + return false; + } + } + } +} diff --git a/ApkManager/Lib/Lib.cs b/ApkManager/Lib/Lib.cs new file mode 100644 index 0000000..bad2d0d --- /dev/null +++ b/ApkManager/Lib/Lib.cs @@ -0,0 +1,26 @@ +namespace ApkManager.Lib +{ + public class WindowPosition + { + public double Top { get; set; } + public double Left { get; set; } + } + + public class NameFormat + { + public string UsePattern { get; set; } + public bool UseLabel { get; set; } + public bool UsePackage { get; set; } + public bool UseVersion { get; set; } + public bool UseBuild { get; set; } + public bool UseSuffixEnclosure { get; set; } + public Separator Separator { get; set; } + } + + public enum Separator + { + Space, + Strip, + Underscore + } +} diff --git a/ApkManager/Lib/LibExtended.cs b/ApkManager/Lib/LibExtended.cs new file mode 100644 index 0000000..ef6f105 --- /dev/null +++ b/ApkManager/Lib/LibExtended.cs @@ -0,0 +1,53 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; + +namespace ApkManager.Lib +{ + public static class LibExtended + { + public static bool IsValidIPAddress(this string text) + { + return Regex.IsMatch(text, @"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d{4})?$"); + } + + public static bool IsMatch(this string text, string pattern) + { + var match = Regex.IsMatch(text, pattern, RegexOptions.IgnoreCase); + Debug.Print("Text: {0} -> Pattern: {1} -> {2}", text, pattern, match); + return match; + } + + public static bool IsMatchInTextAndNotInLabel(this Tuple tuple, string word) + { + var pattern = string.Format("[\\W_]{0}([\\W_]|$)", word); + var mtext = tuple.Item1.IsMatch(pattern); + var mlabel = tuple.Item2.IsMatch(pattern); + return mtext && !mlabel; + } + + public static string Append(this string text, string appendtext) + { + var separator = string.IsNullOrWhiteSpace(text) ? "" : " "; + return string.Concat(text, separator, appendtext).Trim(); + } + + public static string AppendExtApk(this string text) + { + if (!text.ToLower().EndsWith(".apk")) + return string.Concat(text, ".apk").Trim(); + else + return text.Trim(); + } + + public static string TrimInvalidFileNameChars(this string text) + { + foreach (var f in text) + foreach (var i in Path.GetInvalidFileNameChars()) + if (f == i) text = text.Replace(f.ToString(), ""); + + return text.Trim(); + } + } +} diff --git a/ApkManager/Lib/aapt2.exe b/ApkManager/Lib/aapt2.exe new file mode 100644 index 0000000..7ea8dfb Binary files /dev/null and b/ApkManager/Lib/aapt2.exe differ diff --git a/ApkManager/Lib/adb.exe b/ApkManager/Lib/adb.exe new file mode 100644 index 0000000..fcbcf5c Binary files /dev/null and b/ApkManager/Lib/adb.exe differ diff --git a/ApkManager/MainWindow.xaml b/ApkManager/MainWindow.xaml index 4245d47..26158dd 100644 --- a/ApkManager/MainWindow.xaml +++ b/ApkManager/MainWindow.xaml @@ -1,12 +1,126 @@ - + Title="APK Manager" Icon="Resources/Playstore.ico" ShowIconOnTitleBar="False" + Width="450" ResizeMode="NoResize" SizeToContent="Height" WindowStartupLocation="CenterScreen" + AllowDrop="True" Drop="OnFileDrop" Loaded="OnLoaded" Closing="OnClosing"> + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/ApkManager/MainWindow.xaml.cs b/ApkManager/MainWindow.xaml.cs index cf5d43c..4213f4e 100644 --- a/ApkManager/MainWindow.xaml.cs +++ b/ApkManager/MainWindow.xaml.cs @@ -1,28 +1,199 @@ -using System; -using System.Collections.Generic; +using ApkManager.Lib; +using MahApps.Metro.Controls; +using MahApps.Metro.Controls.Dialogs; +using Microsoft.Win32; +using System; +using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace ApkManager { /// /// Interaction logic for MainWindow.xaml /// - public partial class MainWindow : Window + public partial class MainWindow : MetroWindow { - public MainWindow() + private Config cfg; + private Apk loadedApk; + private string pathApk; + + #region WINDOW + public MainWindow(string pathApk = null) { InitializeComponent(); + + // make all flyout width same as root + foreach (Flyout flayout in Flyouts.Items) + flayout.Width = this.Width; + + cfg = new Config(); + this.pathApk = pathApk; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + // loading position + if (cfg.GetWindowPostition()) + { + var position = cfg.WindowPostition(); + if (position.Top!= 0 && position.Left != 0) + { + WindowStartupLocation = WindowStartupLocation.Manual; + this.Top = position.Top; + this.Left = position.Left; + } + } + + // loading switch settings + swHander.IsEnabled = App.IsAdministrator(); + swHander.IsChecked = FileAssociation.IsDefault(); + swInstance.IsChecked = cfg.SingleInstance(); + swWindow.IsChecked = cfg.GetWindowPostition(); + + if (!string.IsNullOrWhiteSpace(pathApk)) + txtPath.Text = pathApk; + } + + private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e) + { + if (cfg.GetWindowPostition()) + cfg.WindowPostition(new WindowPosition() { Top = this.Top, Left = this.Left }); + } + + private void OnFileDrop(object sender, DragEventArgs e) + { + if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return; + + var files = e.Data.GetData(DataFormats.FileDrop) as string[]; + var apk = files.Where(f => f.ToLower().EndsWith(".apk")).FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(apk)) + txtPath.Text = apk; + } + #endregion + + #region METHOD + private void ShowLoading(bool state = true) + { + Dispatcher.Invoke(() => + PanelLoading.Visibility = state ? Visibility.Visible : Visibility.Collapsed + ); + } + #endregion + + private async void TxtPath_TextChanged(object sender, TextChangedEventArgs e) + { + if(!(sender is TextBox tb)) return; + + txtLabel.Text = ". . . ."; + txtPackage.Text = ". . . ."; + txtVersion.Text = ". . . ."; + txtArch.Text = ". . . ."; + txtArch.ToolTip = null; + txtSdk.Text = ". . . ."; + imgIcon.Source = App.GetPlaystoreImageFromResources(); + gbAction.IsEnabled = false; + + if (string.IsNullOrWhiteSpace(tb.Text)) return; + if (loadedApk == null || loadedApk.FilePath != tb.Text) + { + ShowLoading(); + + var aapt = await Aapt.DumbBadging(tb.Text); + if (aapt.Success) + { + loadedApk = aapt.Apk; + txtLabel.Text = loadedApk.Label; + txtPackage.Text = loadedApk.PackageName; + txtVersion.Text = string.Format("{0} ( {1} )", loadedApk.VersionName, loadedApk.VersionCode); + + var foundOne = loadedApk.Platforms.Count <= 1; + txtArch.Text = foundOne ? loadedApk.AbiList : "see list"; + txtArch.ToolTip = foundOne ? null : loadedApk.AbiList; + txtArch.FontStyle = foundOne ? FontStyles.Normal : FontStyles.Italic; + txtArch.Foreground = foundOne ? txtSdk.Foreground : Brushes.Blue; + + txtSdk.Text = loadedApk.SdkVersion.ToString(); + imgIcon.Source = loadedApk.Icon; + gbAction.IsEnabled = true; + } + else + { + txtLabel.Text = "file corrupt?"; + txtPackage.Text = "not an apk file?"; + txtVersion.Text = "???"; + txtArch.Text = "???"; + txtSdk.Text = "???"; + } + + ShowLoading(false); + } + } + + private void FileOpen_Click(object sender, RoutedEventArgs e) + { + var open = new OpenFileDialog() + { + Title = "Select APK", + Filter = "Android Package|*.apk", + DefaultExt = "apk", + RestoreDirectory = true, + Multiselect = false + }; + + if (open.ShowDialog() == true) + txtPath.Text = open.FileName; + } + + private void MenuSettings_Click(object sender, RoutedEventArgs e) + { + menuSettings.IsOpen = !menuSettings.IsOpen; + } + + private void SwitchSettings_Click(object sender, RoutedEventArgs e) + { + if (!(sender is ToggleSwitch ts)) return; + var tag = ts.Tag as string; + var isChecked = ts.IsChecked == true; + + if (tag == "Handler") + ts.IsChecked = FileAssociation.SetAsDefault(isChecked); + if (tag == "Instance") + cfg.SingleInstance(isChecked); + if (tag == "Window") + cfg.SetWindowPostition(isChecked); + } + + private void ButtonReset_Click(object sender, RoutedEventArgs e) + { + txtPath.Text = string.Empty; + } + + private async void ButtonRenamer_Click(object sender, RoutedEventArgs e) + { + try + { + var window = new RenamerWindow(loadedApk); + if (window.ShowDialog().Value == false) return; + + var newdest = window.GetFileDestination(); + File.Move(loadedApk.FilePath, newdest); + txtPath.Text = newdest; + } + catch (Exception ex) + { + await this.ShowMessageAsync("Renamer", ex.Message); + } + } + + private void ButtonInstaller_Click(object sender, RoutedEventArgs e) + { + new AdbWindow(loadedApk).ShowDialog(); } } -} +} \ No newline at end of file diff --git a/ApkManager/Properties/AssemblyInfo.cs b/ApkManager/Properties/AssemblyInfo.cs index 82c462c..44174c4 100644 --- a/ApkManager/Properties/AssemblyInfo.cs +++ b/ApkManager/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("2020.4.1.1333")] +[assembly: AssemblyFileVersion("2020.4.1.1333")] diff --git a/ApkManager/RenamerWindow.xaml b/ApkManager/RenamerWindow.xaml new file mode 100644 index 0000000..c26074d --- /dev/null +++ b/ApkManager/RenamerWindow.xaml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApkManager/RenamerWindow.xaml.cs b/ApkManager/RenamerWindow.xaml.cs new file mode 100644 index 0000000..513d5c5 --- /dev/null +++ b/ApkManager/RenamerWindow.xaml.cs @@ -0,0 +1,252 @@ +using ApkManager.Lib; +using MahApps.Metro.Controls; +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Separator = ApkManager.Lib.Separator; + +namespace ApkManager +{ + /// + /// Interaction logic for RenamerWindow.xaml + /// + public partial class RenamerWindow : MetroWindow + { + private Apk apk; + private Config config; + private string destination; + + public RenamerWindow(Apk apk) + { + InitializeComponent(); + + this.apk = apk; + this.config = new Config(); + } + + public string GetFileDestination() + { + return destination; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + var name = Path.GetFileName(apk.FilePath); + tbSource.Text = name; + tbDestination.Text = name; + + // get config + var nf = config.GetNameFormat(); + + // set pattern + tbPattern.Text = nf.UsePattern; + + // set base + cbBaseLabel.IsChecked = nf.UseLabel; + cbBasePackage.IsChecked = nf.UsePackage; + cbBaseVersion.IsChecked = nf.UseVersion; + cbBaseBuild.IsChecked = nf.UseBuild; + // make sure name base not empty + if (!nf.UseLabel && !nf.UsePackage) + cbBaseLabel.IsChecked = true; + + // set suffix + var tuple = new Tuple(name, apk.Label); + cbSuffixPro.IsChecked = tuple.IsMatchInTextAndNotInLabel("pro"); + cbSuffixPremium.IsChecked = tuple.IsMatchInTextAndNotInLabel("premium"); + cbSuffixPaid.IsChecked = tuple.IsMatchInTextAndNotInLabel("paid"); + cbSuffixDonate.IsChecked = tuple.IsMatchInTextAndNotInLabel("donate"); + cbSuffixVip.IsChecked = tuple.IsMatchInTextAndNotInLabel("vip"); + cbSuffixFull.IsChecked = tuple.IsMatchInTextAndNotInLabel("full"); + cbSuffixPatched.IsChecked = tuple.IsMatchInTextAndNotInLabel("patched"); + cbSuffixUnlocked.IsChecked = tuple.IsMatchInTextAndNotInLabel("unlock(ed)?"); + cbSuffixMod.IsChecked = tuple.IsMatchInTextAndNotInLabel("mod(ded)?"); + cbSuffixAdFree.IsChecked = tuple.IsMatchInTextAndNotInLabel("ad[-| ]?free"); + cbSuffixLite.IsChecked = tuple.IsMatchInTextAndNotInLabel("lite"); + cbSuffixFinal.IsChecked = tuple.IsMatchInTextAndNotInLabel("final"); + cbSuffixBeta.IsChecked = tuple.IsMatchInTextAndNotInLabel("beta"); + + // set separator + cbSuffixEnclosure.IsChecked = nf.UseSuffixEnclosure; + switch (nf.Separator) + { + case Separator.Strip: + rbSeparatorStrip.IsChecked = true; + break; + case Separator.Underscore: + rbSeparatorUnderscore.IsChecked = true; + break; + default: + rbSeparatorSpace.IsChecked = true; + break; + } + + // preview changed + CheckChanged_Click(sender, e); + } + + private void CheckBox_Click(object sender, RoutedEventArgs e) + { + var cb = sender as CheckBox; + + // check if label & package is not used + if (cb.Content == cbBaseLabel.Content && cb.IsChecked == false && cbBasePackage.IsChecked == false) + cbBasePackage.IsChecked = true; + if (cb.Content == cbBasePackage.Content && cb.IsChecked == false && cbBaseLabel.IsChecked == false) + cbBaseLabel.IsChecked = true; + + CheckChanged_Click(sender, e); + } + + private void CheckChanged_Click(object sender, RoutedEventArgs e) + { + // base + var namebase = string.Empty; + if (cbBaseLabel.IsChecked == true) + namebase = namebase.Append(apk.Label); + if (cbBasePackage.IsChecked == true) + namebase = namebase.Append(apk.PackageName); + if (cbBaseVersion.IsChecked == true) + namebase = namebase.Append((apk.VersionName.ToLower().StartsWith("v") ? "" : "v") + apk.VersionName); + if (cbBaseBuild.IsChecked == true) + namebase = namebase.Append("b" + apk.VersionCode); + + // base final + if (rbSeparatorStrip.IsChecked == true) + namebase = namebase.Replace(" ", "-"); + if (rbSeparatorUnderscore.IsChecked == true) + namebase = namebase.Replace(" ", "_"); + + // suffix + var namesuffix = string.Empty; + if (cbSuffixPro.IsChecked == true) + namesuffix = namesuffix.Append("Pro"); + if (cbSuffixPremium.IsChecked == true) + namesuffix = namesuffix.Append("Premium"); + if (cbSuffixPaid.IsChecked == true) + namesuffix = namesuffix.Append("Paid"); + if (cbSuffixDonate.IsChecked == true) + namesuffix = namesuffix.Append("Donate"); + if (cbSuffixVip.IsChecked == true) + namesuffix = namesuffix.Append("VIP"); + if (cbSuffixFull.IsChecked == true) + namesuffix = namesuffix.Append("Full"); + if (cbSuffixPatched.IsChecked == true) + namesuffix = namesuffix.Append("Patched"); + if (cbSuffixUnlocked.IsChecked == true) + namesuffix = namesuffix.Append("Unlocked"); + if (cbSuffixMod.IsChecked == true) + namesuffix = namesuffix.Append("Mod"); + if (cbSuffixAdFree.IsChecked == true) + namesuffix = namesuffix.Append("AdFree"); + if (cbSuffixLite.IsChecked == true) + namesuffix = namesuffix.Append("Lite"); + if (cbSuffixFinal.IsChecked == true) + namesuffix = namesuffix.Append("Final"); + if (cbSuffixBeta.IsChecked == true) + namesuffix = namesuffix.Append("Beta"); + + // suffix final + if (cbSuffixEnclosure.IsChecked == true) + { + namesuffix = Regex.Replace(namesuffix, "(\\w+)", delegate (Match match) { + return string.Format("[{0}]", match.Groups[1].Value); + }).Replace(" ",""); + } + else + { + if (rbSeparatorStrip.IsChecked == true) + namesuffix = namesuffix.Replace(" ", "-"); + if (rbSeparatorUnderscore.IsChecked == true) + namesuffix = namesuffix.Replace(" ", "_"); + } + + // define final name + var name = string.Empty; + + // pattern override + if (!string.IsNullOrWhiteSpace(tbPattern.Text)) + { + name = tbPattern.Text + .Replace("%label%", apk.Label) + .Replace("%package%", apk.PackageName) + .Replace("%version%", apk.VersionName) + .Replace("%build%", apk.VersionCode.ToString()) + .Replace("%base%", namebase) + .Replace("%suffix%", namesuffix); + } + else + { + var separator = ""; + if (rbSeparatorSpace.IsChecked == true) + separator = " "; + if (rbSeparatorStrip.IsChecked == true && cbSuffixEnclosure.IsChecked == false) + separator = "-"; + if (rbSeparatorUnderscore.IsChecked == true && cbSuffixEnclosure.IsChecked == false) + separator = "_"; + + name = string.Format("{0}{1}{2}", namebase, separator, namesuffix).Trim(); + } + + // set new name + tbDestination.Text = name.AppendExtApk().TrimInvalidFileNameChars(); + } + + private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) + { + var invalidchars = Path.GetInvalidFileNameChars().Where(c => e.Text.Where(t => t == c).Count() > 0); + e.Handled = invalidchars.Count() > 0; + base.OnPreviewTextInput(e); + } + + private void TbPattern_TextChanged(object sender, TextChangedEventArgs e) + { + CheckChanged_Click(sender, e); + } + + private void TbDestination_TextChanged(object sender, TextChangedEventArgs e) + { + var tb = sender as TextBox; + btnRenamer.IsEnabled = false; + if (!string.IsNullOrWhiteSpace(tb.Text)) + { + try + { + var filename = tb.Text.AppendExtApk(); + var folder = Path.GetDirectoryName(apk.FilePath); + destination = Path.Combine(folder, filename); + + if (!File.Exists(destination)) + btnRenamer.IsEnabled = true; + } + catch (Exception) { } + } + } + + private void ButtonRename_Click(object sender, RoutedEventArgs e) + { + var nf = new NameFormat() + { + UsePattern = tbPattern.Text, + UseLabel = cbBaseLabel.IsChecked == true, + UsePackage = cbBasePackage.IsChecked == true, + UseVersion = cbBaseVersion.IsChecked == true, + UseBuild = cbBaseBuild.IsChecked == true, + UseSuffixEnclosure = cbSuffixEnclosure.IsChecked == true + }; + if (rbSeparatorSpace.IsChecked == true) + nf.Separator = Separator.Space; + if (rbSeparatorStrip.IsChecked == true) + nf.Separator = Separator.Strip; + if (rbSeparatorUnderscore.IsChecked == true) + nf.Separator = Separator.Underscore; + config.SetNameFormat(nf); + + this.DialogResult = true; + } + } +} diff --git a/ApkManager/Resources/Android.ico b/ApkManager/Resources/Android.ico new file mode 100644 index 0000000..a751858 Binary files /dev/null and b/ApkManager/Resources/Android.ico differ diff --git a/ApkManager/Resources/Playstore.ico b/ApkManager/Resources/Playstore.ico new file mode 100644 index 0000000..0ff00db Binary files /dev/null and b/ApkManager/Resources/Playstore.ico differ diff --git a/ApkManager/Resources/Playstore.png b/ApkManager/Resources/Playstore.png new file mode 100644 index 0000000..98fe3da Binary files /dev/null and b/ApkManager/Resources/Playstore.png differ diff --git a/ApkManager/packages.config b/ApkManager/packages.config new file mode 100644 index 0000000..082fd6a --- /dev/null +++ b/ApkManager/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file