diff --git a/.gitignore b/.gitignore index 175ead5..3625ad9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ dalamud-versions.json # Dependencies node_modules/ +.pnpm-store/ # Production build/ @@ -21,3 +22,6 @@ build/ npm-debug.log* yarn-debug.log* yarn-error.log* + +# Dev tools +.idea/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..48d22c6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM node:lts-alpine + +WORKDIR /app + +RUN apk --no-cache add git +RUN npm install --global pnpm + +EXPOSE 3000 \ No newline at end of file diff --git a/README.md b/README.md index 14a4059..245c4e3 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,15 @@ pnpm start This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. +### Docker Development + +If you want to use Docker for local dev, it's a bit cursed, but it works: + +```shell +docker compose run workspace pnpm install +docker compose up +``` + ## Build ```shell diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..26ac74f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + workspace: + pull_policy: "build" + build: + context: . + command: "pnpm exec docusaurus start --host 0.0.0.0" + ports: + - "3000:3000" + working_dir: "/app" + volumes: + - ".:/app:cached" diff --git a/docs/plugin-development/interacting-with-the-game.md b/docs/plugin-development/interacting-with-the-game.md new file mode 100644 index 0000000..4049975 --- /dev/null +++ b/docs/plugin-development/interacting-with-the-game.md @@ -0,0 +1,269 @@ +# Interacting With The Game + +Virtually any plugin will eventually want to interact with the game itself in some capacity, be it to respond to certain +events or to make decisions depending on what's happening in the game. This can take a few different paths, each +depending on a developer's intent and what they want to do. + +In general, developers are encouraged to use the following priority for game-based interactions: + +1. Where possible, use Dalamud provided APIs to interact with the game. These are generally the safest way to work with + the game, and provides stable APIs that won't change outside of API bumps. The Dalamud APIs also may wrap or + abstract away complex or annoying concepts into simpler to use patterns, as well as provide certain protections to + ensure that invalid data doesn't affect the game as much. These methods tend to be well-documented and are easy to + work with. +2. If the Dalamud API does not expose the behavior required, developers can consume the Client Structs project. This is + shipped with Dalamud and effectively allows plugins to use the game as a library. It provides relatively easy access + to the game's code and structures, although it will often use pointers and other unsafe code, so developers are + responsible for their own safety. +3. If the Client Structs project does not expose the requisite behavior, Dalamud offers escape hatches in the form of + the ability to work with raw memory and raw functions, allowing plugins to read information from undocumented + structures and call or hook methods using their signatures or other references. + +Most plugins will stay firmly in the realm of stages 1 and 2, with stage 3 being used for novel concepts that have yet +to be [reverse engineered](reverse-engineering.md) or fully understood. Where possible, plugin developers who do +reverse engineering as part of their plugin development are encouraged to contribute their findings back to the Client +Structs project, so that other developers may use them in the future. + +This document page won't explain how to use Dalamud-provided APIs to interact with the game, as they're all otherwise +documented and hopefully accessible. Instead, this document will focus on the more advanced concepts; that is, where +the Dalamud API doesn't quite reach. + +## I Want To Do That! + +Sometimes, it is beneficial to ask the game itself to do something, rather than doing it yourself. In effect, this +means using the game code as a library where any arbitrary function can be called and their results used freely. This +allows plugins to perform calculations in the same way the game does, or otherwise take actions in the game just as +it would if Dalamud weren't even there. + +For example, a plugin might want to check if the player is a mentor: + +```csharp +public unsafe bool IsPlayerMentor() { + var playerStatePtr = PlayerState.Instance(); + return playerStatePtr->IsMentor(); +} +``` + +This method will grab the instance of `PlayerState` from Client Structs, and call the appropriate check. + +### Making Your Own Delegates + +Sometimes, a method you're interested in might not be in Client Structs. When this happens, a developer can engage +their reverse engineering prowess to generate a signature, which they can then use to create their own delegate: + +```c# +public class GameFunctions { + private delegate byte IsQuestCompletedDelegate(ushort questId); + + [Signature("E8 ?? ?? ?? ?? 41 88 84 2C")] + private readonly IsQuestCompletedDelegate? _isQuestCompleted = null; + + public GameFunctions() { + SignatureHelper.Initialise(this); + } + + public bool IsQuestCompleted(ushort questId) { + if (this._isQuestCompleted == null) + throw new InvalidOperationException("IsQuestCompleted signature wasn't found!"); + + return this._isQuestCompleted(questId) > 0; + } +} +``` + +This is a lot of code, so let's break it down a bit. + +First, the developer declares a [delegate][delegate-doc] for the function they want to call. This informs the compiler +and the code of the return type (in this case, a `byte`), as well as the arguments of the function. This line alone is +purely declaratory, and has no impact other than definition. If a specific argument is a reference to an undocumented +pointer (or the developer simply doesn't care about accessing any data inside the struct target), the `nint` type will +often be used. + +Next, the developer declares a nullable *instance* of that delegate, with its default value set to `null`. This +instance is then marked with the `[Signature(string signature)]` attribute. This attribute is provided by Dalamud's +`SignatureHelper` class and specifies the signature that identifies the function we're interested in. + +Then, the class's constructor has a call to `SignatureHelper#Initialise`. This method will scan the referenced object +(in this case, `this`) and use reflection to find all class members with the `[Signature()]` tag. It will then +automatically resolve the signature and inject the proper pointer into that variable. If a signature was unable to be +resolved, the delegate instance will be set to `null` for handling by the developer. + +Lastly, the `IsQuestCompleted()` method is defined. This exists in "managed code" (so, in C#) and provides some ease +of use around the raw method. For example, our method will throw an exception if the delegate is null and will convert +the returned `byte` into a `bool`. These wrapper methods are generally often kept simple, but will also often hold +important safety or sanity checks to ensure that there's a clean bridge between C# and the game's native code. + +[delegate-doc]: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/ +[unmanaged-doc]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/unmanaged-types + +#### Another Way To Delegate + +While looking at some plugins, you may instead notice a pattern that looks slightly different: + +```csharp +[Signature("E8 ?? ?? ?? ?? 41 88 84 2C")] +private readonly delegate* unmanaged _isQuestCompletedDelegate; +``` + +This is a shorter (but arguably slightly more complex) way of addressing the same concept. Instead of having to declare +the delegate and other information ahead of time, all the information about the delegate's arguments and return value +is included up front in the `unmanaged<>` segment. The [`unmanaged`][unmanaged-doc] keyword means that this function is +not part of the plugin's C# code, but instead comes from a lower level. The part inside the `<>` denotes the function's +arguments and return type. The return type is always the *last* type in the list, and all others are argument types, in +the same order as the arguments. For example, `` is a function like +`MyFunction(uint someNumber, string someString)` that returns a `byte`. Everything else behaves as it does above, +including nullability. + +## Tell Me When That Happens! + +A plugin may wish to be informed of a certain event happening in the game. If an event or appropriate +callback does not exist within Dalamud, there are a few strategies that plugins may employ to be informed of that +event. + +### Polling + +Perhaps the simplest (albeit likely not the most _efficient_) way of being informed of a specific change is to just +watch for that change. For example, a plugin that wants to watch for a change to the player's health can use something +similar to the following snippet: + +```csharp +public class HealthWatcher : IDisposable { + private int? _lastHealth; + + public HealthWatcher() { + Services.Framework.Update += this.OnFrameworkTick; + } + + public void Dispose() { + // Remember to unregister any events you create! + Services.Framework.Update -= this.OnFrameworkTick; + } + + private void OnFrameworkTick() { + var player = Services.ClientState.LocalPlayer; + + if (player == null) return; // Player is not logged in, nothing we can do. + if (player.CurrentHp == this._lastHealth) return; + + this._lastHealth = currentHealth; + PluginLog.Information("The player's health has updated to {health}.", currentHealth); + } +} +``` + +The above snippet creates an event handler that runs once per framework tick (once every frame). In each frame, +the `OnFrameworkTick()` method will check that a player exists, and compares their HP to a cached value. If the +player's HP differs from the cached value, it will dispatch a message to the Plugin Log. + +:::tip + +It is always a good idea to unregister your events when you're done with them! The above snippet does this through the +`Dispose()` method, which is intended to be called by whatever created this method. + +Failing to unregister events when they're no longer necessary (or, at the very least, on plugin unload) means that code +will *still be called*, and may cause unexpected behavior. As a rule of thumb, for every event you subscribe to with +`+=`, you need to have a `-=` somewhere else. + +::: + +Of course, the above snippet and concept can be adapted freely. Plugins can watch for events by checking something +every second if that better suits their requirements, and the check code can be (almost) anything: devs can read from +Client Structs provided APIs, call game methods, or any sort of calculation that's necessary. + +### Hooking Functions + +Sometimes, though, it may be undesirable to run code every frame. This may be because something happens relatively +rarely, or there isn't a good way to poll for a specific thing happening. When this is the case, a plugin can set up a +"hook". When a plugin creates a hook against a method in the game's code, that hook will be called *instead of* the +game's original function, allowing a plugin to observe, mutate, or even cancel the execution of that method. + +:::warning + +It is important to note that hooking is a *highly invasive* operation! You are substituting out the game's code for +your own, which requires certain levels of care to be taken. For example, if the code inside your hook throws an +exception, you will most likely crash the game. Be sure you are properly handling/managing exceptions that your code +may raise. + +In most cases, hooks are also *blocking* and will prevent the game from executing until they return. Ensure that any +code inside a hook is reasonably performant and won't cause unnecessary delays. + +::: + +Dalamud provides everything necessary for a plugin to create a hook, making the affair pretty simple. For example, a +plugin that wants to be informed when any macro changes might hook RaptureMacroModule's `SetSavePendingFlag`: + +```csharp +public class MyHook : IDisposable { + private delegate void SetSavePendingDelegate(RaptureMacroModule* self, byte needsSave, uint set); + + private readonly Hook? _macroUpdateHook; + + public MyHook() { + var macroUpdateFPtr = RaptureMacroModule.Addresses.SetSavePendingFlag.Value; + this._macroUpdateHook = Hook.FromAddress((nint) macroUpdateFPtr, this.DetourSetSavePending); + this._macroUpdateHook.Enable(); + } + + public void Dispose() { + this._macroUpdateHook.Dispose(); + } + + private nint DetourSetSavePending(RaptureMacroModule* self, byte needsSave, uint set) { + PluginLog.Information("A macro save happened!"); + + try { + // your plugin logic goes here. + } catch (Exception ex) { + PluginLog.Error(ex, "An error occured when handling a macro save event."); + } + + return this._macroUpdateHook.Original(self, needsSave, set); + } +} +``` + +This can also be done with a direct signature via `SignatureHelper`, if the function being hooked is not within Client +Structs: + +```csharp +public class MySiggedHook : IDisposable { + private delegate nint SetSavePendingDelegate(RaptureMacroModule* self, byte needsSave, uint set); + + [Signature("45 85 C0 75 04 88 51 3D", DetourName = nameof(DetourSetSavePending))] + private Hook? _macroUpdateHook; + + public MyHook() { + this._macroUpdateHook?.Enable(); + } + + public void Dispose() { + this._macroUpdateHook?.Dispose(); + } + + private nint DetourSetSavePending(RaptureMacroModule* self, byte needsSave, uint set) { + PluginLog.Information("A macro save happened!"); + + try { + // your plugin logic goes here. + } catch (Exception ex) { + PluginLog.Error(ex, "An error occured when handling a macro save event."); + } + + return this._macroUpdateHook!.Original(self, needsSave, set); + } +} +``` + +Both of these examples more or less follow the same pattern, with only a few semantic differences depending on how the +actual hook is created. In all cases, however, the `delegate` representing the method in question must be defined +properly. This delegate *must* have the expected return type, as well as any expected arguments, and the detour method +*must* match the delegate appropriately. For information about what delegates are and how they work, scroll back up. + +Like polling, hooks must be properly disposed when they are no longer needed. If they are not, the detour function will +continue to run in place of the hooked function and may cause problems or confusing behavior. There have been many +cases where confused plugin devs asked for help only to realize that their old hooks were still in effect! + +Because multiple plugins may hook a single method (or one plugin may hook the same method multiple times!), it's +generally best practice to not modify arguments or interrupt the execution flow. While there are many valid exceptions +to this rule, it is important to be aware that other hooks may be present, and may run before or after the hook you +create. diff --git a/docs/plugin-development/reverse-engineering.md b/docs/plugin-development/reverse-engineering.md new file mode 100644 index 0000000..b92ad38 --- /dev/null +++ b/docs/plugin-development/reverse-engineering.md @@ -0,0 +1,115 @@ +# Reverse Engineering (For Plugin Devs) + +Reverse engineering an application is difficult. Reverse engineering a massive game like Final Fantasy XIV is a lot +more difficult. There are a lot of moving parts, and opening a decompiler for the first time can be daunting to even +seasoned developers. It's very much out of scope of this documentation to teach you _how_ to reverse engineer the game, +but it can at least provide you some guidance to figure things out by yourself. + +## A Primer + +Fundamentally, Final Fantasy XIV is running on your machine, which means it's constantly running game code locally. +This code is responsible for communicating with the server, rendering the scene, drawing UI elements, determining what +state a player is in, and all sorts of other things. All of this happens within the game's process and memory space, +using instructions (generally, in assembly) found in the `ffxiv_dx11.exe` file located on your computer. + +There is, however, a problem here: we don't have Final Fantasy XIV's source code. The final program they ship us is very +stripped down, with very little useful information present in it. To this end, the community has created and maintained +a project known as [FFXIVClientStructs](https://github.com/aers/FFXIVClientStructs), which provides a general source of +information on the game internals and provides set of C# bindings that effectively allow plugins to use the game as a +library. However, this documentation is incomplete and plugin developers will inevitably need to reverse engineer the +game itself to discover new things. Which leads us cleanly to... + +## How do I reverse engineer the game? + +As alluded to before, a full guide on reverse engineering the game is impossible to write. Reverse engineering is a +very complex discipline and takes many years to master. Developers with prior experience with C/C++ may have a slight +head start, but ultimately it's still a difficult puzzle to solve. + +Within reverse engineering there are (at least) two major ways to interact with programs to figure out how they tick: +**Static Analysis** is when a developer reads the game's disassembled or decompiled source code, often using tools +specifically designed for the purpose; and **Dynamic Analysis**, where debuggers and memory editors are used to create +breakpoints and notify on changes to memory. Many developers will make heavy use of both of these mechanisms, as one +will often provide context to the other. + +### Static Analysis + +Static Analysis is the act of reading through a program's disassembled code, often using an interactive disassembler or +decompiler. There are many tools that help with this process, such as [Hex-Rays IDA][ida], [Ghidra][ghidra], +and [Binary Ninja][binja], though others exist. The vast majority of the Dalamud community will use either +IDA or Ghidra for their work, and most tooling that exists is built for one of these two tools. There's no functional +difference to either tool, so it's really up to the developer to choose which one they like more. + +Once you have your disassembler installed and working (you *did* read the manual and find some online tutorials for how +to navigate it, right?), it's time to load up `ffxiv_dx11.exe` and start poking around. At this point, most developers +will load in [a few data files](https://github.com/aers/FFXIVClientStructs/tree/main/ida) and use that to explore the +program in question. + +[ida]: https://hex-rays.com/ +[ghidra]: https://github.com/NationalSecurityAgency/ghidra +[binja]: https://binary.ninja/ + +### Dynamic Analysis + +Dynamic Analysis, unlike static analysis, is the act of inspecting what the code is doing *live*. This is generally +where tools like [Cheat Engine][cheat-engine], [x64dbg][x64dbg], and [ReClass.NET][reclass-net] shine. Developers will +often use these programs to find interesting memory addresses or place breakpoints on known data structures to see what +game code affects a certain location in memory. + +Certain tools, such as [pohky's XivReClassPlugin](https://github.com/pohky/XivReClassPlugin) will additionally tie some +dynamic analysis tools into the ClientStructs database, allowing devs to move faster with access to more information. + +[cheat-engine]: https://www.cheatengine.org/ +[x64dbg]: https://x64dbg.com/ +[reclass-net]: https://github.com/ReClassNET/ReClass.NET + +## On Functions, Offsets, and Signatures + +As alluded to before, Final Fantasy XIV is, in fact, a program. Programs, among other things, tend to follow an +execution flow starting from an *entrypoint* (the first function called), going to other functions, spanning across +threads, responding to user input, and all sorts of other things that make the game actually playable. The game will +call a certain function on a specific user action, or will use a function to fetch some relevant data or perform some +calculation for display in the UI or similar. All this to say: functions are perhaps one of the most critical concepts +to anyone exploring the game code. + +Functions exist in the program's memory space, starting at a specific *offset* from the program's base address. These +function offsets uniquely identify a specific function, and are generally expressed like `ffxiv_dx11.exe+4BC200`[^1] in +decompilers and other tools, though most developers will shorten this to just `4BC200` when talking to each other. +However, function offsets are not fixed. Every individual version of the game will change all function offsets, meaning +they are effectively useless for use in plugins directly. Instead, developers will use a different (and far more +stable) unique identifier: the signature. + +A *signature* is a specific series of bytes (expressed as a hexadecimal string) that uniquely identifies either the +start of a function (known as a *direct signature*) or a reference to a specific function (an *indirect signature*). +For example, take the signature `E8 ?? ?? ?? ?? 41 88 84 2C`. This string only exists once in the game's binary, and +uniquely identifies to a function that checks if the player has completed a specific quest. Because a signature refers +to a part of the binary, it is far more stable - a signature will only break if Square Enix changes the code the +signature represents, or adds new code that generates the same signature. It is not uncommon for signatures to last +multiple major patches. Signatures can either be made by hand by skilled developers, or a tool like +[Caraxi's SigMaker-x64](https://github.com/Caraxi/SigMaker-x64) can be used to automatically generate one. + +### Using Game Functions + +There are two major ways that a developer can use a function: creating a hook to intercept a function, or creating a +delegate to use that function from their plugin. + +Developers will use hooks when they want to intercept, modify, cancel, or otherwise act upon a function call. For +example, a plugin that wants to know when the user switches their status might hook an `UpdateStatus` function and take +some action on their own. Meanwhile, a developer that wants to just check if the player has finished a quest would +create a delegate pointing to an `IsQuestCompleted` function so that they could call it at their will. + +In both of these cases, the developer needs to know the argument list (and return type) of the function they are +interacting with, though static analysis tools will expose this information to the developer[^2]. In many cases, not +all arguments are known (and will generally be represented as `a3` or similar), or an argument may be a pointer to a +specific (and potentially unknown!) struct. + +## On Structures and Data Types + +Structures are just C structures. We port them into C# sometimes, and we use layouts. Pointer math is also a thing. We +use intptr/nint a lot. + +[^1]: Sometimes, you will also see `1404BC200`. This is the `/BASE` of `0x140000000` plus the function's offset, where +the base address is ~~a cool coincidence~~ a property of +[the compiler FFXIV uses](https://learn.microsoft.com/en-us/cpp/build/reference/base-base-address?view=msvc-170). +[^2]: Static analysis tools will use the +[x64 calling conventions](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention) to figure out what +arguments go where.