patching, also rewrite incoming (probably)

This commit is contained in:
Dexrn ZacAttack
2025-01-13 02:08:49 -08:00
parent e47885b451
commit dc2dd78faa
14 changed files with 442 additions and 324 deletions

View File

@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Markup.Localizer;
using Windows.Storage;
using WinDurango.UI.Settings;
using WinDurango.UI.Utils;
@@ -14,7 +15,7 @@ namespace WinDurango.UI
public partial class App : Application
{
// constants
public static readonly string DataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WinDurango");
public static readonly string DataDir = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WinDurango"), "UI");
public static readonly string AppDir = AppContext.BaseDirectory;
private static readonly FileVersionInfo Fvi = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location);
// versioning
@@ -39,9 +40,10 @@ namespace WinDurango.UI
return (major, minor, patch);
}
private async Task InitializeLocalizer()
private static async Task InitializeLocalizer()
{
string StringsFolderPath = Path.Combine(AppContext.BaseDirectory, "Strings");
Logger.WriteDebug(AppContext.BaseDirectory);
StorageFolder stringsFolder = await StorageFolder.GetFolderFromPathAsync(StringsFolderPath);
ILocalizer localizer = await new LocalizerBuilder()
@@ -51,6 +53,8 @@ namespace WinDurango.UI
options.DefaultLanguage = "en-US";
})
.Build();
// await localizer.SetLanguage(Settings.Settings.Language);
Logger.WriteDebug($"Using language {localizer.GetCurrentLanguage()}");
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)

View File

@@ -1,4 +1,5 @@
using Microsoft.UI.Xaml;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
@@ -6,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
@@ -24,12 +26,16 @@ namespace WinDurango.UI.Controls
private string _Publisher;
private string _Version;
private Uri _Logo;
private ProgressDialog currentDialog = null;
// "Just Works"
// this needs to be fixed.
private bool shouldShowDone = true;
private async void HandleUnregister(object sender, SplitButtonClickEventArgs e)
{
if ((bool)unregisterCheckbox.IsChecked)
{
var confirmation = new Confirmation($"Are you sure you want to uninstall {_Name}?", "Uninstall?");
var confirmation = new Confirmation(Localization.Locale.GetLocalizedText("Packages.UninstallConfirmation", _Name), "Uninstall?");
Dialog.BtnClicked answer = await confirmation.Show();
if (answer == Dialog.BtnClicked.Yes)
{
@@ -51,6 +57,58 @@ namespace WinDurango.UI.Controls
_ = Process.Start(new ProcessStartInfo(_package.InstalledPath) { UseShellExecute = true });
}
private async Task StatusUpdateAsync(string status, int progress)
{
if (currentDialog == null)
{
currentDialog = new ProgressDialog("Working", "Patcher", false);
// shitty way of doing it
if (new ProgressDialog("Working", "Patcher", false) != null)
{
await App.MainWindow.DispatcherQueue.EnqueueAsync(async () =>
{
await currentDialog.ShowAsync();
});
} else
{
Logger.WriteDebug("???");
}
} else
{
currentDialog.Text = status;
currentDialog.Progress = progress;
if (progress == 100)
{
Logger.WriteDebug("100");
currentDialog.Hide();
currentDialog = null;
if (shouldShowDone)
await new NoticeDialog("Done!", "Patcher").Show();
}
}
}
private async void RepatchPackage(object sender, RoutedEventArgs args)
{
shouldShowDone = false;
await WinDurangoPatcher.UnpatchPackage(_package, StatusUpdateAsync);
shouldShowDone = true;
await WinDurangoPatcher.PatchPackage(_package, true, StatusUpdateAsync);
App.MainWindow.ReloadAppList();
}
private async void UnpatchPackage(object sender, RoutedEventArgs args)
{
await WinDurangoPatcher.UnpatchPackage(_package, StatusUpdateAsync);
App.MainWindow.ReloadAppList();
}
private async void PatchPackage(object sender, RoutedEventArgs args)
{
await WinDurangoPatcher.PatchPackage(_package, false, StatusUpdateAsync);
App.MainWindow.ReloadAppList();
}
public AppTile(string familyName)
{
_familyName = familyName;
@@ -68,6 +126,7 @@ namespace WinDurango.UI.Controls
_Publisher = _package.PublisherDisplayName ?? _package.Id.PublisherId;
_Version = $"{_package.Id.Version.Major.ToString() ?? "U"}.{_package.Id.Version.Minor.ToString() ?? "U"}.{_package.Id.Version.Build.ToString() ?? "U"}.{_package.Id.Version.Revision.ToString() ?? "U"}";
_Logo = _package.Logo;
string ss = Packages.GetSplashScreenPath(_package);
IReadOnlyList<AppListEntry> appListEntries = null;
try
@@ -112,7 +171,32 @@ namespace WinDurango.UI.Controls
}
infoExpander.Header = _Name;
Flyout rcFlyout = new();
MenuFlyout rcFlyout = new();
bool isPatched = InstalledPackages.GetInstalledPackage(_package.Id.FamilyName).Value.installedPackage.IsPatched;
MenuFlyoutItem patchButton = new MenuFlyoutItem
{
Text = isPatched ? "Repatch" : "Patch",
Name = "patchButton"
};
if (isPatched)
{
patchButton.Click += RepatchPackage;
MenuFlyoutItem unpatchButton = new MenuFlyoutItem
{
Text = "Unpatch",
Name = "unpatchButton"
};
unpatchButton.Click += UnpatchPackage;
rcFlyout.Items.Add(unpatchButton);
} else
{
patchButton.Click += PatchPackage;
}
rcFlyout.Items.Add(patchButton);
expanderVersion.Text = $"Publisher: {_Publisher}\nVersion {_Version}";

View File

@@ -10,7 +10,10 @@ namespace WinDurango.UI.Localization
{
public static string GetLocalizedText(string name, params object[] args)
{
return string.Format(name.GetLocalizedString(), args);
String translated = Localizer.Get().GetLocalizedString(name);
if (translated == "")
Logger.Write(LogLevel.Warning, $"String {name} not found in string resources.");
return string.Format(translated == "" ? $"LOCALIZATION ERROR: String {name} not found in string resources." : translated, args);
}
}
}

View File

@@ -132,13 +132,13 @@ namespace WinDurango.UI.Pages
{
// there is no AppxManifest inside.
Logger.WriteError($"Could not find AppxManifest.xml in {folder.Path} and {mountFolder}");
await new NoticeDialog(GetLocalizedText("ManifestNotFoundMulti", folder.Path, mountFolder), "Error").Show();
await new NoticeDialog(GetLocalizedText("Packages.Error.ManifestNotFoundMulti", folder.Path, mountFolder), "Error").Show();
}
}
else
{
Logger.WriteError($"Could not find AppxManifest.xml in {folder.Path} and no Mount folder exists");
await new NoticeDialog(GetLocalizedText("ManifestNotFoundNoMount", folder.Path), "Error").Show();
await new NoticeDialog(GetLocalizedText("Packages.Error.ManifestNotFoundNoMount", folder.Path), "Error").Show();
}
return;

View File

@@ -3,14 +3,16 @@
[![View stargazers](https://img.shields.io/github/stars/WinDurango-project/WinDurango.UI)](https://github.com/WinDurango-project/WinDurango.UI/stargazers)
GUI for WinDurango, which is planned to allow for easy installing/patching among other random stuff I decide lmfao
### NOTICE: A large amount of the codebase has to be rewritten as I was not a good C# dev when this was originally made (statics everywhere, messy fields, etc)
#### Additonally, the translation shit does NOT work at this moment, I cannot figure out why.
> [!NOTE]
> This does nothing more than provide a GUI for easily registering and patching packages with WinDurango.
> Additionally, I don't know much about C#... so the code is probably very messy.
# Roadmap
## Features
- [ ] Compiling the DLLs and patching automatically
- [X] Patching
- [X] Allow for package removal from UI instead of just completely uninstalling.
- [X] Installation options
- [X] Scan for already installed EraOS/XUWP stuff

View File

@@ -12,14 +12,17 @@ namespace WinDurango.UI.Settings
{
public string FullName { get; set; }
public List<string> SymlinkedDLLs { get; set; }
public List<string> OriginalDLLs { get; set; }
public bool IsPatched { get; set; }
}
// TODO: make this not static
public abstract class InstalledPackages
{
public static void RemoveInstalledPackage(Package pkg)
{
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WinDurango");
string path = App.DataDir;
if (!Directory.Exists(path))
{
_ = Directory.CreateDirectory(path);
@@ -127,7 +130,7 @@ namespace WinDurango.UI.Settings
public static void AddInstalledPackage(Package package)
{
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WinDurango");
string path = App.DataDir;
if (!Directory.Exists(path))
{
_ = Directory.CreateDirectory(path);
@@ -154,7 +157,9 @@ namespace WinDurango.UI.Settings
installedPkgs[package.Id.FamilyName] = new InstalledPackage
{
FullName = package.Id.FullName,
SymlinkedDLLs = []
SymlinkedDLLs = [],
OriginalDLLs = [],
IsPatched = false
};
string updated = JsonSerializer.Serialize(installedPkgs, new JsonSerializerOptions { WriteIndented = true });

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text.Json;
@@ -22,6 +23,10 @@ public class WdSettingsData
public uint SaveVersion { get; set; } = App.VerPacked;
public ThemeSetting Theme { get; set; } = ThemeSetting.Fluent;
public bool DebugLoggingEnabled { get; set; } = false;
public string Language { get; set; } = "en-US";
public string DownloadedWDVer { get; set; }
}
public class WdSettings

View File

@@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="EDbgLog" xml:space="preserve">
<value>Enable debug logging</value>
</data>
<data name="DDbgLog" xml:space="preserve">
<value>Disable debug logging</value>
</data>
</root>

View File

@@ -1,143 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ManifestNotFoundNoMount" xml:space="preserve">
<value>AppxManifest does not exist in {0} and there is no "Mount" folder.</value>
<comment>for when AppxManifest.xml is not found and there is no Mount folder to look in</comment>
</data>
<data name="MountNotFound" xml:space="preserve">
<value>{0} was not found.</value>
</data>
<data name="ManifestNotFoundMulti" xml:space="preserve">
<value>AppxManifest does not exist in {0} and {1}</value>
<comment>for when it's not found in either the Mount folder or current chosen folder</comment>
</data>
<data name="NotFound" xml:space="preserve">
<value>{0} was not found.</value>
</data>
<data name="AlreadyInstalled" xml:space="preserve">
<value>{0} is already installed.</value>
</data>
<data name="PackageInstallFailedEx" xml:space="preserve">
<value>{0} failed to install: {1}</value>
</data>
<data name="PackageUninstallFailedEx" xml:space="preserve">
<value>Failed to uninstall {0}: {1}</value>
</data>
</root>

View File

@@ -117,28 +117,60 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ReadingManifest" xml:space="preserve">
<data name="Packages.ReadingManifest" xml:space="preserve">
<value>Reading manifest...</value>
</data>
<data name="CheckingInstallStatus" xml:space="preserve">
<data name="Packages.CheckingInstallStatus" xml:space="preserve">
<value>Checking if {0} is already installed...</value>
</data>
<data name="InstallingPackage" xml:space="preserve">
<data name="Packages.InstallingPackage" xml:space="preserve">
<value>Installing {0}...</value>
</data>
<data name="GettingAppInfo" xml:space="preserve">
<data name="Packages.GettingAppInfo" xml:space="preserve">
<value>Getting app info...</value>
</data>
<data name="UpdatingAppList" xml:space="preserve">
<data name="Ui.UpdatingAppList" xml:space="preserve">
<value>Updating app list...</value>
</data>
<data name="PackageInstalled" xml:space="preserve">
<data name="Packages.PackageInstalled" xml:space="preserve">
<value>{0} was installed! :)</value>
</data>
<data name="UninstallingPackage" xml:space="preserve">
<data name="Packages.UninstallingPackage" xml:space="preserve">
<value>Uninstalling {0}...</value>
</data>
<data name="PackageUninstalled" xml:space="preserve">
<data name="Packages.PackageUninstalled" xml:space="preserve">
<value>{0} was uninstalled.</value>
</data>
<data name="Packages.Error.AlreadyInstalled" xml:space="preserve">
<value>{0} is already installed.</value>
</data>
<data name="Packages.Error.PackageUninstallFailedEx" xml:space="preserve">
<value>Failed to uninstall {0}: {1}</value>
</data>
<data name="Error.NotFound" xml:space="preserve">
<value>{0} was not found.</value>
</data>
<data name="Packages.Error.PackageInstallFailedEx" xml:space="preserve">
<value>{0} failed to install: {1}</value>
</data>
<data name="Packages.Error.ManifestNotFoundNoMount" xml:space="preserve">
<value>AppxManifest does not exist in {0} and there is no "Mount" folder.</value>
<comment>for when AppxManifest.xml is not found and there is no Mount folder to look in</comment>
</data>
<data name="Packages.Error.ManifestNotFoundMulti" xml:space="preserve">
<value>AppxManifest does not exist in {0} and {1}</value>
<comment>for when it's not found in either the Mount folder or current chosen folder</comment>
</data>
<data name="Error.MountNotFound" xml:space="preserve">
<value>{0} was not found.</value>
</data>
<data name="Ui.Controls.EDbgLog" xml:space="preserve">
<value>Enable debug logging</value>
</data>
<data name="Ui.Controls.DDbgLog" xml:space="preserve">
<value>Disable debug logging</value>
</data>
<data name="Packages.UninstallConfirmation" xml:space="preserve">
<value>Are you sure you want to uninstall {0}?</value>
</data>
</root>

View File

@@ -12,7 +12,7 @@ namespace WinDurango.UI.Utils
public class Logger
{
public static readonly Logger Instance = new();
private static readonly string logDir = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WinDurango"), "logs");
private static readonly string logDir = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "WinDurango\\UI"), "logs");
private static readonly string logFile = Path.Combine(logDir, $"WinDurangoUI_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
private static readonly object @lock = new();
@@ -22,6 +22,7 @@ namespace WinDurango.UI.Utils
Directory.CreateDirectory(logDir);
}
public static void WriteLine(string str) => Instance.WriteLog(LogLevel.Info, str);
public static void WriteDebug(string str) => Instance.WriteLog(LogLevel.Debug, str);
public static void WriteError(string str) => Instance.WriteLog(LogLevel.Error, str);
public static void WriteWarning(string str) => Instance.WriteLog(LogLevel.Warning, str);
@@ -52,7 +53,6 @@ namespace WinDurango.UI.Utils
string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{level.ToString().ToUpper()}] {message}";
Debug.WriteLine(logEntry);
lock (@lock)
{
using StreamWriter writer = new(logFile, true);

View File

@@ -66,7 +66,7 @@ namespace WinDurango.UI.Utils
if (!Directory.Exists(mountDir))
{
await new NoticeDialog(GetLocalizedText($"mountNotFound", mountDir), "Error").Show();
await new NoticeDialog(GetLocalizedText($"Error.mountNotFound", mountDir), "Error").Show();
return;
}
@@ -77,20 +77,21 @@ namespace WinDurango.UI.Utils
var (familyName, installedPackage) = InstalledPackages.GetInstalledPackage(package).Value;
if (hasExvd)
{
foreach (string filePath in Directory.GetFiles(Path.Combine(exvdDir + "\\Windows\\System32")))
{
string fileName = Path.GetFileName(filePath);
// DO NOT COPY THE DLLS FROM THIS
//if (hasExvd)
//{
// foreach (string filePath in Directory.GetFiles(Path.Combine(exvdDir + "\\Windows\\System32")))
// {
// string fileName = Path.GetFileName(filePath);
if (File.Exists(Path.Combine(mountDir, fileName)))
continue;
// if (File.Exists(Path.Combine(mountDir, fileName)))
// continue;
FileSystemInfo symlink = File.CreateSymbolicLink(Path.Combine(mountDir, fileName), filePath);
installedPackage.SymlinkedDLLs.Add(symlink.Name);
InstalledPackages.UpdateInstalledPackage(familyName, installedPackage);
}
}
// FileSystemInfo symlink = File.CreateSymbolicLink(Path.Combine(mountDir, fileName), filePath);
// installedPackage.SymlinkedDLLs.Add(symlink.Name);
// InstalledPackages.UpdateInstalledPackage(familyName, installedPackage);
// }
//}
}
public static string GetSplashScreenPath(Package pkg)
@@ -145,19 +146,19 @@ namespace WinDurango.UI.Utils
// TODO: strip UI
if (!File.Exists(manifestPath))
{
await new NoticeDialog(GetLocalizedText("NotFound", manifestPath), "Error").Show();
await new NoticeDialog(GetLocalizedText("Error.NotFound", manifestPath), "Error").Show();
return null;
}
Logger.WriteInformation($"Installing package \"{manifestPath}\"...");
var status = new ProgressDialog($"Installing package...", "Installing", false);
var status = new ProgressDialog(GetLocalizedText("Packages.InstallingPackage", "Package..."), "Installing", false);
_ = App.MainWindow.DispatcherQueue.TryEnqueue(async () => await status.ShowAsync());
PackageManager pm = new();
try
{
Logger.WriteInformation($"Reading manifest...");
status.Text = "ReadingManifest".GetLocalizedString();
status.Text = "Packages.ReadingManifest".GetLocalizedString();
string manifest;
await using (var stream = File.OpenRead(manifestPath))
{
@@ -181,24 +182,24 @@ namespace WinDurango.UI.Utils
{
status.Hide();
Logger.WriteError($"{pkgName} is already installed.");
await new NoticeDialog(GetLocalizedText("AlreadyInstalled", pkgName), "Error").Show();
await new NoticeDialog(GetLocalizedText("Error.AlreadyInstalled", pkgName), "Error").Show();
return null;
}
status.Progress = 40.0;
status.Text = GetLocalizedText("InstallingPackage", pkgName);
status.Text = GetLocalizedText("Packages.InstallingPackage", pkgName);
Logger.WriteInformation($"Registering...");
var deployment = await pm.RegisterPackageAsync(appxManifestUri, null, DeploymentOptions.DevelopmentMode);
status.Progress = 60.0;
status.Text = "GettingAppInfo".GetLocalizedString();
status.Text = "Packages.GettingAppInfo".GetLocalizedString();
var recentPkg = GetMostRecentlyInstalledPackage();
if (addInstalledPackage)
{
status.Text = $"UpdatingAppList".GetLocalizedString();
status.Text = $"Ui.UpdatingAppList".GetLocalizedString();
status.Progress = 80.0;
InstalledPackages.AddInstalledPackage(recentPkg);
status.Progress = 90.0;
@@ -212,7 +213,7 @@ namespace WinDurango.UI.Utils
status.Hide();
Logger.WriteInformation($"{recentPkg.Id.Name} was installed.");
await new NoticeDialog(GetLocalizedText("PackageInstalled", recentPkg.Id.Name)).Show();
await new NoticeDialog(GetLocalizedText("Packages.PackageInstalled", recentPkg.Id.Name)).Show();
return recentPkg.Id.FamilyName;
}
catch (Exception e)
@@ -221,7 +222,7 @@ namespace WinDurango.UI.Utils
status.Hide();
Logger.WriteError($"{appxManifestUri} failed to install");
Logger.WriteException(e);
await new NoticeDialog(GetLocalizedText("PackageInstallFailedEx", appxManifestUri, e.Message), "Error").Show();
await new NoticeDialog(GetLocalizedText("Packages.Error.PackageInstallFailedEx", appxManifestUri, e.Message), "Error").Show();
return null;
}
}
@@ -229,7 +230,7 @@ namespace WinDurango.UI.Utils
public static async Task RemovePackage(Package package)
{
Logger.WriteError($"Uninstalling {package.DisplayName}...");
var status = new ProgressDialog(GetLocalizedText("UninstallingPackage", package.DisplayName), "Uninstalling", false);
var status = new ProgressDialog(GetLocalizedText("Packages.UninstallingPackage", package.DisplayName), "Uninstalling", false);
_ = App.MainWindow.DispatcherQueue.TryEnqueue(async () => await status.ShowAsync());
PackageManager pm = new();
try
@@ -241,7 +242,7 @@ namespace WinDurango.UI.Utils
status.Progress = 100.0;
status.Hide();
Logger.WriteInformation($"{package.DisplayName} was uninstalled.");
await new NoticeDialog(GetLocalizedText("PackageUninstalled", package.DisplayName)).Show();
await new NoticeDialog(GetLocalizedText("Packages.PackageUninstalled", package.DisplayName)).Show();
App.MainWindow.ReloadAppList();
}
catch (Exception ex)
@@ -249,7 +250,7 @@ namespace WinDurango.UI.Utils
status.Hide();
Logger.WriteError($"{package.DisplayName} failed to uninstall");
Logger.WriteException(ex);
await new NoticeDialog(GetLocalizedText("PackageUninstallFailedEx", package.DisplayName, ex.Message), "Error!").Show();
await new NoticeDialog(GetLocalizedText("Packages.Error.PackageUninstallFailedEx", package.DisplayName, ex.Message), "Error!").Show();
}
return;
}

233
Utils/WinDurangoPatcher.cs Normal file
View File

@@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.IO.Packaging;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Policy;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using WinDurango.UI.Settings;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace WinDurango.UI.Utils
{
public class GitHubRelease
{
public string Name { get; set; }
public string DownloadLink { get; set; }
}
public class WinDurangoPatcher
{
private static GitHubRelease wdRelease;
private static string patchDir = Path.Combine(App.DataDir, "WinDurangoCore");
public static async Task<bool> PatchPackage(Windows.ApplicationModel.Package package, bool forceRedownload, Func<string, int, Task> statusCallback)
{
Logger.WriteInformation($"Patching package {package.Id.FamilyName}");
statusCallback($"Patching {package.Id.FamilyName}", 0);
string curDate = DateTime.UtcNow.ToString("yyyy-MM-dd_HH-mm-ss");
var instPkg = InstalledPackages.GetInstalledPackage(package.Id.FamilyName);
string installPath = package.InstalledPath;
statusCallback($"Getting release", 10);
await GetOrReuseRelease(); // don't use the return value since wdRelease is set regardless
if (!Path.Exists(patchDir) || forceRedownload)
{
if (forceRedownload && Path.Exists(patchDir))
Directory.Delete(patchDir, true);
string zip = Path.Combine(App.DataDir, "WinDurangoCore.zip");
try
{
using (HttpClient client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = true }))
{
statusCallback($"Downloading WinDurango release \"{wdRelease.Name}\"", 20);
Logger.WriteInformation($"Downloading WinDurango release \"{wdRelease.Name}\"");
using (HttpResponseMessage res = await client.GetAsync(wdRelease.DownloadLink, HttpCompletionOption.ResponseHeadersRead))
{
res.EnsureSuccessStatusCode();
using (FileStream stream = new FileStream(zip, FileMode.Create, FileAccess.Write, FileShare.None))
{
statusCallback($"Writing release zip", 30);
await res.Content.CopyToAsync(stream);
}
}
}
}
catch (Exception ex)
{
Logger.WriteException(ex);
return false;
}
try
{
Directory.CreateDirectory(patchDir);
Logger.WriteInformation($"Unzipping");
statusCallback($"Unzipping", 40);
ZipFile.ExtractToDirectory(zip, patchDir);
}
catch (Exception ex)
{
Logger.WriteException(ex);
return false;
}
}
statusCallback($"Patching", 50);
if (Path.Exists(patchDir))
{
DirectoryInfo unzipped = new DirectoryInfo(patchDir);
var unzippedFiles = unzipped.GetFiles("*.dll");
float prog = (90 - 50) / (float)unzippedFiles.Length;
DirectoryInfo pkg = new DirectoryInfo(installPath);
foreach (var file in unzippedFiles)
{
int progress = (int)Math.Round(50 + prog * Array.IndexOf(unzippedFiles, file));
statusCallback($"Copying {file.Name}", progress);
var pkgFName = pkg.GetFiles("*.dll").FirstOrDefault(pkgFName => pkgFName.Name == file.Name);
if (pkgFName != null)
{
Logger.WriteInformation($"Backing up old {pkgFName.Name}");
string DllBackup = Path.Combine(installPath, "WDDllBackup", curDate);
if (!Path.Exists(DllBackup))
Directory.CreateDirectory(DllBackup);
File.Move(pkgFName.FullName, Path.Combine(DllBackup, pkgFName.Name));
if (!instPkg.Value.installedPackage.OriginalDLLs.Contains(Path.Combine(DllBackup, pkgFName.Name)))
instPkg.Value.installedPackage.OriginalDLLs.Add(Path.Combine(DllBackup, pkgFName.Name));
}
string symPath = Path.Combine(installPath, file.Name);
Logger.WriteInformation($"Added {file.Name}");
File.Copy(file.FullName, Path.Combine(installPath, file.Name));
if (!instPkg.Value.installedPackage.SymlinkedDLLs.Contains(symPath))
instPkg.Value.installedPackage.SymlinkedDLLs.Add(symPath);
}
} else
{
Logger.WriteError("How did this happen???? patchDir should exist.");
return false;
}
statusCallback($"Updating package list", 95);
instPkg.Value.installedPackage.IsPatched = true;
InstalledPackages.UpdateInstalledPackage(instPkg.Value.familyName, instPkg.Value.installedPackage);
statusCallback($"Done!", 100);
return true;
}
public static async Task<bool> UnpatchPackage(Windows.ApplicationModel.Package package, Func<string, int, Task> statusCallback)
{
Logger.WriteInformation($"Unpatching package {package.Id.FamilyName}");
statusCallback($"Unpatching {package.Id.FamilyName}", 0);
var instPkg = InstalledPackages.GetInstalledPackage(package.Id.FamilyName);
var dlls = instPkg.Value.installedPackage.SymlinkedDLLs.ToArray();
var origDlls = instPkg.Value.installedPackage.OriginalDLLs.ToArray();
string installPath = package.InstalledPath;
float delProg = (0 - 50) / (float)instPkg.Value.installedPackage.SymlinkedDLLs.Count;
foreach (var dll in dlls)
{
int progress = (int)Math.Round(50 + delProg * Array.IndexOf(instPkg.Value.installedPackage.SymlinkedDLLs.ToArray(), dll));
statusCallback($"Removing {dll}", progress);
try
{
File.Delete(dll);
} catch
{
Logger.WriteError($"Failed to delete {dll}.");
}
if (instPkg.Value.installedPackage.SymlinkedDLLs.Contains(dll))
instPkg.Value.installedPackage.SymlinkedDLLs.Remove(dll);
};
float repProg = (99 - 50) / (float)instPkg.Value.installedPackage.OriginalDLLs.Count;
foreach (var dll in origDlls)
{
int progress = (int)Math.Round(50 + delProg * Array.IndexOf(origDlls, dll));
statusCallback($"Placing back original DLL \"{dll}\"", progress);
try
{
File.Copy(dll, Path.Combine(installPath, dll));
}
catch
{
Logger.WriteError($"Failed to copy {dll}.");
}
if (instPkg.Value.installedPackage.OriginalDLLs.Contains(dll))
instPkg.Value.installedPackage.OriginalDLLs.Remove(dll);
};
instPkg.Value.installedPackage.IsPatched = false;
statusCallback($"Updating package list", 99);
InstalledPackages.UpdateInstalledPackage(instPkg.Value.familyName, instPkg.Value.installedPackage);
statusCallback($"Done!", 100);
return true;
}
// don't wanna use all the api shits lol
public static async Task<GitHubRelease> GetOrReuseRelease()
{
if (wdRelease.GetType() != typeof(GitHubRelease))
await GetLatestRelease();
return wdRelease;
}
public static async Task<GitHubRelease> GetLatestRelease()
{
GitHubRelease release = new GitHubRelease();
string url = $"https://api.github.com/repos/WinDurango/WinDurango/releases";
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (compatible; GitHubAPI/1.0)");
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
JsonDocument document = JsonDocument.Parse(json);
var releases = document.RootElement.EnumerateArray();
if (!releases.MoveNext())
throw new Exception("Couldn't find any releases?????");
JsonElement newestRelease = releases.Current;
string name = newestRelease.GetProperty("name").GetString();
release.Name = name;
var assets = newestRelease.GetProperty("assets").EnumerateArray();
if (!assets.MoveNext())
throw new Exception("Couldn't find any assets?????");
string download = assets.Current.GetProperty("browser_download_url").GetString();
release.DownloadLink = download;
wdRelease = release;
return release;
}
}
}

View File

@@ -32,7 +32,6 @@
<ItemGroup>
<None Remove="Controls\AppTile.xaml" />
<None Remove="Controls\ContributorInfo.xaml" />
<None Remove="Controls\NewFile1.txt" />
<None Remove="Dialogs\AppListDialog.xaml" />
<None Remove="Dialogs\InstallConfirmationDialog.xaml" />
<None Remove="Pages\AboutPage.xaml" />
@@ -42,6 +41,31 @@
<PropertyGroup>
<WindowsSdkPackageVersion>10.0.26100.38</WindowsSdkPackageVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
@@ -100,12 +124,6 @@
</Content>
</ItemGroup>
<ItemGroup>
<Content Remove="Strings\en-US\Controls.resw" />
<Content Remove="Strings\en-US\Errors.resw" />
<Content Remove="Strings\en-US\Resources.resw" />
</ItemGroup>
<ItemGroup>
<None Include="D:\Projects\Dev\C#\WinDurango.UI\.editorconfig" />
</ItemGroup>