diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8b4fc2e..4f517f3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,9 +1,6 @@ name: Lint -on: - pull_request: - branches: - - main +on: [push, pull_request] permissions: checks: write @@ -13,10 +10,19 @@ jobs: name: Run linters runs-on: ubuntu-latest steps: - - name: Check out Git repository + - name: Checkout uses: actions/checkout@v3 - - name: Run linters - uses: wearerequired/lint-action@v2 + - name: Setup pnpm + uses: pnpm/action-setup@v2 with: - eslint: true - eslint_extensions: js,jsx,ts,tsx,md,mdx + version: 8 + run_install: false + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Run lint + run: pnpm run lint-ci diff --git a/docs/faq/development.mdx b/docs/faq/development.mdx index e1852a0..b7ed4b1 100644 --- a/docs/faq/development.mdx +++ b/docs/faq/development.mdx @@ -2,6 +2,8 @@ sidebar_position: 5 --- +{/* lint disable maximum-heading-length */} + # Development ```mdx-code-block diff --git a/docs/faq/reverse-engineering.mdx b/docs/faq/reverse-engineering.mdx index af4f52c..fa242f0 100644 --- a/docs/faq/reverse-engineering.mdx +++ b/docs/faq/reverse-engineering.mdx @@ -2,6 +2,8 @@ sidebar_position: 6 --- +{/* lint disable maximum-heading-length */} + # Reverse Engineering ```mdx-code-block diff --git a/docs/plugin-development/how-tos/AddonEventManager.md b/docs/plugin-development/how-tos/AddonEventManager.md index a54f9ae..363382a 100644 --- a/docs/plugin-development/how-tos/AddonEventManager.md +++ b/docs/plugin-development/how-tos/AddonEventManager.md @@ -1,11 +1,17 @@ + + # AddonEventManager -This service provides a manager for adding and removing custom events from the native game ui. -These events are tracked and managed by Dalamud and automatically removed if your plugin unloads or if the addon they are attached to is unloaded. +This service provides a manager for adding and removing custom events from the +native game ui. These events are tracked and managed by Dalamud and +automatically removed if your plugin unloads or if the addon they are attached +to is unloaded. -When adding events you will be given a handle to that event, you'll need that handle to manually unregister that event. +When adding events you will be given a handle to that event, you'll need that +handle to manually unregister that event. ## Provided Interface + ```cs public interface IAddonEventManager { @@ -22,21 +28,27 @@ public interface IAddonEventManager ``` ## Node and Components -*You must register a `Node` for events, you can not register a `Component`, attempting to register the address of a `Component` will result in a crash.* + +_You must register a `Node` for events, you can not register a `Component`, +attempting to register the address of a `Component` will result in a crash._ Valid Node types will have their type names ending in `Node`. -In the picture below, if you have a `AtkComponentButton*` you will likely want to access the `OwnerNode` property and use that node for registration. +In the picture below, if you have a `AtkComponentButton*` you will likely want +to access the `OwnerNode` property and use that node for registration. ![image](https://github.com/MidoriKami/dalamud-docs/assets/9083275/e4c00a43-67e4-4164-8338-6862e4e12182) ## Registering Events -When registering events you need to provide the pointer to the addon (AtkUnitBase*), -and a pointer to an node (AtkResNode*), along with what event you want to trigger it, and your delegate. + +When registering events you need to provide the pointer to the addon +(AtkUnitBase\*), and a pointer to an node (AtkResNode\*), along with what event +you want to trigger it, and your delegate. You may need to modify the nodeflags to allow that node to respond to events. -This system pairs very well with the IAddonLifecycle service, to allow you to register events when an addon is setup easily. +This system pairs very well with the IAddonLifecycle service, to allow you to +register events when an addon is setup easily. ```cs // Register listener for MonsterNote setup @@ -58,7 +70,7 @@ private void OnPostSetup(AddonEvent type, AddonArgs args) private void TooltipHandler(AddonEventType type, IntPtr addon, IntPtr node) { var addonId = ((AtkUnitBase*) addon)->ID; - + switch (type) { case AddonEventType.MouseOver: @@ -72,17 +84,23 @@ private void TooltipHandler(AddonEventType type, IntPtr addon, IntPtr node) } ``` -The result of this example code is the text node containing the class of the current selected hunting log will show a tooltip when moused over. +The result of this example code is the text node containing the class of the +current selected hunting log will show a tooltip when moused over. ![image](https://github.com/goatcorp/dalamud-docs/assets/9083275/0b859b62-085c-4879-9316-2136232a3fc5) ## Unregistering Events -For events added to non-persistent addons (addons that setup and finalize when opened and closed) they do not need to be manually removed -as the system will automatically untrack and remove any registered events when an addon closes. -For any events added to persistent addons (addons that are always present, such as "_BagWidget", "NamePlate"), you need to save the event handle returned from AddEvent and use that to unregister your events. +For events added to non-persistent addons (addons that setup and finalize when +opened and closed) they do not need to be manually removed as the system will +automatically untrack and remove any registered events when an addon closes. -All registered events are automatically removed from native when your plugin unloads. +For any events added to persistent addons (addons that are always present, such +as "\_BagWidget", "NamePlate"), you need to save the event handle returned from +AddEvent and use that to unregister your events. + +All registered events are automatically removed from native when your plugin +unloads. Here is an example of saving and removing your event handles. @@ -115,9 +133,11 @@ private void UnregisterEvents() ``` ## Cursor API + This service also provides access to functions to change the game cursor icon. -This is useful to indicate to your users that an element can be clicked on to trigger an on-click event. +This is useful to indicate to your users that an element can be clicked on to +trigger an on-click event. ```cs // Register listener for MonsterNote setup @@ -131,7 +151,7 @@ private void OnPostSetup(AddonEvent type, AddonArgs args) var targetNode = addon->GetNodeById(22); targetNode->NodeFlags |= NodeFlags.EmitsEvents | NodeFlags.RespondToMouse | NodeFlags.HasCollision; - + EventManager.AddEvent((nint) addon, (nint) targetNode, AddonEventType.MouseOver, OnClickHandler); EventManager.AddEvent((nint) addon, (nint) targetNode, AddonEventType.MouseOut, OnClickHandler); EventManager.AddEvent((nint) addon, (nint) targetNode, AddonEventType.MouseClick, OnClickHandler); @@ -148,7 +168,7 @@ private void OnClickHandler(AddonEventType type, IntPtr addon, IntPtr node) case AddonEventType.MouseOut: EventManager.ResetCursor(); break; - + case AddonEventType.MouseClick: // Do custom click logic here. break; @@ -156,11 +176,16 @@ private void OnClickHandler(AddonEventType type, IntPtr addon, IntPtr node) } ``` -The result of this example code is the text node containing the class of the current selecting hunting log will trigger your function when clicked on, and changes the game cursor to indicate it is clickable. +The result of this example code is the text node containing the class of the +current selecting hunting log will trigger your function when clicked on, and +changes the game cursor to indicate it is clickable. ![image](https://github.com/goatcorp/dalamud-docs/assets/9083275/78566abc-1f03-41cf-8973-dc3d3186b717) ## Logging -Whenever events are added or removed they are logged to `AddonEventManager` under the verbose logging channel, this can be very useful to see what is happening to your events + +Whenever events are added or removed they are logged to `AddonEventManager` +under the verbose logging channel, this can be very useful to see what is +happening to your events ![image](https://github.com/goatcorp/dalamud-docs/assets/9083275/77cb00ed-e5ea-4219-82fa-ce22b92a41ad) diff --git a/docs/plugin-development/how-tos/AddonLifecycle.md b/docs/plugin-development/how-tos/AddonLifecycle.md index cbade80..138bdbd 100644 --- a/docs/plugin-development/how-tos/AddonLifecycle.md +++ b/docs/plugin-development/how-tos/AddonLifecycle.md @@ -1,16 +1,23 @@ + + # AddonLifecycle -This service provides you very easy access to various states and state changes for `Addons`. +This service provides you very easy access to various states and state changes +for `Addons`. + +The primary goal of this service is to make it easy to modify the native ui, or +get data from addons without needing to reverse engineer and subsequently hook +each and every addon that you need to interact with. -The primary goal of this service is to make it easy to modify the native ui, -or get data from addons without needing to reverse engineer and subsequently hook each and every addon that you need to interact with. +Sometimes an addon doesn't implement its own Draw or other functions, which +makes it quite challenging to hook something that will trigger when you need to +trigger your code. -Sometimes an addon doesn't implement its own Draw or other functions, -which makes it quite challenging to hook something that will trigger when you need to trigger your code. +This service allows you to listen for any addons events by name, no addresses +required. -This service allows you to listen for any addons events by name, no addresses required. +## Provided Interface -### Provided Interface ```cs public interface IAddonLifecycle { @@ -19,7 +26,7 @@ public interface IAddonLifecycle void RegisterListener(AddonEvent eventType, IEnumerable addonNames, AddonEventDelegate handler); void RegisterListener(AddonEvent eventType, string addonName, AddonEventDelegate handler); void RegisterListener(AddonEvent eventType, AddonEventDelegate handler); - + void UnregisterListener(AddonEvent eventType, IEnumerable addonNames, [Optional] AddonEventDelegate handler); void UnregisterListener(AddonEvent eventType, string addonName, [Optional] AddonEventDelegate handler); void UnregisterListener(AddonEvent eventType, [Optional] AddonEventDelegate handler); @@ -27,11 +34,14 @@ public interface IAddonLifecycle void UnregisterListener(params AddonEventDelegate[] handlers); } ``` -### Registering Events -You register for event by specifying which event you want, and then optionally specifiying by name which addons you want to listen for. -If no addon names are provided then the service will send you notifications for **all** addons. -It's generally recommended to specify which addons you want. +## Registering Events + +You register for event by specifying which event you want, and then optionally +specifiying by name which addons you want to listen for. + +If no addon names are provided then the service will send you notifications for +**all** addons. It's generally recommended to specify which addons you want. ```cs AddonLifecycle.RegisterListener(AddonEvent.PreDraw, "FieldMarker", OnPreDraw); @@ -39,34 +49,42 @@ AddonLifecycle.RegisterListener(AddonEvent.PostUpdate, "FieldMarker", OnPostUpda AddonLifecycle.RegisterListener(AddonEvent.PostDraw, new[] { "Character", "FieldMarker", "NamePlate" }, OnPostDraw); ``` -### Unregistering Events -You have a couple options for unregistering, you can unregister using the same syntax as you registered with, -or you can unregister the functions directly without needing to specify which events or addons you want to unregister. +## Unregistering Events + +You have a couple options for unregistering, you can unregister using the same +syntax as you registered with, or you can unregister the functions directly +without needing to specify which events or addons you want to unregister. ```cs AddonLifecycle.UnregisterListener(AddonEvent.PostDraw, new[] { "Character", "FieldMarker", "NamePlate" }, OnPostDraw); AddonLifecycle.UnregisterListener(OnPreDraw, OnPostUpdate); ``` -### Available Events +## Available Events + This service provides several events you can listen for: Setup, Update, Draw, RequestedUpdate, Refresh, Finalize -Each of these events are used in a Pre or Post listener, -for example if you want to be notified ***right before*** and addon is about to do a refresh, you can subscribe to **PreRefresh**. +Each of these events are used in a Pre or Post listener, for example if you want +to be notified **_right before_** and addon is about to do a refresh, you can +subscribe to **PreRefresh**. -*Note: There is no PostFinalize event provided. That would be after the addon has been freed from memory. -If you have a valid usecase for needing a PostFinalize event let us know.* +_Note: There is no PostFinalize event provided. That would be after the addon +has been freed from memory. If you have a valid usecase for needing a +PostFinalize event let us know._ -### Available Data -When your delegate is called, it is passed an AddonArgs object, -this can be cast to a more specific object to get the argument data used in the original call. +## Available Data -Each of the events have their own specific implementation of AddonArgs with the argument data avaialble. -If you are unsure what type of AddonArgs you have, there's a `Type` property you can check. +When your delegate is called, it is passed an AddonArgs object, this can be cast +to a more specific object to get the argument data used in the original call. + +Each of the events have their own specific implementation of AddonArgs with the +argument data available. If you are unsure what type of AddonArgs you have, +there's a `Type` property you can check. For example: + ```cs private void OnPostSetup(AddonEvent type, AddonArgs args) { diff --git a/docs/plugin-development/interacting-with-the-game.md b/docs/plugin-development/interacting-with-the-game.md index 4049975..e5b5c8c 100644 --- a/docs/plugin-development/interacting-with-the-game.md +++ b/docs/plugin-development/interacting-with-the-game.md @@ -1,39 +1,53 @@ + + # 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 +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 +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 +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. +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: @@ -44,12 +58,14 @@ public unsafe bool IsPlayerMentor() { } ``` -This method will grab the instance of `PlayerState` from Client Structs, and call the appropriate check. +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: +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 { @@ -61,11 +77,11 @@ public class GameFunctions { public GameFunctions() { SignatureHelper.Initialise(this); } - + public bool IsQuestCompleted(ushort questId) { - if (this._isQuestCompleted == null) + if (this._isQuestCompleted == null) throw new InvalidOperationException("IsQuestCompleted signature wasn't found!"); - + return this._isQuestCompleted(questId) > 0; } } @@ -73,157 +89,182 @@ public class GameFunctions { 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 +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: +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. +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. +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 +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. +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. +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 +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. +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. +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. +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. +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`: +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: +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 { @@ -231,39 +272,45 @@ public class MySiggedHook : IDisposable { [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. +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/plugin-submission.md b/docs/plugin-development/plugin-submission.md index b435bb7..bfa7e12 100644 --- a/docs/plugin-development/plugin-submission.md +++ b/docs/plugin-development/plugin-submission.md @@ -109,6 +109,7 @@ We hope that this helps clarify how plugins land in the official Dalamud plugin listing. If you have any questions or think that something here could be clarified, feel free to reach out. -[^1]: Technically, this is still possible, but you would need NSA-grade -datacenters and a lot of time (at the moment, probably hundreds of years) to -break the hash algorithm Git uses. +[^1]: + Technically, this is still possible, but you would need NSA-grade + datacenters and a lot of time (at the moment, probably hundreds of years) to + break the hash algorithm Git uses. diff --git a/docs/plugin-development/restrictions.md b/docs/plugin-development/restrictions.md index 6c85f34..23e5f02 100644 --- a/docs/plugin-development/restrictions.md +++ b/docs/plugin-development/restrictions.md @@ -1,3 +1,5 @@ + + # Restrictions ## What am I allowed to do in my plugin? @@ -13,8 +15,8 @@ Please make sure that, as much as possible: - your plugin does not interact with the game servers in a way that is: - automatic, as in polling data or making requests without direct interaction from the user - - outside of specification, as in allowing the player to do or submit things to - the server that would not be possible by normal means + - outside of specification, as in allowing the player to do or submit things + to the server that would not be possible by normal means - your plugin does not augment, alter, or interfere with combat, unless it only provides information about your own party or alliance members that is otherwise available, but represents said information differently. @@ -75,39 +77,45 @@ Discord and ask for more details if required. ## I don't like plugin X, can you block it or delete it? -While there are various plugins from custom repositories that do violate our rules, -there is very little we can do as a project to prevent this. -- If we were to introduce blocks or bans, they would be trivial to circumvent. It - would be minutes of effort to do so. - Dalamud is open source software - everything we do is public, there is no secret code - or private tools and everything can be reproduced. This has obvious drawbacks, but - it allows anyone to inspect what code runs on their machine, and this openness has - ultimately led to the ecosystem of amazing plugins and extensions we have today. -- In our official channels, we try to avoid helping anyone that tries to make plugins - that may violate our rules or ethics. This is not always possible, as we are not immune - to deception or politics. -- Most ready-to-use APIs offered by Dalamud itself are read-only and don't allow changing - the actual state of the game. Plugins run on your PC like any other program and are not - "sandboxed", so they have free reign to interact with the game in any way they please - with their own code. -- Limiting what plugins can do is a very difficult technical problem, and might lead to - a lot of very popular plugins not being able to do what they do anymore for the limitations - to be worthwhile. - We don't want to introduce artificial limitations that break existing plugins that have become - essential to the community that uses Dalamud. - -If you have a problem with a plugin on the official repository, we recommend reading [our plugin submission guidelines](plugin-submission) to get some background -on how we decide if a plugin should be on the official repo. -If you still think that the plugin should not be there, feel free to reach out to a member of our team via Discord. +While there are various plugins from custom repositories that do violate our +rules, there is very little we can do as a project to prevent this. + +- If we were to introduce blocks or bans, they would be trivial to circumvent. + It would be minutes of effort to do so. Dalamud is open source software - + everything we do is public, there is no secret code or private tools and + everything can be reproduced. This has obvious drawbacks, but it allows anyone + to inspect what code runs on their machine, and this openness has ultimately + led to the ecosystem of amazing plugins and extensions we have today. +- In our official channels, we try to avoid helping anyone that tries to make + plugins that may violate our rules or ethics. This is not always possible, as + we are not immune to deception or politics. +- Most ready-to-use APIs offered by Dalamud itself are read-only and don't allow + changing the actual state of the game. Plugins run on your PC like any other + program and are not "sandboxed", so they have free reign to interact with the + game in any way they please with their own code. +- Limiting what plugins can do is a very difficult technical problem, and might + lead to a lot of very popular plugins not being able to do what they do + anymore for the limitations to be worthwhile. We don't want to introduce + artificial limitations that break existing plugins that have become essential + to the community that uses Dalamud. + +If you have a problem with a plugin on the official repository, we recommend +reading [our plugin submission guidelines](plugin-submission) to get some +background on how we decide if a plugin should be on the official repo. If you +still think that the plugin should not be there, feel free to reach out to a +member of our team via Discord. ## I like custom repo plugin X, why is it not on the official repo? There are various reasons for why a plugin might not be on the official repo. + - It doesn't conform to our [rules and guidelines](plugin-submission). -- It only works in conjunction with another custom repo plugin. This doesn't necessarily mean - that the plugin in question, or the plugin it depends on, violate our rules and guidelines. -- The developer decided not to submit it to the official repo. We don't just "take" plugins and put - them there, plugin developers need to go out of their way to submit their work to our repository. - Some may prefer to run their own infrastructure, for various reasons - a main one being that updates - on the official repo may take a while to be processed, as they are code-reviewed by the plugin approval - team. +- It only works in conjunction with another custom repo plugin. This doesn't + necessarily mean that the plugin in question, or the plugin it depends on, + violate our rules and guidelines. +- The developer decided not to submit it to the official repo. We don't just + "take" plugins and put them there, plugin developers need to go out of their + way to submit their work to our repository. Some may prefer to run their own + infrastructure, for various reasons - a main one being that updates on the + official repo may take a while to be processed, as they are code-reviewed by + the plugin approval team. diff --git a/docs/plugin-development/reverse-engineering.md b/docs/plugin-development/reverse-engineering.md index b92ad38..9a3bfdc 100644 --- a/docs/plugin-development/reverse-engineering.md +++ b/docs/plugin-development/reverse-engineering.md @@ -1,48 +1,66 @@ + + # 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. +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... +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 +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. +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 @@ -50,13 +68,17 @@ program in question. ### 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. +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. +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/ @@ -64,52 +86,68 @@ dynamic analysis tools into the ClientStructs database, allowing devs to move fa ## 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. +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. +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. +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. +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. +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). -[^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. +[^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. diff --git a/docs/versions/index.md b/docs/versions/index.md index 8c03430..6692a76 100644 --- a/docs/versions/index.md +++ b/docs/versions/index.md @@ -56,10 +56,11 @@ level. | 4 | 6.0.0.17? | Patch 5.57hf? | .NET 5.0 | [2021-07-12](https://github.com/goatcorp/Dalamud/commit/0cb35619d2907d3cb65fce9be7dd08410fe31b7d) | | 3 | 5.2.3.5? | Patch 5.45? | .NET 4.7.2 | [2021-04-01](https://github.com/goatcorp/Dalamud/commit/9751a9fed2e948cb4f114da107a7b55416c287bf) | | 2 | 5.1.1.2? | Patch 5.4? | .NET 4.7.2 | [2020-12-08](https://github.com/goatcorp/Dalamud/commit/04b83f95336ec0ff006febf29b0af0afe2636a65) | -| 1 | 4.9.8.2[^1] | Patch 5.25? | .NET 4.7.2 | [2020-06-11](https://github.com/goatcorp/Dalamud/commit/ad93b6324f921b11c7e7dbd4565023697512c0bf) | +| 1 | 4.9.8.2[^1] | Patch 5.25? | .NET 4.7.2 | [2020-06-11](https://github.com/goatcorp/Dalamud/commit/ad93b6324f921b11c7e7dbd4565023697512c0bf) | -[^1]: This was the first commit to introduce the `DALAMUD_API_LEVEL` constant. -The more you know! ✨ +[^1]: + This was the first commit to introduce the `DALAMUD_API_LEVEL` constant. The + more you know! ✨ diff --git a/docs/versions/v9.md b/docs/versions/v9.md index 496d4fc..ad53f21 100644 --- a/docs/versions/v9.md +++ b/docs/versions/v9.md @@ -1,6 +1,9 @@ # What's New in Dalamud v9 -Dalamud v9 is the next major version of Dalamud, to be released together with Patch 6.5. This is a high-level overview of changes. You can see a code diff of all of these changes [here.](https://github.com/goatcorp/dalamud/compare/master...v9) +Dalamud v9 is the next major version of Dalamud, to be released together with +Patch 6.5. This is a high-level overview of changes. You can see a code diff of +all of these changes +[here.](https://github.com/goatcorp/dalamud/compare/master...v9) ## Key Information @@ -12,289 +15,414 @@ Dalamud v9 is the next major version of Dalamud, to be released together with Pa - **.NET Version:** .NET 7.0 - .NET 8 is due for official release in November 2023. Due to release timing, we will be targeting .NET 7.0 for v9, and will upgrade to .NET 8 in a future - release, when we can confirm that it is stable on all platforms(Windows, Wine, OS X). + release, when we can confirm that it is stable on all platforms (Windows, + Wine, OS X). ## New Features -* The console(`/xllog`) has been redone, and now lets you create filters that can filter to specific plugins. -* Added a new plugin-specific logging class that you can receive as a service instance via the `IPluginLog` type. - * Static functions in `PluginLog` have been deprecated, and we recommend that you move to the new service. They were slow and unreliable, due to having to look up the calling plugin via reflection, and won’t support new filtering features in the improved console. - * We are planning to remove the static functions in a future API cycle. -* Extended `DtrBarEntry` to now allow for a tooltip, and an OnClick event, to add even more functionality to your DTR bar entries. -* Added `IAddonLifecycle` and `IAddonEventManager` services which simplify access to addons greatly. -* Added comprehensive new service to acquire ImGui textures from game icons/textures via `ITextureProvider`, detailed below. -* Added various new functions to manipulate colors for UI work in `Dalamud.Interface.ColorHelpers`. + +- The console (`/xllog`) has been redone, and now lets you create filters that + can filter to specific plugins. +- Added a new plugin-specific logging class that you can receive as a service + instance via the `IPluginLog` type. + - Static functions in `PluginLog` have been deprecated, and we recommend that + you move to the new service. They were slow and unreliable, due to having to + look up the calling plugin via reflection, and won’t support new filtering + features in the improved console. + - We are planning to remove the static functions in a future API cycle. +- Extended `DtrBarEntry` to now allow for a tooltip, and an OnClick event, to + add even more functionality to your DTR bar entries. +- Added `IAddonLifecycle` and `IAddonEventManager` services which simplify + access to addons greatly. +- Added comprehensive new service to acquire ImGui textures from game + icons/textures via `ITextureProvider`, detailed below. +- Added various new functions to manipulate colors for UI work in + `Dalamud.Interface.ColorHelpers`. ## Major Changes + ### General -* All services must be used via interfaces, their implementations are now private. - * This allows us to create per-plugin implementations of services, which will be able to safely dispose of plugin-specific resources - something we have never been able to do before reliably - and opens up the possibility of creating mocks that allow for testing outside of the game. - * We can also now build features that rely on detecting what plugin is using a service, like plugin-scoped logs and reliable command lists. - * To migrate, you only have to change the type used to the interface equivalent in the `Dalamud.Plugin.Services` namespace(e.g. `DataManager` => `IDataManager`) -* `IDataManager.ClientOpcodes` and `IDataManager.ServerOpcodes` have been removed. Dalamud systems that use opcodes directly will now use hooks instead. We recommend any plugin that has been using these opcodes to switch to hooks. - + +- All services must be used via interfaces, their implementations are now + private. + - This allows us to create per-plugin implementations of services, which will + be able to safely dispose of plugin-specific resources - something we have + never been able to do before reliably - and opens up the possibility of + creating mocks that allow for testing outside of the game. + - We can also now build features that rely on detecting what plugin is using a + service, like plugin-scoped logs and reliable command lists. + - To migrate, you only have to change the type used to the interface + equivalent in the `Dalamud.Plugin.Services` namespace (e.g. `DataManager` => + `IDataManager`) +- `IDataManager.ClientOpcodes` and `IDataManager.ServerOpcodes` have been + removed. Dalamud systems that use opcodes directly will now use hooks instead. + We recommend any plugin that has been using these opcodes to switch to hooks. + ### UI and ImGui -* The `UiBuilder.OpenMainUi` event has been added, which you should subscribe to to open your plugin's "main interface", if applicable - * This is shown as a separate, highlighted button in the plugin installer, in addition to the already existing `OpenConfigUi` button -* Texture- and Icon-related functions on DataManager have been removed and are now available in a new service, `ITextureProvider`. - * API has been simplified in a major way and now properly supports high-resolution textures - * Icons and textures are now only loaded once globally when requesting them using this service, reducing memory usage - * Icons and textures are now automatically unloaded if not drawn for more than 2 seconds, and will be reloaded transparently if they are accessed again by your plugin - * An API that allows plugins to replace paths to tex files has been added. This means that you no longer manually have to implement this functionality to make your plugin compatible with e.g. UI mods. - * ITextureProvider is designed for ImGui specific use cases. Plugins that need to work with raw tex files should implement their own logic. -* All Dalamud APIs that interact with textures now return `IDalamudTextureWrap`, and `IDalamudTextureWrap` no longer inherits from the `TextureWrap` type in ImGuiScene. This has been done to decouple the public API from ImGuiScene, and to remove the need for plugins to reference it. - * To migrate, you merely need to switch the types used. The exposed API is exactly the same. - * `IDalamudTextureWrap.Size`(Vector2) has been added, finally. -* IDisposable ImGui-helpers have been moved to the main Dalamud assembly and are now accessible via the `Dalamud.Interface.Utility` namespace - * We recommend using these for **any new UI**, as they are much safer and reduce the chance of crashes caused by misbehaving UI code - + +- The `UiBuilder.OpenMainUi` event has been added, which you should subscribe to + to open your plugin's "main interface", if applicable + - This is shown as a separate, highlighted button in the plugin installer, in + addition to the already existing `OpenConfigUi` button +- Texture- and Icon-related functions on DataManager have been removed and are + now available in a new service, `ITextureProvider`. + - API has been simplified in a major way and now properly supports + high-resolution textures + - Icons and textures are now only loaded once globally when requesting them + using this service, reducing memory usage + - Icons and textures are now automatically unloaded if not drawn for more than + 2 seconds, and will be reloaded transparently if they are accessed again by + your plugin + - An API that allows plugins to replace paths to tex files has been added. + This means that you no longer manually have to implement this functionality + to make your plugin compatible with e.g. UI mods. + - ITextureProvider is designed for ImGui specific use cases. Plugins that need + to work with raw tex files should implement their own logic. +- All Dalamud APIs that interact with textures now return `IDalamudTextureWrap`, + and `IDalamudTextureWrap` no longer inherits from the `TextureWrap` type in + ImGuiScene. This has been done to decouple the public API from ImGuiScene, and + to remove the need for plugins to reference it. + - To migrate, you merely need to switch the types used. The exposed API is + exactly the same. + - `IDalamudTextureWrap.Size` (`Vector2`) has been added, finally. +- IDisposable ImGui-helpers have been moved to the main Dalamud assembly and are + now accessible via the `Dalamud.Interface.Utility` namespace + - We recommend using these for **any new UI**, as they are much safer and + reduce the chance of crashes caused by misbehaving UI code + ### Hooking -* Obsolete constructors for `Hook` and the static `Hook.FromX()` functions have been removed. - * Please use the equivalent functions in the `IGameInteropProvider` service - * `IGameInteropProvider.HookFromSignature()` has been added -* `SignatureHelper.Initialize()` has been removed. Please use `IGameInteropProvider.InitializeFromAttributes()`. + +- Obsolete constructors for `Hook` and the static `Hook.FromX()` functions + have been removed. + - Please use the equivalent functions in the `IGameInteropProvider` service + - `IGameInteropProvider.HookFromSignature()` has been added +- `SignatureHelper.Initialize()` has been removed. Please use + `IGameInteropProvider.InitializeFromAttributes()`. ## Minor Changes -* `Util.HttpClient` has been removed in favor of allowing plugins to manage their own HTTP lifecycles. - * You can use `Dalamud.Networking.Http.HappyEyeballsCallback` as your `SocketsHttpHandler.ConnectCallback` to enable improved IPv6 connection handling to dual-stack servers. -* `SeStringManager` has been removed. Please use `SeStringBuilder` instead. -* `ChatHandlers` has been removed. - * `ChatHandlers.IsAutoUpdateComplete` is now available as `DalamudPluginInterface.IsAutoUpdateComplete` - * `ChatHandlers.MakeItalics()` can be accessed via `SeStringBuilder` -* `Util.CopyTo()` has been removed, as it has been added to the standard library as `Stream.CopyTo()`. -* `DalamudPluginInterface.PluginNames` and `PluginInternalNames` have been replaced in favor of `InstalledPlugins`, which provides more context. -* Obsolete/non-functional icons in `FontAwesomeIcon` have been removed. -* `DataManager.IsDataReady` has been removed, as it is always true when plugins are loaded. -* `SeStringBuilder.AddItemLink()` now correctly adds a full item link, instead of only adding an `ItemPayload`. -* `Util.IsLinux()` has been changed to `Util.IsWine()`. `Util.GetHostPlatform()` has been added to get the actual platform the game is running on - this relies on a special env var that may not be present on all environments Dalamud can run under yet. -* The Serilog property `SourceContext` is no longer used for Dalamud systems. Plugins implementing their own logging systems should write their plugin internal name to the `Dalamud.PluginName` property instead, otherwise, **filtering in the new console will not work**. -* `UIBuilder.GposeActive` has been moved to `IClientState.IsGPosing`, and is now more reliable/will only be true if the user is actually in GPose. -* `IDalamudPlugin.Name` has been removed. This has not been shown anywhere in more than 2 years. -* Dev-Plugins without a manifest are no longer supported. Please have a manifest or use DalamudPackager! -* Most `FlyTextKind` enum members have been renamed, see commit [here](https://github.com/goatcorp/Dalamud/commit/4989e2b69b8ce23dbe01b8a6786267e6a0ed6ea2). + +- `Util.HttpClient` has been removed in favor of allowing plugins to manage + their own HTTP lifecycles. + - You can use `Dalamud.Networking.Http.HappyEyeballsCallback` as your + `SocketsHttpHandler.ConnectCallback` to enable improved IPv6 connection + handling to dual-stack servers. +- `SeStringManager` has been removed. Please use `SeStringBuilder` instead. +- `ChatHandlers` has been removed. + - `ChatHandlers.IsAutoUpdateComplete` is now available as + `DalamudPluginInterface.IsAutoUpdateComplete` + - `ChatHandlers.MakeItalics()` can be accessed via `SeStringBuilder` +- `Util.CopyTo()` has been removed, as it has been added to the standard library + as `Stream.CopyTo()`. +- `DalamudPluginInterface.PluginNames` and `PluginInternalNames` have been + replaced in favor of `InstalledPlugins`, which provides more context. +- Obsolete/non-functional icons in `FontAwesomeIcon` have been removed. +- `DataManager.IsDataReady` has been removed, as it is always true when plugins + are loaded. +- `SeStringBuilder.AddItemLink()` now correctly adds a full item link, instead + of only adding an `ItemPayload`. +- `Util.IsLinux()` has been changed to `Util.IsWine()`. `Util.GetHostPlatform()` + has been added to get the actual platform the game is running on - this relies + on a special env var that may not be present on all environments Dalamud can + run under yet. +- The Serilog property `SourceContext` is no longer used for Dalamud systems. + Plugins implementing their own logging systems should write their plugin + internal name to the `Dalamud.PluginName` property instead, otherwise, + **filtering in the new console will not work**. +- `UIBuilder.GposeActive` has been moved to `IClientState.IsGPosing`, and is now + more reliable/will only be true if the user is actually in GPose. +- `IDalamudPlugin.Name` has been removed. This has not been shown anywhere in + more than 2 years. +- Dev-Plugins without a manifest are no longer supported. Please have a manifest + or use DalamudPackager! +- Most `FlyTextKind` enum members have been renamed, see commit + [here](https://github.com/goatcorp/Dalamud/commit/4989e2b69b8ce23dbe01b8a6786267e6a0ed6ea2). ## Errata + These changes have been made after the official stabilization. -* `SigScanner` was mistakenly made internal, making it impossible for plugins to create their own scanners - * `SigScanner` was made public with the same API, and the internal service implementation was decoupled + +- `SigScanner` was mistakenly made internal, making it impossible for plugins to + create their own scanners + - `SigScanner` was made public with the same API, and the internal service + implementation was decoupled ## Contributors -We want to thank the following people for their contributions to Dalamud during this patch cycle: -* MidoriKami -* Haselnussbomber -* kalilistic -* Soreepeong -* nebel -* Caraxi -* Ottermandias -* Aireil + +We want to thank the following people for their contributions to Dalamud during +this patch cycle: + +- MidoriKami +- Haselnussbomber +- kalilistic +- Soreepeong +- nebel +- Caraxi +- Ottermandias +- Aireil ## FFXIVClientStructs changes -These are relevant changes made to FFXIVClientStructs, listed here for reference. We want to thank aers, Pohky, WilldWolf and the other FFXIVClientStructs contributors for their work. -From a593cb163e1c5d33b27d34df4d1ccc57d1e67643, as of commit 0af185ef155cf03f24c2dee8f50f3973a7d417aa: +These are relevant changes made to FFXIVClientStructs, listed here for +reference. We want to thank aers, Pohky, WildWolf and the other +FFXIVClientStructs contributors for their work. + +From `a593cb163e1c5d33b27d34df4d1ccc57d1e67643`, as of commit +`0af185ef155cf03f24c2dee8f50f3973a7d417aa`: + +`Client/Game/ActionManager.cs`: + +- Changed `UseAction` to take a `ulong` instead of `long` for `targetID` +- Changed `UseActionLocation` to take a `ulong` instead of `long` for `targetID` +- Changed `GetActionStatus` to take a `ulong` instead of `long` for `targetID` + +`Client/Game/ActionTimelineManager.cs`: + +- Added `Parent` character pointer +- Added `GetHeightAdjustActionTimelineRowId` + +`Client/Game/Character/Character.cs`: + +- Changed `ActionRecipientsObjectIdArray` to be `ulong` instead of `long` +- Added `EmoteController` +- Added `CalculateHeight` + +`Client/Game/Character/CharacterManager.cs`: + +- Changed `LookupBattleCharaByObjectId` to take a `uint` instead of `int` + +`Client/Game/Control/TargetSystem.cs`: + +- Changed GetCurrentTargetID to return `ulong` instead of `uint` +- Changed GameObjectArray.Objects to be `ulong` instead of `long` + +`Client/Game/InstanceContent/PublicContentDirector.cs`: + +- Added `HandleEnterContentInfoPacket` + +`Client/Game/InventoryManager.cs`: + +- Changed `MoveItemSlot` to use `ushort` instead of `uint` for slot + +`Client/Game/Object/GameObject.cs`: + +- Removed `long` operators + +`Client/Game/QuestManager.cs`: + +- Removed `Obsolete` fields and structs + +`Client/Game/RetainerManager.cs`: + +- Removed `Obsolete` fields and structs +- Changed `RetainerList.Retainer` to `Retainer` +- Changed functions from `RetainerList.Retainer` to `Retainer` + +`Client/Game/UI/Map.cs`: + +- Added `MarkerInfo.ShouldRender` field +- Removed obsolete fields and structs + +`Client/Graphics/Kernel/Notifier.cs`: + +- Changed namespace to match file location + +`Client/Graphics/Kernel/Texture.cs`: + +- Changed namespace to match file location -Client/Game/ActionManager.cs: - - Changed UseAction to take a ulong instead of long for targetID - - Changed UseActionLocation to take a ulong instead of long for targetID - - Changed GetActionStatus to take a ulong instead of long for targetID +`Client/Graphics/Scene/CharacterBase.cs`: -Client/Game/ActionTimelineManager.cs - - Added Parent character pointer - - Added GetHeightAdjustActionTimelineRowId +- Changed `ColorSetTextures` to `ColorTableTextures` +- Changed `ColorSetTexturesSpan` to `ColorTableTexturesSpan` -Client/Game/Character/Character.cs: - - Changed ActionRecipientsObjectIdArray to be ulong instead of long - - Added EmoteController - - Added CalculateHeight +`Client/System/Framework/Framework.cs`: -Client/Game/Character/CharacterManager.cs: - - Changed LookupBattleCharaByObjectId to take a uint instead of int +- Added `TaskManager` field -Client/Game/Control/TargetSystem.cs: - - Changed GetCurrentTargetID to return ulong instead of uint - - Changed GameObjectArray.Objects to be ulong instead of long +`Client/System/Resource/Handle/ResourceHandle.cs`: -Client/Game/InstanceContent/PublicContentDirector.cs: - - Added HandleEnterContentInfoPacket +- Added `Unknown0A` field +- Added `Expansion` field +- Added `UserData` field +- Added `LoadState` field +- Added `LoadIntoKernel` method +- Added `Load` method +- Added `GetUserData` method -Client/Game/InventoryManager.cs: - - Changed MoveItemSlot to use ushort instead of uint for slot +`Client/System/Resource/ResourceGraph.cs`: -Client/Game/Object/GameObject.cs: - - Removed long operators +- Changed `ResourceCategory` to be a `ushort` -Client/Game/QuestManager.cs: - - Removed Obsolete fields and structs - -Client/Game/RetainerManager.cs: - - Removed Obsolete fields and structs - - Changed RetainerList.Retainer to Retainer - - Changed functions from RetianerList.Retainer to Retainer - -Client/Game/UI/Map.cs: - - Added MarkerInfo.ShouldRender field - - Removed Obsolete fields and structs - -Client/Graphics/Kernel/Notifier.cs: - - Changed namespace to match file location - -Client/Graphics/Kernel/Texture.cs: - - Changed namespace to match file location - -Client/Graphics/Scene/CharacterBase.cs: - - Changed ColorSetTextures to ColorTableTextures - - Changed ColorSetTexturesSpan to ColorTableTexturesSpan - -Client/System/Framework/Framework.cs: - - Added TaskManager field +`Client/UI/AddonLookingForGroupDetail.cs`: -Client/System/Resource/Handle/ResourceHandle.cs: - - Added Unknown0A field - - Added Expansion field - - Added UserData field - - Added LoadState field - - Added LoadIntoKernel method - - Added Load method - - Added GetUserData method +- Added `RelayPartyFinderInfoButton` field +- Added `CategoryImageNode` field -Client/System/Resource/ResourceGraph.cs: - - Changed ResourceCategory to be a ushort +`Client/UI/AddonRecipeNote.cs`: -Client/UI/AddonLookingForGroupDetail.cs: - - Added RelayPartyFinderInfoButton field - - Added CategoryImageNode field +- Changed many Unk fields to `AtkTextNode*` -Client/UI/AddonRecipeNote.cs: - - Changed many Unk fields to AtkTextNode* +`Client/UI/AddonSalvageItemSelector.cs`: -Client/UI/AddonSalvageItemSelector.cs: - - Changed ItemsData to Items with a fixed size array +- Changed `ItemsData` to `Items` with a fixed size array -Client/UI/Agent/AgentContext.cs: - - Changed ContextMenu.EventParams to be a fixed array +`Client/UI/Agent/AgentContext.cs`: -Client/UI/Agent/AgentFriendList.cs: - - Added SelectedPlayerName, SelectedContentId and SelectedIndex fields +- Changed `ContextMenu.EventParams` to be a fixed array -Client/UI/Agent/AgentHudLayout.cs: - - Changed namespace to FFXIVClientStructs.FFXIV.Client.UI.Agent +`Client/UI/Agent/AgentFriendList.cs`: -Client/UI/Agent/AgentReadyCheck.cs: - - Changed ReadyCheckEntries from FixedArray to FixedSizeArray +- Added `SelectedPlayerName`, `SelectedContentId` and `SelectedIndex` fields -Client/UI/Agent/AgentRetainerList.cs: - - Changed Retainers to fixed array +`Client/UI/Agent/AgentHudLayout.cs`: -Client/UI/Agent/AgentSalvage.cs: - - Changed DesynthResult to DesynthResults and changed FixedArray to FixedSizeArray +- Changed namespace to `FFXIVClientStructs.FFXIV.Client.UI.Agent` -Client/UI/Info/InfoProxyCommonList.cs: - - Changed Data to CharData - - Changed ContentID to ulong - - Changed GetContentIDForEntry to return ulong - - Removed CharIndex field from InfoProxyCommonList - - Removed CharacterDict and CharacterArray from InfoProxyCommonList - - Moved OnlineStatus, MentorState, PartyStatus, DutyStatus to single OnlineStatus enum linked to bitflag of OnlineStatus.exd - - Added Sort field - - Added ExtraFlags field +`Client/UI/Agent/AgentReadyCheck.cs`: -Client/UI/Info/InfoProxyCatalogSearch.cs: - - Was InfoProxyItemSearch +- Changed `ReadyCheckEntries` from `FixedArray` to `FixedSizeArray` -Client/UI/Info/InfoProxyItemSearch.cs: - - Was InfoProxy11 - - Changed SelectedItemId to SearchItemId - - Changed GlobalItemId to ListingId - - Added RetainerListings - - Added RetainerListingsCount - - Added PlayerRetainers +`Client/UI/Agent/AgentRetainerList.cs`: -Client/UI/Misc/CharaViewPortrait.cs: - - Changed DirectionalLightingVerticalAngle and DirectionalLightingHorizontalAngle to be signed +- Changed `Retainers` to fixed array -Client/UI/Misc/ItemOrderModule.cs: - - Changed RetainerSorter to be a `StdMap>` - - Removed RetainerSorterCount +`Client/UI/Agent/AgentSalvage.cs`: -Client/UI/Misc/RaptureGearsetModule.cs: - - Changed Gearset to be a fixed array named Entries - - Changed IsValidGearset to return bool instead of byte - - Changed CreateGearset to return sbyte instead of uint - - Changed HasLinkedGlamourPlate to return bool instead of byte - - Changed IsGearsetLinkedWithBanner to HasLinkedBanner - - Changed GetBannerIndexByGearsetIndex to GetBannerIndex - - Changed SetBannerIndexForGearsetIndex to SetBannerIndex - - Added FindGearsetIDByName - - Added GearsetItem.Flags field - - Changed GearsetEntry.RightLeft to be named RingLeft - -Client/UI/Misc/RaptureMacroModule.cs: - - Changed Instance to be a method instead of a property - - Changed Individual and Shared to be fixed arrays - - Added RaptureTextModule and TextChecker fields - -Client/UI/Misc/RetainerCommentModule.cs: - - Changed Retainers to fixed array - - Changed SetComment to return void instead of void* - -Client/UI/Shell/RaptureShellModule.cs: - - Changed Instance to be a method instead of a property - -Client/UI/UIModule.cs: - - Changed GetUIInputData to return UIInputData* - -Component/GUI/AtkComponentDragDrop.cs: - - Added AtkDragDropInterface field - -Component/GUI/AtkComponentListItemRenderer.cs: - - Added AtkDragDropInterface field - -Component/GUI/AtkDragDropManager.cs: - - Added DragDrop1, DragDrop2 and DragDropS pointer fields - - Added IsDragging, ReclickToDrop, MouseMoved and IsNotDiscarding bool fields - -Component/GUI/AtkLinkedList.cs: - - Obsoleted AtkLinkedList in favor of StdLinkedList +- Changed `DesynthResult` to `DesynthResults` and changed `FixedArray` to + `FixedSizeArray` -Component/GUI/AtkModule.cs: - - Added AtkTextureResourceManager field - - Changed DefaultTextureVersion to AtkTextureResourceManager.DefaultTextureVersion - - Changed ExdModule to AtkTextureResourceManager.ExdModule - -Component/GUI/AtkStage.cs: - - Added AtkTextureResourceManager field +`Client/UI/Info/InfoProxyCommonList.cs`: -Component/GUI/AtkTextureResource.cs: - - Changed Count_1 to Count - - Changed Count_2 to Version - - Added ShareCount field - -Component/GUI/AtkUnitBase.cs: - - Added ShowHideFlags field - - Changed Show and Hide paramenters to match the game - -Component/GUI/AtkUnitList.cs: - - Changed AtkUnitEntries to fixed array named Entries; - - Changed Count to ushort - -STD/Pair.cs: - - Added Deconstruct method to StdPair +- Changed `Data` to `CharData` +- Changed `ContentID` to `ulong` +- Changed `GetContentIDForEntry` to return `ulong` +- Removed `CharIndex` field from `InfoProxyCommonList` +- Removed `CharacterDict` and `CharacterArray` from `InfoProxyCommonList` +- Moved `OnlineStatus`, `MentorState`, `PartyStatus`, `DutyStatus` to single + `OnlineStatus` enum linked to bitflag of `OnlineStatus.exd` +- Added `Sort` field +- Added `ExtraFlags` field + +`Client/UI/Info/InfoProxyCatalogSearch.cs`: + +- Was `InfoProxyItemSearch` + +`Client/UI/Info/InfoProxyItemSearch.cs`: + +- Was `InfoProxy11` +- Changed `SelectedItemId` to `SearchItemId` +- Changed `GlobalItemId` to `ListingId` +- Added `RetainerListings` +- Added `RetainerListingsCount` +- Added `PlayerRetainers` + +`Client/UI/Misc/CharaViewPortrait.cs`: + +- Changed `DirectionalLightingVerticalAngle` and + `DirectionalLightingHorizontalAngle` to be signed + +`Client/UI/Misc/ItemOrderModule.cs`: + +- Changed `RetainerSorter` to be a + `StdMap>` +- Removed `RetainerSorterCount` + +`Client/UI/Misc/RaptureGearsetModule.cs`: + +- Changed `Gearset` to be a fixed array named `Entries` +- Changed `IsValidGearset` to return `bool` instead of `byte` +- Changed `CreateGearset` to return `sbyte` instead of `uint` +- Changed `HasLinkedGlamourPlate` to return `bool` instead of `byte` +- Changed `IsGearsetLinkedWithBanner` to `HasLinkedBanner` +- Changed `GetBannerIndexByGearsetIndex` to `GetBannerIndex` +- Changed `SetBannerIndexForGearsetIndex` to `SetBannerIndex` +- Added `FindGearsetIDByName` +- Added `GearsetItem.Flags` field +- Changed `GearsetEntry.RightLeft` to be named `RingLeft` + +`Client/UI/Misc/RaptureMacroModule.cs`: + +- Changed `Instance` to be a method instead of a property +- Changed `Individual` and `Shared` to be fixed arrays +- Added `RaptureTextModule` and `TextChecker` fields + +`Client/UI/Misc/RetainerCommentModule.cs`: + +- Changed `Retainers` to fixed array +- Changed `SetComment` to return `void` instead of `void*` + +`Client/UI/Shell/RaptureShellModule.cs`: + +- Changed `Instance` to be a method instead of a property + +`Client/UI/UIModule.cs`: + +- Changed `GetUIInputData` to return `UIInputData*` + +`Component/GUI/AtkComponentDragDrop.cs`: + +- Added `AtkDragDropInterface` field + +`Component/GUI/AtkComponentListItemRenderer.cs`: + +- Added `AtkDragDropInterface` field + +`Component/GUI/AtkDragDropManager.cs`: + +- Added `DragDrop1`, `DragDrop2` and `DragDropS` pointer fields +- Added `IsDragging`, `ReclickToDrop`, `MouseMoved` and `IsNotDiscarding` bool + fields + +`Component/GUI/AtkLinkedList.cs`: + +- Obsoleted `AtkLinkedList` in favor of `StdLinkedList` + +`Component/GUI/AtkModule.cs`: + +- Added `AtkTextureResourceManager` field +- Changed `DefaultTextureVersion` to + `AtkTextureResourceManager.DefaultTextureVersion` +- Changed `ExdModule` to `AtkTextureResourceManager.ExdModule` + +`Component/GUI/AtkStage.cs`: + +- Added `AtkTextureResourceManager` field + +`Component/GUI/AtkTextureResource.cs`: + +- Changed `Count_1` to `Count` +- Changed `Count_2` to `Version` +- Added `ShareCount` field + +`Component/GUI/AtkUnitBase.cs`: + +- Added `ShowHideFlags` field +- Changed `Show` and `Hide` paramenters to match the game + +`Component/GUI/AtkUnitList.cs`: + +- Changed `AtkUnitEntries` to fixed array named `Entries`; +- Changed `Count` to `ushort` + +`STD/Pair.cs`: + +- Added `Deconstruct` method to `StdPair` New Files Added: - - Component/GUI/AtkDragDropInterface.cs - - Component/GUI/AtkTextureResourceManager.cs - - Client/UI/UIInputData.cs - - Client/UI/Agent/AgentScenarioTree.cs - - Client/UI/Agent/AgentMycBattleAreaInfo.cs - - Client/UI/Agent/AgentMiragePrismPrismItemDetail.cs - - Client/UI/Agent/AgentItemDetail.cs - - Client/System/Framework/TaskManager.cs - - Client/System/Framework/Task.cs - - Client/System/Framework/RootTask.cs - - Application/Network/WorkDefinitions/EnterContentInfo.cs - - Client/Game/Control/EmoteController.cs - - Client/Game/Conditions.cs - -Changed All Agents to use the new AgentGettersGenerator + +- `Component/GUI/AtkDragDropInterface.cs` +- `Component/GUI/AtkTextureResourceManager.cs` +- `Client/UI/UIInputData.cs` +- `Client/UI/Agent/AgentScenarioTree.cs` +- `Client/UI/Agent/AgentMycBattleAreaInfo.cs` +- `Client/UI/Agent/AgentMiragePrismPrismItemDetail.cs` +- `Client/UI/Agent/AgentItemDetail.cs` +- `Client/System/Framework/TaskManager.cs` +- `Client/System/Framework/Task.cs` +- `Client/System/Framework/RootTask.cs` +- `Application/Network/WorkDefinitions/EnterContentInfo.cs` +- `Client/Game/Control/EmoteController.cs` +- `Client/Game/Conditions.cs` + +Changed All Agents to use the new `AgentGettersGenerator` Changed All VTable to vtbl diff --git a/news/2023-09-21-dalamud-v9-stabilization.md b/news/2023-09-21-dalamud-v9-stabilization.md index ae64e90..a6e1029 100644 --- a/news/2023-09-21-dalamud-v9-stabilization.md +++ b/news/2023-09-21-dalamud-v9-stabilization.md @@ -6,53 +6,68 @@ authors: Hey everyone, -I'm happy to announce that we are publishing the final list of new features and breaking changes -for Dalamud API9, scheduled to be released together with Patch 6.5 in early october. +I'm happy to announce that we are publishing the final list of new features and +breaking changes for Dalamud API9, scheduled to be released together with Patch +6.5 in early october. -This release features a lot of behind-the-scenes changes that make Dalamud more stable, and -hopefully give us the chance to improve our APIs further in the future. +This release features a lot of behind-the-scenes changes that make Dalamud more +stable, and hopefully give us the chance to improve our APIs further in the +future. Please [check the full list of changes here](https://dalamud.dev/versions/v9/). ### Services are now interfaces -One major change that you will encounter is that all of the services you've already been using are now -accessed via interfaces, and not directly via the implementing type. -
-We decided to go this route, as it gives us the opportunity to slowly switch all services over to plugin-specific services - which know what -resources are allocated to what plugin, and allows us to track and dispose of them in case something goes wrong - -and can enhance some Dalamud features, like the command list or logging APIs, with reliable context about what -plugin is using them. -It can also open the door to improving automated testing, and making it easier for us to improve APIs and functionality gradually. +One major change that you will encounter is that all of the services you've +already been using are now accessed via interfaces, and not directly via the +implementing type.
We decided to go this route, as it gives us the +opportunity to slowly switch all services over to plugin-specific services - +which know what resources are allocated to what plugin, and allows us to track +and dispose of them in case something goes wrong - and can enhance some Dalamud +features, like the command list or logging APIs, with reliable context about +what plugin is using them. -To migrate, you most likely only have to **prefix the names** of all of the services you're using with I. That's it! +It can also open the door to improving automated testing, and making it easier +for us to improve APIs and functionality gradually. + +To migrate, you most likely only have to **prefix the names** of all of the +services you're using with I. That's it! ### A note about ImGui -I want to encourage everyone to have a look at the wonderful new ImRaii APIs we introduced into the main Dalamud assembly via `Dalamud.Interface.Utility`! -
-They make it a lot easier to prevent crashes your plugin may cause due to e.g. unhandled exceptions, and are just all-around -more convenient to use, in my opinion. You also will never again forget to pop a color, or that children always need to be ended. -
-There also are some other convenience APIs, for example, to interact with tables. -Another thing you might want to look into, if you aren't already, is the Dalamud WindowSystem - if you use it to create your ImGui -windows, they automatically gain a lot of nice UX features, such as integration into the game-native closing order via escape or controller, -and UI sounds. Your users will appreciate it, I'm sure! +I want to encourage everyone to have a look at the wonderful new ImRaii APIs we +introduced into the main Dalamud assembly via `Dalamud.Interface.Utility`!
+They make it a lot easier to prevent crashes your plugin may cause due to e.g. +unhandled exceptions, and are just all-around more convenient to use, in my +opinion. You also will never again forget to pop a color, or that children +always need to be ended.
There also are some other convenience APIs, for +example, to interact with tables. + +Another thing you might want to look into, if you aren't already, is the Dalamud +WindowSystem - if you use it to create your ImGui windows, they automatically +gain a lot of nice UX features, such as integration into the game-native closing +order via escape or controller, and UI sounds. Your users will appreciate it, +I'm sure! ### Testing your plugins -We decided to not open up a v9 branch on the plugin repository this time around. In the past, this has led to a lot of confusion and -messy testing periods, as we tended to end up with published versions of plugins on the new API level before the release of the -patch we were targeting, which did not end up being stable on that version of the game. -We, of course, encourage you to already start adjusting your plugin to the new APIs locally and in your development branches, to have -them ready to go when the patch hits! -To do this, just use the Dalamud branch switcher(`/xlbranch`) to switch to the v9 branch. +We decided to not open up a v9 branch on the plugin repository this time around. +In the past, this has led to a lot of confusion and messy testing periods, as we +tended to end up with published versions of plugins on the new API level before +the release of the patch we were targeting, which did not end up being stable on +that version of the game. + +We, of course, encourage you to already start adjusting your plugin to the new +APIs locally and in your development branches, to have them ready to go when the +patch hits! To do this, just use the Dalamud branch switcher(`/xlbranch`) to +switch to the v9 branch. ### Feedback -If you have any concerns about the changes we've made, or find that something is not working correctly, please reach out to us via -[GitHub issues](https://github.com/goatcorp/Dalamud/issues/new) or the [#dalamud-dev](https://discord.com/channels/581875019861328007/860813266468732938) + +If you have any concerns about the changes we've made, or find that something is +not working correctly, please reach out to us via +[GitHub issues](https://github.com/goatcorp/Dalamud/issues/new) or the +[#dalamud-dev](https://discord.com/channels/581875019861328007/860813266468732938) channel on our Discord server. -Thanks for sticking around, and have a great autumn! -
-~goat +Thanks for sticking around, and have a great autumn!
~goat diff --git a/package.json b/package.json index 19b2396..70c1da8 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "ci": "node ./ci-build.mjs", "prepare": "husky install", "lint": "eslint . --ext js,jsx,ts,tsx,md,mdx --fix", + "lint-ci": "eslint . --ext js,jsx,ts,tsx,md,mdx", "lint-staged": "lint-staged" }, "dependencies": {