Skip to content

Commit

Permalink
Merge branch 'handle-1.26-feedback'
Browse files Browse the repository at this point in the history
  • Loading branch information
Sewer56 committed Mar 17, 2024
2 parents da366b8 + f4ac054 commit dd22739
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ static string GetProductName(string 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));
var appId = ApplicationConfig.AliasAppId(Path.GetFileName(exePath).ToLower());
var config = new ApplicationConfig(appId, GetProductName(exePath), exePath, Path.GetDirectoryName(exePath));

// Set AppName if empty & Ensure no duplicate ID.
if (string.IsNullOrEmpty(config.AppName))
Expand All @@ -89,18 +90,20 @@ static string GetProductName(string exePath)
}

// Try to auto deploy ASI Loader.
var deployer = new AsiLoaderDeployer(new PathTuple<ApplicationConfig>(applicationConfigFile, config));
if (deployer.CanDeploy())
if (isMsStore)
{
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)
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.
Actions.DisplayMessagebox.Invoke(Resources.AsiLoaderDialogTitle.Get(), Resources.AsiLoaderGamePassAutoInstallFail.Get());
}
}

// Write file to disk.
Expand Down
145 changes: 125 additions & 20 deletions source/Reloaded.Mod.Launcher.Lib/Utility/TryUnprotectGamePassGame.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Windows;
using System.Xml;
using Reloaded.Mod.Installer.DependencyInstaller.IO;
using static Reloaded.Mod.Launcher.Lib.Utility.DesktopAppxActivateOptions;
using FileMode = System.IO.FileMode;

namespace Reloaded.Mod.Launcher.Lib.Utility;
Expand Down Expand Up @@ -40,35 +40,61 @@ public static bool TryIt(string exePath)

// Append command to create 'terminate' file indicating script completion.
using var tempDir = new TemporaryFolderAllocation();

// Execute the script in game context where we have perms to access the files.
var scriptPath = Path.Combine(tempDir.FolderPath, "files.txt");
var scriptFile = Path.Combine(tempDir.FolderPath, "script.ps1");
var scriptContents = $"Invoke-CommandInDesktopPackage -PackageFamilyName '{packageFamilyName}' -AppId '{appId}' -Command '{compressedLoaderPath}' -Args \"`\"{scriptPath}`\"\"";
File.WriteAllLines(scriptPath, exeFiles, Encoding.UTF8);
File.WriteAllText(scriptFile, scriptContents, Encoding.UTF8);

// Run the script
var command = $"-NoProfile -ExecutionPolicy ByPass -File \"{scriptFile}\"";
var processStartInfo = new ProcessStartInfo
{
FileName = @"powershell",
Arguments = command,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
// Execute the script in game context where we have perms to access the files.
// ReSharper disable once SuspiciousTypeConversion.Global
TryActivate(
packageFamilyName + "!" + appId,
compressedLoaderPath,
$"\"{scriptPath}\""
);

using var process = Process.Start(processStartInfo);
process?.WaitForExit();

// Wait until process named 'replace-files-with-itself' is terminated.
// Wait until 'replace-files-with-itself' is terminated.
var processName = "replace-files-with-itself";
while (Process.GetProcessesByName(processName).Length > 0)
Thread.Sleep(1);

return true;
}


[SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")]
private static void TryActivate(string packageFamilyName, string compressedLoaderPath, string scriptPath)
{
// Note: `Get-Command Invoke-CommandInDesktopPackage | Format-List *` in powershell
// and decompile with dnSpy, etc. to check current OS impl.
try
{
var act = (IDesktopAppxActivatorWin11)new DesktopAppxActivator();
act.ActivateWithOptions(
packageFamilyName,
compressedLoaderPath,
scriptPath,
(uint)(CentennialProcess | NonPackagedExeProcessTree),
0,
out _);
return;
}
catch (Exception) { /* ignored */ }

try
{
var act = (IDesktopAppxActivatorWin10)new DesktopAppxActivator();
act.ActivateWithOptions(
packageFamilyName,
compressedLoaderPath,
scriptPath,
(uint)(CentennialProcess | NonPackagedExeProcessTree),
0,
out _);
return;
}
catch (Exception) { /* ignored */ }

throw new Exception("Can't make use of DesktopAppxActivator. Your OS may be too recent. Please report this.");
}

/// <summary/>
/// <param name="exePath">Path to the main game binary.</param>
/// <returns>True if this was auto-unprotected.</returns>
Expand Down Expand Up @@ -150,4 +176,83 @@ private static bool CanRead(string exePath)
return false;
}
}
}

// Enum for activation options
[Flags]
internal enum DesktopAppxActivateOptions
{
None = 0,
Elevate = 1,
NonPackagedExe = 2,
NonPackagedExeProcessTree = 4,
NonPackagedExeFlags = 6,
NoErrorUI = 8,
CheckForAppInstallerUpdates = 16,
CentennialProcess = 32,
UniversalProcess = 64,
Win32AlaCarteProcess = 128,
RuntimeBehaviorFlags = 224,
PartialTrust = 256,
UniversalConsole = 512,
AppSilo = 1024,
TrustLevelFlags = 1280,
}

// COM interface for activating desktop applications
[Guid("F158268A-D5A5-45CE-99CF-00D6C3F3FC0A")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IDesktopAppxActivatorWin11
{
void Activate(
[MarshalAs(UnmanagedType.LPWStr)]
string applicationUserModelId,
[MarshalAs(UnmanagedType.LPWStr)]
string packageRelativeExecutable,
[MarshalAs(UnmanagedType.LPWStr)]
string arguments,
out IntPtr processHandle);

void ActivateWithOptions(
[MarshalAs(UnmanagedType.LPWStr)]
string applicationUserModelId,
[MarshalAs(UnmanagedType.LPWStr)]
string executable,
[MarshalAs(UnmanagedType.LPWStr)]
string arguments,
uint activationOptions,
uint parentProcessId,
out IntPtr processHandle);
}

[Guid("72e3a5b0-8fea-485c-9f8b-822b16dba17f")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IDesktopAppxActivatorWin10
{
void Activate(
[MarshalAs(UnmanagedType.LPWStr)]
string applicationUserModelId,
[MarshalAs(UnmanagedType.LPWStr)]
string packageRelativeExecutable,
[MarshalAs(UnmanagedType.LPWStr)]
string arguments,
out IntPtr processHandle);

void ActivateWithOptions(
[MarshalAs(UnmanagedType.LPWStr)]
string applicationUserModelId,
[MarshalAs(UnmanagedType.LPWStr)]
string executable,
[MarshalAs(UnmanagedType.LPWStr)]
string arguments,
uint activationOptions,
uint parentProcessId,
out IntPtr processHandle);
}

[ComImport]
[Guid("168EB462-775F-42AE-9111-D714B2306C2E")]
class DesktopAppxActivator
{

}
12 changes: 11 additions & 1 deletion source/Reloaded.Mod.Loader.IO/Config/ApplicationConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,17 @@ public static string GetAbsoluteAppLocation(PathTuple<ApplicationConfig> config)
return Path.GetFullPath(finalPath);
}


/// <summary>
/// Replaces the current Application ID with a known alias.
/// </summary>
public static string AliasAppId(string input)
{
return input switch
{
"p4pc_dt_mc.exe" => "p4g.exe", // Persona 4 Golden 64-bit (MS Store)
_ => input
};
}

// Reflection-less JSON
public static JsonTypeInfo<ApplicationConfig> GetJsonTypeInfo(out bool supportsSerialize)
Expand Down
2 changes: 1 addition & 1 deletion source/Reloaded.Mod.Loader/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private IApplicationConfig FindThisApplication()
// In case of GamePass, binary locations can change after App updates (thanks Microsoft!)
// So as last resort, we'll match against the AppId.
Logger.LogWriteLineAsync($"Can't match by App Path, Matching by AppId!", Logger.ColorWarning);
var expectedAppId = Path.GetFileName(fullPath)!.ToLower();
var expectedAppId = ApplicationConfig.AliasAppId(Path.GetFileName(fullPath)!.ToLower());
foreach (var configuration in configurations)
{
if (configuration.Config.AppId.Equals(expectedAppId, StringComparison.OrdinalIgnoreCase))
Expand Down

0 comments on commit dd22739

Please sign in to comment.