Calling The Game's Code
There are multiple ways of interacting with Dalamud-offered services.
All examples here require the corresponding [PluginService]s to be present in
your Plugin; see
SamplePlugin/Plugin.cs
for an example.
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:
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:
public class GameFunctions {
private delegate byte IsQuestCompletedDelegate(ushort questId);
[Signature("E8 ?? ?? ?? ?? 41 88 84 2C")]
private readonly IsQuestCompletedDelegate? _isQuestCompleted = null;
public GameFunctions() {
Plugin.GameInteropProvider.InitializeFromAttributes(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 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 game interop systems and specifies the signature that identifies the
function we're interested in.
Then, the class's constructor has a call to
IGameInteropProvider#InitializeFromAttributes. 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.