mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-05-30 01:24:30 +00:00
rework(docs): better, more readable docs
This commit is contained in:
48
docs/API_REFERENCE.md
Normal file
48
docs/API_REFERENCE.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# API Reference (Short)
|
||||
|
||||
## Registry.Block
|
||||
|
||||
```csharp
|
||||
RegisteredBlock Register(string id, BlockProperties properties)
|
||||
```
|
||||
|
||||
Creates a block and its inventory item.
|
||||
|
||||
## Registry.Item
|
||||
|
||||
```csharp
|
||||
RegisteredItem Register(string id, ItemProperties properties)
|
||||
```
|
||||
|
||||
Creates a new item.
|
||||
|
||||
## Registry.Recipe
|
||||
|
||||
```csharp
|
||||
void AddFurnace(string inputId, string outputId, float xp)
|
||||
```
|
||||
|
||||
Adds a furnace recipe.
|
||||
|
||||
## GameEvents
|
||||
|
||||
```csharp
|
||||
event EventHandler<BlockBreakEventArgs> OnBlockBreak;
|
||||
event EventHandler<BlockPlaceEventArgs> OnBlockPlace;
|
||||
event EventHandler<ChatEventArgs> OnChat;
|
||||
event EventHandler<EntitySpawnEventArgs> OnEntitySpawn;
|
||||
event EventHandler<PlayerJoinEventArgs> OnPlayerJoin;
|
||||
```
|
||||
|
||||
## Logger
|
||||
|
||||
```csharp
|
||||
Logger.Debug(string message)
|
||||
Logger.Info(string message)
|
||||
Logger.Warning(string message)
|
||||
Logger.Error(string message)
|
||||
```
|
||||
|
||||
## Identifier
|
||||
|
||||
Namespaced IDs use `"namespace:path"` (for example, `"mymod:ruby_ore"`). If no namespace is provided, `"minecraft"` is assumed.
|
||||
28
docs/BUILDING.md
Normal file
28
docs/BUILDING.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Building
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Visual Studio 2022+ with C++ Desktop Development and .NET 8 workloads
|
||||
- CMake 3.24+
|
||||
- .NET 8 SDK
|
||||
|
||||
## Build steps
|
||||
|
||||
Build everything from the repo root:
|
||||
|
||||
```bash
|
||||
dotnet build WeaveLoader.sln -c Debug
|
||||
```
|
||||
|
||||
Outputs go to `build/`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The launcher builds the native runtime automatically.
|
||||
- If you only need the runtime DLL, you can build it directly:
|
||||
|
||||
```bash
|
||||
cd WeaveLoaderRuntime
|
||||
cmake -B build -A x64
|
||||
cmake --build build --config Release
|
||||
```
|
||||
37
docs/INTERNALS.md
Normal file
37
docs/INTERNALS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Internals
|
||||
|
||||
This file covers how Weave Loader works under the hood.
|
||||
|
||||
## Symbol resolution
|
||||
|
||||
The runtime reads the game's PDB file and resolves function addresses by mangled C++ names. Resolved RVAs are added to the module base to build callable function pointers.
|
||||
|
||||
We use [raw_pdb](https://github.com/MolecularMatters/raw_pdb) to parse PDBs without the DIA SDK.
|
||||
|
||||
## Texture atlas merging
|
||||
|
||||
1. Mod textures are found under `mods/*/assets/<namespace>/textures/`.
|
||||
2. The vanilla `terrain.png` and `items.png` are loaded.
|
||||
3. Empty cells are detected and mod textures are placed into them.
|
||||
4. The merged atlas is written to `mods/ModLoader/generated/`.
|
||||
5. File open hooks redirect the game to the merged atlases during init.
|
||||
|
||||
## Model asset overlay
|
||||
|
||||
When the game requests `assets/<namespace>/models/...`, WeaveLoader redirects to the mod's matching file under `mods/*/assets/<namespace>/models/`.
|
||||
|
||||
## String table injection
|
||||
|
||||
The runtime locates the game's string table and appends mod strings directly. This avoids relying on inlined string lookup calls.
|
||||
|
||||
## Crash handler
|
||||
|
||||
A vectored exception handler writes crash reports with register dumps, stack traces, and loaded modules. It also skips `__debugbreak()` asserts so the game can continue running.
|
||||
|
||||
## ID ranges
|
||||
|
||||
| Type | Numeric Range | Notes |
|
||||
|------|--------------|-------|
|
||||
| Blocks (Tiles) | 174 -- 255 | 82 slots; maps to TileItem in `Item::items[]` |
|
||||
| Items | 3000 -- 31999 | Constructor param = `numericId - 256` |
|
||||
| Entities | 1000 -- 9999 | Reserved for mod entities |
|
||||
195
docs/MIXINS.md
Normal file
195
docs/MIXINS.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Mixins
|
||||
|
||||
This guide explains how to hook game functions using Weave Loader mixins.
|
||||
|
||||
## What is a mixin?
|
||||
|
||||
A mixin lets you attach your code to an existing game function. You can run code:
|
||||
|
||||
- At the start of a function (head)
|
||||
- At the end of a function (tail)
|
||||
|
||||
## 1. Register a mixin class
|
||||
|
||||
Create a file named `weave.mixins.json` in your mod folder:
|
||||
|
||||
```json
|
||||
{
|
||||
"package": "ExampleMod.Mixins",
|
||||
"mixins": [
|
||||
"CreeperExplosionMixin"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Fields (currently supported):
|
||||
|
||||
- `package`: C# namespace that contains your mixin classes
|
||||
- `mixins`: Class names under that namespace
|
||||
- `client`, `server`: Optional lists (loaded the same way as `mixins`)
|
||||
|
||||
Fields (parsed but not enforced yet):
|
||||
|
||||
- `required`
|
||||
- `injectors.defaultRequire`
|
||||
|
||||
Place the file next to your mod DLL in `build/mods/<YourMod>/`.
|
||||
|
||||
## 2. Create a mixin class
|
||||
|
||||
### Target class
|
||||
|
||||
```csharp
|
||||
using WeaveLoader.API.Mixins;
|
||||
|
||||
[Mixin("Creeper")]
|
||||
public class CreeperExplosionMixin
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
The string in `[Mixin("...")]` is the class name used in `mapping.json`.
|
||||
|
||||
### Inject at the start (head)
|
||||
|
||||
```csharp
|
||||
using WeaveLoader.API.Mixins;
|
||||
|
||||
[Mixin("Creeper")]
|
||||
public class CreeperExplosionMixin
|
||||
{
|
||||
[Inject("tick", At.Head)]
|
||||
private static void OnTick(MixinContext ctx)
|
||||
{
|
||||
// Code runs before Creeper::tick
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Inject at the end (tail)
|
||||
|
||||
```csharp
|
||||
using WeaveLoader.API.Mixins;
|
||||
|
||||
[Mixin("Creeper")]
|
||||
public class CreeperExplosionMixin
|
||||
{
|
||||
[Inject("tick", At.Tail)]
|
||||
private static void OnTick(MixinContext ctx)
|
||||
{
|
||||
// Code runs after Creeper::tick
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Using arguments and return values
|
||||
|
||||
Mixin methods must be `static` and return `void`. They can take either:
|
||||
|
||||
- No parameters
|
||||
- One parameter: `MixinContext`
|
||||
|
||||
`MixinContext` lets you read or change arguments and return values.
|
||||
|
||||
```csharp
|
||||
using WeaveLoader.API.Mixins;
|
||||
|
||||
[Mixin("Item")]
|
||||
public class ItemMixin
|
||||
{
|
||||
[Inject("use", At.Head)]
|
||||
private static void OnUse(MixinContext ctx)
|
||||
{
|
||||
var arg0 = ctx.GetArg(0);
|
||||
var arg1 = ctx.GetArg(1);
|
||||
// use ctx.SetArg(index, value) to modify
|
||||
}
|
||||
|
||||
[Inject("use", At.Tail)]
|
||||
private static void AfterUse(MixinContext ctx)
|
||||
{
|
||||
var ret = ctx.GetReturn();
|
||||
// use ctx.SetReturn(value) to modify
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Accessing the instance (`this`)
|
||||
|
||||
For non-static methods, the instance pointer is always arg 0:
|
||||
|
||||
```csharp
|
||||
var self = ctx.GetArg(0);
|
||||
```
|
||||
|
||||
For static methods, `ctx.ThisPtr` will be `0`.
|
||||
|
||||
## 5. Canceling the original method
|
||||
|
||||
You can skip the original game method at head injection:
|
||||
|
||||
```csharp
|
||||
using WeaveLoader.API.Mixins;
|
||||
using WeaveLoader.API.Native;
|
||||
|
||||
[Mixin("SomeClass")]
|
||||
public class ExampleCancel
|
||||
{
|
||||
[Inject("doThing", At.Head)]
|
||||
private static void OnDoThing(MixinContext ctx)
|
||||
{
|
||||
ctx.SetReturn(new NativeRet { Type = NativeType.I32, Value = 0 });
|
||||
ctx.Cancel();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Canceling is only effective at the head. Tail injections run after the original method.
|
||||
|
||||
## 6. Picking the right method name
|
||||
|
||||
`InjectAttribute` takes either:
|
||||
|
||||
- A method name (`"tick"`) which becomes `Class::tick`
|
||||
- A full name (`"Creeper::tick"`) from `mapping.json`
|
||||
|
||||
If you have overloads, use the exact `fullName` from `mapping.json`.
|
||||
|
||||
## 7. Confirm it is hooked
|
||||
|
||||
When a mixin loads, you should see logs like:
|
||||
|
||||
```
|
||||
Processing mixin: ExampleMod.Mixins.CreeperExplosionMixin -> Creeper
|
||||
HookRegistry: hooked Creeper::tick
|
||||
Mixin hook installed: Creeper::tick (Tail)
|
||||
```
|
||||
|
||||
If you do not see these logs:
|
||||
|
||||
- Check `weave.mixins.json` is next to the mod DLL
|
||||
- Check the namespace in `package`
|
||||
- Check the class name and method name match the PDB mapping
|
||||
- Check the mod loaded at all
|
||||
|
||||
## 8. Common issues
|
||||
|
||||
- **Wrong class or method name**: PDB names are case-sensitive.
|
||||
- **Crashes when accessing args**: Make sure the method signature matches the game method exactly.
|
||||
- **Mixins not running**: Verify you see the “hook installed” log.
|
||||
|
||||
## 9. Logging
|
||||
|
||||
Use `Logger.Debug` inside mixins while testing:
|
||||
|
||||
```csharp
|
||||
Logger.Debug("Creeper mixin tick");
|
||||
```
|
||||
|
||||
Remove or reduce logs for release builds to avoid overhead.
|
||||
|
||||
## 10. Limits and not supported yet
|
||||
|
||||
- Hooks support up to 6 arguments.
|
||||
- Overwrite mixins are not supported yet.
|
||||
- Invoker mixins are not supported yet.
|
||||
129
docs/MODDING.md
Normal file
129
docs/MODDING.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Modding Guide
|
||||
|
||||
## Create a mod
|
||||
|
||||
Create a .NET 8 class library:
|
||||
|
||||
```bash
|
||||
dotnet new classlib -n MyMod --framework net8.0
|
||||
```
|
||||
|
||||
Reference the API:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\WeaveLoader.API\\WeaveLoader.API.csproj" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
Send the output to the mods folder:
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\\build\\mods\\$(AssemblyName)</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
## Minimal mod
|
||||
|
||||
```csharp
|
||||
using WeaveLoader.API;
|
||||
using WeaveLoader.API.Block;
|
||||
using WeaveLoader.API.Item;
|
||||
using WeaveLoader.API.Recipe;
|
||||
using WeaveLoader.API.Events;
|
||||
|
||||
[Mod("mymod", Name = "My Mod", Version = "1.0.0", Author = "You")]
|
||||
public class MyMod : IMod
|
||||
{
|
||||
public void OnInitialize()
|
||||
{
|
||||
var oreBlock = Registry.Block.Register("mymod:example_ore",
|
||||
new BlockProperties()
|
||||
.Material(MaterialType.Stone)
|
||||
.Hardness(3.0f)
|
||||
.Resistance(15.0f)
|
||||
.Sound(SoundType.Stone)
|
||||
.Icon("mymod:block/example_ore")
|
||||
.InCreativeTab(CreativeTab.BuildingBlocks)
|
||||
.Name(Text.Translatable("block.mymod.example_ore")));
|
||||
|
||||
var gem = Registry.Item.Register("mymod:example_gem",
|
||||
new ItemProperties()
|
||||
.MaxStackSize(64)
|
||||
.Icon("mymod:item/example_gem")
|
||||
.InCreativeTab(CreativeTab.Materials)
|
||||
.Name(Text.Translatable("item.mymod.example_gem")));
|
||||
|
||||
Registry.Recipe.AddFurnace("mymod:example_ore", "mymod:example_gem", 1.0f);
|
||||
|
||||
GameEvents.OnBlockBreak += (_, args) =>
|
||||
{
|
||||
if (args.BlockId == oreBlock.NumericId)
|
||||
Logger.Info("Player broke example ore!");
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Assets
|
||||
|
||||
Textures go under `assets/<namespace>/textures/`:
|
||||
|
||||
```
|
||||
MyMod/
|
||||
├── assets/
|
||||
│ └── mymod/
|
||||
│ └── textures/
|
||||
│ ├── block/
|
||||
│ │ └── example_ore.png
|
||||
│ └── item/
|
||||
│ └── example_gem.png
|
||||
├── MyMod.cs
|
||||
└── MyMod.csproj
|
||||
```
|
||||
|
||||
Models go under `assets/<namespace>/models/`:
|
||||
|
||||
```
|
||||
MyMod/
|
||||
├── assets/
|
||||
│ └── mymod/
|
||||
│ └── models/
|
||||
│ ├── block/
|
||||
│ │ └── example_ore.json
|
||||
│ ├── item/
|
||||
│ │ └── example_gem.json
|
||||
│ └── entity/
|
||||
│ └── example_entity.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Build and run
|
||||
|
||||
```bash
|
||||
dotnet build MyMod.csproj
|
||||
```
|
||||
|
||||
Run `build/WeaveLoader.exe` to launch with your mod.
|
||||
|
||||
## Mod lifecycle
|
||||
|
||||
| Phase | Method | When | Use for |
|
||||
|-------|--------|------|---------|
|
||||
| PreInit | `OnPreInit()` | Before vanilla static constructors | Early configuration |
|
||||
| Init | `OnInitialize()` | After vanilla registries are set up | Registering blocks, items, recipes, events |
|
||||
| PostInit | `OnPostInitialize()` | After `Minecraft::init` completes | Cross-mod interactions, late setup |
|
||||
| Tick | `OnTick()` | Every game tick | Per-frame logic |
|
||||
| Shutdown | `OnShutdown()` | When the game exits | Cleanup, saving state |
|
||||
|
||||
Each phase is wrapped in try/catch so one mod failure does not crash others.
|
||||
|
||||
## Localization
|
||||
|
||||
Use `Text.Translatable("item.mymod.example_gem")` for localized names or
|
||||
`Text.Literal("Example Gem")` for fixed text.
|
||||
|
||||
Language files live at `assets/<namespace>/lang/<locale>.lang` using `key=value` lines.
|
||||
By default, WeaveLoader uses the game's selected language.
|
||||
14
docs/PROJECT_STRUCTURE.md
Normal file
14
docs/PROJECT_STRUCTURE.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Project Structure
|
||||
|
||||
```
|
||||
ModLoader/
|
||||
├── WeaveLoader.Launcher/ # C# launcher executable
|
||||
├── WeaveLoaderRuntime/ # C++ DLL injected into the game
|
||||
├── WeaveLoader.Core/ # C# mod discovery and lifecycle
|
||||
├── WeaveLoader.API/ # C# public API for mod authors
|
||||
├── ExampleMod/ # Sample mod
|
||||
├── build/ # Shared build output
|
||||
├── WeaveLoader.sln
|
||||
├── README.md
|
||||
└── CONTRIBUTING.md
|
||||
```
|
||||
Reference in New Issue
Block a user