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:
Jacobwasbeast
2026-03-06 23:31:18 -06:00
parent f5805fc740
commit fa195fdc2e
70 changed files with 336 additions and 336 deletions
+35
View File
@@ -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);
}
}
+61
View File
@@ -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;
}
}
+207
View File
@@ -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
}
+100
View File
@@ -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 &quot;$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime\build&quot; -S &quot;$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime&quot; -A x64"
WorkingDirectory="$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime" />
<Message Importance="high" Text="[WeaveLoader] Building C++ runtime..." />
<Exec Command="cmake --build &quot;$(MSBuildThisFileDirectory)..\WeaveLoaderRuntime\build&quot; --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>