Compare commits

...

1118 commits

Author SHA1 Message Date
goat
dbe61a426e
Merge pull request #2607 from Critical-Impact/datashare
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
Use plugin internal name for DataShare tracking
2026-02-13 21:16:51 +01:00
wolfcomp
eb8e431267 Invert condition for assembly marshaling check
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
2026-02-13 21:38:37 +10:00
wolfcomp
87a0c69020 Fix missing args 2026-02-13 21:38:37 +10:00
wolfcomp
7752b0f918 Add SendPacket delegate to HookVerifier 2026-02-13 21:38:37 +10:00
wolfcomp
fd85a8d3bc Enhance HookVerifier to check marshaled types 2026-02-13 21:38:37 +10:00
AtmoOmen
f01971a7d7 fix Addon/AgentLifyCycle unreg
(cherry picked from commit 29e1715ff589b9dddf5a747b8655ea382e08cf58)
2026-02-13 20:59:39 +10:00
Haselnussbomber
1779d2681a Switch to CS in UnlockState
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 2s
2026-02-13 20:20:10 +10:00
Haselnussbomber
4651397808 Add support for Adventures to IUnlockState 2026-02-13 20:20:10 +10:00
Haselnussbomber
1ba18e54bf Add support for Titles to IUnlockState 2026-02-13 20:20:10 +10:00
Haselnussbomber
907b585b75 Add support for Achievements to IUnlockState 2026-02-13 20:20:10 +10:00
Critical Impact
b963e83cba Use effective working ID + internal name 2026-02-13 19:04:33 +10:00
goat
ec450da054
Merge pull request #2615 from goatcorp/csupdate-master
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
[master] Update ClientStructs
2026-02-12 22:12:23 +01:00
goat
990c4fd7e8
Merge pull request #2614 from reiichi001/add_gpu_to_crashinfo
Add GPU Info to Crash Handler
2026-02-12 22:11:25 +01:00
goat
b1b99bae13
Use correct variable name 2026-02-12 21:03:38 +01:00
goat
c4faf84a2d
Merge pull request #2616 from Glorou/FileOnSelect
Add a file selection changed event to FileDialog and it's manager
2026-02-12 20:48:39 +01:00
goat
abe27891c3
Tidy tidy 2026-02-12 20:45:23 +01:00
goat
1286dbd279
Merge branch 'master' into FileOnSelect 2026-02-12 20:38:23 +01:00
goat
0f14f5dab7
Merge pull request #2618 from marzent/troubleshoot-error
Fix troubleshooting json error on non-Windows platforms
2026-02-12 20:37:34 +01:00
github-actions[bot]
49e281e573 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2026-02-12 19:10:35 +00:00
marzent
0a070970a0 Fix troubleshooting json error on non-Windows platforms 2026-02-11 13:03:32 +01:00
balloon41
3de8c511bf
Update IPlayerState.cs (#2617)
Some checks failed
Tag Build / Tag Build (push) Successful in 5s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Fixed type in BaseRestedExperience summary
2026-02-10 19:37:58 +00:00
Glorou
e2297661f3 Added doc 2026-02-09 11:17:11 -05:00
Glorou
8285aa1014
Clean up FileDialogManager by removing unused code
Removed unused GetCurrentPath method and unnecessary using directives.
2026-02-09 10:50:46 -05:00
Glorou
256ab9dc9c add whitespace 2026-02-09 10:38:32 -05:00
Glorou
332d0d0cf5 Tweaked 2026-02-09 10:38:14 -05:00
Glorou
78912c1552 Init 2026-02-08 21:56:48 -05:00
goat
28e39ab9e2
Merge pull request #2594 from Haselnussbomber/troubleshooting-json
Some checks failed
Tag Build / Tag Build (push) Successful in 5s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Write troubleshooting to json file
2026-02-08 12:40:08 +01:00
goat
526e651750
Merge pull request #2602 from nebel/fix-search-tag-case
Properly lowercase for tags for plugin installer search
2026-02-08 12:38:03 +01:00
goat
4a33d34a3f
Merge pull request #2605 from Haselnussbomber/remove-peheader
Replace PeHeader with TerraFX
2026-02-08 12:37:48 +01:00
goat
0aa746e3bf
Merge pull request #2611 from goatcorp/csupdate-master
[master] Update ClientStructs
2026-02-08 12:37:30 +01:00
goat
5044aeda2b
Merge pull request #2613 from Haselnussbomber/update-netmonwidget
Update Network Monitor Widget
2026-02-08 12:37:04 +01:00
github-actions[bot]
34f13b3823 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2026-02-08 06:57:53 +00:00
Robert Baker
73447f205d
Add GPU Info to Crash Handler
I'm sure there's a better way to do this, but I also shouldn't be allowed to touch any cpp code.

This loops through all dxgi adapters based on example code I ripped from Microsoft and StackOverflow and dumps that into the crash log.

I'm hoping it doesn't make the window too tall, so if there's a better way to list only the display adapters that are unique, I'm all for it.
2026-02-07 20:58:55 -08:00
Haselnussbomber
0490a71990
Update Network Monitor Widget
- Separate checkboxes for up and down tracking
- Clarify tracking is for ZoneUp/ZoneDown
- Rephrase filter checkbox tooltip
2026-02-07 20:36:20 +01:00
goat
2b347eaff9
Merge pull request #2604 from pohky/patch-1
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
Fix null characters in BitmapCodecInfo strings
2026-02-07 13:41:13 +01:00
goat
f4defb735b
Merge pull request #2610 from Infiziert90/character-api-extended
Extend the Character class with CustomizeData
2026-02-07 13:40:56 +01:00
goat
4a75fe73df
Merge pull request #2612 from Soreepeong/fix/tex-from-file
Fix loading `.tex` file from filesystem
2026-02-07 13:39:47 +01:00
Soreepeong
b30a93816b Directly work with TexHeader and TextureBuffer 2026-02-06 18:59:02 +09:00
goaaats
7d2f12c6e2 build: 14.0.2.1
Some checks failed
Tag Build / Tag Build (push) Failing after 4s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2026-02-05 19:56:25 +01:00
goat
fc2220c4d9
Merge pull request #2606 from goatcorp/csupdate-master
[master] Update ClientStructs
2026-02-05 18:45:43 +01:00
github-actions[bot]
d3b9c75e50 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2026-02-05 17:35:21 +00:00
Infi
bcf4f396d6 - Adjust comments 2026-02-05 01:12:00 +01:00
Infi
dc77235c96 - Fix style cop warnings 2026-02-05 00:37:12 +01:00
Infi
d8a13a72aa - Add the CustomizeData struct to ICharacter
- API 15 note the Customize array
2026-02-05 00:20:39 +01:00
Critical Impact
9e18b843db Change column name 2026-02-02 22:32:33 +10:00
Critical Impact
dc783e0c2b Use plugin internal name for datashare tracking 2026-02-02 16:56:31 +10:00
Haselnussbomber
33a7cdefa8
Switch to CS in NetworkMonitorWidget (#2600)
Some checks failed
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
* Switch to CS in NetworkMonitorWidget

* Lazy-init the hooks

* Fix warnings
2026-01-31 12:57:11 -08:00
bleatbot
aa4ace976e
Update ClientStructs (#2598)
Co-authored-by: github-actions[bot] <noreply@github.com>
2026-01-31 12:56:36 -08:00
Haselnussbomber
252b7eeb9b
Replace PeHeader with TerraFX 2026-01-30 19:21:46 +01:00
pohky
73edaadbca
Fix null characters in BitmapCodecInfo strings 2026-01-30 14:51:29 +01:00
nebel
934df7da8a
Properly lowercase for tags for plugin installer search 2026-01-29 00:16:41 +09:00
goaaats
2b51a2a54e build: 14.0.2.0
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Failing after 4s
2026-01-28 00:35:23 +01:00
goaaats
5c250c1725 Make Framework.DelayTicks() deadlock-safe before the game has started 2026-01-27 23:43:51 +01:00
goaaats
470267a185 Restore NetworkMessageDirection enum
Fixes API breakage
2026-01-27 23:17:38 +01:00
MidoriKami
5c7a5295d1
Misc Fixes (#2584)
* Disable default logging, remove log message

* Add IDtrBarEntry.MinimumWidth

* Fix Addon/Agent Lifecycle Register/Unregister

* Rename Agent.ReceiveEvent2

* Add to IReadOnlyDtrBarEntry

* Fix autoformat being terrible

* More style fixes

* Add focused changed lifecycle event

* Fix for obsolete renames
2026-01-27 13:49:35 -08:00
goaaats
e598013e30 Upgrade goatcorp.Reloaded.Hooks, remove goatcorp.Reloaded.Assembler 2026-01-27 18:53:35 +01:00
KazWolfe
c0077b1e26
Revert "Use RowRef in ZoneInitEventArgs (#2540)" (#2597)
This reverts commit 5da79a7dba.
2026-01-27 18:30:34 +01:00
bleatbot
10ef40ddf5
Update ClientStructs (#2595)
Co-authored-by: github-actions[bot] <noreply@github.com>
2026-01-27 16:42:07 +00:00
Limiana
5da79a7dba
Use RowRef in ZoneInitEventArgs (#2540)
* Try-catch packet read

* Actually use RowRef instead
2026-01-27 08:38:42 -08:00
Haselnussbomber
3abf7bb00b
Rework NetworkMonitorWidget, remove GameNetwork (#2593)
* Rework NetworkMonitorWidget, remove GameNetwork

* Rework packet filtering
2026-01-27 08:35:55 -08:00
wolfcomp
afa7b0c1f3
Improve parameter verification logic in HookVerifier (#2596)
* Improve parameter verification logic in HookVerifier

Refactor HookVerifier to enhance parameter type checking and add utility methods for size calculations.

* Reverse bool check

* Fix type size check on return type

* Fix non static member in static class

* Fix compiler errors

* Fix SizeOf calls

* Fix IsStruct call

* Cleanup some warnings
2026-01-27 08:30:58 -08:00
Infi
8f8f4faa12
Apply ImRaii to Widgets Part 2 (#2567)
* Apply ImRaii to multiple widgets

* Apply ImRaii to leftover widgets
2026-01-25 19:21:33 -08:00
Infi
672636c3bf
Remove UiDebug V1 in favor of V2 (#2586)
* - Remove UiDebug1 in favor of UiDebug2

* - Remove all mentions of 2
2026-01-25 19:21:03 -08:00
Haselnussbomber
b9c4c97eba
Add timestamp to TroubleshootingPayload 2026-01-25 16:23:24 +01:00
Haselnussbomber
ac7c4e889a
Write troubleshooting to json file 2026-01-25 12:40:51 +01:00
Haselnussbomber
951290cac7
Fix EnumGenerator configuration mapping (#2590) 2026-01-24 20:06:55 -08:00
Haselnussbomber
61423f1791
Fix being unable to edit TitleBgCollapsed (#2589)
Fixes #999
2026-01-24 20:06:25 -08:00
Haselnussbomber
b601bfdbfb
Add Quest and Leve support to IUnlockState (#2581)
* Fix AgentInterfacePtr.FocusAddon

* Add Quest support to IUnlockState

* Reorder, bail out early in Recipe and McGuffin

* Add Leve support to IUnlockState

* Fix warning

* Disable log spam
2026-01-24 20:03:57 -08:00
bleatbot
3b8f0bc92f
Update ClientStructs (#2579)
Co-authored-by: github-actions[bot] <noreply@github.com>
2026-01-24 20:03:03 -08:00
goaaats
c1df0da9be build: 14.0.1.0 2026-01-11 13:18:32 +01:00
goat
214d9027b5
Merge pull request #2580 from Haselnussbomber/ex-experimental
Remove ExperimentalAttribute from IUnlockState
2026-01-11 01:01:31 +01:00
Haselnussbomber
745b3a4939
Remove ExperimentalAttribute from IUnlockState 2026-01-11 00:49:39 +01:00
goat
f3694a41ff
Merge pull request #2563 from Infiziert90/ImRaii-Widgets
Apply ImRaii to Widgets Part 1
2026-01-11 00:45:19 +01:00
goat
39e60f27f2
Merge pull request #2571 from MidoriKami/AgentLifecycle
Add IAgentLifecycle
2026-01-11 00:44:39 +01:00
MidoriKami
a03e37f700
Merge branch 'master' into AgentLifecycle 2026-01-10 08:56:26 -08:00
goaaats
c545205e66 Remove analyzer for source generator projects 2026-01-10 17:53:49 +01:00
MidoriKami
0c2ce097ed Use generated AgentId 2026-01-10 08:30:15 -08:00
MidoriKami
d689c4763a Merge branch 'master' into AgentLifecycle 2026-01-10 08:16:52 -08:00
goaaats
dd94d10722 Add conversion extension method from source enum 2026-01-10 17:10:05 +01:00
goaaats
8bb6cdd8d6 Add "enum cloning" source generator 2026-01-10 17:10:05 +01:00
Infi
fab7eef244
Update UIColorWidget.cs 2026-01-10 14:25:22 +01:00
Infi
5bfbcbb8f5
Merge branch 'master' into ImRaii-Widgets 2026-01-10 14:13:41 +01:00
goat
55eb7e41d8
Merge pull request #2566 from MidoriKami/AddonLifecycleThreadSafety
IAddonLifecycle Thread Safety
2026-01-10 13:13:36 +01:00
goat
b5028add57
Merge pull request #2550 from RedworkDE/logmessage
Add event for LogMessages being added to the chat
2026-01-10 13:05:08 +01:00
goat
5ee339b5a8
Merge pull request #2572 from Loskh/dark_mode
fix: respect system dark mode setting
2026-01-10 12:50:42 +01:00
goat
7fb43f8707
Merge pull request #2578 from goatcorp/csupdate-master
[master] Update ClientStructs
2026-01-10 12:49:36 +01:00
github-actions[bot]
b2fb6949d2 Update ClientStructs 2026-01-10 06:37:41 +00:00
MidoriKami
6c8b2b4a6d Remove casts 2026-01-09 12:52:33 -08:00
MidoriKami
f635673ce9 Use AgentInterfacePtr 2026-01-09 12:51:08 -08:00
goat
156abbdcbe
Merge branch 'master' into ImRaii-Widgets 2026-01-09 21:42:43 +01:00
goat
47f60eb391
Merge pull request #2568 from Infiziert90/ImRaii-UIDebug2
Improve UIDebug2
2026-01-09 21:39:41 +01:00
goat
ef0d680f06
Merge pull request #2573 from goatcorp/csupdate-master
[master] Update ClientStructs
2026-01-09 21:38:58 +01:00
goat
8afc02b364
Merge pull request #2574 from Haselnussbomber/update-AddonEventType
Update AddonEventType
2026-01-09 21:36:51 +01:00
goat
035be9d67d
Merge pull request #2575 from Haselnussbomber/fix-item-redirect-decorations
Fix leaking colors in sheet redirects for Item
2026-01-09 21:36:26 +01:00
goat
86396946e9
Merge pull request #2576 from goatcorp/schemaupdate-master
[master] Update Excel Schema
2026-01-09 21:36:10 +01:00
github-actions[bot]
b29b7851d9 Update ClientStructs 2026-01-09 18:40:20 +00:00
github-actions[bot]
90c29e5646 Update Excel Schema 2026-01-09 18:40:18 +00:00
Haselnussbomber
290ad9fc41
Fix leaking colors in sheet redirects for Item 2026-01-09 10:48:58 +01:00
RedworkDE
0cc5d301e5 Merge branch 'master' of https://github.com/goatcorp/Dalamud into logmessage 2026-01-06 23:30:16 +01:00
Haselnussbomber
c93f04f0e4
Code cleanup (#2439)
* Use new Lock objects

* Fix CA1513: Use ObjectDisposedException.ThrowIf

* Fix CA1860: Avoid using 'Enumerable.Any()' extension method

* Fix IDE0028: Use collection initializers or expressions

* Fix CA2263: Prefer generic overload when type is known

* Fix CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons

* Fix IDE0270: Null check can be simplified

* Fix IDE0280: Use 'nameof'

* Fix IDE0009: Add '.this'

* Fix IDE0007: Use 'var' instead of explicit type

* Fix IDE0062: Make local function static

* Fix CA1859: Use concrete types when possible for improved performance

* Fix IDE0066: Use switch expression

Only applied to where it doesn't look horrendous.

* Use is over switch

* Fix CA1847: Use String.Contains(char) instead of String.Contains(string) with single characters

* Fix SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time.

* Fix CA1866: Use 'string.EndsWith(char)' instead of 'string.EndsWith(string)' when you have a string with a single char

* Fix IDE0057: Substring can be simplified

* Fix IDE0059: Remove unnecessary value assignment

* Fix CA1510: Use ArgumentNullException throw helper

* Fix IDE0300: Use collection expression for array

* Fix IDE0250: Struct can be made 'readonly'

* Fix IDE0018: Inline variable declaration

* Fix CA1850: Prefer static HashData method over ComputeHash

* Fi CA1872: Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString'

* Update ModuleLog instantiations

* Organize usings
2026-01-06 08:36:55 -08:00
Haselnussbomber
9b9a66bdd2
Update AddonEventType 2026-01-06 14:19:21 +01:00
Loskh
e94ded628a fix: respect system dark mode setting 2026-01-05 22:42:04 +08:00
MidoriKami
d0caf98eb3 Add Agent Lifecycle 2026-01-04 21:40:31 -08:00
bleatbot
27414d33dd
Update ClientStructs (#2569)
Co-authored-by: github-actions[bot] <noreply@github.com>
2026-01-05 02:40:08 +00:00
Infi
bd05f4c1a5 - Switch SeString to ReadOnlySeString
- Utf8String to ReadOnlySeString avoiding Marshal
- Remove unnecessary ImRaii checks
- Switch default to 0
2026-01-05 03:15:01 +01:00
wolfcomp
bcc16c9b0e
Add CPU info to crash log (#2565)
* Add CPU info to crash log

Added a function to retrieve CPU vendor and brand information.

* Add missing include

* Remove unused std::strings
2026-01-04 23:00:31 +00:00
MidoriKami
36c3429566 Force to next tick instead of running immediately 2026-01-04 14:41:30 -08:00
MidoriKami
1398054216 Push AddonLifecycle event register/unregister to main thread 2026-01-04 14:03:15 -08:00
RedworkDE
6e19aca481 Fix StyleCop warnings 2026-01-04 16:00:10 +01:00
RedworkDE
790669e60a Battle log exists, selftest with the use action message 2026-01-04 08:12:06 +01:00
RedworkDE
9b55b020ca Switch selftest to using mounts instead of teleporting 2026-01-03 23:11:50 +01:00
RedworkDE
8b0f0fb44e Merge branch 'master' of https://github.com/goatcorp/Dalamud into logmessage 2026-01-03 22:28:43 +01:00
Infi
09a1fd1925 - Apply ImRaii to SeStringRendererTestWidget
- Add new themes
- Remove uses of Dalamud SeString
- Use collection expression
2026-01-03 21:43:12 +01:00
Infi
5fe6df3887 Cleanup TaskSchedulerWidget and ensure color is always popped 2026-01-03 21:31:28 +01:00
Infi
8c26d67739 Apply ImRaii to TexWidget 2026-01-03 21:24:57 +01:00
Infi
e4ef56b878 Apply ImRaii to UldWidget 2026-01-03 21:09:20 +01:00
Infi
e44fda1911 - Apply ImRaii to UIColorWidget 2026-01-03 21:04:01 +01:00
Infi
a1d2e275a7 - Apply ImRaii to FontAwesomeTestWidget
- Adjust array init
2026-01-03 20:54:24 +01:00
Haselnussbomber
9538af0554
UldWidget fixes (#2557)
* Fix missing directory separator in theme path

* Hide themed texture exception when file not found

* Check ThemeSupportBitmask
2026-01-03 11:46:48 -08:00
bleatbot
5a0257e40e
Update Excel Schema (#2555)
Co-authored-by: github-actions[bot] <noreply@github.com>
2026-01-03 11:31:46 -08:00
bleatbot
bbb6e438b1
Update ClientStructs (#2556)
Co-authored-by: github-actions[bot] <noreply@github.com>
2026-01-03 11:31:20 -08:00
goat
e32e4a0c8e
Merge pull request #2562 from nebel/xldata-fontawesome-font-issue
Some checks failed
Tag Build / Tag Build (push) Successful in 5s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Fix font corruption caused by Font Awesome icon button in Data window
2026-01-02 17:03:31 +01:00
nebel
49abb19673
Fix font corruption caused by Font Awesome icon button in Data window 2026-01-03 00:50:25 +09:00
goaaats
79ce2fff0a Revert "Use v145 build tools for C++ components"
Some checks failed
Tag Build / Tag Build (push) Successful in 4s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
This reverts commit 62b8b0834c.
MS can't manage to get actions images with 2026 for some reason.
2025-12-31 12:15:18 +01:00
goat
fc130e325c Fix warnings
Some checks failed
Tag Build / Tag Build (push) Successful in 5s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-26 16:15:58 +01:00
goat
a659cd8a49 Adjust to Excel renames 2025-12-26 16:15:51 +01:00
goat
62b8b0834c Use v145 build tools for C++ components 2025-12-26 16:09:32 +01:00
goat
61ba319e98
Merge pull request #2547 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-26 16:06:57 +01:00
goat
392e027ae3
Merge pull request #2545 from goatcorp/schemaupdate-master
[master] Update Excel Schema
2025-12-26 16:06:39 +01:00
github-actions[bot]
689d2f01d9 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-26 15:02:13 +00:00
github-actions[bot]
558a011e00 Update Excel Schema
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-26 15:02:13 +00:00
goat
c00363badf build: 14.0.0.3
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Failing after 5s
2025-12-25 11:31:47 +01:00
RedworkDE
c559426d8b Add Data Widget 2025-12-24 12:23:24 +01:00
RedworkDE
31cbf4d8eb Respect null-termination of entity names 2025-12-24 12:17:57 +01:00
RedworkDE
65c604f827 More ReadOnlySeString things 2025-12-24 11:14:37 +01:00
RedworkDE
bf75937cc0 Don't go through SeString to null terminate a string 2025-12-23 21:08:54 +01:00
RedworkDE
186b1b8376 Use SeStringEvaluator instead of RaptureTextModule for the debug display 2025-12-23 14:58:47 +01:00
RedworkDE
f76d77f79d rewview (3) and fix some copy docs comments 2025-12-23 12:41:46 +01:00
RedworkDE
9da178ad56 review (2) 2025-12-23 12:34:04 +01:00
RedworkDE
3aca09d0fb review 2025-12-22 22:28:44 +01:00
RedworkDE
b2397efb25 Add Self Test 2025-12-22 20:21:07 +01:00
RedworkDE
282fa87571 Add event for LogMessages being added to the chat 2025-12-22 19:44:00 +01:00
goat
3be14d4135
Merge pull request #2548 from Soreepeong/fix/sta-cts
Some checks failed
Tag Build / Tag Build (push) Successful in 4s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Fix wrong CancellationToken usage
2025-12-22 10:56:59 +01:00
Soreepeong
8ccfac2318 Fix wrong CancellationToken usage 2025-12-22 18:46:29 +09:00
goat
e2a18dee5e build: 14.0.0.2
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Failing after 5s
2025-12-21 15:32:24 +01:00
goat
f803dfbd44
Merge pull request #2546 from Infiziert90/todo-contentid
Mark PartyMember.ContentId type for API15
2025-12-21 14:46:53 +01:00
Infi
4dcfa9da98 - Add ToDo for ulong change 2025-12-21 14:37:03 +01:00
goat
867ae80b22
Merge pull request #2543 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-21 13:09:10 +01:00
goat
1b39040072
Merge pull request #2535 from Haselnussbomber/rssb
Slight SeString code updates
2025-12-21 13:04:24 +01:00
goat
ea1bb92d17
Merge pull request #2536 from CMDRNuffin/imgui-textboxes-fix-unnecessary-cloning-on-unchanged-text-2-electric-boogaloo
Prevent unnecessary string creation in ImGui TextInput methods
2025-12-21 13:02:07 +01:00
github-actions[bot]
5513bd1633 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-21 11:59:55 +00:00
KazWolfe
f307aded73
Lumina revert (#2544)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
2025-12-21 02:31:17 +00:00
goat
a59efbd84c Fixes for excel renamings 2025-12-21 02:41:22 +01:00
goat
c3952bbf53
Merge pull request #2542 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-21 02:32:41 +01:00
goat
5a8bb73b39
Merge pull request #2541 from goatcorp/schemaupdate-master
[master] Update Excel Schema
2025-12-21 02:31:20 +01:00
github-actions[bot]
96b5ad1b65 Update ClientStructs 2025-12-21 01:23:19 +00:00
github-actions[bot]
69c24fdbb9 Update Excel Schema 2025-12-21 01:23:12 +00:00
goat
3abddbae2c build: 14.0.0.1 2025-12-21 00:51:50 +01:00
goat
b24cadf2d8 Merge branch 'master' of github.com:goatcorp/Dalamud 2025-12-21 00:51:14 +01:00
goat
9205529820
Merge pull request #2539 from goatcorp/csupdate-master
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 5s
[master] Update ClientStructs
2025-12-20 21:54:48 +01:00
goat
da2b80156a Fix some wording on badge tab 2025-12-20 21:45:47 +01:00
github-actions[bot]
1f5f6f8914 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-20 20:45:31 +00:00
goat
08c4f7dbdd
Merge pull request #2538 from Haselnussbomber/zoneinit-fix
Update ZoneInitEventArgs
2025-12-20 21:36:55 +01:00
goat
451db117a6
Merge pull request #2537 from goatcorp/schemaupdate-master
[master] Update Excel Schema
2025-12-20 21:31:41 +01:00
goat
73168b0e53
Merge pull request #2534 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-20 21:30:46 +01:00
Haselnussbomber
3ef6135f15
Fix reading ActiveFestivals in ZoneInitEventArgs 2025-12-20 21:26:10 +01:00
Haselnussbomber
6374c0d6ae
Use UIModuleHandlePacketDetour for ZoneInit 2025-12-20 21:25:49 +01:00
github-actions[bot]
e603af5acc Update ClientStructs 2025-12-20 18:33:10 +00:00
github-actions[bot]
8b0bb343f9 Update Excel Schema
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-20 18:33:04 +00:00
KazWolfe
bc2eac6006
fix: Remove RPC (#2526)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 5s
2025-12-20 16:18:03 +10:00
CMDRNuffin
66fde2d458 Prevent unnecessary string creation in ImGui TextInput methods
We now only create a new string if we either know the buffer changed or
the EnterReturnsTrue flag was set (because that one does a LOT while
still updating the buffer on every actual input), so I had to choose
between replicating all that behavior in each of the various InputText
methods (hell no, lol), scanning the buffer for actual changes (which
would require making another copy) or accepting that in that case we
would create a new string every frame.

This still makes the GC happy in the majority of cases, while giving
callers the option to take a slight performance hit for the convenience
EnterReturnsTrue provides.
2025-12-20 03:19:20 +01:00
goaaats
c7dd694a53 Revert "Prevent ImGui text box methods from cloning unchanged input every frame"
This reverts commit db5f27518f.
Causes issues with certain flags.
2025-12-20 02:02:57 +01:00
goaaats
efed9ca20b Add badges
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
2025-12-19 21:20:06 +01:00
Haselnussbomber
8a49a5ee48
Use spread element for TextArrowPayloads
Also changes CreatePartyFinderLink to use client-language-based text.
2025-12-19 17:39:31 +01:00
Haselnussbomber
a3d930b8e2
Use RentedSeStringBuilder more 2025-12-19 17:34:42 +01:00
goat
5e4ad4a694
Merge pull request #2533 from wolfcomp/patch-7
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 5s
Add new themes and update themed path logic
2025-12-19 12:09:41 +01:00
wolfcomp
c71d8889d7
Access const as non instance 2025-12-19 11:51:09 +01:00
wolfcomp
4ddaaf3809
Add new themes and update themed path logic 2025-12-19 11:41:33 +01:00
goat
3b8917bcc8
Merge pull request #2528 from CMDRNuffin/imgui-textboxes-fix-unnecessary-cloning-on-unchanged-text
Prevent ImGui text box methods from cloning unchanged input every frame
2025-12-19 11:36:19 +01:00
goat
517d5d017b
Merge pull request #2529 from Haselnussbomber/update-uicolorwidget
Update UIColor widget
2025-12-19 11:35:56 +01:00
goat
8fb2c39d80
Merge pull request #2532 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-19 11:35:34 +01:00
github-actions[bot]
89c46944b6 Update ClientStructs 2025-12-19 10:20:39 +00:00
goat
cb3881f07d
Merge pull request #2531 from goatcorp/schemaupdate-master
[master] Update Excel Schema
2025-12-19 08:44:59 +01:00
goat
6ead1c8895
Merge pull request #2530 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-19 08:44:10 +01:00
github-actions[bot]
f3f4ced049 Update Excel Schema
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-19 06:39:07 +00:00
github-actions[bot]
7af0523e88 Update ClientStructs 2025-12-19 06:38:57 +00:00
bleatbot
7eea7d6182
Update ClientStructs (#2525)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-12-19 02:35:52 +00:00
Haselnussbomber
86e12f411d
Update UIColor widget 2025-12-19 03:26:53 +01:00
CMDRNuffin
db5f27518f Prevent ImGui text box methods from cloning unchanged input every frame
The overloads taking a string by ref for the input text of the various
ways to display a text box would all take the input string, copy it into
a buffer for imgui and then unconditionally produce a new string once
the imgui call returned. Now we only create a new string when the return
value of the native function actually indicates that the text changed.

This makes the GC happy, and also users like me who like to make the GC
happy.

Other side effects: The assumption that the reference doesn't change if
the method returns false, which is very reasonable IMO, is now correct.
2025-12-19 01:24:43 +01:00
goaaats
c005bae265 Revert obsolete as error again, fix warnings, Api14ToDo => Api15ToDo
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
2025-12-18 21:00:07 +01:00
goaaats
19fca721e9 Make obsoletions for ClientState error 2025-12-18 20:55:04 +01:00
goaaats
a56d2cf40b Add verifier for hook signatures
This one is real bad, so we should make sure everyone using a canonical signature
2025-12-18 20:28:03 +01:00
bleatbot
3eb65c85c0
Update ClientStructs (#2524)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-12-18 18:08:27 +00:00
goat
1b76aec89f
Merge pull request #2521 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-18 16:21:45 +01:00
goat
b52024d927
Merge branch 'master' into csupdate-master 2025-12-18 16:12:06 +01:00
goat
56b0ae80b6
Merge pull request #2523 from Infiziert90/fix-cs-build
Fix CS build errors from unknown private change
2025-12-18 16:11:51 +01:00
Infi
0b1a697d4d - Comment out erroring unknown prints 2025-12-18 15:38:57 +01:00
goat
05beea003c
Merge pull request #2522 from Loskh/fix_item_name
fix: EventItem name for Japanese client.
2025-12-18 15:11:13 +01:00
Loskh
3c8cef06dd fix: EventItem name for Japanese client. 2025-12-18 21:54:28 +08:00
github-actions[bot]
0d533c18f8 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-18 13:21:49 +00:00
goat
52166e4b9e
Merge pull request #2520 from Critical-Impact/docfx-dlls
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
Use built dlls instead of csproj for docfx
2025-12-18 13:18:49 +01:00
goat
ea756250a8
Merge pull request #2519 from Critical-Impact/versioning-public
Add git hash/scm version properties to DalamudVersionInfo
2025-12-18 13:14:29 +01:00
Critical Impact
984bdbcf0e Use built dlls instead of csproj for docfx 2025-12-18 22:07:58 +10:00
Critical Impact
bd87a3d251 Add git hash/scm version properties to DalamudVersionInfo 2025-12-18 22:01:57 +10:00
goat
77b6d71855
Merge pull request #2514 from MidoriKami/ResolveOriginalAddress
Add AddonLifecycle VirtualAddress helper
2025-12-18 12:08:34 +01:00
goat
6550e71092
Merge pull request #2517 from goatcorp/schemaupdate-master
[master] Update Excel Schema
2025-12-18 12:07:53 +01:00
goat
7ac12627ec
Merge pull request #2515 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-18 12:07:12 +01:00
github-actions[bot]
17c0527f2d Update ClientStructs 2025-12-18 06:40:20 +00:00
github-actions[bot]
3a1e1e6425 Update Excel Schema
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-18 06:40:14 +00:00
MidoriKami
37fa40ab58 Make stylecop happy 2025-12-17 16:44:04 -08:00
goat
fb7cd452f6
Merge pull request #2513 from goatcorp/csupdate-master
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
[master] Update ClientStructs
2025-12-18 01:41:11 +01:00
MidoriKami
7f4352dc43 Add address resolver 2025-12-17 16:38:34 -08:00
goat
707369bfad
Merge pull request #2457 from goatcorp/schemaupdate-master
[master] Update Excel Schema
2025-12-18 01:30:26 +01:00
goat
25dba5e23b
ci: revert global concurrency change again because it breaks PR workflows
Need to figure out something better for this soon, but it's better not to have this at all right now
2025-12-18 01:29:48 +01:00
github-actions[bot]
05037dccc7 Update ClientStructs 2025-12-18 00:24:13 +00:00
github-actions[bot]
574e0d4582 Update Excel Schema
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-18 00:24:11 +00:00
goat
02d4081f2f
ci: disable rollup for now 2025-12-18 01:24:09 +01:00
goat
355ad64eb9
Merge pull request #2508 from Haselnussbomber/excel-bump
Update Lumina and Lumina.Excel
2025-12-18 01:23:18 +01:00
goat
f1f95eda09
Merge pull request #2509 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-18 01:21:18 +01:00
github-actions[bot]
fc804ba0d0 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-17 18:40:02 +00:00
goat
108a7a2c2d
Merge pull request #2510 from Haselnussbomber/conditions
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 7s
Tag Build / Tag Build (push) Successful in 4s
Update Condition/ConditionFlag
2025-12-17 18:48:25 +01:00
goat
c5d90aef64
Merge pull request #2511 from Haselnussbomber/context-menu-fix
Fix crashing Context Menu
2025-12-17 18:42:32 +01:00
goat
92d6c70358
Merge pull request #2512 from Haselnussbomber/hover-action-kind
Update HoverActionKind
2025-12-17 18:41:56 +01:00
Haselnussbomber
2fc9884aad
Update HoverActionKind 2025-12-17 18:17:44 +01:00
Haselnussbomber
b3c4363e0f
Fix crashing Context Menu 2025-12-17 17:09:18 +01:00
Haselnussbomber
841cdf52bd
Update Lumina and Lumina.Excel 2025-12-17 16:15:33 +01:00
Haselnussbomber
19660a20d9
Update Condition/ConditionFlag 2025-12-17 16:12:33 +01:00
goaaats
f142fb1058 Set language version to preview for now
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 7s
Tag Build / Tag Build (push) Successful in 3s
Fixes a docfx error, since they haven't upgraded to a Roslyn version that knows C# 14
2025-12-17 00:50:14 +01:00
goaaats
46954e6add Remove plugin targets from SLN
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 6s
Tag Build / Tag Build (push) Successful in 3s
2025-12-16 21:01:58 +01:00
goaaats
01901c237a Downgrade Iced to resolve version conflict between Dalamud and Injector 2025-12-16 21:01:50 +01:00
goaaats
cdf4e27355 Bump version to 14.0.0.0 2025-12-16 19:28:09 +01:00
goat
a843079a6b
Merge pull request #2506 from goatcorp/api14-rollup
[api14] Rollup changes from master
2025-12-16 18:54:13 +01:00
goat
ddd85513ba
Merge pull request #2507 from Haselnussbomber/enumerator-fixes
[API14] Better enumerator code
2025-12-16 18:53:50 +01:00
Haselnussbomber
89fbe6c8b0
Update UiConfigOption 2025-12-16 17:21:19 +01:00
Haselnussbomber
1c1b60efee
Better enumerator code 2025-12-16 09:48:59 +01:00
goat
2e7c48316f
Merge pull request #2481 from MidoriKami/AddonLifecycleRefactor
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[API14] AddonLifecycle Refactor
2025-12-16 00:32:09 +01:00
github-actions[bot]
b0a0fafb53 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-15 23:31:02 +00:00
goat
8334836b6a
Merge pull request #2386 from KazWolfe/ipc-context
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 8s
Tag Build / Tag Build (push) Successful in 3s
feat: Add IPC Context
2025-12-16 00:30:06 +01:00
goat
8a742e7e59
Merge pull request #2505 from Aireil/patch-25
Remove obsolete enum values from FlyTextKind
2025-12-15 23:25:46 +01:00
Aireil
56325afa7f
Remove obsolete enum values from FlyTextKind
They have been obsolete for nearly 7 months (before 7.3).
2025-12-15 23:14:46 +01:00
MidoriKami
1bff6abae9 Fix oopsie 2025-12-15 13:22:39 -08:00
MidoriKami
d7935d6dd4 Merge remote-tracking branch 'origin/AddonLifecycleRefactor' into AddonLifecycleRefactor 2025-12-15 13:13:19 -08:00
MidoriKami
a715725a9d Add enumerable AtkValue helper 2025-12-15 13:13:08 -08:00
MidoriKami
bc8e986c11
Merge branch 'api14' into AddonLifecycleRefactor 2025-12-15 12:55:35 -08:00
goaaats
ffd99d5791 Add interface to obtain versioning info 2025-12-15 21:43:52 +01:00
goaaats
20af5b40c7 Make all versioning functions internal, move to separate class 2025-12-15 21:31:25 +01:00
goaaats
a1409096fd Redo SeStringRenderer deprecations 2025-12-15 21:20:24 +01:00
goat
fecba89710
Merge pull request #2498 from goatcorp/api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
[api14] Rollup changes from master
2025-12-15 21:12:03 +01:00
goat
b57b96b9a0
Merge pull request #2493 from Haselnussbomber/update-packages
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[API14] Update packages
2025-12-13 23:47:14 +01:00
github-actions[bot]
180676fe47 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-13 04:05:56 +00:00
Haselnussbomber
2d096d9b33
Properly initialize GameInventoryItems (#2504)
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 20s
Tag Build / Tag Build (push) Successful in 5s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-13 14:05:03 +10:00
goaaats
e100ec2abd build: 13.0.0.16
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Failing after 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-12 00:57:04 +01:00
goat
71b0a757e9
Merge pull request #2501 from Haselnussbomber/clear-ImDrawListSplitter
Clear ImDrawListSplitter when disposing SeStringDrawState
2025-12-11 23:15:21 +01:00
Haselnussbomber
0b55dc3e10
Clear ImDrawListSplitter when disposing SeStringDrawState 2025-12-11 22:59:50 +01:00
MidoriKami
4d9751ea5f
Merge branch 'api14' into AddonLifecycleRefactor 2025-12-10 14:16:56 -08:00
goaaats
a39763f161 Mark preset dirty when disabling clickthrough for a window
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 2s
2025-12-10 18:33:37 +01:00
goaaats
201c9cfcf2 Use game window to calculate offsets in fallback mouse position code 2025-12-10 18:13:52 +01:00
goat
e07bda7e58
Merge pull request #2500 from nebel/window-error-pop-dalamud-style
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
Always pop DalamudStandard style if pushed earlier in Draw
2025-12-10 15:26:12 +01:00
nebel
b88a6bb616
Always pop DalamudStandard style if pushed earlier in Draw 2025-12-10 23:12:44 +09:00
goaaats
e53ccdbcc0 build: 13.0.0.15
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Failing after 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-09 00:18:28 +01:00
goaaats
97df73acea Ensure that we don't catch mouse up events without corresponding mouse down events
Fixes an issue wherein the cursor could get locked by the game if WantCaptureMouse becomes true in between down and up events
2025-12-08 21:00:08 +01:00
goaaats
2806e59dba Also remove borders for dev bar, to prevent themes from causing weirdness 2025-12-08 20:09:31 +01:00
goaaats
24caa1cb18 PresetWindow.IsDefault can be JsonIgnore 2025-12-08 20:05:14 +01:00
goaaats
5d08170333 Keep rendering title bar buttons if one is not available clickthrough 2025-12-08 20:03:43 +01:00
goaaats
d0110f7251 Hardcode HasModifiedGameDataFiles to false for now until XL is fixed 2025-12-08 20:03:22 +01:00
MidoriKami
2dbae05522 Add very thurough exception handling 2025-12-07 16:45:59 -08:00
goaaats
8ed1af30df build: 13.0.0.14
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 2s
Tag Build / Tag Build (push) Failing after 2s
2025-12-07 22:55:16 +01:00
goat
e8485dee25
Merge pull request #2492 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-12-07 22:22:08 +01:00
github-actions[bot]
0072f49fe8 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-12-07 21:13:12 +00:00
goaaats
c45c6aafe1 Don't consider failed index integrity checks as having "modified game data files" 2025-12-07 21:57:54 +01:00
goaaats
2029a0f8a6 Also add fallback for SeStringDrawState.ScreenOffset for now, make sure that it is populated 2025-12-07 21:31:25 +01:00
goat
bcb8094c2d
Merge pull request #2497 from Haselnussbomber/update-fontawesome
[API14] Update Font Awesome to 7.1.0
2025-12-07 17:06:43 +01:00
Haselnussbomber
624191d1e0
Update DalamudAssetPath to FontAwesome710FreeSolid.otf 2025-12-07 16:45:40 +01:00
Haselnussbomber
c254c8600e
Update Font Awesome to 7.1.0 2025-12-07 16:31:03 +01:00
goat
61376fe84e
Merge pull request #2496 from Haselnussbomber/remove-targets
[API14] Remove targets
2025-12-07 16:25:32 +01:00
Haselnussbomber
2f5f52b572
Forgot to remove this too 2025-12-07 16:23:13 +01:00
Haselnussbomber
7199bfb0a9
Remove targets 2025-12-07 16:20:55 +01:00
goat
abcddde591
Merge pull request #2494 from Haselnussbomber/remove-obsoleted-sestring-casts
[API14] Remove obsolete casts from Lumina.Text.SeString
2025-12-07 15:59:33 +01:00
goat
2a99108eb1
Merge pull request #2495 from Haselnussbomber/expose-settings-uibuilder
[API14] Add PluginUISoundEffectsEnabled to UiBuilder
2025-12-07 15:58:58 +01:00
Haselnussbomber
8a5f1fd96d
Add PluginUISoundEffectsEnabled to UiBuilder 2025-12-07 15:55:43 +01:00
goaaats
652ff59672 build: 13.0.0.13
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Failing after 2s
2025-12-07 15:52:26 +01:00
goaaats
094483e5a0 List PRs in changelog generator 2025-12-07 15:52:13 +01:00
goaaats
c50237cf66 Add compatibility changes for SeString API breakage 2025-12-07 15:46:01 +01:00
Haselnussbomber
d4fe523d73
Clean up some warnings 2025-12-07 15:38:10 +01:00
Haselnussbomber
9e5723359a
Remove obsolete casts from Lumina.Text.SeString 2025-12-07 15:35:38 +01:00
Haselnussbomber
07f9e03010
Update packages 2025-12-07 15:14:27 +01:00
Haselnussbomber
9cfa81c92d
Remove unused packages 2025-12-07 15:00:20 +01:00
goaaats
b35faf13b5 Show unhandled exceptions through VEH 2025-12-07 13:04:11 +01:00
goaaats
caa869d3ac Clarify exception and docs regarding off-thread drawing with SeStrings, again 2025-12-07 12:54:13 +01:00
goaaats
9fd59f736d Merge from master
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-06 18:48:31 +01:00
goat
ab5ea34e68
ci: make deploying builds globally blocking, don't cancel in-progress
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 2s
2025-12-06 18:46:06 +01:00
goat
501e30e31c
Merge pull request #2490 from goaaats/feat/catch_clr_errors
Catch CLR exceptions
2025-12-06 18:43:32 +01:00
goaaats
3d29157391 Revert "Add git status checks to workflow to see what's dirty"
This reverts commit a36e11574b.
2025-12-06 18:38:44 +01:00
goaaats
b2d9480f9f Submit nuke schema 2025-12-06 18:38:44 +01:00
goat
1ad1343cbc
Merge pull request #2488 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-06 18:36:40 +01:00
goat
61123ce573
Merge pull request #2485 from Haselnussbomber/update-cswin32
[API14] Update Microsoft.Windows.CsWin32
2025-12-06 18:35:41 +01:00
goat
9f565fafd8
Merge pull request #2489 from MidoriKami/Remove-Sigs
Remove AddonEventManagerAddressResolver
2025-12-06 18:33:42 +01:00
goat
88fc933e3f
Merge pull request #2491 from Haselnussbomber/drawstate-fontptr
[API14] Use ImFontPtr in SeStringDrawState
2025-12-06 18:33:09 +01:00
goaaats
e032840ac8 Clean up crash handler window log for external events 2025-12-06 18:32:03 +01:00
Haselnussbomber
1d1db04f04
Use ImFontPtr in SeStringDrawState 2025-12-06 16:09:42 +01:00
goaaats
446c7e3877 Some logging, cleanup 2025-12-06 15:25:04 +01:00
goaaats
e09c43b8de Fix bad exit condition when looping exception records 2025-12-06 15:07:46 +01:00
goaaats
9c2d2b7c1d Report CLR errors through DalamudCrashHandler/VEH by hooking ReportEventW 2025-12-06 15:07:09 +01:00
github-actions[bot]
2e5c560ed7 Update ClientStructs
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-06 12:48:34 +00:00
MidoriKami
45366efd9f Remove SigScanner from ctor 2025-12-05 17:10:58 -08:00
MidoriKami
3c7dbf9f81 Remove AddonEventManagerAddressResolver.cs 2025-12-05 16:59:17 -08:00
goat
a36e11574b
Add git status checks to workflow to see what's dirty
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-12-06 01:10:00 +01:00
Haselnussbomber
d94cacaac3
Disable SafeHandles 2025-12-05 19:10:31 +01:00
Haselnussbomber
7cf20fe102
Update Microsoft.Windows.CsWin32 2025-12-05 18:58:10 +01:00
goat
98a4c0d4fd
Merge pull request #2479 from goatcorp/api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
[api14] Rollup changes from master
2025-12-05 18:20:01 +01:00
goat
f85ef995e3
Merge pull request #2486 from Haselnussbomber/update-terrafx
[API14] Update TerraFX.Interop.Windows
2025-12-05 18:19:42 +01:00
goat
e7d4786a1f
Oops, wrong version 2025-12-05 18:18:57 +01:00
goat
4d949e4a07
Merge branch 'api14' into update-terrafx 2025-12-05 18:17:52 +01:00
goat
68ca60fa8c
Merge pull request #2484 from Haselnussbomber/sharpdx-removal
[API14] Remove SharpDX
2025-12-05 18:16:00 +01:00
goat
411067219e
Merge pull request #2487 from Haselnussbomber/update-nuke
[API14] Update Nuke
2025-12-05 18:14:22 +01:00
Haselnussbomber
fc983458fa
Update Nuke 2025-12-05 01:44:18 +01:00
Haselnussbomber
ddc3113244
Update TerraFX.Interop.Windows 2025-12-05 01:34:47 +01:00
Haselnussbomber
da7be64fdf
Remove SharpDX 2025-12-04 23:34:11 +01:00
Haselnussbomber
0112e17fdb
Replace internal SharpDX usage with TerraFX 2025-12-04 23:33:48 +01:00
github-actions[bot]
6f8e33a39c Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-04 22:03:13 +00:00
goaaats
ddc743aae1 Note that font ptr must be supplied when setting TargetDrawList
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-04 23:00:36 +01:00
goaaats
8dcbd52c22 Merge branch 'Soreepeong-feature/enable-viewport-alpha'
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
2025-12-04 02:07:34 +01:00
goaaats
1b5fbaa82e Access custom font atlas fields directly through bindings 2025-12-04 02:04:45 +01:00
goaaats
9bce0d33a6 Don't try to free CLR memory 2025-12-04 02:04:27 +01:00
goaaats
879c210cc6 Merge 'Enable viewport alpha' (#2362) 2025-12-04 01:47:43 +01:00
goaaats
1fe2d54128 Upgrade cimgui, prep for viewport alpha 2025-12-04 01:29:23 +01:00
goat
bfd592abbe
Merge pull request #2308 from Soreepeong/feature/sestring-to-texture
Add ITextureProvider.CreateTextureFromSeString
2025-12-04 01:19:04 +01:00
goat
df0bfc18c3
Make ImGuiHelpers.CreateDrawData() internal for now 2025-12-04 01:10:51 +01:00
MidoriKami
0480693f92 Merge branch 'api14' into AddonLifecycleRefactor
# Conflicts:
#	Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs
2025-12-03 16:08:31 -08:00
goat
3fbc24904a
Merge branch 'master' into feature/sestring-to-texture 2025-12-04 00:57:07 +01:00
goat
5bb212bfaa
Merge pull request #2424 from Haselnussbomber/fix-service-namespaces
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[API14] Fix services using wrong namespaces
2025-12-04 00:56:10 +01:00
goat
f055af7f7b
Merge pull request #2478 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-12-04 00:51:13 +01:00
goat
a917ebd856
Merge pull request #2468 from KazWolfe/rpc-unix
feat: Add unix sockets
2025-12-04 00:48:23 +01:00
github-actions[bot]
0e6dae9f64 Update ClientStructs
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-12-03 18:39:04 +00:00
goat
4fa4d7f338
Merge pull request #2483 from Haselnussbomber/fix-beasttribe-columnoffset
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 2s
Fix NounProcessor BeastTribe column offset
2025-12-03 17:20:02 +01:00
Haselnussbomber
f198ce46dc
Add self tests for ColumnOffset 2025-12-03 16:47:13 +01:00
Haselnussbomber
518b3a4fb3
Fix NounProcessor BeastTribe column offset 2025-12-03 16:43:12 +01:00
goat
85949072ec
Merge pull request #2476 from MidoriKami/ForceErrorStyle
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 6s
Tag Build / Tag Build (push) Successful in 2s
Erroring Window Style Fix
2025-12-02 23:20:54 +01:00
MidoriKami
14e97a1a37 Use local variable to track pushed style state 2025-12-01 14:19:12 -08:00
goat
f3c826a54b
Merge pull request #2482 from Haselnussbomber/playerstate-level-fix
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Fix PlayerState.Level being synced
2025-12-01 13:39:07 +01:00
Haselnussbomber
fb229a0a12
Fix PlayerState.Level being synced 2025-12-01 12:09:24 +01:00
MidoriKami
85a7c60dae Fix name inconsistency 2025-11-30 22:20:02 -08:00
MidoriKami
c923884626 Disable Logging 2025-11-30 22:15:32 -08:00
MidoriKami
78781c8988 Add Move, MouseOver, MouseOut, Focus 2025-11-30 21:43:26 -08:00
MidoriKami
2e24696731 Set flags, and unlock size 2025-11-30 14:47:24 -08:00
MidoriKami
b81cb9c74c Remove generic args class 2025-11-30 14:07:44 -08:00
MidoriKami
8e8d0246bc Restore original hookwidget logic 2025-11-30 14:00:39 -08:00
MidoriKami
d47a41b295 Fix NET14 Spans defaulting to ReadOnlySpan 2025-11-30 12:48:49 -08:00
MidoriKami
c9276b1771 Merge remote-tracking branch 'origin/AddonLifecycleRefactor' into AddonLifecycleRefactor
# Conflicts:
#	Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
#	Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonDrawArgs.cs
#	Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonFinalizeArgs.cs
#	Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs
#	Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs
#	Dalamud/Game/Addon/Lifecycle/AddonArgsType.cs
#	Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
#	Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs
#	Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs
2025-11-30 12:38:37 -08:00
MidoriKami
386828005b Apply breaking changes 2025-11-30 12:37:51 -08:00
MidoriKami
08c1768286 Bunch of stuff... 2025-11-30 11:33:06 -08:00
MidoriKami
eb9555ee22 Better unload 2025-11-30 11:33:06 -08:00
MidoriKami
be3f71dc73 Fix copy paste error 2025-11-30 11:33:06 -08:00
MidoriKami
e01acb4a80 Remove redundant header 2025-11-30 11:33:05 -08:00
MidoriKami
f8725e5f37 further improve performance 2025-11-30 11:33:05 -08:00
MidoriKami
c3e3e4aa85 Fix accidentally breaking widget 2025-11-30 11:33:05 -08:00
MidoriKami
b82b4f40ce Use hashset to prevent duplicate entries 2025-11-30 11:33:05 -08:00
MidoriKami
4f59e09513 Improve LifecycleInvoke efficiency with Dictionary 2025-11-30 11:33:05 -08:00
MidoriKami
0533872a73 Fix unreachable code complaint 2025-11-30 11:33:05 -08:00
MidoriKami
27a7adfdb9 Minor cleanup 2025-11-30 11:33:05 -08:00
MidoriKami
54bac7f32a Refactor Addon Lifecycle 2025-11-30 11:33:05 -08:00
MidoriKami
26f119096b Bunch of stuff... 2025-11-30 10:39:35 -08:00
MidoriKami
c51e65e0bd Better unload 2025-11-30 10:08:40 -08:00
Kaz Wolfe
874745651b
feat: Add PID, process time, rename ClientIdentifer to ClientState 2025-11-29 21:12:08 -08:00
goaaats
ac2d522415 build: 13.0.0.12
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 5s
Tag Build / Tag Build (push) Failing after 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-11-30 02:47:07 +01:00
Kaz Wolfe
ead1c705a4
fix: Route URIs to the specified InternalName 2025-11-29 17:07:51 -08:00
goaaats
fadf941fa4 Re-add config properties for XLCore/XoM backwards compatibility 2025-11-30 02:01:01 +01:00
goat
a31dda7865
Merge pull request #2475 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-11-29 19:39:18 +01:00
github-actions[bot]
d7e04ad4ff Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-29 18:23:24 +00:00
goaaats
7510c032cc Disable Intel CET support, causes CLR crashes on unpatched Windows
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
2025-11-29 19:22:28 +01:00
goaaats
d12a9ec7da Remove DalamudBetaKey, DalamudBetaKind from config
Fix all code that depends on it to use Util.GetActiveTrack() instead
2025-11-29 19:15:37 +01:00
goat
6367a66aad
Merge pull request #2472 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-11-29 18:43:05 +01:00
goat
edc6962296
Merge pull request #2477 from goaaats/feat/lumina_error
Show a sensible error message when Lumina fails to init
2025-11-29 18:39:33 +01:00
github-actions[bot]
78ecb721cd Update ClientStructs 2025-11-29 12:47:45 +00:00
MidoriKami
b8724f7a59 Fix copy paste error 2025-11-28 09:44:35 -08:00
goaaats
d7915c7020 Show a sensible error message when Lumina fails to init 2025-11-28 18:11:31 +01:00
MidoriKami
170f6e0859 Remove redundant header 2025-11-28 09:11:13 -08:00
MidoriKami
325d28ee32 further improve performance 2025-11-28 09:08:24 -08:00
MidoriKami
29c154f9b5 Fix accidentally breaking widget 2025-11-28 08:35:54 -08:00
MidoriKami
2a60bc61a7 Force style vars so erroring window renders at least partially sanely 2025-11-27 15:52:18 -08:00
MidoriKami
166f249e13 Use hashset to prevent duplicate entries 2025-11-27 14:30:40 -08:00
MidoriKami
c525655be6 Improve LifecycleInvoke efficiency with Dictionary 2025-11-27 14:24:35 -08:00
goat
02e0f1d36c
Merge pull request #2474 from goaaats/fix/pinned_escape
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Don't prevent closing native windows if pinned or clickthrough plugin windows are focused
2025-11-27 18:12:13 +01:00
Haselnussbomber
c661faea6b
Fix services using wrong namespaces 2025-11-27 09:41:02 +01:00
goaaats
4c3ba35f07 Don't inhibit ATK close events if pinned or clickthrough windows are focused 2025-11-27 01:45:13 +01:00
goat
d4f1636dd2
Merge pull request #2473 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-11-26 22:43:25 +01:00
github-actions[bot]
196a5ef709 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-26 20:47:44 +00:00
goaaats
c136934aa8 Always pass a key, even for release
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 1s
Fixes an issue wherein the XL commandline parser wouldn't like the empty argument and error out
2025-11-26 21:46:07 +01:00
goaaats
c6b173dd63 build: 13.0.0.11 2025-11-26 21:22:59 +01:00
goat
5e192ef39b
Merge pull request #2467 from goatcorp/api14-rollup
[api14] Rollup changes from master
2025-11-26 21:15:41 +01:00
github-actions[bot]
947518b3d6 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-26 20:10:02 +00:00
goaaats
9e5195492e Get active track from env var, instead of git branch 2025-11-26 21:08:50 +01:00
Kaz Wolfe
2cef75bbbe
feat: remove socket cleanup tasks 2025-11-26 11:56:30 -08:00
goat
1e7e7c732d
Merge pull request #2470 from goaaats/feat/handle_draw_errors
Handle errors in Window draw events by displaying an error message
2025-11-26 20:54:48 +01:00
goaaats
efd66fd3f8 Handle errors in Window draw events by displaying an error message 2025-11-26 19:29:14 +01:00
MidoriKami
ab0500ca6f Fix unreachable code complaint 2025-11-25 20:45:54 -08:00
MidoriKami
2c1bb76643 Minor cleanup 2025-11-25 18:56:34 -08:00
MidoriKami
9a1fae8246 Refactor Addon Lifecycle 2025-11-25 17:27:48 -08:00
goaaats
bd427d7b54 build: 13.0.0.10
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 8s
Tag Build / Tag Build (push) Failing after 2s
2025-11-25 20:32:39 +01:00
goaaats
d56c7a1963 Add auto-generated changelogs through CI 2025-11-25 20:32:12 +01:00
Kaz Wolfe
8ab7b59ae4
fix: Missing service types causing injection failures
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-25 10:17:12 -08:00
Kaz Wolfe
7b286c427c
chore: remove named pipe transport, use startinfo for pathing 2025-11-25 10:08:24 -08:00
goat
8773964de9
Merge pull request #2469 from Haselnussbomber/nullcheck-TestingDalamudApiLevel
Require TestingDalamudApiLevel to be set for testing
2025-11-25 18:53:22 +01:00
Haselnussbomber
1f30ce4c39
Require TestingDalamudApiLevel to be set for testing 2025-11-25 18:41:21 +01:00
bleatbot
ea07f41ab1
Update ClientStructs (#2465)
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-11-23 14:47:28 -08:00
goaaats
0656bff1f9 Fix experimental diagnostic IDs
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 1s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-11-20 19:31:50 +01:00
Kaz Wolfe
0d8f577576
feat: add debug link handler as demo 2025-11-18 16:28:03 -08:00
Kaz Wolfe
01d8fc0c7e
fix: log tweaks
- also fix a boot failure
2025-11-18 15:57:37 -08:00
Kaz Wolfe
71927a8bf6
feat: Add unix sockets
- Unix sockets run parallel to Named Pipes
  - Named Pipes will only run on non-Wine
  - If the game crashes, the next run will clean up an orphaned socket.
- Restructure RPC to be a bit tidier
2025-11-18 15:20:22 -08:00
goaaats
0daca30203 Make IReliableFileStorage experimental, add legal diagnostic IDs
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 3s
Tag Build / Tag Build (push) Successful in 1s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-11-18 23:17:21 +01:00
goaaats
28941cb69e SelfTestRegistryPluginScoped should inherit from IDalamudService 2025-11-18 21:58:13 +01:00
goaaats
f831a7c010 Wait for pending writes when disposing service 2025-11-18 21:39:47 +01:00
goaaats
05648f019b First draft of IReliableFileStorage service 2025-11-18 20:37:57 +01:00
goaaats
6a69a6e197 Fix some warnings
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-11-18 00:58:08 +01:00
goaaats
cc91916574 Fix bad merge 2025-11-18 00:52:30 +01:00
goaaats
20041be27c Convert ReliableFileStorage to async 2025-11-18 00:44:04 +01:00
goat
b7dda599fb
Merge pull request #2464 from goatcorp/api14-rollup
[api14] Rollup changes from master
2025-11-17 23:16:58 +01:00
github-actions[bot]
63b7ecf0d7 Merge remote-tracking branch 'origin/master' into api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-17 22:05:40 +00:00
goat
e65f441105
Merge pull request #2434 from Haselnussbomber/service-provider
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 6s
Tag Build / Tag Build (push) Successful in 1s
Simpler plugin service dependency injection
2025-11-17 23:04:45 +01:00
goat
1822ef1808
Merge pull request #2466 from Haselnussbomber/playerstate-ctor-fix
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
Add empty ServiceConstructor to PlayerState
2025-11-17 21:07:10 +01:00
Haselnussbomber
cb441631e1
Add empty ServiceConstructor to PlayerState 2025-11-17 20:58:14 +01:00
Haselnussbomber
1bdad092ca
Mark IPlayerState with IDalamudService 2025-11-17 20:28:52 +01:00
Haselnussbomber
f4c9c16c68
Mark IUnlockState with IDalamudService 2025-11-17 20:28:25 +01:00
Haselnussbomber
53a082e68d
Check that PluginInterfaces implement IDalamudService 2025-11-17 20:28:25 +01:00
Haselnussbomber
9ea417c9ef
Fix GetService getting stuck in Task 2025-11-17 20:28:25 +01:00
Haselnussbomber
9001c96986
Return the resulting service, not the Task 2025-11-17 20:28:25 +01:00
Haselnussbomber
46dee9a483
Inherit documentation in DalamudPluginInterface 2025-11-17 20:28:24 +01:00
Haselnussbomber
d3c812ba6c
Add IDalamudService marker interface 2025-11-17 20:28:24 +01:00
Haselnussbomber
7ec1de4c76
Let IDalamudPluginInterface inherit from IServiceProvider 2025-11-17 20:28:24 +01:00
goat
05f31265eb
Merge pull request #2422 from Haselnussbomber/playerstate-service
Add IPlayerState service
2025-11-17 20:28:01 +01:00
Haselnussbomber
64d4f7061a
Rename namespace PlayerState to Player 2025-11-17 19:29:48 +01:00
goat
e4eca842d3
Merge pull request #2461 from goatcorp/api14-rollup
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
[api14] Rollup changes from master
2025-11-17 19:18:36 +01:00
goat
a88247bdfc
Merge pull request #2454 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-11-17 19:17:51 +01:00
github-actions[bot]
c79fa96505 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-17 17:43:53 +00:00
goat
596af24e95
Merge pull request #2421 from Haselnussbomber/unlockstate-service
Add IUnlockState service
2025-11-17 18:42:49 +01:00
goat
ba0cf4c990
Merge pull request #2458 from Haselnussbomber/struct-enumerators
[API14] Use struct enumerators/types
2025-11-17 18:24:07 +01:00
goat
9a49a9588b
Merge pull request #2462 from KazWolfe/rpc
feat: Dalamud RPC service
2025-11-17 18:11:04 +01:00
github-actions[bot]
750fa58147 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-11-17 12:52:07 +00:00
Kaz Wolfe
19a3926051
Better hello message 2025-11-16 21:35:33 -08:00
Kaz Wolfe
4937a2f4bd
CR changes 2025-11-16 18:14:02 -08:00
Kaz Wolfe
78ed4a2b01
feat: Dalamud RPC service
A draft for a simple RPC service for Dalamud. Enables use of Dalamud URIs, to be added later.
2025-11-16 16:08:24 -08:00
goaaats
72dc094b57 build: 13.0.0.9
Some checks failed
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Failing after 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-11-15 19:41:46 +01:00
goat
62b9c1f2a1
Merge pull request #2460 from goatcorp/api14-rollup
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
[api14] Rollup changes from master
2025-11-15 01:09:51 +01:00
github-actions[bot]
a2e923b051 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-15 00:09:30 +00:00
goaaats
fea7b3676f Start correct XL binary through branch switcher, add build branch to metadata
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 5s
Tag Build / Tag Build (push) Successful in 2s
2025-11-15 01:08:23 +01:00
goat
de396e70f8
Merge pull request #2459 from goatcorp/api14-rollup
[api14] Rollup changes from master
2025-11-15 00:51:49 +01:00
github-actions[bot]
7a8f01f418 Merge remote-tracking branch 'origin/master' into api14-rollup 2025-11-14 23:49:59 +00:00
goat
a134c6d064
api14 rollup 2025-11-15 00:49:06 +01:00
Haselnussbomber
9d0879148c
Remove unused StatusEffect struct 2025-11-13 19:06:23 +01:00
Haselnussbomber
778c82fad2
Add struct enumerator to StatusList 2025-11-13 19:06:23 +01:00
Haselnussbomber
7f2ed9adb6
Convert Status to readonly struct and add interface 2025-11-13 19:06:23 +01:00
Haselnussbomber
53b94caeb7
Convert PartyMember to readonly struct 2025-11-13 19:06:18 +01:00
Haselnussbomber
d1dc81318a
Add struct enumerator to PartyList 2025-11-13 19:04:38 +01:00
Haselnussbomber
a48eead85e
Convert Fate to readonly struct 2025-11-13 19:04:35 +01:00
Haselnussbomber
d1bed3ebc5
Add struct enumerator to FateTable 2025-11-13 19:04:12 +01:00
Haselnussbomber
23e7c164d8
Convert BuddyMember to readonly struct 2025-11-13 19:04:11 +01:00
Haselnussbomber
8a9b47c7a4
Add struct enumerator to BuddyList 2025-11-13 19:03:56 +01:00
Haselnussbomber
520e3ea028
Convert AetheryteEntry to readonly struct 2025-11-13 19:03:53 +01:00
Haselnussbomber
dd70c5b8ee
Add struct enumerator to AetheryteList 2025-11-13 18:44:15 +01:00
Haselnussbomber
2b2f628096
Convert ObjectTable enumerator to struct 2025-11-13 18:44:14 +01:00
goaaats
6340afb692 Nuke schema, also remove analyzers from imgui testbed 2025-11-12 21:39:38 +01:00
goaaats
928fbba489 Remove Injector.Boot targets 2025-11-12 21:13:50 +01:00
goaaats
7bc921f543 No analyzers on nuke build 2025-11-12 21:09:21 +01:00
goaaats
a37a13e0ba Use .NET 10 in CI 2025-11-12 21:03:14 +01:00
goaaats
e0eff2fe74 Use standard apphost for Dalamud.Injector 2025-11-12 21:02:07 +01:00
goaaats
7d76d27555 Upgrade packages 2025-11-12 20:31:28 +01:00
goaaats
4e87b4b007 Retarget to .NET 10 2025-11-12 20:15:12 +01:00
goat
b11b769292
Merge pull request #2453 from Exter-N/get-plugin-by-assembly
Some checks failed
Tag Build / Tag Build (push) Successful in 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Add functions to get a plugin by assembly
2025-11-12 01:33:24 +01:00
goat
dc9ff0a54c
Merge pull request #2448 from Haselnussbomber/dtr-removenode-exception-fix
Fix KeyNotFoundException in DtrBar.RemoveNode
2025-11-12 00:49:40 +01:00
Haselnussbomber
45bd30fcca
Use inherited fields 2025-11-12 00:27:51 +01:00
Haselnussbomber
f635c149a2
Set TextNode to null after destroying it 2025-11-12 00:26:06 +01:00
Haselnussbomber
e1fde804ec
Add warning if RemoveNode can't find the node 2025-11-12 00:21:47 +01:00
goat
bcce4f4216
Merge pull request #2455 from Exter-N/dtr-screen-bounds
Add a property to get the bounds of a DTR entry
2025-11-12 00:04:50 +01:00
goat
9450f65159
Merge pull request #2456 from Haselnussbomber/autoupdatenullable
Fix IsAutoUpdateComplete throwing when unloaded
2025-11-11 23:55:14 +01:00
Haselnussbomber
bf0bd64faf
Fix IsAutoUpdateComplete throwing when unloaded 2025-11-11 23:37:10 +01:00
Exter-N
4cfe561c1c Add a property to get the bounds of a DTR entry 2025-11-11 21:28:30 +01:00
goaaats
f6cd6d31ff Adjust branch switcher to XL 7, pass beta kind and key as arguments 2025-11-11 20:29:06 +01:00
Exter-N
65237f84a2 Add functions to get a plugin by assembly
This is intended for advanced IPC scenarios, for example, accepting
a delegate or an object and identifying which plugin it originates
from, in order to display integration information to the user, and/or
to release references when the originating plugin is unloaded/reloaded
if it forgot to clean after itself.
2025-11-11 20:26:54 +01:00
goaaats
dabe7d777b build: 13.0.0.8 2025-11-11 19:27:46 +01:00
goat
93b95fd813
Merge pull request #2452 from KazWolfe/obsolete-fixes
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
fix: some minor IDE complaints
2025-11-11 17:50:32 +01:00
Kaz Wolfe
fe163fbb97
fix: some minor IDE complaints 2025-11-11 08:28:14 -08:00
bleatbot
963b3d9318
Update ClientStructs (#2447)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-11-11 07:27:07 -08:00
goat
bfb07580fe
Merge pull request #2451 from Haselnussbomber/SelfTestRegistry-fix
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
Pass down SelfTestRegistry to SelfTestWindow
2025-11-11 09:19:42 +01:00
Haselnussbomber
af03e292ba
Pass down SelfTestRegistry to SelfTestWindow 2025-11-11 09:09:29 +01:00
bleatbot
5dd121dfcc
Update Excel Schema (#2430)
Some checks failed
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-11-08 08:02:16 -08:00
Haselnussbomber
497e61f699
Remove comment about removed CS enum 2025-11-08 11:58:54 +01:00
Haselnussbomber
5cc327c5f9
Fix obsolete 2025-11-08 11:49:23 +01:00
Haselnussbomber
af8b61f08a
Update to use AgentUpdate event 2025-11-08 11:49:04 +01:00
Haselnussbomber
700aaa4a5d
Fix Unlock event not firing 2025-11-08 11:45:02 +01:00
Haselnussbomber
69caffeb97
Add support for EmjCostume rows 2025-11-08 11:45:02 +01:00
Haselnussbomber
a06c0e3ed2
Add support for EmjVoiceNpc rows 2025-11-08 11:45:01 +01:00
Haselnussbomber
880add5ab3
Add support for MKDLore rows 2025-11-08 11:45:01 +01:00
Haselnussbomber
193d321103
Add support for Soul Shard items 2025-11-08 11:45:01 +01:00
Haselnussbomber
6e8efabc3b
Add support for Occult Record items 2025-11-08 11:45:01 +01:00
Haselnussbomber
68c02caf37
Fix obsolete warnings 2025-11-08 11:45:01 +01:00
Haselnussbomber
878080d660
Fix IsChocoboTaxiStandUnlocked call 2025-11-08 11:45:00 +01:00
Haselnussbomber
986dfa04d0
Mark IUnlockState as experimental 2025-11-08 11:45:00 +01:00
Haselnussbomber
3746c47a84
Ignore RecipeData updates when not logged in
Just to be safe...
2025-11-08 11:45:00 +01:00
Haselnussbomber
c4dd75bdda
Update RecipeData when levels changed 2025-11-08 11:45:00 +01:00
Haselnussbomber
5905afdf10
Cache completed Quests in RecipeData too 2025-11-08 11:45:00 +01:00
Haselnussbomber
62fdd2c60d
Fix IsChocoboTaxiStandUnlocked 2025-11-08 11:45:00 +01:00
Haselnussbomber
ba159f8c5f
Add IsRecipeUnlocked 2025-11-08 11:44:59 +01:00
Haselnussbomber
6ade5b21cf
Add IUnlockState service 2025-11-08 11:44:59 +01:00
goat
3c3eb9159c
Merge pull request #2420 from Haselnussbomber/add-agent-events
Add events based on AgentUpdateFlag
2025-11-08 11:44:44 +01:00
Haselnussbomber
494d9a04fa
Remove unknown NameplateUpdate flag for now 2025-11-08 11:32:34 +01:00
Haselnussbomber
8fd49f261a
Unify agent update events into AgentUpdate 2025-11-08 11:31:10 +01:00
Haselnussbomber
2a65d1e045
Fix KeyNotFoundException in DtrBar.RemoveNode 2025-11-07 16:04:13 +01:00
goat
832edaf005
Merge pull request #2377 from KazWolfe/assert-blame
Some checks failed
Tag Build / Tag Build (push) Successful in 4s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
feat: Identify the plugin causing an assertion failure
2025-11-04 21:44:38 +01:00
KazWolfe
8a49a11dc0
fix: dont nag maintainers when nothing needs to be done (#2418) 2025-11-04 21:18:38 +01:00
goat
32e04458c6
Merge pull request #2416 from Haselnussbomber/fix-testing-api-level
Fix for testing plugins with older stable releases
2025-11-04 20:36:49 +01:00
goat
165060b62b
Merge pull request #2444 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-11-04 20:33:59 +01:00
goat
fc480d8542
Merge pull request #2431 from grittyfrog/push-plttolzpzvkr
Plugin-registerable self tests
2025-11-04 20:32:36 +01:00
github-actions[bot]
7751ea7185 Update ClientStructs
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-11-04 18:35:02 +00:00
goat
97600b1b2c
Merge pull request #2446 from nebel/nameplate-gameobj-more-null-checks-p2
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
Add yet another check to NamePlateUpdateHander.GameObject
2025-11-04 13:24:13 +01:00
nebel
417fe39cd9
Add yet another check to NamePlateUpdateHander.GameObject 2025-11-04 20:28:34 +09:00
goat
81e5793150
Merge pull request #2445 from Haselnussbomber/fix-imgui-callbacks
Some checks failed
Tag Build / Tag Build (push) Successful in 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Fix ImDrawCallback call
2025-11-02 15:58:24 +01:00
Haselnussbomber
ce16b59f5b
Fix ImDrawCallback call 2025-11-02 15:40:22 +01:00
goaaats
606d58c77e build: 13.0.0.7
Some checks failed
Tag Build / Tag Build (push) Failing after 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-10-29 20:57:06 +01:00
goaaats
55246ab1ec Enable sound effects by default for new users 2025-10-29 20:56:38 +01:00
goat
5290e191a1
Merge pull request #2443 from Haselnussbomber/auto-update-on-free-trial
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
Fix systems not working on the Free Trial
2025-10-29 09:18:36 +01:00
Haselnussbomber
08a3998854
Use IsClientIdle in CanUpdateOrNag 2025-10-29 02:31:42 +01:00
Haselnussbomber
e6df536ceb
Don't always flag as idle when on Free Trial 2025-10-29 02:13:58 +01:00
Haselnussbomber
76dab05cbd
Allow auto updates when on Free Trial 2025-10-29 02:11:46 +01:00
goat
22f21614ae
Merge pull request #2432 from goatcorp/csupdate-master
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
[master] Update ClientStructs
2025-10-29 00:29:18 +01:00
github-actions[bot]
11e85c6619 Update ClientStructs
Some checks failed
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-10-28 23:17:54 +00:00
goat
90dc48f7b2
Merge pull request #2426 from Haselnussbomber/eventtutorial-icon
Add BitmapFontIcon.EventTutorial
2025-10-29 00:16:32 +01:00
goat
c508632150
Merge pull request #2438 from Haselnussbomber/fix-resize
Fix AddonEventType.Resize
2025-10-29 00:16:15 +01:00
KazWolfe
de07a5f1b7
fix: missing args in HandleActionHoverHook (#2440)
- remove verbose logs
2025-10-29 00:15:56 +01:00
goat
31340dda84
Merge pull request #2442 from nebel/nameplate-gameobj-more-null-checks
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
Add more null checks to NamePlateUpdateHander.GameObject
2025-10-28 19:12:08 +01:00
nebel
4fff7dee5a
Add more null checks to NamePlateUpdateHander.GameObject 2025-10-28 09:20:31 +09:00
Haselnussbomber
39e6186ba3
Fix AddonEventType.Resize 2025-10-24 03:07:36 +02:00
goat
7a45c0d661
Merge pull request #2437 from Haselnussbomber/fix-NotifyPluginsForStateChange
Some checks failed
Tag Build / Tag Build (push) Successful in 4s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Lock plugin list in NotifyPluginsForStateChange
2025-10-22 21:50:40 +02:00
Haselnussbomber
87e391958e
Lock plugin list in NotifyPluginsForStateChange 2025-10-22 19:14:29 +02:00
goat
21d4dbec66
Merge pull request #2436 from Haselnussbomber/public-end-objects
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
Small ImRaii update
2025-10-22 15:10:41 +02:00
Haselnussbomber
f0568216cb
Provide PopupModal with flags 2025-10-22 07:18:43 +02:00
Haselnussbomber
8ed21b4645
Public ImRaii end objects 2025-10-22 07:16:13 +02:00
Haselnussbomber
0b6f3b8bcf
Add events based on AgentUpdateFlag 2025-10-19 16:51:47 +02:00
goaaats
116e8aadbc build: 13.0.0.6
Some checks failed
Tag Build / Tag Build (push) Failing after 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-10-16 21:10:27 +02:00
goaaats
2be6566e81 Fix broken spacing in update chat message 2025-10-16 20:34:37 +02:00
goat
49eac894a8
Merge pull request #2428 from Haselnussbomber/lumina-ssb-atktweaks
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 2s
Use Luminas SeStringBuilder in DalamudAtkTweaks
2025-10-16 00:46:06 +02:00
goat
16fdf4e32b
Merge pull request #2429 from goatcorp/csupdate-master
[master] Update ClientStructs
2025-10-16 00:01:15 +02:00
goaaats
76afcaf426 Disable CPM for libs 2025-10-15 23:24:21 +02:00
goaaats
7b723687a4 Use NuGet CPM to ensure consistent package versions
Fixes various issues with projects referencing different versions of libraries, causing deployment issues if build order differs
2025-10-15 22:58:26 +02:00
goaaats
168a334756 Injector: remove custom DllImports, replace with CsWin32 2025-10-15 22:58:26 +02:00
goaaats
5fd24f4bed Bump Reloaded.Memory.Buffers, fixes conflict with injector 2025-10-15 22:58:26 +02:00
github-actions[bot]
9fb0a79885 Update ClientStructs
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
2025-10-15 18:34:54 +00:00
goat
db22c5e111
Remove status badge from readme
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
2025-10-14 22:59:50 +02:00
goaaats
2207ad0886 Bump Reloaded.Memory.Buffers to simplify hook buffer acquisition 2025-10-14 22:51:17 +02:00
GrittyFrog
ae777000e2 Plugin-registerable self tests
The goal of this change is to let plugins register their own self-tests. 

We do this through the `ISelfTestRegistry` interface. For a plugin it
would look like this:

```csharp
[PluginService]
public ISelfTestRegistry SelfTestRegistry

// Somewhere that gets called by your plugin
SelfTestRegistry.RegisterTestSteps([
  new MySelfTestStep(),
  new MyOtherSelfTestStep()
])
```

Where `MySelfTest` and `MyOtherSelfTest` are instances of
the existing `ISelfTestStep` interface.

The biggest changes are to `SelfTestWindow` and the introduction of
`SelfTestWithResults`. I wanted to make sure test state wasn't lost when
changing the dropdown state and I was finding it a bit annoying to work
with the Dictionary now that we can't just rely on the index of the
item.

To fix this I moved all the "test run" state into `SelfTestWithResults`,
most of the changes to `SelfTestWindow` are derived from that, other
then the addition of the combo box.

The documentation for this service is a bit sparse, but I wanted to put
it up for review first before I invest a bunch of time making nice
documentation. 

I'm keen to hear if we think this is useful or if any changes are
needed.
2025-10-13 19:04:08 +11:00
Haselnussbomber
4a869bad3f
Use Luminas SeStringBuilder in DalamudAtkTweaks 2025-10-08 03:41:34 +02:00
Haselnussbomber
9852feaf08
Add RentedSeStringBuilder
Co-authored-by: Soreepeong <3614868+Soreepeong@users.noreply.github.com>
2025-10-08 03:41:31 +02:00
Haselnussbomber
26d2f764c6
Add BitmapFontIcon.EventTutorial 2025-10-08 03:05:39 +02:00
goaaats
4ac4505d72 build: 13.0.0.5
Some checks failed
Tag Build / Tag Build (push) Failing after 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-10-07 23:03:29 +02:00
Ottermandias
1ae7de26bf
Fix issue in SigScanner. (#2425)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 4s
2025-10-07 09:40:44 -07:00
bleatbot
1073227b83
Update ClientStructs (#2419)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-10-07 09:06:43 -07:00
bleatbot
11adffd700
Update Excel Schema (#2423)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-10-07 08:33:24 -07:00
Haselnussbomber
2cf869872d
Return IReadOnlyList instead of ReadOnlySpan 2025-10-06 02:08:18 +02:00
Haselnussbomber
bcf651b5c1
Fix FavoriteAetherytes 2025-10-06 01:38:35 +02:00
Haselnussbomber
a55c8ca773
Fix warning 2025-10-05 14:38:50 +02:00
Haselnussbomber
153870a053
Add mentor states 2025-10-05 14:37:22 +02:00
Haselnussbomber
c2fc04c3a8
Improve wording 2025-10-05 14:37:11 +02:00
Haselnussbomber
8cac486249
Add PluginInterface attribute to PlayerState 2025-10-05 14:36:55 +02:00
Haselnussbomber
4422622e1e
Add IPlayerState service 2025-10-05 13:49:25 +02:00
bleatbot
7bf79bdea6
Update ClientStructs (#2417)
Some checks failed
Tag Build / Tag Build (push) Successful in 1s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-09-29 21:13:08 +00:00
bleatbot
7813f5d201
Update ClientStructs (#2413)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 2s
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-09-29 09:09:32 -07:00
srkizer
aa3d6c3efe
Use long running task for plugin ctor (#2406) 2025-09-29 09:09:02 -07:00
Haselnussbomber
d61a35b81f
Update Settings Window (#2400)
* Load new localization before firing change event

* Update texts in SettingsWindow when locale changes

* Localize settings search

* Update settings search input

- Disable when Credits are scrolling,
so Search Results aren't shown instead
- Select all on single click, as usual for a search bar

* Remove unused IsVisible property

* Fix General tab being unselected on language change

* Fix search results throwing, oops

* Missed using LocRef in EnumSettingsEntry

* Set CultureInfo before loading locs

* Change it to LazyLoc instead

So CheapLoc can export localizations...
2025-09-29 09:08:25 -07:00
Haselnussbomber
efaff769b5
Rename Id fields to match CS names (#2405)
* Rename DataId to BaseId

* Fix obsoletes

* Inherit documentation
2025-09-29 09:06:28 -07:00
Haselnussbomber
9091216e1c
Update ClientState (#2410)
* Add MapChanged event to ClientState

* Add PublicInstanceId with event to ClientState

* Set eventhandlers to null

* Rework events and add ZoneInit event
2025-09-29 09:02:53 -07:00
Haselnussbomber
9c5e4f5a32
Add JsonIgnore attribute on IsAvailableForTesting 2025-09-28 16:21:25 +02:00
Haselnussbomber
87adb2dfb7
More expressive code 2025-09-28 16:00:09 +02:00
Haselnussbomber
8edbc0ee78
Ignore user testing opt-in for manifest eligibility check 2025-09-28 15:59:08 +02:00
Haselnussbomber
d1fbee2829
Remove manifest API filter in installer
The API is already checked in `PluginManager.IsManifestEligible`,
so a plugin not matching it doesn't even get here.
2025-09-28 15:39:36 +02:00
Haselnussbomber
191aa8d696
Move IsAvailableForTesting to IPluginManifest 2025-09-28 15:31:46 +02:00
goat
0bc44154aa
Merge pull request #2414 from Haselnussbomber/sestring-evaluator-and-creator-fixes
Some checks failed
Tag Build / Tag Build (push) Successful in 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
SeString Evaluator/Creator fixes
2025-09-23 17:07:16 +02:00
Haselnussbomber
1633e68b76
Fix/simplify range handling 2025-09-23 13:16:39 +02:00
Haselnussbomber
dceeccb242
Load valid sheets for AddFromSheetPopup asynchronously 2025-09-22 23:56:28 +02:00
Haselnussbomber
2625f51021
Fix missing macro strings in AddFromSheetPopup 2025-09-22 23:55:16 +02:00
Haselnussbomber
69a8bdd638
Update GlobalParameters list 2025-09-22 23:49:57 +02:00
Haselnussbomber
9447708058
Fix Copy MacroString button 2025-09-22 23:36:39 +02:00
Haselnussbomber
e2e3a01cc3
Fix SeString Creator input length cutting off long macro strings 2025-09-22 23:33:30 +02:00
Haselnussbomber
9b0c275b8b
Fix ExpressionType.Weekday being off by 1 2025-09-22 23:29:40 +02:00
Caraxi
0b3a5a713e
enforce limits on alpha slider (#2412)
Some checks failed
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-09-21 20:24:48 -07:00
bleatbot
c38365bc99
Update ClientStructs (#2398)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-09-21 20:23:53 -07:00
bleatbot
3d0790e650
Update Excel Schema (#2408)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-09-21 20:23:03 -07:00
goat
1cc9071ce4
Update SeStringEvaluator, Payload, AutoTranslatePayload (#2411)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 3s
* Handle multiple col- entries in Completion.LookupTable

* Pass correct length to DecodeImpl

* Skip to payload end instead of reading remaining bytes

* Use SeStringEvaluator in AutoTranslatePayload

* Rework range check

- Loops through all entries
- Bumped amount of cols up to 8 for future proofing
- Use Ordinal search
- Actually parse ranges and check if the RowId is allowed

* Only check range if it has ranges

* Fix Group being off by 1

* Add AutoTranslatePayload re-encode test

* Remove redundant checks

* Add some AutoTranslatePayload.Text self-tests

* Remove AutoTranslatePayload text cache

- Lumina lookups aren't that taxing anymore
- Cache wasn't invalidated when the language override changes
2025-09-21 22:34:19 +02:00
Haselnussbomber
ce1faa50cf
Remove AutoTranslatePayload text cache
- Lumina lookups aren't that taxing anymore
- Cache wasn't invalidated when the language override changes
2025-09-21 14:00:23 +02:00
Haselnussbomber
374f9fcbd0
Add some AutoTranslatePayload.Text self-tests 2025-09-21 03:10:00 +02:00
Haselnussbomber
9b224857f1
Remove redundant checks 2025-09-21 03:08:28 +02:00
Haselnussbomber
0bb87d87b7
Add AutoTranslatePayload re-encode test 2025-09-21 02:18:55 +02:00
Haselnussbomber
c03e7ecfe6
Fix Group being off by 1 2025-09-21 02:16:09 +02:00
Haselnussbomber
c264fb134e
Only check range if it has ranges 2025-09-20 04:09:23 +02:00
Haselnussbomber
d8555f207e
Rework range check
- Loops through all entries
- Bumped amount of cols up to 8 for future proofing
- Use Ordinal search
- Actually parse ranges and check if the RowId is allowed
2025-09-20 03:54:41 +02:00
Haselnussbomber
3f037e5d20
Use SeStringEvaluator in AutoTranslatePayload 2025-09-20 02:51:00 +02:00
Haselnussbomber
93a44842ed
Skip to payload end instead of reading remaining bytes 2025-09-20 02:51:00 +02:00
Haselnussbomber
327ebf3bb3
Pass correct length to DecodeImpl 2025-09-20 02:50:54 +02:00
Haselnussbomber
bf4fc7864f
Handle multiple col- entries in Completion.LookupTable 2025-09-20 02:50:49 +02:00
goaaats
fa58d7b3cc build: 13.0.0.4
Some checks failed
Tag Build / Tag Build (push) Failing after 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-09-12 01:42:45 +02:00
Asriel
a2b3fb901e
Fix Lumina.Excel branch in workflow (#2407)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 2s
2025-09-11 17:06:07 +00:00
Asriel
5b5fdc0c10
Generalize submodules workflow to add Lumina.Excel (#2340)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 2s
2025-09-10 16:55:25 -07:00
MidoriKami
f07b308757
Add Generic Helper (#2403)
Some checks failed
Tag Build / Tag Build (push) Successful in 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-09-09 17:53:38 +10:00
Caraxi
0047e24031
fix ViewportTextureWrap (#2402)
Some checks failed
Tag Build / Tag Build (push) Successful in 3s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-09-06 14:32:30 +10:00
goaaats
1b08d986ca build: 13.0.0.3
Some checks failed
Tag Build / Tag Build (push) Failing after 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-09-03 01:51:14 +02:00
Limiana
29c4d4939f
Fix SigScanner.ScanAllText only returning a single match 2025-09-02 18:06:06 +00:00
bleatbot
97196a1e35
Update ClientStructs (#2396)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-09-02 17:13:17 +00:00
Infi
7aa77aa2eb
- Use ToMacroString for better understanding what changed (#2393)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 2s
2025-09-02 22:06:02 +10:00
Haselnussbomber
7f3fc5aac1
Use AgentLobbys OnLogout vfunc (#2395) 2025-09-02 22:01:12 +10:00
bleatbot
852b1289e2
Update ClientStructs (#2390)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-09-02 21:59:20 +10:00
MidoriKami
e3498f1b9c
Fix entries yeeting themselves out of existance when scrolled off (#2392)
Some checks failed
Tag Build / Tag Build (push) Successful in 2s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-08-30 12:59:05 +02:00
bleatbot
80c2985795
Update ClientStructs (#2389)
Some checks failed
Tag Build / Tag Build (push) Successful in 1s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-26 21:00:59 +00:00
bleatbot
6890e59af5
Update ClientStructs (#2387)
Some checks are pending
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Tag Build / Tag Build (push) Successful in 2s
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-26 06:43:35 +00:00
bleatbot
fcf5c27186
Update ClientStructs (#2378)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-26 16:32:09 +10:00
Kaz Wolfe
8cced4c1d7
fix: use channel threadlocal instead of a ThreadStatic 2025-08-25 13:31:05 -07:00
Kaz Wolfe
b18b8b40e5
feat: Add IPC Context
- Demo of the ipc context idea
- Accessible via ipcPub.GetContext()
2025-08-25 13:14:13 -07:00
Blair
005699e472
Allow versionless bans (#2381)
Some checks failed
Tag Build / Tag Build (push) Successful in 12s
Build Dalamud / Build on Windows (push) Has been cancelled
Build Dalamud / Check API Compatibility (push) Has been cancelled
Build Dalamud / Deploy dalamud-distrib staging (push) Has been cancelled
2025-08-22 13:03:44 +00:00
Lyna
ba9720cb65
Add Platform/OS to plugin feedback (#2379) 2025-08-20 11:06:52 +02:00
bleatbot
1702c3be29
Update ClientStructs (#2375)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-19 21:44:26 +00:00
R
4a743e9060
Collect additional logs for crash handler (#2376)
* Collect launcher.log on crashes

This has been missing for Crash Handler based tspacks for a while, which makes it harder to get information from XLCore users because of our differing log names. (Really, we should rename both to just be xivlauncher.log or something, but that would break existing tooling)

* Collect wine.log on crashes
2025-08-19 14:35:55 -07:00
Kaz Wolfe
0c9176a8b6
feat: Reword message overview
- Removes extra lines from stack trace
- Use clearer-ish wording for messaging
2025-08-19 12:44:34 -07:00
Kaz Wolfe
9e405b26d2
feat: include line numbers/file info in stacktrace 2025-08-19 12:16:34 -07:00
Kaz Wolfe
32cb6e2127
feat: Identify the plugin causing an assertion failure 2025-08-19 11:07:14 -07:00
goaaats
2affbe3683 build: 13.0.0.2 2025-08-17 13:10:21 +02:00
Soreepeong
544f8b28bf Support make clickthrough 2025-08-16 16:42:30 +09:00
bleatbot
e684e7e208
Update ClientStructs (#2371)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-15 16:20:38 +00:00
Haselnussbomber
f687852879
Set ClientState.TerritoryType on load (#2366)
* Set TerritoryType on load

* Do not fire TerritoryChanged event on load
2025-08-15 16:19:21 +00:00
Haselnussbomber
e2f3fdd0ff
ISeStringEvaluator: Add ReadOnlySpan<byte> support (#2370)
* Add EvaluateMacroString ROS<byte> overload

* Add implicit ROS<byte> to SeStringParameter cast
2025-08-15 09:11:25 -07:00
MidoriKami
ef688c09e2
Now with more child labor (#2374) 2025-08-15 15:38:04 +00:00
srkizer
9092e36b33
Reduce usage of exceptions from Boot (#2373)
* wip

* make pretty

* Remove CRT version check from IM

* fix

* Simplify IsDebuggerPresent hook
2025-08-15 00:02:32 -07:00
Soreepeong
e5451c37af Update InputHandler to match changes in imgui_impl_win32.cpp 2025-08-12 16:18:49 +09:00
Soreepeong
40e63f2d9a Enable viewport alpha 2025-08-12 14:10:55 +09:00
bleatbot
f613b177a2
Update ClientStructs (#2369)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-12 01:28:24 +00:00
wolfcomp
6337e165aa
Update Directory.Build.props (#2368) 2025-08-11 18:13:06 -07:00
goat
86ad1de181
Fix codeberg ssh endpoint 2025-08-11 21:58:25 +02:00
goat
98d9bf3a93
Disable strict host key checking 2025-08-11 21:57:06 +02:00
KazWolfe
6341640243
feat: Add GitLab sync (#2312)
* feat: Add GitLab sync

* Add codeberg to sync script

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
2025-08-11 21:51:00 +02:00
marzent
ae58aaff30
avoid c++ exceptions in import hook (#2367) 2025-08-11 11:23:15 -07:00
bleatbot
4a62138b30
Update ClientStructs (#2365)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-11 16:15:27 +00:00
bleatbot
8134cde2b7
Update ClientStructs (#2364)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-10 18:31:52 +00:00
goaaats
594684b8e9 build: 13.0.0.1 2025-08-10 20:15:40 +02:00
goaaats
10b081e901 Upgrade Lumina to 6.5.0
Adds support for BC5/7 decoding
2025-08-10 19:14:11 +02:00
srkizer
b09a1cde0a
Skip if msvcrt dll does not have version info (#2363) 2025-08-10 18:49:41 +02:00
goaaats
6c1e538da5 Don't check CRT version on wine 2025-08-10 17:38:19 +02:00
goat
4eb49fd449
Only run cs update on master 2025-08-10 17:00:22 +02:00
bleatbot
1db4f2e5cb
Update ClientStructs (#2359)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-10 16:59:56 +02:00
Haselnussbomber
184c8c8fa2
SeStringEvaluator: Add support for SwitchPlatform (#2360) 2025-08-10 16:57:18 +02:00
srkizer
8fcf633f02
Check if CRT version is at least 14.40.33816.0 (#2361)
* Check if CRT version is at least 14.40.33816.0

* Fix ULD path

* Remove debugging code
2025-08-10 16:56:22 +02:00
goat
4413634508
Merge pull request #2352 from Soreepeong/fix/imgui
ImGui bindings fixes
2025-08-09 12:30:46 +02:00
Soreepeong
b66860cdba Add cond=0 default to SetDragDropPayload 2025-08-09 10:14:30 +09:00
Soreepeong
bd824130bd Fix InputTextEx return type 2025-08-09 09:58:14 +09:00
Soreepeong
130eb7e574 Fix Dx11Renderer order of operations 2025-08-09 08:51:57 +09:00
Soreepeong
7705aa800b Use hInstance of Dalamud for RegisterClassExW 2025-08-09 08:30:27 +09:00
Soreepeong
0ce4f6d598 Add back ImGui.ArrowButton 2025-08-09 08:23:38 +09:00
Soreepeong
afe58dae76 Make ImU8String not IDisposable 2025-08-09 08:23:38 +09:00
Soreepeong
5fee90085c Revert "Fix SeStringRendererTestWidget example"
This reverts commit 12f099a57e.
2025-08-09 08:23:38 +09:00
Soreepeong
cc21480d21 Add overloads for InputText which callbacks take Ptr instead 2025-08-09 08:23:38 +09:00
Soreepeong
28658b4889 Fix ImGui.Combo overload resolution priority 2025-08-09 08:23:38 +09:00
Soreepeong
87770c57ab Change focus clearing condition 2025-08-09 08:23:38 +09:00
Soreepeong
5d8e4bee92 Fix ImGuiTextFilter function types 2025-08-09 08:23:37 +09:00
Soreepeong
3e2a6ec9cb Work around ActiveIdUsingKeyInputMask having a wrong type 2025-08-09 08:23:37 +09:00
Haselnussbomber
b3dcdb4539
Fix ImGui.MenuItem calls (again) (#2358) 2025-08-08 15:32:55 -07:00
bleatbot
126a4d32c9
Update ClientStructs (#2357)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-08 20:34:03 +00:00
bleatbot
d006395caf
Update ClientStructs (#2356)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-08 15:14:16 +00:00
bleatbot
f640a6ad27
Update ClientStructs (#2354)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-07 22:25:51 +00:00
bleatbot
fcbb174985
Update ClientStructs (#2321)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-08-07 22:07:58 +00:00
KazWolfe
3d300a7811
feat: Add DtrInteractionEvent to allow plugins to make their own DTR events. (#2353) 2025-08-07 23:37:42 +02:00
goat
579ecdc4b2 Merge branch 'api13' 2025-08-07 13:38:52 +02:00
goat
c39c957923
Merge pull request #1865 from Critical-Impact/ui-builder-fonts
Add font properties to IUiBuilder
2025-08-07 13:33:18 +02:00
goat
99b5ef4894
Merge pull request #2351 from goatcorp/csupdate-api13
[api13] Update ClientStructs
2025-08-07 13:32:17 +02:00
github-actions[bot]
a43bf1a3d0 Update ClientStructs 2025-08-07 11:23:53 +00:00
goat
5240378753
api13 csupdate 2025-08-07 13:23:13 +02:00
goat
abb017f190
Merge pull request #2350 from Soreepeong/fix/imgui
Temporarily fix IME
2025-08-07 13:22:07 +02:00
Soreepeong
20842a64e2 Temporarily fix IME 2025-08-07 13:45:42 +09:00
Kaz Wolfe
245e1614e8
deps: bump clientstructs, lumina 2025-08-06 20:32:26 -07:00
srkizer
1f06006cc0
Fix combobox callback impl (#2347)
* Fix combobox callback impl

* Make ImGuiBackend delegates public

* Release ImGui focus when the game window loses focus
2025-08-06 19:18:40 -07:00
Haselnussbomber
27f924f3b1
Restore commandId parameter (#2349) 2025-08-06 19:17:34 -07:00
goat
0ca890ebca
Merge pull request #2346 from Soreepeong/fix/imgui
Fix/imgui
2025-08-07 02:11:38 +02:00
Soreepeong
5440e9b0b8 Make it more likely for OpenLink target app to be focused 2025-08-07 08:45:58 +09:00
Soreepeong
75a1742af9 Remove dupe code 2025-08-07 08:24:09 +09:00
Soreepeong
e05dab96c6 Match ObjectPool version with Lumina 2025-08-07 08:13:27 +09:00
Soreepeong
4a2f890aa9 Add ClearWindowFocus 2025-08-07 07:52:46 +09:00
Soreepeong
12f099a57e Fix SeStringRendererTestWidget example 2025-08-07 07:51:20 +09:00
Soreepeong
9f31ba7177 Explicitly release focus when clicking on non-imgui 2025-08-07 07:51:13 +09:00
Soreepeong
2fbccb2e95 Make OpenLink happen from the main thread 2025-08-07 07:50:45 +09:00
Soreepeong
ebe184b10c Handle nullptr/Span.Empty handling, add explicit overload for ImU8String.AppendFormatted(object) 2025-08-07 07:48:38 +09:00
Kaz Wolfe
4dcc81bdfc
deps: bump cs 2025-08-06 13:19:38 -07:00
Kaz Wolfe
d23504acdd
deps: bump cs 2025-08-06 11:10:22 -07:00
Kaz Wolfe
e4e970d2e6
deps: bump cs 2025-08-06 10:36:30 -07:00
Kaz Wolfe
616c87e15e
deps: bump CS 2025-08-06 09:12:52 -07:00
Kaz Wolfe
7625a34c3a
deps: bump cs 2025-08-06 09:06:00 -07:00
srkizer
3e40cad063
Use custom GetPinnableReference instead of deferring it to Span (#2345) 2025-08-06 09:03:50 -07:00
wolfcomp
2cd5c5bc68
fix input scalar not working the same way as normal imgui code (#2343) 2025-08-06 09:01:58 -07:00
Critical
7883e2e8fa Add font properties to IUiBuilder 2025-08-06 19:23:03 +10:00
Kaz Wolfe
69b4ed941f
fix: cs bump breaks 2025-08-05 23:32:58 -07:00
Kaz Wolfe
36b9f0b218
deps: bump ClientStructs 2025-08-05 22:59:49 -07:00
Kaz Wolfe
4d0d62f679
deps: bump CS 2025-08-05 19:20:01 -07:00
Haselnussbomber
24b2329c93
Update ConfigOptions (#2342) 2025-08-05 16:34:17 -07:00
Kaz Wolfe
7976f49444
deps: It came to my attention
that some people didn't enjoy the Rick and Morty joke in the prior commit. After careful consideration, I have decided that you have to have a very high IQ to understand Rick and Morty. The humor is extremely subtle, and without a solid grasp of theoretical physics most of the jokes will go over a typical viewer's head. There's also Rick's nihilistic outlook, which is deftly woven into his characterisation - his personal philosophy draws heavily from Narodnaya Volya literature, for instance. The fans understand this stuff; they have the intellectual capacity to truly appreciate the depths of these jokes, to realize that they're not just funny- they say something deep about LIFE. As a consequence people who dislike Rick and Morty truly ARE idiots- of course they wouldn't appreciate, for instance, the humour in Rick's existencial catchphrase "Wubba Lubba Dub Dub," which itself is a cryptic reference to Turgenev's Russian epic Fathers and Sons I'm smirking right now just imagining one of those addlepated simpletons scratching their heads in confusion as Dan Harmon's genius unfolds itself on their television screens. What fools... how I pity them. 😂 And yes by the way, I DO have a Rick and Morty tattoo. And no, you cannot [User was demoted for this commit message]
2025-08-05 16:29:28 -07:00
Kaz Wolfe
ef243a2759
deps: what is my purpose?
- to bump clientstructs.
  - oh my god.
2025-08-05 13:40:53 -07:00
Kaz Wolfe
dc28194ade
fix: addonargs name 2025-08-05 12:34:38 -07:00
Kaz Wolfe
39060d2501
deps: bump cs
- fix a random warning too
2025-08-05 11:15:36 -07:00
Blair
8d29e6b44d
Interface ActivePluginsChangeEventArgs (#2341) 2025-08-05 10:38:11 -07:00
Kaz Wolfe
6213f7627e
deps: bump lumina 2025-08-05 08:58:14 -07:00
Kaz Wolfe
1f94c486a0
deps: boing boing boing 2025-08-05 06:40:20 -07:00
Kaz Wolfe
2f9363b9cc
chore: clean up lumina obsoletes and warnings 2025-08-04 23:24:37 -07:00
Kaz Wolfe
82f9db4ca6
deps: bump ClientStructs
- Minimal 7.3 to at least load
2025-08-04 23:14:36 -07:00
Kaz Wolfe
0fb44f5fe5
fix broken sig 2025-08-04 23:14:35 -07:00
Haselnussbomber
094fcc86d4
Add support for SheetSub payload (#2283) 2025-08-04 23:12:18 -07:00
Asriel
d6aa6b8b64
[API 13] Add Lumina.Excel as submodule (#2332)
* WIP on excel-submodule

* Add Lumina.Excel to build

* Fix sheet changes
2025-08-04 23:11:59 -07:00
Kaz Wolfe
ff36f08d0c
bump cs, fix warnings 2025-08-04 21:11:06 -07:00
Soreepeong
c19ea6ace3 Add ITextureProvider.CreateTextureFromSeString 2025-08-05 11:48:02 +09:00
Kaz Wolfe
d3bd5f1dce
fix: web merging is bad idea 2025-08-04 16:08:12 -07:00
Nukoooo
83d32fe02c
Read .text section bytes from file instead of process memory (#2251)
* Read .text section bytes from file instead of memory

* format

* fix oversight

* only read bytes from file once
2025-08-04 15:55:34 -07:00
Haselnussbomber
9ad0d86463
Update ConditionFlag (#2338) 2025-08-04 15:52:27 -07:00
Ottermandias
d28a164d8c
Add events for certain style changes from within Dalamud. (#2277)
* Add events for certain style changes from within Dalamud.

* Capture reset changes in events too.

* Add non-static versions of events to IUiBuilder, add remarks about timing to events.

* Move statics to InterfaceManager members and make plugin events local.

---------

Co-authored-by: KazWolfe <KazWolfe@users.noreply.github.com>
2025-08-04 15:52:13 -07:00
MidoriKami
bf5fcaaf00
IObjectTable Helpful Enumerables (#2328)
* Add ObjectTable Enumerables

* Put kind check on the correct function
2025-08-04 15:47:39 -07:00
Haselnussbomber
58fbff7c56
Update text-related ImGui calls (#2337)
* Update text-related ImGui calls

* Use ImU8String for SafeTextColored

* Restore wrapped calls

* Update MenuItem call

* Use ImGui.Text over ImGui.TextUnformatted

* Add ImGui.TextColoredWrapped

* Obsolete SafeText helpers

* Fix obsoleted calls

* SafeTextColored didn't exist before imgui-bindings

* Remove %% replacements
2025-08-04 15:46:43 -07:00
Kaz Wolfe
f0021bc8f9
chore: Fix compiler complaints 2025-08-04 11:46:45 -07:00
Kaz Wolfe
413fea4349
chore: re-run generator
- add generated code to linguist ignore
2025-08-04 11:42:58 -07:00
Kaz Wolfe
832288a76e
fix: compiler issue with MenuItem
Resolves #2336.
2025-08-04 11:34:23 -07:00
Kaz Wolfe
bd52c60c6f
Merge branch 'imgui-bindings' into api13
# Conflicts:
#	Dalamud/Game/Gui/GameGui.cs
#	Dalamud/Interface/Internal/UiDebug.cs
#	Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs
#	Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs
2025-08-04 11:30:14 -07:00
srkizer
c69329f592
Manual overloads for ImGui functions accepting text (#2319)
* wip2

* Implement AutoUtf8Buffer

* reformat

* Work on manual bindings

* restructure

* Name scripts properly

* Update utility functions to use ImU8String

* add overloads

* Add more overloads

* Use ImGuiWindow from gen, support AddCallback

* Use LibraryImport for custom ImGuiNative functinos

* Make manual overloads for string-returning functinos

* Make all overloads with self as its first parameter extension methods

* Fix overload resolution by removing unnecessary

* in => scoped in

* Fix compilation errors
2025-08-04 11:14:00 -07:00
Haselnussbomber
0c63541864
Fix disabled MenuItems (#2318) 2025-08-04 11:13:34 -07:00
Kaz Wolfe
cb29322968
chore: Remove IGameNetwork
- Deprecated in favor of hooks. See #2241.
- Subject to Goat yelling at me.
2025-08-03 18:39:38 -07:00
Haselnussbomber
191dfb57e3
[API13] Fire ActivePluginsChanged after a plugin loaded/unloaded (#2334)
* Fire ActivePluginsChanged after a plugin loaded/unloaded

* Add ActivePluginsChangedEventArgs

* Use past tense
2025-08-03 18:28:44 -07:00
wolfcomp
7131ad36a6
Update MarketBoardSelfTestStep.cs (#2309)
Use correct time offset from the game display in the test text.
2025-08-03 18:26:58 -07:00
Blair
0f8b65e95a
Load imgui.so if available for external tools running imgui on native (#2331) 2025-08-03 18:25:30 -07:00
Status102
5426cfc723
fix: typo in JobFlags (#2202) 2025-08-03 18:18:53 -07:00
MidoriKami
6369982b48
IDtrBar Add Additional Click Events (#2325)
* Add additional dtr click events

* Let's just break things and make them really nice

* Add additional dtr click events

* Let's just break things and make them really nice

* Add additional dtr click events

* Let's just break things and make them really nice

* git is stupid

* Documentation fixing
2025-08-03 18:15:05 -07:00
Kaz Wolfe
6a1d5db50e
chore: Bump to v13
- Actually makes the api lockut take effect
- Fix Intellisense complaints
2025-08-03 18:14:03 -07:00
Haselnussbomber
a776358b96
Completion rewrite (#2305) 2025-08-03 18:13:45 -07:00
Haselnussbomber
63e7cb25b5
[Api13] Update ChatLinkHandler functions (#2322)
* Update ChatLinkHandler functions

- Move functions to IChatGui
- Switch CommandId to type Guid and generate them automatically

* Remove unused field
2025-08-03 18:07:21 -07:00
Kaz Wolfe
ecbb4053ce
feat: Add MateriaEntries
Per #2225.
2025-08-03 18:04:08 -07:00
Haselnussbomber
ff934d981c
Remove Experimental from ISeStringEvaluator (#2327) 2025-08-03 17:44:16 -07:00
Haselnussbomber
57c6089fc1
[Api13] Add native wrapper structs (#2330) 2025-08-03 17:43:52 -07:00
Haselnussbomber
b425ee0a2a
Move ItemKind to Dalamud.Utility (#2324) 2025-07-26 12:51:20 -07:00
Haselnussbomber
564c220ed2
[Api13] Remove obsoletes (#2323)
* Remove IFate.HasExpBonus

* Remove IAddonEventManager.AddonEventHandler

* Remove obsolete filesystem functions from Util

* Remove more obsoletes
2025-07-26 12:50:42 -07:00
bleatbot
2aba71f8f2
[master] Update ClientStructs (#2310)
* Update ClientStructs

* Update ClientStructs

---------

Co-authored-by: github-actions[bot] <noreply@github.com>
Co-authored-by: Kaz Wolfe <root@kazwolfe.io>
2025-07-25 22:57:24 +00:00
goat
cc741cec67
Merge pull request #2317 from Soreepeong/fix/imgui-bindings/crt-dll
Use Shared VC Runtime
2025-07-20 13:15:49 +02:00
Soreepeong
84f2f0d064 Use Shared VC Runtime 2025-07-20 19:43:58 +09:00
goaaats
e559ae6b20 Regenerate bindings 2025-07-20 01:24:17 +02:00
goat
6078c42963
Merge pull request #2316 from Haselnussbomber/fixes
[imgui-bindings] Small fixes
2025-07-19 02:05:41 +02:00
Haselnussbomber
ec9f365930
Fix some ImGui.End calls 2025-07-18 18:14:34 +02:00
Haselnussbomber
476b6b9613
Call ImPlot.DestroyContext 2025-07-18 18:14:12 +02:00
goat
6efbb71790
Merge pull request #2315 from Haselnussbomber/imgui-bindings-fixes
[imgui-bindings] Fixes and removals
2025-07-17 02:08:49 +02:00
Haselnussbomber
7c2c74418f
[imgui-bindings] Add ReadOnlySpan<byte> ImRaii/ImGuiHelpers overloads (#2314)
* Add ReadOnlySpan<byte> ImRaii overloads

* Add ReadOnlySpan<byte> ImGuiHelpers overloads
2025-07-17 01:56:38 +02:00
Haselnussbomber
a1b8dbcf27
Simplify ImGuiListClipper initialization 2025-07-17 01:54:40 +02:00
Haselnussbomber
d25b16aa93
Remove unnecessary AddText extension 2025-07-17 01:45:08 +02:00
Haselnussbomber
9956424c13
Remove unnecessary IsNull functions 2025-07-17 01:42:20 +02:00
Haselnussbomber
54ec64e159
Remove IDalamudTextureWrap.ImGuiHandle 2025-07-17 01:36:45 +02:00
Haselnussbomber
72f5da2214
Generate CsWin32 APIs as internally 2025-07-17 01:32:54 +02:00
Haselnussbomber
abea3c4089
Remove ImGui.NET-472 from solution 2025-07-17 01:32:41 +02:00
Kaz Wolfe
f63ee5cb76
Merge branch 'master' into imgui-bindings 2025-07-03 19:02:35 -07:00
goaaats
68656f2b40 TextureManagerPluginScoped must depend on TextureManager to ensure unload order 2025-07-03 15:08:31 +02:00
goaaats
b429c77e3e build: 12.0.1.5 2025-07-03 14:31:17 +02:00
bleatbot
94c7f843b7
Update ClientStructs (#2306)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-07-03 14:23:29 +02:00
bleatbot
0cbb4be2b5
Update ClientStructs (#2301)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-06-25 17:12:15 +00:00
srkizer
03e728e129
Use EnumerateInvocationList instead of GetInvocationList (#2303) 2025-06-23 22:09:48 +02:00
goaaats
90e426b325 Remove ImGui.NET entirely 2025-06-22 22:21:12 +02:00
goaaats
95ec633cc5 merge 2025-06-22 21:39:38 +02:00
MidoriKami
13306e24ba
Refactor IAddonEventManager (#2299) 2025-06-17 10:51:00 -07:00
Haselnussbomber
b1986bd3d1
Self-Test Window improvements (#2298)
* Move Logout self-test to the bottom of the list

* Increase self-test result and make it scrollable

* Allow text wrapping for some strings in self-tests

* Fix context menu self-test not working properly on HQ items
2025-06-17 10:48:40 -07:00
goat
6a42568073 build: 12.0.1.4 2025-06-17 17:41:36 +02:00
bleatbot
12ee9343d4
Update ClientStructs (#2296)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-06-17 15:36:26 +00:00
bleatbot
0691241240
Update ClientStructs (#2293)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-06-15 12:28:02 -07:00
goaaats
adec948319 build: 12.0.1.3 2025-06-09 21:19:04 +02:00
bleatbot
db98959158
Update ClientStructs (#2290)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-06-07 16:57:07 +00:00
MidoriKami
7c4e1c44a6
Update NodeTree.Component.cs (#2291) 2025-06-07 18:55:13 +02:00
bleatbot
878d892631
Update ClientStructs (#2286)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-06-02 18:07:19 -07:00
goaaats
8ce676f2f1 build: 12.0.1.2 2025-06-01 19:13:11 +02:00
salanth357
e2609bbe0c
Improve performance in the Completion module (#2288)
Only resort the data entries when we modify the lists. Also use EncodeWithNullTerminator to ensure the safety of strings.

Also avoid parsing the category names when we're only looking for the presence of the Dalamud category
2025-05-31 13:24:28 +02:00
goaaats
a2f6fb85e5 Don't fade windows if we are docked or collapsed, for now
The proper fix would be not to use the "fake window" approach and insert a drawlist, but not with the current bindings
2025-05-31 13:20:54 +02:00
goaaats
abde79dbc8 Re-add toggle window commands to CorePlugin 2025-05-31 12:00:40 +02:00
goaaats
eb34eb1023 Fix some warnings 2025-05-31 12:00:20 +02:00
goaaats
bf0dbde55f Completion: Don't create Utf8String before the game has initialized 2025-05-29 21:08:03 +02:00
salanth357
84d121c7bc
Add Completion module (#2274)
* Add Completion module

Dalamud and plugin commands will now be tab-completable in the ChatLog

* PR feedback

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
2025-05-29 18:24:21 +00:00
goaaats
944c3700db build: 12.0.1.1 2025-05-29 19:40:32 +02:00
bleatbot
ed8a455dad
Update ClientStructs (#2280)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-05-29 19:39:03 +02:00
Haselnussbomber
911999e98c
Update AddonEventType (#2279) 2025-05-29 19:38:29 +02:00
Haselnussbomber
e20f132abe
Add ISeStringEvaluator.EvaluateMacroString (#2281) 2025-05-29 19:38:10 +02:00
Haselnussbomber
3f3a1f2be1
Add support for sheet payload links (#2282)
* Fix "Print Evaluated" not evaluating with context

* Add support for sheet payload links
2025-05-29 19:37:48 +02:00
Haselnussbomber
e52e0b6df9
Scrollable Self-Test table (#2284) 2025-05-29 19:35:38 +02:00
srkizer
e415699bb3
DrawListTextureWrap: use two textures (#2285)
Making premultiplied pixel data into straight alpha in-place using UAV
seems to be not working on older graphics cards. Now every instance of
DrawListTextureWrap keeps two GPU textures, where one keeps a
premultiplied data which will be written to using ImGui draw data and
read from to calculate straight alpha pixel data.
2025-05-29 17:06:48 +02:00
goaaats
d80202a755 build: 12.0.1.0 2025-05-27 22:50:02 +02:00
Aireil
544eb39753
Add new kinds to FlyTextKind enum (#2278) 2025-05-27 20:58:29 +02:00
bleatbot
a12147c945
Update ClientStructs (#2276)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-05-27 15:56:34 +00:00
bleatbot
452ff3adb3
Update ClientStructs (#2269)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-05-26 21:16:58 +02:00
MidoriKami
fe8efc9dde
UIDebug2 Timeline Labelset support (#2275)
* Add LabelSet Display

* Update TimelineTree.cs
2025-05-26 21:09:50 +02:00
MidoriKami
43ab6f6f63
UiDebug2 Misc Fixes (#2273)
* Fix incorrect type check

* Fix address calculation
2025-05-24 00:44:24 +02:00
goaaats
15352a3e23 Upgrade to goatcorp.Reloaded.Hooks 4.2.0-goatcorp5
Possibly fixes an issue with Wine 10
2025-05-17 12:57:55 +02:00
goaaats
421d8cee3b Revert "Respect fadein/out for Window.DrawConditions() and Window.IsOpen"
This reverts commit c33a5346b1.
2025-05-10 19:31:06 +02:00
goaaats
c33a5346b1 Respect fadein/out for Window.DrawConditions() and Window.IsOpen 2025-05-10 18:37:34 +02:00
goaaats
5c33d150cd Add Window.OnSafeToRemove(), tune timings a bit more 2025-05-10 14:21:30 +02:00
goaaats
e9aa2e2ac3 Respect alpha when drawing installer loading overlay 2025-05-10 13:23:32 +02:00
goaaats
271c258e40 Add per-window opt-out for fades 2025-05-10 13:16:12 +02:00
goaaats
9afece8679 Improve fade-in, use delta time, draw fade-out texture on fake window to preserve focus order 2025-05-10 13:08:24 +02:00
goaaats
b4e62571a6 Add option to auto-update disabled plugins 2025-05-10 01:10:53 +02:00
goaaats
b1c874c123 Fade in/out window system windows 2025-05-10 00:59:28 +02:00
Haselnussbomber
2c735e9ec3
Do not throw ObjectDisposedException in IsEnabled and Disable (#2266) 2025-05-09 23:10:35 +02:00
goaaats
bf491525f6 Upgrade cimgui, use custom FindWindowByName instead of hardcoded offset 2025-05-09 23:06:31 +02:00
Haselnussbomber
33605e3ace
Move AntiDebug to xivfixes (#2264)
* Move AntiDebug to xivfixes

* Update BootEnabledGameFixes

* Check BootEnabledGameFixes

* Apply suggestions from code review

Co-authored-by: KazWolfe <KazWolfe@users.noreply.github.com>

---------

Co-authored-by: KazWolfe <KazWolfe@users.noreply.github.com>
2025-05-09 13:53:44 -07:00
srkizer
4dce0c00e8
Implement DrawListTextureWrap (#2036)
* Implement DrawListTextureWrap

* Fix unloading

* minor fixes

* Add CreateFromClipboardAsync

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
2025-05-09 22:47:42 +02:00
goaaats
a12c63d6a2 Fix notification positioning when multi-monitor is enabled 2025-05-09 22:08:09 +02:00
goaaats
df8de39098 Even more boot load logging 2025-05-07 23:49:32 +02:00
goaaats
ac14d61a86 Add some more logging to boot plugin loads 2025-05-07 22:40:05 +02:00
goaaats
5c5ce37a70 Fix docs warning 2025-05-07 22:38:10 +02:00
goaaats
50fdc2e5c5 TSM should force itself to front if it is expanded 2025-05-07 00:00:48 +02:00
goaaats
2b49170f6a Add configurable "anchor position" for notifications 2025-05-07 00:00:32 +02:00
goaaats
20ef5fb919 build: 12.0.0.15 2025-05-03 17:47:44 +02:00
foophoof
09f519ce6f
Convert PluginStatWindow to ImRaii (#2268)
Currently the Hooks tab asserts because of a missing ImGui.EndTabItem. Instead of just adding that, I took the opportunity to convert everything to use ImRaii instead.
2025-05-01 21:05:42 -07:00
goaaats
85b77226e9 Clarify wording in settings and error notifications 2025-05-02 02:22:06 +02:00
goaaats
5304c9abc3 Fix CS obsoletion 2025-05-02 02:13:24 +02:00
goaaats
60ca710fa7 build: 12.0.0.14 2025-05-02 00:10:15 +02:00
bleatbot
987227492e
Update ClientStructs (#2263)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-05-01 18:59:03 +00:00
Haselnussbomber
30b5c0be11
Update ConditionFlag (#2265) 2025-05-01 20:49:30 +02:00
goaaats
ddf0a97c83 Add plugin error notifications, per-plugin event invocation wrappers 2025-05-01 20:47:03 +02:00
goaaats
1913a4cd2c Provide services in the same order in Debug and Release 2025-05-01 16:38:57 +02:00
goaaats
22430ce054 Make ConsoleManagerPluginScoped internal as it's supposed to be 2025-05-01 14:47:02 +02:00
goaaats
69d8968dca IoC: Allow private scoped objects to resolve singleton services 2025-05-01 14:47:02 +02:00
goaaats
c82bb8191d Show exception in service init error message 2025-05-01 14:47:02 +02:00
bleatbot
febf4e55a4
Update ClientStructs (#2260)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-04-28 21:38:30 +02:00
Asriel
fd1eafddfb
Update Lumina to 5.7.0 and Lumina.Excel to 7.2.2 (#2258) 2025-04-28 21:13:48 +02:00
goat
f5d93fb08e
Add MinimumDalamudVersion to manifest, validate at install, update and load (#2248) 2025-04-28 21:09:40 +02:00
Haselnussbomber
82cdcf0a0c
Use GameInventoryType in InventoryWidget (#2257) 2025-04-26 19:19:51 +02:00
goaaats
ced601e2f5 build: 12.0.0.13 2025-04-26 12:48:07 +02:00
goaaats
4633820e48 build: 12.0.0.12 2025-04-26 12:47:44 +02:00
goaaats
a925e37ceb Actually respect remember state flag 2025-04-26 12:47:44 +02:00
Aireil
0bddf30577
Fix fly text combo in Dalamud Data (#2262) 2025-04-26 01:28:12 +02:00
goaaats
b25fceb900 build: 12.0.0.11 2025-04-25 23:01:01 +02:00
goaaats
06851ab14a Add separators between profiles and entries for better visual clarity 2025-04-25 22:59:04 +02:00
goaaats
731d7e0f6e Un-whether-or-not the codebase 2025-04-25 22:49:05 +02:00
goaaats
f4102db488 Add "startup behavior" to profiles
Choose between remember, always enable, always disable
2025-04-25 22:49:05 +02:00
goaaats
08f959444b Add "restart in safe mode" button to blocked message 2025-04-25 22:49:05 +02:00
goaaats
999e3ea57a ForceSave() when enabling safe mode from the exception window 2025-04-25 22:49:05 +02:00
goaaats
0db49a5642 DalamudConfiguration.ForceSave should wait for the save task to exit 2025-04-25 22:49:05 +02:00
Haselnussbomber
ce49b0d51f
SeStringEvaluator: fallback to games ClientLanguage (#2261) 2025-04-25 20:59:44 +02:00
goaaats
b996ff5e10 Theme progress bars and plots by default 2025-04-25 19:01:56 +02:00
goaaats
f482badd8e Add progress bar to boot plugin loads, show which are pending 2025-04-25 18:54:01 +02:00
goaaats
e29171cc99 Fix race condition in plugin load
When opening the installer while boot plugins are still loaded, it may have been possible for plugins to be added to the installed plugins list twice, causing various statekeeping issues
2025-04-24 21:59:36 +02:00
bleatbot
096f9c3e52
Update ClientStructs (#2256)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-04-24 17:51:53 +00:00
goaaats
d881fea12b build: 12.0.0.10 2025-04-22 21:53:23 +02:00
Aireil
a9299b4aea
Add Dataset to FlyTextKind enum (#2255) 2025-04-22 12:03:06 -07:00
bleatbot
4995383fcc
Update ClientStructs (#2244)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-04-22 14:44:43 +00:00
Tupae
cf5a6f2635
Add a command line for multimonitor toogle (#2254)
* Update DalamudCommands.cs

Added a /xlmulti command for toggling multimonitor.

Using Dalamud local build encounters Assertion Failure with Umbra plugin enabled. Otherwise works fine.

* Update DalamudCommands.cs

replaced /xlmulti with /xltogglemultimonitor to be more explicit

---------

Co-authored-by: Fractal <Fractal@FRACTAL>
2025-04-22 14:34:36 +00:00
Haselnussbomber
aea62732e5
Safer AddonEventManager node removal (#2232)
* Remove events on Framework thread

* Make sure the addon is not unloaded

* Cleanup node finding loop

* Wait for PluginEventController to be disposed
2025-04-22 07:34:19 -07:00
Haselnussbomber
61a17dac28
SeString Creator and Evaluator fixes (#2250)
* Fix SeString Creator example

* SeStringEvaluator: Don't print auto translation brackets for categories

* SeStringEvaluator: Fix map id mask

MapId is a ushort, not a byte.
2025-04-21 14:02:26 +02:00
Haselnussbomber
39ac9f9dad
Make ItemUtil public (#2252) 2025-04-21 14:01:59 +02:00
goaaats
c7cc771ee2 Upgrade Reloaded.Hooks/Reloaded.Assembler to address concurrency problems 2025-04-20 15:16:46 +02:00
goaaats
672793b6c0 Add hook stress test 2025-04-17 17:23:23 +02:00
goaaats
4e724fbe45 Remove old PInvoke libs 2025-04-15 23:58:01 +02:00
Haselnussbomber
81ced564d6
Better ImGui setup condition (#2249)
* Better ImGui setup condition

* Fix build
2025-04-15 21:44:10 +00:00
goaaats
90b15d45c6 Add dummy ImGuiScene project to make XL happy 2025-04-15 23:26:09 +02:00
goaaats
cb8d9cc397 Replace update message with link to changelog
...instead of pointing to Discord
2025-04-15 21:43:23 +02:00
Cytraen
af1eb275cf
sort by search score in plugin installer if opened w/ search text (#2246) 2025-04-15 21:33:28 +02:00
goaaats
b810154125 Add MinimumDalamudVersion to manifest, validate at install, update and load 2025-04-15 21:29:18 +02:00
goaaats
3f724170b2 Fix a warning 2025-04-15 21:14:24 +02:00
goaaats
2f0c57d5ad Reassign leftover Api12ToDo to Api13ToDo 2025-04-15 19:12:07 +02:00
goaaats
561db8620a Switch to org fork 2025-04-14 22:51:17 +02:00
goaaats
1841014155 build: 12.0.0.9 2025-04-14 21:33:50 +02:00
goaaats
dc83879c89 Texture handle shim 2025-04-14 21:20:39 +02:00
goaaats
6858c646a2 Fix conds in Window 2025-04-14 21:06:38 +02:00
KazWolfe
e52ac55d91
deps: bump clientstructs (#2243)
- fix fate HasExpBonus
- remove deprecation from concrete class
  - kept on interface
2025-04-14 19:57:56 +02:00
nebel
708f3d0ab2
Improve null checking for badly set properties in NamePlateGui (#2245) 2025-04-14 19:54:53 +02:00
goaaats
47b4a9b502 Uncomment checks 2025-04-13 22:03:18 +02:00
goaaats
71d7ba8c88 remove bad cast 2025-04-13 21:59:45 +02:00
goaaats
308b9e4575 Re-add ImGui.NET, add compatibility shims 2025-04-13 21:52:27 +02:00
goaaats
b2fd7cc9e1 revert accidental injector changes 2025-04-13 20:48:31 +02:00
goaaats
fe562e8cf3 Remove most hand-authored native functions 2025-04-12 22:20:55 +02:00
goaaats
2787e37548 build: 12.0.0.8 2025-04-12 12:18:08 +02:00
Blair
26aaf974bc
Add menu functions to ImRaii (#2227) 2025-04-09 22:16:51 +02:00
Haselnussbomber
98c5fbd666
Update AddonEventType (#2238)
* Update AddonEventType

* Fix copy paste mistake
2025-04-09 22:16:28 +02:00
Blair
a555514de3
Show correct changelog in plugin installer window (#2236) 2025-04-09 22:15:09 +02:00
Ottermandias
d3dd3ab7c7
Add configurable default sorting to FileDialog. (#2233) 2025-04-09 22:14:47 +02:00
Haselnussbomber
bc18198435
Fix kilo macro not having thousands separators (#2237) 2025-04-09 22:13:27 +02:00
Haselnussbomber
499952b3d2
SeStringEvaluator: Fix HeadAll not capitalizing correctly (#2240)
* Fix obsoletes

* Fix HeadAll not capitalizing correctly

* Fix incorrect denoun cases in SeString Creator

* Implement Utf8String.ToUpper in C#

* Handle characters with accents too

* Add remarks to ToUpper functions
2025-04-09 22:13:11 +02:00
goaaats
1bc216ccd6 Use bindings for DalamudIme 2025-04-08 22:21:36 +02:00
goaaats
ad3b0f0194 Fix some warnings 2025-04-08 22:21:04 +02:00
goaaats
af2b451955 Fix window additions 2025-04-08 00:46:27 +02:00
goaaats
0fd4cfee68 Fix ImRaii.TabItem 2025-04-08 00:13:14 +02:00
goaaats
eaadd3d136 Regen cimgui 2025-04-07 21:49:03 +02:00
goaaats
b8ce2d4001 Add standalone testbed 2025-04-07 20:05:11 +02:00
Haselnussbomber
f96e2ae37c
Fix reading world name for PcName (#2235) 2025-04-06 21:36:26 +00:00
Haselnussbomber
48968322c4
Update ConditionFlag enum (#2234) 2025-04-06 23:30:45 +02:00
goaaats
5ddf473450 Fix nothing rendering, oops 2025-04-06 21:50:55 +02:00
goaaats
b5a8bfe399 move bindings around 2025-04-06 21:08:34 +02:00
goaaats
1bce618684 Add generated files for now 2025-04-06 20:59:55 +02:00
goaaats
0690cce995 wip bindings upgrade 2025-04-06 20:59:23 +02:00
goaaats
bd7e56850a Remove ImGui.NET, add Hexa.NET.ImGui 2025-04-03 21:54:19 +02:00
goat
8213a29ef8
Merge pull request #1923 from Soreepeong/imguiscene-inside
Take ImGuiScene into Dalamud
2025-04-03 21:43:40 +02:00
goaaats
3bfb7b9d17 Upgrade ImGui.NET 2025-04-03 21:32:05 +02:00
goaaats
7166ab2fd7 Fix warning 2025-04-03 21:23:52 +02:00
goaaats
844d9a3b18 Fix duplicate package reference 2025-04-03 21:23:45 +02:00
goaaats
e2650807c1 Fix sln 2025-04-03 21:15:58 +02:00
goaaats
2951dc93ec Merge master 2025-04-03 21:14:12 +02:00
goaaats
97b01d697b Delay IM hook setup to after the game sets up rendering
This seems to (hack)fix some odd behavior around shader caching as reported by various users.
2025-04-03 19:15:12 +02:00
goaaats
f812bf8f09 Correctly test Framework Run vs. RunOnTick 2025-04-03 19:13:42 +02:00
goaaats
c1805bd510 Fix warning 2025-04-03 00:37:20 +02:00
goaaats
ca438b6e69 Add self-test for framework task scheduling 2025-04-03 00:32:37 +02:00
goaaats
810611e086 Add "jump to" button to self-test window 2025-04-03 00:17:20 +02:00
goaaats
7286d9714c Add question to toast self test 2025-04-03 00:03:16 +02:00
goaaats
d2cae32bc2 AgingSteps => SelfTestSteps 2025-04-02 22:38:28 +02:00
goaaats
95bac801b2 Remove 2025 april fools 2025-04-01 23:16:35 +02:00
goaaats
5eee678899 Unify body notice drawing logic, clear search when switching groups 2025-04-01 23:15:31 +02:00
goaaats
34ca8b8f99 Fix updates failing due to flipped WorkingPluginId check 2025-04-01 19:58:19 +02:00
goaaats
34679d085b Extract plugin to temp directory before copying to final location, some minor cleanup 2025-04-01 18:55:27 +02:00
goaaats
f2c89bfc00 build: 12.0.0.7 2025-04-01 01:54:01 +02:00
goaaats
2a049c40d6 Make it lame again 2025-04-01 01:53:14 +02:00
goaaats
4a1f7824ce build: 12.0.0.6 2025-04-01 01:28:05 +02:00
goaaats
c67ed345b7 Add prizes 2025-04-01 01:27:44 +02:00
goaaats
46c0a86375 build: 12.0.0.5 2025-03-31 22:51:28 +02:00
goaaats
104504f6a6 Add 2025 april fools 2025-03-31 22:51:12 +02:00
goaaats
3d04d75811 build: 12.0.0.4 2025-03-31 18:31:00 +02:00
KazWolfe
126e96689b
fix: Repair broken contextmenu test (#2231)
- Bump clientstructs
2025-03-31 18:25:47 +02:00
bleatbot
d86232dbe8
Update ClientStructs (#2228)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-30 20:51:42 +00:00
goaaats
7cb0d61fe4 Use RunOnTick instead 2025-03-29 20:09:26 +01:00
goaaats
dac14b9168 Add test for fastfail exception 2025-03-29 20:04:41 +01:00
goaaats
813bb1d360 build: 12.0.0.3 2025-03-29 19:06:48 +01:00
bleatbot
37ca457d97
Update ClientStructs (#2226)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-29 17:54:10 +00:00
bleatbot
a9fd92ad62
Update ClientStructs (#2224)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-29 05:53:25 +00:00
goaaats
106b59dc45 build: 12.0.0.2 2025-03-28 23:25:38 +01:00
Haselnussbomber
0e87856bd2
Remove MateriaSlotCount check (#2223) 2025-03-28 22:16:17 +00:00
goaaats
541c073c32 build: 12.0.0.1 2025-03-28 22:41:00 +01:00
Haselnussbomber
0d9eca1877
Fix GameInventoryItem array initialization (#2222) 2025-03-28 18:32:23 +00:00
bleatbot
baf796f5e1
Update ClientStructs (#2221)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-28 17:51:54 +00:00
Haselnussbomber
b28fab2d08
More InvokeSafely calls (#2215)
* Safely invoke DutyState event handlers

* Safely invoke GameGui event handlers
2025-03-28 09:19:25 -07:00
bleatbot
c1557df585
Update ClientStructs (#2216)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-28 16:07:55 +00:00
Bloodsoul
e5e3b2780c
Update FateState.cs (#2218)
Updating to 7.2 according to related changes in CS
01c4dfe8ca
2025-03-28 16:07:35 +00:00
Haselnussbomber
6160252418
Update GameInventoryItem (#2219)
* Update GameInventoryItem

- Resolve symbolic InventoryItem, used in HandIn
- Harden Materia/MateriaGrade/Stains results
- Make sure GameInventoryItem is constructed correctly

* Remove some duplicate code from InventoryWidget

* Fix null check
2025-03-28 09:00:14 -07:00
Haselnussbomber
f5b3d85066
Bump Lumina.Excel to 7.2.1 (#2220) 2025-03-28 08:57:39 -07:00
goaaats
fb61d72fae Remove legacy corrupted state exceptions entirely 2025-03-28 00:52:06 +01:00
KazWolfe
9c80b72e5e
deps: bump clientstructs (#2217)
- and fix the errors
2025-03-27 23:48:12 +00:00
bleatbot
6dddfa1597
Update ClientStructs (#2213)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-27 02:57:35 +00:00
AllunEve
0a06a9cbbd
Astral fire and Umbral ice are Gone. (#2214)
Co-authored-by: Alluneve <jeroen@buelens.com>
2025-03-26 19:48:32 -07:00
Caraxi
46ed621761
Add BitmapFontIcons from 7.2 (#2212) 2025-03-26 23:20:37 +00:00
goaaats
f94f03e114 Merge branch 'api12' 2025-03-26 20:44:48 +01:00
goat
202da067c1
ci: disable rollup 2025-03-26 20:43:09 +01:00
marzent
8320a824ce
return an error HRESULT when dotnet runtime can't be found (#2209) 2025-03-26 12:38:27 -07:00
Blair
19ba6a961f
Switch INotificationManager and ITitleScreenMenu to use ISharedImmediateTexture (#1879)
* Switch INotificationManager and ITitleScreenMenu to use ISharedImmediateTexture #1879

* Remove SetIconTexture
Remove some remarks that no longer apply

* Cleanup StyleCop warnings
2025-03-26 20:36:18 +01:00
Kaz Wolfe
81bc0012ed
more cs bumpies uwu
- boing
- boing
- boing
2025-03-26 12:12:36 -07:00
Kaz Wolfe
494291511c
*CS bumping noises* 2025-03-26 09:52:36 -07:00
Aireil
43e380493f
Fix FlyTextKind enum (#2207)
* Fix FlyTextKind enum

* Remove the experimental tags
2025-03-25 20:25:10 -07:00
Haselnussbomber
2f029567e4
Fixing colors and NounResolver (#2208)
* Fixing colors and NounResolver

* Remove failing special case NounProcessor selftest
2025-03-25 17:32:24 -07:00
Kaz Wolfe
cba1a7d18c
fix: AddonLifecycle sig, cs bump 2025-03-25 16:17:21 -07:00
Kaz Wolfe
dc30651fb6
deps: bump cs 2025-03-25 13:02:20 -07:00
marzent
abb5cf5dd1
fix wine_get_host_version calling convention (#2206) 2025-03-25 18:43:18 +01:00
Haselnussbomber
fe5ce40a97
Fix ISeStringEvaluator service not resolving (#2204) 2025-03-25 09:38:21 -07:00
Haselnussbomber
0377e84765
Config Options for 7.2 (#2205) 2025-03-25 09:37:18 -07:00
Kaz Wolfe
c5af536032
docs: Explain what the UI*Payloads actually are. 2025-03-25 09:35:21 -07:00
Kaz Wolfe
67561af32f
fix: Lumina updates 2025-03-25 09:28:57 -07:00
Kaz Wolfe
12ce09870f
deps: bump deps
- Lumina.Excel to 7.2.0
- ClientStructs to latest

Co-authored-by: Asriel Camora <asriel@camora.dev>
2025-03-25 09:28:42 -07:00
Kaz Wolfe
919ca87bbe
update clientstructs
- fix broken gamepad sig by delegating to CS
2025-03-24 21:58:23 -07:00
Kaz Wolfe
445fe09181
fix: dalamud boot deadlock over wine 2025-03-24 18:50:39 -07:00
Kaz Wolfe
27dc659561
chore: Convert AsLuminaSeString to AsReadOnlySeString
- Somewhat more useful, or so one would hope.
2025-03-24 18:32:44 -07:00
Kaz Wolfe
a7509ef77d
feat: More utilities!
- Also document that CStringExtensions only works with bundled CS.
2025-03-24 18:16:39 -07:00
Kaz Wolfe
314c046ec9
feat: Add CStringPointer#ExtractText
- Move CString extensions to their own class.
- Add some words to our dictionary.
2025-03-24 17:45:17 -07:00
goaaats
ddda4d477d Make VS happy about SLN changes 2025-03-24 21:54:57 +01:00
goaaats
24358f5af6 Fix warning in injector 2025-03-24 21:41:55 +01:00
Kaz Wolfe
5dcd1cc52f
chore: fix angry compiler 2025-03-24 13:36:01 -07:00
goaaats
9a25946ec1 Associate Dalamud.Common and LocExporter with x64 configs 2025-03-24 21:29:07 +01:00
Kaz Wolfe
2176b32219
chore: Bump ClientStructs and make it build again 2025-03-24 13:25:13 -07:00
goaaats
9e3c03d0e8 Add deprecation warning to targets file 2025-03-24 20:45:16 +01:00
goaaats
d1e6e34f40 Bump to version 12 2025-03-24 20:44:57 +01:00
goaaats
f209ac087a SeStringEvaluator: Throw if not on main thread 2025-03-24 20:24:34 +01:00
Kaz Wolfe
84f5dad0a3
fix: JobGauge enum casing 2025-03-24 11:31:19 -07:00
goaaats
c0c61c66be Fix warnings 2025-03-24 19:21:04 +01:00
goaaats
fc562778da Rename SMN enums to follow code standards 2025-03-24 19:19:44 +01:00
goaaats
61a4f3bcef Re-remove obsolete ServicePointManager TLS version override 2025-03-24 19:17:59 +01:00
goaaats
a1305159dc Remove unused enumerator object pool from ObjectTable 2025-03-24 19:17:43 +01:00
goaaats
daeb923f6d Add missing declaration for utils::is_running_on_wine() 2025-03-24 19:16:52 +01:00
Kaz Wolfe
30cadef34b
fix: support XL_PLATFORM as well (hopefully) 2025-03-24 09:12:13 -07:00
Haselnussbomber
fdbfdbb2cd
Add SeStringEvaluator service (#2188)
* Add SeStringEvaluator service

* Move DrawCopyableText into WidgetUtil

* Use Icon2RemapTable in SeStringRenderer

* Beautify some code

* Make sure to use the correct language

* Add SeString Creator widget

* Fix getting local parameters

* Update expressionNames

* misc changes

* Use InvariantCulture in TryResolveSheet

* Add SeStringEvaluatorAgingStep

* Fix item id comparisons

* Add SheetRedirectResolverAgingStep

* Add NounProcessorAgingStep

* Update SeString.CreateItemLink

This also adds the internal ItemUtil class.

* Fix name of SeStringCreator widget

* Add Global Parameters tab to SeStringCreatorWidget

* Load widgets on demand

* Update SeStringCreatorWidget

* Resizable SeStringCreatorWidget panels

* Update GamepadStateAgingStep

* Experimental status was removed in #2144

* Update SheetRedirectResolver, rewrite Noun params

* Fixes for 4 am code

* Remove incorrect column offset

I have no idea how that happened.

* Draw names of linked things

---------

Co-authored-by: Soreepeong <3614868+Soreepeong@users.noreply.github.com>
Co-authored-by: KazWolfe <KazWolfe@users.noreply.github.com>
2025-03-24 09:00:27 -07:00
marzent
7cac19ce81
Implement dalamud-platform launch argument (#1452)
* implement dalamud platform launch arg
* implement cross-platform gamePath fallback
* refactor platform detection heuristic
* add cross platform dalamud runtime detection
2025-03-24 08:42:24 -07:00
Vanillaaaa
9815cf1d88
perf: show whether plugin toggle is enable or disable (#2189) 2025-03-23 21:38:47 -07:00
Kaz Wolfe
8939cac80f
fix: actually add values 2025-03-23 18:45:15 -07:00
Kaz Wolfe
aaa3e33a8b
chore: PluginLoadReason is flags 2025-03-23 18:37:44 -07:00
Kaz Wolfe
e84654005e
fix: Force devs to choose between ValueClamped and ValueUnclamped 2025-03-23 18:28:07 -07:00
Kaz Wolfe
3074115b34
chore: Add api 13 tracker 2025-03-23 18:27:48 -07:00
goat
1676522ea8
clamp value of all easings by default
(cherry picked from commit 1aada98393)
2025-03-23 18:15:29 -07:00
Kaz Wolfe
c3df199e29
chore: Obsolete accessing ObjectTable off main thread 2025-03-23 18:12:41 -07:00
Kaz Wolfe
6a1cea731f
chore: remove obsoleted AppendMacroString methods 2025-03-23 18:11:03 -07:00
Ethan Henderson
23c87015b6
add(DRKGauge): Add DeliriumComboStep, and its enum DeliriumStep (#2198) 2025-03-23 15:14:16 -07:00
Ethan Henderson
82dd4629cb
SMNGauge: Fix typo, update CS (#2197)
* chore(SMNGauge): update gauge to latest CS struct
* chore(SMNGauge): Fix `AttunementTimerRemaining` typo
* refactor(SMNGauge): Cast `AttunementType` to a new enum instead of leaving it raw
* fix(SMNGauge): Use `AttunementType` for `Is<X>Attuned`
2025-03-23 15:13:27 -07:00
KazWolfe
a2124bb73d fix: Attempt to better handle hook disposal (#1803)
- Use a Weak Concurrent Collection to track scoped hooks
- Make `Hook`s remove themselves from the Tracked Hook list.
2025-03-13 22:42:09 +01:00
Nathan C
203d80c602 Build adjustments for code annotation (#2155)
* Drop special formatting, allowing annotations to work properly.

* Suppress duplicate warnings, adding a prefix to prevent annotation

* Tweak message, don't rebuild on test

* Move testing into same job step

* Only run PR build on newly opened PRs

* flip build order, derp

* Test suppressing summary for annotations...

* Get the build order right, testing without conditionals...

* Run tests after compile, suppress warnings from test

* Reverted previous change to `main.yml`.

* Drop special formatting, allowing annotations to work properly.

* Suppress duplicate warnings, adding a prefix to prevent annotation

* Tweak message, don't rebuild on test

* Move testing into same job step

* Only run PR build on newly opened PRs

* flip build order, derp

* Test suppressing summary for annotations...

* Get the build order right, testing without conditionals...

* Run tests after compile, suppress warnings from test

* Reverted previous change to `main.yml`.

* Drop special formatting, allowing annotations to work properly.

* Suppress duplicate warnings, adding a prefix to prevent annotation

* Tweak message, don't rebuild on test

* Move testing into same job step

* Only run PR build on newly opened PRs

* flip build order, derp

* Test suppressing summary for annotations...

* Get the build order right, testing without conditionals...

* Run tests after compile, suppress warnings from test

* Reverted previous change to `main.yml`.

* Add conditional for CI builds, add --skip-tests to make up for the combined build/test step.

* Behavior change, now requires arg `ci` to be passed to trigger tests. Tests can also be manually triggered with `test`.
2025-03-13 22:42:08 +01:00
KazWolfe
577977350f
fix: Attempt to better handle hook disposal (#1803)
- Use a Weak Concurrent Collection to track scoped hooks
- Make `Hook`s remove themselves from the Tracked Hook list.
2025-03-13 22:16:28 +01:00
Nathan C
ee2c8dd9cc
Build adjustments for code annotation (#2155)
* Drop special formatting, allowing annotations to work properly.

* Suppress duplicate warnings, adding a prefix to prevent annotation

* Tweak message, don't rebuild on test

* Move testing into same job step

* Only run PR build on newly opened PRs

* flip build order, derp

* Test suppressing summary for annotations...

* Get the build order right, testing without conditionals...

* Run tests after compile, suppress warnings from test

* Reverted previous change to `main.yml`.

* Drop special formatting, allowing annotations to work properly.

* Suppress duplicate warnings, adding a prefix to prevent annotation

* Tweak message, don't rebuild on test

* Move testing into same job step

* Only run PR build on newly opened PRs

* flip build order, derp

* Test suppressing summary for annotations...

* Get the build order right, testing without conditionals...

* Run tests after compile, suppress warnings from test

* Reverted previous change to `main.yml`.

* Drop special formatting, allowing annotations to work properly.

* Suppress duplicate warnings, adding a prefix to prevent annotation

* Tweak message, don't rebuild on test

* Move testing into same job step

* Only run PR build on newly opened PRs

* flip build order, derp

* Test suppressing summary for annotations...

* Get the build order right, testing without conditionals...

* Run tests after compile, suppress warnings from test

* Reverted previous change to `main.yml`.

* Add conditional for CI builds, add --skip-tests to make up for the combined build/test step.

* Behavior change, now requires arg `ci` to be passed to trigger tests. Tests can also be manually triggered with `test`.
2025-03-13 22:01:39 +01:00
goat
2c00bf5b21
Merge pull request #2195 from goatcorp/net9-rollup
[net9] Rollup changes from master
2025-03-13 21:57:01 +01:00
github-actions[bot]
18e19f7b49 Merge remote-tracking branch 'origin/master' into net9-rollup 2025-03-13 19:34:36 +00:00
KazWolfe
2f0c31d024
fix: Use builtin method for target management (#2194)
- Properly validate target preconditions
2025-03-13 20:34:13 +01:00
bleatbot
2e2c73f707
[net9] Rollup changes from master (#2193)
* feat: handle UNC paths in FileDialog (#2191)

* Remove command from assembly map when removed from command map (#2183)

---------

Co-authored-by: Alex Vallière <6031413+AlexValliere@users.noreply.github.com>
Co-authored-by: Blair <criticalimpact@gmail.com>
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-12 23:27:12 +01:00
Blair
e47b816495
Remove command from assembly map when removed from command map (#2183) 2025-03-12 23:26:37 +01:00
goaaats
97add3fc90 Globally disable legacy corrupted state exceptions for now 2025-03-12 23:23:31 +01:00
Alex Vallière
3a990b77f0
feat: handle UNC paths in FileDialog (#2191) 2025-03-12 21:51:33 +00:00
goaaats
b21a0c9298 Add console argument/env var to disable legacy corrupted state exceptions policy 2025-03-09 19:34:26 +01:00
goaaats
cf1d8a81c6 Remove obsolete ServicePointManager TLS version override 2025-03-08 16:27:52 +01:00
goat
89dfd72e24
ci: build with 9.0.200 SDK 2025-03-08 16:13:54 +01:00
goaaats
6604678050 merge from master 2025-03-08 16:10:25 +01:00
goaaats
01b3a5428e build: 11.0.8.0 2025-03-04 20:25:47 +01:00
bleatbot
d255a8ff27
Update ClientStructs (#2181)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-03-04 16:32:34 +00:00
Cytraen
e6017f96c0
fix: only enable ImGui asserts when adding a valid dev plugin path (#2185) 2025-03-01 14:39:13 +01:00
Cytraen
3a9dc48c11
fix: trim quotes from dev plugin before validation (#2186) 2025-03-01 14:38:14 +01:00
nil
cd03ef7d02
fix: always scan for dev plugins when opening the plugin installer (#2184)
* fix: scan for dev plugins on plugin installer window open

* fix: show scan dev plugins button based on plugin locations and not installed plugins
2025-03-01 01:37:58 +00:00
goaaats
dd47147538 Installer: if main-repo cross update is available, adjust warning message 2025-02-27 21:38:12 +01:00
goaaats
defa49865c Fix warning 2025-02-27 21:37:05 +01:00
goaaats
3340bfd205 Remove confusing comment about IDisposable behavior from ISharedImmediateTexture, it no longer inherits from IDisposable 2025-02-26 21:09:15 +01:00
goaaats
3bd380c854 build: 11.0.7.0 2025-02-26 00:06:33 +01:00
goaaats
38ff24e8cd Merge branch 'rc' 2025-02-26 00:05:41 +01:00
bleatbot
df78a4ae04
Update ClientStructs (#2179)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-02-25 18:04:36 +00:00
goaaats
03562189e4 build: 11.0.6.0 2025-02-16 15:34:50 +01:00
Jackson
0ada421460
Expose more of LocalPlugin to ExposedPlugin (#2177) 2025-02-15 19:58:04 +00:00
bleatbot
f8a5fbac39
Update ClientStructs (#2175)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-02-15 20:46:21 +01:00
Lyna
5dd097d72b
Pinning window disables close on escape key down (#2178)
* Pinning window disables close on escape key down

* Update tooltip for window pinning
2025-02-06 19:55:06 +01:00
goat
af1ddf5bfb build: 11.0.5.3 2025-01-21 20:59:02 +01:00
KazWolfe
6434972b7d
deps: Bump Lumina to 5.6.1 (#2171) 2025-01-21 20:55:26 +01:00
bleatbot
724d14248d
Update ClientStructs (#2174)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-01-21 19:22:56 +00:00
bleatbot
9d2264eec3
Update ClientStructs (#2173)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-01-21 16:24:51 +00:00
bleatbot
121b3d9a00
Update ClientStructs (#2168)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-01-20 22:26:52 +01:00
goat
a72dc92411 make window config dirty when disabling clickthrough with title bar button 2025-01-20 22:11:17 +01:00
goat
56acb7dead build: 11.0.5.2 2025-01-20 22:03:51 +01:00
goat
d779408fdc don't set IsDocsBuild flag on project 2025-01-10 19:57:08 +01:00
goat
d7279f5f21 don't build cimgui components if we're building for docs 2025-01-10 19:36:20 +01:00
srkizer
d932f2f06e
fix: apply scale to TSM entry (#2169) 2025-01-10 17:52:54 +01:00
Haselnussbomber
f2c132c7d8
Allow setting a DisplayOrder on commands (#2162)
* Allow setting a DisplayOrder on commands

* Linq on demand

* Sort commands in /help alphabetically

* note default in xmldoc

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
2025-01-09 21:15:14 +00:00
bleatbot
0b9a2fd9c8
Update ClientStructs (#2163)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-01-09 22:12:21 +01:00
KazWolfe
bed591d890
fix: Forcefully load stacktrace if assert UI is shown (#2164)
- Add DiagnosticUtil to automatically filter out the "boring" stack entries.

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
2025-01-09 21:11:41 +00:00
KazWolfe
a656fefb2b
feat: Allow /xldev to disable Safe Mode (#2166)
- Adds new menu item to /xldev to disable Safe Mode, allowing users to load plugins again.
  - Safe mode cannot be re-enabled once disabled.
- Add new ModuleLog.Create<T> for eventual ILogger magic
- Make safe mode writable
- Remove redundant check in CheckPolicy
2025-01-09 22:01:46 +01:00
goat
da8be03124 don't log imgui asserts unless we've shown them this session 2025-01-09 22:00:53 +01:00
goat
27b6dfcbea don't allow window additions for multi-monitor windows 2025-01-09 21:59:32 +01:00
goat
a690ccbeef build: 11.0.5.1 2025-01-03 18:32:34 +01:00
wolfcomp
e999a8f678
Add timezone offset to system time in crash log (#2142) 2025-01-03 08:57:34 -08:00
goat
6d0c9d4f4a build: 11.0.5.0 2025-01-03 17:00:03 +01:00
bleatbot
b74f4fba01
Update ClientStructs (#2151)
Co-authored-by: github-actions[bot] <noreply@github.com>
2025-01-03 15:07:57 +00:00
goat
e0eca2ac70 don't auto-enable imgui asserts for now, until we have better tracing 2025-01-02 17:29:49 +01:00
goat
d22ff8fad8 don't save windows that haven't changed 2025-01-02 17:29:17 +01:00
goat
cac76f045b remove useless PathMap in c# projects 2025-01-02 15:10:15 +01:00
goat
be252bacc0
Merge pull request #2161 from goaaats/feat/stabilize_window_additions
Stabilize WindowSystem additional options menu
2024-12-30 23:29:37 +01:00
goat
35b49823e5 WindowSystem: fix clickthrough option not applying to child windows, persist options
Persistence is pretty WIP. I want to offer multiple presets in the future, and save more things like window positions.
2024-12-30 21:14:08 +01:00
goat
49a18e3c1e Api11ToDo -> Api12ToDo 2024-12-30 14:01:50 +01:00
goat
335b3d1ed5 add todo to re-apply easings behavior change 2024-12-30 14:01:12 +01:00
goat
b36bdb2086 Revert "clamp value of all easings by default"
This reverts commit 1aada98393.
Breaks the API.
2024-12-30 13:59:59 +01:00
goat
0cac90b032 plugin installer: only allow feedback for non-outdated plugins 2024-12-29 19:01:49 +01:00
goat
6dd85c9e3e remove some unused stuff in Util, fix warnings 2024-12-29 16:56:31 +01:00
goat
1aada98393 clamp value of all easings by default 2024-12-29 16:53:16 +01:00
goat
2e77e90532 title screen menu: some visual fixes
Use SeStringRenderer to render menu entries
Clamp alpha values to avoid flickering
2024-12-29 13:49:31 +01:00
goat
c79b1cd83a config: save asynchronously to prevent hitches 2024-12-29 13:17:03 +01:00
goat
01980c3133 plugin installer: add linebreak after ban reason 2024-12-29 12:42:43 +01:00
goat
d200c12c2f copy cimgui DLLs in their vcxproj instead of Dalamud 2024-12-28 14:02:06 +01:00
goat
4af9bc05dc don't rebuild Dalamud.Boot every time, copy out of output directory instead 2024-12-28 14:01:34 +01:00
goat
55bfe9eeb0 upgrade cimgui, fix IME and image rendering 2024-12-27 18:19:48 +01:00
goat
3f3f5f4b72 hide assert logs that spam every frame automatically, add "verbose" mode 2024-12-26 22:10:10 +01:00
goat
10f4009a0c don't implicitly depend on DataManager in data widget load functions 2024-12-26 17:17:04 +01:00
goat
f3d7c6f2ea don't access nullptr addon when shutting down early 2024-12-26 17:17:04 +01:00
Haselnussbomber
f98da094f4
Fetch market board uploader info from main thread (#2158) 2024-12-26 16:39:54 +01:00
goat
0a3e88efca clone culture to apply fixes
The default cultures are read-only
2024-12-26 15:26:22 +01:00
goat
b3c0a8c207 editorconfig: trim trailing whitespace 2024-12-26 14:14:16 +01:00
goat
2e2feb144f fix some warnings 2024-12-26 14:11:14 +01:00
goat
ce5ee71c91 patch NNBSP number separator out of cultures that use it (fixes #2157) 2024-12-26 13:48:28 +01:00
goat
1d9116f0a0 unhandled exceptions when calling Draw() must be fatal 2024-12-26 13:29:54 +01:00
goat
b02194ca6a DalamudInterface doesn't need DataManager 2024-12-26 13:09:06 +01:00
goat
776a838dbe remove legacy MonoMod patches for codebase and assembly location 2024-12-25 23:48:07 +01:00
goat
b79c646b9b only turn ImGui asserts on if we are actually loading a devplugin 2024-12-25 23:48:07 +01:00
KazWolfe
1083081706
fix: Safely invoke marketboard events being sent to plugins. (#2150) 2024-12-25 23:33:23 +01:00
goat
61dd058a6e Merge branch 'intree_imgui' 2024-12-25 17:05:36 +01:00
goat
8b06ae3f00 add ImGui assert options to experimental tab 2024-12-25 17:04:35 +01:00
goat
9d8bcb7a24 add "disable for this session" button to ImGui asserts 2024-12-25 17:03:22 +01:00
goat
b822807eab
Merge pull request #2156 from goaaats/intree_imgui
Add proper ImGui assert mechanism
2024-12-25 14:40:09 +01:00
goat
12bf2f4478 replace nonfunctional managed asserts with proper imgui-handled assert mechanism 2024-12-25 12:47:38 +01:00
goat
46b055f657
Merge pull request #2154 from goaaats/intree_imgui
Build imgui in-tree
2024-12-24 22:12:34 +01:00
goat
8773d3b873 fix debug build 2024-12-24 16:00:09 +01:00
goat
2d4e4122aa link with static CRT 2024-12-24 03:57:28 +01:00
goat
c31fe0920f remove hackfix hooks 2024-12-24 03:44:31 +01:00
goat
419b62b2c9 fix release build 2024-12-24 02:48:42 +01:00
goat
87525091c9 make it work with nuke 2024-12-24 01:52:38 +01:00
goat
7629cac8af use ImGuiScene without shipped binaries 2024-12-23 23:29:16 +01:00
goat
1714191711 attest imgui DLLs 2024-12-23 23:25:21 +01:00
goat
f696f7250f shoddy include in dalamud build for now 2024-12-23 23:24:13 +01:00
goat
371f1bfa18 comment out kizer hack for now, need to move to natives 2024-12-23 23:17:33 +01:00
goat
43ab6bd24f remove win32 targets 2024-12-23 22:39:55 +01:00
goat
9616573f40 reference and add vcxproj for cimgui, cimplot, cimguizmo 2024-12-23 22:36:31 +01:00
goat
5a2473293d reorganize solution a bit 2024-12-23 22:01:17 +01:00
goat
667ae312c5
some readme housekeeping 2024-12-23 21:15:03 +01:00
goat
a1ae33bfee don't set plugin to fail state if we threw InvalidPluginOperationException
These are supposed to indicate to the user that they called a function at the wrong point in time. We don't want to actually mutate the state in that case.
2024-12-23 21:03:31 +01:00
goat
8276c19c6f add plugin enable/disable/toggle commands 2024-12-23 21:00:02 +01:00
goat
8572ed8b1e ProfileCommandHandler -> PluginManagementCommandHandler 2024-12-23 17:05:35 +01:00
goat
bc21621d9a add release script 2024-12-23 16:50:06 +01:00
goat
c647d07d94 build: 11.0.4.0 2024-12-22 19:19:56 +01:00
goat
2d6689b9d3 move lumina version out of csproj 2024-12-22 19:14:18 +01:00
bleatbot
87d069267b
Update ClientStructs (#2148)
Co-authored-by: github-actions[bot] <noreply@github.com>
2024-12-20 14:33:54 +00:00
Infi
2e6cb6ef00
- Add chat notification back to AutoUpdate (#2146)
- ImRaii UI elements in AutoUpdate tab
- Fix scoping in IconButton
2024-12-19 22:19:50 +01:00
goat
f7ef68d65e build: 11.0.3.0 2024-12-17 21:50:03 +01:00
KazWolfe
a7faabd4e8
[net9] Rollup changes from master (#2133)
* Use MiniGame BGM scene for About tab

* Use span comparison in GameGui.IsInLobby

* [master] Update ClientStructs (#2126)

Co-authored-by: github-actions[bot] <noreply@github.com>

* [master] Update ClientStructs (#2134)

Co-authored-by: github-actions[bot] <noreply@github.com>

* [master] Update ClientStructs (#2135)

Co-authored-by: github-actions[bot] <noreply@github.com>

* Fix config option summaries (#2138)

* Fix config option summaries

* Obsolete LockonDefaultZoom_179 with error

* Don't allow nameplate GameObject lookup when ObjectId is invalid (#2137)

* build: 11.0.2.0

* Update ClientStructs (#2139)

Co-authored-by: github-actions[bot] <noreply@github.com>

* chore: remove [Experimental] from SeStringRenderer (#2144)

It hasn't caused problem for a while (though performance probably can be
better), and looks good enough to remove this attribute.

* Update ClientStructs (#2140)

Co-authored-by: github-actions[bot] <noreply@github.com>

* Return nullable on XivChatType.GetDetails (#2143)

---------

Co-authored-by: Haselnussbomber <mail@haselnussbomber.de>
Co-authored-by: github-actions[bot] <noreply@github.com>
Co-authored-by: nebel <9887+nebel@users.noreply.github.com>
Co-authored-by: goat <goatsdev@protonmail.com>
Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
Co-authored-by: srkizer <soreepeong@gmail.com>
2024-12-17 08:14:45 -08:00
github-actions[bot]
f9bdceb662 Merge remote-tracking branch 'origin/master' into net9-rollup 2024-12-17 15:57:14 +00:00
Haselnussbomber
3ccd93aaf1
Return nullable on XivChatType.GetDetails (#2143) 2024-12-17 15:56:55 +00:00
bleatbot
4dedaac50e
Update ClientStructs (#2140)
Co-authored-by: github-actions[bot] <noreply@github.com>
2024-12-17 15:42:09 +00:00
srkizer
70af62885b
chore: remove [Experimental] from SeStringRenderer (#2144)
It hasn't caused problem for a while (though performance probably can be
better), and looks good enough to remove this attribute.
2024-12-17 07:31:44 -08:00
goat
722ee32353
Use MiniGame BGM scene for About tab (#2132)
* Use MiniGame BGM scene for About tab

* Use span comparison in GameGui.IsInLobby
2024-12-06 21:43:39 +01:00
bleatbot
e2298ea099
Update ClientStructs (#2139)
Co-authored-by: github-actions[bot] <noreply@github.com>
2024-12-06 21:42:35 +01:00
Haselnussbomber
865a05fb1a
Merge branch 'master' into proper-use-of-bgm-scene 2024-12-01 19:21:23 +01:00
Haselnussbomber
f7f1b0afc5
Use span comparison in GameGui.IsInLobby 2024-11-30 20:42:46 +01:00
Haselnussbomber
da718b64c1
Use MiniGame BGM scene for About tab 2024-11-30 19:59:42 +01:00
KazWolfe
df3900f571
Merge pull request #2102 from goatcorp/net9-rollup
[net9] Rollup changes from master
2024-11-28 09:32:22 -08:00
github-actions[bot]
73072d78ba Merge remote-tracking branch 'origin/master' into net9-rollup 2024-11-28 17:19:34 +00:00
goat
26be395a07
Merge pull request #2087 from goatcorp/net9-rollup
[net9] Rollup changes from master
2024-11-17 14:36:39 +01:00
github-actions[bot]
a8f3098d74 Merge remote-tracking branch 'origin/master' into net9-rollup 2024-11-17 13:36:00 +00:00
goat
e1877491e7
Merge pull request #2086 from goatcorp/net9-rollup
[net9] Rollup changes from master
2024-11-15 19:42:00 +01:00
github-actions[bot]
605c3019f6 Merge remote-tracking branch 'origin/master' into net9-rollup 2024-11-15 18:34:17 +00:00
Kaz Wolfe
23cdf3ce4a
Merge branch 'api11' into net9 2024-11-14 23:29:14 -08:00
goat
a5622f3040 possibly work around nuke .NET9 issue 2024-11-14 01:16:51 +01:00
goat
6efa41d33d use net9 SDK in CI 2024-11-14 01:08:39 +01:00
goat
9d4bc95f7d initial net9 port 2024-11-14 01:08:07 +01:00
Soreepeong
3c4b9f96ff Merge remote-tracking branch 'upstream/master' into imguiscene-inside 2024-07-27 23:38:12 +09:00
Soreepeong
a9fc59d0df Rebase cleanup 2024-07-19 18:56:39 +09:00
Soreepeong
427e5e7c06 cleanup 2024-07-19 18:49:11 +09:00
Soreepeong
5081ac10e1 Remove unused functions 2024-07-19 18:48:07 +09:00
Soreepeong
b2223a643f Add fallback path 2024-07-19 18:48:07 +09:00
Soreepeong
733f79b5cb *shade: Support variations in offset for underlying objects 2024-07-19 18:48:07 +09:00
Soreepeong
184463a056 Rewrite ImGuiScene 2024-07-19 18:48:05 +09:00
Soreepeong
6bf264acf0 Support DirectX debugging injector cmdline 2024-07-19 18:13:37 +09:00
Soreepeong
faf61477c7 Move ImGuiScene into Dalamud project 2024-07-19 18:13:37 +09:00
1544 changed files with 1162458 additions and 18541 deletions

View file

@ -6,6 +6,7 @@ charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# 4 space indentation
indent_style = space

8
.gitattributes vendored
View file

@ -17,7 +17,7 @@
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
@ -46,9 +46,9 @@
###############################################################################
# diff behavior for common document formats
#
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
@ -61,3 +61,5 @@
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
imgui/**/Generated/**/*.cs linguist-generated=true

308
.github/generate_changelog.py vendored Normal file
View file

@ -0,0 +1,308 @@
#!/usr/bin/env python3
"""
Generate a changelog from git commits between the last two tags and post to Discord webhook.
"""
import subprocess
import re
import sys
import json
import argparse
import os
from typing import List, Tuple, Optional, Dict, Any
def run_git_command(args: List[str]) -> str:
"""Run a git command and return its output."""
try:
result = subprocess.run(
["git"] + args,
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Git command failed: {e}", file=sys.stderr)
sys.exit(1)
def get_last_two_tags() -> Tuple[str, str]:
"""Get the latest two git tags."""
tags = run_git_command(["tag", "--sort=-version:refname"])
tag_list = [t for t in tags.split("\n") if t]
# Filter out old tags that start with 'v' (old versioning scheme)
tag_list = [t for t in tag_list if not t.startswith('v')]
if len(tag_list) < 2:
print("Error: Need at least 2 tags in the repository", file=sys.stderr)
sys.exit(1)
return tag_list[0], tag_list[1]
def get_submodule_commit(submodule_path: str, tag: str) -> Optional[str]:
"""Get the commit hash of a submodule at a specific tag."""
try:
# Get the submodule commit at the specified tag
result = run_git_command(["ls-tree", tag, submodule_path])
# Format is: "<mode> commit <hash>\t<path>"
parts = result.split()
if len(parts) >= 3 and parts[1] == "commit":
return parts[2]
return None
except:
return None
def get_repo_info() -> Tuple[str, str]:
"""Get repository owner and name from git remote."""
try:
remote_url = run_git_command(["config", "--get", "remote.origin.url"])
# Handle both HTTPS and SSH URLs
# SSH: git@github.com:owner/repo.git
# HTTPS: https://github.com/owner/repo.git
match = re.search(r'github\.com[:/](.+?)/(.+?)(?:\.git)?$', remote_url)
if match:
owner = match.group(1)
repo = match.group(2)
return owner, repo
else:
print("Error: Could not parse GitHub repository from remote URL", file=sys.stderr)
sys.exit(1)
except:
print("Error: Could not get git remote URL", file=sys.stderr)
sys.exit(1)
def get_commits_between_tags(tag1: str, tag2: str) -> List[str]:
"""Get commit SHAs between two tags."""
log_output = run_git_command([
"log",
f"{tag2}..{tag1}",
"--format=%H"
])
commits = [sha.strip() for sha in log_output.split("\n") if sha.strip()]
return commits
def get_pr_for_commit(commit_sha: str, owner: str, repo: str, token: str) -> Optional[Dict[str, Any]]:
"""Get PR information for a commit using GitHub API."""
try:
import requests
except ImportError:
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
sys.exit(1)
headers = {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"
}
if token:
headers["Authorization"] = f"Bearer {token}"
url = f"https://api.github.com/repos/{owner}/{repo}/commits/{commit_sha}/pulls"
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
prs = response.json()
if prs and len(prs) > 0:
# Return the first PR (most relevant one)
pr = prs[0]
return {
"number": pr["number"],
"title": pr["title"],
"author": pr["user"]["login"],
"url": pr["html_url"]
}
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
# Commit might not be associated with a PR
return None
elif e.response.status_code == 403:
print("Warning: GitHub API rate limit exceeded. Consider providing a token.", file=sys.stderr)
return None
else:
print(f"Warning: Failed to fetch PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
return None
except Exception as e:
print(f"Warning: Error fetching PR for commit {commit_sha[:7]}: {e}", file=sys.stderr)
return None
return None
def get_prs_between_tags(tag1: str, tag2: str, owner: str, repo: str, token: str) -> List[Dict[str, Any]]:
"""Get PRs between two tags using GitHub API."""
commits = get_commits_between_tags(tag1, tag2)
print(f"Found {len(commits)} commits, fetching PR information...")
prs = []
seen_pr_numbers = set()
for i, commit_sha in enumerate(commits, 1):
if i % 10 == 0:
print(f"Progress: {i}/{len(commits)} commits processed...")
pr_info = get_pr_for_commit(commit_sha, owner, repo, token)
if pr_info and pr_info["number"] not in seen_pr_numbers:
seen_pr_numbers.add(pr_info["number"])
prs.append(pr_info)
return prs
def filter_prs(prs: List[Dict[str, Any]], ignore_patterns: List[str]) -> List[Dict[str, Any]]:
"""Filter out PRs matching any of the ignore patterns."""
compiled_patterns = [re.compile(pattern) for pattern in ignore_patterns]
filtered = []
for pr in prs:
if not any(pattern.search(pr["title"]) for pattern in compiled_patterns):
filtered.append(pr)
return filtered
def generate_changelog(version: str, prev_version: str, prs: List[Dict[str, Any]],
cs_commit_new: Optional[str], cs_commit_old: Optional[str],
owner: str, repo: str) -> str:
"""Generate markdown changelog."""
# Calculate statistics
pr_count = len(prs)
unique_authors = len(set(pr["author"] for pr in prs))
changelog = f"# Dalamud Release v{version}\n\n"
changelog += f"We just released Dalamud v{version}, which should be available to users within a few minutes. "
changelog += f"This release includes **{pr_count} PR{'s' if pr_count != 1 else ''} from {unique_authors} contributor{'s' if unique_authors != 1 else ''}**.\n"
changelog += f"[Click here](<https://github.com/{owner}/{repo}/compare/{prev_version}...{version}>) to see all Dalamud changes.\n\n"
if cs_commit_new and cs_commit_old and cs_commit_new != cs_commit_old:
changelog += f"It ships with an updated **FFXIVClientStructs [`{cs_commit_new[:7]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n"
changelog += f"[Click here](<https://github.com/aers/FFXIVClientStructs/compare/{cs_commit_old}...{cs_commit_new}>) to see all CS changes.\n"
elif cs_commit_new:
changelog += f"It ships with **FFXIVClientStructs [`{cs_commit_new[:7]}`](<https://github.com/aers/FFXIVClientStructs/commit/{cs_commit_new}>)**.\n"
changelog += "## Dalamud Changes\n\n"
for pr in prs:
changelog += f"* {pr['title']} ([#**{pr['number']}**](<{pr['url']}>) by **{pr['author']}**)\n"
return changelog
def post_to_discord(webhook_url: str, content: str, version: str) -> None:
"""Post changelog to Discord webhook as a file attachment."""
try:
import requests
except ImportError:
print("Error: requests library is required. Install it with: pip install requests", file=sys.stderr)
sys.exit(1)
filename = f"changelog-v{version}.md"
# Prepare the payload
data = {
"content": f"Dalamud v{version} has been released!",
"attachments": [
{
"id": "0",
"filename": filename
}
]
}
# Prepare the files
files = {
"payload_json": (None, json.dumps(data)),
"files[0]": (filename, content.encode('utf-8'), 'text/markdown')
}
try:
result = requests.post(webhook_url, files=files)
result.raise_for_status()
print(f"Successfully posted to Discord webhook, code {result.status_code}")
except requests.exceptions.HTTPError as err:
print(f"Failed to post to Discord: {err}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Failed to post to Discord: {e}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description="Generate changelog from git commits and post to Discord webhook"
)
parser.add_argument(
"--webhook-url",
required=True,
help="Discord webhook URL"
)
parser.add_argument(
"--github-token",
default=os.environ.get("GITHUB_TOKEN"),
help="GitHub API token (or set GITHUB_TOKEN env var). Increases rate limit."
)
parser.add_argument(
"--ignore",
action="append",
default=[],
help="Regex patterns to ignore PRs (can be specified multiple times)"
)
parser.add_argument(
"--submodule-path",
default="lib/FFXIVClientStructs",
help="Path to the FFXIVClientStructs submodule (default: lib/FFXIVClientStructs)"
)
args = parser.parse_args()
# Get repository info
owner, repo = get_repo_info()
print(f"Repository: {owner}/{repo}")
# Get the last two tags
latest_tag, previous_tag = get_last_two_tags()
print(f"Generating changelog between {previous_tag} and {latest_tag}")
# Get submodule commits at both tags
cs_commit_new = get_submodule_commit(args.submodule_path, latest_tag)
cs_commit_old = get_submodule_commit(args.submodule_path, previous_tag)
if cs_commit_new:
print(f"FFXIVClientStructs commit (new): {cs_commit_new[:7]}")
if cs_commit_old:
print(f"FFXIVClientStructs commit (old): {cs_commit_old[:7]}")
# Get PRs between tags
prs = get_prs_between_tags(latest_tag, previous_tag, owner, repo, args.github_token)
prs.reverse()
print(f"Found {len(prs)} PRs")
# Filter PRs
filtered_prs = filter_prs(prs, args.ignore)
print(f"After filtering: {len(filtered_prs)} PRs")
# Generate changelog
changelog = generate_changelog(latest_tag, previous_tag, filtered_prs,
cs_commit_new, cs_commit_old, owner, repo)
print("\n" + "="*50)
print("Generated Changelog:")
print("="*50)
print(changelog)
print("="*50 + "\n")
# Post to Discord
post_to_discord(args.webhook_url, changelog, latest_tag)
if __name__ == "__main__":
main()

32
.github/workflows/backup.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: Back up code to other forges
on:
schedule:
- cron: '0 2 * * *' # Run every day at 2 AM
workflow_dispatch: # Allow manual trigger
jobs:
push-to-forges:
runs-on: ubuntu-latest
if: github.repository == 'goatcorp/Dalamud'
steps:
- name: Checkout the repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd #v0.9.1
with:
ssh-private-key: |
${{ secrets.MIRROR_GITLAB_SYNC_KEY }}
${{ secrets.MIRROR_CODEBERG_SYNC_KEY }}
- name: Add remotes & push
env:
GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=accept-new"
run: |
git remote add gitlab git@gitlab.com:goatcorp/Dalamud.git
git push gitlab --all --force
git remote add codeberg git@codeberg.org:goatcorp/Dalamud.git
git push codeberg --all --force

View file

@ -0,0 +1,48 @@
name: Generate Changelog
on:
workflow_dispatch:
push:
tags:
- '*'
permissions: read-all
jobs:
generate-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history and tags
submodules: true # Fetch submodules
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.14'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Generate and post changelog
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_TERMINAL_PROMPT: 0
run: |
python .github/generate_changelog.py \
--webhook-url "${{ secrets.DISCORD_CHANGELOG_WEBHOOK_URL }}" \
--ignore "Update ClientStructs" \
--ignore "^build:"
- name: Upload changelog as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: changelog
path: changelog-*.md
if-no-files-found: ignore

View file

@ -1,8 +1,9 @@
name: Build Dalamud
on: [push, pull_request, workflow_dispatch]
concurrency:
group: build_dalamud_${{ github.ref_name }}
cancel-in-progress: true
cancel-in-progress: false
jobs:
build:
@ -22,7 +23,7 @@ jobs:
uses: microsoft/setup-msbuild@v1.0.2
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.100'
dotnet-version: '10.0.100'
- name: Define VERSION
run: |
$env:COMMIT = $env:GITHUB_SHA.Substring(0, 7)
@ -32,10 +33,8 @@ jobs:
($env:REPO_NAME) >> VERSION
($env:BRANCH) >> VERSION
($env:COMMIT) >> VERSION
- name: Build Dalamud
run: .\build.ps1 compile
- name: Test Dalamud
run: .\build.ps1 test
- name: Build and Test Dalamud
run: .\build.ps1 ci
- name: Sign Dalamud
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
env:
@ -55,6 +54,7 @@ jobs:
bin/Release/Dalamud.*.dll
bin/Release/Dalamud.*.exe
bin/Release/FFXIVClientStructs.dll
bin/Release/cim*.dll
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
@ -86,9 +86,9 @@ jobs:
- name: "Verify Compatibility"
run: |
$FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll"
$retcode = 0
foreach ($file in $FILES_TO_VALIDATE) {
$testout = ""
Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ==="
@ -99,7 +99,7 @@ jobs:
$retcode = 1
}
}
exit $retcode
deploy_stg:
@ -128,18 +128,18 @@ jobs:
GH_BRANCH: ${{ steps.extract_branch.outputs.branch }}
run: |
Compress-Archive .\scratch\* .\canary.zip # Recreate the release zip
$branchName = $env:GH_BRANCH
if ($branchName -eq "master") {
$branchName = "stg"
}
$newVersion = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\TEMP_gitver.txt")
$revision = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\revision.txt")
$commitHash = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\commit_hash.txt")
Remove-Item -Force -Recurse .\scratch
if (Test-Path -Path $branchName) {
$versionData = Get-Content ".\${branchName}\version" | ConvertFrom-Json
$oldVersion = $versionData.AssemblyVersion
@ -158,7 +158,7 @@ jobs:
Write-Host "Deployment folder doesn't exist. Not doing anything."
Remove-Item .\canary.zip
}
- name: Commit changes
shell: bash
env:
@ -166,8 +166,8 @@ jobs:
run: |
git config --global user.name "Actions User"
git config --global user.email "actions@github.com"
git add .
git commit -m "[CI] Update staging for ${DVER} on ${GH_BRANCH}" || true
git push origin main || true

View file

@ -1,8 +1,8 @@
name: Rollup changes to next version
on:
push:
branches:
- master
# push:
# branches:
# - master
workflow_dispatch:
jobs:
@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
branches:
- net9
- api14
defaults:
run:

View file

@ -1,16 +1,26 @@
name: Check for FFXIVCS changes
name: Check for Submodule Changes
on:
schedule:
- cron: "0 0,12,18 */1 * *"
- cron: "0 0,6,12,18 * * *"
workflow_dispatch:
jobs:
check:
name: FFXIVCS Check
name: Check ${{ matrix.submodule.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branches: [master]
submodule:
- name: ClientStructs
path: lib/FFXIVClientStructs
branch: main
branch-prefix: csupdate
- name: Excel Schema
path: lib/Lumina.Excel
branch: master
branch-prefix: schemaupdate
defaults:
run:
@ -24,30 +34,41 @@ jobs:
ref: ${{ matrix.branches }}
token: ${{ secrets.UPDATE_PAT }}
- name: Create update branch
run: git checkout -b csupdate/${{ matrix.branches }}
run: git checkout -b ${{ matrix.submodule.branch-prefix }}/${{ matrix.branches }}
- name: Initialize mandatory git config
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email noreply@github.com
git config --global pull.rebase false
- name: Update submodule
id: update-submodule
run: |
git checkout -b csupdate-${{ matrix.branches }}
git checkout -b ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }}
git reset --hard origin/${{ matrix.branches }}
cd lib/FFXIVClientStructs
cd ${{ matrix.submodule.path }}
git fetch
git reset --hard origin/main
git reset --hard origin/${{ matrix.submodule.branch }}
cd ../..
git add lib/FFXIVClientStructs
git commit --message "Update ClientStructs"
git push origin csupdate-${{ matrix.branches }} --force
git add ${{ matrix.submodule.path }}
if [[ -z "$(git status --porcelain --untracked-files=no)" ]]; then
echo "No changes detected!"
echo "SUBMIT_PR=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git commit --message "Update ${{ matrix.submodule.name }}"
git push origin ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }} --force
echo "SUBMIT_PR=true" >> "$GITHUB_OUTPUT"
- name: Create PR
if: ${{ steps.update-submodule.outputs.SUBMIT_PR == 'true' }}
run: |
echo ${{ secrets.UPDATE_PAT }} | gh auth login --with-token
prNumber=$(gh pr list --base ${{ matrix.branches }} --head csupdate-${{ matrix.branches }} --state open --json number --template "{{range .}}{{.number}}{{end}}")
prNumber=$(gh pr list --base ${{ matrix.branches }} --head ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }} --state open --json number --template "{{range .}}{{.number}}{{end}}")
if [ -z "$prNumber" ]; then
echo "No PR found, creating one"
gh pr create --head csupdate-${{ matrix.branches }} --title "[${{ matrix.branches }}] Update ClientStructs" --body "" --base ${{ matrix.branches }}
gh pr create --head ${{ matrix.submodule.branch-prefix }}-${{ matrix.branches }} --title "[${{ matrix.branches }}] Update ${{ matrix.submodule.name }}" --body "" --base ${{ matrix.branches }}
else
echo "PR already exists, ignoring"
fi

5
.gitignore vendored
View file

@ -327,4 +327,7 @@ ASALocalRun/
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
.mfractor/
# HexaGen generated files
#imgui/**/Generated/**/*

18
.gitmodules vendored
View file

@ -1,6 +1,3 @@
[submodule "lib/ImGuiScene"]
path = lib/ImGuiScene
url = https://github.com/goatcorp/ImGuiScene
[submodule "lib/FFXIVClientStructs"]
path = lib/FFXIVClientStructs
url = https://github.com/aers/FFXIVClientStructs
@ -10,3 +7,18 @@
[submodule "lib/TsudaKageyu-minhook"]
path = lib/TsudaKageyu-minhook
url = https://github.com/TsudaKageyu/minhook.git
[submodule "lib/cimgui"]
path = lib/cimgui
url = https://github.com/goatcorp/gc-cimgui
[submodule "lib/cimplot"]
path = lib/cimplot
url = https://github.com/goatcorp/gc-cimplot
[submodule "lib/cimguizmo"]
path = lib/cimguizmo
url = https://github.com/goatcorp/gc-cimguizmo
[submodule "lib/Hexa.NET.ImGui"]
path = lib/Hexa.NET.ImGui
url = https://github.com/goatcorp/Hexa.NET.ImGui.git
[submodule "lib/Lumina.Excel"]
path = lib/Lumina.Excel
url = https://github.com/NotAdam/Lumina.Excel.git

View file

@ -1,19 +1,57 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"definitions": {
"build": {
"type": "object",
"Host": {
"type": "string",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"ExecutableTarget": {
"type": "string",
"enum": [
"CI",
"Clean",
"Compile",
"CompileCImGui",
"CompileCImGuizmo",
"CompileCImPlot",
"CompileDalamud",
"CompileDalamudBoot",
"CompileDalamudCrashHandler",
"CompileImGuiNatives",
"CompileInjector",
"Restore",
"SetCILogging",
"Test"
]
},
"Verbosity": {
"type": "string",
"description": "",
"enum": [
"Verbose",
"Normal",
"Minimal",
"Quiet"
]
},
"NukeBuild": {
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
@ -23,29 +61,8 @@
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"IsDocsBuild": {
"type": "boolean",
"description": "Whether we are building for documentation - emits generated files"
"$ref": "#/definitions/Host"
},
"NoLogo": {
"type": "boolean",
@ -74,53 +91,46 @@
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"Clean",
"Compile",
"CompileDalamud",
"CompileDalamudBoot",
"CompileDalamudCrashHandler",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
"Test"
]
"$ref": "#/definitions/ExecutableTarget"
}
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"Clean",
"Compile",
"CompileDalamud",
"CompileDalamudBoot",
"CompileDalamudCrashHandler",
"CompileInjector",
"CompileInjectorBoot",
"Restore",
"Test"
]
"$ref": "#/definitions/ExecutableTarget"
}
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
"$ref": "#/definitions/Verbosity"
}
}
}
}
}
},
"allOf": [
{
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"IsDocsBuild": {
"type": "boolean",
"description": "Whether we are building for documentation - emits generated files"
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
}
}
},
{
"$ref": "#/definitions/NukeBuild"
}
]
}

View file

@ -26,6 +26,38 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
RT_MANIFEST_THEMES RT_MANIFEST "themes.manifest"
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_APPNAME "Dalamud Boot"
IDS_MSVCRT_ACTION_OPENDOWNLOAD
"Download Microsoft Visual C++ Redistributable 2022\nExit the game and download the latest setup file from Microsoft."
IDS_MSVCRT_ACTION_IGNORE
"Ignore and Continue\nAttempt to continue with the currently installed version.\nDalamud or plugins may fail to load."
IDS_MSVCRT_DIALOG_MAININSTRUCTION
"Outdated Microsoft Visual C++ Redistributable"
IDS_MSVCRT_DIALOG_CONTENT
"The Microsoft Visual C++ Redistributable version detected on this computer (v{0}.{1}.{2}.{3}) is out of date and may not work with Dalamud."
IDS_MSVCRT_DOWNLOADURL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
IDS_INITIALIZEFAIL_ACTION_ABORT "Abort\nExit the game."
IDS_INITIALIZEFAIL_ACTION_CONTINUE
"Load game without Dalamud\nThe game will launch without Dalamud enabled."
IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION "Failed to load Dalamud."
IDS_INITIALIZEFAIL_DIALOG_CONTENT
"An error is preventing Dalamud from being loaded along with the game."
END
STRINGTABLE
BEGIN
IDS_INITIALIZEFAIL_DIALOG_FOOTER
"Last operation: {0}\nHRESULT: 0x{1:08X}\nDescription: {2}"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////

View file

@ -28,7 +28,7 @@
<PlatformToolset>v143</PlatformToolset>
<LinkIncremental>false</LinkIncremental>
<CharacterSet>Unicode</CharacterSet>
<OutDir>..\bin\$(Configuration)\</OutDir>
<OutDir>bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
@ -48,8 +48,7 @@
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpp23</LanguageStandard>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -66,6 +65,7 @@
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>false</IntrinsicFunctions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26812</DisableSpecificWarnings>
@ -80,6 +80,7 @@
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26812</DisableSpecificWarnings>
@ -132,6 +133,10 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="DalamudStartInfo.cpp" />
<ClCompile Include="error_info.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="hooks.cpp" />
<ClCompile Include="logging.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
@ -175,6 +180,7 @@
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\trampoline.h" />
<ClInclude Include="crashhandler_shared.h" />
<ClInclude Include="DalamudStartInfo.h" />
<ClInclude Include="error_info.h" />
<ClInclude Include="hooks.h" />
<ClInclude Include="logging.h" />
<ClInclude Include="ntdll.h" />
@ -200,8 +206,9 @@
<ItemGroup>
<Manifest Include="themes.manifest" />
</ItemGroup>
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
<Delete Files="$(OutDir)$(TargetName).lib" />
<Delete Files="$(OutDir)$(TargetName).exp" />
<Target Name="CopyOutputDlls" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(OutDir)$(TargetName).dll" DestinationFolder="..\bin\$(Configuration)\" />
<Copy SourceFiles="$(OutDir)$(TargetName).pdb" DestinationFolder="..\bin\$(Configuration)\" />
<Copy SourceFiles="$(OutDir)nethost.dll" DestinationFolder="..\bin\$(Configuration)\" />
</Target>
</Project>

View file

@ -76,6 +76,9 @@
<ClCompile Include="ntdll.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="error_info.cpp">
<Filter>Common Boot</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
@ -146,6 +149,9 @@
<ClInclude Include="ntdll.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="error_info.h">
<Filter>Common Boot</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Dalamud.Boot.rc" />

View file

@ -108,7 +108,13 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
config.LogName = json.value("LogName", config.LogName);
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
if (json.contains("TempDirectory") && !json["TempDirectory"].is_null()) {
config.TempDirectory = json.value("TempDirectory", config.TempDirectory);
}
config.Language = json.value("Language", config.Language);
config.Platform = json.value("Platform", config.Platform);
config.GameVersion = json.value("GameVersion", config.GameVersion);
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
@ -116,6 +122,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
config.NoLoadThirdPartyPlugins = json.value("NoLoadThirdPartyPlugins", config.NoLoadThirdPartyPlugins);
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
config.BootDebugDirectX = json.value("BootDebugDirectX", config.BootDebugDirectX);
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
config.BootDisableFallbackConsole = json.value("BootDisableFallbackConsole", config.BootDisableFallbackConsole);
config.BootWaitMessageBox = json.value("BootWaitMessageBox", config.BootWaitMessageBox);

View file

@ -17,7 +17,7 @@ struct DalamudStartInfo {
DirectHook = 1,
};
friend void from_json(const nlohmann::json&, DotNetOpenProcessHookMode&);
enum class ClientLanguage : int {
Japanese,
English,
@ -44,9 +44,11 @@ struct DalamudStartInfo {
std::string ConfigurationPath;
std::string LogPath;
std::string LogName;
std::string TempDirectory;
std::string PluginDirectory;
std::string AssetDirectory;
ClientLanguage Language = ClientLanguage::English;
std::string Platform;
std::string GameVersion;
std::string TroubleshootingPackData;
int DelayInitializeMs = 0;
@ -54,6 +56,7 @@ struct DalamudStartInfo {
bool NoLoadThirdPartyPlugins;
std::string BootLogPath;
bool BootDebugDirectX = false;
bool BootShowConsole = false;
bool BootDisableFallbackConsole = false;
WaitMessageboxFlags BootWaitMessageBox = WaitMessageboxFlags::None;

View file

@ -6,6 +6,8 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679
struct exception_info
{
LPEXCEPTION_POINTERS pExceptionPointers;

View file

@ -1,17 +1,136 @@
#include "pch.h"
#include <d3d11.h>
#include <dxgi1_3.h>
#include "DalamudStartInfo.h"
#include "hooks.h"
#include "logging.h"
#include "utils.h"
#include "veh.h"
#include "xivfixes.h"
#include "resource.h"
HMODULE g_hModule;
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
static void CheckMsvcrtVersion() {
// Commit introducing inline mutex ctor: tagged vs-2022-17.14 (2024-06-18)
// - https://github.com/microsoft/STL/commit/22a88260db4d754bbc067e2002430144d6ec5391
// MSVC Redist versions:
// - https://github.com/abbodi1406/vcredist/blob/master/source_links/README.md
// - 14.40.33810.0 dsig 2024-04-28
// - 14.40.33816.0 dsig 2024-09-11
constexpr WORD RequiredMsvcrtVersionComponents[] = {14, 40, 33816, 0};
constexpr auto RequiredMsvcrtVersion = 0ULL
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[0]) << 48)
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[1]) << 32)
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[2]) << 16)
| (static_cast<uint64_t>(RequiredMsvcrtVersionComponents[3]) << 0);
constexpr const wchar_t* RuntimeDllNames[] = {
#ifdef _DEBUG
L"msvcp140d.dll",
L"vcruntime140d.dll",
L"vcruntime140_1d.dll",
#else
L"msvcp140.dll",
L"vcruntime140.dll",
L"vcruntime140_1.dll",
#endif
};
uint64_t lowestVersion = 0;
for (const auto& runtimeDllName : RuntimeDllNames) {
const utils::loaded_module mod(GetModuleHandleW(runtimeDllName));
if (!mod) {
logging::E("MSVCRT DLL not found: {}", runtimeDllName);
continue;
}
const auto path = mod.path()
.transform([](const auto& p) { return p.wstring(); })
.value_or(runtimeDllName);
if (const auto versionResult = mod.get_file_version()) {
const auto& versionFull = versionResult->get();
logging::I("MSVCRT DLL {} has version {}.", path, utils::format_file_version(versionFull));
const auto version = 0ULL |
(static_cast<uint64_t>(versionFull.dwFileVersionMS) << 32) |
(static_cast<uint64_t>(versionFull.dwFileVersionLS) << 0);
if (version < RequiredMsvcrtVersion && (lowestVersion == 0 || lowestVersion > version))
lowestVersion = version;
} else {
logging::E("Failed to detect MSVCRT DLL version for {}: {}", path, versionResult.error().describe());
}
}
if (!lowestVersion)
return;
enum IdTaskDialogAction {
IdTaskDialogActionOpenDownload = 101,
IdTaskDialogActionIgnore,
};
const TASKDIALOG_BUTTON buttons[]{
{IdTaskDialogActionOpenDownload, MAKEINTRESOURCEW(IDS_MSVCRT_ACTION_OPENDOWNLOAD)},
{IdTaskDialogActionIgnore, MAKEINTRESOURCEW(IDS_MSVCRT_ACTION_IGNORE)},
};
const WORD lowestVersionComponents[]{
static_cast<WORD>(lowestVersion >> 48),
static_cast<WORD>(lowestVersion >> 32),
static_cast<WORD>(lowestVersion >> 16),
static_cast<WORD>(lowestVersion >> 0),
};
const auto dialogContent = std::vformat(
utils::get_string_resource(IDS_MSVCRT_DIALOG_CONTENT),
std::make_wformat_args(
lowestVersionComponents[0],
lowestVersionComponents[1],
lowestVersionComponents[2],
lowestVersionComponents[3]));
const TASKDIALOGCONFIG config{
.cbSize = sizeof config,
.hInstance = g_hModule,
.dwFlags = TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS,
.pszWindowTitle = MAKEINTRESOURCEW(IDS_APPNAME),
.pszMainIcon = MAKEINTRESOURCEW(IDI_ICON1),
.pszMainInstruction = MAKEINTRESOURCEW(IDS_MSVCRT_DIALOG_MAININSTRUCTION),
.pszContent = dialogContent.c_str(),
.cButtons = _countof(buttons),
.pButtons = buttons,
.nDefaultButton = IdTaskDialogActionOpenDownload,
};
int buttonPressed;
if (utils::scoped_dpi_awareness_context ctx;
FAILED(TaskDialogIndirect(&config, &buttonPressed, nullptr, nullptr)))
buttonPressed = IdTaskDialogActionOpenDownload;
switch (buttonPressed) {
case IdTaskDialogActionOpenDownload:
ShellExecuteW(
nullptr,
L"open",
utils::get_string_resource(IDS_MSVCRT_DOWNLOADURL).c_str(),
nullptr,
nullptr,
SW_SHOW);
ExitProcess(0);
break;
}
}
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
g_startInfo.from_envvars();
std::string jsonParseError;
try {
from_json(nlohmann::json::parse(std::string_view(static_cast<char*>(lpParam))), g_startInfo);
@ -20,8 +139,8 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
}
if (g_startInfo.BootShowConsole)
ConsoleSetup(L"Dalamud Boot");
ConsoleSetup(utils::get_string_resource(IDS_APPNAME).c_str());
logging::update_dll_load_status(true);
const auto logFilePath = unicode::convert<std::wstring>(g_startInfo.BootLogPath);
@ -29,16 +148,16 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
auto attemptFallbackLog = false;
if (logFilePath.empty()) {
attemptFallbackLog = true;
logging::I("No log file path given; not logging to file.");
} else {
try {
logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole);
logging::I("Logging to file: {}", logFilePath);
} catch (const std::exception& e) {
attemptFallbackLog = true;
logging::E("Couldn't open log file: {}", logFilePath);
logging::E("Error: {} / {}", errno, e.what());
}
@ -59,15 +178,15 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
SYSTEMTIME st;
GetLocalTime(&st);
logFilePath += std::format(L"Dalamud.Boot.{:04}{:02}{:02}.{:02}{:02}{:02}.{:03}.{}.log", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentProcessId());
try {
logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole);
logging::I("Logging to fallback log file: {}", logFilePath);
} catch (const std::exception& e) {
if (!g_startInfo.BootShowConsole && !g_startInfo.BootDisableFallbackConsole)
ConsoleSetup(L"Dalamud Boot - Fallback Console");
logging::E("Couldn't open fallback log file: {}", logFilePath);
logging::E("Error: {} / {}", errno, e.what());
}
@ -83,16 +202,81 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
} else {
logging::E("Failed to initialize MinHook (status={}({}))", MH_StatusToString(mhStatus), static_cast<int>(mhStatus));
}
logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
logging::I("Built at: " __DATE__ "@" __TIME__);
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
CheckMsvcrtVersion();
if (g_startInfo.BootDebugDirectX) {
logging::I("Enabling DirectX Debugging.");
const auto hD3D11 = GetModuleHandleW(L"d3d11.dll");
const auto hDXGI = GetModuleHandleW(L"dxgi.dll");
const auto pfnD3D11CreateDevice = static_cast<decltype(&D3D11CreateDevice)>(
hD3D11 ? static_cast<void*>(GetProcAddress(hD3D11, "D3D11CreateDevice")) : nullptr);
if (pfnD3D11CreateDevice) {
static hooks::direct_hook<decltype(D3D11CreateDevice)> s_hookD3D11CreateDevice(
"d3d11.dll!D3D11CreateDevice",
pfnD3D11CreateDevice);
s_hookD3D11CreateDevice.set_detour([](
IDXGIAdapter* pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
const D3D_FEATURE_LEVEL* pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
ID3D11Device** ppDevice,
D3D_FEATURE_LEVEL* pFeatureLevel,
ID3D11DeviceContext** ppImmediateContext
) -> HRESULT {
return s_hookD3D11CreateDevice.call_original(
pAdapter,
DriverType,
Software,
(Flags & ~D3D11_CREATE_DEVICE_PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY) | D3D11_CREATE_DEVICE_DEBUG,
pFeatureLevels,
FeatureLevels,
SDKVersion,
ppDevice,
pFeatureLevel,
ppImmediateContext);
});
} else {
logging::W("Could not find d3d11!D3D11CreateDevice.");
}
const auto pfnCreateDXGIFactory = static_cast<decltype(&CreateDXGIFactory)>(
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory")) : nullptr);
const auto pfnCreateDXGIFactory1 = static_cast<decltype(&CreateDXGIFactory1)>(
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory1")) : nullptr);
static const auto pfnCreateDXGIFactory2 = static_cast<decltype(&CreateDXGIFactory2)>(
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory2")) : nullptr);
if (pfnCreateDXGIFactory2) {
static hooks::direct_hook<decltype(CreateDXGIFactory)> s_hookCreateDXGIFactory(
"dxgi.dll!CreateDXGIFactory",
pfnCreateDXGIFactory);
static hooks::direct_hook<decltype(CreateDXGIFactory1)> s_hookCreateDXGIFactory1(
"dxgi.dll!CreateDXGIFactory1",
pfnCreateDXGIFactory1);
s_hookCreateDXGIFactory.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
});
s_hookCreateDXGIFactory1.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
});
} else {
logging::W("Could not find dxgi!CreateDXGIFactory2.");
}
}
if (minHookLoaded) {
logging::I("Applying fixes...");
xivfixes::apply_all(true);
std::thread([] { xivfixes::apply_all(true); }).join();
logging::I("Fixes OK");
} else {
logging::W("Skipping fixes, as MinHook has failed to load.");
@ -103,11 +287,14 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
while (!IsDebuggerPresent())
Sleep(100);
logging::I("Debugger attached.");
__debugbreak();
}
const auto fs_module_path = utils::get_module_path(g_hModule);
const auto runtimeconfig_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
const auto module_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.dll").wstring();
const auto fs_module_path = utils::loaded_module(g_hModule).path();
if (!fs_module_path)
return fs_module_path.error();
const auto runtimeconfig_path = std::filesystem::path(*fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
const auto module_path = std::filesystem::path(*fs_module_path).replace_filename(L"Dalamud.dll").wstring();
// ============================== CLR ========================================= //
@ -144,6 +331,51 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
logging::I("VEH was disabled manually");
}
// ============================== CLR Reporting =================================== //
// This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it
// was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now.
// Ideally all of this will go away once they get to it.
static std::shared_ptr<hooks::global_import_hook<decltype(ReportEventW)>> s_report_event_hook;
s_report_event_hook = std::make_shared<hooks::global_import_hook<decltype(ReportEventW)>>(
"advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW");
s_report_event_hook->set_detour([hook = s_report_event_hook.get()](
HANDLE hEventLog,
WORD wType,
WORD wCategory,
DWORD dwEventID,
PSID lpUserSid,
WORD wNumStrings,
DWORD dwDataSize,
LPCWSTR* lpStrings,
LPVOID lpRawData)-> BOOL {
// Check for CLR Error Event IDs
// https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/eventreporter.cpp#L370
if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception
dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast
dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime
dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow
dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed
{
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
}
if (wNumStrings == 0 || lpStrings == nullptr) {
logging::W("ReportEventW called with no strings.");
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
}
// In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log.
const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
const std::wstring error_details(lpStrings[0]);
veh::raise_external_event(error_details);
return original_ret;
});
logging::I("ReportEventW hook installed.");
// ============================== Dalamud ==================================== //
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
@ -174,11 +406,11 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese
case DLL_PROCESS_DETACH:
// do not show debug message boxes on abort() here
_set_abort_behavior(0, _WRITE_ABORT_MSG);
// process is terminating; don't bother cleaning up
if (lpReserved)
return TRUE;
logging::update_dll_load_status(false);
xivfixes::apply_all(false);

View file

@ -0,0 +1,26 @@
#include "error_info.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
DalamudBootError::DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription, long hresult) noexcept
: m_dalamudErrorDescription(dalamudErrorDescription)
, m_hresult(hresult) {
}
DalamudBootError::DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription) noexcept
: DalamudBootError(dalamudErrorDescription, E_FAIL) {
}
const char* DalamudBootError::describe() const {
switch (m_dalamudErrorDescription) {
case DalamudBootErrorDescription::ModuleResourceLoadFail:
return "Failed to load resource.";
case DalamudBootErrorDescription::ModuleResourceVersionReadFail:
return "Failed to query version information.";
case DalamudBootErrorDescription::ModuleResourceVersionSignatureFail:
return "Invalid version info found.";
default:
return "(unavailable)";
}
}

42
Dalamud.Boot/error_info.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include <expected>
#include <string>
typedef unsigned long DWORD;
typedef _Return_type_success_(return >= 0) long HRESULT;
enum class DalamudBootErrorDescription {
None,
ModulePathResolutionFail,
ModuleResourceLoadFail,
ModuleResourceVersionReadFail,
ModuleResourceVersionSignatureFail,
};
class DalamudBootError {
DalamudBootErrorDescription m_dalamudErrorDescription;
long m_hresult;
public:
DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription, long hresult) noexcept;
DalamudBootError(DalamudBootErrorDescription dalamudErrorDescription) noexcept;
const char* describe() const;
operator HRESULT() const {
return m_hresult;
}
};
template<typename T>
using DalamudExpected = std::expected<
std::conditional_t<
std::is_reference_v<T>,
std::reference_wrapper<std::remove_reference_t<T>>,
T
>,
DalamudBootError
>;
using DalamudUnexpected = std::unexpected<DalamudBootError>;

View file

@ -84,19 +84,13 @@ void hooks::getprocaddress_singleton_import_hook::initialize() {
const auto dllName = unicode::convert<std::string>(pData->Loaded.FullDllName->Buffer);
utils::loaded_module mod(pData->Loaded.DllBase);
std::wstring version, description;
try {
version = utils::format_file_version(mod.get_file_version());
} catch (...) {
version = L"<unknown>";
}
try {
description = mod.get_description();
} catch (...) {
description = L"<unknown>";
}
const auto version = mod.get_file_version()
.transform([](const auto& v) { return utils::format_file_version(v.get()); })
.value_or(L"<unknown>");
const auto description = mod.get_description()
.value_or(L"<unknown>");
logging::I(R"({} "{}" ("{}" ver {}) has been loaded at 0x{:X} ~ 0x{:X} (0x{:X}); finding import table items to hook.)",
LogTag, dllName, description, version,
reinterpret_cast<size_t>(pData->Loaded.DllBase),
@ -125,7 +119,9 @@ void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loade
if (mod.is_current_process())
return;
const auto path = unicode::convert<std::string>(mod.path().wstring());
const auto path = mod.path()
.transform([](const auto& p) { return unicode::convert<std::string>(p.wstring()); })
.value_or("<unknown>");
for (const auto& [hModule, targetFns] : m_targetFns) {
for (const auto& [targetFn, pfnThunk] : targetFns) {
@ -133,7 +129,7 @@ void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loade
if (void* pGetProcAddressImport; mod.find_imported_function_pointer(dllName.c_str(), targetFn.c_str(), 0, pGetProcAddressImport)) {
auto& hook = m_hooks[hModule][targetFn][mod];
if (!hook) {
logging::I("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, unicode::convert<std::string>(mod.path().wstring()));
logging::I("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, path);
hook.emplace(std::format("getprocaddress_singleton_import_hook::hook_module({}!{})", dllName, targetFn), static_cast<void**>(pGetProcAddressImport), pfnThunk);
}

View file

@ -11,6 +11,9 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
// https://developercommunity.visualstudio.com/t/Access-violation-with-std::mutex::lock-a/10664660
#define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR
// Windows Header Files (1)
#include <Windows.h>
@ -21,6 +24,7 @@
#include <iphlpapi.h>
#include <PathCch.h>
#include <Psapi.h>
#include <shellapi.h>
#include <ShlObj.h>
#include <Shlwapi.h>
#include <SubAuth.h>
@ -51,6 +55,7 @@
#include <set>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
// https://www.akenotsuki.com/misc/srell/en/

View file

@ -3,12 +3,23 @@
// Used by Dalamud.Boot.rc
//
#define IDI_ICON1 101
#define IDS_APPNAME 102
#define IDS_MSVCRT_ACTION_OPENDOWNLOAD 103
#define IDS_MSVCRT_ACTION_IGNORE 104
#define IDS_MSVCRT_DIALOG_MAININSTRUCTION 105
#define IDS_MSVCRT_DIALOG_CONTENT 106
#define IDS_MSVCRT_DOWNLOADURL 107
#define IDS_INITIALIZEFAIL_ACTION_ABORT 108
#define IDS_INITIALIZEFAIL_ACTION_CONTINUE 109
#define IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION 110
#define IDS_INITIALIZEFAIL_DIALOG_CONTENT 111
#define IDS_INITIALIZEFAIL_DIALOG_FOOTER 112
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101

View file

@ -2,6 +2,7 @@
#include "logging.h"
#include "utils.h"
#include "resource.h"
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
@ -379,12 +380,50 @@ extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointPara
auto desc = err.Description();
if (desc.length() == 0)
desc = err.ErrorMessage();
if (MessageBoxW(nullptr, std::format(
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\n{}\n{}",
last_operation,
desc.GetBSTR()).c_str(),
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
ExitProcess(-1);
enum IdTaskDialogAction {
IdTaskDialogActionAbort = 101,
IdTaskDialogActionContinue,
};
const TASKDIALOG_BUTTON buttons[]{
{IdTaskDialogActionAbort, MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_ACTION_ABORT)},
{IdTaskDialogActionContinue, MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_ACTION_CONTINUE)},
};
const auto hru32 = static_cast<uint32_t>(hr);
const auto footer = std::vformat(
utils::get_string_resource(IDS_INITIALIZEFAIL_DIALOG_FOOTER),
std::make_wformat_args(
last_operation,
hru32,
desc.GetBSTR()));
const TASKDIALOGCONFIG config{
.cbSize = sizeof config,
.hInstance = g_hModule,
.dwFlags = TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS | TDF_EXPAND_FOOTER_AREA,
.pszWindowTitle = MAKEINTRESOURCEW(IDS_APPNAME),
.pszMainIcon = MAKEINTRESOURCEW(IDI_ICON1),
.pszMainInstruction = MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_DIALOG_MAININSTRUCTION),
.pszContent = MAKEINTRESOURCEW(IDS_INITIALIZEFAIL_DIALOG_CONTENT),
.cButtons = _countof(buttons),
.pButtons = buttons,
.nDefaultButton = IdTaskDialogActionAbort,
.pszFooter = footer.c_str(),
};
int buttonPressed;
if (utils::scoped_dpi_awareness_context ctx;
FAILED(TaskDialogIndirect(&config, &buttonPressed, nullptr, nullptr)))
buttonPressed = IdTaskDialogActionAbort;
switch (buttonPressed) {
case IdTaskDialogActionAbort:
ExitProcess(-1);
break;
}
if (hMainThreadContinue) {
CloseHandle(hMainThreadContinue);
hMainThreadContinue = nullptr;

View file

@ -1,23 +1,29 @@
#include "pch.h"
#include "DalamudStartInfo.h"
#include "utils.h"
std::filesystem::path utils::loaded_module::path() const {
std::wstring buf(MAX_PATH, L'\0');
for (;;) {
if (const auto len = GetModuleFileNameExW(GetCurrentProcess(), m_hModule, &buf[0], static_cast<DWORD>(buf.size())); len != buf.size()) {
if (buf.empty())
throw std::runtime_error(std::format("Failed to resolve module path: Win32 error {}", GetLastError()));
DalamudExpected<std::filesystem::path> utils::loaded_module::path() const {
for (std::wstring buf(MAX_PATH, L'\0');; buf.resize(buf.size() * 2)) {
if (const auto len = GetModuleFileNameW(m_hModule, &buf[0], static_cast<DWORD>(buf.size()));
len != buf.size()) {
if (!len) {
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModulePathResolutionFail,
HRESULT_FROM_WIN32(GetLastError()));
}
buf.resize(len);
return buf;
}
if (buf.size() * 2 < PATHCCH_MAX_CCH)
buf.resize(buf.size() * 2);
else if (auto p = std::filesystem::path(buf); exists(p))
return p;
else
throw std::runtime_error("Failed to resolve module path: no amount of buffer size would fit the data");
if (buf.size() > PATHCCH_MAX_CCH) {
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModulePathResolutionFail,
E_OUTOFMEMORY);
}
}
}
@ -143,66 +149,90 @@ void* utils::loaded_module::get_imported_function_pointer(const char* pcszDllNam
throw std::runtime_error(std::format("Failed to find import for {}!{} ({}).", pcszDllName, pcszFunctionName ? pcszFunctionName : "<unnamed>", hintOrOrdinal));
}
std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)> utils::loaded_module::get_resource(LPCWSTR lpName, LPCWSTR lpType) const {
DalamudExpected<std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>> utils::loaded_module::get_resource(LPCWSTR lpName, LPCWSTR lpType) const {
const auto hres = FindResourceW(m_hModule, lpName, lpType);
if (!hres)
throw std::runtime_error("No such resource");
return DalamudUnexpected(std::in_place, DalamudBootErrorDescription::ModuleResourceLoadFail, GetLastError());
const auto hRes = LoadResource(m_hModule, hres);
if (!hRes)
throw std::runtime_error("LoadResource failure");
return DalamudUnexpected(std::in_place, DalamudBootErrorDescription::ModuleResourceLoadFail, GetLastError());
return {hRes, &FreeResource};
return std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>(hRes, &FreeResource);
}
std::wstring utils::loaded_module::get_description() const {
const auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
const auto pBlock = LockResource(rsrc.get());
DalamudExpected<std::wstring> utils::loaded_module::get_description() const {
auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
if (!rsrc)
return DalamudUnexpected(std::move(rsrc.error()));
const auto pBlock = LockResource(rsrc->get());
struct LANGANDCODEPAGE {
WORD wLanguage;
WORD wCodePage;
} * lpTranslate;
UINT cbTranslate;
if (!VerQueryValueW(pBlock,
TEXT("\\VarFileInfo\\Translation"),
L"\\VarFileInfo\\Translation",
reinterpret_cast<LPVOID*>(&lpTranslate),
&cbTranslate)) {
throw std::runtime_error("Invalid version information (1)");
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
HRESULT_FROM_WIN32(GetLastError()));
}
for (size_t i = 0; i < (cbTranslate / sizeof(LANGANDCODEPAGE)); i++) {
wchar_t subblockNameBuf[64];
*std::format_to_n(
subblockNameBuf,
_countof(subblockNameBuf),
L"\\StringFileInfo\\{:04x}{:04x}\\FileDescription",
lpTranslate[i].wLanguage,
lpTranslate[i].wCodePage).out = 0;;
wchar_t* buf = nullptr;
UINT size = 0;
if (!VerQueryValueW(pBlock,
std::format(L"\\StringFileInfo\\{:04x}{:04x}\\FileDescription",
lpTranslate[i].wLanguage,
lpTranslate[i].wCodePage).c_str(),
reinterpret_cast<LPVOID*>(&buf),
&size)) {
if (!VerQueryValueW(pBlock, subblockNameBuf, reinterpret_cast<LPVOID*>(&buf), &size))
continue;
}
auto currName = std::wstring_view(buf, size);
while (!currName.empty() && currName.back() == L'\0')
currName = currName.substr(0, currName.size() - 1);
if (const auto p = currName.find(L'\0'); p != std::string::npos)
currName = currName.substr(0, p);
if (currName.empty())
continue;
return std::wstring(currName);
}
throw std::runtime_error("Invalid version information (2)");
return DalamudUnexpected(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
}
VS_FIXEDFILEINFO utils::loaded_module::get_file_version() const {
const auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
const auto pBlock = LockResource(rsrc.get());
std::expected<std::reference_wrapper<const VS_FIXEDFILEINFO>, DalamudBootError> utils::loaded_module::get_file_version() const {
auto rsrc = get_resource(MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
if (!rsrc)
return DalamudUnexpected(std::move(rsrc.error()));
const auto pBlock = LockResource(rsrc->get());
UINT size = 0;
LPVOID lpBuffer = nullptr;
if (!VerQueryValueW(pBlock, L"\\", &lpBuffer, &size))
throw std::runtime_error("Failed to query version information.");
if (!VerQueryValueW(pBlock, L"\\", &lpBuffer, &size)) {
return std::unexpected<DalamudBootError>(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionReadFail,
HRESULT_FROM_WIN32(GetLastError()));
}
const VS_FIXEDFILEINFO& versionInfo = *static_cast<const VS_FIXEDFILEINFO*>(lpBuffer);
if (versionInfo.dwSignature != 0xfeef04bd)
throw std::runtime_error("Invalid version info found.");
if (versionInfo.dwSignature != 0xfeef04bd) {
return std::unexpected<DalamudBootError>(
std::in_place,
DalamudBootErrorDescription::ModuleResourceVersionSignatureFail);
}
return versionInfo;
}
@ -352,7 +382,7 @@ const char* utils::signature_finder::result::resolve_jump_target(size_t instruct
nmd_x86_instruction instruction{};
if (!nmd_x86_decode(&Match[instructionOffset], NMD_X86_MAXIMUM_INSTRUCTION_LENGTH, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL))
throw std::runtime_error("Matched address does not have a valid assembly instruction");
size_t numExplicitOperands = 0;
for (size_t i = 0; i < instruction.num_operands; i++)
numExplicitOperands += instruction.operands[i].is_implicit ? 0 : 1;
@ -584,17 +614,14 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
return res;
}
std::filesystem::path utils::get_module_path(HMODULE hModule) {
std::wstring buf(MAX_PATH, L'\0');
while (true) {
if (const auto res = GetModuleFileNameW(hModule, &buf[0], static_cast<int>(buf.size())); !res)
throw std::runtime_error(std::format("GetModuleFileName failure: 0x{:X}", GetLastError()));
else if (res < buf.size()) {
buf.resize(res);
return buf;
} else
buf.resize(buf.size() * 2);
}
bool utils::is_running_on_wine() {
return g_startInfo.Platform != "WINDOWS";
}
std::wstring utils::get_string_resource(uint32_t resId) {
LPCWSTR pstr;
const auto len = LoadStringW(g_hModule, resId, reinterpret_cast<LPWSTR>(&pstr), 0);
return std::wstring(pstr, len);
}
HWND utils::try_find_game_window() {
@ -620,7 +647,7 @@ void utils::wait_for_game_window() {
std::wstring utils::escape_shell_arg(const std::wstring& arg) {
// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
std::wstring res;
if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
res.append(arg);
@ -672,3 +699,22 @@ std::wstring utils::format_win32_error(DWORD err) {
return std::format(L"Win32 error ({}=0x{:X})", err, err);
}
utils::scoped_dpi_awareness_context::scoped_dpi_awareness_context()
: scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) {
}
utils::scoped_dpi_awareness_context::scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT context) {
const auto user32 = GetModuleHandleW(L"user32.dll");
m_setThreadDpiAwarenessContext =
user32
? reinterpret_cast<decltype(&SetThreadDpiAwarenessContext)>(
GetProcAddress(user32, "SetThreadDpiAwarenessContext"))
: nullptr;
m_old = m_setThreadDpiAwarenessContext ? m_setThreadDpiAwarenessContext(context) : DPI_AWARENESS_CONTEXT_UNAWARE;
}
utils::scoped_dpi_awareness_context::~scoped_dpi_awareness_context() {
if (m_setThreadDpiAwarenessContext)
m_setThreadDpiAwarenessContext(m_old);
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <expected>
#include <filesystem>
#include <functional>
#include <span>
@ -7,6 +8,7 @@
#include <memory>
#include <vector>
#include "error_info.h"
#include "unicode.h"
namespace utils {
@ -18,14 +20,13 @@ namespace utils {
loaded_module(void* hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
loaded_module(size_t hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
std::filesystem::path path() const;
DalamudExpected<std::filesystem::path> path() const;
bool is_current_process() const { return m_hModule == GetModuleHandleW(nullptr); }
bool owns_address(const void* pAddress) const;
operator HMODULE() const {
return m_hModule;
}
operator HMODULE() const { return m_hModule; }
operator bool() const { return m_hModule; }
size_t address_int() const { return reinterpret_cast<size_t>(m_hModule); }
size_t image_size() const { return is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage; }
@ -58,9 +59,9 @@ namespace utils {
void* get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) const;
template<typename TFn> TFn** get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { return reinterpret_cast<TFn**>(get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)); }
[[nodiscard]] std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)> get_resource(LPCWSTR lpName, LPCWSTR lpType) const;
[[nodiscard]] std::wstring get_description() const;
[[nodiscard]] VS_FIXEDFILEINFO get_file_version() const;
[[nodiscard]] DalamudExpected<std::unique_ptr<std::remove_pointer_t<HGLOBAL>, decltype(&FreeResource)>> get_resource(LPCWSTR lpName, LPCWSTR lpType) const;
[[nodiscard]] DalamudExpected<std::wstring> get_description() const;
[[nodiscard]] DalamudExpected<const VS_FIXEDFILEINFO&> get_file_version() const;
static loaded_module current_process();
static std::vector<loaded_module> all_modules();
@ -267,7 +268,9 @@ namespace utils {
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
}
std::filesystem::path get_module_path(HMODULE hModule);
bool is_running_on_wine();
std::wstring get_string_resource(uint32_t resId);
/// @brief Find the game main window.
/// @return Handle to the game main window, or nullptr if it doesn't exist (yet).
@ -278,4 +281,18 @@ namespace utils {
std::wstring escape_shell_arg(const std::wstring& arg);
std::wstring format_win32_error(DWORD err);
class scoped_dpi_awareness_context {
DPI_AWARENESS_CONTEXT m_old;
decltype(&SetThreadDpiAwarenessContext) m_setThreadDpiAwarenessContext;
public:
scoped_dpi_awareness_context();
scoped_dpi_awareness_context(DPI_AWARENESS_CONTEXT);
~scoped_dpi_awareness_context();
scoped_dpi_awareness_context(const scoped_dpi_awareness_context&) = delete;
scoped_dpi_awareness_context(scoped_dpi_awareness_context&&) = delete;
scoped_dpi_awareness_context& operator=(const scoped_dpi_awareness_context&) = delete;
scoped_dpi_awareness_context& operator=(scoped_dpi_awareness_context&&) = delete;
};
}

View file

@ -31,6 +31,8 @@ HANDLE g_crashhandler_process = nullptr;
HANDLE g_crashhandler_event = nullptr;
HANDLE g_crashhandler_pipe_write = nullptr;
wchar_t g_external_event_info[16384] = L"";
std::recursive_mutex g_exception_handler_mutex;
std::chrono::time_point<std::chrono::system_clock> g_time_start;
@ -102,9 +104,13 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
return false;
}
static void append_injector_launch_args(std::vector<std::wstring>& args)
static DalamudExpected<void> append_injector_launch_args(std::vector<std::wstring>& args)
{
args.emplace_back(L"--game=\"" + utils::loaded_module::current_process().path().wstring() + L"\"");
if (auto path = utils::loaded_module::current_process().path())
args.emplace_back(L"--game=\"" + path->wstring() + L"\"");
else
return DalamudUnexpected(std::in_place, std::move(path.error()));
switch (g_startInfo.DalamudLoadMethod) {
case DalamudStartInfo::LoadMethod::Entrypoint:
args.emplace_back(L"--mode=entrypoint");
@ -118,6 +124,7 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
args.emplace_back(L"--logname=\"" + unicode::convert<std::wstring>(g_startInfo.LogName) + L"\"");
args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert<std::wstring>(g_startInfo.PluginDirectory) + L"\"");
args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
args.emplace_back(L"--dalamud-temp-directory=\"" + unicode::convert<std::wstring>(g_startInfo.TempDirectory) + L"\"");
args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast<int>(g_startInfo.Language)));
args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs));
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler
@ -155,6 +162,8 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
args.emplace_back(szArgList[i]);
LocalFree(szArgList);
}
return {};
}
LONG exception_handler(EXCEPTION_POINTERS* ex)
@ -184,7 +193,11 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
std::wstring stackTrace;
if (!g_clr)
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
{
stackTrace = std::wstring(g_external_event_info);
}
else if (!g_clr)
{
stackTrace = L"(no CLR stack trace available)";
}
@ -245,6 +258,12 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
{
// special case for CLR exceptions, always trigger crash handler
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
{
return exception_handler(ex);
}
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
{
// pass
@ -262,7 +281,7 @@ LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
return EXCEPTION_CONTINUE_SEARCH;
return EXCEPTION_CONTINUE_SEARCH;
}
return exception_handler(ex);
@ -291,7 +310,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536))
{
hWritePipe.emplace(hWritePipeRaw, &CloseHandle);
if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
{
hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle);
@ -309,9 +328,9 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
}
// additional information
STARTUPINFOEXW siex{};
STARTUPINFOEXW siex{};
PROCESS_INFORMATION pi{};
siex.StartupInfo.cb = sizeof siex;
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
siex.StartupInfo.wShowWindow = g_startInfo.CrashHandlerShow ? SW_SHOW : SW_HIDE;
@ -358,11 +377,20 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
args.emplace_back(std::format(L"--process-handle={}", reinterpret_cast<size_t>(hInheritableCurrentProcess)));
args.emplace_back(std::format(L"--exception-info-pipe-read-handle={}", reinterpret_cast<size_t>(hReadPipeInheritable->get())));
args.emplace_back(std::format(L"--asset-directory={}", unicode::convert<std::wstring>(g_startInfo.AssetDirectory)));
args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
? utils::loaded_module(g_hModule).path().parent_path().wstring()
: std::filesystem::path(unicode::convert<std::wstring>(g_startInfo.BootLogPath)).parent_path().wstring()));
if (const auto path = utils::loaded_module(g_hModule).path()) {
args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
? path->parent_path().wstring()
: std::filesystem::path(unicode::convert<std::wstring>(g_startInfo.BootLogPath)).parent_path().wstring()));
} else {
logging::W("Failed to read path of the Dalamud Boot module: {}", path.error().describe());
return false;
}
args.emplace_back(L"--");
append_injector_launch_args(args);
if (auto r = append_injector_launch_args(args); !r) {
logging::W("Failed to generate injector launch args: {}", r.error().describe());
return false;
}
for (const auto& arg : args)
{
@ -370,7 +398,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
argstr.push_back(L' ');
}
argstr.pop_back();
if (!handles.empty() && !UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles[0], std::span(handles).size_bytes(), nullptr, nullptr))
{
logging::W("Failed to launch DalamudCrashHandler.exe: UpdateProcThreadAttribute error 0x{:x}", GetLastError());
@ -385,7 +413,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
TRUE, // Set handle inheritance to FALSE
EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W)
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
nullptr, // Use parent's starting directory
&siex.StartupInfo, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
))
@ -401,7 +429,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
}
CloseHandle(pi.hThread);
g_crashhandler_process = pi.hProcess;
g_crashhandler_pipe_write = hWritePipe->release();
logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId);
@ -419,3 +447,16 @@ bool veh::remove_handler()
}
return false;
}
void veh::raise_external_event(const std::wstring& info)
{
const auto info_size = std::min(info.size(), std::size(g_external_event_info) - 1);
wcsncpy_s(g_external_event_info, info.c_str(), info_size);
RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr);
}
extern "C" __declspec(dllexport) void BootVehRaiseExternalEventW(LPCWSTR info)
{
const std::wstring info_wstr(info);
veh::raise_external_event(info_wstr);
}

View file

@ -4,4 +4,5 @@ namespace veh
{
bool add_handler(bool doFullDump, const std::string& workingDirectory);
bool remove_handler();
void raise_external_event(const std::wstring& info);
}

View file

@ -8,12 +8,6 @@
#include "ntdll.h"
#include "utils.h"
template<typename T>
static std::span<T> assume_nonempty_span(std::span<T> t, const char* descr) {
if (t.empty())
throw std::runtime_error(std::format("Unexpected empty span found: {}", descr));
return t;
}
void xivfixes::unhook_dll(bool bApply) {
static const auto LogTag = "[xivfixes:unhook_dll]";
static const auto LogTagW = L"[xivfixes:unhook_dll]";
@ -23,77 +17,90 @@ void xivfixes::unhook_dll(bool bApply) {
const auto mods = utils::loaded_module::all_modules();
const auto test_module = [&](size_t i, const utils::loaded_module & mod) {
std::filesystem::path path;
try {
path = mod.path();
std::wstring version, description;
try {
version = utils::format_file_version(mod.get_file_version());
} catch (...) {
version = L"<unknown>";
}
try {
description = mod.get_description();
} catch (...) {
description = L"<unknown>";
}
logging::I(R"({} [{}/{}] Module 0x{:X} ~ 0x{:X} (0x{:X}): "{}" ("{}" ver {}))", LogTagW, i + 1, mods.size(), mod.address_int(), mod.address_int() + mod.image_size(), mod.image_size(), path.wstring(), description, version);
} catch (const std::exception& e) {
logging::W("{} [{}/{}] Module 0x{:X}: Failed to resolve path: {}", LogTag, i + 1, mods.size(), mod.address_int(), e.what());
for (size_t i = 0; i < mods.size(); i++) {
const auto& mod = mods[i];
const auto path = mod.path();
if (!path) {
logging::W(
"{} [{}/{}] Module 0x{:X}: Failed to resolve path: {}",
LogTag,
i + 1,
mods.size(),
mod.address_int(),
path.error().describe());
return;
}
const auto moduleName = unicode::convert<std::string>(path.filename().wstring());
const auto version = mod.get_file_version()
.transform([](const auto& v) { return utils::format_file_version(v.get()); })
.value_or(L"<unknown>");
std::vector<char> buf;
std::string formatBuf;
const auto description = mod.get_description()
.value_or(L"<unknown>");
logging::I(
R"({} [{}/{}] Module 0x{:X} ~ 0x{:X} (0x{:X}): "{}" ("{}" ver {}))",
LogTagW,
i + 1,
mods.size(),
mod.address_int(),
mod.address_int() + mod.image_size(),
mod.image_size(),
path->wstring(),
description,
version);
const auto moduleName = unicode::convert<std::string>(path->filename().wstring());
const auto& sectionHeader = mod.section_header(".text");
const auto section = mod.span_as<char>(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize);
if (section.empty()) {
logging::W("{} Error: .text[VA:VA + VS] is empty", LogTag);
return;
}
auto hFsDllRaw = CreateFileW(path->c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (hFsDllRaw == INVALID_HANDLE_VALUE) {
logging::W("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError());
return;
}
auto hFsDll = std::unique_ptr<void, decltype(&CloseHandle)>(hFsDllRaw, &CloseHandle);
std::vector<char> buf(section.size());
SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT);
if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast<DWORD>(buf.size()), &read, nullptr)) {
if (read < section.size_bytes()) {
logging::W("{} ReadFile: read {} bytes < requested {} bytes", LogTagW, read, section.size_bytes());
return;
}
} else {
logging::I("{} ReadFile: Win32 error {}", LogTagW, GetLastError());
return;
}
const auto doRestore = g_startInfo.BootUnhookDlls.contains(unicode::convert<std::string>(path->filename().u8string()));
try {
const auto& sectionHeader = mod.section_header(".text");
const auto section = assume_nonempty_span(mod.span_as<char>(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize), ".text[VA:VA+VS]");
auto hFsDllRaw = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (hFsDllRaw == INVALID_HANDLE_VALUE) {
logging::W("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError());
return;
}
auto hFsDll = std::unique_ptr<void, decltype(CloseHandle)*>(hFsDllRaw, &CloseHandle);
buf.resize(section.size());
SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT);
if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast<DWORD>(buf.size()), &read, nullptr)) {
if (read < section.size_bytes()) {
logging::W("{} ReadFile: read {} bytes < requested {} bytes", LogTagW, read, section.size_bytes());
return;
}
} else {
logging::I("{} ReadFile: Win32 error {}", LogTagW, GetLastError());
return;
}
const auto doRestore = g_startInfo.BootUnhookDlls.contains(unicode::convert<std::string>(path.filename().u8string()));
std::optional<utils::memory_tenderizer> tenderizer;
for (size_t i = 0, instructionLength = 1, printed = 0; i < buf.size(); i += instructionLength) {
if (section[i] == buf[i]) {
std::string formatBuf;
for (size_t inst = 0, instructionLength = 1, printed = 0; inst < buf.size(); inst += instructionLength) {
if (section[inst] == buf[inst]) {
instructionLength = 1;
continue;
}
const auto rva = sectionHeader.VirtualAddress + i;
const auto rva = sectionHeader.VirtualAddress + inst;
nmd_x86_instruction instruction{};
if (!nmd_x86_decode(&section[i], section.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
if (!nmd_x86_decode(&section[inst], section.size() - inst, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
instructionLength = 1;
if (printed < 64) {
logging::W("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[i]));
logging::W("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[inst]));
printed++;
}
} else {
instructionLength = instruction.length;
if (printed < 64) {
formatBuf.resize(128);
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(&section[i]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES);
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(&section[inst]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES);
formatBuf.resize(strnlen(&formatBuf[0], formatBuf.size()));
const auto& directory = mod.data_directory(IMAGE_DIRECTORY_ENTRY_EXPORT);
@ -103,25 +110,25 @@ void xivfixes::unhook_dll(bool bApply) {
const auto functions = mod.span_as<DWORD>(exportDirectory.AddressOfFunctions, exportDirectory.NumberOfFunctions);
std::string resolvedExportName;
for (size_t j = 0; j < names.size(); ++j) {
for (size_t nameIndex = 0; nameIndex < names.size(); ++nameIndex) {
std::string_view name;
if (const char* pcszName = mod.address_as<char>(names[j]); pcszName < mod.address() || pcszName >= mod.address() + mod.image_size()) {
if (const char* pcszName = mod.address_as<char>(names[nameIndex]); pcszName < mod.address() || pcszName >= mod.address() + mod.image_size()) {
if (IsBadReadPtr(pcszName, 256)) {
logging::W("{} Name #{} points to an invalid address outside the executable. Skipping.", LogTag, j);
logging::W("{} Name #{} points to an invalid address outside the executable. Skipping.", LogTag, nameIndex);
continue;
}
name = std::string_view(pcszName, strnlen(pcszName, 256));
logging::W("{} Name #{} points to a seemingly valid address outside the executable: {}", LogTag, j, name);
logging::W("{} Name #{} points to a seemingly valid address outside the executable: {}", LogTag, nameIndex, name);
}
if (ordinals[j] >= functions.size()) {
logging::W("{} Ordinal #{} points to function index #{} >= #{}. Skipping.", LogTag, j, ordinals[j], functions.size());
if (ordinals[nameIndex] >= functions.size()) {
logging::W("{} Ordinal #{} points to function index #{} >= #{}. Skipping.", LogTag, nameIndex, ordinals[nameIndex], functions.size());
continue;
}
const auto rva = functions[ordinals[j]];
if (rva == &section[i] - mod.address()) {
const auto rva = functions[ordinals[nameIndex]];
if (rva == &section[inst] - mod.address()) {
resolvedExportName = std::format("[export:{}]", name);
break;
}
@ -135,7 +142,7 @@ void xivfixes::unhook_dll(bool bApply) {
if (doRestore) {
if (!tenderizer)
tenderizer.emplace(section, PAGE_EXECUTE_READWRITE);
memcpy(&section[i], &buf[i], instructionLength);
memcpy(&section[inst], &buf[inst], instructionLength);
}
}
@ -147,21 +154,7 @@ void xivfixes::unhook_dll(bool bApply) {
} catch (const std::exception& e) {
logging::W("{} Error: {}", LogTag, e.what());
}
};
// This is needed since try and __try cannot be used in the same function. Lambdas circumvent the limitation.
const auto windows_exception_handler = [&]() {
for (size_t i = 0; i < mods.size(); i++) {
const auto& mod = mods[i];
__try {
test_module(i, mod);
} __except (EXCEPTION_EXECUTE_HANDLER) {
logging::W("{} Error: Access Violation", LogTag);
}
}
};
windows_exception_handler();
}
}
using TFnGetInputDeviceManager = void* ();
@ -294,13 +287,11 @@ static bool is_xivalex(const std::filesystem::path& dllPath) {
static bool is_openprocess_already_dealt_with() {
static const auto s_value = [] {
for (const auto& mod : utils::loaded_module::all_modules()) {
try {
if (is_xivalex(mod.path()))
return true;
} catch (...) {
// pass
}
const auto path = mod.path().value_or({});
if (path.empty())
continue;
if (is_xivalex(path))
return true;
}
return false;
}();
@ -648,6 +639,27 @@ void xivfixes::symbol_load_patches(bool bApply) {
}
}
void xivfixes::disable_game_debugging_protection(bool bApply) {
static const char* LogTag = "[xivfixes:disable_game_debugging_protection]";
static std::optional<hooks::import_hook<decltype(IsDebuggerPresent)>> s_hookIsDebuggerPresent;
if (bApply) {
if (!g_startInfo.BootEnabledGameFixes.contains("disable_game_debugging_protection")) {
logging::I("{} Turned off via environment variable.", LogTag);
return;
}
s_hookIsDebuggerPresent.emplace("kernel32.dll!IsDebuggerPresent", "kernel32.dll", "IsDebuggerPresent", 0);
s_hookIsDebuggerPresent->set_detour([]() { return false; });
logging::I("{} Enable", LogTag);
} else {
if (s_hookIsDebuggerPresent) {
logging::I("{} Disable", LogTag);
s_hookIsDebuggerPresent.reset();
}
}
}
void xivfixes::apply_all(bool bApply) {
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
{
@ -658,6 +670,7 @@ void xivfixes::apply_all(bool bApply) {
{ "backup_userdata_save", &backup_userdata_save },
{ "prevent_icmphandle_crashes", &prevent_icmphandle_crashes },
{ "symbol_load_patches", &symbol_load_patches },
{ "disable_game_debugging_protection", &disable_game_debugging_protection },
}
) {
try {

View file

@ -8,6 +8,7 @@ namespace xivfixes {
void backup_userdata_save(bool bApply);
void prevent_icmphandle_crashes(bool bApply);
void symbol_load_patches(bool bApply);
void disable_game_debugging_protection(bool bApply);
void apply_all(bool bApply);
}

View file

@ -1,13 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>
</Project>

View file

@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using Dalamud.Common.Game;
using Newtonsoft.Json;
@ -15,7 +16,7 @@ public record DalamudStartInfo
/// </summary>
public DalamudStartInfo()
{
// ignored
this.Platform = OSPlatform.Create("UNKNOWN");
}
/// <summary>
@ -33,6 +34,12 @@ public record DalamudStartInfo
/// </summary>
public string? ConfigurationPath { get; set; }
/// <summary>
/// Gets or sets the directory for temporary files. This directory needs to exist and be writable to the user.
/// It should also be predictable and easy for launchers to find.
/// </summary>
public string? TempDirectory { get; set; }
/// <summary>
/// Gets or sets the path of the log files.
/// </summary>
@ -58,6 +65,12 @@ public record DalamudStartInfo
/// </summary>
public ClientLanguage Language { get; set; } = ClientLanguage.English;
/// <summary>
/// Gets or sets the underlying platform<72>Dalamud runs on.
/// </summary>
[JsonConverter(typeof(OSPlatformConverter))]
public OSPlatform Platform { get; set; }
/// <summary>
/// Gets or sets the current game version code.
/// </summary>
@ -94,6 +107,11 @@ public record DalamudStartInfo
/// </summary>
public bool BootShowConsole { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable D3D11 and DXGI debugging if possible.
/// </summary>
public bool BootDebugDirectX { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the fallback console should be shown, if needed.
/// </summary>
@ -120,7 +138,7 @@ public record DalamudStartInfo
public bool BootVehFull { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not ETW should be enabled.
/// Gets or sets a value indicating whether ETW should be enabled.
/// </summary>
public bool BootEnableEtw { get; set; }

View file

@ -0,0 +1,78 @@
using System.Runtime.InteropServices;
using Newtonsoft.Json;
namespace Dalamud.Common;
/// <summary>
/// Converts a <see cref="OSPlatform"/> to and from a string (e.g. <c>"FreeBSD"</c>).
/// </summary>
public sealed class OSPlatformConverter : JsonConverter
{
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
}
else if (value is OSPlatform)
{
writer.WriteValue(value.ToString());
}
else
{
throw new JsonSerializationException("Expected OSPlatform object value");
}
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else
{
if (reader.TokenType == JsonToken.String)
{
try
{
return OSPlatform.Create((string)reader.Value!);
}
catch (Exception ex)
{
throw new JsonSerializationException($"Error parsing OSPlatform string: {reader.Value}", ex);
}
}
else
{
throw new JsonSerializationException($"Unexpected token or value when parsing OSPlatform. Token: {reader.TokenType}, Value: {reader.Value}");
}
}
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(OSPlatform);
}
}

View file

@ -0,0 +1,18 @@
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Common.Util;
public static class EnvironmentUtils
{
/// <summary>
/// Attempts to get an environment variable using the Try pattern.
/// </summary>
/// <param name="variableName">The env var to get.</param>
/// <param name="value">An output containing the env var, if present.</param>
/// <returns>A boolean indicating whether the var was present.</returns>
public static bool TryGetEnvironmentVariable(string variableName, [NotNullWhen(true)] out string? value)
{
value = Environment.GetEnvironmentVariable(variableName);
return value != null;
}
}

View file

@ -1,9 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Dalamud.CorePlugin</AssemblyName>
<TargetFramework>net8.0-windows</TargetFramework>
<Platforms>x64</Platforms>
<LangVersion>10.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
@ -27,10 +25,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lumina" Version="5.6.0" />
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PackageReference Include="Lumina" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@ -40,15 +37,6 @@
<ProjectReference Include="..\Dalamud\Dalamud.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>

View file

@ -46,6 +46,8 @@ namespace Dalamud.CorePlugin
#else
private readonly WindowSystem windowSystem = new("Dalamud.CorePlugin");
private readonly PluginWindow window;
private Localization localization;
private IPluginLog pluginLog;
@ -63,7 +65,8 @@ namespace Dalamud.CorePlugin
this.Interface = pluginInterface;
this.pluginLog = log;
this.windowSystem.AddWindow(new PluginWindow());
this.window = new PluginWindow();
this.windowSystem.AddWindow(this.window);
this.Interface.UiBuilder.Draw += this.OnDraw;
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
@ -136,12 +139,12 @@ namespace Dalamud.CorePlugin
{
this.pluginLog.Information("Command called!");
// this.window.IsOpen = true;
this.window.IsOpen ^= true;
}
private void OnOpenConfigUi()
{
// this.window.IsOpen = true;
this.window.IsOpen = true;
}
private void OnOpenMainUi()

View file

@ -1,8 +1,8 @@
using System;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Dalamud.CorePlugin
{

View file

@ -1,110 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{8874326B-E755-4D13-90B4-59AB263A3E6B}</ProjectGuid>
<RootNamespace>Dalamud_Injector_Boot</RootNamespace>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Platform>x64</Platform>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<TargetName>Dalamud.Injector</TargetName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<LinkIncremental>false</LinkIncremental>
<CharacterSet>Unicode</CharacterSet>
<OutDir>..\bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PreprocessorDefinitions>CPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ProgramDatabaseFile>$(OutDir)$(TargetName).Boot.pdb</ProgramDatabaseFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>false</IntrinsicFunctions>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>false</EnableCOMDATFolding>
<OptimizeReferences>false</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ItemGroup>
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
<Link>nethost.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Image Include="dalamud.ico" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\Dalamud.Boot\logging.cpp" />
<ClCompile Include="..\Dalamud.Boot\unicode.cpp" />
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\Dalamud.Boot\logging.h" />
<ClInclude Include="..\Dalamud.Boot\unicode.h" />
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
<Delete Files="$(OutDir)$(TargetName).lib" />
<Delete Files="$(OutDir)$(TargetName).exp" />
</Target>
</Project>

View file

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{4faac519-3a73-4b2b-96e7-fb597f02c0be}</UniqueIdentifier>
<Extensions>ico;rc</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<Image Include="dalamud.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Dalamud.Boot\logging.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Dalamud.Boot\unicode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Dalamud.Boot\logging.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Dalamud.Boot\unicode.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -1,48 +0,0 @@
#define WIN32_LEAN_AND_MEAN
#include <filesystem>
#include <Windows.h>
#include <shellapi.h>
#include "..\Dalamud.Boot\logging.h"
#include "..\lib\CoreCLR\CoreCLR.h"
#include "..\lib\CoreCLR\boot.h"
int wmain(int argc, wchar_t** argv)
{
// Take care: don't redirect stderr/out here, we need to write our pid to stdout for XL to read
//logging::start_file_logging("dalamud.injector.boot.log", false);
logging::I("Dalamud Injector, (c) 2021 XIVLauncher Contributors");
logging::I("Built at : " __DATE__ "@" __TIME__);
wchar_t _module_path[MAX_PATH];
GetModuleFileNameW(NULL, _module_path, sizeof _module_path / 2);
std::filesystem::path fs_module_path(_module_path);
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.runtimeconfig.json").c_str());
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.Injector.dll").c_str());
// =========================================================================== //
void* entrypoint_vfn;
const auto result = InitializeClrAndGetEntryPoint(
GetModuleHandleW(nullptr),
false,
runtimeconfig_path,
module_path,
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
L"Main",
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
&entrypoint_vfn);
if (FAILED(result))
return result;
typedef int (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
logging::I("Running Dalamud Injector...");
const auto ret = entrypoint_fn(argc, argv);
logging::I("Done!");
return ret;
}

View file

@ -1 +0,0 @@
#pragma once

View file

@ -1 +0,0 @@
MAINICON ICON "dalamud.ico"

View file

@ -1,11 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>10.0</LangVersion>
</PropertyGroup>
<PropertyGroup Label="Feature">
@ -17,12 +13,13 @@
</PropertyGroup>
<PropertyGroup Label="Output">
<OutputType>Library</OutputType>
<OutputType>Exe</OutputType>
<OutputPath>..\bin\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ApplicationIcon>dalamud.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Label="Documentation">
@ -45,10 +42,6 @@
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap>
</PropertyGroup>
<PropertyGroup Label="Warnings">
<NoWarn>IDE0003;IDE0044;IDE1006;CS1591;CS1701;CS1702</NoWarn>
@ -60,17 +53,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Iced" Version="1.17.0" />
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="PeNet" Version="2.6.4" />
<PackageReference Include="Reloaded.Memory" Version="7.0.0" />
<PackageReference Include="Reloaded.Memory.Buffers" Version="2.0.0" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PackageReference Include="Iced" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Microsoft.Windows.CsWin32" />
<PackageReference Include="PeNet" />
<PackageReference Include="Reloaded.Memory" />
<PackageReference Include="Reloaded.Memory.Buffers" />
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.Async" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -13,8 +13,8 @@ using Reloaded.Memory.Buffers;
using Reloaded.Memory.Sources;
using Reloaded.Memory.Utilities;
using Serilog;
using Windows.Win32.Foundation;
using static Dalamud.Injector.NativeFunctions;
using static Iced.Intel.AssemblerRegisters;
namespace Dalamud.Injector
@ -88,7 +88,7 @@ namespace Dalamud.Injector
if (lpParameter == 0)
throw new Exception("Unable to allocate LoadLibraryW parameter");
this.CallRemoteFunction(this.loadLibraryShellPtr, lpParameter, out var err);
var err = this.CallRemoteFunction(this.loadLibraryShellPtr, lpParameter);
this.extMemory.Read<IntPtr>(this.loadLibraryRetPtr, out address);
if (address == IntPtr.Zero)
throw new Exception($"LoadLibraryW(\"{modulePath}\") failure: {new Win32Exception((int)err).Message} ({err})");
@ -108,7 +108,7 @@ namespace Dalamud.Injector
if (lpParameter == 0)
throw new Exception("Unable to allocate GetProcAddress parameter ptr");
this.CallRemoteFunction(this.getProcAddressShellPtr, lpParameter, out var err);
var err = this.CallRemoteFunction(this.getProcAddressShellPtr, lpParameter);
this.extMemory.Read<nuint>(this.getProcAddressRetPtr, out address);
if (address == 0)
throw new Exception($"GetProcAddress(0x{module:X}, \"{functionName}\") failure: {new Win32Exception((int)err).Message} ({err})");
@ -119,27 +119,30 @@ namespace Dalamud.Injector
/// </summary>
/// <param name="methodAddress">Method address.</param>
/// <param name="parameterAddress">Parameter address.</param>
/// <param name="exitCode">Thread exit code.</param>
public void CallRemoteFunction(nuint methodAddress, nuint parameterAddress, out uint exitCode)
/// <returns>Thread exit code.</returns>
public unsafe uint CallRemoteFunction(nuint methodAddress, nuint parameterAddress)
{
// Create and initialize a thread at our address and parameter address.
var threadHandle = CreateRemoteThread(
this.targetProcess.Handle,
IntPtr.Zero,
var threadHandle = Windows.Win32.PInvoke.CreateRemoteThread(
new HANDLE(this.targetProcess.Handle.ToPointer()),
null,
UIntPtr.Zero,
methodAddress,
parameterAddress,
CreateThreadFlags.RunImmediately,
out _);
(delegate* unmanaged[Stdcall]<void*, uint>)methodAddress,
parameterAddress.ToPointer(),
0, // Run immediately
null);
if (threadHandle == IntPtr.Zero)
throw new Exception($"CreateRemoteThread failure: {Marshal.GetLastWin32Error()}");
_ = WaitForSingleObject(threadHandle, uint.MaxValue);
_ = Windows.Win32.PInvoke.WaitForSingleObject(threadHandle, uint.MaxValue);
GetExitCodeThread(threadHandle, out exitCode);
uint exitCode = 0;
if (!Windows.Win32.PInvoke.GetExitCodeThread(threadHandle, &exitCode))
throw new Exception($"GetExitCodeThread failure: {Marshal.GetLastWin32Error()}");
CloseHandle(threadHandle);
Windows.Win32.PInvoke.CloseHandle(threadHandle);
return exitCode;
}
private void SetupLoadLibrary(ProcessModule kernel32Module, ExportFunction[] kernel32Exports)

View file

@ -1,914 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Dalamud.Injector
{
/// <summary>
/// Native user32 functions.
/// </summary>
internal static partial class NativeFunctions
{
/// <summary>
/// MB_* from winuser.
/// </summary>
public enum MessageBoxType : uint
{
/// <summary>
/// The default value for any of the various subtypes.
/// </summary>
DefaultValue = 0x0,
// To indicate the buttons displayed in the message box, specify one of the following values.
/// <summary>
/// The message box contains three push buttons: Abort, Retry, and Ignore.
/// </summary>
AbortRetryIgnore = 0x2,
/// <summary>
/// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead
/// of MB_ABORTRETRYIGNORE.
/// </summary>
CancelTryContinue = 0x6,
/// <summary>
/// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends
/// a WM_HELP message to the owner.
/// </summary>
Help = 0x4000,
/// <summary>
/// The message box contains one push button: OK. This is the default.
/// </summary>
Ok = DefaultValue,
/// <summary>
/// The message box contains two push buttons: OK and Cancel.
/// </summary>
OkCancel = 0x1,
/// <summary>
/// The message box contains two push buttons: Retry and Cancel.
/// </summary>
RetryCancel = 0x5,
/// <summary>
/// The message box contains two push buttons: Yes and No.
/// </summary>
YesNo = 0x4,
/// <summary>
/// The message box contains three push buttons: Yes, No, and Cancel.
/// </summary>
YesNoCancel = 0x3,
// To display an icon in the message box, specify one of the following values.
/// <summary>
/// An exclamation-point icon appears in the message box.
/// </summary>
IconExclamation = 0x30,
/// <summary>
/// An exclamation-point icon appears in the message box.
/// </summary>
IconWarning = IconExclamation,
/// <summary>
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
/// </summary>
IconInformation = 0x40,
/// <summary>
/// An icon consisting of a lowercase letter i in a circle appears in the message box.
/// </summary>
IconAsterisk = IconInformation,
/// <summary>
/// A question-mark icon appears in the message box.
/// The question-mark message icon is no longer recommended because it does not clearly represent a specific type
/// of message and because the phrasing of a message as a question could apply to any message type. In addition,
/// users can confuse the message symbol question mark with Help information. Therefore, do not use this question
/// mark message symbol in your message boxes. The system continues to support its inclusion only for backward
/// compatibility.
/// </summary>
IconQuestion = 0x20,
/// <summary>
/// A stop-sign icon appears in the message box.
/// </summary>
IconStop = 0x10,
/// <summary>
/// A stop-sign icon appears in the message box.
/// </summary>
IconError = IconStop,
/// <summary>
/// A stop-sign icon appears in the message box.
/// </summary>
IconHand = IconStop,
// To indicate the default button, specify one of the following values.
/// <summary>
/// The first button is the default button.
/// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified.
/// </summary>
DefButton1 = DefaultValue,
/// <summary>
/// The second button is the default button.
/// </summary>
DefButton2 = 0x100,
/// <summary>
/// The third button is the default button.
/// </summary>
DefButton3 = 0x200,
/// <summary>
/// The fourth button is the default button.
/// </summary>
DefButton4 = 0x300,
// To indicate the modality of the dialog box, specify one of the following values.
/// <summary>
/// The user must respond to the message box before continuing work in the window identified by the hWnd parameter.
/// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy
/// of windows in the application, the user may be able to move to other windows within the thread. All child windows
/// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the
/// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified.
/// </summary>
ApplModal = DefaultValue,
/// <summary>
/// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style.
/// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate
/// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with
/// windows other than those associated with hWnd.
/// </summary>
SystemModal = 0x1000,
/// <summary>
/// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the
/// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle
/// available but still needs to prevent input to other windows in the calling thread without suspending other threads.
/// </summary>
TaskModal = 0x2000,
// To specify other options, use one or more of the following values.
/// <summary>
/// Same as desktop of the interactive window station. For more information, see Window Stations. If the current
/// input desktop is not the default desktop, MessageBox does not return until the user switches to the default
/// desktop.
/// </summary>
DefaultDesktopOnly = 0x20000,
/// <summary>
/// The text is right-justified.
/// </summary>
Right = 0x80000,
/// <summary>
/// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.
/// </summary>
RtlReading = 0x100000,
/// <summary>
/// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function
/// for the message box.
/// </summary>
SetForeground = 0x10000,
/// <summary>
/// The message box is created with the WS_EX_TOPMOST window style.
/// </summary>
Topmost = 0x40000,
/// <summary>
/// The caller is a service notifying the user of an event. The function displays a message box on the current active
/// desktop, even if there is no user logged on to the computer.
/// </summary>
ServiceNotification = 0x200000,
}
/// <summary>
/// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message,
/// such as status or error information. The message box returns an integer value that indicates which button the user
/// clicked.
/// </summary>
/// <param name="hWnd">
/// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no
/// owner window.
/// </param>
/// <param name="text">
/// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage
/// return and/or linefeed character between each line.
/// </param>
/// <param name="caption">
/// The dialog box title. If this parameter is NULL, the default title is Error.</param>
/// <param name="type">
/// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups
/// of flags.
/// </param>
/// <returns>
/// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or
/// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an
/// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK.
/// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function
/// succeeds, the return value is one of the ID* enum values.
/// </returns>
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type);
}
/// <summary>
/// Native kernel32 functions.
/// </summary>
internal static partial class NativeFunctions
{
/// <summary>
/// MEM_* from memoryapi.
/// </summary>
[Flags]
public enum AllocationType
{
/// <summary>
/// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce
/// placeholders, lpAddress and dwSize must exactly match those of the placeholder.
/// </summary>
CoalescePlaceholders = 0x1,
/// <summary>
/// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using
/// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify
/// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER.
/// </summary>
PreservePlaceholder = 0x2,
/// <summary>
/// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved
/// memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents
/// will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed.
/// To reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Attempting to commit
/// a specific address range by specifying MEM_COMMIT without MEM_RESERVE and a non-NULL lpAddress fails unless the
/// entire range has already been reserved. The resulting error code is ERROR_INVALID_ADDRESS. An attempt to commit
/// a page that is already committed does not cause the function to fail. This means that you can commit pages without
/// first determining the current commitment state of each page. If lpAddress specifies an address within an enclave,
/// flAllocationType must be MEM_COMMIT.
/// </summary>
Commit = 0x1000,
/// <summary>
/// Reserves a range of the process's virtual address space without allocating any actual physical storage in memory
/// or in the paging file on disk. You commit reserved pages by calling VirtualAllocEx again with MEM_COMMIT. To
/// reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Other memory allocation
/// functions, such as malloc and LocalAlloc, cannot use reserved memory until it has been released.
/// </summary>
Reserve = 0x2000,
/// <summary>
/// Decommits the specified region of committed pages. After the operation, the pages are in the reserved state.
/// The function does not fail if you attempt to decommit an uncommitted page. This means that you can decommit
/// a range of pages without first determining the current commitment state. The MEM_DECOMMIT value is not supported
/// when the lpAddress parameter provides the base address for an enclave.
/// </summary>
Decommit = 0x4000,
/// <summary>
/// Releases the specified region of pages, or placeholder (for a placeholder, the address space is released and
/// available for other allocations). After this operation, the pages are in the free state. If you specify this
/// value, dwSize must be 0 (zero), and lpAddress must point to the base address returned by the VirtualAlloc function
/// when the region is reserved. The function fails if either of these conditions is not met. If any pages in the
/// region are committed currently, the function first decommits, and then releases them. The function does not
/// fail if you attempt to release pages that are in different states, some reserved and some committed. This means
/// that you can release a range of pages without first determining the current commitment state.
/// </summary>
Release = 0x8000,
/// <summary>
/// Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages
/// should not be read from or written to the paging file. However, the memory block will be used again later, so
/// it should not be decommitted. This value cannot be used with any other value. Using this value does not guarantee
/// that the range operated on with MEM_RESET will contain zeros. If you want the range to contain zeros, decommit
/// the memory and then recommit it. When you use MEM_RESET, the VirtualAllocEx function ignores the value of fProtect.
/// However, you must still set fProtect to a valid protection value, such as PAGE_NOACCESS. VirtualAllocEx returns
/// an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable
/// if it is mapped to a paging file.
/// </summary>
Reset = 0x80000,
/// <summary>
/// MEM_RESET_UNDO should only be called on an address range to which MEM_RESET was successfully applied earlier.
/// It indicates that the data in the specified memory range specified by lpAddress and dwSize is of interest to
/// the caller and attempts to reverse the effects of MEM_RESET. If the function succeeds, that means all data in
/// the specified address range is intact. If the function fails, at least some of the data in the address range
/// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on
/// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the
/// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid
/// protection value, such as PAGE_NOACCESS.
/// </summary>
ResetUndo = 0x1000000,
/// <summary>
/// Reserves an address range that can be used to map Address Windowing Extensions (AWE) pages. This value must
/// be used with MEM_RESERVE and no other values.
/// </summary>
Physical = 0x400000,
/// <summary>
/// Allocates memory at the highest possible address. This can be slower than regular allocations, especially when
/// there are many allocations.
/// </summary>
TopDown = 0x100000,
/// <summary>
/// Causes the system to track pages that are written to in the allocated region. If you specify this value, you
/// must also specify MEM_RESERVE. To retrieve the addresses of the pages that have been written to since the region
/// was allocated or the write-tracking state was reset, call the GetWriteWatch function. To reset the write-tracking
/// state, call GetWriteWatch or ResetWriteWatch. The write-tracking feature remains enabled for the memory region
/// until the region is freed.
/// </summary>
WriteWatch = 0x200000,
/// <summary>
/// Allocates memory using large page support. The size and alignment must be a multiple of the large-page minimum.
/// To obtain this value, use the GetLargePageMinimum function. If you specify this value, you must also specify
/// MEM_RESERVE and MEM_COMMIT.
/// </summary>
LargePages = 0x20000000,
}
/// <summary>
/// Unprefixed flags from CreateRemoteThread.
/// </summary>
[Flags]
public enum CreateThreadFlags
{
/// <summary>
/// The thread runs immediately after creation.
/// </summary>
RunImmediately = 0x0,
/// <summary>
/// The thread is created in a suspended state, and does not run until the ResumeThread function is called.
/// </summary>
CreateSuspended = 0x4,
/// <summary>
/// The dwStackSize parameter specifies the initial reserve size of the stack. If this flag is not specified, dwStackSize specifies the commit size.
/// </summary>
StackSizeParamIsReservation = 0x10000,
}
/// <summary>
/// DUPLICATE_* values for DuplicateHandle's dwDesiredAccess.
/// </summary>
[Flags]
public enum DuplicateOptions : uint
{
/// <summary>
/// Closes the source handle. This occurs regardless of any error status returned.
/// </summary>
CloseSource = 0x00000001,
/// <summary>
/// Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.
/// </summary>
SameAccess = 0x00000002,
}
/// <summary>
/// PAGE_* from memoryapi.
/// </summary>
[Flags]
public enum MemoryProtection
{
/// <summary>
/// Enables execute access to the committed region of pages. An attempt to write to the committed region results
/// in an access violation. This flag is not supported by the CreateFileMapping function.
/// </summary>
Execute = 0x10,
/// <summary>
/// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region
/// results in an access violation.
/// </summary>
ExecuteRead = 0x20,
/// <summary>
/// Enables execute, read-only, or read/write access to the committed region of pages.
/// </summary>
ExecuteReadWrite = 0x40,
/// <summary>
/// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to
/// write to a committed copy-on-write page results in a private copy of the page being made for the process. The
/// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not
/// supported by the VirtualAlloc or VirtualAllocEx functions.
/// </summary>
ExecuteWriteCopy = 0x80,
/// <summary>
/// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed
/// region results in an access violation. This flag is not supported by the CreateFileMapping function.
/// </summary>
NoAccess = 0x01,
/// <summary>
/// Enables read-only access to the committed region of pages. An attempt to write to the committed region results
/// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed
/// region results in an access violation.
/// </summary>
ReadOnly = 0x02,
/// <summary>
/// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled,
/// attempting to execute code in the committed region results in an access violation.
/// </summary>
ReadWrite = 0x04,
/// <summary>
/// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to
/// a committed copy-on-write page results in a private copy of the page being made for the process. The private
/// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is
/// enabled, attempting to execute code in the committed region results in an access violation. This flag is not
/// supported by the VirtualAlloc or VirtualAllocEx functions.
/// </summary>
WriteCopy = 0x08,
/// <summary>
/// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like
/// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations
/// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable
/// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect
/// or CreateFileMapping functions.
/// </summary>
TargetsInvalid = 0x40000000,
/// <summary>
/// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect.
/// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information
/// will be maintained while the page protection changes. This flag is only valid when the protection changes to
/// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY.
/// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call
/// targets for CFG.
/// </summary>
TargetsNoUpdate = TargetsInvalid,
/// <summary>
/// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a
/// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time
/// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn
/// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a
/// system service, the service typically returns a failure status indicator. This value cannot be used with
/// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function.
/// </summary>
Guard = 0x100,
/// <summary>
/// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required
/// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an
/// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS,
/// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the
/// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared
/// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function.
/// </summary>
NoCache = 0x200,
/// <summary>
/// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required
/// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an
/// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS,
/// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory
/// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access
/// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function.
/// </summary>
WriteCombine = 0x400,
}
/// <summary>
/// PROCESS_* from processthreadsapi.
/// </summary>
[Flags]
public enum ProcessAccessFlags : uint
{
/// <summary>
/// All possible access rights for a process object.
/// </summary>
AllAccess = 0x001F0FFF,
/// <summary>
/// Required to create a process.
/// </summary>
CreateProcess = 0x0080,
/// <summary>
/// Required to create a thread.
/// </summary>
CreateThread = 0x0002,
/// <summary>
/// Required to duplicate a handle using DuplicateHandle.
/// </summary>
DupHandle = 0x0040,
/// <summary>
/// Required to retrieve certain information about a process, such as its token, exit code,
/// and priority class (see OpenProcessToken).
/// </summary>
QueryInformation = 0x0400,
/// <summary>
/// Required to retrieve certain information about a process(see GetExitCodeProcess, GetPriorityClass, IsProcessInJob,
/// QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted
/// PROCESS_QUERY_LIMITED_INFORMATION.
/// </summary>
QueryLimitedInformation = 0x1000,
/// <summary>
/// Required to set certain information about a process, such as its priority class (see SetPriorityClass).
/// </summary>
SetInformation = 0x0200,
/// <summary>
/// Required to set memory limits using SetProcessWorkingSetSize.
/// </summary>
SetQuote = 0x0100,
/// <summary>
/// Required to suspend or resume a process.
/// </summary>
SuspendResume = 0x0800,
/// <summary>
/// Required to terminate a process using TerminateProcess.
/// </summary>
Terminate = 0x0001,
/// <summary>
/// Required to perform an operation on the address space of a process(see VirtualProtectEx and WriteProcessMemory).
/// </summary>
VmOperation = 0x0008,
/// <summary>
/// Required to read memory in a process using ReadProcessMemory.
/// </summary>
VmRead = 0x0010,
/// <summary>
/// Required to write to memory in a process using WriteProcessMemory.
/// </summary>
VmWrite = 0x0020,
/// <summary>
/// Required to wait for the process to terminate using the wait functions.
/// </summary>
Synchronize = 0x00100000,
}
/// <summary>
/// WAIT_* from synchapi.
/// </summary>
public enum WaitResult
{
/// <summary>
/// The specified object is a mutex object that was not released by the thread that owned the mutex object
/// before the owning thread terminated.Ownership of the mutex object is granted to the calling thread and
/// the mutex state is set to nonsignaled. If the mutex was protecting persistent state information, you
/// should check it for consistency.
/// </summary>
Abandoned = 0x80,
/// <summary>
/// The state of the specified object is signaled.
/// </summary>
Object0 = 0x0,
/// <summary>
/// The time-out interval elapsed, and the object's state is nonsignaled.
/// </summary>
Timeout = 0x102,
/// <summary>
/// The function has failed. To get extended error information, call GetLastError.
/// </summary>
WAIT_FAILED = 0xFFFFFFF,
}
/// <summary>
/// Closes an open object handle.
/// </summary>
/// <param name="hObject">
/// A valid handle to an open object.
/// </param>
/// <returns>
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error
/// information, call GetLastError. If the application is running under a debugger, the function will throw an exception if it receives
/// either a handle value that is not valid or a pseudo-handle value. This can happen if you close a handle twice, or if you call
/// CloseHandle on a handle returned by the FindFirstFile function instead of calling the FindClose function.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
/// <summary>
/// Creates a thread that runs in the virtual address space of another process. Use the CreateRemoteThreadEx function
/// to create a thread that runs in the virtual address space of another process and optionally specify extended attributes.
/// </summary>
/// <param name="hProcess">
/// A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD,
/// PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without
/// these rights on certain platforms. For more information, see Process Security and Access Rights.
/// </param>
/// <param name="lpThreadAttributes">
/// A pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new thread and determines whether
/// child processes can inherit the returned handle. If lpThreadAttributes is NULL, the thread gets a default security descriptor
/// and the handle cannot be inherited. The access control lists (ACL) in the default security descriptor for a thread come from
/// the primary token of the creator.
/// </param>
/// <param name="dwStackSize">
/// The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is 0 (zero), the
/// new thread uses the default size for the executable. For more information, see Thread Stack Size.
/// </param>
/// <param name="lpStartAddress">
/// A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the
/// starting address of the thread in the remote process. The function must exist in the remote process. For more information,
/// see ThreadProc.
/// </param>
/// <param name="lpParameter">
/// A pointer to a variable to be passed to the thread function.
/// </param>
/// <param name="dwCreationFlags">
/// The flags that control the creation of the thread.
/// </param>
/// <param name="lpThreadId">
/// A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
/// </param>
/// <returns>
/// If the function succeeds, the return value is a handle to the new thread. If the function fails, the return value is
/// NULL.To get extended error information, call GetLastError. Note that CreateRemoteThread may succeed even if lpStartAddress
/// points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and
/// the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process.
/// This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to
/// invalid or missing dynamic-link libraries (DLL).
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
UIntPtr dwStackSize,
nuint lpStartAddress,
nuint lpParameter,
CreateThreadFlags dwCreationFlags,
out uint lpThreadId);
/// <summary>
/// Retrieves the termination status of the specified thread.
/// </summary>
/// <param name="hThread">
/// A handle to the thread. The handle must have the THREAD_QUERY_INFORMATION or THREAD_QUERY_LIMITED_INFORMATION
/// access right.For more information, see Thread Security and Access Rights.
/// </param>
/// <param name="lpExitCode">
/// A pointer to a variable to receive the thread termination status.
/// </param>
/// <returns>
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get
/// extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
/// <summary>
/// Opens an existing local process object.
/// </summary>
/// <param name="dwDesiredAccess">
/// The access to the process object. This access right is checked against the security descriptor for the process. This parameter can be one or
/// more of the process access rights. If the caller has enabled the SeDebugPrivilege privilege, the requested access is granted regardless of the
/// contents of the security descriptor.
/// </param>
/// <param name="bInheritHandle">
/// If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle.
/// </param>
/// <param name="dwProcessId">
/// The identifier of the local process to be opened. If the specified process is the System Idle Process(0x00000000), the function fails and the
/// last error code is ERROR_INVALID_PARAMETER.If the specified process is the System process or one of the Client Server Run-Time Subsystem(CSRSS)
/// processes, this function fails and the last error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from
/// opening them. If you are using GetCurrentProcessId as an argument to this function, consider using GetCurrentProcess instead of OpenProcess, for
/// improved performance.
/// </param>
/// <returns>
/// If the function succeeds, the return value is an open handle to the specified process.
/// If the function fails, the return value is NULL.To get extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(
ProcessAccessFlags dwDesiredAccess,
bool bInheritHandle,
int dwProcessId);
/// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex.
/// Reserves, commits, or changes the state of a region of memory within the virtual address space of a specified process.
/// The function initializes the memory it allocates to zero. To specify the NUMA node for the physical memory, see
/// VirtualAllocExNuma.
/// </summary>
/// <param name="hProcess">
/// The handle to a process. The function allocates memory within the virtual address space of this process. The handle
/// must have the PROCESS_VM_OPERATION access right. For more information, see Process Security and Access Rights.
/// </param>
/// <param name="lpAddress">
/// The pointer that specifies a desired starting address for the region of pages that you want to allocate. If you
/// are reserving memory, the function rounds this address down to the nearest multiple of the allocation granularity.
/// If you are committing memory that is already reserved, the function rounds this address down to the nearest page
/// boundary. To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo
/// function. If lpAddress is NULL, the function determines where to allocate the region. If this address is within
/// an enclave that you have not initialized by calling InitializeEnclave, VirtualAllocEx allocates a page of zeros
/// for the enclave at that address. The page must be previously uncommitted, and will not be measured with the EEXTEND
/// instruction of the Intel Software Guard Extensions programming model. If the address in within an enclave that you
/// initialized, then the allocation operation fails with the ERROR_INVALID_ADDRESS error.
/// </param>
/// <param name="dwSize">
/// The size of the region of memory to allocate, in bytes. If lpAddress is NULL, the function rounds dwSize up to the
/// next page boundary. If lpAddress is not NULL, the function allocates all pages that contain one or more bytes in
/// the range from lpAddress to lpAddress+dwSize. This means, for example, that a 2-byte range that straddles a page
/// boundary causes the function to allocate both pages.
/// </param>
/// <param name="flAllocationType">
/// The type of memory allocation. This parameter must contain one of the MEM_* enum values.
/// </param>
/// <param name="flProtect">
/// The memory protection for the region of pages to be allocated. If the pages are being committed, you can specify
/// any one of the memory protection constants.
/// </param>
/// <returns>
/// If the function succeeds, the return value is the base address of the allocated region of pages. If the function
/// fails, the return value is NULL.To get extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
int dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
/// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfreeex.
/// Releases, decommits, or releases and decommits a region of memory within the virtual address space of a specified
/// process.
/// </summary>
/// <param name="hProcess">
/// A handle to a process. The function frees memory within the virtual address space of the process. The handle must
/// have the PROCESS_VM_OPERATION access right.For more information, see Process Security and Access Rights.
/// </param>
/// <param name="lpAddress">
/// A pointer to the starting address of the region of memory to be freed. If the dwFreeType parameter is MEM_RELEASE,
/// lpAddress must be the base address returned by the VirtualAllocEx function when the region is reserved.
/// </param>
/// <param name="dwSize">
/// The size of the region of memory to free, in bytes. If the dwFreeType parameter is MEM_RELEASE, dwSize must be 0
/// (zero). The function frees the entire region that is reserved in the initial allocation call to VirtualAllocEx.
/// If dwFreeType is MEM_DECOMMIT, the function decommits all memory pages that contain one or more bytes in the range
/// from the lpAddress parameter to (lpAddress+dwSize). This means, for example, that a 2-byte region of memory that
/// straddles a page boundary causes both pages to be decommitted. If lpAddress is the base address returned by
/// VirtualAllocEx and dwSize is 0 (zero), the function decommits the entire region that is allocated by VirtualAllocEx.
/// After that, the entire region is in the reserved state.
/// </param>
/// <param name="dwFreeType">
/// The type of free operation. This parameter must be one of the MEM_* enum values.
/// </param>
/// <returns>
/// If the function succeeds, the return value is a nonzero value. If the function fails, the return value is 0 (zero).
/// To get extended error information, call GetLastError.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern bool VirtualFreeEx(
IntPtr hProcess,
IntPtr lpAddress,
int dwSize,
AllocationType dwFreeType);
/// <summary>
/// Waits until the specified object is in the signaled state or the time-out interval elapses. To enter an alertable wait
/// state, use the WaitForSingleObjectEx function.To wait for multiple objects, use WaitForMultipleObjects.
/// </summary>
/// <param name="hHandle">
/// A handle to the object. For a list of the object types whose handles can be specified, see the following Remarks section.
/// If this handle is closed while the wait is still pending, the function's behavior is undefined. The handle must have the
/// SYNCHRONIZE access right. For more information, see Standard Access Rights.
/// </param>
/// <param name="dwMilliseconds">
/// The time-out interval, in milliseconds. If a nonzero value is specified, the function waits until the object is signaled
/// or the interval elapses. If dwMilliseconds is zero, the function does not enter a wait state if the object is not signaled;
/// it always returns immediately. If dwMilliseconds is INFINITE, the function will return only when the object is signaled.
/// </param>
/// <returns>
/// If the function succeeds, the return value indicates the event that caused the function to return.
/// It can be one of the WaitResult values.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
/// <summary>
/// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or
/// the operation fails.
/// </summary>
/// <param name="hProcess">
/// A handle to the process memory to be modified. The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access
/// to the process.
/// </param>
/// <param name="lpBaseAddress">
/// A pointer to the base address in the specified process to which data is written. Before data transfer occurs, the
/// system verifies that all data in the base address and memory of the specified size is accessible for write access,
/// and if it is not accessible, the function fails.
/// </param>
/// <param name="lpBuffer">
/// A pointer to the buffer that contains data to be written in the address space of the specified process.
/// </param>
/// <param name="dwSize">
/// The number of bytes to be written to the specified process.
/// </param>
/// <param name="lpNumberOfBytesWritten">
/// A pointer to a variable that receives the number of bytes transferred into the specified process. This parameter
/// is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored.
/// </param>
/// <returns>
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get
/// extended error information, call GetLastError.The function fails if the requested write operation crosses into an
/// area of the process that is inaccessible.
/// </returns>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
int dwSize,
out IntPtr lpNumberOfBytesWritten);
/// <summary>
/// Duplicates an object handle.
/// </summary>
/// <param name="hSourceProcessHandle">
/// A handle to the process with the handle to be duplicated.
///
/// The handle must have the PROCESS_DUP_HANDLE access right.
/// </param>
/// <param name="hSourceHandle">
/// The handle to be duplicated. This is an open object handle that is valid in the context of the source process.
/// For a list of objects whose handles can be duplicated, see the following Remarks section.
/// </param>
/// <param name="hTargetProcessHandle">
/// A handle to the process that is to receive the duplicated handle.
///
/// The handle must have the PROCESS_DUP_HANDLE access right.
/// </param>
/// <param name="lpTargetHandle">
/// A pointer to a variable that receives the duplicate handle. This handle value is valid in the context of the target process.
///
/// If hSourceHandle is a pseudo handle returned by GetCurrentProcess or GetCurrentThread, DuplicateHandle converts it to a real handle to a process or thread, respectively.
///
/// If lpTargetHandle is NULL, the function duplicates the handle, but does not return the duplicate handle value to the caller. This behavior exists only for backward compatibility with previous versions of this function. You should not use this feature, as you will lose system resources until the target process terminates.
///
/// This parameter is ignored if hTargetProcessHandle is NULL.
/// </param>
/// <param name="dwDesiredAccess">
/// The access requested for the new handle. For the flags that can be specified for each object type, see the following Remarks section.
///
/// This parameter is ignored if the dwOptions parameter specifies the DUPLICATE_SAME_ACCESS flag. Otherwise, the flags that can be specified depend on the type of object whose handle is to be duplicated.
///
/// This parameter is ignored if hTargetProcessHandle is NULL.
/// </param>
/// <param name="bInheritHandle">
/// A variable that indicates whether the handle is inheritable. If TRUE, the duplicate handle can be inherited by new processes created by the target process. If FALSE, the new handle cannot be inherited.
///
/// This parameter is ignored if hTargetProcessHandle is NULL.
/// </param>
/// <param name="dwOptions">
/// Optional actions.
/// </param>
/// <returns>
/// If the function succeeds, the return value is nonzero.
///
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.
/// </returns>
/// <remarks>
/// See https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle.
/// </remarks>
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DuplicateHandle(
IntPtr hSourceProcessHandle,
IntPtr hSourceHandle,
IntPtr hTargetProcessHandle,
out IntPtr lpTargetHandle,
uint dwDesiredAccess,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
DuplicateOptions dwOptions);
}
}

View file

@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": false
}

View file

@ -0,0 +1,8 @@
CreateRemoteThread
WaitForSingleObject
GetExitCodeThread
DuplicateHandle
MessageBox
GetModuleHandle
GetProcAddress

View file

@ -11,47 +11,34 @@ using System.Text.RegularExpressions;
using Dalamud.Common;
using Dalamud.Common.Game;
using Dalamud.Common.Util;
using Newtonsoft.Json;
using Reloaded.Memory.Buffers;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using static Dalamud.Injector.NativeFunctions;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Dalamud.Injector
{
/// <summary>
/// Entrypoint to the program.
/// </summary>
public sealed class EntryPoint
public sealed class Program
{
/// <summary>
/// A delegate used during initialization of the CLR from Dalamud.Injector.Boot.
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">char** string arguments.</param>
/// <returns>Return value (HRESULT).</returns>
public delegate int MainDelegate(int argc, IntPtr argvPtr);
/// <summary>
/// Start the Dalamud injector.
/// </summary>
/// <param name="argc">Count of arguments.</param>
/// <param name="argvPtr">byte** string arguments.</param>
/// <param name="argsArray">Command line arguments.</param>
/// <returns>Return value (HRESULT).</returns>
public static int Main(int argc, IntPtr argvPtr)
public static int Main(string[] argsArray)
{
try
{
List<string> args = new(argc);
unsafe
{
var argv = (IntPtr*)argvPtr;
for (var i = 0; i < argc; i++)
args.Add(Marshal.PtrToStringUni(argv[i]));
}
// API14 TODO: Refactor
var args = argsArray.ToList();
args.Insert(0, Assembly.GetExecutingAssembly().Location);
Init(args);
args.Remove("-v"); // Remove "verbose" flag
@ -89,11 +76,13 @@ namespace Dalamud.Injector
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
// Remove already handled arguments
args.Remove("--debug-directx");
args.Remove("--console");
args.Remove("--msgbox1");
args.Remove("--msgbox2");
args.Remove("--msgbox3");
args.Remove("--etw");
args.Remove("--no-legacy-corrupted-state-exceptions");
args.Remove("--veh");
args.Remove("--veh-full");
args.Remove("--no-plugin");
@ -262,6 +251,35 @@ namespace Dalamud.Injector
}
}
private static OSPlatform DetectPlatformHeuristic()
{
var ntdll = Windows.Win32.PInvoke.GetModuleHandle("ntdll.dll");
var wineServerCallPtr = Windows.Win32.PInvoke.GetProcAddress(ntdll, "wine_server_call");
var wineGetHostVersionPtr = Windows.Win32.PInvoke.GetProcAddress(ntdll, "wine_get_host_version");
var winePlatform = GetWinePlatform(wineGetHostVersionPtr);
var isWine = wineServerCallPtr != nint.Zero;
static unsafe string? GetWinePlatform(nint wineGetHostVersionPtr)
{
if (wineGetHostVersionPtr == nint.Zero) return null;
var methodDelegate = (delegate* unmanaged[Cdecl]<out char*, out char*, void>)wineGetHostVersionPtr;
methodDelegate(out var platformPtr, out var _);
if (platformPtr == null) return null;
return Marshal.PtrToStringAnsi((nint)platformPtr);
}
if (!isWine)
return OSPlatform.Windows;
if (winePlatform == "Darwin")
return OSPlatform.OSX;
return OSPlatform.Linux;
}
private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args)
{
int len;
@ -273,13 +291,19 @@ namespace Dalamud.Injector
var configurationPath = startInfo.ConfigurationPath;
var pluginDirectory = startInfo.PluginDirectory;
var assetDirectory = startInfo.AssetDirectory;
var tempDirectory = startInfo.TempDirectory;
var delayInitializeMs = startInfo.DelayInitializeMs;
var logName = startInfo.LogName;
var logPath = startInfo.LogPath;
var languageStr = startInfo.Language.ToString().ToLowerInvariant();
var platformStr = startInfo.Platform.ToString().ToLowerInvariant();
var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant();
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
// env vars are brought in prior to launch args, since args can override them.
if (EnvironmentUtils.TryGetEnvironmentVariable("XL_PLATFORM", out var xlPlatformEnv))
platformStr = xlPlatformEnv.ToLowerInvariant();
for (var i = 2; i < args.Count; i++)
{
if (args[i].StartsWith(key = "--dalamud-working-directory="))
@ -298,6 +322,10 @@ namespace Dalamud.Injector
{
assetDirectory = args[i][key.Length..];
}
else if (args[i].StartsWith(key = "--dalamud-temp-directory="))
{
tempDirectory = args[i][key.Length..];
}
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
{
delayInitializeMs = int.Parse(args[i][key.Length..]);
@ -306,6 +334,10 @@ namespace Dalamud.Injector
{
languageStr = args[i][key.Length..].ToLowerInvariant();
}
else if (args[i].StartsWith(key = "--dalamud-platform="))
{
platformStr = args[i][key.Length..].ToLowerInvariant();
}
else if (args[i].StartsWith(key = "--dalamud-tspack-b64="))
{
troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..]));
@ -377,11 +409,38 @@ namespace Dalamud.Injector
throw new CommandLineException($"\"{languageStr}\" is not a valid supported language.");
}
OSPlatform platform;
// covers both win32 and Windows
if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len])
{
platform = OSPlatform.Windows;
}
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "linux").Length))] == key[0..len])
{
platform = OSPlatform.Linux;
}
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "macos").Length))] == key[0..len])
{
platform = OSPlatform.OSX;
}
else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "osx").Length))] == key[0..len])
{
platform = OSPlatform.OSX;
}
else
{
platform = DetectPlatformHeuristic();
Log.Warning("Heuristically determined host system platform as {platform}", platform);
}
startInfo.WorkingDirectory = workingDirectory;
startInfo.ConfigurationPath = configurationPath;
startInfo.PluginDirectory = pluginDirectory;
startInfo.AssetDirectory = assetDirectory;
startInfo.TempDirectory = tempDirectory;
startInfo.Language = clientLanguage;
startInfo.Platform = platform;
startInfo.DelayInitializeMs = delayInitializeMs;
startInfo.GameVersion = null;
startInfo.TroubleshootingPackData = troubleshootingData;
@ -397,6 +456,7 @@ namespace Dalamud.Injector
startInfo.LogName ??= string.Empty;
// Set boot defaults
startInfo.BootDebugDirectX = args.Contains("--debug-directx");
startInfo.BootShowConsole = args.Contains("--console");
startInfo.BootEnableEtw = args.Contains("--etw");
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
@ -409,6 +469,7 @@ namespace Dalamud.Injector
"backup_userdata_save",
"prevent_icmphandle_crashes",
"symbol_load_patches",
"disable_game_debugging_protection",
};
startInfo.BootDotnetOpenProcessHookMode = 0;
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
@ -463,13 +524,14 @@ namespace Dalamud.Injector
}
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]");
Console.WriteLine(" [--dalamud-plugin-directory=path]");
Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-platform=win32|linux|macOS]");
Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]");
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
Console.WriteLine("Verbose logging:\t[-v]");
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
Console.WriteLine("Enable ETW:\t[--etw]");
Console.WriteLine("Disable legacy corrupted state exceptions:\t[--no-legacy-corrupted-state-exceptions]");
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--unhandled-exception=default|stalldebug|none]");
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
@ -550,10 +612,13 @@ namespace Dalamud.Injector
if (warnManualInjection)
{
var result = MessageBoxW(IntPtr.Zero, $"Take care: you are manually injecting Dalamud into FFXIV({string.Join(", ", processes.Select(x => $"{x.Id}"))}).\n\nIf you are doing this to use plugins before they are officially whitelisted on patch days, things may go wrong and you may get into trouble.\nWe discourage you from doing this and you won't be warned again in-game.", "Dalamud", MessageBoxType.IconWarning | MessageBoxType.OkCancel);
var result = Windows.Win32.PInvoke.MessageBox(
HWND.Null,
$"Take care: you are manually injecting Dalamud into FFXIV({string.Join(", ", processes.Select(x => $"{x.Id}"))}).\n\nIf you are doing this to use plugins before they are officially whitelisted on patch days, things may go wrong and you may get into trouble.\nWe discourage you from doing this and you won't be warned again in-game.",
"Dalamud",
MESSAGEBOX_STYLE.MB_ICONWARNING | MESSAGEBOX_STYLE.MB_OKCANCEL);
// IDCANCEL
if (result == 2)
if (result == MESSAGEBOX_RESULT.IDCANCEL)
{
Log.Information("User cancelled injection");
return -2;
@ -729,15 +794,42 @@ namespace Dalamud.Injector
{
try
{
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json");
gamePath = Path.Combine(JsonSerializer.CreateDefault().Deserialize<Dictionary<string, string>>(new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], "game", "ffxiv_dx11.exe");
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath);
if (dalamudStartInfo.Platform == OSPlatform.Windows)
{
var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher");
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json");
gamePath = Path.Combine(
JsonSerializer.CreateDefault()
.Deserialize<Dictionary<string, string>>(
new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"],
"game",
"ffxiv_dx11.exe");
Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath);
}
else if (dalamudStartInfo.Platform == OSPlatform.Linux)
{
var homeDir = $"Z:\\home\\{Environment.UserName}";
var xivlauncherDir = Path.Combine(homeDir, ".xlcore");
var launcherConfigPath = Path.Combine(xivlauncherDir, "launcher.ini");
var config = File.ReadAllLines(launcherConfigPath)
.Where(line => line.Contains('='))
.ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]);
gamePath = Path.Combine("Z:" + config["GamePath"].Replace('/', '\\'), "game", "ffxiv_dx11.exe");
Log.Information("Using game installation path configuration from from XIVLauncher Core: {0}", gamePath);
}
else
{
var homeDir = $"Z:\\Users\\{Environment.UserName}";
var xomlauncherDir = Path.Combine(homeDir, "Library", "Application Support", "XIV on Mac");
// we could try to parse the binary plist file here if we really wanted to...
gamePath = Path.Combine(xomlauncherDir, "ffxiv", "game", "ffxiv_dx11.exe");
Log.Information("Using default game installation path from XOM: {0}", gamePath);
}
}
catch (Exception)
{
Log.Error("Failed to read launcherConfigV3.json to get the set-up game path, please specify one using -g");
Log.Error("Failed to read launcher config to get the set-up game path, please specify one using -g");
return -1;
}
@ -792,20 +884,6 @@ namespace Dalamud.Injector
if (encryptArguments)
{
var rawTickCount = (uint)Environment.TickCount;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
[System.Runtime.InteropServices.DllImport("c")]
#pragma warning disable SA1300
static extern ulong clock_gettime_nsec_np(int clockId);
#pragma warning restore SA1300
const int CLOCK_MONOTONIC_RAW = 4;
var rawTickCountFixed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000;
Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed);
rawTickCount = (uint)rawTickCountFixed;
}
var ticks = rawTickCount & 0xFFFF_FFFFu;
var key = ticks & 0xFFFF_0000u;
gameArguments.Insert(0, $"T={ticks}");
@ -850,30 +928,48 @@ namespace Dalamud.Injector
Inject(process, startInfo, false);
}
var processHandleForOwner = IntPtr.Zero;
var processHandleForOwner = HANDLE.Null;
if (handleOwner != IntPtr.Zero)
{
if (!DuplicateHandle(Process.GetCurrentProcess().Handle, process.Handle, handleOwner, out processHandleForOwner, 0, false, DuplicateOptions.SameAccess))
unsafe
{
Log.Warning("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
if (!Windows.Win32.PInvoke.DuplicateHandle(
new HANDLE(Process.GetCurrentProcess().Handle.ToPointer()),
new HANDLE(process.Handle.ToPointer()),
new HANDLE(handleOwner),
&processHandleForOwner,
0,
false,
DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS))
{
Log.Warning("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
}
}
}
Console.WriteLine($"{{\"pid\": {process.Id}, \"handle\": {processHandleForOwner}}}");
Console.WriteLine($"{{\"pid\": {process.Id}, \"handle\": {(IntPtr)processHandleForOwner}}}");
Log.CloseAndFlush();
return 0;
}
private static Process GetInheritableCurrentProcessHandle()
private static unsafe Process GetInheritableCurrentProcessHandle()
{
if (!DuplicateHandle(Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, out var inheritableCurrentProcessHandle, 0, true, DuplicateOptions.SameAccess))
var currentProcessHandle = new HANDLE(Process.GetCurrentProcess().Handle.ToPointer());
var inheritableHandle = HANDLE.Null;
if (!Windows.Win32.PInvoke.DuplicateHandle(
currentProcessHandle,
currentProcessHandle,
currentProcessHandle,
&inheritableHandle,
0,
true,
DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS))
{
Log.Error("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
return null;
throw new Win32Exception("Failed to call DuplicateHandle");
}
return new ExistingProcess(inheritableCurrentProcessHandle);
return new ExistingProcess(inheritableHandle);
}
private static int ProcessLaunchTestCommand(List<string> args)
@ -964,13 +1060,13 @@ namespace Dalamud.Injector
}
injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress);
injector.CallRemoteFunction(initAddress, startInfoAddress, out var exitCode);
var exitCode = injector.CallRemoteFunction(initAddress, startInfoAddress);
// ======================================================
if (exitCode > 0)
{
Log.Error($"Dalamud.Boot::Initialize returned {exitCode}");
Log.Error("Dalamud.Boot::Initialize returned {ExitCode}", exitCode);
return;
}

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

@ -1,13 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>11.0</LangVersion>
</PropertyGroup>
<PropertyGroup Label="Feature">
<RootNamespace>Dalamud.Test</RootNamespace>
<AssemblyTitle>Dalamud.Test</AssemblyTitle>
@ -50,19 +42,19 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.assert" Version="2.4.1" />
<PackageReference Include="xunit.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1">
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.abstractions" />
<PackageReference Include="xunit.analyzers" />
<PackageReference Include="xunit.assert" />
<PackageReference Include="xunit.core" />
<PackageReference Include="xunit.extensibility.core" />
<PackageReference Include="xunit.extensibility.execution" />
<PackageReference Include="xunit.runner.console">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -1,6 +1,10 @@
using System;
using System;
using System.IO;
using Dalamud.Configuration;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Xunit;
namespace Dalamud.Test.Game.Text.SeStringHandling
@ -50,19 +54,41 @@ namespace Dalamud.Test.Game.Text.SeStringHandling
var config = new MockConfig { Text = seString };
PluginConfigurations.SerializeConfig(config);
}
[Fact]
public void TestConfigDeserializable()
{
var builder = new SeStringBuilder();
var seString = builder.AddText("Some text").Build();
var config = new MockConfig { Text = seString };
// This relies on the type information being maintained, which is why we're using these
// static methods instead of default serialization/deserialization.
var configSerialized = PluginConfigurations.SerializeConfig(config);
var configDeserialized = (MockConfig)PluginConfigurations.DeserializeConfig(configSerialized);
Assert.Equal(config, configDeserialized);
}
[Theory]
[InlineData(49, 209)]
[InlineData(71, 7)]
[InlineData(62, 116)]
public void TestAutoTranslatePayloadReencode(uint group, uint key)
{
var payload = new AutoTranslatePayload(group, key);
Assert.Equal(group, payload.Group);
Assert.Equal(key, payload.Key);
var encoded = payload.Encode();
using var stream = new MemoryStream(encoded);
using var reader = new BinaryReader(stream);
var decodedPayload = Payload.Decode(reader) as AutoTranslatePayload;
Assert.Equal(group, decodedPayload.Group);
Assert.Equal(key, decodedPayload.Key);
Assert.Equal(encoded, decodedPayload.Encode());
}
}
}

View file

@ -31,19 +31,19 @@ public class ReliableFileStorageTests
.Select(
i => Parallel.ForEachAsync(
Enumerable.Range(1, 100),
(j, _) =>
async (j, _) =>
{
if (i % 2 == 0)
{
// ReSharper disable once AccessToDisposedClosure
rfs.Instance.WriteAllText(tempFile, j.ToString());
await rfs.Instance.WriteAllTextAsync(tempFile, j.ToString());
}
else if (i % 3 == 0)
{
try
{
// ReSharper disable once AccessToDisposedClosure
rfs.Instance.ReadAllText(tempFile);
await rfs.Instance.ReadAllTextAsync(tempFile);
}
catch (FileNotFoundException)
{
@ -54,8 +54,6 @@ public class ReliableFileStorageTests
{
File.Delete(tempFile);
}
return ValueTask.CompletedTask;
})));
}
@ -112,41 +110,41 @@ public class ReliableFileStorageTests
}
[Fact]
public void Exists_WhenFileInBackup_ReturnsTrue()
public async Task Exists_WhenFileInBackup_ReturnsTrue()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
File.Delete(tempFile);
Assert.True(rfs.Instance.Exists(tempFile));
}
[Fact]
public void Exists_WhenFileInBackup_WithDifferentContainerId_ReturnsFalse()
public async Task Exists_WhenFileInBackup_WithDifferentContainerId_ReturnsFalse()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
File.Delete(tempFile);
Assert.False(rfs.Instance.Exists(tempFile, Guid.NewGuid()));
}
[Fact]
public void WriteAllText_ThrowsIfPathIsEmpty()
public async Task WriteAllText_ThrowsIfPathIsEmpty()
{
using var rfs = CreateRfs();
Assert.Throws<ArgumentException>(() => rfs.Instance.WriteAllText("", TestFileContent1));
await Assert.ThrowsAsync<ArgumentException>(async () => await rfs.Instance.WriteAllTextAsync("", TestFileContent1));
}
[Fact]
public void WriteAllText_ThrowsIfPathIsNull()
public async Task WriteAllText_ThrowsIfPathIsNull()
{
using var rfs = CreateRfs();
Assert.Throws<ArgumentNullException>(() => rfs.Instance.WriteAllText(null!, TestFileContent1));
await Assert.ThrowsAsync<ArgumentNullException>(async () => await rfs.Instance.WriteAllTextAsync(null!, TestFileContent1));
}
[Fact]
@ -155,26 +153,26 @@ public class ReliableFileStorageTests
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
Assert.True(File.Exists(tempFile));
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile, forceBackup: true));
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true));
Assert.Equal(TestFileContent1, await File.ReadAllTextAsync(tempFile));
}
[Fact]
public void WriteAllText_SeparatesContainers()
public async Task WriteAllText_SeparatesContainers()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
var containerId = Guid.NewGuid();
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
rfs.Instance.WriteAllText(tempFile, TestFileContent2, containerId);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent2, containerId);
File.Delete(tempFile);
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile, forceBackup: true));
Assert.Equal(TestFileContent2, rfs.Instance.ReadAllText(tempFile, forceBackup: true, containerId));
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true));
Assert.Equal(TestFileContent2, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true, containerId));
}
[Fact]
@ -183,7 +181,7 @@ public class ReliableFileStorageTests
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateFailedRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
Assert.True(File.Exists(tempFile));
Assert.Equal(TestFileContent1, await File.ReadAllTextAsync(tempFile));
@ -195,38 +193,38 @@ public class ReliableFileStorageTests
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
rfs.Instance.WriteAllText(tempFile, TestFileContent2);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent2);
Assert.True(File.Exists(tempFile));
Assert.Equal(TestFileContent2, rfs.Instance.ReadAllText(tempFile, forceBackup: true));
Assert.Equal(TestFileContent2, await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup: true));
Assert.Equal(TestFileContent2, await File.ReadAllTextAsync(tempFile));
}
[Fact]
public void WriteAllText_SupportsNullContent()
public async Task WriteAllText_SupportsNullContent()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, null);
await rfs.Instance.WriteAllTextAsync(tempFile, null);
Assert.True(File.Exists(tempFile));
Assert.Equal("", rfs.Instance.ReadAllText(tempFile));
Assert.Equal("", await rfs.Instance.ReadAllTextAsync(tempFile));
}
[Fact]
public void ReadAllText_ThrowsIfPathIsEmpty()
public async Task ReadAllText_ThrowsIfPathIsEmpty()
{
using var rfs = CreateRfs();
Assert.Throws<ArgumentException>(() => rfs.Instance.ReadAllText(""));
await Assert.ThrowsAsync<ArgumentException>(async () => await rfs.Instance.ReadAllTextAsync(""));
}
[Fact]
public void ReadAllText_ThrowsIfPathIsNull()
public async Task ReadAllText_ThrowsIfPathIsNull()
{
using var rfs = CreateRfs();
Assert.Throws<ArgumentNullException>(() => rfs.Instance.ReadAllText(null!));
await Assert.ThrowsAsync<ArgumentNullException>(async () => await rfs.Instance.ReadAllTextAsync(null!));
}
[Fact]
@ -236,40 +234,40 @@ public class ReliableFileStorageTests
await File.WriteAllTextAsync(tempFile, TestFileContent1);
using var rfs = CreateRfs();
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile));
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile));
}
[Fact]
public void ReadAllText_WhenFileMissingWithBackup_ReturnsContent()
public async Task ReadAllText_WhenFileMissingWithBackup_ReturnsContent()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
File.Delete(tempFile);
Assert.Equal(TestFileContent1, rfs.Instance.ReadAllText(tempFile));
Assert.Equal(TestFileContent1, await rfs.Instance.ReadAllTextAsync(tempFile));
}
[Fact]
public void ReadAllText_WhenFileMissingWithBackup_ThrowsWithDifferentContainerId()
public async Task ReadAllText_WhenFileMissingWithBackup_ThrowsWithDifferentContainerId()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
var containerId = Guid.NewGuid();
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
File.Delete(tempFile);
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile, containerId: containerId));
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, containerId: containerId));
}
[Fact]
public void ReadAllText_WhenFileMissing_ThrowsIfDbFailed()
public async Task ReadAllText_WhenFileMissing_ThrowsIfDbFailed()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateFailedRfs();
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile));
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile));
}
[Fact]
@ -278,7 +276,7 @@ public class ReliableFileStorageTests
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
await File.WriteAllTextAsync(tempFile, TestFileContent1);
using var rfs = CreateRfs();
rfs.Instance.ReadAllText(tempFile, text => Assert.Equal(TestFileContent1, text));
await rfs.Instance.ReadAllTextAsync(tempFile, text => Assert.Equal(TestFileContent1, text));
}
[Fact]
@ -290,7 +288,7 @@ public class ReliableFileStorageTests
var readerCalledOnce = false;
using var rfs = CreateRfs();
Assert.Throws<FileReadException>(() => rfs.Instance.ReadAllText(tempFile, Reader));
await Assert.ThrowsAsync<FileReadException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, Reader));
return;
@ -303,7 +301,7 @@ public class ReliableFileStorageTests
}
[Fact]
public void ReadAllText_WithReader_WhenReaderThrows_ReadsContentFromBackup()
public async Task ReadAllText_WithReader_WhenReaderThrows_ReadsContentFromBackup()
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
@ -311,10 +309,10 @@ public class ReliableFileStorageTests
var assertionCalled = false;
using var rfs = CreateRfs();
rfs.Instance.WriteAllText(tempFile, TestFileContent1);
await rfs.Instance.WriteAllTextAsync(tempFile, TestFileContent1);
File.Delete(tempFile);
rfs.Instance.ReadAllText(tempFile, Reader);
await rfs.Instance.ReadAllTextAsync(tempFile, Reader);
Assert.True(assertionCalled);
return;
@ -335,17 +333,17 @@ public class ReliableFileStorageTests
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
await File.WriteAllTextAsync(tempFile, TestFileContent1);
using var rfs = CreateRfs();
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile, _ => throw new FileNotFoundException()));
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, _ => throw new FileNotFoundException()));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ReadAllText_WhenFileDoesNotExist_Throws(bool forceBackup)
public async Task ReadAllText_WhenFileDoesNotExist_Throws(bool forceBackup)
{
var tempFile = Path.Combine(CreateTempDir(), TestFileName);
using var rfs = CreateRfs();
Assert.Throws<FileNotFoundException>(() => rfs.Instance.ReadAllText(tempFile, forceBackup));
await Assert.ThrowsAsync<FileNotFoundException>(async () => await rfs.Instance.ReadAllTextAsync(tempFile, forceBackup));
}
private static DisposableReliableFileStorage CreateRfs()

View file

@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32319.34
MinimumVisualStudioVersion = 10.0.40219.1
@ -6,32 +6,28 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
targets\Dalamud.Plugin.Bootstrap.targets = targets\Dalamud.Plugin.Bootstrap.targets
targets\Dalamud.Plugin.targets = targets\Dalamud.Plugin.targets
Directory.Build.props = Directory.Build.props
tools\BannedSymbols.txt = tools\BannedSymbols.txt
tools\dalamud.ruleset = tools\dalamud.ruleset
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "build", "build\build.csproj", "{94E5B016-02B1-459B-97D9-E783F28764B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud", "Dalamud\Dalamud.csproj", "{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}"
ProjectSection(ProjectDependencies) = postProject
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}
{8430077C-F736-4246-A052-8EA1CECE844E} = {8430077C-F736-4246-A052-8EA1CECE844E}
{F258347D-31BE-4605-98CE-40E43BDF6F9D} = {F258347D-31BE-4605-98CE-40E43BDF6F9D}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boot\Dalamud.Boot.vcxproj", "{55198DC3-A03D-408E-A8EB-2077780C8576}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Injector.Boot", "Dalamud.Injector.Boot\Dalamud.Injector.Boot.vcxproj", "{8874326B-E755-4D13-90B4-59AB263A3E6B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.CorePlugin", "Dalamud.CorePlugin\Dalamud.CorePlugin.csproj", "{4AFDB34A-7467-4D41-B067-53BC4101D9D0}"
EndProject
@ -49,14 +45,57 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator", "lib\FFX
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator.Runtime", "lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj", "{A6AA1C3F-9470-4922-9D3F-D4549657AB22}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimgui", "external\cimgui\cimgui.vcxproj", "{8430077C-F736-4246-A052-8EA1CECE844E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "imgui", "imgui", "{DBE5345E-6594-4A59-B183-1C3D5592269D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CS", "CS", "{8BBACF2D-7AB8-4610-A115-0E363D35C291}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimplot", "external\cimplot\cimplot.vcxproj", "{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimguizmo", "external\cimguizmo\cimguizmo.vcxproj", "{F258347D-31BE-4605-98CE-40E43BDF6F9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImGui", "imgui\Dalamud.Bindings.ImGui\Dalamud.Bindings.ImGui.csproj", "{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImGuizmo", "imgui\Dalamud.Bindings.ImGuizmo\Dalamud.Bindings.ImGuizmo.csproj", "{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImPlot", "imgui\Dalamud.Bindings.ImPlot\Dalamud.Bindings.ImPlot.csproj", "{9C70BD06-D52C-425E-9C14-5D66BC6046EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bindings", "Bindings", "{A217B3DF-607A-4EFB-B107-3C4809348043}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StandaloneImGuiTestbed", "imgui\StandaloneImGuiTestbed\StandaloneImGuiTestbed.csproj", "{4702A911-2513-478C-A434-2776393FDE77}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGuiScene", "imgui\ImGuiScene\ImGuiScene.csproj", "{66753AC7-0029-4373-9CC4-7760B1F46141}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lumina", "Lumina", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel.Generator", "lib\Lumina.Excel\src\Lumina.Excel.Generator\Lumina.Excel.Generator.csproj", "{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel", "lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj", "{88FB719B-EB41-73C5-8D25-C03E0C69904F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source Generators", "Source Generators", "{50BEC23B-FFFD-427B-A95D-27E1D1958FFF}"
ProjectSection(SolutionItems) = preProject
generators\Directory.Build.props = generators\Directory.Build.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj", "{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Sample", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Sample\Dalamud.EnumGenerator.Sample.csproj", "{8CDAEB2D-5022-450A-A97F-181C6270185F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Tests", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Tests\Dalamud.EnumGenerator.Tests.csproj", "{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|x64
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|x64
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|x64
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64
@ -69,26 +108,10 @@ Global
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64
@ -97,18 +120,22 @@ Global
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.Build.0 = Release|Any CPU
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64
{317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|Any CPU
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|Any CPU
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|x64
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|x64
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|x64
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|x64
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|x64
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|x64
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|x64
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|x64
{3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3620414C-7DFC-423E-929F-310E19F5D930}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -117,21 +144,85 @@ Global
{A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.Build.0 = Release|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.Build.0 = Release|Any CPU
{8430077C-F736-4246-A052-8EA1CECE844E}.Debug|Any CPU.ActiveCfg = Debug|x64
{8430077C-F736-4246-A052-8EA1CECE844E}.Debug|Any CPU.Build.0 = Debug|x64
{8430077C-F736-4246-A052-8EA1CECE844E}.Release|Any CPU.ActiveCfg = Release|x64
{8430077C-F736-4246-A052-8EA1CECE844E}.Release|Any CPU.Build.0 = Release|x64
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Debug|Any CPU.ActiveCfg = Debug|x64
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Debug|Any CPU.Build.0 = Debug|x64
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Release|Any CPU.ActiveCfg = Release|x64
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Release|Any CPU.Build.0 = Release|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.ActiveCfg = Debug|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.Build.0 = Debug|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.ActiveCfg = Release|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.Build.0 = Release|x64
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Debug|Any CPU.ActiveCfg = Debug|x64
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Debug|Any CPU.Build.0 = Debug|x64
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Release|Any CPU.ActiveCfg = Release|x64
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Release|Any CPU.Build.0 = Release|x64
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Debug|Any CPU.ActiveCfg = Debug|x64
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Debug|Any CPU.Build.0 = Debug|x64
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Release|Any CPU.ActiveCfg = Release|x64
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Release|Any CPU.Build.0 = Release|x64
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Debug|Any CPU.ActiveCfg = Debug|x64
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Debug|Any CPU.Build.0 = Debug|x64
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.ActiveCfg = Release|x64
{9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.Build.0 = Release|x64
{4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.ActiveCfg = Debug|x64
{4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.Build.0 = Debug|x64
{4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.ActiveCfg = Release|x64
{4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.Build.0 = Release|x64
{66753AC7-0029-4373-9CC4-7760B1F46141}.Debug|Any CPU.ActiveCfg = Debug|x64
{66753AC7-0029-4373-9CC4-7760B1F46141}.Debug|Any CPU.Build.0 = Debug|x64
{66753AC7-0029-4373-9CC4-7760B1F46141}.Release|Any CPU.ActiveCfg = Release|x64
{66753AC7-0029-4373-9CC4-7760B1F46141}.Release|Any CPU.Build.0 = Release|x64
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Release|Any CPU.Build.0 = Release|Any CPU
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.Build.0 = Release|Any CPU
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.Build.0 = Release|Any CPU
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.Build.0 = Release|Any CPU
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0483026E-C6CE-4B1A-AA68-46544C08140B} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{3620414C-7DFC-423E-929F-310E19F5D930} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0} = {8F079208-C227-4D96-9427-2BEBE0003944}
{3620414C-7DFC-423E-929F-310E19F5D930} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
{A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
{8430077C-F736-4246-A052-8EA1CECE844E} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{DBE5345E-6594-4A59-B183-1C3D5592269D} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{8BBACF2D-7AB8-4610-A115-0E363D35C291} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{F258347D-31BE-4605-98CE-40E43BDF6F9D} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{B0AA8737-33A3-4766-8CBE-A48F2EF283BA} = {A217B3DF-607A-4EFB-B107-3C4809348043}
{5E6EDD75-AE95-43A6-9D67-95B840EB4B71} = {A217B3DF-607A-4EFB-B107-3C4809348043}
{9C70BD06-D52C-425E-9C14-5D66BC6046EF} = {A217B3DF-607A-4EFB-B107-3C4809348043}
{4702A911-2513-478C-A434-2776393FDE77} = {A217B3DF-607A-4EFB-B107-3C4809348043}
{66753AC7-0029-4373-9CC4-7760B1F46141} = {A217B3DF-607A-4EFB-B107-3C4809348043}
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{88FB719B-EB41-73C5-8D25-C03E0C69904F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
{8CDAEB2D-5022-450A-A97F-181C6270185F} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}

View file

@ -54,6 +54,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EFormat_002ESettingsUpgrade_002EAlignmentTabFillStyleMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=bannedplugin/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=clientopcode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=collectability/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dalamud/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=FFXIV/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Flytext/@EntryIndexedValue">True</s:Boolean>
@ -66,6 +67,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=PLUGINR/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Refilter/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=serveropcode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=spiritbond/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Universalis/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsanitized/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uploaders/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -3,7 +3,9 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game.Text;
using Dalamud.Interface;
@ -11,15 +13,20 @@ using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.ReShadeHandling;
using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing.Persistence;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Internal.AutoUpdate;
using Dalamud.Plugin.Internal.Profiles;
using Dalamud.Storage;
using Dalamud.Utility;
using Newtonsoft.Json;
using Serilog;
using Serilog.Events;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Dalamud.Configuration.Internal;
/// <summary>
@ -45,6 +52,8 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
[JsonIgnore]
private bool isSaveQueued;
private Task? writeTask;
/// <summary>
/// Delegate for the <see cref="DalamudConfiguration.DalamudConfigurationSaved"/> event that occurs when the dalamud configuration is saved.
/// </summary>
@ -62,12 +71,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public List<string>? BadWords { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
/// Gets or sets a value indicating whether the taskbar should flash once a duty is found.
/// </summary>
public bool DutyFinderTaskbarFlash { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found.
/// Gets or sets a value indicating whether a message should be sent in chat once a duty is found.
/// </summary>
public bool DutyFinderChatMessage { get; set; } = true;
@ -84,7 +93,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
/// <summary>
/// Gets or sets a dictionary of seen FTUE levels.
/// </summary>
public Dictionary<string, int> SeenFtueLevels { get; set; } = new();
public Dictionary<string, int> SeenFtueLevels { get; set; } = [];
/// <summary>
/// Gets or sets the last loaded Dalamud version.
@ -97,34 +106,29 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public XivChatType GeneralChatType { get; set; } = XivChatType.Debug;
/// <summary>
/// Gets or sets a value indicating whether or not plugin testing builds should be shown.
/// Gets or sets a value indicating whether plugin testing builds should be shown.
/// </summary>
public bool DoPluginTest { get; set; } = false;
/// <summary>
/// Gets or sets a key to opt into Dalamud staging builds.
/// </summary>
public string? DalamudBetaKey { get; set; } = null;
/// <summary>
/// Gets or sets a list of custom repos.
/// </summary>
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = [];
/// <summary>
/// Gets or sets a value indicating whether or not a disclaimer regarding third-party repos has been dismissed.
/// Gets or sets a value indicating whether a disclaimer regarding third-party repos has been dismissed.
/// </summary>
public bool? ThirdRepoSpeedbumpDismissed { get; set; } = null;
/// <summary>
/// Gets or sets a list of hidden plugins.
/// </summary>
public List<string> HiddenPluginInternalName { get; set; } = new();
public List<string> HiddenPluginInternalName { get; set; } = [];
/// <summary>
/// Gets or sets a list of seen plugins.
/// </summary>
public List<string> SeenPluginInternalName { get; set; } = new();
public List<string> SeenPluginInternalName { get; set; } = [];
/// <summary>
/// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
@ -132,37 +136,25 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
/// However by specifiying this value manually, you can add arbitrary files outside the normal
/// file paths.
/// </summary>
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = [];
/// <summary>
/// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
/// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
/// injected Dalamud instance.
/// </summary>
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = new();
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = [];
/// <summary>
/// Gets or sets the global UI scale.
/// </summary>
public float GlobalUiScale { get; set; } = 1.0f;
/// <summary>
/// Gets or sets a value indicating whether to use AXIS fonts from the game.
/// </summary>
[Obsolete($"See {nameof(DefaultFontSpec)}")]
public bool UseAxisFontsFromGame { get; set; } = true;
/// <summary>
/// Gets or sets the default font spec.
/// </summary>
public IFontSpec? DefaultFontSpec { get; set; }
/// <summary>
/// Gets or sets the gamma value to apply for Dalamud fonts. Do not use.
/// </summary>
[Obsolete("It happens that nobody touched this setting", true)]
public float FontGammaLevel { get; set; } = 1.4f;
/// <summary>Gets or sets the opacity of the IME state indicator.</summary>
/// <value>0 will hide the state indicator. 1 will make the state indicator fully visible. Values outside the
/// range will be clamped to [0, 1].</value>
@ -170,38 +162,38 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public float ImeStateIndicatorOpacity { get; set; } = 1f;
/// <summary>
/// Gets or sets a value indicating whether or not plugin UI should be hidden.
/// Gets or sets a value indicating whether plugin UI should be hidden.
/// </summary>
public bool ToggleUiHide { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes.
/// Gets or sets a value indicating whether plugin UI should be hidden during cutscenes.
/// </summary>
public bool ToggleUiHideDuringCutscenes { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose.
/// Gets or sets a value indicating whether plugin UI should be hidden during GPose.
/// </summary>
public bool ToggleUiHideDuringGpose { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not a message containing Dalamud's current version and the number of loaded plugins should be sent at login.
/// Gets or sets a value indicating whether a message containing Dalamud's current version and the number of loaded plugins should be sent at login.
/// </summary>
public bool PrintDalamudWelcomeMsg { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login.
/// Gets or sets a value indicating whether a message containing detailed plugin information should be sent at login.
/// </summary>
public bool PrintPluginsWelcomeMsg { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not plugins should be auto-updated.
/// Gets or sets a value indicating whether plugins should be auto-updated.
/// </summary>
[Obsolete("Use AutoUpdateBehavior instead.")]
public bool AutoUpdatePlugins { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu.
/// Gets or sets a value indicating whether Dalamud should add buttons to the system menu.
/// </summary>
public bool DoButtonsSystemMenu { get; set; } = true;
@ -216,12 +208,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public bool LogSynchronously { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
/// Gets or sets a value indicating whether the debug log should scroll automatically.
/// </summary>
public bool LogAutoScroll { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not the debug log should open at startup.
/// Gets or sets a value indicating whether the debug log should open at startup.
/// </summary>
public bool LogOpenAtStartup { get; set; }
@ -233,36 +225,35 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
/// <summary>
/// Gets or sets a list representing the command history for the Dalamud Console.
/// </summary>
public List<string> LogCommandHistory { get; set; } = new();
public List<string> LogCommandHistory { get; set; } = [];
/// <summary>
/// Gets or sets a value indicating whether or not the dev bar should open at startup.
/// Gets or sets a value indicating whether the dev bar should open at startup.
/// </summary>
public bool DevBarOpenAtStartup { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
/// Gets or sets a value indicating whether ImGui asserts should be enabled at startup.
/// </summary>
public bool AssertsEnabledAtStartup { get; set; }
public bool? ImGuiAssertsEnabledAtStartup { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
/// Gets or sets a value indicating whether docking should be globally enabled in ImGui.
/// </summary>
public bool IsDocking { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects.
/// Gets or sets a value indicating whether plugin user interfaces should trigger sound effects.
/// This setting is effected by the in-game "System Sounds" option and volume.
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "ABI")]
public bool EnablePluginUISoundEffects { get; set; }
public bool EnablePluginUISoundEffects { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not an additional button allowing pinning and clickthrough options should be shown
/// Gets or sets a value indicating whether an additional button allowing pinning and clickthrough options should be shown
/// on plugin title bars when using the Window System.
/// </summary>
[JsonProperty("EnablePluginUiAdditionalOptionsExperimental")]
public bool EnablePluginUiAdditionalOptions { get; set; } = false;
public bool EnablePluginUiAdditionalOptions { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether viewports should always be disabled.
@ -270,32 +261,22 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public bool IsDisableViewport { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui.
/// Gets or sets a value indicating whether navigation via a gamepad should be globally enabled in ImGui.
/// </summary>
public bool IsGamepadNavigationEnabled { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not focus management is enabled.
/// Gets or sets a value indicating whether focus management is enabled.
/// </summary>
public bool IsFocusManagementEnabled { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup.
/// </summary>
public bool IsAntiAntiDebugEnabled { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether to resume game main thread after plugins load.
/// </summary>
public bool IsResumeGameAfterPluginLoad { get; set; } = false;
/// <summary>
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
/// </summary>
public string? DalamudBetaKind { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
/// Gets or sets a value indicating whether any plugin should be loaded when the game is started.
/// It is reset immediately when read.
/// </summary>
public bool PluginSafeMode { get; set; }
@ -307,7 +288,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public int? PluginWaitBeforeFree { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not crashes during shutdown should be reported.
/// Gets or sets a value indicating whether crashes during shutdown should be reported.
/// </summary>
public bool ReportShutdownCrashes { get; set; }
@ -339,15 +320,20 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public ProfileModel? DefaultProfile { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not profiles are enabled.
/// Gets or sets a value indicating whether profiles are enabled.
/// </summary>
public bool ProfilesEnabled { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether or not the user has seen the profiles tutorial.
/// Gets or sets a value indicating whether the user has seen the profiles tutorial.
/// </summary>
public bool ProfilesHasSeenTutorial { get; set; } = false;
/// <summary>
/// Gets or sets the default UI preset.
/// </summary>
public PresetModel DefaultUiPreset { get; set; } = new();
/// <summary>
/// Gets or sets the order of DTR elements, by title.
/// </summary>
@ -383,7 +369,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public bool? ReduceMotions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not market board data should be uploaded.
/// Gets or sets a value indicating whether market board data should be uploaded.
/// </summary>
public bool IsMbCollect { get; set; } = true;
@ -419,7 +405,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
}
/// <summary>
/// Gets or sets a value indicating whether or not to show info on dev bar.
/// Gets or sets a value indicating whether to show info on dev bar.
/// </summary>
public bool ShowDevBarInfo { get; set; } = true;
@ -484,27 +470,60 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public AutoUpdateBehavior? AutoUpdateBehavior { get; set; } = null;
/// <summary>
/// Gets or sets a value indicating whether or not users should be notified regularly about pending updates.
/// Gets or sets a value indicating whether users should be notified regularly about pending updates.
/// </summary>
public bool CheckPeriodicallyForUpdates { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether users should be notified about updates in chat.
/// </summary>
public bool SendUpdateNotificationToChat { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether disabled plugins should be auto-updated.
/// </summary>
public bool UpdateDisabledPlugins { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating where notifications are anchored to on the screen.
/// </summary>
public Vector2 NotificationAnchorPosition { get; set; } = new(1f, 1f);
#pragma warning disable SA1600
#pragma warning disable SA1516
// XLCore/XoM compatibility until they move it out
public string? DalamudBetaKey { get; set; } = null;
public string? DalamudBetaKind { get; set; }
#pragma warning restore SA1516
#pragma warning restore SA1600
/// <summary>
/// Gets or sets a list of badge passwords used to unlock badges.
/// </summary>
public List<string> UsedBadgePasswords { get; set; } = [];
/// <summary>
/// Gets or sets a value indicating whether badges should be shown on the title screen.
/// </summary>
public bool ShowBadgesOnTitleScreen { get; set; } = true;
/// <summary>
/// Load a configuration from the provided path.
/// </summary>
/// <param name="path">Path to read from.</param>
/// <param name="fs">File storage.</param>
/// <returns>The deserialized configuration file.</returns>
public static DalamudConfiguration Load(string path, ReliableFileStorage fs)
public static async Task<DalamudConfiguration> Load(string path, ReliableFileStorage fs)
{
DalamudConfiguration deserialized = null;
try
{
fs.ReadAllText(path, text =>
await fs.ReadAllTextAsync(path, text =>
{
deserialized =
JsonConvert.DeserializeObject<DalamudConfiguration>(text, SerializerSettings);
// If this reads as null, the file was empty, that's no good
if (deserialized == null)
throw new Exception("Read config was null.");
@ -530,7 +549,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
{
Log.Error(e, "Failed to set defaults for DalamudConfiguration");
}
return deserialized;
}
@ -548,13 +567,18 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public void ForceSave()
{
this.Save();
this.isSaveQueued = false;
this.writeTask?.GetAwaiter().GetResult();
}
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
// Make sure that we save, if a save is queued while we are shutting down
this.Update();
// Wait for the write task to finish
this.writeTask?.Wait();
}
/// <summary>
@ -566,8 +590,6 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
{
this.Save();
this.isSaveQueued = false;
Log.Verbose("Config saved");
}
}
@ -579,11 +601,15 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
{
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/animation/animation_win.cc;l=29?q=ReducedMotion&ss=chromium
var winAnimEnabled = 0;
var success = NativeFunctions.SystemParametersInfo(
(uint)NativeFunctions.AccessibilityParameter.SPI_GETCLIENTAREAANIMATION,
0,
ref winAnimEnabled,
0);
bool success;
unsafe
{
success = Windows.Win32.PInvoke.SystemParametersInfo(
SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETCLIENTAREAANIMATION,
0,
&winAnimEnabled,
0);
}
if (!success)
{
@ -595,22 +621,50 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
this.ReduceMotions = winAnimEnabled == 0;
}
}
// Migrate old auto-update setting to new auto-update behavior
this.AutoUpdateBehavior ??= this.AutoUpdatePlugins
? Plugin.Internal.AutoUpdate.AutoUpdateBehavior.UpdateAll
: Plugin.Internal.AutoUpdate.AutoUpdateBehavior.OnlyNotify;
#pragma warning restore CS0618
}
private void Save()
{
ThreadSafety.AssertMainThread();
if (this.configPath is null)
throw new InvalidOperationException("configPath is not set.");
Service<ReliableFileStorage>.Get().WriteAllText(
this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
this.DalamudConfigurationSaved?.Invoke(this);
// Wait for previous write to finish
this.writeTask?.Wait();
this.writeTask = Task.Run(async () =>
{
await Service<ReliableFileStorage>.Get().WriteAllTextAsync(
this.configPath,
JsonConvert.SerializeObject(this, SerializerSettings));
Log.Verbose("DalamudConfiguration saved");
}).ContinueWith(t =>
{
if (t.IsFaulted)
{
Log.Error(
t.Exception,
"Failed to save DalamudConfiguration to {Path}",
this.configPath);
}
});
foreach (var action in Delegate.EnumerateInvocationList(this.DalamudConfigurationSaved))
{
try
{
action(this);
}
catch (Exception ex)
{
Log.Error(ex, "Exception during raise of {handler}", action.Method);
}
}
}
}

View file

@ -12,18 +12,24 @@ internal sealed class DevPluginSettings
/// </summary>
public bool StartOnBoot { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether we should show notifications for errors this plugin
/// is creating.
/// </summary>
public bool NotifyForErrors { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether this plugin should automatically reload on file change.
/// </summary>
public bool AutomaticReloading { get; set; } = false;
/// <summary>
/// Gets or sets an ID uniquely identifying this specific instance of a devPlugin.
/// </summary>
public Guid WorkingPluginId { get; set; } = Guid.Empty;
/// <summary>
/// Gets or sets a list of validation problems that have been dismissed by the user.
/// </summary>
public List<string> DismissedValidationProblems { get; set; } = new();
public List<string> DismissedValidationProblems { get; set; } = [];
}

View file

@ -5,11 +5,6 @@ namespace Dalamud.Configuration.Internal;
/// </summary>
internal class EnvironmentConfiguration
{
/// <summary>
/// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled.
/// </summary>
public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX");
/// <summary>
/// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled.
/// </summary>
@ -26,7 +21,7 @@ internal class EnvironmentConfiguration
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
/// <summary>
/// Gets a value indicating whether or not Dalamud context menus should be disabled.
/// Gets a value indicating whether Dalamud context menus should be disabled.
/// </summary>
public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU");

View file

@ -2,6 +2,8 @@ using System.IO;
using System.Reflection;
using Dalamud.Storage;
using Dalamud.Utility;
using Newtonsoft.Json;
namespace Dalamud.Configuration;
@ -9,6 +11,7 @@ namespace Dalamud.Configuration;
/// <summary>
/// Configuration to store settings for a dalamud plugin.
/// </summary>
[Api15ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
public sealed class PluginConfigurations
{
private readonly DirectoryInfo configDirectory;
@ -36,7 +39,7 @@ public sealed class PluginConfigurations
public void Save(IPluginConfiguration config, string pluginName, Guid workingPluginId)
{
Service<ReliableFileStorage>.Get()
.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config), workingPluginId);
.WriteAllTextAsync(this.GetConfigFile(pluginName).FullName, SerializeConfig(config), workingPluginId).GetAwaiter().GetResult();
}
/// <summary>
@ -52,12 +55,12 @@ public sealed class PluginConfigurations
IPluginConfiguration? config = null;
try
{
Service<ReliableFileStorage>.Get().ReadAllText(path.FullName, text =>
Service<ReliableFileStorage>.Get().ReadAllTextAsync(path.FullName, text =>
{
config = DeserializeConfig(text);
if (config == null)
throw new Exception("Read config was null.");
}, workingPluginId);
}, workingPluginId).GetAwaiter().GetResult();
}
catch (FileNotFoundException)
{

View file

@ -11,13 +11,13 @@ public interface IConsoleEntry
/// Gets the name of the entry.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the description of the entry.
/// </summary>
public string Description { get; }
}
/// <summary>
/// Interface representing a command in the console.
/// </summary>
@ -27,7 +27,7 @@ public interface IConsoleCommand : IConsoleEntry
/// Execute this command.
/// </summary>
/// <param name="arguments">Arguments to invoke the entry with.</param>
/// <returns>Whether or not execution succeeded.</returns>
/// <returns>Whether execution succeeded.</returns>
public bool Invoke(IEnumerable<object> arguments);
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
@ -17,10 +17,10 @@ namespace Dalamud.Console;
[ServiceManager.BlockingEarlyLoadedService("Console is needed by other blocking early loaded services.")]
internal partial class ConsoleManager : IServiceType
{
private static readonly ModuleLog Log = new("CON");
private Dictionary<string, IConsoleEntry> entries = new();
private static readonly ModuleLog Log = ModuleLog.Create<ConsoleManager>();
private Dictionary<string, IConsoleEntry> entries = [];
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleManager"/> class.
/// </summary>
@ -29,17 +29,17 @@ internal partial class ConsoleManager : IServiceType
{
this.AddCommand("toggle", "Toggle a boolean variable.", this.OnToggleVariable);
}
/// <summary>
/// Event that is triggered when a command is processed. Return true to stop the command from being processed any further.
/// </summary>
public event Func<string, bool>? Invoke;
/// <summary>
/// Gets a read-only dictionary of console entries.
/// </summary>
public IReadOnlyDictionary<string, IConsoleEntry> Entries => this.entries;
/// <summary>
/// Add a command to the console.
/// </summary>
@ -53,13 +53,13 @@ internal partial class ConsoleManager : IServiceType
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(description);
ArgumentNullException.ThrowIfNull(func);
if (this.FindEntry(name) != null)
throw new InvalidOperationException($"Entry '{name}' already exists.");
var command = new ConsoleCommand(name, description, func);
this.entries.Add(name, command);
return command;
}
@ -77,14 +77,14 @@ internal partial class ConsoleManager : IServiceType
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(description);
Traits.ThrowIfTIsNullableAndNull(defaultValue);
if (this.FindEntry(name) != null)
throw new InvalidOperationException($"Entry '{name}' already exists.");
var variable = new ConsoleVariable<T>(name, description);
variable.Value = defaultValue;
this.entries.Add(name, variable);
return variable;
}
@ -98,11 +98,8 @@ internal partial class ConsoleManager : IServiceType
{
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(alias);
var target = this.FindEntry(name);
if (target == null)
throw new EntryNotFoundException(name);
var target = this.FindEntry(name) ?? throw new EntryNotFoundException(name);
if (this.FindEntry(alias) != null)
throw new InvalidOperationException($"Entry '{alias}' already exists.");
@ -135,21 +132,21 @@ internal partial class ConsoleManager : IServiceType
public T GetVariable<T>(string name)
{
ArgumentNullException.ThrowIfNull(name);
var entry = this.FindEntry(name);
if (entry is ConsoleVariable<T> variable)
return variable.Value;
if (entry is ConsoleVariable)
throw new InvalidOperationException($"Variable '{name}' is not of type {typeof(T).Name}.");
if (entry is null)
throw new EntryNotFoundException(name);
throw new InvalidOperationException($"Command '{name}' is not a variable.");
}
/// <summary>
/// Set the value of a variable.
/// </summary>
@ -162,18 +159,18 @@ internal partial class ConsoleManager : IServiceType
{
ArgumentNullException.ThrowIfNull(name);
Traits.ThrowIfTIsNullableAndNull(value);
var entry = this.FindEntry(name);
if (entry is ConsoleVariable<T> variable)
variable.Value = value;
if (entry is ConsoleVariable)
throw new InvalidOperationException($"Variable '{name}' is not of type {typeof(T).Name}.");
if (entry is null)
throw new EntryNotFoundException(name);
throw new EntryNotFoundException(name);
throw new InvalidOperationException($"Command '{name}' is not a variable.");
}
@ -181,16 +178,26 @@ internal partial class ConsoleManager : IServiceType
/// Process a console command.
/// </summary>
/// <param name="command">The command to process.</param>
/// <returns>Whether or not the command was successfully processed.</returns>
/// <returns>Whether the command was successfully processed.</returns>
public bool ProcessCommand(string command)
{
if (this.Invoke?.Invoke(command) == true)
return true;
foreach (var action in Delegate.EnumerateInvocationList(this.Invoke))
{
try
{
if (action(command))
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Exception during raise of {handler}", action.Method);
}
}
var matches = GetCommandParsingRegex().Matches(command);
if (matches.Count == 0)
return false;
var entryName = matches[0].Value;
if (string.IsNullOrEmpty(entryName) || entryName.Any(char.IsWhiteSpace))
{
@ -204,7 +211,7 @@ internal partial class ConsoleManager : IServiceType
Log.Error("Command {CommandName} not found", entryName);
return false;
}
var parsedArguments = new List<object>();
if (entry.ValidArguments != null)
@ -217,13 +224,13 @@ internal partial class ConsoleManager : IServiceType
PrintUsage(entry);
return false;
}
var argumentToMatch = entry.ValidArguments[i - 1];
var group = matches[i];
if (!group.Success)
continue;
var value = group.Value;
if (string.IsNullOrEmpty(value))
continue;
@ -262,15 +269,15 @@ internal partial class ConsoleManager : IServiceType
throw new Exception("Unhandled argument type.");
}
}
if (parsedArguments.Count != entry.ValidArguments.Count)
{
// Either fill in the default values or error out
for (var i = parsedArguments.Count; i < entry.ValidArguments.Count; i++)
{
var argument = entry.ValidArguments[i];
// If the default value is DBNull, we need to error out as that means it was not specified
if (argument.DefaultValue == DBNull.Value)
{
@ -281,7 +288,7 @@ internal partial class ConsoleManager : IServiceType
parsedArguments.Add(argument.DefaultValue);
}
if (parsedArguments.Count != entry.ValidArguments.Count)
{
Log.Error("Too many arguments for command {CommandName}", entryName);
@ -302,20 +309,20 @@ internal partial class ConsoleManager : IServiceType
return entry.Invoke(parsedArguments);
}
[GeneratedRegex("""("[^"]+"|[^\s"]+)""", RegexOptions.Compiled)]
private static partial Regex GetCommandParsingRegex();
private static void PrintUsage(ConsoleEntry entry, bool error = true)
{
Log.WriteLog(
error ? LogEventLevel.Error : LogEventLevel.Information,
error ? LogEventLevel.Error : LogEventLevel.Information,
"Usage: {CommandName} {Arguments}",
null,
entry.Name,
string.Join(" ", entry.ValidArguments?.Select(x => $"<{x.Type.ToString().ToLowerInvariant()}>") ?? Enumerable.Empty<string>()));
}
private ConsoleEntry? FindEntry(string name)
{
return this.entries.TryGetValue(name, out var entry) ? entry as ConsoleEntry : null;
@ -333,10 +340,10 @@ internal partial class ConsoleManager : IServiceType
return true;
}
private static class Traits
{
public static void ThrowIfTIsNullableAndNull<T>(T? argument, [CallerArgumentExpression("argument")] string? paramName = null)
public static void ThrowIfTIsNullableAndNull<T>(T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
{
if (argument == null && !typeof(T).IsValueType)
throw new ArgumentNullException(paramName);
@ -364,17 +371,17 @@ internal partial class ConsoleManager : IServiceType
/// <inheritdoc/>
public string Description { get; }
/// <summary>
/// Gets or sets a list of valid argument types for this console entry.
/// </summary>
public IReadOnlyList<ArgumentInfo>? ValidArguments { get; protected set; }
/// <summary>
/// Execute this command.
/// </summary>
/// <param name="arguments">Arguments to invoke the entry with.</param>
/// <returns>Whether or not execution succeeded.</returns>
/// <returns>Whether execution succeeded.</returns>
public abstract bool Invoke(IEnumerable<object> arguments);
/// <summary>
@ -388,19 +395,19 @@ internal partial class ConsoleManager : IServiceType
{
if (type == typeof(string))
return new ArgumentInfo(ConsoleArgumentType.String, defaultValue);
if (type == typeof(int))
return new ArgumentInfo(ConsoleArgumentType.Integer, defaultValue);
if (type == typeof(float))
return new ArgumentInfo(ConsoleArgumentType.Float, defaultValue);
if (type == typeof(bool))
return new ArgumentInfo(ConsoleArgumentType.Bool, defaultValue);
throw new ArgumentException($"Invalid argument type: {type.Name}");
}
public record ArgumentInfo(ConsoleArgumentType Type, object? DefaultValue);
}
@ -436,7 +443,7 @@ internal partial class ConsoleManager : IServiceType
private class ConsoleCommand : ConsoleEntry, IConsoleCommand
{
private readonly Delegate func;
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleCommand"/> class.
/// </summary>
@ -447,17 +454,17 @@ internal partial class ConsoleManager : IServiceType
: base(name, description)
{
this.func = func;
if (func.Method.ReturnType != typeof(bool))
throw new ArgumentException("Console command functions must return a boolean indicating success.");
var validArguments = new List<ArgumentInfo>();
foreach (var parameterInfo in func.Method.GetParameters())
{
var paraT = parameterInfo.ParameterType;
validArguments.Add(TypeToArgument(paraT, parameterInfo.DefaultValue));
}
this.ValidArguments = validArguments;
}
@ -491,7 +498,7 @@ internal partial class ConsoleManager : IServiceType
{
this.ValidArguments = new List<ArgumentInfo> { TypeToArgument(typeof(T), null) };
}
/// <inheritdoc/>
public T Value { get; set; }
@ -507,16 +514,16 @@ internal partial class ConsoleManager : IServiceType
{
this.Value = (T)(object)!boolValue;
}
Log.WriteLog(LogEventLevel.Information, "{VariableName} = {VariableValue}", null, this.Name, this.Value);
return true;
}
if (first.GetType() != typeof(T))
throw new ArgumentException($"Console variable must be set with an argument of type {typeof(T).Name}.");
this.Value = (T)first;
return true;
}
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@ -11,6 +11,47 @@ namespace Dalamud.Console;
#pragma warning disable Dalamud001
/// <summary>
/// Utility functions for the console manager.
/// </summary>
internal static partial class ConsoleManagerPluginUtil
{
private static readonly string[] ReservedNamespaces = ["dalamud", "xl", "plugin"];
/// <summary>
/// Get a sanitized namespace name from a plugin's internal name.
/// </summary>
/// <param name="pluginInternalName">The plugin's internal name.</param>
/// <returns>A sanitized namespace.</returns>
public static string GetSanitizedNamespaceName(string pluginInternalName)
{
// Must be lowercase
pluginInternalName = pluginInternalName.ToLowerInvariant();
// Remove all non-alphabetic characters
pluginInternalName = NonAlphaRegex().Replace(pluginInternalName, string.Empty);
// Remove reserved namespaces from the start or end
foreach (var reservedNamespace in ReservedNamespaces)
{
if (pluginInternalName.StartsWith(reservedNamespace))
{
pluginInternalName = pluginInternalName[reservedNamespace.Length..];
}
if (pluginInternalName.EndsWith(reservedNamespace))
{
pluginInternalName = pluginInternalName[..^reservedNamespace.Length];
}
}
return pluginInternalName;
}
[GeneratedRegex(@"[^a-z]")]
private static partial Regex NonAlphaRegex();
}
/// <summary>
/// Plugin-scoped version of the console service.
/// </summary>
@ -19,12 +60,12 @@ namespace Dalamud.Console;
#pragma warning disable SA1015
[ResolveVia<IConsole>]
#pragma warning restore SA1015
public class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
{
[ServiceManager.ServiceDependency]
private readonly ConsoleManager console = Service<ConsoleManager>.Get();
private readonly List<IConsoleEntry> trackedEntries = new();
private readonly List<IConsoleEntry> trackedEntries = [];
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleManagerPluginScoped"/> class.
@ -38,7 +79,7 @@ public class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
/// <inheritdoc/>
public string Prefix { get; private set; }
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
@ -46,7 +87,7 @@ public class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
{
this.console.RemoveEntry(trackedEntry);
}
this.trackedEntries.Clear();
}
@ -108,21 +149,21 @@ public class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
this.console.RemoveEntry(entry);
this.trackedEntries.Remove(entry);
}
private string GetPrefixedName(string name)
{
ArgumentNullException.ThrowIfNull(name);
// If the name is empty, return the prefix to allow for a single command or variable to be top-level.
if (name.Length == 0)
return this.Prefix;
if (name.Any(char.IsWhiteSpace))
throw new ArgumentException("Name cannot contain whitespace.", nameof(name));
return $"{this.Prefix}.{name}";
}
private IConsoleCommand InternalAddCommand(string name, string description, Delegate func)
{
var command = this.console.AddCommand(this.GetPrefixedName(name), description, func);
@ -130,44 +171,3 @@ public class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
return command;
}
}
/// <summary>
/// Utility functions for the console manager.
/// </summary>
internal static partial class ConsoleManagerPluginUtil
{
private static readonly string[] ReservedNamespaces = ["dalamud", "xl", "plugin"];
/// <summary>
/// Get a sanitized namespace name from a plugin's internal name.
/// </summary>
/// <param name="pluginInternalName">The plugin's internal name.</param>
/// <returns>A sanitized namespace.</returns>
public static string GetSanitizedNamespaceName(string pluginInternalName)
{
// Must be lowercase
pluginInternalName = pluginInternalName.ToLowerInvariant();
// Remove all non-alphabetic characters
pluginInternalName = NonAlphaRegex().Replace(pluginInternalName, string.Empty);
// Remove reserved namespaces from the start or end
foreach (var reservedNamespace in ReservedNamespaces)
{
if (pluginInternalName.StartsWith(reservedNamespace))
{
pluginInternalName = pluginInternalName[reservedNamespace.Length..];
}
if (pluginInternalName.EndsWith(reservedNamespace))
{
pluginInternalName = pluginInternalName[..^reservedNamespace.Length];
}
}
return pluginInternalName;
}
[GeneratedRegex(@"[^a-z]")]
private static partial Regex NonAlphaRegex();
}

View file

@ -9,13 +9,17 @@ using System.Threading.Tasks;
using Dalamud.Common;
using Dalamud.Configuration.Internal;
using Dalamud.Game;
using Dalamud.Hooking.Internal.Verification;
using Dalamud.Plugin.Internal;
using Dalamud.Storage;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
using PInvoke;
using Serilog;
using Windows.Win32.Foundation;
using Windows.Win32.Security;
#if DEBUG
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
#endif
@ -29,13 +33,13 @@ namespace Dalamud;
/// The main Dalamud class containing all subsystems.
/// </summary>
[ServiceManager.ProvidedService]
internal sealed class Dalamud : IServiceType
internal sealed unsafe class Dalamud : IServiceType
{
#region Internals
private static int shownServiceError = 0;
private readonly ManualResetEvent unloadSignal;
#endregion
/// <summary>
@ -48,15 +52,15 @@ internal sealed class Dalamud : IServiceType
public Dalamud(DalamudStartInfo info, ReliableFileStorage fs, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
{
this.StartInfo = info;
this.unloadSignal = new ManualResetEvent(false);
this.unloadSignal.Reset();
// Directory resolved signatures(CS, our own) will be cached in
var cacheDir = new DirectoryInfo(Path.Combine(this.StartInfo.WorkingDirectory!, "cachedSigs"));
if (!cacheDir.Exists)
cacheDir.Create();
// Set up the SigScanner for our target module
TargetSigScanner scanner;
using (Timings.Start("SigScanner Init"))
@ -71,26 +75,31 @@ internal sealed class Dalamud : IServiceType
configuration,
scanner,
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
using (Timings.Start("HookVerifier Init"))
{
HookVerifier.Initialize(scanner);
}
// Set up FFXIVClientStructs
this.SetupClientStructsResolver(cacheDir);
void KickoffGameThread()
{
Log.Verbose("=============== GAME THREAD KICKOFF ===============");
Timings.Event("Game thread kickoff");
NativeFunctions.SetEvent(mainThreadContinueEvent);
Windows.Win32.PInvoke.SetEvent(new HANDLE(mainThreadContinueEvent));
}
void HandleServiceInitFailure(Task t)
{
Log.Error(t.Exception!, "Service initialization failure");
if (Interlocked.CompareExchange(ref shownServiceError, 1, 0) != 0)
return;
Util.Fatal(
"Dalamud failed to load all necessary services.\n\nThe game will continue, but you may not be able to use plugins.",
$"Dalamud failed to load all necessary services.\nThe game will continue, but you may not be able to use plugins.\n\n{t.Exception}",
"Dalamud", false);
}
@ -116,15 +125,15 @@ internal sealed class Dalamud : IServiceType
HandleServiceInitFailure(t);
});
this.DefaultExceptionFilter = NativeFunctions.SetUnhandledExceptionFilter(nint.Zero);
NativeFunctions.SetUnhandledExceptionFilter(this.DefaultExceptionFilter);
Log.Debug($"SE default exception filter at {this.DefaultExceptionFilter.ToInt64():X}");
this.DefaultExceptionFilter = SetExceptionHandler(nint.Zero);
SetExceptionHandler(this.DefaultExceptionFilter);
Log.Debug($"SE default exception filter at {new IntPtr(this.DefaultExceptionFilter):X}");
var debugSig = "40 55 53 57 48 8D AC 24 70 AD FF FF";
this.DebugExceptionFilter = Service<TargetSigScanner>.Get().ScanText(debugSig);
Log.Debug($"SE debug exception filter at {this.DebugExceptionFilter.ToInt64():X}");
}
/// <summary>
/// Gets the start information for this Dalamud instance.
/// </summary>
@ -170,8 +179,9 @@ internal sealed class Dalamud : IServiceType
if (!reportCrashesSetting && !pmHasDevPlugins)
{
// Leaking on purpose for now
var attribs = Kernel32.SECURITY_ATTRIBUTES.Create();
Kernel32.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE");
var attribs = default(SECURITY_ATTRIBUTES);
attribs.nLength = (uint)Unsafe.SizeOf<SECURITY_ATTRIBUTES>();
Windows.Win32.PInvoke.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE");
}
this.unloadSignal.Set();
@ -188,28 +198,30 @@ internal sealed class Dalamud : IServiceType
/// <summary>
/// Replace the current exception handler with the default one.
/// </summary>
internal void UseDefaultExceptionHandler() =>
this.SetExceptionHandler(this.DefaultExceptionFilter);
internal void UseDefaultExceptionHandler() =>
SetExceptionHandler(this.DefaultExceptionFilter);
/// <summary>
/// Replace the current exception handler with a debug one.
/// </summary>
internal void UseDebugExceptionHandler() =>
this.SetExceptionHandler(this.DebugExceptionFilter);
SetExceptionHandler(this.DebugExceptionFilter);
/// <summary>
/// Disable the current exception handler.
/// </summary>
internal void UseNoExceptionHandler() =>
this.SetExceptionHandler(nint.Zero);
SetExceptionHandler(nint.Zero);
/// <summary>
/// Helper function to set the exception handler.
/// </summary>
private void SetExceptionHandler(nint newFilter)
private static nint SetExceptionHandler(nint newFilter)
{
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(newFilter);
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, oldFilter);
var oldFilter =
Windows.Win32.PInvoke.SetUnhandledExceptionFilter((delegate* unmanaged[Stdcall]<global::Windows.Win32.System.Diagnostics.Debug.EXCEPTION_POINTERS*, int>)newFilter);
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, (nint)oldFilter);
return (nint)oldFilter;
}
private void SetupClientStructsResolver(DirectoryInfo cacheDir)

View file

@ -1,16 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<TargetFramework>net8.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64;AnyCPU</Platforms>
<LangVersion>12.0</LangVersion>
<EnableWindowsTargeting>True</EnableWindowsTargeting>
</PropertyGroup>
<PropertyGroup Label="Feature">
<DalamudVersion>11.0.2.0</DalamudVersion>
<Description>XIV Launcher addon framework</Description>
<DalamudVersion>14.0.2.1</DalamudVersion>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version>
<FileVersion>$(DalamudVersion)</FileVersion>
@ -39,6 +35,10 @@
<Deterministic>true</Deterministic>
<Nullable>annotations</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Enable when cswin32 properly supports implementing COM interfaces and we can
make IDropTarget work -->
<!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> -->
</PropertyGroup>
<PropertyGroup Label="Configuration">
@ -47,10 +47,6 @@
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\</PathMap>
</PropertyGroup>
<PropertyGroup Label="Warnings">
<NoWarn>IDE0002;IDE0003;IDE1006;IDE0044;CA1822;CS1591;CS1701;CS1702</NoWarn>
@ -65,42 +61,59 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BitFaster.Caching" Version="2.4.1" />
<PackageReference Include="CheapLoc" Version="1.1.8" />
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.2.4" PrivateAssets="all" />
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageReference Include="Lumina" Version="5.6.0" />
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
<PackageReference Include="BitFaster.Caching" />
<PackageReference Include="CheapLoc" />
<PackageReference Include="DotNet.ReproducibleBuilds" PrivateAssets="all" />
<PackageReference Include="goatcorp.Reloaded.Hooks" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Lumina" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MinSharp" Version="1.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PackageReference Include="MinSharp" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.Async" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="sqlite-net-pcl" />
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.0" />
<PackageReference Include="System.Resources.Extensions" Version="8.0.0" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
<PackageReference Include="System.Reactive" />
<PackageReference Include="System.Reflection.MetadataLoadContext" />
<PackageReference Include="TerraFX.Interop.Windows" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
<ItemGroup>
<None Remove="EnumCloneMap.txt"/>
<AdditionalFiles Include="EnumCloneMap.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-frag.hlsl.bytes">
<LogicalName>imgui-frag.hlsl.bytes</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-vertex.hlsl.bytes">
<LogicalName>imgui-vertex.hlsl.bytes</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud.Common\Dalamud.Common.csproj" />
<ProjectReference Include="..\imgui\Dalamud.Bindings.ImGuizmo\Dalamud.Bindings.ImGuizmo.csproj" />
<ProjectReference Include="..\imgui\Dalamud.Bindings.ImGui\Dalamud.Bindings.ImGui.csproj" />
<ProjectReference Include="..\imgui\Dalamud.Bindings.ImPlot\Dalamud.Bindings.ImPlot.csproj" />
<ProjectReference Include="..\imgui\ImGuiScene\ImGuiScene.csproj" />
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
<ProjectReference Include="..\lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj" />
<ProjectReference Include="..\lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj" />
</ItemGroup>
<ItemGroup>
@ -112,6 +125,8 @@
<Content Include="licenses.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Remove="Interface\ImGuiBackend\Renderers\gaussian.hlsl" />
<None Remove="Interface\ImGuiBackend\Renderers\fullscreen-quad.hlsl.bytes" />
</ItemGroup>
<ItemGroup>
@ -121,6 +136,13 @@
<EmbeddedResource Include="Interface\ImGuiSeStringRenderer\Internal\TextProcessing\LineBreak.txt" LogicalName="LineBreak.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.ps.bin" />
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.DrawToPremul.vs.bin" />
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.ps.bin" />
<EmbeddedResource WithCulture="false" Include="Interface\Textures\TextureWraps\Internal\DrawListTextureWrap\Renderer.MakeStraight.vs.bin" />
</ItemGroup>
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
<ItemGroup>
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
@ -145,6 +167,9 @@
<Exec Command="git -C &quot;$(ProjectDir.Replace('\','\\'))&quot; describe --tags --always --dirty" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="DalamudGitDescribeOutput" />
</Exec>
<Exec Command="git -C &quot;$(ProjectDir.Replace('\','\\'))&quot; rev-parse --abbrev-ref HEAD" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="DalamudGitBranch" />
</Exec>
<Exec Command="git -C &quot;$(ProjectDir.Replace('\','\\'))\..\lib\FFXIVClientStructs&quot; describe --long --always --dirty" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="ClientStructsGitDescribeOutput" />
</Exec>
@ -152,6 +177,7 @@
<PropertyGroup>
<CommitCount>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitCommitCount), @"\t|\n|\r", ""))</CommitCount>
<CommitHash>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitCommitHash), @"\t|\n|\r", ""))</CommitHash>
<Branch>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitBranch), @"\t|\n|\r", ""))</Branch>
<SCMVersion>$([System.Text.RegularExpressions.Regex]::Replace($(DalamudGitDescribeOutput), @"\t|\n|\r", ""))</SCMVersion>
<CommitHashClientStructs>$([System.Text.RegularExpressions.Regex]::Replace($(ClientStructsGitDescribeOutput), @"\t|\n|\r", ""))</CommitHashClientStructs>
</PropertyGroup>
@ -165,6 +191,7 @@
<!-- stub out version since it takes a while. -->
<PropertyGroup>
<SCMVersion>Local build at $([System.DateTime]::Now.ToString(yyyy-MM-dd HH:mm:ss))</SCMVersion>
<Branch>???</Branch>
<CommitHashClientStructs>???</CommitHashClientStructs>
</PropertyGroup>
</Target>
@ -188,6 +215,10 @@
<_Parameter1>GitCommitCount</_Parameter1>
<_Parameter2>$(CommitCount)</_Parameter2>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyMetadata" Condition="'$(Branch)' != ''">
<_Parameter1>GitBranch</_Parameter1>
<_Parameter2>$(Branch)</_Parameter2>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyMetadata" Condition="'$(CommitHashClientStructs)' != ''">
<_Parameter1>GitHashClientStructs</_Parameter1>
<_Parameter2>$(CommitHashClientStructs)</_Parameter2>
@ -200,9 +231,4 @@
<!-- writes the attribute to the customAssemblyInfo file -->
<WriteCodeFragment Language="C#" OutputFile="$(CustomAssemblyInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
</Target>
<!-- Copy plugin .targets folder into distrib -->
<Target Name="CopyPluginTargets" AfterTargets="Build">
<Copy SourceFiles="$(ProjectDir)\..\targets\Dalamud.Plugin.targets;$(ProjectDir)\..\targets\Dalamud.Plugin.Bootstrap.targets" DestinationFolder="$(OutDir)\targets" />
</Target>
</Project>

View file

@ -1,3 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=interface_005Cfontawesome/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Int64 x:Key="/Default/PerformanceThreshold/AnalysisFileSizeThreshold/=CSHARP/@EntryIndexedValue">300000</s:Int64>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=interface_005Cfontawesome/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=interface_005Ctextures_005Ctexturewraps_005Cinternal_005Cdrawlisttexturewrap/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -73,7 +73,7 @@ public enum DalamudAsset
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
[DalamudAssetPath("UIRes", "troubleIcon.png")]
TroubleIcon = 1006,
/// <summary>
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
/// </summary>
@ -124,6 +124,13 @@ public enum DalamudAsset
[DalamudAssetPath("UIRes", "tsmShade.png")]
TitleScreenMenuShade = 1013,
/// <summary>
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: Atlas containing badges.
/// </summary>
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
[DalamudAssetPath("UIRes", "badgeAtlas.png")]
BadgeAtlas = 1015,
/// <summary>
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
/// </summary>
@ -151,7 +158,7 @@ public enum DalamudAsset
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
/// </summary>
[DalamudAsset(DalamudAssetPurpose.Font)]
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
[DalamudAssetPath("UIRes", "FontAwesome710FreeSolid.otf")]
FontAwesomeFreeSolid = 2003,
/// <summary>

View file

@ -8,11 +8,13 @@ using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
using Lumina;
using Lumina.Data;
using Lumina.Excel;
using Newtonsoft.Json;
using Serilog;
namespace Dalamud.Data;
@ -41,7 +43,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
try
{
Log.Verbose("Starting data load...");
using (Timings.Start("Lumina Init"))
{
var luminaOptions = new LuminaOptions
@ -53,12 +55,25 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
DefaultExcelLanguage = this.Language.ToLumina(),
};
this.GameData = new(
Path.Combine(Path.GetDirectoryName(Environment.ProcessPath)!, "sqpack"),
luminaOptions)
try
{
StreamPool = new(),
};
this.GameData = new(
Path.Combine(Path.GetDirectoryName(Environment.ProcessPath)!, "sqpack"),
luminaOptions)
{
StreamPool = new(),
};
}
catch (Exception ex)
{
Log.Error(ex, "Lumina GameData init failed");
Util.Fatal(
"Dalamud could not read required game data files. This likely means your game installation is corrupted or incomplete.\n\n" +
"Please repair your installation by right-clicking the login button in XIVLauncher and choosing \"Repair game files\".",
"Dalamud");
return;
}
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
@ -69,9 +84,14 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
var tsInfo =
JsonConvert.DeserializeObject<LauncherTroubleshootingInfo>(
dalamud.StartInfo.TroubleshootingPackData);
this.HasModifiedGameDataFiles =
tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed or LauncherTroubleshootingInfo.IndexIntegrityResult.Exception;
// Don't fail for IndexIntegrityResult.Exception, since the check during launch has a very small timeout
// this.HasModifiedGameDataFiles =
// tsInfo?.IndexIntegrity is LauncherTroubleshootingInfo.IndexIntegrityResult.Failed;
// TODO: Put above back when check in XL is fixed
this.HasModifiedGameDataFiles = false;
if (this.HasModifiedGameDataFiles)
Log.Verbose("Game data integrity check failed!\n{TsData}", dalamud.StartInfo.TroubleshootingPackData);
}
@ -130,7 +150,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
#region Lumina Wrappers
/// <inheritdoc/>
public ExcelSheet<T> GetExcelSheet<T>(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow<T>
public ExcelSheet<T> GetExcelSheet<T>(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow<T>
=> this.Excel.GetSheet<T>(language?.ToLumina(), name);
/// <inheritdoc/>
@ -138,7 +158,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
=> this.Excel.GetSubrowSheet<T>(language?.ToLumina(), name);
/// <inheritdoc/>
public FileResource? GetFile(string path)
public FileResource? GetFile(string path)
=> this.GetFile<FileResource>(path);
/// <inheritdoc/>
@ -161,7 +181,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
: Task.FromException<T>(new FileNotFoundException("The file could not be found."));
/// <inheritdoc/>
public bool FileExists(string path)
public bool FileExists(string path)
=> this.GameData.FileExists(path);
#endregion

View file

@ -3,7 +3,9 @@ using System.Collections.Generic;
using Dalamud.Hooking;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
using Lumina.Text.ReadOnly;
namespace Dalamud.Data;
@ -13,7 +15,7 @@ namespace Dalamud.Data;
/// </summary>
internal sealed unsafe class RsvResolver : IDisposable
{
private static readonly ModuleLog Log = new("RsvProvider");
private static readonly ModuleLog Log = ModuleLog.Create<RsvResolver>();
private readonly Hook<LayoutWorld.Delegates.AddRsvString> addRsvStringHook;

View file

@ -1,6 +1,6 @@
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@ -14,13 +14,15 @@ using Dalamud.Plugin.Internal;
using Dalamud.Storage;
using Dalamud.Support;
using Dalamud.Utility;
using Newtonsoft.Json;
using PInvoke;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using static Dalamud.NativeFunctions;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Dalamud;
@ -58,7 +60,7 @@ public sealed class EntryPoint
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr)!;
if ((info.BootWaitMessageBox & 4) != 0)
MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
Windows.Win32.PInvoke.MessageBox(HWND.Null, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MESSAGEBOX_STYLE.MB_OK);
new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
}
@ -135,13 +137,16 @@ public sealed class EntryPoint
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
{
NativeLibrary.Load(Path.Combine(info.WorkingDirectory!, "cimgui.dll"));
// Setup logger
InitLogging(info.LogPath!, info.BootShowConsole, true, info.LogName);
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
// Load configuration first to get some early persistent state, like log level
var fs = new ReliableFileStorage(Path.GetDirectoryName(info.ConfigurationPath)!);
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs);
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs)
.GetAwaiter().GetResult();
// Set the appropriate logging level from the configuration
if (!configuration.LogSynchronously)
@ -179,16 +184,17 @@ public sealed class EntryPoint
Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory);
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
// Apply common fixes for culture issues
CultureFixes.Apply();
if (!Util.IsWine())
// Currently VEH is not fully functional on WINE
if (info.Platform != OSPlatform.Windows)
InitSymbolHandler(info);
var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent);
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
Util.GetScmVersion(),
Util.GetGitHashClientStructs(),
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]",
Versioning.GetScmVersion(),
Versioning.GetGitHashClientStructs(),
FFXIVClientStructs.ThisAssembly.Git.Commits);
dalamud.WaitForUnload();
@ -258,10 +264,12 @@ public sealed class EntryPoint
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
var searchPath = $".;{symbolPath}";
// Remove any existing Symbol Handler and Init a new one with our search path added
SymCleanup(GetCurrentProcess());
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess();
if (!SymInitialize(GetCurrentProcess(), searchPath, true))
// Remove any existing Symbol Handler and Init a new one with our search path added
Windows.Win32.PInvoke.SymCleanup(currentProcess);
if (!Windows.Win32.PInvoke.SymInitialize(currentProcess, searchPath, true))
throw new Win32Exception();
}
catch (Exception ex)
@ -285,7 +293,6 @@ public sealed class EntryPoint
}
var pluginInfo = string.Empty;
var supportText = ", please visit us on Discord for more help";
try
{
var pm = Service<PluginManager>.GetNullable();
@ -293,9 +300,6 @@ public sealed class EntryPoint
if (plugin != null)
{
pluginInfo = $"Plugin that caused this:\n{plugin.Name}\n\nClick \"Yes\" and remove it.\n\n";
if (plugin.IsThirdParty)
supportText = string.Empty;
}
}
catch
@ -303,31 +307,18 @@ public sealed class EntryPoint
// ignored
}
const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
var result = MessageBoxW(
Process.GetCurrentProcess().MainWindowHandle,
$"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\n{ex.GetType().Name}\n{info}\n\n{pluginInfo}More information has been recorded separately{supportText}.\n\nDo you want to disable all plugins the next time you start the game?",
"Dalamud",
flags);
if (result == (int)User32.MessageBoxResult.IDYES)
{
Log.Information("User chose to disable plugins on next launch...");
var config = Service<DalamudConfiguration>.Get();
config.PluginSafeMode = true;
config.QueueSave();
}
Log.CloseAndFlush();
Environment.Exit(-1);
ErrorHandling.CrashWithContext($"{ex}\n\n{info}\n\n{pluginInfo}");
break;
default:
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject);
Log.CloseAndFlush();
Environment.Exit(-1);
break;
}
Environment.Exit(-1);
}
private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args)

3
Dalamud/EnumCloneMap.txt Normal file
View file

@ -0,0 +1,3 @@
# Format: Target.Full.TypeName = Source.Full.EnumTypeName
# Example: Generate a local enum MyGeneratedEnum in namespace Sample.Gen mapped to SourceEnums.SampleSourceEnum
Dalamud.Game.Agent.AgentId = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentId

View file

@ -0,0 +1,89 @@
namespace Dalamud.Game;
/// <summary>
/// Enum describing possible action kinds.
/// </summary>
public enum ActionKind
{
/// <summary>
/// A Trait.
/// </summary>
Trait = 0,
/// <summary>
/// An Action.
/// </summary>
Action = 1,
/// <summary>
/// A usable Item.
/// </summary>
Item = 2, // does not work?
/// <summary>
/// A usable EventItem.
/// </summary>
EventItem = 3, // does not work?
/// <summary>
/// An EventAction.
/// </summary>
EventAction = 4,
/// <summary>
/// A GeneralAction.
/// </summary>
GeneralAction = 5,
/// <summary>
/// A BuddyAction.
/// </summary>
BuddyAction = 6,
/// <summary>
/// A MainCommand.
/// </summary>
MainCommand = 7,
/// <summary>
/// A Companion.
/// </summary>
Companion = 8, // unresolved?!
/// <summary>
/// A CraftAction.
/// </summary>
CraftAction = 9,
/// <summary>
/// An Action (again).
/// </summary>
Action2 = 10, // what's the difference?
/// <summary>
/// A PetAction.
/// </summary>
PetAction = 11,
/// <summary>
/// A CompanyAction.
/// </summary>
CompanyAction = 12,
/// <summary>
/// A Mount.
/// </summary>
Mount = 13,
// 14-18 unused
/// <summary>
/// A BgcArmyAction.
/// </summary>
BgcArmyAction = 19,
/// <summary>
/// An Ornament.
/// </summary>
Ornament = 20,
}

View file

@ -1,107 +0,0 @@
using System.Runtime.CompilerServices;
using System.Threading;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
namespace Dalamud.Game.Addon;
/// <summary>Argument pool for Addon Lifecycle services.</summary>
[ServiceManager.EarlyLoadedService]
internal sealed class AddonLifecyclePooledArgs : IServiceType
{
private readonly AddonSetupArgs?[] addonSetupArgPool = new AddonSetupArgs?[64];
private readonly AddonFinalizeArgs?[] addonFinalizeArgPool = new AddonFinalizeArgs?[64];
private readonly AddonDrawArgs?[] addonDrawArgPool = new AddonDrawArgs?[64];
private readonly AddonUpdateArgs?[] addonUpdateArgPool = new AddonUpdateArgs?[64];
private readonly AddonRefreshArgs?[] addonRefreshArgPool = new AddonRefreshArgs?[64];
private readonly AddonRequestedUpdateArgs?[] addonRequestedUpdateArgPool = new AddonRequestedUpdateArgs?[64];
private readonly AddonReceiveEventArgs?[] addonReceiveEventArgPool = new AddonReceiveEventArgs?[64];
[ServiceManager.ServiceConstructor]
private AddonLifecyclePooledArgs()
{
}
/// <summary>Rents an instance of an argument.</summary>
/// <param name="arg">The rented instance.</param>
/// <returns>The returner.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PooledEntry<AddonSetupArgs> Rent(out AddonSetupArgs arg) => new(out arg, this.addonSetupArgPool);
/// <summary>Rents an instance of an argument.</summary>
/// <param name="arg">The rented instance.</param>
/// <returns>The returner.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PooledEntry<AddonFinalizeArgs> Rent(out AddonFinalizeArgs arg) => new(out arg, this.addonFinalizeArgPool);
/// <summary>Rents an instance of an argument.</summary>
/// <param name="arg">The rented instance.</param>
/// <returns>The returner.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PooledEntry<AddonDrawArgs> Rent(out AddonDrawArgs arg) => new(out arg, this.addonDrawArgPool);
/// <summary>Rents an instance of an argument.</summary>
/// <param name="arg">The rented instance.</param>
/// <returns>The returner.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PooledEntry<AddonUpdateArgs> Rent(out AddonUpdateArgs arg) => new(out arg, this.addonUpdateArgPool);
/// <summary>Rents an instance of an argument.</summary>
/// <param name="arg">The rented instance.</param>
/// <returns>The returner.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PooledEntry<AddonRefreshArgs> Rent(out AddonRefreshArgs arg) => new(out arg, this.addonRefreshArgPool);
/// <summary>Rents an instance of an argument.</summary>
/// <param name="arg">The rented instance.</param>
/// <returns>The returner.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PooledEntry<AddonRequestedUpdateArgs> Rent(out AddonRequestedUpdateArgs arg) =>
new(out arg, this.addonRequestedUpdateArgPool);
/// <summary>Rents an instance of an argument.</summary>
/// <param name="arg">The rented instance.</param>
/// <returns>The returner.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PooledEntry<AddonReceiveEventArgs> Rent(out AddonReceiveEventArgs arg) =>
new(out arg, this.addonReceiveEventArgPool);
/// <summary>Returns the object to the pool on dispose.</summary>
/// <typeparam name="T">The type.</typeparam>
public readonly ref struct PooledEntry<T>
where T : AddonArgs, new()
{
private readonly Span<T> pool;
private readonly T obj;
/// <summary>Initializes a new instance of the <see cref="PooledEntry{T}"/> struct.</summary>
/// <param name="arg">An instance of the argument.</param>
/// <param name="pool">The pool to rent from and return to.</param>
public PooledEntry(out T arg, Span<T> pool)
{
this.pool = pool;
foreach (ref var item in pool)
{
if (Interlocked.Exchange(ref item, null) is { } v)
{
this.obj = arg = v;
return;
}
}
this.obj = arg = new();
}
/// <summary>Returns the item to the pool.</summary>
public void Dispose()
{
var tmp = this.obj;
foreach (ref var item in this.pool)
{
if (Interlocked.Exchange(ref item, tmp) is not { } tmp2)
return;
tmp = tmp2;
}
}
}
}

View file

@ -1,5 +1,5 @@
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Events;
@ -14,9 +14,9 @@ internal unsafe class AddonEventEntry
/// Name of an invalid addon.
/// </summary>
public const string InvalidAddonName = "NullAddon";
private string? addonName;
/// <summary>
/// Gets the pointer to the addons AtkUnitBase.
/// </summary>
@ -33,20 +33,20 @@ internal unsafe class AddonEventEntry
public required nint Node { get; init; }
/// <summary>
/// Gets the handler that gets called when this event is triggered.
/// Gets the delegate that gets called when this event is triggered.
/// </summary>
public required IAddonEventManager.AddonEventHandler Handler { get; init; }
public required IAddonEventManager.AddonEventDelegate Delegate { get; init; }
/// <summary>
/// Gets the unique id for this event.
/// </summary>
public required uint ParamKey { get; init; }
/// <summary>
/// Gets the event type for this event.
/// </summary>
public required AddonEventType EventType { get; init; }
/// <summary>
/// Gets the event handle for this event.
/// </summary>

View file

@ -11,9 +11,9 @@ namespace Dalamud.Game.Addon.Events;
internal unsafe class AddonEventListener : IDisposable
{
private ReceiveEventDelegate? receiveEventDelegate;
private AtkEventListener* eventListener;
/// <summary>
/// Initializes a new instance of the <see cref="AddonEventListener"/> class.
/// </summary>
@ -24,7 +24,7 @@ internal unsafe class AddonEventListener : IDisposable
this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener));
this.eventListener->VirtualTable = (AtkEventListener.AtkEventListenerVirtualTable*)Marshal.AllocHGlobal(sizeof(void*) * 3);
this.eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, void>)(delegate* unmanaged<void>)&NullSub;
this.eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, AtkEventListener*>)(delegate* unmanaged<void>)&NullSub;
this.eventListener->VirtualTable->ReceiveGlobalEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)(delegate* unmanaged<void>)&NullSub;
this.eventListener->VirtualTable->ReceiveEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate);
}
@ -38,17 +38,17 @@ internal unsafe class AddonEventListener : IDisposable
/// <param name="eventPtr">Pointer to the AtkEvent.</param>
/// <param name="eventDataPtr">Pointer to the AtkEventData.</param>
public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr);
/// <summary>
/// Gets the address of this listener.
/// </summary>
public nint Address => (nint)this.eventListener;
/// <inheritdoc />
public void Dispose()
{
if (this.eventListener is null) return;
Marshal.FreeHGlobal((nint)this.eventListener->VirtualTable);
Marshal.FreeHGlobal((nint)this.eventListener);
@ -88,7 +88,7 @@ internal unsafe class AddonEventListener : IDisposable
node->RemoveEvent(eventType, param, this.eventListener, false);
});
}
[UnmanagedCallersOnly]
private static void NullSub()
{

View file

@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
@ -9,7 +9,6 @@ using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Events;
@ -24,33 +23,29 @@ internal unsafe class AddonEventManager : IInternalDisposableService
/// PluginName for Dalamud Internal use.
/// </summary>
public static readonly Guid DalamudInternalKey = Guid.NewGuid();
private static readonly ModuleLog Log = new("AddonEventManager");
private static readonly ModuleLog Log = ModuleLog.Create<AddonEventManager>();
[ServiceManager.ServiceDependency]
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
private readonly AddonLifecycleEventListener finalizeEventListener;
private readonly AddonEventManagerAddressResolver address;
private readonly Hook<UpdateCursorDelegate> onUpdateCursor;
private readonly Hook<AtkUnitManager.Delegates.UpdateCursor> onUpdateCursor;
private readonly ConcurrentDictionary<Guid, PluginEventController> pluginEventControllers;
private AddonCursorType? cursorOverride;
[ServiceManager.ServiceConstructor]
private AddonEventManager(TargetSigScanner sigScanner)
{
this.address = new AddonEventManagerAddressResolver();
this.address.Setup(sigScanner);
private AtkCursor.CursorType? cursorOverride;
[ServiceManager.ServiceConstructor]
private AddonEventManager()
{
this.pluginEventControllers = new ConcurrentDictionary<Guid, PluginEventController>();
this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController());
this.cursorOverride = null;
this.onUpdateCursor = Hook<UpdateCursorDelegate>.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour);
this.onUpdateCursor = Hook<AtkUnitManager.Delegates.UpdateCursor>.FromAddress(AtkUnitManager.Addresses.UpdateCursor.Value, this.UpdateCursorDetour);
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
@ -58,8 +53,6 @@ internal unsafe class AddonEventManager : IInternalDisposableService
this.onUpdateCursor.Enable();
}
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
@ -69,7 +62,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
{
pluginEventController.Dispose();
}
this.addonLifecycle.UnregisterListener(this.finalizeEventListener);
}
@ -80,19 +73,19 @@ internal unsafe class AddonEventManager : IInternalDisposableService
/// <param name="atkUnitBase">The parent addon for this event.</param>
/// <param name="atkResNode">The node that will trigger this event.</param>
/// <param name="eventType">The event type for this event.</param>
/// <param name="eventHandler">The handler to call when event is triggered.</param>
/// <param name="eventDelegate">The delegate to call when event is triggered.</param>
/// <returns>IAddonEventHandle used to remove the event.</returns>
internal IAddonEventHandle? AddEvent(Guid pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
internal IAddonEventHandle? AddEvent(Guid pluginId, nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate)
{
if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
{
return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventDelegate);
}
else
{
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
}
return null;
}
@ -112,12 +105,12 @@ internal unsafe class AddonEventManager : IInternalDisposableService
Log.Verbose($"Unable to locate controller for {pluginId}. No event was removed.");
}
}
/// <summary>
/// Force the game cursor to be the specified cursor.
/// </summary>
/// <param name="cursor">Which cursor to use.</param>
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor;
internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor;
/// <summary>
/// Un-forces the game cursor.
@ -167,22 +160,23 @@ internal unsafe class AddonEventManager : IInternalDisposableService
pluginList.Value.RemoveForAddon(addonInfo.AddonName);
}
}
private nint UpdateCursorDetour(RaptureAtkModule* module)
private void UpdateCursorDetour(AtkUnitManager* thisPtr)
{
try
{
var atkStage = AtkStage.Instance();
if (this.cursorOverride is not null && atkStage is not null)
{
var cursor = (AddonCursorType)atkStage->AtkCursor.Type;
if (cursor != this.cursorOverride)
ref var atkCursor = ref atkStage->AtkCursor;
if (atkCursor.Type != this.cursorOverride)
{
AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1);
}
return nint.Zero;
return;
}
}
catch (Exception e)
@ -190,7 +184,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
Log.Error(e, "Exception in UpdateCursorDetour.");
}
return this.onUpdateCursor!.Original(module);
this.onUpdateCursor!.Original(thisPtr);
}
}
@ -218,7 +212,7 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo
public AddonEventManagerPluginScoped(LocalPlugin plugin)
{
this.plugin = plugin;
this.eventManagerService.AddPluginEventController(plugin.EffectiveWorkingPluginId);
}
@ -230,31 +224,34 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo
{
this.eventManagerService.ResetCursor();
}
this.eventManagerService.RemovePluginEventController(this.plugin.EffectiveWorkingPluginId);
Service<Framework>.Get().RunOnFrameworkThread(() =>
{
this.eventManagerService.RemovePluginEventController(this.plugin.EffectiveWorkingPluginId);
}).Wait();
}
/// <inheritdoc/>
public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
=> this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler);
public IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate)
=> this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventDelegate);
/// <inheritdoc/>
public void RemoveEvent(IAddonEventHandle eventHandle)
=> this.eventManagerService.RemoveEvent(this.plugin.EffectiveWorkingPluginId, eventHandle);
/// <inheritdoc/>
public void SetCursor(AddonCursorType cursor)
{
this.isForcingCursor = true;
this.eventManagerService.SetCursor(cursor);
}
/// <inheritdoc/>
public void ResetCursor()
{
this.isForcingCursor = false;
this.eventManagerService.ResetCursor();
}
}

View file

@ -1,21 +0,0 @@
namespace Dalamud.Game.Addon.Events;
/// <summary>
/// AddonEventManager memory address resolver.
/// </summary>
internal class AddonEventManagerAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the AtkModule UpdateCursor method.
/// </summary>
public nint UpdateCursor { get; private set; }
/// <summary>
/// Scan for and setup any configured address pointers.
/// </summary>
/// <param name="scanner">The signature scanner to facilitate setup.</param>
protected override void Setup64Bit(ISigScanner scanner)
{
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS
}
}

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.Addon.Events;
namespace Dalamud.Game.Addon.Events;
/// <summary>
/// Reimplementation of AtkEventType.
@ -9,150 +9,301 @@ public enum AddonEventType : byte
/// Mouse Down.
/// </summary>
MouseDown = 3,
/// <summary>
/// Mouse Up.
/// </summary>
MouseUp = 4,
/// <summary>
/// Mouse Move.
/// </summary>
MouseMove = 5,
/// <summary>
/// Mouse Over.
/// </summary>
MouseOver = 6,
/// <summary>
/// Mouse Out.
/// </summary>
MouseOut = 7,
/// <summary>
/// Mouse Wheel.
/// </summary>
MouseWheel = 8,
/// <summary>
/// Mouse Click.
/// </summary>
MouseClick = 9,
/// <summary>
/// Mouse Double Click.
/// </summary>
MouseDoubleClick = 10,
/// <summary>
/// Input Received.
/// </summary>
InputReceived = 12,
/// <summary>
/// Input Navigation (LEFT, RIGHT, UP, DOWN, TAB_NEXT, TAB_PREV, TAB_BOTH_NEXT, TAB_BOTH_PREV, PAGEUP, PAGEDOWN).
/// </summary>
InputNavigation = 13,
/// <summary>
/// InputBase Input Received (AtkComponentTextInput and AtkComponentNumericInput).<br/>
/// For example, this is fired for moving the text cursor, deletion of a character and inserting a new line.
/// </summary>
InputBaseInputReceived = 15,
/// <summary>
/// Fired at the very beginning of AtkInputManager.HandleInput on AtkStage.ViewportEventManager. Used in LovmMiniMap.
/// </summary>
RawInputData = 16,
/// <summary>
/// Focus Start.
/// </summary>
FocusStart = 18,
/// <summary>
/// Focus Stop.
/// </summary>
FocusStop = 19,
/// <summary>
/// Button Press, sent on MouseDown on Button.
/// Resize (ChatLogPanel).
/// </summary>
Resize = 21,
/// <summary>
/// AtkComponentButton Press, sent on MouseDown on Button.
/// </summary>
ButtonPress = 23,
/// <summary>
/// Button Release, sent on MouseUp and MouseOut.
/// AtkComponentButton Release, sent on MouseUp and MouseOut.
/// </summary>
ButtonRelease = 24,
/// <summary>
/// Button Click, sent on MouseUp and MouseClick on button.
/// AtkComponentButton Click, sent on MouseUp and MouseClick on button.
/// </summary>
ButtonClick = 25,
/// <summary>
/// List Item RollOver.
/// Value Update (NumericInput, ScrollBar, etc.)
/// </summary>
ValueUpdate = 27,
/// <summary>
/// AtkComponentSlider Value Update.
/// </summary>
SliderValueUpdate = 29,
/// <summary>
/// AtkComponentSlider Released.
/// </summary>
SliderReleased = 30,
/// <summary>
/// AtkComponentList Button Press.
/// </summary>
ListButtonPress = 31,
/// <summary>
/// AtkComponentList Roll Over.
/// </summary>
ListItemRollOver = 33,
/// <summary>
/// List Item Roll Out.
/// AtkComponentList Roll Out.
/// </summary>
ListItemRollOut = 34,
/// <summary>
/// List Item Toggle.
/// AtkComponentList Click.
/// </summary>
ListItemToggle = 35,
ListItemClick = 35,
/// <summary>
/// Drag Drop Begin.
/// AtkComponentList Double Click.
/// </summary>
ListItemDoubleClick = 36,
/// <summary>
/// AtkComponentList Highlight.
/// </summary>
ListItemHighlight = 37,
/// <summary>
/// AtkComponentList Select.
/// </summary>
ListItemSelect = 38,
/// <summary>
/// AtkComponentList Pad Drag Drop Begin.
/// </summary>
ListItemPadDragDropBegin = 40,
/// <summary>
/// AtkComponentList Pad Drag Drop End.
/// </summary>
ListItemPadDragDropEnd = 41,
/// <summary>
/// AtkComponentList Pad Drag Drop Insert.
/// </summary>
ListItemPadDragDropInsert = 42,
/// <summary>
/// AtkComponentDragDrop Begin.
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
/// </summary>
DragDropBegin = 47,
DragDropBegin = 50,
/// <summary>
/// Drag Drop Insert.
/// AtkComponentDragDrop End.
/// </summary>
DragDropEnd = 51,
/// <summary>
/// AtkComponentDragDrop Insert Attempt.
/// </summary>
DragDropInsertAttempt = 52,
/// <summary>
/// AtkComponentDragDrop Insert.
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
/// </summary>
DragDropInsert = 50,
DragDropInsert = 53,
/// <summary>
/// Drag Drop Roll Over.
/// AtkComponentDragDrop Can Accept Check.
/// </summary>
DragDropRollOver = 52,
DragDropCanAcceptCheck = 54,
/// <summary>
/// Drag Drop Roll Out.
/// AtkComponentDragDrop Roll Over.
/// </summary>
DragDropRollOut = 53,
DragDropRollOver = 55,
/// <summary>
/// Drag Drop Discard.
/// AtkComponentDragDrop Roll Out.
/// </summary>
DragDropRollOut = 56,
/// <summary>
/// AtkComponentDragDrop Discard.
/// Sent when dropping an icon into empty screenspace, eg to remove an action from a hotBar.
/// </summary>
DragDropDiscard = 54,
DragDropDiscard = 57,
/// <summary>
/// Drag Drop Unknown.
/// </summary>
[Obsolete("Use DragDropDiscard")]
DragDropUnk54 = 54,
/// <summary>
/// Drag Drop Cancel.
/// AtkComponentDragDrop Click.
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
/// </summary>
DragDropCancel = 55,
DragDropClick = 58,
/// <summary>
/// Drag Drop Unknown.
/// AtkComponentDragDrop Cancel.
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
/// </summary>
[Obsolete("Use DragDropCancel")]
DragDropUnk55 = 55,
[Obsolete("Renamed to DragDropClick")]
DragDropCancel = 58,
/// <summary>
/// Icon Text Roll Over.
/// AtkComponentIconText Roll Over.
/// </summary>
IconTextRollOver = 56,
IconTextRollOver = 59,
/// <summary>
/// Icon Text Roll Out.
/// AtkComponentIconText Roll Out.
/// </summary>
IconTextRollOut = 57,
IconTextRollOut = 60,
/// <summary>
/// Icon Text Click.
/// AtkComponentIconText Click.
/// </summary>
IconTextClick = 58,
IconTextClick = 61,
/// <summary>
/// Window Roll Over.
/// AtkDialogue Close.
/// </summary>
WindowRollOver = 67,
DialogueClose = 62,
/// <summary>
/// Window Roll Out.
/// AtkDialogue Submit.
/// </summary>
WindowRollOut = 68,
DialogueSubmit = 63,
/// <summary>
/// Window Change Scale.
/// AtkTimer Tick.
/// </summary>
WindowChangeScale = 69,
TimerTick = 64,
/// <summary>
/// AtkTimer End.
/// </summary>
TimerEnd = 65,
/// <summary>
/// AtkTimer Start.
/// </summary>
TimerStart = 66,
/// <summary>
/// AtkSimpleTween Progress.
/// </summary>
TweenProgress = 67,
/// <summary>
/// AtkSimpleTween Complete.
/// </summary>
TweenComplete = 68,
/// <summary>
/// AtkAddonControl Child Addon Attached.
/// </summary>
ChildAddonAttached = 69,
/// <summary>
/// AtkComponentWindow Roll Over.
/// </summary>
WindowRollOver = 70,
/// <summary>
/// AtkComponentWindow Roll Out.
/// </summary>
WindowRollOut = 71,
/// <summary>
/// AtkComponentWindow Change Scale.
/// </summary>
WindowChangeScale = 72,
/// <summary>
/// AtkTimeline Active Label Changed.
/// </summary>
TimelineActiveLabelChanged = 75,
/// <summary>
/// AtkTextNode Link Mouse Click.
/// </summary>
LinkMouseClick = 75,
/// <summary>
/// AtkTextNode Link Mouse Over.
/// </summary>
LinkMouseOver = 76,
/// <summary>
/// AtkTextNode Link Mouse Out.
/// </summary>
LinkMouseOut = 77,
}

View file

@ -0,0 +1,68 @@
namespace Dalamud.Game.Addon.Events.EventDataTypes;
/// <summary>
/// Object representing data that is relevant in handling native events.
/// </summary>
public class AddonEventData
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonEventData"/> class.
/// </summary>
internal AddonEventData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AddonEventData"/> class.
/// </summary>
/// <param name="eventData">Other event data to copy.</param>
internal AddonEventData(AddonEventData eventData)
{
this.AtkEventType = eventData.AtkEventType;
this.Param = eventData.Param;
this.AtkEventPointer = eventData.AtkEventPointer;
this.AtkEventDataPointer = eventData.AtkEventDataPointer;
this.AddonPointer = eventData.AddonPointer;
this.NodeTargetPointer = eventData.NodeTargetPointer;
this.AtkEventListener = eventData.AtkEventListener;
}
/// <summary>
/// Gets the AtkEventType for this event.
/// </summary>
public AddonEventType AtkEventType { get; internal set; }
/// <summary>
/// Gets the param field for this event.
/// </summary>
public uint Param { get; internal set; }
/// <summary>
/// Gets the pointer to the AtkEvent object for this event.
/// </summary>
/// <remarks>Note: This is not a pointer to the AtkEventData object.<br/><br/>
/// Warning: AtkEvent->Node has been modified to be the AtkUnitBase*, and AtkEvent->Target has been modified to be the AtkResNode* that triggered this event.</remarks>
public nint AtkEventPointer { get; internal set; }
/// <summary>
/// Gets the pointer to the AtkEventData object for this event.
/// </summary>
/// <remarks>This field will contain relevant data such as left vs right click, scroll up vs scroll down.</remarks>
public nint AtkEventDataPointer { get; internal set; }
/// <summary>
/// Gets the pointer to the AtkUnitBase that is handling this event.
/// </summary>
public nint AddonPointer { get; internal set; }
/// <summary>
/// Gets the pointer to the AtkResNode that triggered this event.
/// </summary>
public nint NodeTargetPointer { get; internal set; }
/// <summary>
/// Gets or sets a pointer to the AtkEventListener responsible for handling this event.
/// Note: As the event listener is dalamud allocated, there's no reason to expose this field.
/// </summary>
internal nint AtkEventListener { get; set; }
}

View file

@ -0,0 +1,82 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using AtkMouseData = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData;
using ModifierFlag = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData.ModifierFlag;
namespace Dalamud.Game.Addon.Events.EventDataTypes;
/// <inheritdoc />
public unsafe class AddonMouseEventData : AddonEventData
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonMouseEventData"/> class.
/// </summary>
internal AddonMouseEventData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AddonMouseEventData"/> class.
/// </summary>
/// <param name="eventData">Other event data to copy.</param>
internal AddonMouseEventData(AddonEventData eventData)
: base(eventData)
{
}
/// <summary>
/// Gets a value indicating whether the event was a Left Mouse Click.
/// </summary>
public bool IsLeftClick => this.MouseData.ButtonId is 0;
/// <summary>
/// Gets a value indicating whether the event was a Right Mouse Click.
/// </summary>
public bool IsRightClick => this.MouseData.ButtonId is 1;
/// <summary>
/// Gets a value indicating whether there are any modifiers set such as alt, control, shift, or dragging.
/// </summary>
public bool IsNoModifier => this.MouseData.Modifier is 0;
/// <summary>
/// Gets a value indicating whether alt was being held when this event triggered.
/// </summary>
public bool IsAltHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Alt);
/// <summary>
/// Gets a value indicating whether control was being held when this event triggered.
/// </summary>
public bool IsControlHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Ctrl);
/// <summary>
/// Gets a value indicating whether shift was being held when this event triggered.
/// </summary>
public bool IsShiftHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Shift);
/// <summary>
/// Gets a value indicating whether this event is a mouse drag or not.
/// </summary>
public bool IsDragging => this.MouseData.Modifier.HasFlag(ModifierFlag.Dragging);
/// <summary>
/// Gets a value indicating whether the event was a scroll up.
/// </summary>
public bool IsScrollUp => this.MouseData.WheelDirection is 1;
/// <summary>
/// Gets a value indicating whether the event was a scroll down.
/// </summary>
public bool IsScrollDown => this.MouseData.WheelDirection is -1;
/// <summary>
/// Gets the position of the mouse when this event was triggered.
/// </summary>
public Vector2 Position => new(this.MouseData.PosX, this.MouseData.PosY);
private AtkEventData* AtkEventData => (AtkEventData*)this.AtkEventDataPointer;
private AtkMouseData MouseData => this.AtkEventData->MouseData;
}

View file

@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Addon.Events.EventDataTypes;
using Dalamud.Game.Gui;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
@ -15,7 +15,7 @@ namespace Dalamud.Game.Addon.Events;
/// </summary>
internal unsafe class PluginEventController : IDisposable
{
private static readonly ModuleLog Log = new("AddonEventManager");
private static readonly ModuleLog Log = ModuleLog.Create<AddonEventManager>();
/// <summary>
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
@ -26,8 +26,8 @@ internal unsafe class PluginEventController : IDisposable
}
private AddonEventListener EventListener { get; init; }
private List<AddonEventEntry> Events { get; } = new();
private List<AddonEventEntry> Events { get; } = [];
/// <summary>
/// Adds a tracked event.
@ -35,16 +35,16 @@ internal unsafe class PluginEventController : IDisposable
/// <param name="atkUnitBase">The Parent addon for the event.</param>
/// <param name="atkResNode">The Node for the event.</param>
/// <param name="atkEventType">The Event Type.</param>
/// <param name="handler">The delegate to call when invoking this event.</param>
/// <param name="eventDelegate">The delegate to call when invoking this event.</param>
/// <returns>IAddonEventHandle used to remove the event.</returns>
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventDelegate eventDelegate)
{
var node = (AtkResNode*)atkResNode;
var addon = (AtkUnitBase*)atkUnitBase;
var eventType = (AtkEventType)atkEventType;
var eventId = this.GetNextParamKey();
var eventGuid = Guid.NewGuid();
var eventHandle = new AddonEventHandle
{
AddonName = addon->NameString,
@ -52,11 +52,11 @@ internal unsafe class PluginEventController : IDisposable
EventType = atkEventType,
EventGuid = eventGuid,
};
var eventEntry = new AddonEventEntry
{
Addon = atkUnitBase,
Handler = handler,
Delegate = eventDelegate,
Node = atkResNode,
EventType = atkEventType,
ParamKey = eventId,
@ -92,14 +92,14 @@ internal unsafe class PluginEventController : IDisposable
if (this.Events.Where(entry => entry.AddonName == addonName).ToList() is { Count: not 0 } events)
{
Log.Verbose($"Addon: {addonName} is Finalizing, removing {events.Count} events.");
foreach (var registeredEvent in events)
{
this.RemoveEvent(registeredEvent.Handle);
}
}
}
/// <inheritdoc/>
public void Dispose()
{
@ -107,7 +107,7 @@ internal unsafe class PluginEventController : IDisposable
{
this.RemoveEvent(registeredEvent.Handle);
}
this.EventListener.Dispose();
}
@ -120,7 +120,7 @@ internal unsafe class PluginEventController : IDisposable
throw new OverflowException($"uint.MaxValue number of ParamKeys used for this event controller.");
}
/// <summary>
/// Attempts to remove a tracked event from native UI.
/// This method performs several safety checks to only remove events from a still active addon.
@ -139,20 +139,22 @@ internal unsafe class PluginEventController : IDisposable
// Is our stored addon pointer the same as the active addon pointer?
if (currentAddonPointer != eventEntry.Addon) return;
// Make sure the addon is not unloaded
var atkUnitBase = currentAddonPointer.Struct;
if (atkUnitBase->UldManager.LoadedState == AtkLoadState.Unloaded) return;
// Does this addon contain the node this event is for? (by address)
var atkUnitBase = (AtkUnitBase*)currentAddonPointer;
var nodeFound = false;
foreach (var index in Enumerable.Range(0, atkUnitBase->UldManager.NodeListCount))
foreach (var node in atkUnitBase->UldManager.Nodes)
{
var node = atkUnitBase->UldManager.NodeList[index];
// If this node matches our node, then we know our node is still valid.
if (node is not null && (nint)node == eventEntry.Node)
if ((nint)node.Value == eventEntry.Node)
{
nodeFound = true;
break;
}
}
// If we didn't find the node, we can't remove the event.
if (!nodeFound) return;
@ -166,33 +168,41 @@ internal unsafe class PluginEventController : IDisposable
var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey;
var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address;
var eventTypeMatches = currentEvent->State.EventType == eventType;
if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches)
{
eventFound = true;
break;
}
// Move to the next event.
currentEvent = currentEvent->NextEvent;
}
// If we didn't find the event, we can't remove the event.
if (!eventFound) return;
// We have a valid addon, valid node, valid event, and valid key.
this.EventListener.UnregisterEvent(atkResNode, eventType, eventEntry.ParamKey);
}
private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr)
{
try
{
if (eventPtr is null) return;
if (this.Events.FirstOrDefault(handler => handler.ParamKey == eventParam) is not { } eventInfo) return;
// We stored the AtkUnitBase* in EventData->Node, and EventData->Target contains the node that triggered the event.
eventInfo.Handler.Invoke((AddonEventType)eventType, (nint)eventPtr->Node, (nint)eventPtr->Target);
eventInfo.Delegate.Invoke((AddonEventType)eventType, new AddonEventData
{
AddonPointer = (nint)eventPtr->Node,
NodeTargetPointer = (nint)eventPtr->Target,
AtkEventDataPointer = (nint)eventDataPtr,
AtkEventListener = (nint)self,
AtkEventType = (AddonEventType)eventType,
Param = eventParam,
AtkEventPointer = (nint)eventPtr,
});
}
catch (Exception exception)
{

View file

@ -1,92 +1,46 @@
using System.Runtime.CompilerServices;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Game.NativeWrapper;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Base class for AddonLifecycle AddonArgTypes.
/// </summary>
public abstract unsafe class AddonArgs
public class AddonArgs
{
/// <summary>
/// Constant string representing the name of an addon that is invalid.
/// </summary>
public const string InvalidAddon = "NullAddon";
private string? addonName;
private IntPtr addon;
/// <summary>
/// Initializes a new instance of the <see cref="AddonArgs"/> class.
/// </summary>
internal AddonArgs()
{
}
/// <summary>
/// Gets the name of the addon this args referrers to.
/// </summary>
public string AddonName => this.GetAddonName();
public string AddonName { get; private set; } = InvalidAddon;
/// <summary>
/// Gets the pointer to the addons AtkUnitBase.
/// </summary>
public nint Addon
public AtkUnitBasePtr Addon
{
get => this.AddonInternal;
init => this.AddonInternal = value;
get;
internal set
{
field = value;
if (!this.Addon.IsNull && !string.IsNullOrEmpty(value.Name))
this.AddonName = value.Name;
}
}
/// <summary>
/// Gets the type of these args.
/// </summary>
public abstract AddonArgsType Type { get; }
/// <summary>
/// Gets or sets the pointer to the addons AtkUnitBase.
/// </summary>
internal nint AddonInternal
{
get => this.addon;
set
{
this.addon = value;
// Note: always clear addonName on updating the addon being pointed.
// Same address may point to a different addon.
this.addonName = null;
}
}
/// <summary>
/// Checks if addon name matches the given span of char.
/// </summary>
/// <param name="name">The name to check.</param>
/// <returns>Whether it is the case.</returns>
internal bool IsAddon(ReadOnlySpan<char> name)
{
if (this.Addon == nint.Zero) return false;
if (name.Length is 0 or > 0x20)
return false;
var addonPointer = (AtkUnitBase*)this.Addon;
if (addonPointer->Name[0] == 0) return false;
// note: might want to rewrite this to just compare to NameString
return MemoryHelper.EqualsZeroTerminatedString(
name,
(nint)Unsafe.AsPointer(ref addonPointer->Name[0]),
null,
0x20);
}
/// <summary>
/// Helper method for ensuring the name of the addon is valid.
/// </summary>
/// <returns>The name of the addon for this object. <see cref="InvalidAddon"/> when invalid.</returns>
private string GetAddonName()
{
if (this.Addon == nint.Zero) return InvalidAddon;
var addonPointer = (AtkUnitBase*)this.Addon;
if (addonPointer->Name[0] == 0) return InvalidAddon;
return this.addonName ??= addonPointer->NameString;
}
public virtual AddonArgsType Type => AddonArgsType.Generic;
}

View file

@ -0,0 +1,22 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for Close events.
/// </summary>
public class AddonCloseArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonCloseArgs"/> class.
/// </summary>
internal AddonCloseArgs()
{
}
/// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Close;
/// <summary>
/// Gets or sets a value indicating whether the window should fire the callback method on close.
/// </summary>
public bool FireCallback { get; set; }
}

View file

@ -1,24 +0,0 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for Draw events.
/// </summary>
public class AddonDrawArgs : AddonArgs, ICloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonDrawArgs"/> class.
/// </summary>
[Obsolete("Not intended for public construction.", false)]
public AddonDrawArgs()
{
}
/// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Draw;
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
}

View file

@ -1,24 +0,0 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for ReceiveEvent events.
/// </summary>
public class AddonFinalizeArgs : AddonArgs, ICloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonFinalizeArgs"/> class.
/// </summary>
[Obsolete("Not intended for public construction.", false)]
public AddonFinalizeArgs()
{
}
/// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Finalize;
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
}

View file

@ -0,0 +1,22 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for OnFocusChanged events.
/// </summary>
public class AddonFocusChangedArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonFocusChangedArgs"/> class.
/// </summary>
internal AddonFocusChangedArgs()
{
}
/// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.FocusChanged;
/// <summary>
/// Gets or sets a value indicating whether the window is being focused or unfocused.
/// </summary>
public bool ShouldFocus { get; set; }
}

View file

@ -0,0 +1,32 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for Hide events.
/// </summary>
public class AddonHideArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonHideArgs"/> class.
/// </summary>
internal AddonHideArgs()
{
}
/// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Hide;
/// <summary>
/// Gets or sets a value indicating whether to call the hide callback handler when this hides.
/// </summary>
public bool CallHideCallback { get; set; }
/// <summary>
/// Gets or sets the flags that the window will set when it Shows/Hides.
/// </summary>
public uint SetShowHideFlags { get; set; }
/// <summary>
/// Gets or sets a value indicating whether something for this event message.
/// </summary>
internal bool UnknownBool { get; set; }
}

View file

@ -3,13 +3,12 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for ReceiveEvent events.
/// </summary>
public class AddonReceiveEventArgs : AddonArgs, ICloneable
public class AddonReceiveEventArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonReceiveEventArgs"/> class.
/// </summary>
[Obsolete("Not intended for public construction.", false)]
public AddonReceiveEventArgs()
internal AddonReceiveEventArgs()
{
}
@ -32,13 +31,7 @@ public class AddonReceiveEventArgs : AddonArgs, ICloneable
public nint AtkEvent { get; set; }
/// <summary>
/// Gets or sets the pointer to a block of data for this event message.
/// Gets or sets the pointer to an AtkEventData for this event message.
/// </summary>
public nint Data { get; set; }
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
public nint AtkEventData { get; set; }
}

View file

@ -1,17 +1,22 @@
using System.Collections.Generic;
using Dalamud.Game.NativeWrapper;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
using FFXIVClientStructs.Interop;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for Refresh events.
/// </summary>
public class AddonRefreshArgs : AddonArgs, ICloneable
public class AddonRefreshArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonRefreshArgs"/> class.
/// </summary>
[Obsolete("Not intended for public construction.", false)]
public AddonRefreshArgs()
internal AddonRefreshArgs()
{
}
@ -31,11 +36,32 @@ public class AddonRefreshArgs : AddonArgs, ICloneable
/// <summary>
/// Gets the AtkValues in the form of a span.
/// </summary>
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
[Api15ToDo("Make this internal, remove obsolete")]
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
/// <summary>
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
/// </summary>
/// <returns>
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
/// </returns>
public IEnumerable<AtkValuePtr> AtkValueEnumerable
{
get
{
for (var i = 0; i < this.AtkValueCount; i++)
{
AtkValuePtr ptr;
unsafe
{
#pragma warning disable CS0618 // Type or member is obsolete
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
#pragma warning restore CS0618 // Type or member is obsolete
}
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
yield return ptr;
}
}
}
}

View file

@ -1,15 +1,14 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for OnRequestedUpdate events.
/// </summary>
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
public class AddonRequestedUpdateArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonRequestedUpdateArgs"/> class.
/// </summary>
[Obsolete("Not intended for public construction.", false)]
public AddonRequestedUpdateArgs()
internal AddonRequestedUpdateArgs()
{
}
@ -25,10 +24,4 @@ public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
/// Gets or sets the StringArrayData** for this event.
/// </summary>
public nint StringArrayData { get; set; }
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
}

View file

@ -1,17 +1,22 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using System.Collections.Generic;
using Dalamud.Game.NativeWrapper;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
using FFXIVClientStructs.Interop;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for Setup events.
/// </summary>
public class AddonSetupArgs : AddonArgs, ICloneable
public class AddonSetupArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonSetupArgs"/> class.
/// </summary>
[Obsolete("Not intended for public construction.", false)]
public AddonSetupArgs()
internal AddonSetupArgs()
{
}
@ -31,11 +36,32 @@ public class AddonSetupArgs : AddonArgs, ICloneable
/// <summary>
/// Gets the AtkValues in the form of a span.
/// </summary>
[Obsolete("Pending removal, Use AtkValueEnumerable instead.")]
[Api15ToDo("Make this internal, remove obsolete")]
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
/// <summary>
/// Gets an enumerable collection of <see cref="AtkValuePtr"/> of the event's AtkValues.
/// </summary>
/// <returns>
/// An <see cref="IEnumerable{T}"/> of <see cref="AtkValuePtr"/> corresponding to the event's AtkValues.
/// </returns>
public IEnumerable<AtkValuePtr> AtkValueEnumerable
{
get
{
for (var i = 0; i < this.AtkValueCount; i++)
{
AtkValuePtr ptr;
unsafe
{
#pragma warning disable CS0618 // Type or member is obsolete
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
#pragma warning restore CS0618 // Type or member is obsolete
}
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
yield return ptr;
}
}
}
}

View file

@ -0,0 +1,27 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for Show events.
/// </summary>
public class AddonShowArgs : AddonArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonShowArgs"/> class.
/// </summary>
internal AddonShowArgs()
{
}
/// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Show;
/// <summary>
/// Gets or sets a value indicating whether the window should play open sound effects.
/// </summary>
public bool SilenceOpenSoundEffect { get; set; }
/// <summary>
/// Gets or sets the flags that the window will unset when it Shows/Hides.
/// </summary>
public uint UnsetShowHideFlags { get; set; }
}

View file

@ -1,38 +0,0 @@
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary>
/// Addon argument data for Update events.
/// </summary>
public class AddonUpdateArgs : AddonArgs, ICloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="AddonUpdateArgs"/> class.
/// </summary>
[Obsolete("Not intended for public construction.", false)]
public AddonUpdateArgs()
{
}
/// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Update;
/// <summary>
/// Gets the time since the last update.
/// </summary>
public float TimeDelta
{
get => this.TimeDeltaInternal;
init => this.TimeDeltaInternal = value;
}
/// <summary>
/// Gets or sets the time since the last update.
/// </summary>
internal float TimeDeltaInternal { get; set; }
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonUpdateArgs Clone() => (AddonUpdateArgs)this.MemberwiseClone();
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
}

View file

@ -5,38 +5,48 @@
/// </summary>
public enum AddonArgsType
{
/// <summary>
/// Generic arg type that contains no meaningful data.
/// </summary>
Generic,
/// <summary>
/// Contains argument data for Setup.
/// </summary>
Setup,
/// <summary>
/// Contains argument data for Update.
/// </summary>
Update,
/// <summary>
/// Contains argument data for Draw.
/// </summary>
Draw,
/// <summary>
/// Contains argument data for Finalize.
/// </summary>
Finalize,
/// <summary>
/// Contains argument data for RequestedUpdate.
/// </summary>
/// </summary>
RequestedUpdate,
/// <summary>
/// Contains argument data for Refresh.
/// </summary>
/// </summary>
Refresh,
/// <summary>
/// Contains argument data for ReceiveEvent.
/// </summary>
ReceiveEvent,
/// <summary>
/// Contains argument data for Show.
/// </summary>
Show,
/// <summary>
/// Contains argument data for Hide.
/// </summary>
Hide,
/// <summary>
/// Contains argument data for Close.
/// </summary>
Close,
/// <summary>
/// Contains argument data for OnFocusChanged.
/// </summary>
FocusChanged,
}

View file

@ -16,7 +16,7 @@ public enum AddonEvent
/// </summary>
/// <seealso cref="AddonSetupArgs"/>
PreSetup,
/// <summary>
/// An event that is fired after an addon has finished its initial setup. This event is particularly useful for
/// developers seeking to add custom elements to now-initialized and populated node lists, as well as reading data
@ -29,7 +29,6 @@ public enum AddonEvent
/// An event that is fired before an addon begins its update cycle via <see cref="AtkUnitBase.Update"/>. This event
/// is fired every frame that an addon is loaded, regardless of visibility.
/// </summary>
/// <seealso cref="AddonUpdateArgs"/>
PreUpdate,
/// <summary>
@ -42,7 +41,6 @@ public enum AddonEvent
/// An event that is fired before an addon begins drawing to screen via <see cref="AtkUnitBase.Draw"/>. Unlike
/// <see cref="PreUpdate"/>, this event is only fired if an addon is visible or otherwise drawing to screen.
/// </summary>
/// <seealso cref="AddonDrawArgs"/>
PreDraw,
/// <summary>
@ -62,9 +60,8 @@ public enum AddonEvent
/// <br />
/// As this is part of the destruction process for an addon, this event does not have an associated Post event.
/// </remarks>
/// <seealso cref="AddonFinalizeArgs"/>
PreFinalize,
/// <summary>
/// An event that is fired before a call to <see cref="AtkUnitBase.OnRequestedUpdate"/> is made in response to a
/// change in the subscribed <see cref="AddonRequestedUpdateArgs.NumberArrayData"/> or
@ -81,13 +78,13 @@ public enum AddonEvent
/// to the Free Company's overview.
/// </example>
PreRequestedUpdate,
/// <summary>
/// An event that is fired after an addon has finished processing an <c>ArrayData</c> update.
/// See <see cref="PreRequestedUpdate"/> for more information.
/// </summary>
PostRequestedUpdate,
/// <summary>
/// An event that is fired before an addon calls its <see cref="AtkUnitManager.RefreshAddon"/> method. Refreshes are
/// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to
@ -96,13 +93,13 @@ public enum AddonEvent
/// <seealso cref="AddonRefreshArgs"/>
/// <seealso cref="PostRefresh"/>
PreRefresh,
/// <summary>
/// An event that is fired after an addon has finished its refresh.
/// See <see cref="PreRefresh"/> for more information.
/// </summary>
PostRefresh,
/// <summary>
/// An event that is fired before an addon begins processing a user-driven event via
/// <see cref="AtkEventListener.ReceiveEvent"/>, such as mousing over an element or clicking a button. This event
@ -112,10 +109,108 @@ public enum AddonEvent
/// <seealso cref="AddonReceiveEventArgs"/>
/// <seealso cref="PostReceiveEvent"/>
PreReceiveEvent,
/// <summary>
/// An event that is fired after an addon finishes calling its <see cref="AtkEventListener.ReceiveEvent"/> method.
/// See <see cref="PreReceiveEvent"/> for more information.
/// </summary>
PostReceiveEvent,
/// <summary>
/// An event that is fired before an addon processes its open method.
/// </summary>
PreOpen,
/// <summary>
/// An event that is fired after an addon has processed its open method.
/// </summary>
PostOpen,
/// <summary>
/// An even that is fired before an addon processes its Close method.
/// </summary>
PreClose,
/// <summary>
/// An event that is fired after an addon has processed its Close method.
/// </summary>
PostClose,
/// <summary>
/// An event that is fired before an addon processes its Show method.
/// </summary>
PreShow,
/// <summary>
/// An event that is fired after an addon has processed its Show method.
/// </summary>
PostShow,
/// <summary>
/// An event that is fired before an addon processes its Hide method.
/// </summary>
PreHide,
/// <summary>
/// An event that is fired after an addon has processed its Hide method.
/// </summary>
PostHide,
/// <summary>
/// An event that is fired before an addon processes its OnMove method.
/// OnMove is triggered only when a move is completed.
/// </summary>
PreMove,
/// <summary>
/// An event that is fired after an addon has processed its OnMove method.
/// OnMove is triggered only when a move is completed.
/// </summary>
PostMove,
/// <summary>
/// An event that is fired before an addon processes its MouseOver method.
/// </summary>
PreMouseOver,
/// <summary>
/// An event that is fired after an addon has processed its MouseOver method.
/// </summary>
PostMouseOver,
/// <summary>
/// An event that is fired before an addon processes its MouseOut method.
/// </summary>
PreMouseOut,
/// <summary>
/// An event that is fired after an addon has processed its MouseOut method.
/// </summary>
PostMouseOut,
/// <summary>
/// An event that is fired before an addon processes its Focus method.
/// </summary>
/// <remarks>
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
/// </remarks>
PreFocus,
/// <summary>
/// An event that is fired after an addon has processed its Focus method.
/// </summary>
/// <remarks>
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
/// </remarks>
PostFocus,
/// <summary>
/// An event that is fired before an addon processes its FocusChanged method.
/// </summary>
PreFocusChanged,
/// <summary>
/// An event that is fired after a addon processes its FocusChanged method.
/// </summary>
PostFocusChanged,
}

View file

@ -1,16 +1,15 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Hooking;
using Dalamud.Hooking.Internal;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle;
@ -21,75 +20,56 @@ namespace Dalamud.Game.Addon.Lifecycle;
[ServiceManager.EarlyLoadedService]
internal unsafe class AddonLifecycle : IInternalDisposableService
{
private static readonly ModuleLog Log = new("AddonLifecycle");
/// <summary>
/// Gets a list of all allocated addon virtual tables.
/// </summary>
public static readonly List<AddonVirtualTable> AllocatedTables = [];
private static readonly ModuleLog Log = ModuleLog.Create<AddonLifecycle>();
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get();
[ServiceManager.ServiceDependency]
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
private readonly nint disallowedReceiveEventAddress;
private readonly AddonLifecycleAddressResolver address;
private readonly AddonSetupHook<AtkUnitBase.Delegates.OnSetup> onAddonSetupHook;
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
private readonly CallHook<AtkUnitBase.Delegates.Draw> onAddonDrawHook;
private readonly CallHook<AtkUnitBase.Delegates.Update> onAddonUpdateHook;
private readonly Hook<AtkUnitManager.Delegates.RefreshAddon> onAddonRefreshHook;
private readonly CallHook<AtkUnitBase.Delegates.OnRequestedUpdate> onAddonRequestedUpdateHook;
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
private bool isInvokingListeners;
[ServiceManager.ServiceConstructor]
private AddonLifecycle(TargetSigScanner sigScanner)
private AddonLifecycle()
{
this.address = new AddonLifecycleAddressResolver();
this.address.Setup(sigScanner);
this.disallowedReceiveEventAddress = (nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveEvent;
var refreshAddonAddress = (nint)RaptureAtkUnitManager.StaticVirtualTablePointer->RefreshAddon;
this.onAddonSetupHook = new AddonSetupHook<AtkUnitBase.Delegates.OnSetup>(this.address.AddonSetup, this.OnAddonSetup);
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
this.onAddonDrawHook = new CallHook<AtkUnitBase.Delegates.Draw>(this.address.AddonDraw, this.OnAddonDraw);
this.onAddonUpdateHook = new CallHook<AtkUnitBase.Delegates.Update>(this.address.AddonUpdate, this.OnAddonUpdate);
this.onAddonRefreshHook = Hook<AtkUnitManager.Delegates.RefreshAddon>.FromAddress(refreshAddonAddress, this.OnAddonRefresh);
this.onAddonRequestedUpdateHook = new CallHook<AtkUnitBase.Delegates.OnRequestedUpdate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
this.onAddonSetupHook.Enable();
this.onAddonFinalizeHook.Enable();
this.onAddonDrawHook.Enable();
this.onAddonUpdateHook.Enable();
this.onAddonRefreshHook.Enable();
this.onAddonRequestedUpdateHook.Enable();
this.onInitializeAddonHook = Hook<AtkUnitBase.Delegates.Initialize>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->Initialize, this.OnAddonInitialize);
this.onInitializeAddonHook.Enable();
}
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
/// <summary>
/// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
/// </summary>
internal List<AddonLifecycleReceiveEventListener> ReceiveEventListeners { get; } = new();
/// <summary>
/// Gets a list of all AddonLifecycle Event Listeners.
/// </summary>
internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
/// </summary> <br/>
/// Mapping is: EventType -> AddonName -> ListenerList
internal Dictionary<AddonEvent, Dictionary<string, HashSet<AddonLifecycleEventListener>>> EventListeners { get; } = [];
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
this.onAddonSetupHook.Dispose();
this.onAddonFinalizeHook.Dispose();
this.onAddonDrawHook.Dispose();
this.onAddonUpdateHook.Dispose();
this.onAddonRefreshHook.Dispose();
this.onAddonRequestedUpdateHook.Dispose();
this.onInitializeAddonHook?.Dispose();
this.onInitializeAddonHook = null;
foreach (var receiveEventListener in this.ReceiveEventListeners)
AllocatedTables.ForEach(entry => entry.Dispose());
AllocatedTables.Clear();
}
/// <summary>
/// Resolves a virtual table address to the original virtual table address.
/// </summary>
/// <param name="tableAddress">The modified address to resolve.</param>
/// <returns>The original address.</returns>
internal static AtkUnitBase.AtkUnitBaseVirtualTable* GetOriginalVirtualTable(AtkUnitBase.AtkUnitBaseVirtualTable* tableAddress)
{
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
if (matchedTable == null)
{
receiveEventListener.Dispose();
return null;
}
return matchedTable.OriginalVirtualTable;
}
/// <summary>
@ -98,20 +78,14 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
/// <param name="listener">The listener to register.</param>
internal void RegisterListener(AddonLifecycleEventListener listener)
{
this.framework.RunOnTick(() =>
if (this.isInvokingListeners)
{
this.EventListeners.Add(listener);
// If we want receive event messages have an already active addon, enable the receive event hook.
// If the addon isn't active yet, we'll grab the hook when it sets up.
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
{
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
{
receiveEventListener.TryEnable();
}
}
});
this.framework.RunOnTick(() => this.RegisterListenerMethod(listener));
}
else
{
this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
}
}
/// <summary>
@ -120,27 +94,16 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
/// <param name="listener">The listener to unregister.</param>
internal void UnregisterListener(AddonLifecycleEventListener listener)
{
// Set removed state to true immediately, then lazily remove it from the EventListeners list on next Framework Update.
listener.Removed = true;
listener.IsRequestedToClear = true;
this.framework.RunOnTick(() =>
if (this.isInvokingListeners)
{
this.EventListeners.Remove(listener);
// If we are disabling an ReceiveEvent listener, check if we should disable the hook.
if (listener is { EventType: AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent })
{
// Get the ReceiveEvent Listener for this addon
if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener)
{
// If there are no other listeners listening for this event, disable the hook.
if (!this.EventListeners.Any(listeners => listeners.AddonName.Contains(listener.AddonName) && listener.EventType is AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent))
{
receiveEventListener.Disable();
}
}
}
});
this.framework.RunOnTick(() => this.UnregisterListenerMethod(listener));
}
else
{
this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
}
}
/// <summary>
@ -151,220 +114,104 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
/// <param name="blame">What to blame on errors.</param>
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
{
// Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
foreach (var listener in this.EventListeners)
this.isInvokingListeners = true;
// Early return if we don't have any listeners of this type
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
// Handle listeners for this event type that don't care which addon is triggering it
if (addonListeners.TryGetValue(string.Empty, out var globalListeners))
{
if (listener.EventType != eventType)
continue;
// If the listener is pending removal, and is waiting until the next Framework Update, don't invoke listener.
if (listener.Removed)
continue;
// Match on string.empty for listeners that want events for all addons.
if (!string.IsNullOrWhiteSpace(listener.AddonName) && !args.IsAddon(listener.AddonName))
continue;
try
foreach (var listener in globalListeners)
{
listener.FunctionDelegate.Invoke(eventType, args);
}
catch (Exception e)
{
Log.Error(e, $"Exception in {blame} during {eventType} invoke.");
}
}
}
private void RegisterReceiveEventHook(AtkUnitBase* addon)
{
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
// Disallows hooking the core internal event handler.
var addonName = addon->NameString;
var receiveEventAddress = (nint)addon->VirtualTable->ReceiveEvent;
if (receiveEventAddress != this.disallowedReceiveEventAddress)
{
// If we have a ReceiveEvent listener already made for this hook address, add this addon's name to that handler.
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.FunctionAddress == receiveEventAddress) is { } existingListener)
{
if (!existingListener.AddonNames.Contains(addonName))
if (listener.IsRequestedToClear) continue;
try
{
existingListener.AddonNames.Add(addonName);
listener.FunctionDelegate.Invoke(eventType, args);
}
}
// Else, we have an addon that we don't have the ReceiveEvent for yet, make it.
else
{
this.ReceiveEventListeners.Add(new AddonLifecycleReceiveEventListener(this, addonName, receiveEventAddress));
}
// If we have an active listener for this addon already, we need to activate this hook.
if (this.EventListeners.Any(listener => (listener.EventType is AddonEvent.PostReceiveEvent or AddonEvent.PreReceiveEvent) && listener.AddonName == addonName))
{
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } receiveEventListener)
catch (Exception e)
{
receiveEventListener.TryEnable();
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global addon event listener.");
}
}
}
// Handle listeners that are listening for this addon and event type specifically
if (addonListeners.TryGetValue(args.AddonName, out var addonListener))
{
foreach (var listener in addonListener)
{
if (listener.IsRequestedToClear) continue;
try
{
listener.FunctionDelegate.Invoke(eventType, args);
}
catch (Exception e)
{
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific addon {args.AddonName}.");
}
}
}
this.isInvokingListeners = false;
}
private void UnregisterReceiveEventHook(string addonName)
private void RegisterListenerMethod(AddonLifecycleEventListener listener)
{
// Remove this addons ReceiveEvent Registration
if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } eventListener)
if (!this.EventListeners.ContainsKey(listener.EventType))
{
eventListener.AddonNames.Remove(addonName);
// If there are no more listeners let's remove and dispose.
if (eventListener.AddonNames.Count is 0)
if (!this.EventListeners.TryAdd(listener.EventType, []))
{
this.ReceiveEventListeners.Remove(eventListener);
eventListener.Dispose();
return;
}
}
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
{
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
{
return;
}
}
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
}
private void UnregisterListenerMethod(AddonLifecycleEventListener listener)
{
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
{
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
{
addonListener.Remove(listener);
}
}
}
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
private void OnAddonInitialize(AtkUnitBase* addon)
{
try
{
this.RegisterReceiveEventHook(addon);
this.LogInitialize(addon->NameString);
// AddonVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
AllocatedTables.Add(new AddonVirtualTable(addon, this));
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
Log.Error(e, "Exception in AddonLifecycle during OnAddonInitialize.");
}
using var returner = this.argsPool.Rent(out AddonSetupArgs arg);
arg.AddonInternal = (nint)addon;
arg.AtkValueCount = valueCount;
arg.AtkValues = (nint)values;
this.InvokeListenersSafely(AddonEvent.PreSetup, arg);
valueCount = arg.AtkValueCount;
values = (AtkValue*)arg.AtkValues;
try
{
addon->OnSetup(valueCount, values);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original AddonSetup. This may be a bug in the game or another plugin hooking this method.");
}
this.InvokeListenersSafely(AddonEvent.PostSetup, arg);
this.onInitializeAddonHook!.Original(addon);
}
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
[Conditional("DEBUG")]
private void LogInitialize(string addonName)
{
try
{
var addonName = atkUnitBase[0]->NameString;
this.UnregisterReceiveEventHook(addonName);
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
}
using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg);
arg.AddonInternal = (nint)atkUnitBase[0];
this.InvokeListenersSafely(AddonEvent.PreFinalize, arg);
try
{
this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original AddonFinalize. This may be a bug in the game or another plugin hooking this method.");
}
}
private void OnAddonDraw(AtkUnitBase* addon)
{
using var returner = this.argsPool.Rent(out AddonDrawArgs arg);
arg.AddonInternal = (nint)addon;
this.InvokeListenersSafely(AddonEvent.PreDraw, arg);
try
{
addon->Draw();
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original AddonDraw. This may be a bug in the game or another plugin hooking this method.");
}
this.InvokeListenersSafely(AddonEvent.PostDraw, arg);
}
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
{
using var returner = this.argsPool.Rent(out AddonUpdateArgs arg);
arg.AddonInternal = (nint)addon;
arg.TimeDeltaInternal = delta;
this.InvokeListenersSafely(AddonEvent.PreUpdate, arg);
try
{
addon->Update(delta);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original AddonUpdate. This may be a bug in the game or another plugin hooking this method.");
}
this.InvokeListenersSafely(AddonEvent.PostUpdate, arg);
}
private bool OnAddonRefresh(AtkUnitManager* thisPtr, AtkUnitBase* addon, uint valueCount, AtkValue* values)
{
var result = false;
using var returner = this.argsPool.Rent(out AddonRefreshArgs arg);
arg.AddonInternal = (nint)addon;
arg.AtkValueCount = valueCount;
arg.AtkValues = (nint)values;
this.InvokeListenersSafely(AddonEvent.PreRefresh, arg);
valueCount = arg.AtkValueCount;
values = (AtkValue*)arg.AtkValues;
try
{
result = this.onAddonRefreshHook.Original(thisPtr, addon, valueCount, values);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original AddonRefresh. This may be a bug in the game or another plugin hooking this method.");
}
this.InvokeListenersSafely(AddonEvent.PostRefresh, arg);
return result;
}
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg);
arg.AddonInternal = (nint)addon;
arg.NumberArrayData = (nint)numberArrayData;
arg.StringArrayData = (nint)stringArrayData;
this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg);
numberArrayData = (NumberArrayData**)arg.NumberArrayData;
stringArrayData = (StringArrayData**)arg.StringArrayData;
try
{
addon->OnRequestedUpdate(numberArrayData, stringArrayData);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original AddonRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
}
this.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, arg);
Log.Debug($"Initializing {addonName}");
}
}
@ -381,7 +228,7 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
[ServiceManager.ServiceDependency]
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
private readonly List<AddonLifecycleEventListener> eventListeners = new();
private readonly List<AddonLifecycleEventListener> eventListeners = [];
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
@ -452,10 +299,14 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
this.eventListeners.RemoveAll(entry =>
{
if (entry.FunctionDelegate != handler) return false;
this.addonLifecycleService.UnregisterListener(entry);
return true;
});
}
}
/// <inheritdoc/>
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
=> (nint)AddonLifecycle.GetOriginalVirtualTable((AtkUnitBase.AtkUnitBaseVirtualTable*)virtualTableAddress);
}

View file

@ -1,56 +0,0 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle;
/// <summary>
/// AddonLifecycleService memory address resolver.
/// </summary>
internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the addon setup hook invoked by the AtkUnitManager.
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
/// This is called for a majority of all addon OnSetup's.
/// </summary>
public nint AddonSetup { get; private set; }
/// <summary>
/// Gets the address of the other addon setup hook invoked by the AtkUnitManager.
/// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue.
/// This seems to be called rarely for specific addons.
/// </summary>
public nint AddonSetup2 { get; private set; }
/// <summary>
/// Gets the address of the addon finalize hook invoked by the AtkUnitManager.
/// </summary>
public nint AddonFinalize { get; private set; }
/// <summary>
/// Gets the address of the addon draw hook invoked by virtual function call.
/// </summary>
public nint AddonDraw { get; private set; }
/// <summary>
/// Gets the address of the addon update hook invoked by virtual function call.
/// </summary>
public nint AddonUpdate { get; private set; }
/// <summary>
/// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call.
/// </summary>
public nint AddonOnRequestedUpdate { get; private set; }
/// <summary>
/// Scan for and setup any configured address pointers.
/// </summary>
/// <param name="sig">The signature scanner to facilitate setup.</param>
protected override void Setup64Bit(ISigScanner sig)
{
this.AddonSetup = sig.ScanText("4C 8B 88 ?? ?? ?? ?? 66 44 39 BB");
this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5");
this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01");
this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2");
this.AddonOnRequestedUpdate = sig.ScanText("FF 90 98 01 00 00 48 8B 5C 24 30 48 83 C4 20");
}
}

View file

@ -25,19 +25,19 @@ internal class AddonLifecycleEventListener
/// string.Empty if it wants to be called for any addon.
/// </summary>
public string AddonName { get; init; }
/// <summary>
/// Gets or sets a value indicating whether this event has been unregistered.
/// </summary>
public bool Removed { get; set; }
/// <summary>
/// Gets the event type this listener is looking for.
/// </summary>
public AddonEvent EventType { get; init; }
/// <summary>
/// Gets the delegate this listener invokes.
/// </summary>
public IAddonLifecycle.AddonEventDelegate FunctionDelegate { get; init; }
/// <summary>
/// Gets or sets if the listener is requested to be cleared.
/// </summary>
internal bool IsRequestedToClear { get; set; }
}

View file

@ -1,111 +0,0 @@
using System.Collections.Generic;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Hooking;
using Dalamud.Logging.Internal;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle;
/// <summary>
/// This class is a helper for tracking and invoking listener delegates for Addon_OnReceiveEvent.
/// Multiple addons may use the same ReceiveEvent function, this helper makes sure that those addon events are handled properly.
/// </summary>
internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
{
private static readonly ModuleLog Log = new("AddonLifecycle");
[ServiceManager.ServiceDependency]
private readonly AddonLifecyclePooledArgs argsPool = Service<AddonLifecyclePooledArgs>.Get();
/// <summary>
/// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class.
/// </summary>
/// <param name="service">AddonLifecycle service instance.</param>
/// <param name="addonName">Initial Addon Requesting this listener.</param>
/// <param name="receiveEventAddress">Address of Addon's ReceiveEvent function.</param>
internal AddonLifecycleReceiveEventListener(AddonLifecycle service, string addonName, nint receiveEventAddress)
{
this.AddonLifecycle = service;
this.AddonNames = [addonName];
this.FunctionAddress = receiveEventAddress;
}
/// <summary>
/// Gets the list of addons that use this receive event hook.
/// </summary>
public List<string> AddonNames { get; init; }
/// <summary>
/// Gets the address of the ReceiveEvent function as provided by the vtable on setup.
/// </summary>
public nint FunctionAddress { get; init; }
/// <summary>
/// Gets the contained hook for these addons.
/// </summary>
public Hook<AtkUnitBase.Delegates.ReceiveEvent>? Hook { get; private set; }
/// <summary>
/// Gets or sets the Reference to AddonLifecycle service instance.
/// </summary>
private AddonLifecycle AddonLifecycle { get; set; }
/// <summary>
/// Try to hook and enable this receive event handler.
/// </summary>
public void TryEnable()
{
this.Hook ??= Hook<AtkUnitBase.Delegates.ReceiveEvent>.FromAddress(this.FunctionAddress, this.OnReceiveEvent);
this.Hook?.Enable();
}
/// <summary>
/// Disable the hook for this receive event handler.
/// </summary>
public void Disable()
{
this.Hook?.Disable();
}
/// <inheritdoc/>
public void Dispose()
{
this.Hook?.Dispose();
}
private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
{
// Check that we didn't get here through a call to another addons handler.
var addonName = addon->NameString;
if (!this.AddonNames.Contains(addonName))
{
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
return;
}
using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg);
arg.AddonInternal = (nint)addon;
arg.AtkEventType = (byte)eventType;
arg.EventParam = eventParam;
arg.AtkEvent = (IntPtr)atkEvent;
arg.Data = (nint)atkEventData;
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg);
eventType = (AtkEventType)arg.AtkEventType;
eventParam = arg.EventParam;
atkEvent = (AtkEvent*)arg.AtkEvent;
atkEventData = (AtkEventData*)arg.Data;
try
{
this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original AddonReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
}
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, arg);
}
}

View file

@ -1,80 +0,0 @@
using System.Runtime.InteropServices;
using Reloaded.Hooks.Definitions;
namespace Dalamud.Game.Addon.Lifecycle;
/// <summary>
/// This class represents a callsite hook used to replace the address of the OnSetup function in r9.
/// </summary>
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
internal class AddonSetupHook<T> : IDisposable where T : Delegate
{
private readonly Reloaded.Hooks.AsmHook asmHook;
private T? detour;
private bool activated;
/// <summary>
/// Initializes a new instance of the <see cref="AddonSetupHook{T}"/> class.
/// </summary>
/// <param name="address">Address of the instruction to replace.</param>
/// <param name="detour">Delegate to invoke.</param>
internal AddonSetupHook(nint address, T detour)
{
this.detour = detour;
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
var code = new[]
{
"use64",
$"mov r9, 0x{detourPtr:X8}",
};
var opt = new AsmHookOptions
{
PreferRelativeJump = true,
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
MaxOpcodeSize = 5,
};
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
}
/// <summary>
/// Gets a value indicating whether or not the hook is enabled.
/// </summary>
public bool IsEnabled => this.asmHook.IsEnabled;
/// <summary>
/// Starts intercepting a call to the function.
/// </summary>
public void Enable()
{
if (!this.activated)
{
this.activated = true;
this.asmHook.Activate();
return;
}
this.asmHook.Enable();
}
/// <summary>
/// Stops intercepting a call to the function.
/// </summary>
public void Disable()
{
this.asmHook.Disable();
}
/// <summary>
/// Remove a hook from the current process.
/// </summary>
public void Dispose()
{
this.asmHook.Disable();
this.detour = null;
}
}

View file

@ -0,0 +1,679 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Logging.Internal;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle;
/// <summary>
/// Represents a class that holds references to an addons original and modified virtual table entries.
/// </summary>
internal unsafe class AddonVirtualTable : IDisposable
{
// This need to be at minimum the largest virtual table size of all addons
// Copying extra entries is not problematic, and is considered safe.
private const int VirtualTableEntryCount = 200;
private const bool EnableLogging = false;
private static readonly ModuleLog Log = new("LifecycleVT");
private readonly AddonLifecycle lifecycleService;
// Each addon gets its own set of args that are used to mutate the original call when used in pre-calls
private readonly AddonSetupArgs setupArgs = new();
private readonly AddonArgs finalizeArgs = new();
private readonly AddonArgs drawArgs = new();
private readonly AddonArgs updateArgs = new();
private readonly AddonRefreshArgs refreshArgs = new();
private readonly AddonRequestedUpdateArgs requestedUpdateArgs = new();
private readonly AddonReceiveEventArgs receiveEventArgs = new();
private readonly AddonArgs openArgs = new();
private readonly AddonCloseArgs closeArgs = new();
private readonly AddonShowArgs showArgs = new();
private readonly AddonHideArgs hideArgs = new();
private readonly AddonArgs onMoveArgs = new();
private readonly AddonArgs onMouseOverArgs = new();
private readonly AddonArgs onMouseOutArgs = new();
private readonly AddonArgs focusArgs = new();
private readonly AddonFocusChangedArgs focusChangedArgs = new();
private readonly AtkUnitBase* atkUnitBase;
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
private readonly AtkUnitBase.Delegates.Dtor destructorFunction;
private readonly AtkUnitBase.Delegates.OnSetup onSetupFunction;
private readonly AtkUnitBase.Delegates.Finalizer finalizerFunction;
private readonly AtkUnitBase.Delegates.Draw drawFunction;
private readonly AtkUnitBase.Delegates.Update updateFunction;
private readonly AtkUnitBase.Delegates.OnRefresh onRefreshFunction;
private readonly AtkUnitBase.Delegates.OnRequestedUpdate onRequestedUpdateFunction;
private readonly AtkUnitBase.Delegates.ReceiveEvent onReceiveEventFunction;
private readonly AtkUnitBase.Delegates.Open openFunction;
private readonly AtkUnitBase.Delegates.Close closeFunction;
private readonly AtkUnitBase.Delegates.Show showFunction;
private readonly AtkUnitBase.Delegates.Hide hideFunction;
private readonly AtkUnitBase.Delegates.OnMove onMoveFunction;
private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction;
private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction;
private readonly AtkUnitBase.Delegates.Focus focusFunction;
private readonly AtkUnitBase.Delegates.OnFocusChange onFocusChangeFunction;
/// <summary>
/// Initializes a new instance of the <see cref="AddonVirtualTable"/> class.
/// </summary>
/// <param name="addon">AtkUnitBase* for the addon to replace the table of.</param>
/// <param name="lifecycleService">Reference to AddonLifecycle service to callback and invoke listeners.</param>
internal AddonVirtualTable(AtkUnitBase* addon, AddonLifecycle lifecycleService)
{
this.atkUnitBase = addon;
this.lifecycleService = lifecycleService;
// Save original virtual table
this.OriginalVirtualTable = addon->VirtualTable;
// Create copy of original table
// Note this will copy any derived/overriden functions that this specific addon has.
// Note: currently there are 73 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
this.ModifiedVirtualTable = (AtkUnitBase.AtkUnitBaseVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
NativeMemory.Copy(addon->VirtualTable, this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
// Overwrite the addons existing virtual table with our own
addon->VirtualTable = this.ModifiedVirtualTable;
// Pin each of our listener functions
this.destructorFunction = this.OnAddonDestructor;
this.onSetupFunction = this.OnAddonSetup;
this.finalizerFunction = this.OnAddonFinalize;
this.drawFunction = this.OnAddonDraw;
this.updateFunction = this.OnAddonUpdate;
this.onRefreshFunction = this.OnAddonRefresh;
this.onRequestedUpdateFunction = this.OnRequestedUpdate;
this.onReceiveEventFunction = this.OnAddonReceiveEvent;
this.openFunction = this.OnAddonOpen;
this.closeFunction = this.OnAddonClose;
this.showFunction = this.OnAddonShow;
this.hideFunction = this.OnAddonHide;
this.onMoveFunction = this.OnAddonMove;
this.onMouseOverFunction = this.OnAddonMouseOver;
this.onMouseOutFunction = this.OnAddonMouseOut;
this.focusFunction = this.OnAddonFocus;
this.onFocusChangeFunction = this.OnAddonFocusChange;
// Overwrite specific virtual table entries
this.ModifiedVirtualTable->Dtor = (delegate* unmanaged<AtkUnitBase*, byte, AtkEventListener*>)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
this.ModifiedVirtualTable->OnSetup = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, void>)Marshal.GetFunctionPointerForDelegate(this.onSetupFunction);
this.ModifiedVirtualTable->Finalizer = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.finalizerFunction);
this.ModifiedVirtualTable->Draw = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.drawFunction);
this.ModifiedVirtualTable->Update = (delegate* unmanaged<AtkUnitBase*, float, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
this.ModifiedVirtualTable->OnRefresh = (delegate* unmanaged<AtkUnitBase*, uint, AtkValue*, bool>)Marshal.GetFunctionPointerForDelegate(this.onRefreshFunction);
this.ModifiedVirtualTable->OnRequestedUpdate = (delegate* unmanaged<AtkUnitBase*, NumberArrayData**, StringArrayData**, void>)Marshal.GetFunctionPointerForDelegate(this.onRequestedUpdateFunction);
this.ModifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AtkUnitBase*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(this.onReceiveEventFunction);
this.ModifiedVirtualTable->Open = (delegate* unmanaged<AtkUnitBase*, uint, bool>)Marshal.GetFunctionPointerForDelegate(this.openFunction);
this.ModifiedVirtualTable->Close = (delegate* unmanaged<AtkUnitBase*, bool, bool>)Marshal.GetFunctionPointerForDelegate(this.closeFunction);
this.ModifiedVirtualTable->Show = (delegate* unmanaged<AtkUnitBase*, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
this.ModifiedVirtualTable->Hide = (delegate* unmanaged<AtkUnitBase*, bool, bool, uint, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
this.ModifiedVirtualTable->OnMove = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMoveFunction);
this.ModifiedVirtualTable->OnMouseOver = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
this.ModifiedVirtualTable->OnMouseOut = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
this.ModifiedVirtualTable->Focus = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
this.ModifiedVirtualTable->OnFocusChange = (delegate* unmanaged<AtkUnitBase*, bool, void>)Marshal.GetFunctionPointerForDelegate(this.onFocusChangeFunction);
}
/// <summary>
/// Gets the original virtual table address for this addon.
/// </summary>
internal AtkUnitBase.AtkUnitBaseVirtualTable* OriginalVirtualTable { get; private set; }
/// <summary>
/// Gets the modified virtual address for this addon.
/// </summary>
internal AtkUnitBase.AtkUnitBaseVirtualTable* ModifiedVirtualTable { get; private set; }
/// <inheritdoc/>
public void Dispose()
{
// Ensure restoration is done atomically.
Interlocked.Exchange(ref *(nint*)&this.atkUnitBase->VirtualTable, (nint)this.OriginalVirtualTable);
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
}
private AtkEventListener* OnAddonDestructor(AtkUnitBase* thisPtr, byte freeFlags)
{
AtkEventListener* result = null;
try
{
this.LogEvent(EnableLogging);
try
{
result = this.OriginalVirtualTable->Dtor(thisPtr, freeFlags);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Dtor. This may be a bug in the game or another plugin hooking this method.");
}
if ((freeFlags & 1) == 1)
{
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
AddonLifecycle.AllocatedTables.Remove(this);
}
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDestructor.");
}
return result;
}
private void OnAddonSetup(AtkUnitBase* addon, uint valueCount, AtkValue* values)
{
try
{
this.LogEvent(EnableLogging);
this.setupArgs.Addon = addon;
this.setupArgs.AtkValueCount = valueCount;
this.setupArgs.AtkValues = (nint)values;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreSetup, this.setupArgs);
valueCount = this.setupArgs.AtkValueCount;
values = (AtkValue*)this.setupArgs.AtkValues;
try
{
this.OriginalVirtualTable->OnSetup(addon, valueCount, values);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon OnSetup. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostSetup, this.setupArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonSetup.");
}
}
private void OnAddonFinalize(AtkUnitBase* thisPtr)
{
try
{
this.LogEvent(EnableLogging);
this.finalizeArgs.Addon = thisPtr;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFinalize, this.finalizeArgs);
try
{
this.OriginalVirtualTable->Finalizer(thisPtr);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Finalizer. This may be a bug in the game or another plugin hooking this method.");
}
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFinalize.");
}
}
private void OnAddonDraw(AtkUnitBase* addon)
{
try
{
this.LogEvent(EnableLogging);
this.drawArgs.Addon = addon;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreDraw, this.drawArgs);
try
{
this.OriginalVirtualTable->Draw(addon);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Draw. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostDraw, this.drawArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonDraw.");
}
}
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
{
try
{
this.LogEvent(EnableLogging);
this.updateArgs.Addon = addon;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreUpdate, this.updateArgs);
// Note: Do not pass or allow manipulation of delta.
// It's realistically not something that should be needed.
// And even if someone does, they are encouraged to hook Update themselves.
try
{
this.OriginalVirtualTable->Update(addon, delta);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostUpdate, this.updateArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonUpdate.");
}
}
private bool OnAddonRefresh(AtkUnitBase* addon, uint valueCount, AtkValue* values)
{
var result = false;
try
{
this.LogEvent(EnableLogging);
this.refreshArgs.Addon = addon;
this.refreshArgs.AtkValueCount = valueCount;
this.refreshArgs.AtkValues = (nint)values;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRefresh, this.refreshArgs);
valueCount = this.refreshArgs.AtkValueCount;
values = (AtkValue*)this.refreshArgs.AtkValues;
try
{
result = this.OriginalVirtualTable->OnRefresh(addon, valueCount, values);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon OnRefresh. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRefresh, this.refreshArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonRefresh.");
}
return result;
}
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
try
{
this.LogEvent(EnableLogging);
this.requestedUpdateArgs.Addon = addon;
this.requestedUpdateArgs.NumberArrayData = (nint)numberArrayData;
this.requestedUpdateArgs.StringArrayData = (nint)stringArrayData;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, this.requestedUpdateArgs);
numberArrayData = (NumberArrayData**)this.requestedUpdateArgs.NumberArrayData;
stringArrayData = (StringArrayData**)this.requestedUpdateArgs.StringArrayData;
try
{
this.OriginalVirtualTable->OnRequestedUpdate(addon, numberArrayData, stringArrayData);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon OnRequestedUpdate. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.requestedUpdateArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnRequestedUpdate.");
}
}
private void OnAddonReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
{
try
{
this.LogEvent(EnableLogging);
this.receiveEventArgs.Addon = (nint)addon;
this.receiveEventArgs.AtkEventType = (byte)eventType;
this.receiveEventArgs.EventParam = eventParam;
this.receiveEventArgs.AtkEvent = (IntPtr)atkEvent;
this.receiveEventArgs.AtkEventData = (nint)atkEventData;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreReceiveEvent, this.receiveEventArgs);
eventType = (AtkEventType)this.receiveEventArgs.AtkEventType;
eventParam = this.receiveEventArgs.EventParam;
atkEvent = (AtkEvent*)this.receiveEventArgs.AtkEvent;
atkEventData = (AtkEventData*)this.receiveEventArgs.AtkEventData;
try
{
this.OriginalVirtualTable->ReceiveEvent(addon, eventType, eventParam, atkEvent, atkEventData);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.receiveEventArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonReceiveEvent.");
}
}
private bool OnAddonOpen(AtkUnitBase* thisPtr, uint depthLayer)
{
var result = false;
try
{
this.LogEvent(EnableLogging);
this.openArgs.Addon = thisPtr;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreOpen, this.openArgs);
try
{
result = this.OriginalVirtualTable->Open(thisPtr, depthLayer);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Open. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostOpen, this.openArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonOpen.");
}
return result;
}
private bool OnAddonClose(AtkUnitBase* thisPtr, bool fireCallback)
{
var result = false;
try
{
this.LogEvent(EnableLogging);
this.closeArgs.Addon = thisPtr;
this.closeArgs.FireCallback = fireCallback;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreClose, this.closeArgs);
fireCallback = this.closeArgs.FireCallback;
try
{
result = this.OriginalVirtualTable->Close(thisPtr, fireCallback);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Close. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostClose, this.closeArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonClose.");
}
return result;
}
private void OnAddonShow(AtkUnitBase* thisPtr, bool silenceOpenSoundEffect, uint unsetShowHideFlags)
{
try
{
this.LogEvent(EnableLogging);
this.showArgs.Addon = thisPtr;
this.showArgs.SilenceOpenSoundEffect = silenceOpenSoundEffect;
this.showArgs.UnsetShowHideFlags = unsetShowHideFlags;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreShow, this.showArgs);
silenceOpenSoundEffect = this.showArgs.SilenceOpenSoundEffect;
unsetShowHideFlags = this.showArgs.UnsetShowHideFlags;
try
{
this.OriginalVirtualTable->Show(thisPtr, silenceOpenSoundEffect, unsetShowHideFlags);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostShow, this.showArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonShow.");
}
}
private void OnAddonHide(AtkUnitBase* thisPtr, bool unkBool, bool callHideCallback, uint setShowHideFlags)
{
try
{
this.LogEvent(EnableLogging);
this.hideArgs.Addon = thisPtr;
this.hideArgs.UnknownBool = unkBool;
this.hideArgs.CallHideCallback = callHideCallback;
this.hideArgs.SetShowHideFlags = setShowHideFlags;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreHide, this.hideArgs);
unkBool = this.hideArgs.UnknownBool;
callHideCallback = this.hideArgs.CallHideCallback;
setShowHideFlags = this.hideArgs.SetShowHideFlags;
try
{
this.OriginalVirtualTable->Hide(thisPtr, unkBool, callHideCallback, setShowHideFlags);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostHide, this.hideArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonHide.");
}
}
private void OnAddonMove(AtkUnitBase* thisPtr)
{
try
{
this.LogEvent(EnableLogging);
this.onMoveArgs.Addon = thisPtr;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMove, this.onMoveArgs);
try
{
this.OriginalVirtualTable->OnMove(thisPtr);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon OnMove. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMove, this.onMoveArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMove.");
}
}
private void OnAddonMouseOver(AtkUnitBase* thisPtr)
{
try
{
this.LogEvent(EnableLogging);
this.onMouseOverArgs.Addon = thisPtr;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOver, this.onMouseOverArgs);
try
{
this.OriginalVirtualTable->OnMouseOver(thisPtr);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon OnMouseOver. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOver, this.onMouseOverArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOver.");
}
}
private void OnAddonMouseOut(AtkUnitBase* thisPtr)
{
try
{
this.LogEvent(EnableLogging);
this.onMouseOutArgs.Addon = thisPtr;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreMouseOut, this.onMouseOutArgs);
try
{
this.OriginalVirtualTable->OnMouseOut(thisPtr);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon OnMouseOut. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostMouseOut, this.onMouseOutArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonMouseOut.");
}
}
private void OnAddonFocus(AtkUnitBase* thisPtr)
{
try
{
this.LogEvent(EnableLogging);
this.focusArgs.Addon = thisPtr;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocus, this.focusArgs);
try
{
this.OriginalVirtualTable->Focus(thisPtr);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon Focus. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocus, this.focusArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocus.");
}
}
private void OnAddonFocusChange(AtkUnitBase* thisPtr, bool isFocused)
{
try
{
this.LogEvent(EnableLogging);
this.focusChangedArgs.Addon = thisPtr;
this.focusChangedArgs.ShouldFocus = isFocused;
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocusChanged, this.focusChangedArgs);
isFocused = this.focusChangedArgs.ShouldFocus;
try
{
this.OriginalVirtualTable->OnFocusChange(thisPtr, isFocused);
}
catch (Exception e)
{
Log.Error(e, "Caught exception when calling original Addon OnFocusChanged. This may be a bug in the game or another plugin hooking this method.");
}
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocusChanged, this.focusChangedArgs);
}
catch (Exception e)
{
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocusChange.");
}
}
[Conditional("DEBUG")]
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
{
if (loggingEnabled)
{
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
if (caller is "OnAddonUpdate" or "OnAddonDraw" or "OnAddonReceiveEvent" or "OnRequestedUpdate")
return;
Log.Debug($"[{caller}]: {this.atkUnitBase->NameString}");
}
}
}

View file

@ -0,0 +1,39 @@
using Dalamud.Game.NativeWrapper;
namespace Dalamud.Game.Agent.AgentArgTypes;
/// <summary>
/// Base class for AgentLifecycle AgentArgTypes.
/// </summary>
public unsafe class AgentArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="AgentArgs"/> class.
/// </summary>
internal AgentArgs()
{
}
/// <summary>
/// Gets the pointer to the Agents AgentInterface*.
/// </summary>
public AgentInterfacePtr Agent { get; internal set; }
/// <summary>
/// Gets the agent id.
/// </summary>
public AgentId AgentId { get; internal set; }
/// <summary>
/// Gets the type of these args.
/// </summary>
public virtual AgentArgsType Type => AgentArgsType.Generic;
/// <summary>
/// Gets the typed pointer to the Agents AgentInterface*.
/// </summary>
/// <typeparam name="T">AgentInterface.</typeparam>
/// <returns>Typed pointer to contained Agents AgentInterface.</returns>
public T* GetAgentPointer<T>() where T : unmanaged
=> (T*)this.Agent.Address;
}

Some files were not shown because too many files have changed in this diff Show more