Skip to content

Commit

Permalink
Patch exes instead of using probing path
Browse files Browse the repository at this point in the history
  • Loading branch information
cyanfish committed Aug 24, 2024
1 parent 50ed7fc commit 9a54bc5
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 49 deletions.
5 changes: 2 additions & 3 deletions NAPS2.Sdk/Util/AssemblyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ static AssemblyHelper()
// https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview?tabs=cli#api-incompatibility
EntryFolder = AppContext.BaseDirectory;

// If this is NAPS2.Worker.exe inside the "lib" subfolder, the base path needs to be the parent folder
var args = Environment.GetCommandLineArgs();
if (args.Length > 0 && args[0].ToLowerInvariant().Contains("naps2.worker") && EntryFolder.EndsWith(@"\lib\"))
// If this is inside the "lib" subfolder, the base path needs to be the parent folder
if (EntryFolder.EndsWith(@"\lib\"))
{
EntryFolder = EntryFolder.Substring(0, EntryFolder.Length - 4);
}
Expand Down
88 changes: 42 additions & 46 deletions NAPS2.Tools/Project/Packaging/PackageCommand.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text;
using System.Text.RegularExpressions;
using NAPS2.Tools.Project.Targets;
using Newtonsoft.Json;
Expand Down Expand Up @@ -108,21 +109,15 @@ private static void PopulatePackageInfo(string buildPath, Platform platform, Pac
throw new Exception($"Could not find path: {dir.FullName}");
}

// Parse the NAPS2.deps.json file to:
// (a) As part of our effort to relocate DLLs under the "lib" subfolder for a cleaner install directory,
// get the map of subfolders where the prober will look for each DLL
// (b) Strip out dependencies we're "manually" trimming via "excludeDlls"
// TODO: Fix for NAPS2.Console
// Parse the NAPS2.deps.json file to strip out dependencies we're "manually" trimming via "excludeDlls"
var depsFile = dir.EnumerateFiles("*.deps.json").First();
JObject deps;
using (var stream = depsFile.OpenText())
using (var reader = new JsonTextReader(stream))
deps = (JObject) JToken.ReadFrom(reader);
var targets = (JObject) deps["targets"]![".NETCoreApp,Version=v9.0/win-x64"]!;
var dllMap = new Dictionary<string, string>();
foreach (var pair in targets)
{
var pathPrefix = pair.Key;
var target = (JObject) pair.Value!;
if (target.TryGetValue("runtime", out var runtime))
{
Expand All @@ -134,7 +129,6 @@ private static void PopulatePackageInfo(string buildPath, Platform platform, Pac
{
((JObject) runtime).Remove(runtimeDlls.Key);
}
dllMap.Add(dllName, Path.Combine("lib", pathPrefix.Replace('/', Path.DirectorySeparatorChar), string.Join(Path.DirectorySeparatorChar, parts.SkipLast(1))));
}
}
if (target.TryGetValue("resources", out var resources))
Expand All @@ -157,66 +151,32 @@ private static void PopulatePackageInfo(string buildPath, Platform platform, Pac
{
((JObject) native).Remove(runtimeDlls.Key);
}
if (!runtimeDlls.Key.StartsWith("host"))
{
dllMap.Add(runtimeDlls.Key,
Path.Combine("lib", pathPrefix.Replace('/', Path.DirectorySeparatorChar)));
}
}
}
}
using (StreamWriter file = depsFile.CreateText())
using (JsonTextWriter writer = new JsonTextWriter(file) { Formatting = Formatting.Indented })
deps.WriteTo(writer);

string GetProbingPath(string dll)
{
if (dll is "NAPS2.dll" or "NAPS2.Console.dll") return "";
return dllMap.GetValueOrDefault(dll, "");
}

string GetResourceProbingPath(string dll, string lang)
{
dll = dll.Replace(".resources.dll", ".dll");
if (dllMap.TryGetValue(dll, out var path))
{
return Path.Combine(path, lang);
}
return lang;
}

// Add additionalProbingPaths=["lib"] to the NAPS2.runtimeconfig.json file
// TODO: Fix for NAPS2.Console
var runtimeConfigFile = dir.EnumerateFiles("*.runtimeconfig.json").First();
JObject runtimeConfig;
using (var stream = runtimeConfigFile.OpenText())
using (var reader = new JsonTextReader(stream))
runtimeConfig = (JObject) JToken.ReadFrom(reader);

((JObject) runtimeConfig["runtimeOptions"]!)["additionalProbingPaths"] = new JArray { "lib" };

using (StreamWriter file = runtimeConfigFile.CreateText())
using (JsonTextWriter writer = new JsonTextWriter(file) { Formatting = Formatting.Indented })
runtimeConfig.WriteTo(writer);

// Add each included file to the package contents
foreach (var exeFile in dir.EnumerateFiles("*.exe"))
{
if (excludeDlls.All(exclude => !exeFile.Name.StartsWith(exclude)))
{
if (exeFile.Name == "NAPS2.Worker.exe") continue;
PatchExe(exeFile);
pkgInfo.AddFile(exeFile, "");
}
}
foreach (var configFile in dir.EnumerateFiles("*.json"))
{
pkgInfo.AddFile(configFile, "");
pkgInfo.AddFile(configFile, "lib");
}
foreach (var dllFile in dir.EnumerateFiles("*.dll"))
{
if (excludeDlls.All(exclude => !dllFile.Name.StartsWith(exclude)))
{
pkgInfo.AddFile(dllFile, GetProbingPath(dllFile.Name));
pkgInfo.AddFile(dllFile, "lib");
}
}
foreach (var langFolder in dir.EnumerateDirectories()
Expand All @@ -226,13 +186,49 @@ string GetResourceProbingPath(string dll, string lang)
{
if (excludeDlls.All(exclude => !resourceDll.Name.StartsWith(exclude)))
{
pkgInfo.AddFile(resourceDll, GetResourceProbingPath(resourceDll.Name, langFolder.Name));
pkgInfo.AddFile(resourceDll, Path.Combine("lib", langFolder.Name));
pkgInfo.Languages.Add(langFolder.Name);
}
}
}
}

private static void PatchExe(FileInfo exeFile)
{
// The dotnet base exes (e.g. NAPS2.exe) have a hard-coded path for the relevant dll (e.g. NAPS2.dll).
// This path is also the path at which all the dependencies are searched. By default, the path is in the current
// directory, but we can easily replace it with a subpath to the "lib" folder. (Note that the path is padded so
// we don't even need to offset the bytes afterward.) This means everything other than the exes can live in
// that "lib" subfolder once we do this patch.
var bytes = File.ReadAllBytes(exeFile.FullName);
var from = Path.ChangeExtension(exeFile.Name, ".dll");
var to = @"lib\" + from;
var fromBytes = Encoding.UTF8.GetBytes(from);
var toBytes = Encoding.UTF8.GetBytes(to);
var index = SearchBytes(bytes, fromBytes);
for (int i = 0; i < toBytes.Length; i++)
{
bytes[index + i] = toBytes[i];
}
File.WriteAllBytes(exeFile.FullName, bytes);
}

private static int SearchBytes(byte[] haystack, byte[] needle)
{
var len = needle.Length;
var limit = haystack.Length - len;
for (var i = 0; i <= limit; i++)
{
var k = 0;
for (; k < len; k++)
{
if (needle[k] != haystack[i + k]) break;
}
if (k == len) return i;
}
return -1;
}

private static void AddPlatformFiles(PackageInfo pkgInfo, string buildPath, string platformPath)
{
var folder = new DirectoryInfo(Path.Combine(buildPath, platformPath));
Expand Down
1 change: 1 addition & 0 deletions NAPS2.Tools/Project/Packaging/ZipArchivePackager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static void PackageZip(Func<PackageInfo> pkgInfoFunc)
Cli.Run("dotnet", "publish NAPS2.App.Worker -c Release /p:DebugType=None /p:DebugSymbols=false /p:DefineConstants=ZIP");
Cli.Run("dotnet", "publish NAPS2.App.WinForms -c Release /p:DebugType=None /p:DebugSymbols=false /p:DefineConstants=ZIP");
Cli.Run("dotnet", "publish NAPS2.App.Console -c Release /p:DebugType=None /p:DebugSymbols=false /p:DefineConstants=ZIP");
Cli.Run("dotnet", "build NAPS2.App.PortableLauncher -c Release");

var pkgInfo = pkgInfoFunc();
var zipPath = pkgInfo.GetPath("zip");
Expand Down

0 comments on commit 9a54bc5

Please sign in to comment.