mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-09-23 09:22:34 +00:00
Rebrand LegacyForge to Weave Loader
Rename across entire codebase: - LegacyForge -> WeaveLoader (identifiers, namespaces, classes, DLLs) - LegacyForgeRuntime -> WeaveLoaderRuntime (C++ project) - LegacyForge.API/Core/Launcher -> WeaveLoader.API/Core/Launcher (C# projects) - [LegacyForge] -> [WeaveLoader] (log prefixes) - legacyforge -> weaveloader (config files, log files, backup suffixes) - Display name "Weave Loader" in README, CONTRIBUTING, LICENSE
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace WeaveLoader.Launcher;
|
||||
|
||||
public class Config
|
||||
{
|
||||
public string? GameExePath { get; set; }
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public static Config Load(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return new Config();
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(path);
|
||||
return JsonSerializer.Deserialize<Config>(json, JsonOptions) ?? new Config();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Config();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(string path)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(this, JsonOptions);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WeaveLoader.Launcher;
|
||||
|
||||
/// <summary>
|
||||
/// Thin wrapper around the Win32 GetOpenFileName API.
|
||||
/// No WinForms/WPF dependency required.
|
||||
/// </summary>
|
||||
internal static class FileDialog
|
||||
{
|
||||
public static string? OpenFileDialog(string title, string filter)
|
||||
{
|
||||
var ofn = new OpenFileName();
|
||||
ofn.lStructSize = Marshal.SizeOf(ofn);
|
||||
ofn.lpstrFilter = filter;
|
||||
ofn.lpstrFile = new string('\0', 260);
|
||||
ofn.nMaxFile = 260;
|
||||
ofn.lpstrTitle = title;
|
||||
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
|
||||
|
||||
if (GetOpenFileName(ofn))
|
||||
return ofn.lpstrFile.TrimEnd('\0');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private const int OFN_PATHMUSTEXIST = 0x00000800;
|
||||
private const int OFN_FILEMUSTEXIST = 0x00001000;
|
||||
private const int OFN_NOCHANGEDIR = 0x00000008;
|
||||
|
||||
[DllImport("comdlg32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private class OpenFileName
|
||||
{
|
||||
public int lStructSize;
|
||||
public IntPtr hwndOwner;
|
||||
public IntPtr hInstance;
|
||||
public string? lpstrFilter;
|
||||
public string? lpstrCustomFilter;
|
||||
public int nMaxCustFilter;
|
||||
public int nFilterIndex;
|
||||
public string? lpstrFile;
|
||||
public int nMaxFile;
|
||||
public string? lpstrFileTitle;
|
||||
public int nMaxFileTitle;
|
||||
public string? lpstrInitialDir;
|
||||
public string? lpstrTitle;
|
||||
public int Flags;
|
||||
public short nFileOffset;
|
||||
public short nFileExtension;
|
||||
public string? lpstrDefExt;
|
||||
public IntPtr lCustData;
|
||||
public IntPtr lpfnHook;
|
||||
public string? lpTemplateName;
|
||||
public IntPtr pvReserved;
|
||||
public int dwReserved;
|
||||
public int FlagsEx;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace WeaveLoader.Launcher;
|
||||
|
||||
/// <summary>
|
||||
/// Handles launching the game process in a suspended state and injecting
|
||||
/// the WeaveLoaderRuntime DLL via CreateRemoteThread + LoadLibraryW.
|
||||
/// </summary>
|
||||
public static class Injector
|
||||
{
|
||||
public record InjectedProcess(
|
||||
IntPtr ProcessHandle,
|
||||
IntPtr ThreadHandle,
|
||||
int ProcessId);
|
||||
|
||||
public static InjectedProcess LaunchSuspended(string exePath, string? workingDir = null)
|
||||
{
|
||||
workingDir ??= Path.GetDirectoryName(exePath);
|
||||
|
||||
var si = new STARTUPINFO { cb = Marshal.SizeOf<STARTUPINFO>() };
|
||||
|
||||
bool success = CreateProcess(
|
||||
exePath,
|
||||
null,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
false,
|
||||
CREATE_SUSPENDED,
|
||||
IntPtr.Zero,
|
||||
workingDir,
|
||||
ref si,
|
||||
out PROCESS_INFORMATION pi);
|
||||
|
||||
if (!success)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
$"Failed to launch process: {exePath}");
|
||||
|
||||
return new InjectedProcess(pi.hProcess, pi.hThread, pi.dwProcessId);
|
||||
}
|
||||
|
||||
public static void InjectDll(InjectedProcess process, string dllPath)
|
||||
{
|
||||
string fullDllPath = Path.GetFullPath(dllPath);
|
||||
if (!File.Exists(fullDllPath))
|
||||
throw new FileNotFoundException($"Runtime DLL not found: {fullDllPath}");
|
||||
|
||||
byte[] dllPathBytes = Encoding.Unicode.GetBytes(fullDllPath + '\0');
|
||||
|
||||
IntPtr remoteMem = VirtualAllocEx(
|
||||
process.ProcessHandle,
|
||||
IntPtr.Zero,
|
||||
(uint)dllPathBytes.Length,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_READWRITE);
|
||||
|
||||
if (remoteMem == IntPtr.Zero)
|
||||
{
|
||||
TerminateProcess(process.ProcessHandle, 1);
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"Failed to allocate memory in target process");
|
||||
}
|
||||
|
||||
bool wrote = WriteProcessMemory(
|
||||
process.ProcessHandle,
|
||||
remoteMem,
|
||||
dllPathBytes,
|
||||
(uint)dllPathBytes.Length,
|
||||
out _);
|
||||
|
||||
if (!wrote)
|
||||
{
|
||||
TerminateProcess(process.ProcessHandle, 1);
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"Failed to write DLL path to target process");
|
||||
}
|
||||
|
||||
IntPtr kernel32 = GetModuleHandle("kernel32.dll");
|
||||
IntPtr loadLibraryW = GetProcAddress(kernel32, "LoadLibraryW");
|
||||
|
||||
IntPtr remoteThread = CreateRemoteThread(
|
||||
process.ProcessHandle,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
loadLibraryW,
|
||||
remoteMem,
|
||||
0,
|
||||
out _);
|
||||
|
||||
if (remoteThread == IntPtr.Zero)
|
||||
{
|
||||
TerminateProcess(process.ProcessHandle, 1);
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"Failed to create remote thread for DLL injection");
|
||||
}
|
||||
|
||||
uint waitResult = WaitForSingleObject(remoteThread, 10000);
|
||||
if (waitResult != 0)
|
||||
{
|
||||
CloseHandle(remoteThread);
|
||||
VirtualFreeEx(process.ProcessHandle, remoteMem, 0, MEM_RELEASE);
|
||||
TerminateProcess(process.ProcessHandle, 1);
|
||||
throw new Exception(
|
||||
$"Timed out waiting for DLL injection (WaitForSingleObject returned {waitResult})");
|
||||
}
|
||||
|
||||
GetExitCodeThread(remoteThread, out uint exitCode);
|
||||
CloseHandle(remoteThread);
|
||||
VirtualFreeEx(process.ProcessHandle, remoteMem, 0, MEM_RELEASE);
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
TerminateProcess(process.ProcessHandle, 1);
|
||||
throw new Exception(
|
||||
"DLL injection failed: LoadLibraryW returned NULL.\n" +
|
||||
"This usually means the DLL or one of its dependencies could not be found.\n" +
|
||||
"Make sure the MSVC redistributable is installed and the DLL was built in Release mode.");
|
||||
}
|
||||
|
||||
Console.WriteLine($" LoadLibraryW returned module handle 0x{exitCode:X} -- DLL loaded in target process.");
|
||||
}
|
||||
|
||||
public static void ResumeProcess(InjectedProcess process)
|
||||
{
|
||||
ResumeThread(process.ThreadHandle);
|
||||
CloseHandle(process.ThreadHandle);
|
||||
}
|
||||
|
||||
#region Win32 Interop
|
||||
|
||||
private const uint CREATE_SUSPENDED = 0x00000004;
|
||||
private const uint MEM_COMMIT = 0x1000;
|
||||
private const uint MEM_RESERVE = 0x2000;
|
||||
private const uint MEM_RELEASE = 0x8000;
|
||||
private const uint PAGE_READWRITE = 0x04;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct STARTUPINFO
|
||||
{
|
||||
public int cb;
|
||||
public string lpReserved, lpDesktop, lpTitle;
|
||||
public int dwX, dwY, dwXSize, dwYSize;
|
||||
public int dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
|
||||
public short wShowWindow, cbReserved2;
|
||||
public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess, hThread;
|
||||
public int dwProcessId, dwThreadId;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool CreateProcess(
|
||||
string lpApplicationName, string? lpCommandLine,
|
||||
IntPtr lpProcessAttributes, IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles, uint dwCreationFlags,
|
||||
IntPtr lpEnvironment, string? lpCurrentDirectory,
|
||||
ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr VirtualAllocEx(
|
||||
IntPtr hProcess, IntPtr lpAddress, uint dwSize,
|
||||
uint flAllocationType, uint flProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool VirtualFreeEx(
|
||||
IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool WriteProcessMemory(
|
||||
IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer,
|
||||
uint nSize, out int lpNumberOfBytesWritten);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr CreateRemoteThread(
|
||||
IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize,
|
||||
IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags,
|
||||
out uint lpThreadId);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
|
||||
private static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
|
||||
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint ResumeThread(IntPtr hThread);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WeaveLoader.Launcher;
|
||||
|
||||
class Program
|
||||
{
|
||||
private const string RuntimeDllName = "WeaveLoaderRuntime.dll";
|
||||
|
||||
[STAThread]
|
||||
static int Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("╔══════════════════════════════════╗");
|
||||
Console.WriteLine("║ WeaveLoader v1.0 ║");
|
||||
Console.WriteLine("║ Mod Loader for MC Legacy Edition║");
|
||||
Console.WriteLine("╚══════════════════════════════════╝");
|
||||
Console.WriteLine();
|
||||
|
||||
// All paths relative to where the exe lives, not the working directory
|
||||
string baseDir = AppContext.BaseDirectory;
|
||||
string configFile = Path.Combine(baseDir, "weaveloader.json");
|
||||
string runtimeDll = Path.Combine(baseDir, RuntimeDllName);
|
||||
string modsDir = Path.Combine(baseDir, "mods");
|
||||
|
||||
try
|
||||
{
|
||||
var config = Config.Load(configFile);
|
||||
|
||||
if (args.Length > 0 && File.Exists(args[0]))
|
||||
{
|
||||
config.GameExePath = args[0];
|
||||
config.Save(configFile);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(config.GameExePath) || !File.Exists(config.GameExePath))
|
||||
{
|
||||
Console.WriteLine("Please select Minecraft.Client.exe...");
|
||||
string? selected = FileDialog.OpenFileDialog(
|
||||
"Select Minecraft.Client.exe",
|
||||
"Executable Files (*.exe)\0*.exe\0All Files (*.*)\0*.*\0");
|
||||
|
||||
if (string.IsNullOrEmpty(selected) || !File.Exists(selected))
|
||||
{
|
||||
Console.Error.WriteLine("Error: No file selected or file not found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
config.GameExePath = Path.GetFullPath(selected);
|
||||
config.Save(configFile);
|
||||
Console.WriteLine($"Saved game path to {configFile}");
|
||||
}
|
||||
|
||||
if (!File.Exists(runtimeDll))
|
||||
{
|
||||
Console.Error.WriteLine($"Error: {RuntimeDllName} not found.");
|
||||
Console.Error.WriteLine($"Expected at: {runtimeDll}");
|
||||
Console.Error.WriteLine();
|
||||
Console.Error.WriteLine("The C++ runtime DLL must be built separately with CMake:");
|
||||
Console.Error.WriteLine(" cd WeaveLoaderRuntime");
|
||||
Console.Error.WriteLine(" cmake -B build -A x64");
|
||||
Console.Error.WriteLine(" cmake --build build --config Release");
|
||||
Console.Error.WriteLine();
|
||||
Console.Error.WriteLine("Then copy WeaveLoaderRuntime.dll to the same folder as WeaveLoader.exe.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(modsDir))
|
||||
{
|
||||
Directory.CreateDirectory(modsDir);
|
||||
Console.WriteLine($"Created mods/ directory");
|
||||
}
|
||||
|
||||
int modCount = Directory.GetFiles(modsDir, "*.dll").Length;
|
||||
Console.WriteLine($"Found {modCount} mod(s) in mods/");
|
||||
Console.WriteLine($"Launching {Path.GetFileName(config.GameExePath)}...");
|
||||
|
||||
var process = Injector.LaunchSuspended(config.GameExePath);
|
||||
Console.WriteLine($"[OK] Game process created (PID: {process.ProcessId})");
|
||||
Console.WriteLine($"[..] Injecting {RuntimeDllName}...");
|
||||
|
||||
Injector.InjectDll(process, runtimeDll);
|
||||
Console.WriteLine($"[OK] {RuntimeDllName} injected and loaded in target process.");
|
||||
|
||||
Injector.ResumeProcess(process);
|
||||
Console.WriteLine("[OK] Game resumed. WeaveLoader is active.");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Press any key to exit the launcher (game will keep running).");
|
||||
Console.ReadKey(true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Fatal error: {ex.Message}");
|
||||
Console.Error.WriteLine(ex.StackTrace);
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadKey(true);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>WeaveLoader.Launcher</RootNamespace>
|
||||
<AssemblyName>WeaveLoader</AssemblyName>
|
||||
<Description>WeaveLoader launcher - injects the mod loader runtime into Minecraft Legacy Edition</Description>
|
||||
<Version>1.0.0</Version>
|
||||
<OutputPath>..\build</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Build the C++ runtime DLL via CMake before the launcher -->
|
||||
<Target Name="BuildNativeRuntime" BeforeTargets="Build">
|
||||
<Message Importance="high" Text="[WeaveLoader] Configuring C++ runtime with CMake..." />
|
||||
<Exec Command="cmake -B "$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime\build" -S "$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime" -A x64"
|
||||
WorkingDirectory="$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime" />
|
||||
<Message Importance="high" Text="[WeaveLoader] Building C++ runtime..." />
|
||||
<Exec Command="cmake --build "$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime\build" --config $(Configuration)"
|
||||
WorkingDirectory="$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime" />
|
||||
</Target>
|
||||
|
||||
<!-- Copy the runtimeconfig.json to the build output -->
|
||||
<Target Name="CopyRuntimeConfig" AfterTargets="Build">
|
||||
<Copy SourceFiles="$(MSBuildThisFileDirectory)..\WeaveLoader.Core\WeaveLoader.Core.runtimeconfig.json"
|
||||
DestinationFolder="$(OutputPath)"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user