Skip to content

Commit

Permalink
Fuck Microsoft Store DRM (Copy Protection) (#332)
Browse files Browse the repository at this point in the history
* Added: Unprotect MS Store Binaries

* Added: Auto ASI Loader Deploy to Games (incl. GamePass)

* Improved: Can now survive GamePass updates with last resort AppId match

* Edit App Page: Display 'Don't Inject' Setting

* Added: Respect the DontInject Flag

* Fixed: Some comment work on TryUnprotectGamePassGame

* Added: Remaining Sanity Bug Fixes for GamePass

* Added: GamePass Related Note

* Changed: Only show ASI Loader Install fail on GamePass Auto Deploy
  • Loading branch information
Sewer56 authored Mar 9, 2024
1 parent b5c3c1e commit df71ffe
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ static string GetProductName(string exePath)
return Path.GetFileName(exePath);
}
}

try { exePath = SymlinkResolver.GetFinalPathName(exePath); }
catch (Exception e) { Errors.HandleException(e, Resources.ErrorAddApplicationCantReadSymlink.Get()); }


var isMsStore = TryUnprotectGamePassGame.TryIt(exePath);
var config = new ApplicationConfig(Path.GetFileName(exePath).ToLower(), GetProductName(exePath), exePath, Path.GetDirectoryName(exePath));

// Set AppName if empty & Ensure no duplicate ID.
Expand All @@ -87,7 +88,23 @@ static string GetProductName(string exePath)
Console.WriteLine(e);
}

// Try to auto deploy ASI Loader.
var deployer = new AsiLoaderDeployer(new PathTuple<ApplicationConfig>(applicationConfigFile, config));
if (deployer.CanDeploy())
{
deployer.DeployAsiLoader(out var loaderPath, out var bootstrapperPath);
DeployAsiLoaderCommand.PrintDeployedAsiLoaderInfo(loaderPath!, bootstrapperPath);
config.DontInject = true;
}
else
{
// For GamePass, we can't dll inject, so we need to throw error to user screen.
if (isMsStore)
Actions.DisplayMessagebox.Invoke(Resources.AsiLoaderDialogTitle.Get(), Resources.AsiLoaderGamePassAutoInstallFail.Get());
}

// Write file to disk.
config.IsMsStore = isMsStore;
Directory.CreateDirectory(applicationDirectory);
IConfig<ApplicationConfig>.ToPath(config, applicationConfigFile);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,20 @@ public void Execute(object? parameter)
return;

_deployer.DeployAsiLoader(out string? loaderPath, out string bootstrapperPath);
string deployedBootstrapper = $"{Resources.AsiLoaderDialogBootstrapperDeployed.Get()} {bootstrapperPath}";
PrintDeployedAsiLoaderInfo(bootstrapperPath, loaderPath);
}

internal static void PrintDeployedAsiLoaderInfo(string bootstrapperPath, string? loaderPath)
{
var deployedBootstrapper = $"{Resources.AsiLoaderDialogBootstrapperDeployed.Get()} {bootstrapperPath}";
if (loaderPath == null)
{
// Installed Bootstrapper but not loader.
Actions.DisplayMessagebox.Invoke(Resources.AsiLoaderDialogTitle.Get(), deployedBootstrapper);
}
else
{
string deployedLoader = $"{Resources.AsiLoaderDialogLoaderDeployed.Get()} {loaderPath}";
var deployedLoader = $"{Resources.AsiLoaderDialogLoaderDeployed.Get()} {loaderPath}";
Actions.DisplayMessagebox.Invoke(Resources.AsiLoaderDialogTitle.Get(), $"{deployedLoader}\n{deployedBootstrapper}");
}
}
Expand Down
11 changes: 10 additions & 1 deletion source/Reloaded.Mod.Launcher.Lib/Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,16 @@ private static Task DoSanityTests()
// Needs to be ran after SetupViewModelsAsync
var apps = IoC.GetConstant<ApplicationConfigService>().Items;
var mods = IoC.GetConstant<ModConfigService>().Items.ToArray();


// Unprotect all MS Store titles if needed.
foreach (var app in apps)
{
if (app.Config.IsMsStore)
{
TryUnprotectGamePassGame.TryIgnoringErrors(app.Config.AppLocation);
}
}

// Enforce compatibility non-async, since this is unlikely to do anything.
foreach (var app in apps)
EnforceModCompatibility(app, mods);
Expand Down
21 changes: 14 additions & 7 deletions source/Reloaded.Mod.Launcher.Lib/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ public static bool HandleCommandLineArgs()
PopulateCommandLineArgs();

// Check if Kill Process
bool forceInject = false;
if (_commandLineArguments.TryGetValue(Constants.ParameterKill, out string? processId))
{
KillProcessWithId(processId);
// for outdated bootstrappers, assume injection is required when kill specified.
// otherwise follow regular setting
forceInject = true;
}

// Check if Launch
if (_commandLineArguments.TryGetValue(Constants.ParameterLaunch, out string? applicationToLaunch))
{
LaunchApplicationAndExit(applicationToLaunch);
LaunchApplicationAndExit(applicationToLaunch, forceInject);
result = true;
}

Expand Down Expand Up @@ -69,7 +75,7 @@ private static void KillProcessWithId(string processId)
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void LaunchApplicationAndExit(string applicationToLaunch)
private static void LaunchApplicationAndExit(string applicationToLaunch, bool forceInject)
{
// Acquire arguments
var loaderConfig = IoC.Get<LoaderConfig>();
Expand All @@ -85,17 +91,18 @@ private static void LaunchApplicationAndExit(string applicationToLaunch)
arguments = $"{arguments} {application.Config.AppArguments}";

_commandLineArguments.TryGetValue(Constants.ParameterWorkingDirectory, out var workingDirectory);

var inject = !application!.Config.DontInject | forceInject;

// Show warning for Wine users.
if (Shared.Environment.IsWine)
{
// Set up UI Resources, since they're needed for the dialog.
if (CompatibilityDialogs.WineShowLaunchDialog())
StartGame(applicationToLaunch, arguments, workingDirectory);
StartGame(applicationToLaunch, arguments, workingDirectory, inject);
}
else
{
StartGame(applicationToLaunch, arguments, workingDirectory);
StartGame(applicationToLaunch, arguments, workingDirectory, inject);
}
}

Expand Down Expand Up @@ -142,11 +149,11 @@ private static void OpenPackAndExit(string r2PackLocation)

private static void InitControllerSupport() => Actions.InitControllerSupport();

private static void StartGame(string applicationToLaunch, string arguments, string? workingDirectory = null)
private static void StartGame(string applicationToLaunch, string arguments, string? workingDirectory, bool inject)
{
// Launch the application.
var launcher = ApplicationLauncher.FromLocationAndArguments(applicationToLaunch, arguments, workingDirectory);
launcher.Start();
launcher.Start(inject);
}

private static void PopulateCommandLineArgs()
Expand Down
3 changes: 3 additions & 0 deletions source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,7 @@ public static void Init(IDictionaryResourceProvider provider)
public static IDictionaryResource<string> SearchOptionSortViews { get; set; }
public static IDictionaryResource<string> SearchOptionAscending { get; set; }
public static IDictionaryResource<string> SearchOptionDescending { get; set; }

// Update 1.26.0: GamePass ASI Loader Auto Deploy
public static IDictionaryResource<string> AsiLoaderGamePassAutoInstallFail { get; set; }
}
19 changes: 11 additions & 8 deletions source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static ApplicationLauncher FromApplicationConfig(PathTuple<ApplicationCon
/// <summary>
/// Starts the application, injecting Reloaded into it.
/// </summary>
public void Start()
public void Start(bool inject = true)
{
// Start up the process
Native.STARTUPINFO startupInfo = new Native.STARTUPINFO();
Expand All @@ -66,14 +66,17 @@ public void Start()
var process = Process.GetProcessById((int) processInformation.dwProcessId);
using var injector = new ApplicationInjector(process);

try
if (inject)
{
injector.Inject();
}
catch (Exception)
{
Native.ResumeThread(processInformation.hThread);
throw;
try
{
injector.Inject();
}
catch (Exception)
{
Native.ResumeThread(processInformation.hThread);
throw;
}
}

Native.ResumeThread(processInformation.hThread);
Expand Down
49 changes: 2 additions & 47 deletions source/Reloaded.Mod.Launcher.Lib/Utility/SymlinkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public static class SymlinkResolver
private const short MaxPath = short.MaxValue; // Windows 10 with path extension.
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);


private const uint FILE_READ_EA = 0x0008;
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;

Expand All @@ -42,25 +41,12 @@ static extern IntPtr CreateFile(
/// Resolves a symbolic link and normalizes the path.
/// </summary>
/// <param name="path">The path to be resolved.</param>
/// <param name="allowUwp">Resolves UWP application paths.</param>
public static string GetFinalPathName(string path, bool allowUwp = true)
public static string GetFinalPathName(string path)
{
// Special Case for UWP/MSStore.
if (allowUwp)
{
try
{
var folder = Path.GetDirectoryName(path)!;
var manifest = Path.Combine(folder, "appxmanifest.xml");
if (File.Exists(manifest))
return TryGetFilePathFromUWPAppManifest(path, manifest);
}
catch (Exception) { }
}

var h = CreateFile(path, FILE_READ_EA, FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
if (h == INVALID_HANDLE_VALUE)
throw new Win32Exception();
return path;

try
{
Expand All @@ -78,37 +64,6 @@ public static string GetFinalPathName(string path, bool allowUwp = true)
}
}

private static string TryGetFilePathFromUWPAppManifest(string path, string manifest)
{
var document = new XmlDocument();
document.Load(manifest);

var tag = document.GetElementsByTagName("Identity")[0]!;
var packageName = tag!.Attributes!["Name"]!.Value;

// I wish I could use WinRT APIs but support is removed from runtime and the official way cuts off support for Win7/8.1
var newFolder = GetPowershellPackageInstallLocation(packageName!);
return Path.Combine(newFolder, Path.GetFileName(path));
}

private static string GetPowershellPackageInstallLocation(string packageName)
{
var processStartInfo = new ProcessStartInfo
{
FileName = @"powershell",
Arguments = $"(Get-AppxPackage {packageName}).InstallLocation",
RedirectStandardOutput = true,
CreateNoWindow = true
};
var process = Process.Start(processStartInfo);
process?.WaitForExit();
var output = process?.StandardOutput.ReadToEnd().TrimEnd();
if (output == null)
throw new Exception("Failed to get Package Install location via PowerShell.");

return output;
}

private static string RemoveDevicePrefix(string path)
{
const string DevicePrefix = @"\\?\";
Expand Down
Loading

0 comments on commit df71ffe

Please sign in to comment.