mirror of
https://github.com/whoeevee/ivinject.git
synced 2026-01-09 00:25:03 +01:00
First commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
using System.Diagnostics;
|
||||
using ivinject.Features.Injection.Models;
|
||||
using static ivinject.Common.DirectoryExtensions;
|
||||
|
||||
namespace ivinject.Features.Injection;
|
||||
|
||||
internal class DebManager(IviInjectionEntry debEntry) : IDisposable
|
||||
{
|
||||
private readonly string _tempPath = TempDirectoryPath();
|
||||
|
||||
internal async Task<IviInjectionEntry[]> ExtractDebEntries()
|
||||
{
|
||||
if (debEntry.Type is not IviInjectionEntryType.DebianPackage)
|
||||
throw new ArgumentException("Entry type is not DebianPackage");
|
||||
|
||||
Directory.CreateDirectory(_tempPath);
|
||||
|
||||
var process = Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "tar",
|
||||
Arguments = $"-xf {debEntry.FullName} --directory={_tempPath}"
|
||||
}
|
||||
);
|
||||
|
||||
await process!.WaitForExitAsync();
|
||||
|
||||
var dataArchive = Directory.GetFiles(_tempPath, "data*.*")[0];
|
||||
|
||||
process = Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "tar",
|
||||
Arguments = $"-xf {dataArchive}",
|
||||
WorkingDirectory = _tempPath
|
||||
}
|
||||
);
|
||||
|
||||
await process!.WaitForExitAsync();
|
||||
|
||||
var dataFiles = Directory.EnumerateFiles(
|
||||
_tempPath,
|
||||
"*",
|
||||
SearchOption.AllDirectories
|
||||
);
|
||||
|
||||
var dataDirectories = Directory.EnumerateDirectories(
|
||||
_tempPath,
|
||||
"*",
|
||||
SearchOption.AllDirectories
|
||||
);
|
||||
|
||||
return dataFiles.Concat(dataDirectories)
|
||||
.Select(entry => new IviInjectionEntry(entry))
|
||||
.Where(entry => entry.Type is not IviInjectionEntryType.Unknown)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public void Dispose() => Directory.Delete(_tempPath, true);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Diagnostics;
|
||||
using ivinject.Common.Models;
|
||||
using ivinject.Features.Injection.Models;
|
||||
using RegularExpressions = ivinject.Features.Injection.Models.RegularExpressions;
|
||||
|
||||
namespace ivinject.Features.Injection;
|
||||
|
||||
internal static class DependencyExtensions
|
||||
{
|
||||
internal static async Task<string[]> GetSharedLibraries(this IviMachOBinary binary)
|
||||
{
|
||||
using var process = Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "otool",
|
||||
Arguments = $"-L {binary.FullName}",
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
|
||||
var output = await process!.StandardOutput.ReadToEndAsync();
|
||||
var matches = RegularExpressions.OToolSharedLibrary().Matches(output);
|
||||
|
||||
// the first result is actually LC_ID_DYLIB, not LC_LOAD_DYLIB
|
||||
return matches.Select(match => match.Groups[1].Value).ToArray()[1..];
|
||||
}
|
||||
|
||||
internal static async Task ChangeDependency(
|
||||
this IviMachOBinary binary,
|
||||
string oldPath,
|
||||
string newPath
|
||||
)
|
||||
{
|
||||
using var process = Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "install_name_tool",
|
||||
Arguments = $"-change {oldPath} {newPath} {binary.FullName}",
|
||||
RedirectStandardError = true
|
||||
}
|
||||
);
|
||||
|
||||
await process!.WaitForExitAsync();
|
||||
}
|
||||
|
||||
internal static async Task<bool> AddRunPath(
|
||||
this IviMachOBinary binary,
|
||||
string rPath
|
||||
)
|
||||
{
|
||||
using var process = Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "install_name_tool",
|
||||
Arguments = $"-add_rpath {rPath} {binary.FullName}",
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
|
||||
await process!.WaitForExitAsync();
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
|
||||
internal static async Task InsertDependency(
|
||||
this IviMachOBinary binary,
|
||||
string dependency
|
||||
)
|
||||
{
|
||||
using var process = Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "insert-dylib",
|
||||
Arguments = $"{dependency} {binary.FullName} --all-yes --inplace",
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
|
||||
await process!.WaitForExitAsync();
|
||||
}
|
||||
|
||||
internal static async Task<IEnumerable<string>> AllDependencies(this List<IviCopiedBinary> copiedBinaries)
|
||||
{
|
||||
return (await Task.WhenAll(
|
||||
copiedBinaries.Select(async binary => await binary.Binary.GetSharedLibraries())
|
||||
))
|
||||
.SelectMany(dependencies => dependencies)
|
||||
.Distinct();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
using Claunia.PropertyList;
|
||||
using ivinject.Common.Models;
|
||||
using ivinject.Features.Injection.Models;
|
||||
using ivinject.Features.Packaging;
|
||||
using ivinject.Features.Packaging.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static ivinject.Common.DirectoryExtensions;
|
||||
|
||||
namespace ivinject.Features.Injection;
|
||||
|
||||
internal class InjectionManager(ILogger logger)
|
||||
{
|
||||
private readonly List<IviCopiedBinary> _copiedBinaries = [];
|
||||
private IviPackageInfo _packageInfo = null!;
|
||||
|
||||
private static readonly IviInjectionEntry[] KnownFrameworkEntries = Directory.GetDirectories(
|
||||
Path.Combine(HomeDirectoryPath(), ".ivinject"),
|
||||
"*.framework"
|
||||
)
|
||||
.Select(framework => new IviInjectionEntry(framework))
|
||||
.ToArray();
|
||||
|
||||
private static readonly IviInjectionEntry OrionFramework = KnownFrameworkEntries
|
||||
.Single(framework => framework.Name == "Orion.framework");
|
||||
|
||||
private static readonly IviInjectionEntry SubstrateFramework = KnownFrameworkEntries
|
||||
.Single(framework => framework.Name == "CydiaSubstrate.framework");
|
||||
|
||||
internal void UpdateWithPackage(IviPackageInfo packageInfo) =>
|
||||
_packageInfo = packageInfo;
|
||||
|
||||
private async Task<IviInjectionEntry[]> PrepareEntriesAsync(
|
||||
IviInjectionEntry[] entries,
|
||||
List<IDisposable> debManagers
|
||||
)
|
||||
{
|
||||
var finalEntries = entries.Where(entry =>
|
||||
entry.Type is not IviInjectionEntryType.DebianPackage
|
||||
).ToList();
|
||||
|
||||
var debFiles = entries.Where(entry =>
|
||||
entry.Type is IviInjectionEntryType.DebianPackage
|
||||
);
|
||||
|
||||
foreach (var debFile in debFiles)
|
||||
{
|
||||
var name = debFile.Name;
|
||||
|
||||
var debManager = new DebManager(debFile);
|
||||
|
||||
var debEntries = await debManager.ExtractDebEntries();
|
||||
logger.LogInformation("{} entries within {} will be injected", debEntries.Length, name);
|
||||
|
||||
finalEntries.AddRange(debEntries);
|
||||
debManagers.Add(debManager);
|
||||
}
|
||||
|
||||
return finalEntries.ToArray();
|
||||
}
|
||||
|
||||
private void CopyEntries(IEnumerable<IviInjectionEntry> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var isReplaced = false;
|
||||
var pathInBundle = entry.GetPathInBundle(_packageInfo.DirectoriesInfo);
|
||||
|
||||
var isDynamicLibrary = entry.Type is IviInjectionEntryType.DynamicLibrary;
|
||||
|
||||
if (isDynamicLibrary || entry.Type is IviInjectionEntryType.Unknown)
|
||||
{
|
||||
if (File.Exists(pathInBundle))
|
||||
{
|
||||
File.Delete(pathInBundle);
|
||||
isReplaced = true;
|
||||
}
|
||||
|
||||
File.Copy(entry.FullName, pathInBundle);
|
||||
|
||||
if (isDynamicLibrary)
|
||||
_copiedBinaries.Add(
|
||||
new IviCopiedBinary
|
||||
{
|
||||
Binary = new IviMachOBinary(pathInBundle),
|
||||
Type = entry.Type
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Directory.Exists(pathInBundle))
|
||||
{
|
||||
Directory.Delete(pathInBundle, true);
|
||||
isReplaced = true;
|
||||
}
|
||||
|
||||
CopyDirectory(entry.FullName, pathInBundle, true);
|
||||
|
||||
if (entry.Type is not IviInjectionEntryType.Bundle) {
|
||||
var infoDictionaryPath = Path.Combine(pathInBundle, "Info.plist");
|
||||
var infoDictionary = (NSDictionary)PropertyListParser.Parse(infoDictionaryPath);
|
||||
|
||||
_copiedBinaries.Add(
|
||||
new IviCopiedBinary
|
||||
{
|
||||
Binary = new IviMachOBinary(
|
||||
Path.Combine(pathInBundle, infoDictionary.BundleExecutable())
|
||||
),
|
||||
Type = entry.Type
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("{} {}", isReplaced ? "Replaced" : "Copied", entry.Name);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task AddEntriesAsync(IEnumerable<IviInjectionEntry> files)
|
||||
{
|
||||
var debManagers = new List<IDisposable>();
|
||||
|
||||
var entries = await PrepareEntriesAsync(files.ToArray(), debManagers);
|
||||
CopyEntries(entries);
|
||||
|
||||
debManagers.ForEach(manager => manager.Dispose());
|
||||
}
|
||||
|
||||
internal async Task<bool> ThinCopiedBinariesAsync()
|
||||
{
|
||||
var fatBinaries =
|
||||
_copiedBinaries.Select(binary => binary.Binary).Where(binary => binary.IsFatFile);
|
||||
|
||||
foreach (var fatBinary in fatBinaries)
|
||||
{
|
||||
var previousSize = fatBinary.FileSize;
|
||||
|
||||
if (!await fatBinary.Thin())
|
||||
return false;
|
||||
|
||||
logger.LogInformation(
|
||||
"Thinned {} ({} -> {})",
|
||||
fatBinary.Name,
|
||||
previousSize,
|
||||
fatBinary.FileSize
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task CopyKnownFrameworksAsync()
|
||||
{
|
||||
var allDependencies = await _copiedBinaries.AllDependencies();
|
||||
|
||||
var entries = KnownFrameworkEntries.Where(framework =>
|
||||
allDependencies.Any(dependency => dependency.Contains(framework.Name))
|
||||
).ToList();
|
||||
|
||||
if (entries.Contains(OrionFramework) && !entries.Contains(SubstrateFramework))
|
||||
entries.Add(SubstrateFramework);
|
||||
|
||||
CopyEntries(entries);
|
||||
}
|
||||
|
||||
internal async Task FixCopiedDependenciesAsync()
|
||||
{
|
||||
var copiedNames = _copiedBinaries.Select(binary => binary.Name);
|
||||
|
||||
foreach (var binary in _copiedBinaries.Select(binary => binary.Binary))
|
||||
{
|
||||
var dependencies = await binary.GetSharedLibraries();
|
||||
|
||||
var brokenDependencies = dependencies.Where(dependency =>
|
||||
!dependency.StartsWith('@') && copiedNames.Any(dependency.Contains)
|
||||
);
|
||||
|
||||
foreach (var dependency in brokenDependencies)
|
||||
{
|
||||
var copiedBinary = _copiedBinaries.Single(copiedBinary =>
|
||||
dependency.Contains(copiedBinary.Name)
|
||||
);
|
||||
|
||||
var newPath = copiedBinary.GetRunPath(_packageInfo.DirectoriesInfo);
|
||||
await binary.ChangeDependency(dependency, newPath);
|
||||
|
||||
logger.LogInformation(
|
||||
"Fixed dependency path in {} ({} -> {})",
|
||||
binary.Name,
|
||||
dependency,
|
||||
newPath
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task InsertLoadCommandsAsync()
|
||||
{
|
||||
var mainBinary = _packageInfo.MainBinary;
|
||||
|
||||
var copiedDependencies = (await _copiedBinaries.AllDependencies())
|
||||
.Where(dependency => dependency.StartsWith('@'));
|
||||
|
||||
var mainBinaryDependencies = await mainBinary.GetSharedLibraries();
|
||||
|
||||
// if (mainBinaryDependencies.All(dependency => !dependency.StartsWith("@rpath")))
|
||||
// {
|
||||
// await mainBinary.AddRunPath("@executable_path/Frameworks");
|
||||
// logger.LogInformation("Added Frameworks to {}'s run path", mainBinary.Name);
|
||||
// }
|
||||
|
||||
var dependenciesToInsert = _copiedBinaries
|
||||
.Where(binary =>
|
||||
binary.Type is IviInjectionEntryType.DynamicLibrary or IviInjectionEntryType.Framework
|
||||
)
|
||||
.Where(binary =>
|
||||
copiedDependencies.All(dependency => !dependency.Contains(binary.Name))
|
||||
);
|
||||
|
||||
foreach (var dependency in dependenciesToInsert)
|
||||
{
|
||||
if (mainBinaryDependencies.Contains(dependency.Name))
|
||||
continue;
|
||||
|
||||
var runPath = dependency.GetRunPath(_packageInfo.DirectoriesInfo);
|
||||
|
||||
await mainBinary.InsertDependency(runPath);
|
||||
logger.LogInformation("Inserted load command {} into {}", runPath, mainBinary.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Text;
|
||||
using ivinject.Common.Models;
|
||||
using ivinject.Features.Packaging.Models;
|
||||
|
||||
namespace ivinject.Features.Injection.Models;
|
||||
|
||||
internal class IviCopiedBinary
|
||||
{
|
||||
internal required IviInjectionEntryType Type { get; init; }
|
||||
internal required IviMachOBinary Binary { get; init; }
|
||||
|
||||
internal string Name => Binary.Name;
|
||||
|
||||
internal string GetRunPath(IviDirectoriesInfo directoriesInfo)
|
||||
{
|
||||
var builder = new StringBuilder("@rpath/");
|
||||
|
||||
builder.Append(
|
||||
Type switch
|
||||
{
|
||||
IviInjectionEntryType.Framework => Path.GetRelativePath(
|
||||
directoriesInfo.FrameworksDirectory,
|
||||
Binary.FullName
|
||||
),
|
||||
IviInjectionEntryType.PlugIn => Path.GetRelativePath(
|
||||
directoriesInfo.PlugInsDirectory,
|
||||
Binary.FullName
|
||||
),
|
||||
_ => Binary.Name
|
||||
}
|
||||
);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using ivinject.Features.Packaging.Models;
|
||||
|
||||
namespace ivinject.Features.Injection.Models;
|
||||
|
||||
internal class IviInjectionEntry
|
||||
{
|
||||
private readonly FileInfo _fileInfo;
|
||||
internal string FullName => _fileInfo.FullName;
|
||||
internal string Name => _fileInfo.Name;
|
||||
internal IviInjectionEntryType Type => _fileInfo.Extension switch
|
||||
{
|
||||
".dylib" => IviInjectionEntryType.DynamicLibrary,
|
||||
".deb" => IviInjectionEntryType.DebianPackage,
|
||||
".bundle" => IviInjectionEntryType.Bundle,
|
||||
".framework" => IviInjectionEntryType.Framework,
|
||||
".appex" => IviInjectionEntryType.PlugIn,
|
||||
_ => IviInjectionEntryType.Unknown
|
||||
};
|
||||
|
||||
internal IviInjectionEntry(FileInfo fileInfo)
|
||||
=> _fileInfo = fileInfo;
|
||||
|
||||
internal IviInjectionEntry(string filePath)
|
||||
=> _fileInfo = new FileInfo(filePath);
|
||||
|
||||
internal string GetPathInBundle(IviDirectoriesInfo directoriesInfo) =>
|
||||
Type switch
|
||||
{
|
||||
IviInjectionEntryType.DynamicLibrary or IviInjectionEntryType.Unknown =>
|
||||
Path.Combine(
|
||||
Type is IviInjectionEntryType.DynamicLibrary
|
||||
? directoriesInfo.FrameworksDirectory
|
||||
: directoriesInfo.BundleDirectory,
|
||||
Name
|
||||
),
|
||||
IviInjectionEntryType.Framework => Path.Combine(
|
||||
directoriesInfo.FrameworksDirectory,
|
||||
Name
|
||||
),
|
||||
IviInjectionEntryType.PlugIn => Path.Combine(
|
||||
directoriesInfo.PlugInsDirectory,
|
||||
Name
|
||||
),
|
||||
_ => Path.Combine(directoriesInfo.BundleDirectory, Name)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace ivinject.Features.Injection.Models;
|
||||
|
||||
internal enum IviInjectionEntryType
|
||||
{
|
||||
DynamicLibrary,
|
||||
DebianPackage,
|
||||
Bundle,
|
||||
Framework,
|
||||
PlugIn,
|
||||
Unknown
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ivinject.Features.Injection.Models;
|
||||
|
||||
internal static partial class RegularExpressions
|
||||
{
|
||||
[GeneratedRegex(@"([\/@].*) \(.*\)", RegexOptions.Compiled)]
|
||||
internal static partial Regex OToolSharedLibrary();
|
||||
}
|
||||
Reference in New Issue
Block a user