Compare commits

..
754 Commits
Author SHA1 Message Date
amacocian fe4720c5e1 Update GWCA - fixes Character Select widget (#1626) 2026-09-09 22:58:09 +02:00
github-actions[bot]andamacocian b57d2ad5d3 Bump version to 0.9.10.21 2026-09-09 22:58:09 +02:00
amacocian 5faeae64d3 Download service robustness (Closes #1074) (#1624) 2026-09-04 19:34:25 +02:00
amacocian fdcf238d68 Fix wine password param expansion (#1623) 2026-09-04 19:34:25 +02:00
amacocian d9f7d05672 Add widgets for empty views (Closes #1093) (#1622) 2026-09-04 19:34:25 +02:00
amacocian c61ee2ecd7 Fix toolbox assertion error in wine due to long computer name (Closes #1619) (#1621) 2026-09-04 19:34:25 +02:00
amacocian 82512f9461 Improve documentation (#1620) 2026-09-04 19:34:25 +02:00
github-actions[bot]andamacocian 2dc13b7184 Bump version to 0.9.10.20 2026-09-04 19:34:25 +02:00
amacocian da691e3310 Improvements to skill updater (#1617) 2026-08-31 23:07:24 +02:00
amacocian 5e020f51d9 Update skills (#1616) 2026-08-31 23:07:24 +02:00
github-actions[bot]andamacocian 1f63e59b8a Bump version to 0.9.10.19 2026-08-31 23:07:24 +02:00
amacocian dcb9c29505 Update deps (#1612)
* Update dependencies

* Fix GWCA issues
2026-08-30 23:27:31 +02:00
amacocian db83e5a954 Setup stable computer name in Wine prefix (Closes #1609) (#1611) 2026-08-30 23:27:31 +02:00
amacocian c7c5506ee0 Update GWCA (#1610) 2026-08-30 23:27:31 +02:00
github-actions[bot]andamacocian 2988b6b38d Bump version to 0.9.10.18 2026-08-30 23:27:31 +02:00
amacocian debef9a36f Update documentation with Steam support (#1603) 2026-08-02 15:01:35 +02:00
amacocian 839bc57b48 Improve interaction with system shell (#1601) 2026-08-02 15:01:35 +02:00
amacocian 175c16bd00 Notifications show an animation for their expiration (#1600) 2026-08-02 15:01:35 +02:00
amacocian ad356f431e Steam login support (Windows) (Closes #1597) (#1599) 2026-08-02 15:01:35 +02:00
github-actions[bot]andamacocian ebe368e61e Bump version to 0.9.10.17 2026-08-02 15:01:35 +02:00
amacocian d800a630b5 Sanitize secrets (#1595) 2026-07-28 14:55:57 +02:00
github-actions[bot]andamacocian 239f91a65f Bump version to 0.9.10.16 2026-07-28 14:55:57 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
aff7c24b6a Bump actions/setup-dotnet from 5 to 6 (#1588)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 13:54:14 +02:00
amacocian 5bf0af61ba Fix commandline arguments with space (#1593) 2026-07-28 13:54:14 +02:00
amacocian 03ed31e85b Fix telemetry (Closes #1591) (#1592) 2026-07-28 13:54:14 +02:00
amacocian b21a4482bd Redact sensitive logs (#1590) 2026-07-28 13:54:14 +02:00
github-actions[bot]andamacocian 2ad8ae79ce Bump version to 0.9.10.15 2026-07-28 13:54:14 +02:00
amacocianandAlexandru Macocian a4be747dd5 Fix more notifications expiration times (#1587)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-07-18 07:00:00 +02:00
amacocianandAlexandru Macocian a6cd5d6f37 Update dependencies (#1585)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-07-18 07:00:00 +02:00
amacocianandAlexandru Macocian 0e80fb7d86 Update GWCA (#1584)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-07-18 07:00:00 +02:00
github-actions[bot]andamacocian 0793f181bb Bump version to 0.9.10.14 2026-07-18 07:00:00 +02:00
amacocian a9ee0424f7 FocusView browser maximize (Closes #1577) (#1578) 2026-07-03 18:45:45 +02:00
amacocian 49f45b6b76 Fix notifications disappearing too soon (Closes #1573) (#1576) 2026-07-03 18:45:45 +02:00
github-actions[bot]andamacocian 4216a03438 Bump version to 0.9.10.13 2026-07-03 18:45:45 +02:00
amacocianandAlexandru Macocian a9a8346a5b Fix character select (Closes #1561) (#1571)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-30 17:09:16 +02:00
amacocianandAlexandru Macocian 70a95b5e08 Executable list eviction limit only on invalid exes (#1570)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-30 17:09:16 +02:00
amacocianandAlexandru Macocian 1e8c2e44e2 Fix Kill command on Linux (Closes #1564) (#1569)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-30 17:09:16 +02:00
amacocianandAlexandru Macocian d553aadead Update skills after patch update (Closes #1565) (#1568)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-30 17:09:16 +02:00
amacocianandAlexandru Macocian fabb98f74e Improve exe matching on Linux (#1567)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-30 17:09:16 +02:00
amacocianandAlexandru Macocian 0a0ec5cefc Fix multibox support on Linux (#1566)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-30 17:09:16 +02:00
amacocianandAlexandru Macocian 0d31587855 Revert GWCA static linking (Closes #1562) (#1563)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-30 17:09:16 +02:00
github-actions[bot]andamacocian f4a08e36dd Bump version to 0.9.10.12 2026-06-30 17:09:16 +02:00
amacocian c71dd6ed9c Update docs (#1559) 2026-06-29 22:36:44 +02:00
amacocianandAlexandru Macocian a61cff8dfd Focus View Grid layout and resizable components (#1558) (Closes #1552)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-29 22:36:44 +02:00
amacocianandAlexandru Macocian 9d7f145c25 Sign windows binaries (#1557)
* Sign windows binaries

* Fix temp test

* Remove temporary test

---------

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-29 22:36:44 +02:00
amacocianandAlexandru Macocian dc340189d6 Improve x11 support in Linux with GDK X11 fallback and scale support (#1556) (Closes #1551)
* Fix GDK X11 support

* Better scaling support for Linux

---------

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-29 22:36:44 +02:00
amacocianandAlexandru Macocian 5edb5ff780 Update dependencies + move to StbImageSharp (#1555)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-29 22:36:44 +02:00
amacocianandAlexandru Macocian 890bb26c01 Make Daybreak.API compile statically against GWCA (#1554)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-29 22:36:44 +02:00
amacocianandAlexandru Macocian 6c2574c722 Debug scripts improvements (#1553)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-06-29 22:36:44 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8760f743ab Bump Microsoft.Web.WebView2 from 1.0.3967.48 to 1.0.4022.49 (#1548)
---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-version: 1.0.4022.49
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 22:36:44 +02:00
dependabot[bot]andamacocian 97da183bd3 Bump actions/checkout from 6 to 7 (#1549) 2026-06-29 22:36:44 +02:00
github-actions[bot]andamacocian 65439f25dc Bump version to 0.9.10.11 2026-06-29 22:36:44 +02:00
amacocian a755319fb1 Add value to inventory service response (Closes #1545) (#1546) 2026-06-03 22:09:58 +02:00
amacocian 7e72cb3d3f Add more testing coverage (#1543) 2026-06-03 22:09:58 +02:00
amacocian ca154827b2 Setup linux api build and some tests (#1542) 2026-06-03 22:09:58 +02:00
amacocian 089a10a056 Reorganize skills (#1541)
* Reorganize skills

* Minor fixes
2026-06-03 22:09:58 +02:00
amacocian 65767d6ab5 Fix build paths for linux (Closes #1539) (#1540) 2026-06-03 22:09:58 +02:00
github-actions[bot]andamacocian 5828263717 Bump version to 0.9.10.10 2026-06-03 22:09:58 +02:00
amacocian 05f73cb306 GWCA fixes (#1538) 2026-05-13 14:37:06 +02:00
amacocian 0271e6b08c Fix gwca generator (#1537) 2026-05-13 14:37:06 +02:00
amacocian 640f28584e Implement logsview filter (#1535) 2026-05-13 14:37:06 +02:00
amacocian dd89e3316e Improve toolbox integration (Closes #1532) (#1534) 2026-05-13 14:37:06 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
10fb70787b Bump Microsoft.Web.WebView2 from 1.0.3912.50 to 1.0.3967.48 (#1533)
---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-version: 1.0.3967.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 14:37:06 +02:00
github-actions[bot]andamacocian 1393223826 Bump version to 0.9.10.9 2026-05-13 14:37:06 +02:00
amacocian b23907025d Update dependencies (#1530) 2026-04-29 15:48:06 +02:00
amacocian 09aed18d9d Fix executable update (Closes #1526) (#1529) 2026-04-29 15:48:06 +02:00
amacocian b7d58f5e4c Update GWCA (Closes #1527) (#1528) 2026-04-29 15:48:06 +02:00
github-actions[bot]andamacocian 9888bc741f Bump version to 0.9.10.8 2026-04-29 15:48:06 +02:00
amacocian c1b2ed7548 Revert Daybreak placeholder (#1522) 2026-04-20 14:01:37 +02:00
github-actions[bot]andamacocian c23a44a313 Bump version to 0.9.10.7 2026-04-20 14:01:37 +02:00
amacocian b217f183cb Replace Swagger UI with Scalar and OpenApi (Closes #1517) (#1520) 2026-04-20 12:03:58 +02:00
amacocian d24a0a49e8 Update GWCA (Closes #1515) (#1519) 2026-04-20 12:03:58 +02:00
amacocian cdd620a786 Update dependencies (#1518) 2026-04-20 12:03:58 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
abbf87184b Bump vers-one/dotnet-project-version-updater from 1.7 to 1.8 (#1508)
Bumps [vers-one/dotnet-project-version-updater](https://github.com/vers-one/dotnet-project-version-updater) from 1.7 to 1.8.
- [Release notes](https://github.com/vers-one/dotnet-project-version-updater/releases)
- [Commits](https://github.com/vers-one/dotnet-project-version-updater/compare/v1.7...v1.8)

---
updated-dependencies:
- dependency-name: vers-one/dotnet-project-version-updater
  dependency-version: '1.8'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 12:03:58 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
15fa4520aa Bump Microsoft.CodeAnalysis.Analyzers from 5.3.0-2.25625.1 to 5.3.0 (#1509)
---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.Analyzers
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 12:03:58 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bc963f486a Bump actions/github-script from 8 to 9 (#1511)
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 12:03:58 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
66386a6b85 Bump Microsoft.Web.WebView2 from 1.0.3800.47 to 1.0.3912.50 (#1512)
---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-version: 1.0.3912.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 12:03:58 +02:00
amacocian 8d34d5d774 Make character name whitespace (Closes #1507) (#1516) 2026-04-20 12:03:58 +02:00
amacocian d5c8e7ad03 Remove reloaded dependency (use native bytecode) (#1514) 2026-04-20 12:03:58 +02:00
github-actions[bot]andamacocian 8eba2b92dd Bump version to 0.9.10.6 2026-04-20 12:03:58 +02:00
amacocianandAlexandru Macocian d9bd9d5395 Update GWCA (#1505)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-03-13 15:13:57 +01:00
amacocianandAlexandru Macocian a59f88d396 Fix launch configuration reordering (Closes #1488) (#1504)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-03-13 15:13:57 +01:00
amacocianandAlexandru Macocian 3799108f7e Fix ReShade deleting files when disabled and not installed (Closes #1500) (#1503)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-03-13 15:13:57 +01:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a7f0f31a94 Bump Microsoft.CodeAnalysis.Analyzers and Microsoft.CodeAnalysis.CSharp (#1501)
Bumps Microsoft.CodeAnalysis.Analyzers from 3.11.0 to 5.3.0-2.25625.1
Bumps Microsoft.CodeAnalysis.CSharp from 5.0.0 to 5.3.0

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.Analyzers
  dependency-version: 5.3.0-2.25625.1
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: Microsoft.CodeAnalysis.CSharp
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 15:13:57 +01:00
amacocian 6ffa774270 Update GWCA (Closes #1494 Closes #1483) (#1498) 2026-03-13 15:13:57 +01:00
amacocianandAlexandru Macocian 2124ae92b4 Fix template loading from API (#1495)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-03-13 15:13:57 +01:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
00312a6367 Bump actions/download-artifact from 7 to 8 (#1492)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 15:13:57 +01:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
df0d32bc30 Bump actions/upload-artifact from 6 to 7 (#1493)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 15:13:57 +01:00
Brendan McCoyandamacocian ecd19b0324 Add dye tint support to inventory item model (#1490)
Switched to using GWCA's GetBagByIndex for safer bag pointer access.
2026-03-13 15:13:57 +01:00
github-actions[bot]andamacocian aa2e9905c1 Bump version to 0.9.10.5 2026-03-13 15:13:57 +01:00
amacocian 384fdaf67c Fix tests (#1487) 2026-02-26 14:13:40 -08:00
amacocian a48cd84e1a Remove character flag when provided by launch config (Closes #1480) (#1485) 2026-02-26 14:13:40 -08:00
amacocianandAlexandru Macocian 97fed608d2 Update GWCA (#1484)
* Improve GWCA bindings generator based on headers

* Move all structs to gwca

* Progress

* Complete parser work

* More generator adjustments

* GWCA fixes

* GWCA improvements

* GWCA fixes

* GWCA fixes

* GWCA fixes

* GWCA fixes

* Fix API usage of GWCA

* Update GWCA

---------

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-26 14:13:40 -08:00
amacocian 68f241989a Update GWCA (Closes #1477 Closes #1474) (#1482) 2026-02-26 14:13:40 -08:00
amacocian 123d76fe0f Fix stackoverflow risk on ScreenshotService (Closes #1476) (#1481) 2026-02-26 14:13:40 -08:00
Brendan McCoyandamacocian 9d495a35a6 Add Interaction and ModelFileID to Daybreak API InventoryService (#1478)
* Add Interaction and ModelFileId properties to ItemEntry

Signed-off-by: Brendan McCoy  <13607368+brendan-mccoy@users.noreply.github.com>

* Add Interaction and ModelFileId to inventory items

Signed-off-by: Brendan McCoy  <13607368+brendan-mccoy@users.noreply.github.com>

---------

Signed-off-by: Brendan McCoy  <13607368+brendan-mccoy@users.noreply.github.com>
2026-02-26 14:13:40 -08:00
amacocian 5d9d59610a Templates rework (#1479)
* Setup templates rework

* Update docs

* Implement backwards compatible parsing

* Work

* Finish build templates
2026-02-26 14:13:40 -08:00
amacocian 0bdfe69e78 Update docs with details about templates (#1475) 2026-02-26 14:13:40 -08:00
github-actions[bot]andamacocian 8fd1584995 Bump version to 0.9.10.4 2026-02-26 14:13:40 -08:00
amacocian 5da5eea085 Build template manager bugfix (#1472) 2026-02-24 06:37:10 -08:00
amacocian fd9d8934f4 Update docs (Closes #1469) (#1471) 2026-02-24 06:37:10 -08:00
amacocian b9736f051a Cap attribute points in build editor (Closed #1467) (#1470) 2026-02-24 06:37:10 -08:00
amacocian 724e264b77 Builds synchronization view (Closes #1289) (#1468) 2026-02-24 06:37:10 -08:00
amacocian 4974d5f9ed Fix exceptions when mods are enabled but not installed (Closes #1459) (#1466) 2026-02-24 06:37:10 -08:00
amacocian aa1da65538 Ability to reorder launch configs (Closes #1460) (#1465) 2026-02-24 06:37:10 -08:00
amacocian e90ae48c40 Fix item upgrade collisions (Closes #1461) (#1464) 2026-02-24 06:37:10 -08:00
amacociangithub-actions[bot] <github-actions[bot]@users.noreply.github.com>dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Alexandru Macocian
ef513e1230 Gwca bindings (#1462)
* Bump version to 0.9.10.1

* Bump Microsoft.Web.WebView2 from 1.0.3719.77 to 1.0.3800.47 (#1442)

---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-version: 1.0.3800.47
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Improve mod view responsiveness (#1443)

* Improve application launcher logging (Closes #1441) (#1444)

* Damage modifier 0x248 (Closes #1438) (#1445)

* Initial commit

* Setup generator

* Progress

* Update generator

* Initial commit

* Setup generator

* Progress

* Update generator

* Fix library loading

* Move code to gwca

* Fix leave party

* Disable console on Release

* Add more exports to gwca

* Upgrade gwca

* Fixes to character selector

* Fix GetWindowHandle

* Move character selector to frame based

* Fix config spamming

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-24 06:37:10 -08:00
github-actions[bot]andamacocian 72fcf64589 Bump version to 0.9.10.3 2026-02-24 06:37:10 -08:00
amacocian 53fc08bcb5 Simplify readychecker (#1457)
* Fix windows ready checker (Closes #1456)

* Add extra debugging line

* Extra debug info
2026-02-19 08:23:43 -08:00
amacocian 4077777cd9 Deprecate mDNS scanning (Closes #1454) (#1455) 2026-02-19 08:23:43 -08:00
amacocian 77c2403a40 Fix companion view attach after restart (Closes #1452) (#1453) 2026-02-19 08:23:43 -08:00
github-actions[bot]andamacocian fa3c5e95a6 Bump version to 0.9.10.2 2026-02-19 08:23:43 -08:00
amacocian db012ae094 Update dependencies (#1450) 2026-02-18 12:23:33 -08:00
amacocian d124197e43 Fix installer temp (Closes #1436) (#1449) 2026-02-18 12:23:33 -08:00
amacocian 85593ea94b Linux support for color extraction (Closes #1447) (#1448) 2026-02-18 12:23:33 -08:00
amacocian fe9178c66d Damage modifier 0x248 (Closes #1438) (#1445) 2026-02-18 12:23:33 -08:00
amacocian e5e4e42e1e Improve application launcher logging (Closes #1441) (#1444) 2026-02-18 12:23:33 -08:00
amacocian cc3236f4d6 Improve mod view responsiveness (#1443) 2026-02-18 12:23:33 -08:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6d598cdf95 Bump Microsoft.Web.WebView2 from 1.0.3719.77 to 1.0.3800.47 (#1442)
---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-version: 1.0.3800.47
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-18 12:23:33 -08:00
github-actions[bot]andamacocian 974975eb78 Bump version to 0.9.10.1 2026-02-18 12:23:33 -08:00
amacocian 4346ed758b Fix compare version script (#1435) 2026-02-13 06:25:46 -08:00
amacocianandAlexandru Macocian a7320ac32c Update dependencies (#1433)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-13 06:25:46 -08:00
amacocian dce365b227 Update wiki (Closes #1428) (#1432) 2026-02-13 06:25:46 -08:00
amacocian 3ad32bee8f Update gwmarket url (Closes #1430) (#1431) 2026-02-13 06:25:46 -08:00
amacocian 9e3fa738bb DirectSong improvements (#1429) (Closes #1189)
* DirectSong improvements

* Use ChthonVIIs directsong remix

* DirectSong fetches remix mp3s

* Stop using ds executable to register entries on Windows

* DirectSong don't rely on admin rights

* Windows fixes
2026-02-13 06:25:46 -08:00
github-actions[bot]andamacocian 4a6eccca24 Bump version to 0.9.10.0 2026-02-13 06:25:46 -08:00
amacocian b1898c460c Bump versions 2026-02-12 11:35:38 +01:00
amacocian 5c39a99b8f Use PAT to create release branches 2026-02-12 11:32:23 +01:00
amacocian e873b0da6f Cleanup pipelines 2026-02-12 11:25:00 +01:00
amacocian 9d9c533df7 Update documentation (#1426) 2026-02-11 14:22:43 -08:00
amacocian 9f15df9b6d Color-coded launch configurations (Closes #1319) (#1425) 2026-02-11 14:22:43 -08:00
amacocian 5447633500 DXVK Support for Linux (Closes #1394)
* DXVK support (Closes #1394)

* Enable DXVK only on Linux for now
2026-02-11 14:22:43 -08:00
amacocian 47369851f2 Configuration scoped modlist (#1423) (Closes #1225)
* Mod selection for each launch configuration (Closes #1225)

* Store enabled mods with the launched application
2026-02-11 14:22:43 -08:00
amacocianandAlexandru Macocian 176365b4a4 Build improvements (#1422)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-11 14:22:43 -08:00
amacocian ca526d352e Release 0.9.9.108 (#1421) 2026-02-11 14:22:43 -08:00
amacocian c2d30ac60c Fix changelog (Closes #1417) (#1419) 2026-02-10 06:06:54 -08:00
amacocian 3984b5a66d Make installer executable (Closes #1416) (#1418) 2026-02-10 06:06:54 -08:00
amacocian 5be32e32ac Release 0.9.9.107 (#1415) 2026-02-10 06:06:54 -08:00
amacocian 5102ad0fe7 Fix hotkeys running when window is in focus (Closes #1411) (#1413) 2026-02-10 05:40:05 -08:00
amacocian 067da8a091 Fix updater and installer (Closes #1407) (#1412) 2026-02-10 05:40:05 -08:00
amacocian 20d0bade79 Increase duration of startup notifications (Closes #1408) (#1410) 2026-02-10 05:40:05 -08:00
amacocian a40c28711e Release 0.9.9.106 (#1409) 2026-02-10 05:40:05 -08:00
amacocian 1b878a73ae Fix injector properties 2026-02-09 20:18:11 +01:00
amacocian f4bbd3dbc6 Make option synchronization cross platform (Closes #1390) (#1405) 2026-02-09 10:46:23 -08:00
amacocian cd46572c22 Window manipulation fixes (Closes #1391) (#1404) 2026-02-09 10:46:23 -08:00
amacocian 33dc001604 Flag to disable launch when mods are outdated (Closes #1388) (#1403) 2026-02-09 10:46:23 -08:00
amacocian c9676d59d1 Fix missing dependency notification after first launch (Closes 1389) (#1402) 2026-02-09 10:46:23 -08:00
amacocian 7f05738a63 Make injector global invariant (Closes #1393) (#1401) 2026-02-09 10:46:23 -08:00
amacocian f6687e0c72 Release pipeline improvements (#1400) 2026-02-09 10:46:23 -08:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1bf2fb07cc Bump WyriHaximus/github-action-get-previous-tag from 1 to 2 (#1395)
Bumps [WyriHaximus/github-action-get-previous-tag](https://github.com/wyrihaximus/github-action-get-previous-tag) from 1 to 2.
- [Release notes](https://github.com/wyrihaximus/github-action-get-previous-tag/releases)
- [Commits](https://github.com/wyrihaximus/github-action-get-previous-tag/compare/v1...v2)

---
updated-dependencies:
- dependency-name: WyriHaximus/github-action-get-previous-tag
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 10:46:23 -08:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c826849bb7 Bump actions/download-artifact from 4 to 7 (#1396)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 10:46:23 -08:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
82fdf5f18b Bump actions/upload-artifact from 4 to 6 (#1397)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 10:46:23 -08:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5216cfcea3 Bump Microsoft.Web.WebView2 from 1.0.3650.58 to 1.0.3719.77 (#1398)
---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-version: 1.0.3719.77
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 10:46:23 -08:00
amacocian a6bee38a19 Release 0.9.9.105 (#1399) 2026-02-09 10:46:23 -08:00
amacocianandAlexandru Macocian 2394059ca3 Fix codeql (#1387)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-08 12:28:44 -08:00
amacocian d4fb2e0545 Fix pipelines (#1386) 2026-02-08 12:28:44 -08:00
amacocian cd52c20977 Update documentation (#1384) 2026-02-08 12:28:44 -08:00
amacocian 2070ae2689 Fix wiki (Closes #1379) (#1383) 2026-02-08 12:28:44 -08:00
amacocianandAlexandru Macocian 3ca5117875 Make application DPI aware (Closes #1369) (#1381)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-08 12:28:44 -08:00
amacocian ea0a7a1edb Screen position toggle (Closes #1379) (#1380) 2026-02-08 12:28:44 -08:00
amacocian 824881c108 Native linux support (#1378) (Closes #1375 Closes #1376 Closes #1377 Closes #1374 Closes #1373 Closes #1372 Closes #1371) 2026-02-08 12:28:44 -08:00
amacocian 77e3d5ddf2 Pass arguments to the photino window and webview (#1370) 2026-02-08 12:28:44 -08:00
amacocian 9626d7a42e Make build more linux friendly (#1368) 2026-02-08 12:28:44 -08:00
amacocian 75a6fb2b6c Release 0.9.9.104 (#1367) 2026-02-08 12:28:44 -08:00
amacocian d887f2c901 Make build code writable (Closes #1358) (#1364) 2026-02-01 13:32:13 +01:00
amacocian b079be2445 Fix exception when saving new builds (Closes #1359) (#1363) 2026-02-01 13:32:13 +01:00
amacocian f26ac481e9 Fix version checker updater (Closes #1360) (#1362) 2026-02-01 13:32:13 +01:00
amacocian 6bfa52a0bf Release 0.9.9.103 (#1361) 2026-02-01 13:32:13 +01:00
amacocian 0ce4940342 Fix character select (Closes #1348) (#1356) 2026-01-30 21:57:59 +01:00
amacocian 969d24640f Increase update notification timeout (Closes #1350) (#1355) 2026-01-30 21:57:59 +01:00
amacocian 51916c0707 Reset window position launch argument (Closes #1353) (#1354) 2026-01-30 21:57:59 +01:00
amacocian 05910734fc Update wiki with plugin list (Closes #1342) (#1352) 2026-01-30 21:57:59 +01:00
amacocian 55ccc6e367 Release 0.9.9.102 (#1351) 2026-01-30 21:57:59 +01:00
amacocian 6391b13cb4 Fix quest log parsing (Closes #1344) (#1346) 2026-01-28 21:00:56 +01:00
amacocian 32f08793ed Release 0.9.9.101 (#1345) 2026-01-28 21:00:56 +01:00
amacocian d6237764d6 Show build snippet on build list view (Closes #1330) (#1340) 2026-01-23 18:57:39 +01:00
amacocian 15ff789942 Adjust build metadata logging (Closes #1334) (#1339) 2026-01-23 18:57:39 +01:00
amacocian f4c4b5bac2 Remove metricsview (Closes #1333) (#1338) 2026-01-23 18:57:39 +01:00
amacocian 26b02ac94d Improve telemetry (#1337) (Closes #1335)
* Improve telemetry (Closes #1335)

* Fix CD pipeline
2026-01-23 18:57:39 +01:00
amacocian 1f8d4c126e Improve x86 __fastcall wrapper (Closes #1331) (#1332) 2026-01-23 18:57:39 +01:00
amacocian 8306e10ffe Release 0.9.9.100 (#1329) 2026-01-23 18:57:39 +01:00
amacocian cc5fe38daa Improve Skill.None display in build snippet (Closes #1316) (#1327) 2026-01-20 12:37:08 +01:00
amacocian cc1bd1d77f Fix telemetry warnings (Closes #1325) (#1326) 2026-01-20 12:37:08 +01:00
amacocian 437210858c Fix gwmarket clipboard API permissions (Closes #1318) (#1324) 2026-01-20 12:37:08 +01:00
amacocian 47b7a480bf Update gwmarket to v2 (Closes #1321) Allow downloads for gwmarket (Closes #1318) (#1323) 2026-01-20 12:37:08 +01:00
amacocian fe66d8e8b0 Add more item modifiers (#1320)
* Add more item modifiers

* Fix armor penetration mod
2026-01-20 12:37:08 +01:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4b138e02d4 Bump actions/github-script from 7 to 8 (#1317)
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-20 12:37:08 +01:00
amacocian 32bc16c695 Release 0.9.9.99 (#1315) 2026-01-20 12:37:08 +01:00
amacocian 0fae63f902 Update readme (Closes #1311) (#1313)
* Update README.md with new build details (Closes #1311)

* Change image source in README.md

Updated image link in README.

Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>

---------

Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2026-01-18 21:20:03 +01:00
amacocian 2447f7e517 Update wiki (Closes #1310) (Closes #1294) (#1312) 2026-01-18 21:20:03 +01:00
amacocian f5483352f0 Add more modifiers (#1309) 2026-01-18 21:20:03 +01:00
amacocian f081858d60 Release 0.9.9.98 (#1308) 2026-01-18 21:20:03 +01:00
amacocian 446f5cffd8 Update wiki with plugin details (Closes #1298) (#1306) 2026-01-17 17:18:07 +01:00
amacocian 21b9cf1726 Setup embedded file provider (Closes #1298) (#1305) 2026-01-17 17:18:07 +01:00
PsidreFelixandamacocian 4f1398a8d1 Added backdrop image 'dervish.png' to wwwroot/img directory. (#1304)
Enabled new theme 'Dervish' in CoreThemes.cs
2026-01-17 17:18:07 +01:00
amacocian 072f00638c Fix title info API (Closes #1290) (#1303) 2026-01-17 17:18:07 +01:00
amacocian f7f22532bf Trade chat iframes allow clipboard access (Closes #1291) (#1302) 2026-01-17 17:18:07 +01:00
amacocian a565017ad2 Label merged issues (#1301) 2026-01-17 17:18:07 +01:00
amacocian 8b5f5c19e4 Simplify update process (Closes #1299) (#1300) 2026-01-17 17:18:07 +01:00
amacocian 6d260a927d Build component improvements (Closes #1292) (#1296)
* Improve build component UX

* Make builds show pop-up
2026-01-17 17:18:07 +01:00
amacocian 97eb3af350 Release 0.9.9.97 (#1293) 2026-01-17 17:18:07 +01:00
amacocian ad70e80d4d Item modifiers parser (#1287)
* Parse item modifiers

* Move everything to System.Text.Json

* Complete item parser

* Add some more energy + health mods

* Fix more mods

* Output ids as hex
2026-01-13 21:41:04 +01:00
amacocianandAlexandru Macocian dcffce5298 Release 0.9.9.96 (#1285)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-13 21:41:04 +01:00
amacocianandAlexandru Macocian 9ddcb6ba80 Improve changelog when missing Metadata.json (Closes #1278) (#1283)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-12 13:38:52 +01:00
amacocianandAlexandru Macocian d77cefb699 Reduce the incidence of github actions (#1282)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-12 13:38:52 +01:00
amacocianandAlexandru Macocian bc8917692b Fix installer working directory (Closes #1279) (#1281)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-12 13:38:52 +01:00
amacocian 02409f10fe Release 0.9.9.95 (#1277) 2026-01-12 13:38:52 +01:00
amacocian f167ee538a Update wiki and help menu (#1275) 2026-01-12 02:23:36 +01:00
amacocian c5dccb67dc Implement GwMarket view (Closes #1273) (#1274) 2026-01-12 02:23:36 +01:00
amacocian 2cb105acde Fix Guild Wars copy functionality (Closes #1263) (#1272) 2026-01-12 02:23:36 +01:00
amacocian fbc0c39a37 Fix taskbar icon (#1271) 2026-01-12 02:23:36 +01:00
amacocian 1da19b2e1d Fix restart (Closes #1267) (#1270) 2026-01-12 02:23:36 +01:00
amacocian ef2076022e Release 0.9.9.94 (#1266) 2026-01-12 02:23:36 +01:00
amacocianandGitHub d6ad8429c8 Add options example to SimplePlugin (#1265) 2026-01-11 22:03:46 +00:00
amacocianandGitHub 7535be7134 Update SimplePlugin example (#1264) 2026-01-11 21:49:43 +00:00
amacocian 33f8f73066 Hotfix Win10 (Closes #1529) and installer (Closes #1528) (#1260) 2026-01-11 15:51:58 +01:00
amacocian 4c29a5617b Disable VirusTotal check in CD (Closes #1256) (#1257)
Related Work Items: #125
2026-01-11 15:51:58 +01:00
amacocian 2116285cac Release 0.9.9.93 (#1255) 2026-01-11 15:51:58 +01:00
amacocian e85c286120 Fix inventory endpoint not returning all items (Closes #1235) (#1253) 2026-01-11 04:03:52 +01:00
amacocian 730722a0e0 Setup two-step plugin loading sequence (Closes #1244) (#1251) 2026-01-11 04:03:52 +01:00
amacocian 8b90f13bd5 Repair tool exclude tpf files (Closes #1240) (#1250) 2026-01-11 04:03:52 +01:00
amacocian 1360c86395 Fix copy functionality (Closes #1246) (#1249) 2026-01-11 04:03:52 +01:00
amacocian f6f0afc5d6 Fix auto-launch (Closes #1080) (#1248) 2026-01-11 04:03:52 +01:00
amacocian 194fca5861 Update wiki (#1247) 2026-01-11 04:03:52 +01:00
amacocian 114ddacfcc Upgrade renderer (#1245). Fix log view fetching logs. Disable version checker. Closes #1243 #1242 #1239 #1226 2026-01-11 04:03:52 +01:00
amacocianandAlexandru Macocian dc5dfc8b07 Release 0.9.9.92 (#1241)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-11 04:03:52 +01:00
amacocian 976586649e Hotfix installer (#1233) 2026-01-07 22:41:42 +01:00
amacocian 4322b636ef Release 0.9.9.91 (#1232) 2026-01-07 22:41:42 +01:00
amacocian cd8581f86a Setup log rotation policy for Daybreak.API (Closes #1220) (#1230) 2026-01-07 22:04:49 +01:00
amacocian 9417a1a486 Repurpose installer into a repair tool (Closes #1100) (#1229) 2026-01-07 22:04:49 +01:00
amacocian f838d16543 Extract injector to its own executable. Convert Daybreak to x64 (#1228) (Closes #1222 #1223) 2026-01-07 22:04:49 +01:00
amacocian 0ec1c9c96f Pin WebView WPF packages to lower stable version due to performance reasons (#1226) (#1227)
Related Work Items: #1225
2026-01-07 22:04:49 +01:00
amacocian bab997b64a Update dependencies (#1224)
* Update dependencies

* UI fixes
2026-01-07 22:04:49 +01:00
amacocian a0c6b7d956 Release 0.9.9.90 (#1219) 2026-01-07 22:04:49 +01:00
amacocian fea568959b Fix dynamic theme crash (Closes #1211) (#1217) 2026-01-05 14:46:18 +01:00
amacocian 443582fda6 Add item type to inventory response (#1216) 2026-01-05 14:46:18 +01:00
amacocian 54e4c17af2 Release 0.9.9.89 (#1215) 2026-01-05 14:46:18 +01:00
amacocian 645c94f21b More handling for Dynamic Theme errors (Addresses #1211) (#1213) 2026-01-05 13:49:38 +01:00
amacocian 8e7b12b760 Inventory api (#1212)
* Expose inventory API

* Update wiki
2026-01-05 13:49:38 +01:00
amacocian 3e6ac4149f Release 0.9.9.88 (#1210) 2026-01-05 13:49:38 +01:00
amacocian ebe045a962 Installer improvements (Closes #1203) (#1207) 2026-01-04 12:57:07 +01:00
amacocian 5298ab1e83 Handle dynamic screenshots exceptions (Closes #1204) (#1206) 2026-01-04 12:57:07 +01:00
amacocian 64df882406 Release 0.9.9.87 (#1205) 2026-01-04 12:57:07 +01:00
amacocianandAlexandru Macocian 9dd39c0d93 Rework character select (#1201)
* Rework character select

* Change index by frames

* Fixes

---------

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-02 20:15:00 +02:00
amacocianandAlexandru Macocian 2f5108fd3b Fix party loadout loading (#1200)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-02 20:15:00 +02:00
amacocianandAlexandru Macocian 9510fdc5de Setup CORS policy for Daybreak API (Closes #1198) (#1199)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-02 20:15:00 +02:00
amacocianandAlexandru Macocian 09781b5516 Synchronize trade chat light/dark mode with Daybreak (Closes #1169) (#1197)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-02 20:15:00 +02:00
amacocianandAlexandru Macocian 3044861652 Switch to DateTime from DateTimeOffset (Addresses #1187) (#1195)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-02 20:15:00 +02:00
amacocianandAlexandru Macocian 8077d36e77 Simplify launch button logic (#1194)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-02 20:15:00 +02:00
amacocianandAlexandru Macocian 4ac9daa984 Release 0.9.9.86 (#1192)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-01-02 20:15:00 +02:00
amacocianandAlexandru Macocian f1bfa66e79 Fix for load build into game (Closes #1181) (#1190)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-31 20:31:38 +02:00
amacocianandAlexandru Macocian a4485fc60f Fix dropdown UI for launch configs (Closes #1177) (#1186)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-31 20:31:38 +02:00
amacocianandAlexandru Macocian 04c9d86ac4 Uniformize executables and launch configs UI (Closes #1178) (#1185)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-31 20:31:38 +02:00
amacocianandAlexandru Macocian 0d30adf63b Update mod management wiki (Closes #1176) (#1184)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-31 20:31:38 +02:00
amacocianandAlexandru Macocian 2949d47a29 Fix notification close button (Closes #1180) (#1183)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-31 20:31:38 +02:00
amacocianandAlexandru Macocian f300f81d00 Improve accessibility for Trade UIs (#1182)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-31 20:31:38 +02:00
amacocianandAlexandru Macocian 20ff35fb12 Release 0.9.9.85 (#1179)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-31 20:31:38 +02:00
amacocian 8c02b63051 Pop-up launch configuration path on hover (Closes #1167) (#1174) 2025-12-21 15:22:21 +01:00
amacocian 3bae3cf64f Support for mod uninstall (Closes #321) (#1173) 2025-12-21 15:22:21 +01:00
amacocian 948532a694 Support for mod updates (#1172) 2025-12-21 15:22:21 +01:00
amacocian c43195d0bc Fix notification time conversion (Closes #1163 #1168 #1165) (#1171) 2025-12-21 15:22:21 +01:00
amacocian 7d48fde5ea Deprecate sound support (#1170) 2025-12-21 15:22:21 +01:00
amacocianandAlexandru Macocian 69096795f7 Setup support for live themes (#1162)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-21 15:22:21 +01:00
amacocianandAlexandru Macocian 7b2320e158 Optimize package sizes (#1161)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-21 15:22:21 +01:00
amacocianandAlexandru Macocian baa2628c6f Release 0.9.9.84 (#1160)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-21 15:22:21 +01:00
amacocianandAlexandru Macocian 62a6a018e1 Improve wiki with GW install details (Closes #1064) (#1158)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian 77c15c9d63 Fix to allow searches in iframes (Closes #1146) (#1157)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian 0f338f5a53 Setup simple ReShade management view (Closes #1147) (#1156)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian a714fd7c68 gMod create notification on launch when mods are missing (Closes #1141) (#1155)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian 1e933a4764 Reword trade alerts setting (Closes #1145) (#1154)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian 6cfa0d8976 Launch view sets latest config (Closes #1140) (#1153)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian b7d3bf67cc Steam support (Closes #418) (#1152)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian 099d745c79 Allow empty accounts (Closes #1131) (#1151)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocianandAlexandru Macocian 4ed61b3cae Remove trader quotes settings (Closes #1144) (#1150)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-16 20:56:41 +01:00
amacocian fab37a5ea0 Release 0.9.9.83 (#1139) 2025-12-16 20:56:41 +01:00
amacocian 1baa373f48 Fix trade chat notification titles (Closes #1127) (#1136) 2025-12-12 15:39:58 +01:00
amacocian 1b234c249d Fix duplicate menus on hard refresh (Closes #1133) (#1135) 2025-12-12 15:39:58 +01:00
amacocianandAlexandru Macocian fc177a3007 Wiki view (Closes #1118) (#1134)
* Setup wiki files deploy

* Create wiki view (Closes #1118)

---------

Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-12 15:39:58 +01:00
amacocianandAlexandru Macocian de94b93f2d Fix screenshot service missing directory exception (Closes #1128) (#1130)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-12 15:39:58 +01:00
amacocianandAlexandru Macocian b211ab9409 Deprecate local database (#1129)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-12 15:39:58 +01:00
amacocianandAlexandru Macocian 440ccf4518 Add Daybreak wiki as a submodule (#1118) (#1126)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-12 15:39:58 +01:00
amacocianandAlexandru Macocian 3f7a1cc310 Release 0.9.9.82 (#1125)
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2025-12-12 15:39:58 +01:00
amacocian d3cce2e24e Add toggle for alerts in the alerts list (Closes #1117) (#1123) 2025-12-10 01:14:07 +01:00
amacocian 8fa05c8854 Launch button improvements (Closes #1086 #1114) (#1122) 2025-12-10 01:14:07 +01:00
amacocian d71ac6df06 Fix version management links (Closes #1113) (#1121) 2025-12-10 01:14:07 +01:00
amacocian cc3173ffe5 Rotating game screenshots theme (Closes #1115) (#1120) 2025-12-10 01:14:07 +01:00
amacocian a92f566119 Release 0.9.9.81 (#1119) 2025-12-10 01:14:07 +01:00
amacocian 8df32b1009 Fix for spinning widget on missing title (Closes #1091) (#1111) 2025-12-09 14:24:06 +01:00
amacocian 26cb97caf4 Daybreak.API writes a message when a build is loaded (Closes #1092) (#1110) 2025-12-09 14:24:06 +01:00
amacocian 66e2c5eb95 Fix gMod version parser (Closes #1105) (#1109) 2025-12-09 14:24:06 +01:00
amacocian 99444df04e Use gstatic (#1108)
* Internet service uses faster gstatic (Closes #1061)
2025-12-09 14:24:06 +01:00
amacocian b08d553c56 Fix telemetry host (Closes #1106) (#1107) 2025-12-09 14:24:06 +01:00
amacocian d7a48db83f Deprecate DXVK and DSOAL (#1104) 2025-12-09 14:24:06 +01:00
amacocian b5d7719f48 Adjust toolbox injection process. Closes #1101 (#1103) 2025-12-09 14:24:06 +01:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6dfdc7376e Bump actions/setup-dotnet from 4 to 5 (#1062)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 14:24:06 +01:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7ee7be4d37 Bump actions/checkout from 4 to 6 (#1079)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 14:24:06 +01:00
amacocian 3554249d80 Release 0.9.9.80 (#1102) 2025-12-09 14:24:06 +01:00
amacocian 5a6fca6fa7 Upgrade to .net 10 (#1089) 2025-12-08 22:39:48 +01:00
amacocian 8d0a53a812 Improve exception handling (Closes #1087) (#1088) 2025-12-08 22:39:48 +01:00
amacocian 794afa6c2c Blazor UI Migration (#1085)
* Migrate to Fluent UI

* Setup window events for Blazor WebView

* Setup basic FluentUI styling

* FluentUI links

* Setup views and viewmodel infra

* Remove old navigation logic

* Prepare an example of iframe embed

* Style window + navigation menu

* Setup page based navigation

* Add some UI error handling

* Introduce UI resiliency to exceptions

* Setup settings menu dropdown

* Basic setup for options views

* Setup reusable OptionBase

* Setup options views

* Basic styling

* Setup scoped css files.
Split App into components

* Hook option updates to OptionProvider

* Fix option dropdowns

* Setup multiple themes

* Make fonts scalable

* Move ViewModels under Views

* Launch view + styling

* Setup FocusView base

* Finish BuildListView implementation

* Base for build views

* Setup build routing

* Implement single build editor

* Implement skill description on hover

* Scrape skill information from the wiki and cache it locally

* Scrape and fix Guild Wars wiki info

* Improve mouse hover performance by querying position only when needed

* Skill snippet snaps to edges

* Setup accounts and executables view

* Launch configurations view

* Fix build skill icons
Attach skill types to skills
Scrape upkeep from wiki

* Fix skilltype overflow

* Adjust navigation menu

* Setup privilege elevation flow

* Setup MSAL

* Options synchronization view

* Telemetry view

* Version management and update view

* Setup plugins view

* Setup notifications panel

* Base for logs view

* Finish LogView

* Setup metrics view

* Setup UI logging interop

* GuildWarsPartySearch view implementation

* Setup gitlab mirror job

* Eventviewer base

* Remove gitlab mirror

* Remove mirror pipeline

* Fix metricsview crashes

* Extract progress component

* Setup trade chat views

* Setup Download view

* Extract progress styling

* Setup guild wars copy view

* Setup trade alerts

* Fix team template view skill selection

* Setup basic onboarding view

* Overhaul asynchronous progress operations and remove redundant versioning code

* Setup modsview base

* Setup modsview with mod states

* Setup mod installation process

* Cleanup more ui migration
Remove price history #1076

* Screen selector manage view

* Upgrade to .net 10
Closes #1078

* gMod management view

* Trader message notification

* ReShade management

* Update for toolbox

* Setup focusview fetching

* Setup CharacterComponent

* Setup vanquishing component

* Setup TitleInformationComponent

* Closes #1082
Fix API project for Guild Wars Reforged

* Small fixes + attempts to get new exe version

* FileId fixes

* Current Map and Player Resources widgets

* Quest log widget

* BuildComponent implementation

* Closes #1075
Embed wwwroot into the executable

* Cleanup

* Fixes to slnx

* Fix test project
2025-12-08 22:39:48 +01:00
amacocian 7d78a89d12 Migrate sln to slnx (#1056)
* Migrate sln to slnx

* Patch pipelines
2025-12-08 22:39:48 +01:00
amacocian dcb50e9da3 Release 0.9.9.79 (#1055) 2025-12-08 22:39:48 +01:00
amacocian 86e3579383 Adjust CD pipeline triggers for the component projects (Closes #1038) (#1053) 2025-07-26 22:09:54 +02:00
amacocian ea39346678 Remove screenshots that are no longer available (#1052) 2025-07-26 22:09:54 +02:00
amacocian e4cdc4f939 Fix chart drawing (#1051)
* Reorder dependencies

* Fix visual bug with graphs
2025-07-26 22:09:54 +02:00
amacocian 5acac1ae7a Update dependencies (#1050)
* Update dependencies (Closes #940)

* Update dependencies

* Chart adjustment
2025-07-26 22:09:54 +02:00
amacocian 8c0e734c71 Fix aggregate exception due to concurrency issue on db initialization at startup (Closes #1029) (#1047) 2025-07-26 22:09:54 +02:00
amacocian a56eb20aa0 Fix updater HttpRequestException when connection cannot be established (Closes #1042) (#1046) 2025-07-26 22:09:54 +02:00
amacocian 482854801c Fix format exception popping up during build parse (Closes #1041) (#1045) 2025-07-26 22:09:54 +02:00
amacocian 1a37b8b08f Release 0.9.9.78 (#1044) 2025-07-26 22:09:54 +02:00
amacocian 40d8280ba6 Sort party members by profession when applying party loadout (Closes #1030) (#1036) 2025-06-30 11:09:16 +02:00
amacocian 9533ef0607 Fix builds not showing in FocusView due to locked skills (Closes #1031) (#1035) 2025-06-30 11:09:16 +02:00
amacocian ac963f60b5 Fix build codes not applying due to unescaped url characters (Closes #1032) (#1034) 2025-06-30 11:09:16 +02:00
amacocian 5b6ea99f79 Release 0.9.9.77 (#1033) 2025-06-30 11:09:16 +02:00
amacocian 83ddafea62 Aggregate custom metrics when enabled (Closes #1024) (#1027) 2025-06-29 17:32:52 +02:00
amacocian ab1b604aec Code cleanup (#1026)
* Resolve warnings and move shared props to Directory.Build.props and Directory.Packages.props

* Cleanup more messages

* Fix more messages

* Fix build issues
2025-06-29 17:32:52 +02:00
amacocian d445d0c5f2 Release 0.9.9.76 (#1025) 2025-06-29 17:32:52 +02:00
amacocian f5216020b6 Bugfix for LaunchButton (Closes #1020) (#1022) 2025-06-27 13:59:45 +02:00
amacocian 1b7654f70f Allow users to load and save builds from Focus View (Closes #1018) (#1021) 2025-06-27 13:59:45 +02:00
amacocian 06288ec2a2 Release 0.9.9.75 (#1019) 2025-06-27 13:59:45 +02:00
amacocian 670fba36df Setup APM connection (#1016) 2025-06-25 20:26:30 +02:00
amacocian 0d74a9c15e Fix time parsing exception on price charts (Closes #989) (#1015) 2025-06-25 20:26:30 +02:00
amacocian e0729d00ff Setup opt-in telemetry service (under Diagnostics/Telemetry) (#1014)
* Setup opt-in telemetry service (under Diagnostics/Telemetry)

* Set application environment
2025-06-25 20:26:30 +02:00
amacocian 0adce88894 Release 0.9.9.74 (#1013) 2025-06-25 20:26:30 +02:00
amacocian 77bf5e5359 Allow mods to be reinjected if they detect they need to (#1010) (Closes #1005 #724 #639) 2025-06-24 18:44:42 +02:00
amacocian 5a92b916dd Improve Focus View performance and hooking availability (#1009) (Closes #1007) 2025-06-24 18:44:42 +02:00
amacocian accfed2440 Improve game discovery and attach performance (#1008) (Closes #1004) 2025-06-24 18:44:42 +02:00
amacocian bdc45eb4b3 Release 0.9.9.73 (#1006) 2025-06-24 18:44:42 +02:00
amacocian 6916138351 Speed up build process (#1003) 2025-06-23 18:51:14 +02:00
amacocian 741fb9059d Update builds (#1002) 2025-06-23 18:51:14 +02:00
amacocian 6c9e47e58e Fix CI (#1001) 2025-06-23 18:51:14 +02:00
amacocian 0c3873b737 Update extractor and api (#999) 2025-06-23 18:51:14 +02:00
amacocian 411f213d8b Fix Launch button to allow launching multiple configs from the same exe (#998) 2025-06-23 18:51:14 +02:00
amacocian a55c2b78e9 Update dependencies (#997) 2025-06-23 18:51:14 +02:00
amacocian da3d878d90 Enable re-attachment for multi-exe configs (#996) Closes #993 2025-06-23 18:51:14 +02:00
amacocian f08104a851 Implement Daybreak.API. Reorganize FocusView components (#995) 2025-06-23 18:51:14 +02:00
amacocian 052bcaa29d Greatly improve installer performance (#994) 2025-06-23 18:51:14 +02:00
amacocian 51edeae93a Further decouple shared project from main application (#991) 2025-06-23 18:51:14 +02:00
amacocian 6d12b2beaf Release 0.9.9.72 (#990) 2025-06-23 18:51:14 +02:00
amacocian 7347de58af Build editor improvements (Closes #776) (#986) 2025-05-16 13:41:10 +02:00
amacocian f1ee932f23 EventsView scroll to current/upcoming event (Closes #777) (#985) 2025-05-16 13:41:10 +02:00
amacocian cdbc81e310 Performance improvements to Build Synchronization View (Closes #980) (#984) 2025-05-16 13:41:10 +02:00
amacocian 629c6ed14f Support for ReShade latest version (Closes #926) (#983) 2025-05-16 13:41:10 +02:00
amacocian 6577db54bc Update README (#982) 2025-05-16 13:41:10 +02:00
amacocian 926e9b7b48 Release 0.9.9.71 (#981) 2025-05-16 13:41:10 +02:00
amacocian c8433210e3 Hotfix for missing DXVK (#978) 2025-05-15 16:43:07 +02:00
amacocian 3274405c51 Release 0.9.9.70 (Closes #976) (#977) 2025-05-15 16:43:07 +02:00
amacocian 01bde80c39 Use latest DXVK binaries (#974) 2025-05-15 14:18:23 +02:00
amacocian 3f810b8a94 Launch Configurations can select any available executable (Closes #970) (#973) 2025-05-15 14:18:23 +02:00
amacocian 5d7ba2598f DXVK support (Closes #231) (#972) 2025-05-15 14:18:23 +02:00
amacocian 670d6bcbd5 Bump version (#971) 2025-05-15 14:18:23 +02:00
amacocian 9690d649fa Fix plugin example (#968) 2025-05-14 16:15:27 +02:00
amacocian 821ce69547 Show vials of dye in price quotes view (Closes #966) (#967) 2025-05-14 16:15:27 +02:00
amacocian 4f524ada67 Change executable checkmark to foreground color (Closes #774) (#965) 2025-05-14 16:15:27 +02:00
amacocian 0c2234f31b Disable executables during update (Closes #779) (#964) 2025-05-14 16:15:27 +02:00
amacocian 70085efd39 Make AccountsView and ExecutablesView scrollable (Closes #956) (#963) 2025-05-14 16:15:27 +02:00
amacocian bfa2128868 Move Accounts, Executables and LaunchConfigurations on the first positions under Settings (Closes #957) (#962) 2025-05-14 16:15:27 +02:00
amacocian 9b0da9b13c Improve startup speed and responsiveness (Closes #959) (#961) 2025-05-14 16:15:27 +02:00
amacocian 721ee8e494 Huge application refactoring to better support plugins (#960)
* Move shared code to Shared project
* Move from Realm to Squealify
* Rename Daybreak.Shared namespaces
* Setup API project to expose a test API
2025-05-14 16:15:27 +02:00
amacocian 5458db891f Release 0.9.9.68 (#958) 2025-05-14 16:15:27 +02:00
amacocian 9d9c15d0f6 Update dependencies (#954) 2025-05-07 12:01:57 +02:00
amacocian 4f49fc1cdf Expose API for creating menu buttons (#953) 2025-05-07 12:01:57 +02:00
amacocian 7efc7ba698 Release 0.9.9.67 (#952) 2025-05-07 12:01:57 +02:00
amacocian a79a6eae98 Bugfix greyed-out launch button (#949) 2025-04-30 19:20:26 +02:00
amacocian 0801256f4c Release 0.9.9.66 (#948) 2025-04-30 19:20:26 +02:00
amacocian e0799dd100 Remove redundant operations on launch UI. Improve UI performance (#946) 2025-04-30 18:48:03 +02:00
amacocian 24f4607899 Release 0.9.9.65 (#945) 2025-04-30 18:48:03 +02:00
amacocian 7aee0dfc16 Bugfix for multi-launch UI freezes (Closes #941) (#943) 2025-04-29 15:05:45 +02:00
amacocian 5fcb2314bc Release 0.9.9.64 (#942) 2025-04-29 15:05:45 +02:00
amacocian f218b89f6c Match Guildwars folder name structures (#938) 2025-04-27 16:01:22 +02:00
amacocian cc607b37c9 Release 0.9.9.63 (#937) 2025-04-27 16:01:22 +02:00
amacocian f80ea8cf0c Inject toolbox before GW initialization (#935) (#927) 2025-04-26 15:48:54 +02:00
amacocian 39f6707d23 Release 0.9.9.62 (#934) 2025-04-26 15:48:54 +02:00
amacocian eb11029ece Update pipeline runtimes (#933) 2025-04-25 02:42:36 +02:00
amacocian 4583b5b2d3 Fix toolbox injection (Closes #927) (#931) 2025-04-25 02:42:36 +02:00
amacocian 8c01377667 Update .NET9 runtime (#930) 2025-04-25 02:42:36 +02:00
amacocian 9a130d71dd Release 0.9.9.61 (#929) 2025-04-25 02:42:36 +02:00
amacocian 018ed6c5b5 Update dependencies (#928) 2025-04-25 02:42:36 +02:00
amacocianandGitHub cb48248c82 Change VirusTotal publish folder (#913) 2024-12-11 17:18:54 +01:00
amacocianandGitHub e1284375e2 Add debugging info to CD pipeline (#912) 2024-12-11 12:34:00 +00:00
amacocianandGitHub d38d620a84 Add VirusTotal scanning to CD pipeline (#911) 2024-12-11 11:36:39 +00:00
amacocian ce810dafab Support for toolbox builds (#908)
Closes #866
2024-12-10 14:50:59 +01:00
amacocian aabc98e4fc Build template metadata with support for hero positions. Closes #906 (#907) 2024-12-10 14:50:59 +01:00
amacocian 7eb0b7cc4f Release 0.9.9.60 (#905) 2024-12-10 14:50:59 +01:00
amacocian f1eb829ac4 Fix non-persistent notifications showing up after expiring. Closes #901 (#903) 2024-12-07 15:37:53 +01:00
amacocian 4b03a5a16f Increase memory scanner responsiveness (#902) 2024-12-07 15:37:53 +01:00
amacocian 71c90100be Fix memory scanner deadlock. Closes #899 (#900) 2024-12-07 15:37:53 +01:00
amacocian ee512d130d Fix launch button text when Focus View is disabled. Closes #897 (#898) 2024-12-07 15:37:53 +01:00
amacocian 03d0759e57 Release 0.9.9.59 (#896) 2024-12-07 15:37:53 +01:00
amacocian 0db7628d66 Cleanup GWCA dependencies (#895) 2024-12-06 21:19:01 +01:00
amacocian d1d1ba6c08 Fix build pipelines (#894) 2024-12-06 21:19:01 +01:00
amacocian d16a88a9bc Remove submodules file (#893) 2024-12-06 21:19:01 +01:00
amacocian a9d93f4162 Update dependencies (#891) 2024-12-06 21:19:01 +01:00
amacocian 632b5a55ac Remove GWCA (#890) Closes #889 #887 2024-12-06 21:19:01 +01:00
amacocian 6424505631 Release 0.9.9.58 (#888) 2024-12-06 21:19:01 +01:00
amacocian dfc1d3ba0a View for Guild Wars Party Search (#880)
Closes #877
2024-11-22 20:19:04 +01:00
amacocian 5bee88016d Upgrade vulnerable testing package (#879) 2024-11-22 20:19:04 +01:00
amacocian 5a37250680 Release 0.9.9.57 (#878) 2024-11-22 20:19:04 +01:00
amacocian 1c4ccb9fa3 Update dependencies (#874) 2024-11-22 15:09:42 +01:00
amacocian 8403dadca7 Patch GWCA module to not rely on the GW Window to start the http server. To be reverted once GWCA is patched (#871) (#873) 2024-11-22 15:09:42 +01:00
amacocian 4ac4cb0886 Check and update toolbox version (#872) 2024-11-22 15:09:42 +01:00
amacocian 74f13d2447 Update GWCA (#869) 2024-11-22 15:09:42 +01:00
amacocian bf53793729 Release 0.9.9.56 (#868) 2024-11-22 15:09:42 +01:00
amacocian e69dbf6791 Fix error test (#864) 2024-11-17 16:25:36 +01:00
amacocian a0eafa8a13 Setup button for bug reporting (#862)
Overhaul bug exception window
Closes #784
2024-11-17 16:25:36 +01:00
amacocian b0a236beb4 Use safe DateTime conversion (#861) 2024-11-17 16:25:36 +01:00
amacocian c10b81f3a1 Release 0.9.9.55 (#860) 2024-11-17 16:25:36 +01:00
amacocian 6cd51d6584 Fix theme sync (#858)
Closes #857
2024-11-16 00:16:40 +01:00
amacocian a283117cc8 Remove Minimap and InventoryComponent (#856)
Closes #855
2024-11-16 00:16:40 +01:00
amacocian 329e64232c Stop spamming when datetime conversion fails. This is a temporary fix for the spam (#854)
Closes #852
2024-11-16 00:16:40 +01:00
amacocian feb3c9042e Release 0.9.9.54 (#853) 2024-11-16 00:16:40 +01:00
amacocian 71f15f56a4 Update dependencies (#850) 2024-11-13 15:10:02 +01:00
amacocian d37951b832 Update GWCA (#849) 2024-11-13 15:10:02 +01:00
amacocian 908a149df4 Release 0.9.9.53 (#848) 2024-11-13 15:10:02 +01:00
amacocian 38cf140dfa Update GWCA (#838) 2024-10-23 22:57:08 +02:00
amacocian d13e1a2d08 Migrate db engine to Realm (#837) 2024-10-23 22:57:08 +02:00
amacocian a0c7842b84 Update dependencies (#836) 2024-10-23 22:57:08 +02:00
amacocian de13b68633 Release 0.9.9.52 (#835) 2024-10-23 22:57:08 +02:00
amacocian f289602068 Update dependencies - fixes critical System.Text.Json vulnerability (#831) 2024-10-10 12:01:01 +02:00
amacocian 15f4ee8979 Release 0.9.9.51 (#830) 2024-10-10 12:01:01 +02:00
amacocian d826dea457 Use sell quotes. Add sell prices and buy prices to trade quote view (#825) 2024-10-07 11:44:47 +02:00
amacocian b89a6666a3 Update dependencies and replace deprecated packages (#824) 2024-10-07 11:44:47 +02:00
amacocian 1dc69fd0c2 Release 0.9.9.50 (#823) 2024-10-07 11:44:47 +02:00
amacocian 5005b07f09 Update GWCA module (#819) 2024-09-27 17:37:40 +02:00
amacocian 0b692165e0 Update dependencies (#818) 2024-09-27 17:37:40 +02:00
amacocian ffee0fdf4c Release 0.9.9.49 (#817) 2024-09-27 17:37:40 +02:00
amacocian 72b4cd5470 Update GWCA (#808) 2024-08-20 17:34:59 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dc9be53ace Bump MSTest.TestAdapter from 3.5.0 to 3.5.2 (#795)
Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.5.0 to 3.5.2.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.5.0...v3.5.2)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2024-08-20 17:34:59 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3a8a6b0f7f Bump MSTest.TestFramework from 3.5.0 to 3.5.2 (#797)
Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.5.0 to 3.5.2.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.5.0...v3.5.2)

---
updated-dependencies:
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-20 17:34:59 +02:00
amacocian 7cdd0534f6 Set version checker as first mod in the list (#807)
Closes #778
2024-08-20 17:34:59 +02:00
amacocian 4b14ab9312 Add database cleanup (#806)
Closes #791
2024-08-20 17:34:59 +02:00
amacocian 57e1ddb8da Release 0.9.9.48 (#805) 2024-08-20 17:34:59 +02:00
amacocian c7609c0f7e Update dependencies (#803) 2024-08-20 16:34:04 +02:00
amacocian 1eca8021d2 Fixes for stuttering on image and notification load (#802) 2024-08-20 16:34:04 +02:00
amacocian 0077f35c4d Fix a bug in credentials migrator that would delete newly created credentials (#801) 2024-08-20 16:34:04 +02:00
amacocian 28b0a0a138 Release 0.9.9.47 (#800) 2024-08-20 16:34:04 +02:00
amacocian cc7ce92013 Update dependencies (#789) 2024-07-18 12:15:25 +03:00
amacocian 5bd02e56ee Ignore operation canceled exceptions (#788) 2024-07-18 12:15:25 +03:00
amacocian 8c7cd0a562 Bugfix for executable paths exception (#787) 2024-07-18 12:15:25 +03:00
amacocian 3ab3f72366 Release 0.9.9.46 (#785) 2024-07-18 12:15:25 +03:00
amacocian 733883a6c2 Check executables for fixes and updates (Closes #763) (#772) 2024-07-07 16:55:34 +03:00
amacocian d1ed8aeaf6 Fix GWCA module (#771) 2024-07-07 16:55:34 +03:00
amacocian 66ad44b8ce Update GWCA (#770) 2024-07-07 16:55:34 +03:00
amacocian 18e51ae408 Fix Nahpui Quarter missing id (Closes #761) (#769) 2024-07-07 16:55:34 +03:00
amacocian 06940dbf38 Update dependencies (#768) 2024-07-07 16:55:34 +03:00
amacocian 875b29f8ba Daybreak maintains and updates executables (#767) 2024-07-07 16:55:34 +03:00
amacocian a953c3a0d3 Release 0.9.9.45 (#766) 2024-07-07 16:55:34 +03:00
amacocian ce198fcfc9 Update GWCA (#759) 2024-06-11 15:03:37 +02:00
amacocian 3d84648af2 Update test dependencies (#757) 2024-06-11 15:03:37 +02:00
amacocian 38bb43bba3 Daybreak paths now fall back to root Daybreak folder (Closes #716) (#756)
* Daybreak paths now fall back to root Daybreak folder (Closes #716)

* Fix some more paths
2024-06-11 15:03:37 +02:00
amacocian cc00709016 Show skill icons in skill list menu (Closes #747) (#755)
* Show skill icons in skill list menu (Closes #747)

* Remove unused code
2024-06-11 15:03:37 +02:00
amacocian ee02b3b49a Download Gw.exe directly (Closes #753) (#754)
* Download Gw.exe directly (Closes #753)

* Downloader improvements
2024-06-11 15:03:37 +02:00
amacocian d1d6b7d757 Update GWCA module (#752) 2024-06-11 15:03:37 +02:00
amacocian 4e1c0c4f50 Save dump on crash (Closes #749) (#751) 2024-06-11 15:03:37 +02:00
amacocian 2a7e2c90fc Update dependencies (#750) 2024-06-11 15:03:37 +02:00
amacocian 0dc957a99d Release 0.9.9.44 2024-06-11 15:03:37 +02:00
amacocian f3f0aeb97e Update GWCA (#736) 2024-05-22 13:09:53 +02:00
amacocian d6dbe6f2cd Freeze layouts calculations on resize (Closes #734) (#735) 2024-05-22 13:09:53 +02:00
amacocian 79da1801bc GWCA Debug Module (#733) 2024-05-22 13:09:53 +02:00
amacocian 2b692b2d88 Update GWCA (#732) 2024-05-22 13:09:53 +02:00
amacocian a7a98f7288 Release 0.9.9.43 (#731) 2024-05-22 13:09:53 +02:00
amacocian 5d27d9c380 Update GWCA plugin (#729) 2024-05-10 23:09:23 +02:00
amacocian fb4185b541 Update httplib (#728) 2024-05-10 23:09:23 +02:00
amacocian d2d6e87e12 Add timeouts to GWCA modules (Closes #725) (#727) 2024-05-10 23:09:23 +02:00
amacocian 354cdda109 Release 0.9.9.42 (#726) 2024-05-10 23:09:23 +02:00
amacocian 83429e264a Update GWCA (#723) 2024-05-09 10:33:05 +02:00
amacocian ffdf374cdd Allow mods to cancel Guild Wars startup (Closes #717) (#721) 2024-05-09 10:33:05 +02:00
amacocian aed34608ee Improve GWCA plugin logging (Closes #718) (#720) 2024-05-09 10:33:05 +02:00
amacocian 18981e131d Release 0.9.9.41 (#719) 2024-05-09 10:33:05 +02:00
amacocian fe0a77d89d Fix automatic screen placer (Closes #708) (#714) 2024-05-06 00:27:48 +03:00
amacocian dbfd555f0d Improve detection of Guild Wars launching successfully (Closes #712) (#713) 2024-05-06 00:27:48 +03:00
amacocian 85d4ac3a29 Remove price checker (now incl. in Toolbox) (Closes #707) (#711) 2024-05-06 00:27:48 +03:00
amacocian dcb983d3de Update GWCA (#710) 2024-05-06 00:27:48 +03:00
amacocian ba1e4348ad Release 0.9.9.40 (#709) 2024-05-06 00:27:48 +03:00
amacocian 04bc7cffc6 Update GWCA (Closes #696) (#705) 2024-05-01 12:35:14 +03:00
amacocian 01b74a936b Rewrite GWCA module for better maintainability (#702)
* Use game thread enqueue method

* Remove manual memory handling

* Use game thread enqueue method

* Remove manual memory handling

* Setup module classes
2024-05-01 12:35:14 +03:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
654b6bc10f Bump HtmlAgilityPack from 1.11.60 to 1.11.61 (#704)
Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.60 to 1.11.61.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.60...v1.11.61)

---
updated-dependencies:
- dependency-name: HtmlAgilityPack
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 12:35:14 +03:00
amacocian c9e6b9f626 Shortcut auto-place no longer removes arguments (Closes #695) (#701) 2024-05-01 12:35:14 +03:00
amacocian 554f1aea9b Fix extracted minimap not updating (Closes #698) (#700) 2024-05-01 12:35:14 +03:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6fb817855c Bump SharpCompress from 0.37.0 to 0.37.2 (#697)
Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.37.0 to 0.37.2.
- [Release notes](https://github.com/adamhathcock/sharpcompress/releases)
- [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.37.0...0.37.2)

---
updated-dependencies:
- dependency-name: SharpCompress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 12:35:14 +03:00
amacocian b3f5fadfe3 Release 0.9.9.39 (#699) 2024-05-01 12:35:14 +03:00
DubbleClick 9275b7a7f8 simpler PreGameModule, test this 2024-04-28 23:14:58 +02:00
DubbleClick 2d06ff85b8 ignore .idea folder 2024-04-28 23:00:38 +02:00
amacocian 07d06313d9 Support for GuildWars launch arguments (Closes #691) (#693) 2024-04-27 13:46:53 +02:00
amacocian 546409130c Auto launch configuration (Closes #689) (#692) 2024-04-27 13:46:53 +02:00
amacocian 82436872f8 Launch argument support (Closes #689) (#690) 2024-04-27 13:46:53 +02:00
amacocian ad6f76af0c Event notification expiration set to 15s (Closes #686) (#688) 2024-04-27 13:46:53 +02:00
amacocian 8f0ad91a22 Bundle quote alerts (Closes #676) (#687) 2024-04-27 13:46:53 +02:00
amacocian 6a70c5b7cc Fix 'yearss' typo in Trade chat (Closes #677) (#685) 2024-04-27 13:46:53 +02:00
amacocian 5f5d67fdf2 Release 0.9.9.38 (#684) 2024-04-27 13:46:53 +02:00
dependabot[bot]andamacocian 150b41647a Bump SharpCompress from 0.36.0 to 0.37.0 (#675)
Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.36.0 to 0.37.0.
- [Release notes](https://github.com/adamhathcock/sharpcompress/releases)
- [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.36.0...0.37.0)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 20:48:21 +02:00
amacocian 66e52e7324 Remove redundant GWCA calls on non-visible components (#681) 2024-04-26 20:48:21 +02:00
amacocian 23328240a9 Fix launch button spamming GWCA (#680) 2024-04-26 20:48:21 +02:00
amacocian 3f06e9486d Setup release 0.9.9.37 (#678) 2024-04-26 20:48:21 +02:00
dependabot[bot]amacociandependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
52a8e912e9 Bump Microsoft.Web.WebView2 from 1.0.2420.47 to 1.0.2478.35 (#669)
Bumps Microsoft.Web.WebView2 from 1.0.2420.47 to 1.0.2478.35.

---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-23 14:59:34 +02:00
amacocian 641aa8250e Minimap player arrow (#673) 2024-04-23 14:59:34 +02:00
amacocian faa47f32c7 Minimap angle (#672) 2024-04-23 14:59:34 +02:00
amacocian e3dac780df Update GWCA (#671) Closes (#668) 2024-04-23 14:59:34 +02:00
amacocian dfc0f12150 Release 0.9.9.36 (#670) 2024-04-23 14:59:34 +02:00
amacocian 638fb38fa0 Fix exception when extracting maximized minimap (Closes #662) (#666) 2024-04-22 14:33:10 +02:00
amacocian 4d6a06ccdf Floodplains of Mahnkelon id (Closes #653) (#665) 2024-04-22 14:33:10 +02:00
amacocian 3228604876 Item modifiers improvements (#664)
* Improve modifiers check for items

* Update minor vigor
2024-04-22 14:33:10 +02:00
amacocian 44879924d6 Release 0.9.9.35 2024-04-22 14:33:10 +02:00
amacocian 6b7478f6ae Save minimap pinned state (Closes #655) (#659) 2024-04-18 19:24:19 +02:00
amacocian f54d95c1e7 Fix word highlighting edge cases (Closes #656) (#658) 2024-04-18 19:24:19 +02:00
amacocian 5315a45a98 Fix browser navigation (Closes #654) (#657) 2024-04-18 19:24:19 +02:00
amacocian 8575d970a3 Setup release 0.9.9.34 2024-04-18 19:24:19 +02:00
amacocian 5c611eca4f GWCA Module Bugfix (#651) 2024-04-16 18:41:44 +02:00
amacocian 71aa37b965 Added 558 id for sparkfly swamp (#650) 2024-04-16 18:41:44 +02:00
amacocian 0f1b1cc83d Release 0.9.9.33 2024-04-16 18:41:44 +02:00
amacocian 198c1e6eb3 Update Daybreak.GWCA to 0.9.9.32 2024-04-16 11:28:18 +02:00
amacocian e7c9111a1f Allow credentials to be backed up (Closes #525) (#648) 2024-04-16 11:28:18 +02:00
amacocian 1191e996db Update GWCA (#647) 2024-04-16 11:28:18 +02:00
amacocian 19de3c99f9 Release 0.9.9.23 2024-04-16 11:28:18 +02:00
amacocian d857b6dc26 Bugfixes in GWCA module (#645) 2024-04-15 21:56:37 +02:00
amacocian 68b5b79035 Release 0.9.9.31 2024-04-15 21:56:37 +02:00
amacocian 318032d998 Inventory component loading widget (#643) 2024-04-15 12:13:52 +02:00
amacocian 483860532f Update GWCA (#642) 2024-04-15 12:13:52 +02:00
amacocian 694dbe82a9 Race condition crashfix - Downgrade LiteDB dependency - Closes #640 (#641) 2024-04-15 12:13:52 +02:00
amacocian a936f24e32 Setup release branch 2024-04-15 12:13:52 +02:00
amacocian a2ec87ced2 Update GWCA module (#638) 2024-04-12 02:46:53 +02:00
amacocian 48955a7dd7 Setup GWCA whisper functionality (#636) 2024-04-12 02:46:53 +02:00
amacocian 9d7c6c29b0 Setup periodical price checker for inventory (#635) 2024-04-12 02:46:53 +02:00
amacocian 902d210dfd Fix pointer in GWCA plugin (#633) 2024-04-12 02:46:53 +02:00
amacocian 57867851a9 Release 0.9.9.29 2024-04-12 02:46:53 +02:00
amacocianandGitHub ca82cc92bf Release 0.9.9.28 (#632)
Update GWCA
2024-04-11 17:58:39 +00:00
amacocianandGitHub 569ce1e656 Release-0.9.9.27
Release 0.9.9.27
Changelog viewer
2024-04-11 19:28:36 +02:00
amacocian a96c6c3f66 Release 0.9.9.27
Implement changelog view (#629)
2024-04-11 19:19:15 +02:00
amacocian 28c3b2f8c1 Fix CD pipeline 2024-04-11 18:29:04 +02:00
amacocianandGitHub deeccd81b4 Release 0.9.9.26 (#628)
* Event Calendar (#625)

* Event calendar view

* Update GWCA (#626)

* Publish changelog (#627)

* Update dependencies
2024-04-11 16:26:22 +00:00
amacocianandGitHub 6a27245ae8 Release 0.9.9.25 (#618) 2024-04-02 16:59:21 +00:00
amacocianandGitHub 6f4063abb7 Release v0.9.9.24 (#617)
Fix price history functionality
Implement all runes
Update dependencies
2024-03-27 21:33:30 +00:00
amacocianandGitHub f307bcd665 Release 0.9.9.23 (#615)
* Create 0.9.9.23 release

* Update dependencies
2024-03-18 17:52:51 +00:00
amacocianandGitHub 02ca41ba6d Release 0.9.9.22 (#612)
* Fix Daybreak restart commands

* Option to disable dynamic backgrounds

* Improve game running detection
2024-03-04 15:33:00 +00:00
amacocianandGitHub a0ebc87b7c Release 0.9.9.21 (#609)
Fix updater packaging logic
2024-02-27 14:08:02 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d21c55deb0 Bump Xotl/cool-github-releases from 1.1.8 to 1.1.10 (#608)
Bumps [Xotl/cool-github-releases](https://github.com/xotl/cool-github-releases) from 1.1.8 to 1.1.10.
- [Release notes](https://github.com/xotl/cool-github-releases/releases)
- [Commits](https://github.com/xotl/cool-github-releases/compare/v1.1.8...v1.1.10)

---
updated-dependencies:
- dependency-name: Xotl/cool-github-releases
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-27 12:53:07 +00:00
amacocianandGitHub 50a8131ec2 Update dependencies (#607)
Improve updater
2024-02-27 12:42:39 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e70cfa3d99 Bump coverlet.collector from 6.0.0 to 6.0.1 (#596)
Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/coverlet-coverage/coverlet/releases)
- [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: coverlet.collector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-24 20:46:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4c0c45f1c7 Bump MSTest.TestFramework from 3.2.1 to 3.2.2 (#600)
Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.2.1...v3.2.2)

---
updated-dependencies:
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-24 20:32:37 +00:00
amacocianandGitHub a9e819a83a Release 0.9.9.19 (#602)
Disable GWCA component when Focus View is disabled
2024-02-24 20:20:11 +00:00
amacocianandGitHub 65830f9125 Release 0.9.9.18 (#595)
* Setup release 0.9.9.18

* Minimap transparency
Closes #594

* Toggle to display exploration percentage
Closes #593
2024-02-18 22:55:07 +01:00
amacocianandGitHub 3d9106addf Release 0.9.9.17 (#592)
* Setup release 0.9.9.17

* Save extracted minimap window position (#591)

Closes #590

* Add ability to pin minimap to screen
Closes #585

* Fix ObjectDisposedException
Closes #589
2024-02-18 16:04:52 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>amacocian
2863032d9a Bump MSTest.TestAdapter from 3.2.0 to 3.2.1 (#587)
Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2024-02-14 10:48:58 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
aada189b1b Bump MSTest.TestFramework from 3.2.0 to 3.2.1 (#588)
Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v.3.2.0...v3.2.1)

---
updated-dependencies:
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-14 11:40:09 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
22ea4de66e Bump NSubstitute.Analyzers.CSharp from 1.0.16 to 1.0.17 (#586)
Bumps [NSubstitute.Analyzers.CSharp](https://github.com/nsubstitute/NSubstitute.Analyzers) from 1.0.16 to 1.0.17.
- [Changelog](https://github.com/nsubstitute/NSubstitute.Analyzers/blob/1.0.17/ReleaseNotes.md)
- [Commits](https://github.com/nsubstitute/NSubstitute.Analyzers/compare/1.0.16...1.0.17)

---
updated-dependencies:
- dependency-name: NSubstitute.Analyzers.CSharp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 05:46:15 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9859617a74 Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.9.0 (#578)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0 to 17.9.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.9.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-07 15:41:59 +01:00
amacocianandGitHub 0629c1929d Release 0.9.9.16 (#582)
* Setup release 0.9.9.16

* Copy buttons for build templates (#580)

* Extract minimap functionality (#581)

* Improve performance of RowAutoMargin functionality
2024-02-07 15:24:47 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6ec45396c3 Bump microsoft/setup-msbuild from 1.3.2 to 2.0.0 (#575)
Bumps [microsoft/setup-msbuild](https://github.com/microsoft/setup-msbuild) from 1.3.2 to 2.0.0.
- [Release notes](https://github.com/microsoft/setup-msbuild/releases)
- [Changelog](https://github.com/microsoft/setup-msbuild/blob/main/building-release.md)
- [Commits](https://github.com/microsoft/setup-msbuild/compare/v1.3.2...v2.0.0)

---
updated-dependencies:
- dependency-name: microsoft/setup-msbuild
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 10:23:57 +01:00
amacocianandGitHub b76a9b80fb Release 0.9.9.15 (#574)
* Setup release 0.9.9.15

* Export team build from focus view (#573)

Closes #573

* Fix load skill template
2024-02-04 19:30:22 +01:00
amacocianandGitHub f4bc58bbf7 Release 0.9.9.14 (#570)
* Team build support (#565)

* Setup support for team builds
Fix parser to load team builds
Fix bug in attribute loading
Adjust encoder to order and skip attributes for consistency

* Small buildtemplate binding fixes
Setup TeamBuildTemplateView

* Setup button to create team build

* Fix launch button (#566)

Closes #563

* Team build improvements (#568)

Closes #564

* Closes #562 (#569)

Closes #561

* Increment version
2024-02-04 14:11:01 +00:00
amacocianandGitHub 09fdbb6f95 Release 0.9.9.13 (#560) 2024-02-01 14:52:15 +00:00
amacocianandGitHub 8ee7ed23e4 Release 0.9.9.12 (#558)
* Release 0.9.9.12

* Move injection to injector executable (#554)

* Fix logview missing history (#556)

Closes #526

* Detect unresponsive Guild Wars instance (#557)

Closes #534
Closes #422
2024-01-31 22:02:14 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>amacocian
8d8a78cf88 Bump microsoft/setup-msbuild from 1.3.1 to 1.3.2 (#552)
Bumps [microsoft/setup-msbuild](https://github.com/microsoft/setup-msbuild) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/microsoft/setup-msbuild/releases)
- [Changelog](https://github.com/microsoft/setup-msbuild/blob/main/building-release.md)
- [Commits](https://github.com/microsoft/setup-msbuild/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: microsoft/setup-msbuild
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2024-01-29 17:06:36 +01:00
amacocianandGitHub f0652231cb Fix minimap mouse interaction (#553)
* Rework minimap renderer

* Fix dragging

* Fix mouse over detection
2024-01-29 15:50:32 +00:00
amacocianandGitHub d9aa760256 Release 0.9.9.10 (#550)
* Release 0.9.9.10
Option to rotate minimap with the camera
Fix launch button not disabling properly when launching
Remove minimap id overlay
Closes #549
Closes #533
Closes #532

* Increment version

* Increment Daybreak.GWCA version
2024-01-28 14:56:51 +00:00
amacocianandGitHub a65f375255 Release 0.9.9.9 (#548)
Closes #547
Closes #546
Closes #536
2024-01-27 19:34:47 +00:00
amacocianandGitHub 3ee58e2711 Release 0.9.9.8 (#545)
* Synchronize browser history

* Intercept mouse button navigation
Closes #535
Closes #544
2024-01-26 14:24:32 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>amacocian
9b3b083aec Bump MSTest.TestAdapter from 3.1.1 to 3.2.0 (#542)
Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.1.1...v.3.2.0)

---
updated-dependencies:
- dependency-name: MSTest.TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2024-01-26 13:09:38 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ecb9b8f172 Bump MSTest.TestFramework from 3.1.1 to 3.2.0 (#543)
Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/microsoft/testfx/releases)
- [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md)
- [Commits](https://github.com/microsoft/testfx/compare/v3.1.1...v.3.2.0)

---
updated-dependencies:
- dependency-name: MSTest.TestFramework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-26 13:08:22 +01:00
amacocianandGitHub 5765c27240 Release 0.9.9.7 (#541)
* Introduce support for browser extensions

* Setup ublock origin

* Release 0.9.9.7 branch

* Automatically backup settings
Closes #539

* Compare settings with remote
2024-01-23 18:25:31 +00:00
amacocianandGitHub c53456112a Update GWCA (#538)
Fix inventory bug
2024-01-20 10:19:46 +00:00
amacocianandGitHub ccea21886e Release 0.9.9.5 (#531)
* Remove character name from credentials
Update vulnerable packages

* Update SharpCompress
2024-01-17 20:00:12 +00:00
amacocianandGitHub 37be8de5ac Update httplib (#529)
Fix missing data when player has no titles
Fix http requests stalling
2024-01-15 19:20:55 +00:00
amacocianandGitHub 107e43a15f Update GWCA (#528) 2024-01-15 13:34:41 +00:00
amacocianandGitHub 17b3ed34db Disable wintersday mode (#527) 2024-01-09 14:06:05 +02:00
amacocianandGitHub cce9cd34af Update README.md (#524)
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2023-12-22 07:58:23 +02:00
amacocianandGitHub 6a80f8ee5d Escape parameters passed to Guild Wars on launch (#522) 2023-12-20 14:54:34 +00:00
amacocianandGitHub edb7f0d0bd Beta release 0.9 (#521)
Closes #453
2023-12-20 00:27:29 +00:00
amacocianandGitHub 8e69375338 Fix application updater start message (#520)
Closes #519
2023-12-19 23:59:39 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>amacocian
d24ae98fb0 Bump github/codeql-action from 2 to 3 (#516)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2023-12-19 23:22:27 +00:00
amacocianandGitHub 111b3ea632 Setup setting synchronization functionality (#518)
* Setup setting synchronization functionality
Update dependencies
Closes #515

* Disable mainwindow periodic check
2023-12-19 23:05:53 +00:00
amacocian cc1e759972 Improve updater responsiveness
Closes #514
2023-12-15 16:18:03 +01:00
amacocianandGitHub ad0527ed73 Notifications improvements (#513)
Add checkbox for unopened notifications
Added sort options for notifications
Closes #511
Closes #510
2023-12-15 15:13:48 +00:00
amacocianandGitHub 8ffbb918ea Add frequency checking option for trader quote alerts (#512)
Hide sell trade quotes since they don't populate
Closes #509
Closes #508
2023-12-15 13:08:51 +00:00
amacocianandGitHub c27a0dba65 Setup alerts for quotes (#507) 2023-12-12 15:22:40 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ad8305c66d Bump actions/setup-dotnet from 3 to 4 (#505)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-12 12:41:35 +01:00
amacocianandGitHub 29fe12f094 Revert injector extraction (#504)
* Revert "Extract injector to its own process (#503)"

This reverts commit 9dbe4c5eaf.

* Revert injector extraction
2023-12-04 14:54:34 +01:00
amacocianandGitHub 9dbe4c5eaf Extract injector to its own process (#503) 2023-12-04 14:12:18 +01:00
amacocianandGitHub 1e9aca3807 Onboard SharpNav for pathfinding (#502) 2023-11-29 23:05:41 +00:00
amacocianandGitHub 895da23f7d Re-enable beta update by default (#500)
Extra pictures
2023-11-23 18:39:44 +00:00
amacocianandGitHub f788a10879 Implement file version checking for updater (#499)
* Implement file version checking for updater

* Increment version
2023-11-23 17:17:58 +00:00
amacocianandGitHub 9fe4be0467 Remove privilege mode for updater (#497) 2023-11-22 20:51:54 +00:00
amacocianandGitHub b1a7d3a111 Improve update procedure (#496)
* Improve update procedure

* Change release procedure

* Fix gh release

* Fix release tag

* Change release structure to support folders

* Revert zip file changes to make them backwards compatible

* Change path

* Make launcher not contain the entire dependencies

* Add downtime after API rate limit

* Add rate limit checking before create release

* Enumerate through rates

* Adjust rate limit call

* Change to create release as draft

* Add update logic based on azure blobs

* Fix publish

* Create zip

* Disable CD pipeline for prs

* Increment version

* Fix tests
2023-11-22 21:13:01 +01:00
amacocianandGitHub 8b9fdaaa30 Release v152 (#493)
Improve LaunchView responsiveness
Show current uMod version
Make services and models internal
Closes #491
Closes #490
Closes #381
2023-11-21 16:25:32 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
60d5947535 Bump SharpCompress from 0.34.1 to 0.34.2 (#489)
Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.34.1 to 0.34.2.
- [Release notes](https://github.com/adamhathcock/sharpcompress/releases)
- [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.34.1...0.34.2)

---
updated-dependencies:
- dependency-name: SharpCompress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-21 15:25:31 +01:00
amacocianandGitHub 0ba0e64a2a Small fixes (#488)
Fix copy folder picker
Fix metrics
Closes #487
Closes #474
2023-11-19 00:17:26 +00:00
amacocianandGitHub e1dfd1f0d0 Update to .net8.0 (#486)
Fix uMod installation steps
ReShade monitor config changes
2023-11-18 23:40:09 +00:00
amacocianandGitHub cd2a529381 Catch exceptions on startup (#485)
Extend process injection timeout (mainly due to uMod)
2023-11-17 15:50:08 +00:00
amacocianandGitHub 6ba8dcac49 Upgrade GWCA (#480) 2023-11-14 13:09:32 +00:00
amacocianandGitHub f2e76b5459 Show winter related background for Wintersday (#479)
* Show winter related background for Wintersday
Closes #449
Contributes to #453

* Increment version
2023-11-13 22:40:41 +00:00
amacocianandGitHub cfbf6ed021 Update toolbox and uMod links to gwdevhub (#477) 2023-11-12 15:30:06 +00:00
amacocianandGitHub a372eb5125 Replace uMod with gMod (#476) 2023-11-10 17:09:54 +00:00
amacocianandGitHub 76b89ca0e8 Make Daybreak create a modlist.txt for uMod (#475) 2023-11-10 11:06:30 +00:00
amacocianandGitHub 2c1721d52c Bugfixes (#473)
Fix NRE in minimap
Add a fallback routine to figure out if an elevated guildwars executable is running
2023-11-10 00:11:24 +00:00
amacocianandGitHub 324a77a625 Update UMod startup action (#472)
Minor mod fixes
2023-11-09 22:09:47 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
88e59b8ec9 Bump Microsoft.NET.Test.Sdk from 17.8.0-release-23468-02 to 17.8.0 (#471)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0-release-23468-02 to 17.8.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0-release-23468-02...v17.8.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-09 23:00:54 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>amacocian
df71a17d9f Bump Microsoft.Web.WebView2 from 1.0.2088.41 to 1.0.2151.40 (#469)
* Bump Microsoft.Web.WebView2 from 1.0.2088.41 to 1.0.2151.40

Bumps Microsoft.Web.WebView2 from 1.0.2088.41 to 1.0.2151.40.

---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix version

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Macocian <amacocian@yahoo.com>
2023-11-08 19:08:20 +00:00
amacocianandGitHub 8451f53e19 UMod bugfixes (#470) 2023-11-08 18:55:07 +00:00
amacocianandGitHub e795af5c11 Fix 7Zip Extractor (#467)
* Fix 7Zip Extractor
Closes #466

* Increment version
2023-11-05 21:27:37 +00:00
amacocianandGitHub efa3c5175c Add DirectSong support (#465)
Various small fixes
Closes #464
2023-11-05 20:31:30 +00:00
amacocianandGitHub 32efe42a61 Minor fixes (#463)
Fix build loading from GWCA
Introduce persistence toggle for logging to reduce disk usage
Improve performance of memory usage and processor usage counters
Add disk usage counters
Closes #462
Closes #461
2023-11-04 19:30:27 +00:00
amacocianandGitHub 97d2d09cdc Fix toolbox and gwca integration (#460)
Closes #458
2023-11-03 20:05:52 +00:00
amacocianandGitHub ed90d567a4 Wintersday preparation (#457)
Snowfall effect
Setup OnlinePictureClient to support more online sources
Contributes to #453
2023-11-02 17:04:18 +00:00
amacocianandGitHub 51d6fb0f42 Fix vanquishing API (#448)
Pass Health/Energy from GWCA
Fix for enemy primary professions
Fix for LiveCharts2 memory leak
Update GWCA

Closes #447
Closes #446
Closes #445
Closes #444
Closes #442
2023-10-29 16:59:05 +00:00
amacocianandGitHub c10525708c Expose APIs for entity and item names (#443)
Rework logic to reuse functions in Utils.h
Rely on GameState for entity positions and state, greatly improves performance
2023-10-29 00:35:26 +00:00
amacocianandGitHub 0a69d795aa Button to load plugin (#438)
Detect broken GuildWars
Closes #437
Closes #407
2023-10-28 17:36:37 +00:00
amacocianandGitHub 3b1d6831a5 Fix quest metadata position (#436)
* Fix quest metadata position
Closes #435

* Increment version
2023-10-28 14:47:07 +02:00
amacocianandGitHub b4c1dbcddb Deprecate memory reader (#434)
* Deprecate memory reader
Switch completely to GWCA plugin
Fix InventoryModule item ids
Fix InventoryModule modifiers
FIx BloogumClient image retrieval

* Increment version
2023-10-28 12:28:26 +00:00
amacocianandGitHub db5d62d7c0 Name module improvements (#431)
Add waiting logic for names
Remove debouncing logic
Add ability to search wiki by npc name
Update GWCA
2023-10-28 09:51:43 +00:00
amacocianandGitHub ed5ccf38c6 GWCA follow-up (#430)
* Improvements to GWCA integration
Optimize GWCA calls
Add API to get entity names
Fix memory violations in GWCA
Closes #429
Closes #428

* Increment version
2023-10-27 21:33:44 +00:00
amacocianandGitHub 7671e0c04c GWCA Integration (#426)
* GWCA integration
GWCAMemoryReader partial implementation

* Complete Daybreak.GWCA implementation
Fix metrics view
Upgrade dependencies

* Update version

* Add solution dir property

* Another try

* Fix version check

* Fix codeql pipeline

* Codeql fixes

* Fetch submodules

* Final codeql change
2023-10-26 23:18:08 +02:00
amacocianandGitHub f3b8221b29 Improve Guild Wars launcher process (#423)
Added timeouts for mod startup
Added detection logic for uMod breaking after GuildWars update

Closes #415
2023-10-13 11:44:47 +00:00
amacocianandGitHub 8fdc9a67c0 Setup launch configurations (#420)
Adjust Daybreak logic to support loading multiple configurations
Closes #419
Closes #410
2023-10-11 13:29:58 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
bb2e739876 Bump Microsoft.NET.Test.Sdk (#416)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0-preview-23424-02 to 17.8.0-release-23468-02.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0-preview-23424-02...v17.8.0-release-23468-02)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-05 16:47:47 +02:00
amacocianandGitHub 852cbff5f3 Integrate with ReShade (#417)
Change build to x86 to support new process injection
Add an intermediary step for mods when launching Guild Wars
2023-10-05 12:26:54 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>amacocian
6e1c111876 Bump HtmlAgilityPack from 1.11.53 to 1.11.54 (#414)
* Bump HtmlAgilityPack from 1.11.53 to 1.11.54

Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.53 to 1.11.54.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.53...v1.11.54)

---
updated-dependencies:
- dependency-name: HtmlAgilityPack
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Increment version

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexandru Macocian <amacocian@yahoo.com>
2023-10-04 10:51:24 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9f60839e40 Bump LiveChartsCore.SkiaSharpView.WPF from 2.0.0-rc1 to 2.0.0-rc1.2 (#411)
Bumps [LiveChartsCore.SkiaSharpView.WPF](https://github.com/beto-rodriguez/LiveCharts2) from 2.0.0-rc1 to 2.0.0-rc1.2.
- [Release notes](https://github.com/beto-rodriguez/LiveCharts2/releases)
- [Commits](https://github.com/beto-rodriguez/LiveCharts2/commits)

---
updated-dependencies:
- dependency-name: LiveChartsCore.SkiaSharpView.WPF
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-26 18:26:03 +02:00
amacocianandGitHub dd37db18b5 Theming fixes and improvements (#413)
Fixes issue with LightMode initialization
Syncs Daybreak theme with Windows theme
Closes #412
Closes #406
2023-09-26 16:09:38 +00:00
amacocianandGitHub 793d60f7b5 Add tooltips for buttons (#409)
Closes #379
2023-09-25 14:22:52 +00:00
amacocianandGitHub 08674cd7a6 UMod improvements (#405)
Allow reordering of uMod load order
Allow uMod to load zip files
Fix auto-enable description

Closes #388
Closes #386
Closes #385
2023-09-24 19:57:12 +00:00
amacocianandGitHub dca3e39a6d Force background image (#404)
* Force background image
Closes #403

* Increment version
2023-09-24 20:56:46 +02:00
amacocianandGitHub 8a1033d4b6 Fix for missing options on fresh install (#402)
Closes #401
2023-09-24 20:34:41 +02:00
amacocianandGitHub e4ebe558d7 Use Reloaded.Injector to avoid cross WoW64 boundary issues (#400)
Closes #399
2023-09-23 01:40:40 +00:00
amacocianandGitHub 70728cc5ed Delay toolbox startup (#398)
Closes #397
2023-09-23 01:54:20 +02:00
amacocianandGitHub b49a0a5ff4 Update dependencies (#396) 2023-09-22 23:43:07 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b48d959ffb Bump FluentAssertions from 6.11.0 to 6.12.0 (#394)
Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.11.0 to 6.12.0.
- [Release notes](https://github.com/fluentassertions/fluentassertions/releases)
- [Changelog](https://github.com/fluentassertions/fluentassertions/blob/develop/AcceptApiChanges.ps1)
- [Commits](https://github.com/fluentassertions/fluentassertions/compare/6.11.0...6.12.0)

---
updated-dependencies:
- dependency-name: FluentAssertions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 01:23:05 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b8e1ad89af Bump Microsoft.NET.Test.Sdk (#391)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0-preview-23371-04 to 17.8.0-preview-23424-02.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0-preview-23371-04...v17.8.0-preview-23424-02)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 01:22:46 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b2bfbfcd86 Bump actions/checkout from 3 to 4 (#390)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 01:22:17 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
062e28a6d2 Bump NSubstitute from 5.0.0 to 5.1.0 (#395)
Bumps NSubstitute from 5.0.0 to 5.1.0.

---
updated-dependencies:
- dependency-name: NSubstitute
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 01:21:42 +02:00
amacocian dc0f833291 Re-create dependabot.yaml 2023-09-23 01:20:22 +02:00
amacocian e598237916 Remove dependabot.yaml 2023-09-23 01:19:56 +02:00
amacocianandGitHub 6b96d3d9e9 Inject toolbox dll into GuildWars (#389)
Closes #384
2023-09-22 23:11:36 +00:00
amacocianandGitHub 1dbb610846 Code cleanup (#387)
Closes #382
2023-09-22 16:10:07 +00:00
amacocianandGitHub 5c649b0c15 Integrate with uMod dll (#383) 2023-09-22 15:35:05 +00:00
amacocianandGitHub 527ab041d9 Fixes for the Background provider (#377)
Closes #376
2023-09-19 16:21:23 +00:00
amacocianandGitHub ff94e90259 Toggle background effects (#375) 2023-09-19 08:20:52 +00:00
amacocianandGitHub 51efbcce1e Update README.md (#374)
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2023-09-19 09:28:37 +02:00
amacocianandGitHub 87b2345105 Daybreak Splash Screen (#373)
Closes #369
2023-09-16 12:57:07 +00:00
amacocianandGitHub 5022d33ca9 Ask for restart only when detecting change in plugins (#372)
Closes #371
2023-09-15 23:21:01 +00:00
amacocianandGitHub 141f949a63 Plugin support (#370)
Adds a plugin management view
Implements a basic plugin as an example
Closes #361
2023-09-15 19:22:52 +00:00
amacocianandGitHub 3913c71fd8 Release v107 (#368)
Handle browser component missing in FocusView
Fix credit text when failing to load image
Closes #298
Closes #366
2023-09-12 13:57:12 +00:00
amacocianandGitHub 5706652c40 Pathing drawing fix (#365)
Closes #364
2023-09-11 15:08:52 +00:00
amacocianandGitHub 768b6b20f5 Pathfinding improvements (#360)
Use optimization passes to improve final paths
2023-09-11 14:04:09 +00:00
amacocianandGitHub 7066b7dbb6 GW2 style backgrounds (#359)
* Guildwars 2 styled background
Fix Screenshot provider
Closes #358

* Remove unnecessary code
2023-09-11 10:14:50 +00:00
amacocianandGitHub 17e01fc7b9 Disable launch button when loadin guildwars (#357)
Verify window placement on startup
Update as notification
Closes #349
Closes #347
Closes #346
2023-09-09 15:31:13 +00:00
amacocianandGitHub ee9fcf38b7 Release v102 (#355)
Restrict maximum notification size
Browser detects builds in downloaded files
BuildTemplate has a button to navigate to the build source
Closes #354
Closes #353
Closes #352
2023-09-08 10:32:47 +00:00
amacocianandGitHub 5abb24bdd4 Append source url for builds loaded through the browser (#351)
Closes #350
2023-09-07 23:48:13 +00:00
amacocianandGitHub 53f17d3f22 Display event remaining time (#348)
* Event notifications display remaining time
Closes #344

* Increment version
2023-09-03 20:06:41 +00:00
amacocianandGitHub b728c38dc1 Map fixes & Npcs (#345) 2023-09-01 12:33:52 +00:00
amacocianandGitHub 5e57b6ecd0 Notification fixes (#343)
* Notification fixes
Closes #342

* Update version
2023-08-31 22:26:49 +00:00
amacocianandGitHub e9ffcdf7c3 Add search capability to logs (#341)
Closes #340
2023-08-22 13:37:18 +00:00
amacocian 118657de7c Allow CD pipeline to be run manually 2023-08-09 23:57:14 +02:00
amacocian 34c332504b Fix cd pipeline 2023-08-09 23:54:45 +02:00
amacocianandGitHub d7a29d68a1 Release v96 (#338)
Add support for non-persistent notifications
LogsView message limit
Tradechat exponential backoff
Possibility to revert to normal mode

Closes #335
Closes #322
Closes #337
2023-08-09 20:56:24 +00:00
amacocianandGitHub eefa02837d Replace Moq with NSubstitute (#336)
* Replace Moq with NSubstitute

* CI fixes
Pin .net 7.0.9 in CI
Update test dependencies

* CI fixes
2023-08-09 15:23:50 +02:00
amacocianandGitHub 5b2812fa61 Release 95 (#334)
Fix mods launching regardless of status
LogsView improvements
NotificationsView improvements
Decrease frequency of ConnectivityStatus
Guildwars array validation
Chromium build template trim selection
Build template viewer improve skill dropdown
Update dependencies

Closes #333
Closes #332
Closes #318
Closes #319
2023-08-08 15:17:16 +00:00
amacocianandGitHub e3ddfb88c1 Add ETA to update view (#331)
Closes #205
2023-08-07 13:14:52 +00:00
amacocianandGitHub 664d94aeaa PreGameContext read implementation (#328)
Fixes for memory scanner
Closes #327
Closes #326
2023-08-07 11:02:36 +00:00
amacocianandGitHub e2baf63099 Events notifications (#325)
Closes #324
2023-08-06 22:29:21 +00:00
amacocianandGitHub c1b7000139 Connection data (#317) 2023-07-24 09:44:43 +00:00
amacocianandGitHub 6e8333d76f CD fixes 2 (#314)
* CD fixes 2

* Increment version
2023-07-19 13:16:12 +03:00
amacocianandGitHub c67eddc9d4 Re-enable CD pipeline (#313) 2023-07-19 10:09:21 +00:00
amacocianandGitHub 292cf6fd94 Screen placer fixes (#312)
* Screen placer fixes
Make screen placer as a mod
Validate login data

* Remove restrictions on CD pipeline
2023-07-19 12:43:15 +03:00
amacocianandGitHub 69038bca43 Revert "Screen placer fixes (#310)" (#311)
This reverts commit f05d15ce36.
2023-07-19 12:25:36 +03:00
amacocianandGitHub f05d15ce36 Screen placer fixes (#310)
Make screen placer as a mod
Validate login data
2023-07-19 12:08:02 +03:00
amacocianandGitHub 98cb349d67 Screen placer fixes (#310)
Make screen placer as a mod
Validate login data
2023-07-19 09:06:57 +00:00
amacocianandGitHub a4314ee4d5 Permission fixes for DSOAL (#309)
* Permission fixes for DSOAL
Registry service fixes

* Show notification on fix
2023-07-16 18:08:03 +00:00
amacocianandGitHub 96097bd25a Unify UI margins (#308) 2023-07-16 16:52:20 +00:00
amacocianandGitHub 14029e6af2 Cloudflare iaum mitigation (#307)
* Cloudflare iaum mitigation
Closes #305

* Increment version
2023-07-15 11:05:27 +00:00
amacocianandGitHub 365777141d Daybreak v84 (#306)
DSOAL installation requiring admin mode
DOSAL installation overwriting existing symbollic link
DSOAL check symbollic link before launching
MenuList can be scrollable
RegisterService implementation
Notifications show date of notification
Performance improvements for picture loading
Allow metric streaming to MetricsView
Handle cloudflare blocking responses
Closes #304
Closes #303
Closes #301
Closes #300
Mitigates #305
2023-07-15 10:32:30 +00:00
amacocianandGitHub aaa5da8d9b More nightfall endgame npcs (#299) 2023-06-30 20:00:52 +00:00
amacocianandGitHub b898ab5679 Fix cache size calculation (#297) 2023-06-29 18:32:18 +00:00
amacocianandGitHub b896fa5cc7 Bugfix for image memory cache (#296)
Improved memory usage monitor
2023-06-29 16:21:19 +00:00
amacocianandGitHub bca9f6d66b Fix loading screen flickering (#295)
* Fix loading screen flickering

* Increment version
2023-06-29 15:01:41 +00:00
amacocianandGitHub d6f71cb2eb Performance improvements (#293)
Ability to disable minimap
Closes #291
Fix main player health/energy proper values
Better granularity for memory access
Focus view uses less memory footprint to gather necesarry resources
2023-06-29 13:38:28 +00:00
amacocianandGitHub 986d09867c Cache duplication fix (#292)
* Cache duplication fix
Performance improvement
Closes #290

* Increment version
2023-06-29 10:08:00 +02:00
amacocianandGitHub 97cb2151f9 Memory reader cache to better control memory access (#289)
Expand guildwars locations, campaign, continent
Make bloogum use image cache
Make bloogum show images based on character location
Link Campaign -> Continent -> Region -> Map
2023-06-28 15:32:19 +00:00
amacocianandGitHub 338ac100a1 Realm of torment npcs (#288) 2023-06-27 16:55:44 +00:00
amacocianandGitHub 01badcee6a More nightfall npcs (#287) 2023-06-27 11:26:25 +00:00
amacocianandGitHub 302cf1114c Fix bug with trader messages options (#286)
Reorder options
2023-06-26 19:05:13 +00:00
amacocianandGitHub 613e03dc75 High def skill icons (#284)
* Use high-definition skill icons

* Increment version
2023-06-26 17:42:38 +00:00
amacocianandGitHub 4267359cb2 DSOAL support (#282)
Reworked mod support
2023-06-26 13:16:29 +00:00
amacocianandGitHub daac8ec787 More nightfall npcs (#281)
Fixes for quests and npcs
UTs for quests, npcs and items
2023-06-26 10:00:17 +00:00
amacocianandGitHub 857adec459 More vabbian npcs (#280)
* More vabbian npcs

* Increment version
2023-06-23 08:50:56 +00:00
amacocianandGitHub b46e10a1a6 Vabbian npcs (#279) 2023-06-22 12:50:18 +00:00
amacocianandGitHub 10799b7e9c Fix modifiers view (#278)
Show modifiers for unknown items
Fix enemy drawing
Added some npcs
2023-06-21 20:19:44 +00:00
amacocianandGitHub 6bbeaa1738 More Kournan npcs (#277) 2023-06-21 17:36:43 +00:00
amacocianandGitHub 1a0f1cbf9a Performance improvements to images (#275) 2023-06-21 09:20:23 +00:00
amacocianandGitHub 68e708fe96 Price history improvements (#274)
Inventory view improvements
Base for inscriptions
Base for runes
2023-06-20 00:32:28 +00:00
amacocianandGitHub a2166f2327 Add Guild Hall npcs (#272) 2023-06-18 15:24:23 +00:00
amacocianandGitHub 6d55366bdc Mark unknown entities with a question mark (#271) 2023-06-18 11:58:39 +00:00
amacocianandGitHub 28fcaa81cf Installer fix (#270)
Closes #269
2023-06-17 18:52:01 +00:00
amacocianandGitHub c5efc2990e Guildwars copy service (#268)
Fix one focus view bug
2023-06-17 14:18:35 +02:00
amacocianandGitHub 597010b9a1 Memory reader base pointer fixes (#267)
Installer fixes
2023-06-16 16:17:20 +00:00
amacocianandGitHub 1f71816256 Make regex trade searches case-insensitive (#264) 2023-06-16 13:09:37 +00:00
amacocianandGitHub 7c3618d2b5 Trade alerts (#263)
Notifications improvements
2023-06-16 11:06:18 +00:00
amacocianandGitHub df9038039c Highlight color specific trading words (#262) 2023-06-09 10:13:36 +00:00
amacocianandGitHub 9a4b23976a Release v56 (#259)
Inventory Component toggle
Fix inventory component visibility
Inventory component performance improvements
2023-06-08 21:33:13 +00:00
amacocianandGitHub 8732e0fe5e Release v55 (#256)
Sounds
Split FocusView into components
Focus view inventory component
Notifications view
Internet connectivity
2023-06-08 19:58:26 +00:00
amacocianandGitHub c23b84c4d5 Improve pathfinding algorithm (#255)
Improve minimap color contrasts
2023-06-01 20:56:49 +00:00
amacocianandGitHub 345b6a792a Color coded boss icons based on primary profession (#254) 2023-05-30 22:00:00 +00:00
amacocianandGitHub d9ae52165d Show npc profession in context menu (#252) 2023-05-30 20:54:03 +00:00
amacocianandGitHub 4f40e8328b Release v51 (#251)
* Release v51
Fixes duplicate npcs
Adds npcs for all games
Improve focus view memory reading latency
Clean up metrics
Closes #202
Closes #203
Closes #201

* Small fixes
2023-05-30 17:08:25 +00:00
amacocianandGitHub ebdd828b06 Improve the price chart date visibility (#250) 2023-05-22 18:49:41 +00:00
amacocianandGitHub 711acb55db Release v49 (#249)
Cache loaded images to improve performance
Separate quotes from trade views
Implement price history view
Local price history database
Fix notification popups
Fix icon caching

Closes #248
Closes #247
Closes #246
Closes #245
2023-05-19 20:08:43 +00:00
amacocianandGitHub 7dfc21bfe0 Setup NotificationService (#244)
Separate price quotes from trades
2023-05-16 20:05:28 +00:00
amacocianandGitHub 78644a6eec Add Flag and Person minimap icons (#242) 2023-05-15 20:41:11 +00:00
amacocianandGitHub 30b1d77575 Implement trade chat views (#239)
More UI improvements
Much better IconCache implementation
Re-enable screen selector
2023-05-15 17:55:25 +00:00
amacocianandGitHub a9626d03a4 Update README.md (#238)
Signed-off-by: Macocian Alexandru Victor <amacocian@yahoo.com>
2023-05-14 20:57:05 +02:00
amacocianandGitHub be731bbffb Unify UI buttons (#237)
* Unify UI buttons
Use Theme Accent as highlight
Fix browser concurrency issues
Fix browser enable/disable during runtime
Unify button sizes and looks

Closes #235
Closes #236

* Increment version
2023-05-10 20:35:24 +00:00
amacocianandGitHub 8cd40e2de3 Dynamic Options View (#234)
Import MahApps Metro
2023-05-08 18:52:10 +00:00
amacocianandGitHub 7745594bb6 Draw engagement area for party (#233)
Clean uMod installation
Closes #232
Closes #220
2023-05-07 13:07:37 +00:00
amacocianandGitHub e43bedf1c2 Fix auto-load template procedure (#229)
Fix character name loading on guildwars startup
Closes #126
2023-05-03 21:22:32 +00:00
amacocianandGitHub d6623a8646 Automatically add downloaded tpfs to mod list (#228)
* Automatically add downloaded tpfs to mod list
Closes #227

* Increment version
2023-05-03 16:33:25 +00:00
amacocianandGitHub 403ba51a7d Add possibility to download tpf files from the uMod browser page (#226)
* Add possibility to download tpf files from the uMod browser page
* Organize menu by sections
* Limit logs to improve performance
* Fix screenmanageroptions name
* Fix focusview default webpage
2023-05-03 15:04:40 +00:00
amacocianandGitHub b185a606e6 Integrate with mods (#223)
* Integrate with GWToolboxpp and uMod
* Separate configuration into multiple extensible parts
* Generalize downloading service
2023-05-03 11:10:18 +00:00
amacocianandGitHub 342a9a4826 Add more minimap icons (#221)
Support for team colored icons
2023-05-01 11:36:22 +00:00
amacocianandGitHub 2df2e8a65e Added support for multi-color SVG icons (#219) 2023-04-30 16:04:09 +00:00
amacocianandGitHub 2d5e9231a9 Minimap improvements (#217)
Map icons have overload for minimum size
Pathfinding now finds paths even when start/end points are not on the map
Map can now draw SVGs
2023-04-30 12:31:11 +00:00
amacocianandGitHub 818e2bf1d0 Drawing bugfixes (#210) 2023-04-10 18:47:03 +02:00
amacocianandGitHub f0a876f7cc Rework minimap drawing (#208)
Setup extensible drawing modules
2023-04-10 13:57:35 +00:00
amacocianandGitHub 3fae8f91cb Add support for map icons (#207)
Add res-shrine icons to minimap
2023-04-09 20:26:15 +00:00
amacocianandGitHub 8b08056ca7 Show targeted npc details (#206) 2023-04-09 18:30:25 +00:00
amacocianandGitHub 39eaf53deb Setup startup actions (#204)
Rename temp installer on startup
2023-04-09 16:21:11 +00:00
amacocianandGitHub 4674d1d7e5 Add support for npc types and names (#200)
* Introduce npcs and support for npc names

* Update version
2023-04-07 10:34:11 +00:00
amacocianandGitHub b8b5cc8b1d Optimize pathfinding (#198)
* Optimize pathfinding
Simplified initial search to no longer take distance into account
Metrics for pathfinding

* Increment version
2023-04-05 15:30:48 +00:00
amacocianandGitHub dc987233a7 Retry pathfinding loading on failure (#197) 2023-04-04 21:35:16 +00:00
amacocianandGitHub 266b400c22 Pathfinding v1 (#196)
* Pathfinding algorithm for minimap

* Fix adjacency lists

* Better adjancency list generation

* Improve pathfinding algorithm

* Some other tries

* Pathfinding algorithm for minimap

* Fix adjacency lists

* Better adjancency list generation

* Improve pathfinding algorithm

* Some other tries

* Improvements to the minimap and pathing

* Improve pathfinder

* Draw path forests

* Implement working pathfinding solution
2023-04-04 20:43:14 +00:00
amacocianandGitHub a4ac083fd0 General client improvements (#195)
Hit-target for off-screen quest markers
Save window position on every move instead of only on closing
2023-04-03 20:21:12 +00:00
amacocianandGitHub 320dd16838 Show objective direction for loaded quests (#194) 2023-04-02 19:22:17 +00:00
amacocianandGitHub 5c05c636bc Add quest marker functionality to the minimap (#192) 2023-04-02 14:57:49 +00:00
amacocianandGitHub 7103a8a83a Show party member context (#190)
Fix one Map id
2023-03-25 18:30:29 +00:00
amacocianandGitHub ad749d801a Minimap improvements (#189)
Closes #187
Closes #188

Minimap performance improvements
Add context menues for main player and living entities
2023-03-25 08:36:15 +00:00
amacocianandGitHub 747e9a0490 Minimap improvements (#186)
Closes #185
Closes #184
Closes #183
Show player information when right clicking on minimap
Allow minimap fullscreen
Decouple scrolling from memory reading frequency
Ignore task cancelled exceptions
2023-03-24 22:39:52 +00:00
amacocianandGitHub 836402bd0f Draw player position history on the minimap (#182) 2023-03-21 15:19:25 +00:00
amacocianandGitHub a3d5e1ce1f Add another check for corrupted images (#181)
* Add another check for corrupted images

* Check validity of icons on startup
Improve screen position saving
2023-03-21 08:45:22 +00:00
f2cc59a4ab Daybreak 0.9.8.18 (#179)
* Minimap implementation

* Fix minimap

* Add debounce logic to entities positions

* Cleanup

* Fix x64 build memory reading
Move build to x64
Fix entity debouncing
Added some backstops to prevent reading corrupted memory

* Add minimap zooming and panning functionality
Add main window position saving procedure

* Increment version

---------

Co-authored-by: Alex Macocian <amacocian@hotmail.com>
2023-03-20 21:38:53 +02:00
amacocianandGitHub 6ea77f7e05 Bugfixes and improvements (#176)
Closes #175
Closes #174
Closes #173
2023-03-09 19:53:28 +00:00
amacocianandGitHub 5ea8df7cb9 Bugfixes to memory reader (#172) 2023-03-08 20:17:58 +00:00
amacocianandGitHub c70e8ec90d Implement Guildwars downloader (#171) 2023-03-08 13:24:14 +00:00
amacocianandGitHub 210e3a8cb1 Add title data to memory retrieval (#169)
Add metrics for diagnostics
Add metrics view
2023-03-07 17:20:20 +00:00
amacocianandGitHub a0c3bb094c Performance improvements (#168)
* Performance improvements and bugfixes

* Increment version

* Better builds links

* Unify loading widget size
2023-03-05 08:55:21 +01:00
amacocianandGitHub b4d932692a Reduce words in FocusView character line (#167) 2023-03-04 17:18:03 +00:00
amacocianandGitHub 667534a07a Fix version parsing (#166) 2023-03-04 18:06:53 +01:00
amacocianandGitHub 159928f24b Integrate builds with Focus view (#164)
* Integrate builds with Focus view
Detect when GameData is invalid
Detect when reading memory is faulty
Show current character data in Focus View
Closes #161
Closes #162
Closes #163

* Increment version

* Bugfix version comparison script
2023-03-04 17:21:08 +01:00
amacocianandGitHub 8a91ded610 Fix buildtemplate skill removal (#160)
Closes #159
2023-03-03 22:28:27 +00:00
amacocianandGitHub 4226b7eb32 Improve performance of buildtemplateview (#158) 2023-03-03 21:50:25 +00:00
amacocianandGitHub b61c99dd1c Fix build template failing to load secondary attributes (#157)
Improve build template handling logic
Add attribute points calculator
2023-03-03 20:51:03 +00:00
amacocianandGitHub 1ccb0d5334 Add map entry to FocusView (#155)
Fix Icon Downloader
Improve settings navigation
Change multiple views to match the design of the rest of the application
2023-03-02 20:11:00 +01:00
amacocianandGitHub 7fa01353dd UX Bugfixes and improvements (#151)
Fix memory reading issues when restarting the game
Fixed current energy and health retrieval and calculation
Added health and energy bars in focus view
Fixed focus view size calculation
Improved buildtemplate performance
Improved buildtemplate navigation
Added loading widgets to menus that are loading asynchronously
2023-03-02 16:49:00 +00:00
amacocianandGitHub 4f849d880b Ux improvements (#148)
Rearrange quest log layout
Fix some quest names
Fix some buttons ux
Mitigate issues when reading guildwars memory
2023-03-02 01:58:07 +00:00
amacocianandGitHub 4b60c96aad Closes #146 #145 (#147) 2023-03-02 00:39:52 +00:00
amacocianandGitHub a4dc0d65cf Implement Focus View (#144)
* Implement Focus View
Read extra information from guildwars

* Prever focusview browser from being disposed
2023-03-01 18:42:53 +00:00
amacocianandGitHub 85e017423f #142 - Fix Vanguard Quests Missing (#143)
Closes #142
2023-02-28 18:24:37 +00:00
1297 changed files with 114140 additions and 17700 deletions
+5 -2
View File
@@ -6,8 +6,8 @@ csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:error
csharp_prefer_simple_using_statement = true:error
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_namespace_declarations = file_scoped:error
csharp_style_prefer_method_group_conversion = true:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
@@ -46,6 +46,9 @@ csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
csharp_style_var_elsewhere = false:silent
csharp_style_prefer_top_level_statements = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_prefer_readonly_struct = true:suggestion
[*.{cs,vb}]
#### Naming styles ####
+71
View File
@@ -0,0 +1,71 @@
# Copilot instructions for Daybreak
Daybreak is a cross-platform Guild Wars launcher built on .NET 10 and
Photino.Blazor. Read the
[Architecture Overview](../README.md#architecture-overview) for the project
layout and [CONTRIBUTING.md](../CONTRIBUTING.md) for the branching and release
process.
## Workflow
- Branch off the current `release/[version]` branch, never off `master`.
- One feature or fix per branch and per PR.
- Never push to `master` and never edit the version in `Directory.Build.props`.
Both are owned by the release workflows.
- Title PRs `Short description (Closes #123)` so the linked issue gets labelled.
## Build and test
CI does not run on PRs targeting a release branch, so validate locally. Run the
smallest command that covers the change:
```bash
dotnet build Daybreak.Linux/Daybreak.Linux.csproj # or Daybreak.Windows
dotnet test Daybreak.Tests/Daybreak.Tests.csproj --filter "FullyQualifiedName~YourTests"
```
Keep the build warning-free.
## Where code goes
| Change | Project |
| -------------------------------------------------- | ------------------------------------ |
| Models, utilities, interfaces shared by everything | `Daybreak.Shared` |
| Blazor views and services | `Daybreak.Core` |
| Platform-specific implementations | `Daybreak.Windows`, `Daybreak.Linux` |
`Daybreak.Tests` only references `Daybreak.Core`. When adding platform code, put
the logic worth testing in `Daybreak.Shared` and leave the platform project as a
thin caller.
Add NuGet packages through `Directory.Packages.props`.
## C# conventions
`.editorconfig` is the source of truth. The projects are set up to treat
warnings as errors.
## Verify before claiming
Prefer evidence over reasoning about behaviour, especially for the Wine and
injection paths. Wine is scriptable, so reproduce the actual failure and confirm
the fix against it rather than inferring what a Win32 call does.
## Writing style
Applies to docs, comments, PR descriptions and answers.
- Be short. Cut filler, recap and process narration.
- No AI-isms and no inflated language.
- Do not paste code into documentation. Link to the file so there is only one
copy of it.
- Explain the reason for a change, not a summary of the diff.
## Markdown
- No inline HTML. Use `![alt][ref]` with a reference definition instead of an
`img` tag.
- Start a mermaid fence with the diagram type, for example `flowchart LR`. YAML
frontmatter inside the fence is rejected by some renderers.
- Long URLs belong in reference-style link definitions at the bottom of the
file.
+28
View File
@@ -0,0 +1,28 @@
# Pre-configure SimplySign Desktop for unattended use:
# - auto-show the login dialog on launch (so the TOTP keystrokes have a target)
# - cache the smart-card PIN in the CSP, so signtool signs without a per-call
# PIN prompt, and forget it again when the session disconnects.
# Mirrors the settings used by the blinkdisk / docuscope CI signing setups.
$ErrorActionPreference = "Stop"
$RegistryPath = "HKCU:\Software\Certum\SimplySign"
$settings = [ordered]@{
ShowLoginDialogOnStart = 1
ShowLoginDialogOnAppRequest = 1
RememberLastUserName = 0
Autostart = 0
UnregisterCertificatesOnDisconnect = 0
RememberPINinCSP = 1
ForgetPINinCSPonDisconnect = 1
LangID = 9
}
Write-Host "=== Configuring SimplySign Desktop registry ==="
New-Item -Path $RegistryPath -Force | Out-Null
foreach ($name in $settings.Keys) {
Set-ItemProperty -Path $RegistryPath -Name $name -Value $settings[$name] -Type DWord
Write-Host " $name = $($settings[$name])"
}
Write-Host "Done."
+245
View File
@@ -0,0 +1,245 @@
# Authenticate SimplySign Desktop non-interactively.
#
# Certum's SimplySign cloud has no headless login: the certificate only reaches
# the Windows store after the GUI client authenticates. So we generate the
# current TOTP from the otpauth:// secret (CERTUM_OTP_URI) and paste the
# credentials into the login dialog. This needs an interactive desktop session,
# which GitHub-hosted Windows runners provide.
#
# Based on https://www.devas.life/how-to-automate-signing-your-windows-app-with-certum/
# and the refinements in blinkdisk's connect-simplysign.ps1.
param(
[string]$OtpUri = $env:CERTUM_OTP_URI,
[string]$UserId = $env:CERTUM_USERID,
[string]$ExePath = $env:CERTUM_EXE_PATH
)
if (-not $OtpUri) { Write-Host "ERROR: CERTUM_OTP_URI not provided"; exit 1 }
if (-not $UserId) { Write-Host "ERROR: CERTUM_USERID not provided"; exit 1 }
if (-not $ExePath) {
$ExePath = "C:\Program Files\Certum\SimplySign Desktop\SimplySignDesktop.exe"
}
Write-Host "=== SimplySign Desktop TOTP authentication ==="
if (-not (Test-Path $ExePath)) {
Write-Host "ERROR: SimplySign Desktop not found at $ExePath"
exit 1
}
# --- Parse the otpauth:// URI (works on PowerShell 5.1 and 7+) ---------------
$uri = [Uri]$OtpUri
try {
$q = [System.Web.HttpUtility]::ParseQueryString($uri.Query)
} catch {
$q = @{}
foreach ($part in $uri.Query.TrimStart('?') -split '&') {
$kv = $part -split '=', 2
if ($kv.Count -eq 2) { $q[$kv[0]] = [Uri]::UnescapeDataString($kv[1]) }
}
}
$Base32 = $q['secret']
$Digits = if ($q['digits']) { [int]$q['digits'] } else { 6 }
$Period = if ($q['period']) { [int]$q['period'] } else { 30 }
$Algorithm = if ($q['algorithm']) { $q['algorithm'].ToUpper() } else { 'SHA256' }
if (-not $Base32) { Write-Host "ERROR: otpauth URI has no 'secret'"; exit 1 }
if ($Algorithm -notin @('SHA1', 'SHA256', 'SHA512')) {
Write-Host "ERROR: unsupported TOTP algorithm: $Algorithm"
exit 1
}
# --- TOTP generator (RFC 6238), inline C# so there are no dependencies --------
Add-Type -Language CSharp @"
using System;
using System.Security.Cryptography;
public static class Totp
{
private const string B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
private static byte[] Base32Decode(string s)
{
s = s.TrimEnd('=').ToUpperInvariant();
byte[] bytes = new byte[s.Length * 5 / 8];
int bitBuffer = 0, bitsLeft = 0, idx = 0;
foreach (char c in s)
{
int val = B32.IndexOf(c);
if (val < 0) throw new ArgumentException("Invalid Base32 char: " + c);
bitBuffer = (bitBuffer << 5) | val;
bitsLeft += 5;
if (bitsLeft >= 8) { bytes[idx++] = (byte)(bitBuffer >> (bitsLeft - 8)); bitsLeft -= 8; }
}
return bytes;
}
private static HMAC Hmac(string algorithm, byte[] key)
{
switch (algorithm.ToUpper())
{
case "SHA1": return new HMACSHA1(key);
case "SHA256": return new HMACSHA256(key);
case "SHA512": return new HMACSHA512(key);
default: throw new ArgumentException("Unsupported algorithm: " + algorithm);
}
}
public static string Now(string secret, int digits, int period, string algorithm)
{
byte[] key = Base32Decode(secret);
long counter = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / period;
byte[] cnt = BitConverter.GetBytes(counter);
if (BitConverter.IsLittleEndian) Array.Reverse(cnt);
byte[] hash;
using (var h = Hmac(algorithm, key)) { hash = h.ComputeHash(cnt); }
int offset = hash[hash.Length - 1] & 0x0F;
int binary =
((hash[offset] & 0x7F) << 24) |
((hash[offset + 1] & 0xFF) << 16) |
((hash[offset + 2] & 0xFF) << 8) |
(hash[offset + 3] & 0xFF);
int otp = binary % (int)Math.Pow(10, digits);
return otp.ToString(new string('0', digits));
}
}
"@
function Find-UiByName($el, $walker, $name) {
$c = $walker.GetFirstChild($el)
while ($c) {
if ($c.Current.Name -eq $name) { return $c }
$r = Find-UiByName $c $walker $name
if ($r) { return $r }
$c = $walker.GetNextSibling($c)
}
return $null
}
# An outdated SimplySign build pops a modal "New version found - download?" box
# over the login form, which swallows the credential keystrokes. Decline it by
# clicking "No" (Invoke / legacy default action / click the element's point).
function Dismiss-UpdatePrompt {
try { Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes -ErrorAction Stop } catch { return $false }
if (-not ('Win32Mouse' -as [type])) {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class Win32Mouse {
[DllImport("user32.dll")] static extern bool SetCursorPos(int x, int y);
[DllImport("user32.dll")] static extern void mouse_event(uint f, uint x, uint y, uint d, int e);
public static void Click(int x, int y){ SetCursorPos(x,y); mouse_event(0x02,0,0,0,0); mouse_event(0x04,0,0,0,0); }
}
"@
}
$procIds = @((Get-Process -Name '*SimplySign*' -ErrorAction SilentlyContinue).Id)
$walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker
$top = $walker.GetFirstChild([System.Windows.Automation.AutomationElement]::RootElement)
while ($top) {
if ($procIds -contains $top.Current.ProcessId) {
$no = Find-UiByName $top $walker "No"
if ($no) {
Write-Host "Update prompt detected; declining (clicking 'No')."
try { ($no.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern)).Invoke(); return $true } catch {}
try { ($no.GetCurrentPattern([System.Windows.Automation.LegacyIAccessiblePattern]::Pattern)).DoDefaultAction(); return $true } catch {}
try { $pt = $no.GetClickablePoint(); [Win32Mouse]::Click([int]$pt.X, [int]$pt.Y); return $true } catch { Write-Host "Click 'No' failed: $($_.Exception.Message)" }
}
}
$top = $walker.GetNextSibling($top)
}
return $false
}
# Start from a clean slate so a stale window/session can't swallow the keystrokes.
if ($existing = Get-Process -Name "SimplySignDesktop" -ErrorAction Ignore) {
Write-Host "Killing existing SimplySignDesktop process..."
$existing | Stop-Process -Force
Start-Sleep -Seconds 1
}
Write-Host "Launching SimplySign Desktop..."
$proc = Start-Process -FilePath $ExePath -PassThru
Start-Sleep -Seconds 3
$wshell = New-Object -ComObject WScript.Shell
Write-Host "Focusing the login window..."
$focused = $wshell.AppActivate($proc.Id)
if (-not $focused) { $focused = $wshell.AppActivate('SimplySign Desktop') }
for ($i = 0; (-not $focused) -and ($i -lt 10); $i++) {
Start-Sleep -Milliseconds 500
$focused = $wshell.AppActivate($proc.Id) -or $wshell.AppActivate('SimplySign Desktop')
}
if (-not $focused) {
Write-Host "ERROR: could not bring SimplySign Desktop to the foreground"
exit 1
}
# Decline the "new version available" modal if it's covering the login form.
if (Dismiss-UpdatePrompt) { Start-Sleep -Milliseconds 800 }
# Re-assert focus: dismissing the dialog can move the foreground window, and
# keystrokes sent to the wrong window are silently dropped.
$wshell.AppActivate($proc.Id) | Out-Null
$wshell.AppActivate('SimplySign Desktop') | Out-Null
Start-Sleep -Milliseconds 400
# Paste rather than type: SendKeys mangles characters like + ^ % ( ) and can
# drop characters; pasting delivers the exact string. Falls back to typing if
# the clipboard is unavailable.
function Set-Field([string]$text) {
try {
Set-Clipboard -Value $text -ErrorAction Stop
Start-Sleep -Milliseconds 150
$wshell.SendKeys("^v")
} catch {
Write-Host "Clipboard unavailable ($($_.Exception.Message)); typing instead."
$wshell.SendKeys($text)
}
Start-Sleep -Milliseconds 250
}
Write-Host "Injecting credentials..."
Set-Field $UserId
$wshell.SendKeys("{TAB}")
Start-Sleep -Milliseconds 200
# Generate the code right before sending it so it can't expire while we focus.
$otp = [Totp]::Now($Base32, $Digits, $Period, $Algorithm)
Set-Field $otp
Start-Sleep -Milliseconds 200
$wshell.SendKeys("{ENTER}")
# Don't leave the OTP / username sitting on the clipboard afterwards.
try { Set-Clipboard -Value " " -ErrorAction Stop } catch {}
Write-Host "Waiting for authentication to settle..."
Start-Sleep -Seconds 5
if (-not (Get-Process -Id $proc.Id -ErrorAction SilentlyContinue)) {
Write-Host "ERROR: SimplySign Desktop exited - authentication failed."
exit 1
}
# Confirm the login actually mounted the signing certificate into the store.
$thumb = if ($env:CERTUM_CERT_SHA1) { ($env:CERTUM_CERT_SHA1 -replace '\s', '').ToUpperInvariant() } else { $null }
Write-Host "Verifying the signing certificate reached the store..."
$deadline = (Get-Date).AddSeconds(60)
$found = $false
do {
$certs = Get-ChildItem Cert:\CurrentUser\My, Cert:\LocalMachine\My -ErrorAction SilentlyContinue
if ($thumb) { $found = [bool]($certs | Where-Object { $_.Thumbprint -eq $thumb }) }
else { $found = [bool]($certs | Where-Object { $_.Subject -like '*Certum*' -or $_.Issuer -like '*Certum*' }) }
if ($found) { break }
Start-Sleep -Seconds 3
} while ((Get-Date) -lt $deadline)
if ($found) {
Write-Host "=== Authentication complete: signing certificate is available. ==="
} else {
Write-Host "ERROR: signing certificate did not appear after authentication."
exit 1
}
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# Install Certum SimplySign Desktop on the (ephemeral) Windows runner.
# SimplySign Desktop is what mounts the cloud signing certificate as a virtual
# smart card into the Windows certificate store; signtool then selects it by
# thumbprint. The version + checksum are pinned for reproducibility.
set -euo pipefail
VERSION="9.3.4.72"
URL="https://files.certum.eu/software/SimplySignDesktop/Windows/${VERSION}/SimplySignDesktop-${VERSION}-64-bit-en.msi"
EXPECTED_SHA256="bd51ebbaaac20fc7d59ab7103b5ed532b7800586df4e31f6999d03a394f9c515"
INSTALLER="SimplySignDesktop.msi"
INSTALL_DIR="/c/Program Files/Certum/SimplySign Desktop"
echo "=== Installing Certum SimplySign Desktop ${VERSION} ==="
# Idempotent: a cached/pre-provisioned image may already have it.
if [ -d "$INSTALL_DIR" ]; then
echo "Already installed at: $INSTALL_DIR"
exit 0
fi
echo "Downloading installer..."
curl -L "$URL" -o "$INSTALLER" --fail --max-time 600
ACTUAL_SHA256=$(sha256sum "$INSTALLER" | awk '{print $1}')
if [ "$EXPECTED_SHA256" != "$ACTUAL_SHA256" ]; then
echo "ERROR: checksum mismatch"
echo " expected: $EXPECTED_SHA256"
echo " actual: $ACTUAL_SHA256"
exit 1
fi
echo "Checksum verified."
echo "Running msiexec (silent)..."
# Start-Process inherits this CWD, so the relative installer path resolves here.
powershell -Command "Start-Process msiexec.exe -ArgumentList '/i','${INSTALLER}','/quiet','/norestart','/l*v','install.log','ALLUSERS=1','REBOOT=ReallySuppress' -Wait -NoNewWindow"
if [ ! -d "$INSTALL_DIR" ]; then
echo "ERROR: installation directory not found after install"
echo "Last 20 lines of install.log:"
tail -20 install.log 2>/dev/null || echo "(no install.log)"
exit 1
fi
echo "SimplySign Desktop installed."
+66
View File
@@ -0,0 +1,66 @@
# Sign the given files with the Certum cloud certificate (selected by SHA1
# thumbprint from the store) and verify each signature. Assumes SimplySign
# Desktop has already authenticated (connect-simplysign.ps1).
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string[]]$Files,
[string]$CertSha1 = $env:CERTUM_CERT_SHA1,
[string]$TimestampUrl = "http://time.certum.pl"
)
$ErrorActionPreference = "Stop"
if (-not $CertSha1) { throw "CERTUM_CERT_SHA1 not provided." }
$thumb = ($CertSha1 -replace '\s', '').ToUpperInvariant()
# Resolve the newest signtool.exe from the installed Windows SDK(s).
$kitsBin = "C:\Program Files (x86)\Windows Kits\10\bin"
$signtool = Get-ChildItem -Path $kitsBin -Recurse -Filter signtool.exe -File -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like '*\x64\*' } |
Sort-Object { [version]$_.Directory.Parent.Name } |
Select-Object -Last 1 -ExpandProperty FullName
if (-not $signtool) { throw "signtool.exe not found under $kitsBin" }
Write-Host "Using signtool: $signtool"
# The SimplySign virtual smart card registers in the store a moment after the
# desktop client authenticates; poll for our certificate before signing.
Write-Host "Waiting for certificate $thumb to appear in the store..."
$deadline = (Get-Date).AddSeconds(120)
$cert = $null
$elapsed = 0
do {
$cert = Get-ChildItem Cert:\CurrentUser\My, Cert:\LocalMachine\My -ErrorAction SilentlyContinue |
Where-Object { $_.Thumbprint -eq $thumb } | Select-Object -First 1
if ($cert) { break }
Start-Sleep -Seconds 3
$elapsed += 3
Write-Host " ...still waiting (${elapsed}s)"
} while ((Get-Date) -lt $deadline)
if (-not $cert) {
Write-Host "Certificate not found. CurrentUser\My + LocalMachine\My contents:"
Get-ChildItem Cert:\CurrentUser\My, Cert:\LocalMachine\My -ErrorAction SilentlyContinue |
Select-Object Thumbprint, Subject, HasPrivateKey | Format-Table -AutoSize | Out-String | Write-Host
Write-Host "SimplySign processes:"
Get-Process -Name '*SimplySign*' -ErrorAction SilentlyContinue |
Select-Object Name, Id, Responding, MainWindowTitle | Format-Table -AutoSize | Out-String | Write-Host
throw "Certificate $thumb not found in the store - SimplySign authentication likely failed (see dump above)."
}
Write-Host "Found signing certificate: $($cert.Subject)"
$failed = @()
foreach ($file in $Files) {
if (-not (Test-Path $file)) { throw "File to sign not found: $file" }
Write-Host "=== Signing $file ==="
& $signtool sign /sha1 $thumb /fd sha256 /td sha256 /tr $TimestampUrl /v $file
if ($LASTEXITCODE -ne 0) { $failed += $file; continue }
& $signtool verify /pa /v $file
if ($LASTEXITCODE -ne 0) { $failed += $file }
}
if ($failed.Count -gt 0) {
throw "Signing/verification failed for: $($failed -join ', ')"
}
Write-Host "All binaries signed and verified."
+26
View File
@@ -0,0 +1,26 @@
# This action simulates a CD pipeline without the package publishing
# to ensure that changes don't break the real run
name: Daybreak Dry-Run CD Pipeline
on:
pull_request:
branches:
- master
paths:
- "Daybreak.Core/**"
- "Daybreak.Installer/**"
- "Daybreak.API/**"
- "Daybreak.Shared/**"
- "Daybreak.Windows/**"
- "Daybreak.Linux/**"
- ".github/scripts/**"
- ".github/workflows/cd-template.yml"
- ".github/workflows/cd-dry-run.yml"
jobs:
cd:
uses: ./.github/workflows/cd-template.yml
with:
publishRelease: false
signBinaries: false
secrets: inherit
+328
View File
@@ -0,0 +1,328 @@
# Reusable CD pipeline with optional release publishing
name: Daybreak CD Template
on:
workflow_call:
inputs:
publishRelease:
description: "Whether to create and publish the GitHub release"
required: false
type: boolean
default: false
signBinaries:
description: "Whether to code-sign Windows binaries"
required: false
type: boolean
default: false
jobs:
# ───────────────────────────────────────────────────────
# Stage 1 — Build the x86 Windows-only components
# (Injector + API) that both platforms need.
# ───────────────────────────────────────────────────────
build-x86:
runs-on: windows-2025
outputs:
version: ${{ steps.get_version.outputs.version }}
env:
Configuration: Release
Actions_Allow_Unsecure_Commands: true
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
CERTUM_USERID: ${{ secrets.CERTUM_USERID }}
CERTUM_CERT_SHA1: ${{ secrets.CERTUM_CERT_SHA1 }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Get version
id: get_version
shell: pwsh
run: |
$version = (Select-String -Path Directory.Build.props -Pattern '<Version>(.*)</Version>').Matches.Groups[1].Value
echo "version=$version" >> $env:GITHUB_OUTPUT
- name: Install .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.x"
- name: Publish x86 components
run: .\Scripts\PublishWindowsX86.ps1
shell: pwsh
# Certum SimplySign code signing. Binaries are signed only when explicitly
# requested and the Certum secrets are provisioned.
- name: Set up Certum SimplySign
if: inputs.signBinaries && env.CERTUM_OTP_URI != ''
shell: bash
run: |
chmod +x ./.github/scripts/install-simplysign.sh
./.github/scripts/install-simplysign.sh
powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign.ps1"
- name: Authenticate Certum SimplySign
if: inputs.signBinaries && env.CERTUM_OTP_URI != ''
shell: bash
run: powershell -ExecutionPolicy Bypass -File "./.github/scripts/connect-simplysign.ps1"
- name: Sign x86 binaries
if: inputs.signBinaries && env.CERTUM_OTP_URI != ''
run: |
./.github/scripts/sign-certum.ps1 -Files @(
"Publish/Injector/Daybreak.Injector.exe",
"Publish/Api/Daybreak.API.dll"
)
shell: pwsh
- name: Upload x86 artifacts
uses: actions/upload-artifact@v7
with:
name: x86-components
path: |
Publish/Injector/
Publish/Api/
retention-days: 1
# ───────────────────────────────────────────────────────
# Stage 2 — Platform builds (parallel)
# ───────────────────────────────────────────────────────
build-windows:
needs: build-x86
runs-on: windows-2025
env:
Configuration: Release
Actions_Allow_Unsecure_Commands: true
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
CERTUM_USERID: ${{ secrets.CERTUM_USERID }}
CERTUM_CERT_SHA1: ${{ secrets.CERTUM_CERT_SHA1 }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Install .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.x"
- name: Setup project secrets
run: |
dotnet user-secrets --project Daybreak.Core\Daybreak.Core.csproj set AadApplicationId "${{ secrets.AadApplicationId }}"
dotnet user-secrets --project Daybreak.Core\Daybreak.Core.csproj set AadTenantId "${{ secrets.AadTenantId }}"
dotnet user-secrets --project Daybreak.Core\Daybreak.Core.csproj set ApmServiceAccount "${{ secrets.ApmServiceAccount }}"
dotnet user-secrets --project Daybreak.Core\Daybreak.Core.csproj set ApmServiceKey "${{ secrets.ApmServiceKey }}"
dotnet user-secrets --project Daybreak.Core\Daybreak.Core.csproj set ApmUri "${{ secrets.ApmUri }}"
- name: Download x86 artifacts
uses: actions/download-artifact@v8
with:
name: x86-components
path: Publish/
- name: Publish Windows x64
run: .\Scripts\PublishWindows.ps1
shell: pwsh
- name: Set up Certum SimplySign
if: inputs.signBinaries && env.CERTUM_OTP_URI != ''
shell: bash
run: |
chmod +x ./.github/scripts/install-simplysign.sh
./.github/scripts/install-simplysign.sh
powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign.ps1"
- name: Authenticate Certum SimplySign
if: inputs.signBinaries && env.CERTUM_OTP_URI != ''
shell: bash
run: powershell -ExecutionPolicy Bypass -File "./.github/scripts/connect-simplysign.ps1"
- name: Sign Windows build
if: inputs.signBinaries && env.CERTUM_OTP_URI != ''
run: |
./.github/scripts/sign-certum.ps1 -Files @(
"Publish/Daybreak.exe",
"Publish/Installer/Daybreak.Installer.exe"
)
shell: pwsh
- name: Upload Windows build
uses: actions/upload-artifact@v7
with:
name: windows-build
path: Publish/
retention-days: 1
build-linux:
needs: build-x86
runs-on: ubuntu-latest
env:
Configuration: Release
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Install .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.x"
- name: Setup project secrets
run: |
dotnet user-secrets --project Daybreak.Core/Daybreak.Core.csproj set AadApplicationId "${{ secrets.AadApplicationId }}"
dotnet user-secrets --project Daybreak.Core/Daybreak.Core.csproj set AadTenantId "${{ secrets.AadTenantId }}"
dotnet user-secrets --project Daybreak.Core/Daybreak.Core.csproj set ApmServiceAccount "${{ secrets.ApmServiceAccount }}"
dotnet user-secrets --project Daybreak.Core/Daybreak.Core.csproj set ApmServiceKey "${{ secrets.ApmServiceKey }}"
dotnet user-secrets --project Daybreak.Core/Daybreak.Core.csproj set ApmUri "${{ secrets.ApmUri }}"
- name: Download x86 artifacts
uses: actions/download-artifact@v8
with:
name: x86-components
path: Publish/
- name: Publish Linux x64
run: pwsh ./Scripts/PublishLinux.ps1
shell: bash
- name: Set executable permissions
run: |
chmod +x Publish/Daybreak
chmod +x Publish/Daybreak.Wayland.sh
chmod +x Publish/Installer/Daybreak.Installer
- name: Upload Linux build
uses: actions/upload-artifact@v7
with:
name: linux-build
path: Publish/
retention-days: 1
# ───────────────────────────────────────────────────────
# Stage 3 — Package (always)
# ───────────────────────────────────────────────────────
package:
needs: [build-x86, build-windows, build-linux]
runs-on: ubuntu-latest
env:
Actions_Allow_Unsecure_Commands: true
Version: ${{ needs.build-x86.outputs.version }}
steps:
# ── Windows package ──
- name: Download Windows build
uses: actions/download-artifact@v8
with:
name: windows-build
path: windows-publish/
- name: Clean and zip Windows build
run: |
cd windows-publish
find . -name '*.pdb' -delete
find . -name '*.dbg' -delete
if [ -f Installer/Daybreak.Installer.exe ]; then
mv Installer/Daybreak.Installer.exe Installer/Daybreak.Installer.Temp.exe
fi
cd ..
cd windows-publish && zip -r ../daybreakv${{ env.Version }}.zip . && cd ..
# ── Linux package ──
- name: Download Linux build
uses: actions/download-artifact@v8
with:
name: linux-build
path: linux-publish/
- name: Clean and zip Linux build
run: |
cd linux-publish
find . -name '*.pdb' -delete
find . -name '*.dbg' -delete
if [ -f Installer/Daybreak.Installer ]; then
chmod +x Installer/Daybreak.Installer
mv Installer/Daybreak.Installer Installer/Daybreak.Installer.Temp
fi
chmod +x Daybreak Daybreak.Wayland.sh
cd ..
cd linux-publish && zip -r ../daybreakv${{ env.Version }}-linux.zip . && cd ..
- name: Upload release artifacts
uses: actions/upload-artifact@v7
with:
name: release-zips
path: |
daybreakv${{ env.Version }}.zip
daybreakv${{ env.Version }}-linux.zip
retention-days: 7
# ───────────────────────────────────────────────────────
# Stage 4 — Release (optional)
# ───────────────────────────────────────────────────────
release:
needs: [build-x86, package]
runs-on: ubuntu-latest
if: ${{ inputs.publishRelease }}
env:
Actions_Allow_Unsecure_Commands: true
Version: ${{ needs.build-x86.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Download release artifacts
uses: actions/download-artifact@v8
with:
name: release-zips
path: release-assets/
- name: Get Latest Tag
id: getLatestTag
uses: WyriHaximus/github-action-get-previous-tag@v2
- name: Generate changelog
id: changelog
run: |
changeLog=$(git log --no-merges --pretty="%h - %s (%an)<br />" ${{ steps.getLatestTag.outputs.tag }}..HEAD)
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$changeLog" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# ── Release ──
- name: Create release draft
uses: Xotl/cool-github-releases@v1.1.10
with:
mode: update
tag_name: v${{ env.Version }}
release_name: Daybreak v${{ env.Version }}
assets: release-assets/daybreakv${{ env.Version }}.zip;release-assets/daybreakv${{ env.Version }}-linux.zip
github_token: ${{ secrets.GITHUB_TOKEN }}
replace_assets: true
body_mrkdwn: |
${{ steps.changelog.outputs.content }}
isDraft: true
- name: Publish release
run: gh release edit v${{ env.Version }} --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+21 -101
View File
@@ -1,109 +1,29 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# This continuous integration pipeline is triggered anytime a user pushes code to the repo.
# This pipeline builds the Wpf project, runs unit tests, then saves the MSIX build artifact.
name: Daybreak CD Pipeline
# This continuous delivery pipeline is triggered on pushes to master.
# It builds the Windows x86 components (Injector + API), then builds
# the Windows x64 and Linux x64 platform bundles in parallel, and
# finally packages both into release zips and publishes a GitHub release.
name: Daybreak CD Pipeline
on:
push:
branches:
- master
paths:
- "Daybreak/**"
- "Daybreak.Core/**"
- "Daybreak.Installer/**"
- "Daybreak.API/**"
- "Daybreak.Shared/**"
- "Daybreak.Windows/**"
- "Daybreak.Linux/**"
- ".github/scripts/**"
- ".github/workflows/cd-template.yml"
- ".github/workflows/cd.yaml"
workflow_dispatch:
jobs:
build:
strategy:
matrix:
targetplatform: [x86]
runs-on: windows-latest
env:
Configuration: Release
Solution_Path: Daybreak.sln
Test_Project_Path: Daybreak.Tests\Daybreak.Tests.csproj
Wpf_Project_Path: Daybreak\Daybreak.csproj
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Latest Tag
id: getLatestTag
uses: WyriHaximus/github-action-get-previous-tag@v1
- name: Generate changelog
id: gen_changelog
run: |
$changeLog = git log --no-merges --pretty="%h - %s (%an)<br />" ${{ env.LatestReleaseTag }}..HEAD
echo "::set-env name=Changelog::$changeLog"
env:
LatestReleaseTag: ${{steps.getLatestTag.outputs.tag}}
- name: Print changelog
run: |
echo "${{ env.Changelog }}"
- name: Install .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: '7.x'
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.3.1
- name: Setup project secrets
run: |
dotnet user-secrets --project Daybreak\Daybreak.csproj set AadApplicationId "${{ secrets.AadApplicationId }}"
dotnet user-secrets --project Daybreak\Daybreak.csproj set AadTenantId "${{ secrets.AadTenantId }}"
- name: Restore project
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Build Daybreak project
run: dotnet build Daybreak -c $env:Configuration
- name: Set version variable
run: |
$version = .\Scripts\GetBuildVersion.ps1
echo "::set-env name=Version::$version"
- name: Create publish launcher files
run: dotnet publish .\Daybreak\Daybreak.csproj -c $env:Configuration -r $env:RuntimeIdentifier -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained true -o .\Publish
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Create publish installer files
run: dotnet publish .\Daybreak.Installer\Daybreak.Installer.csproj -c $env:Configuration -r $env:RuntimeIdentifier -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained true -o .\Publish
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Pack publish files
run: |
Write-Host $env
.\Scripts\BuildRelease.ps1 -version $env:Version
shell: pwsh
- name: Publish release
uses: Xotl/cool-github-releases@v1.1.8
with:
mode: update
tag_name: v${{ env.Version }}
release_name: Daybreak v${{ env.Version }}
assets: .\Publish\daybreakv${{ env.Version }}.zip
github_token: ${{ env.GITHUB_TOKEN }}
replace_assets: true
body_mrkdwn: ${{ env.Changelog }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cd:
uses: ./.github/workflows/cd-template.yml
with:
publishRelease: true
signBinaries: true
secrets: inherit
+50 -37
View File
@@ -1,53 +1,66 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# This continuous integration pipeline is triggered anytime a user pushes code to the repo.
# This pipeline builds the Wpf project, runs unit tests, then saves the MSIX build artifact.
name: Daybreak CI Pipeline
# This continuous integration pipeline is triggered on pull requests to master.
# It validates that both the Windows and Linux builds compile successfully.
name: Daybreak CI Pipeline
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
strategy:
matrix:
targetplatform: [x86]
build-windows:
runs-on: windows-latest
env:
Solution_Path: Daybreak.sln
Test_Project_Path: Daybreak.Tests\Daybreak.Tests.csproj
Wpf_Project_Path: Daybreak\Daybreal.csproj
Actions_Allow_Unsecure_Commands: true
Configuration: Debug
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Install .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: '7.x'
- name: Install .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.x"
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.3.1
- name: Build Windows project
run: dotnet build Daybreak.Windows/Daybreak.Windows.csproj -c $env:Configuration
- name: Execute Unit Tests
run: dotnet test $env:Test_Project_Path
- name: Build Injector
run: dotnet build Daybreak.Injector/Daybreak.Injector.csproj -c $env:Configuration
- name: Restore the Wpf application to populate the obj folder
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
env:
Configuration: Debug
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Build API
run: dotnet build Daybreak.API/Daybreak.API.csproj -c $env:Configuration
- name: Build Installer
run: dotnet build Daybreak.Installer/Daybreak.Installer.csproj -c $env:Configuration
- name: Run tests
run: dotnet test Daybreak.Tests/Daybreak.Tests.csproj -c $env:Configuration
build-linux:
runs-on: ubuntu-latest
env:
Configuration: Debug
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Install .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.x"
- name: Build Linux project
run: dotnet build Daybreak.Linux/Daybreak.Linux.csproj -c $Configuration
- name: Build Installer (linux-x64)
run: dotnet build Daybreak.Installer/Daybreak.Installer.csproj -c $Configuration
+20 -11
View File
@@ -12,10 +12,7 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '41 12 * * 6'
@@ -36,13 +33,22 @@ jobs:
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
env:
Configuration: Release
Solution_Path: Daybreak.slnx
Test_Project_Path: Daybreak.Tests\Daybreak.Tests.csproj
Wpf_Project_Path: Daybreak.Windows\Daybreak.Windows.csproj
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -57,12 +63,12 @@ jobs:
# If this step fails, then you should remove it and run the build manually (see below)
- name: Install .NET Core
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v6
with:
dotnet-version: '6.x'
dotnet-version: '10.x'
- name: Autobuild
uses: github/codeql-action/autobuild@v2
#- name: Autobuild
# uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -74,5 +80,8 @@ jobs:
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Build Daybreak project
run: dotnet build Daybreak.Windows -c $env:Configuration --property:SolutionDir=$env:GITHUB_WORKSPACE
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
@@ -0,0 +1,12 @@
# Manually trigger to create a new release branch with bumped build version
name: Create Build Release Branch
on:
workflow_dispatch:
jobs:
create-release:
uses: ./.github/workflows/create-release-branch-template.yml
with:
versionBump: "*.*.^.0"
secrets: inherit
@@ -0,0 +1,12 @@
# Manually trigger to create a new release branch with bumped major version
name: Create Major Release Branch
on:
workflow_dispatch:
jobs:
create-release:
uses: ./.github/workflows/create-release-branch-template.yml
with:
versionBump: "^.0.0.0"
secrets: inherit
@@ -0,0 +1,12 @@
# Manually trigger to create a new release branch with bumped minor version
name: Create Minor Release Branch
on:
workflow_dispatch:
jobs:
create-release:
uses: ./.github/workflows/create-release-branch-template.yml
with:
versionBump: "*.^.0.0"
secrets: inherit
@@ -0,0 +1,66 @@
# Reusable template for creating release branches with version bumps
name: Create Release Branch Template
on:
workflow_call:
inputs:
versionBump:
description: "Version bump command: bump-major, bump-minor, bump-build, bump-revision"
required: true
type: string
jobs:
create-release-branch:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }}
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Bump version in Directory.Build.props
id: bump
uses: vers-one/dotnet-project-version-updater@v1.8
with:
file: "Directory.Build.props"
version: ${{ inputs.versionBump }}
- name: Check if release branch already exists
id: check_branch
run: |
branch_name="release/${{ steps.bump.outputs.newVersion }}"
if git ls-remote --heads origin "$branch_name" | grep -q "$branch_name"; then
echo "Branch $branch_name already exists"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "Branch $branch_name does not exist"
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Create release branch
if: steps.check_branch.outputs.exists == 'false'
run: |
new_version="${{ steps.bump.outputs.newVersion }}"
branch_name="release/$new_version"
# Create new branch from master
git checkout -b "$branch_name"
# Commit the version change (already updated by the action)
git add Directory.Build.props
git commit -m "Bump version to $new_version"
# Push the new branch
git push origin "$branch_name"
echo "✅ Created release branch: $branch_name with version $new_version"
@@ -0,0 +1,20 @@
# Automatically creates a new release branch with bumped revision
# after a successful CD pipeline run on master
name: Create Revision Release Branch
on:
workflow_run:
workflows: ["Daybreak CD Pipeline"]
types:
- completed
branches:
- master
jobs:
create-release:
# Only run if the CD pipeline succeeded
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: ./.github/workflows/create-release-branch-template.yml
with:
versionBump: bump-revision
secrets: inherit
@@ -0,0 +1,47 @@
name: Label Merged Issues
on:
pull_request:
types: [closed]
branches:
- 'release/**'
jobs:
label-issues:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Label linked issues as merged
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prBody = context.payload.pull_request.body || '';
const prTitle = context.payload.pull_request.title || '';
// Match issue references like #123, fixes #123, closes #123, resolves #123
const issuePattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)?[\s:]*#(\d+)/gi;
const issueNumbers = new Set();
// Search in PR body and title
const textToSearch = `${prTitle} ${prBody}`;
let match;
while ((match = issuePattern.exec(textToSearch)) !== null) {
issueNumbers.add(parseInt(match[1]));
}
// Apply 'merged' label to each linked issue
for (const issueNumber of issueNumbers) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: ['merged']
});
console.log(`Added 'merged' label to issue #${issueNumber}`);
} catch (error) {
console.log(`Failed to label issue #${issueNumber}: ${error.message}`);
}
}
+32 -25
View File
@@ -1,48 +1,55 @@
name: Daybreak Version Check
name: Daybreak Version Check
on:
pull_request:
branches:
- master
paths:
- "Daybreak/**"
- "Daybreak.Core/**"
- "Daybreak.Installer/**"
- "Daybreak.API/**"
- "Daybreak.Shared/**"
- "Daybreak.Windows/**"
- "Daybreak.Linux/**"
jobs:
check_version:
strategy:
matrix:
targetplatform: [x64]
runs-on: windows-latest
runs-on: ubuntu-latest
env:
Configuration: Release
Solution_Path: Daybreak.sln
Solution_Path: Daybreak.slnx
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Latest Tag
id: getLatestTag
uses: WyriHaximus/github-action-get-previous-tag@v1
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Build Daybreak project
run: dotnet build Daybreak -c $env:Configuration
- name: Install .NET Core
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.x"
- name: Set version variable
run: |
$version = .\Scripts\GetBuildVersion.ps1
echo "::set-env name=Version::$version"
- name: Get Latest Tag
id: getLatestTag
uses: WyriHaximus/github-action-get-previous-tag@v2
- name: Build Daybreak project
run: dotnet build Daybreak.Linux -c $Configuration --property:SolutionDir=$GITHUB_WORKSPACE
- name: Get version
id: get_version
run: |
version=$(grep '<Version>' Directory.Build.props | sed 's/.*<Version>\(.*\)<\/Version>.*/\1/')
echo "version=$version" >> $GITHUB_OUTPUT
- name: Check version difference
run: pwsh ./Scripts/CompareVersions.ps1 -currentVersion ${{ steps.get_version.outputs.version }} -lastVersion ${{ steps.getLatestTag.outputs.tag }}
shell: pwsh
- name: Check version difference
run: |
.\Scripts\CompareVersions -currentVersion ${{ env.Version }} -lastVersion ${{ env.LatestReleaseTag }}
env:
LatestReleaseTag: ${{ steps.getLatestTag.outputs.tag }}
+11 -3
View File
@@ -28,12 +28,16 @@ x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
build/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Local NuGet packages folder (per-machine isolation)
.packages/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
@@ -177,8 +181,8 @@ DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Click-Once / local publish directories
[Pp]ublish/
# Publish Web Output
*.[Pp]ublish.xml
@@ -361,4 +365,8 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
.idea
Daybreak/wwwroot/wiki/*
+3
View File
@@ -0,0 +1,3 @@
[submodule "Daybreak.wiki"]
path = Daybreak.wiki
url = https://github.com/gwdevhub/Daybreak.wiki.git
+108
View File
@@ -0,0 +1,108 @@
-- Per-project Neovim config for the Daybreak repo.
-- Auto-sourced when nvim is started inside the repo, provided the user has
-- `vim.o.exrc = true` in their config (or runs `:set exrc`). Neovim 0.9+
-- prompts to trust this file on first load.
--
-- Commands added:
-- :DaybreakStageX86 [Config] build x86 components via wine and stage
-- them into Daybreak.Linux/bin/<Config>/...
-- /{Injector,Api}/ (default Config: Debug)
-- :DaybreakRunLinux run Daybreak.Linux via `dotnet run` in a
-- terminal split (honors launchSettings.json,
-- which easy-dot-net's launcher does not)
--
-- Note: a Linux/wine attach-debugger workflow was attempted (winedbg --gdb
-- stub *and* native Linux gdb -p attach), but both freeze GW reliably because
-- of ptrace/wineserver interactions that wine doesn't tolerate. For now,
-- prefer logging-based debugging via the API's console + log file in the
-- game directory (Daybreak.API<YYYYMMDD>.log next to Gw.exe).
local repo = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':p:h')
local scripts = repo .. '/Scripts'
-- ───── Progress notification with spinner ──────────────────────────────────
-- Uses nvim-notify / snacks.nvim's `id`/`replace` semantics to update a
-- single notification in place. Falls back to plain notify if the backend
-- doesn't support replace (the spinner just stops updating).
local SPINNER = { '', '', '', '', '', '', '', '' }
local function start_progress(title, message)
-- stable id so snacks.nvim/nvim-notify replace the same notification
local id = 'daybreak-' .. title:gsub('%s+', '-') .. '-' .. tostring(vim.uv.hrtime())
local state = { title = title, msg = message, frame = 1, done = false, id = id }
local function render()
local icon = state.done and '' or (SPINNER[state.frame] .. ' ')
local opts = {
title = state.title,
id = state.id,
timeout = state.done and 4000 or false,
hide_from_history = not state.done,
icon = state.done and '' or nil,
}
if state.notif then opts.replace = state.notif end
state.notif = vim.notify(icon .. state.msg, vim.log.levels.INFO, opts)
end
render()
state.timer = vim.uv.new_timer()
state.timer:start(80, 80, vim.schedule_wrap(function()
if state.done then return end
state.frame = (state.frame % #SPINNER) + 1
render()
end))
return {
update = function(msg) state.msg = msg; render() end,
finish = function(ok, msg)
state.done = true
if state.timer then state.timer:stop(); state.timer:close(); state.timer = nil end
state.msg = msg or state.msg
local final_opts = {
title = state.title,
id = state.id,
timeout = 4000,
icon = ok and '' or '',
}
if state.notif then final_opts.replace = state.notif end
vim.notify((ok and '' or '') .. state.msg,
ok and vim.log.levels.INFO or vim.log.levels.ERROR, final_opts)
end,
}
end
-- ───── :DaybreakStageX86 [Config] ─────────────────────────────────────────
vim.api.nvim_create_user_command('DaybreakStageX86', function(opts)
local config = opts.args ~= '' and opts.args or 'Debug'
local progress = start_progress('Daybreak StageX86', 'Building x86 components (' .. config .. ')…')
vim.fn.jobstart({ scripts .. '/StageX86ForDebug.sh', config, '' }, {
cwd = repo,
on_exit = function(_, code)
vim.schedule(function()
if code == 0 then
progress.finish(true, 'Staged x86 (' .. config .. ')')
else
progress.finish(false, 'Stage failed (exit ' .. code ..
'). See /tmp/wine-injector-publish.log, /tmp/wine-api-publish.log')
end
end)
end,
stdout_buffered = false,
stderr_buffered = false,
})
end, {
nargs = '?',
complete = function() return { 'Debug', 'Release' } end,
desc = 'Build x86 components in wine and stage into Daybreak.Linux output',
})
-- ───── :DaybreakRunLinux ───────────────────────────────────────────────────
-- easy-dot-net launches the produced binary directly, bypassing launchSettings.json.
-- This runs Daybreak.Linux through `dotnet run`, which honors the environment
-- variables (GDK_BACKEND, WEBKIT_DISABLE_DMABUF_RENDERER, etc.) declared there.
-- Opens in the current window (no split) so it integrates with normal buffer
-- navigation; Ctrl-\ Ctrl-N to leave terminal mode, then your usual buffer
-- bindings work.
vim.api.nvim_create_user_command('DaybreakRunLinux', function()
vim.cmd('enew')
vim.fn.termopen({ 'dotnet', 'run', '--project', 'Daybreak.Linux/Daybreak.Linux.csproj', '-c', 'Debug' },
{ cwd = repo })
vim.cmd('startinsert')
end, { desc = 'Run Daybreak.Linux via dotnet run in the current window' })
+45
View File
@@ -0,0 +1,45 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Daybreak.Linux",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/Daybreak.Linux/Daybreak.Linux.csproj",
"console": "integratedTerminal",
"justMyCode": true,
"stopAtEntry": false,
"suppressJITOptimizations": false,
"symbolOptions": {
"searchMicrosoftSymbolServer": false,
"searchNuGetOrgSymbolServer": false
},
"logging": {
"engineLogging": false,
"moduleLoad": false,
"exceptions": false,
"browserStdOut": false
}
},
{
"name": "Debug Daybreak.Windows",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/Daybreak.Windows/Daybreak.Windows.csproj",
"console": "integratedTerminal",
"justMyCode": true,
"stopAtEntry": false,
"suppressJITOptimizations": false,
"symbolOptions": {
"searchMicrosoftSymbolServer": false,
"searchNuGetOrgSymbolServer": false
},
"logging": {
"engineLogging": false,
"moduleLoad": false,
"exceptions": false,
"browserStdOut": false
}
}
]
}
+29
View File
@@ -0,0 +1,29 @@
{
"dotnet.defaultSolution": "Daybreak.slnx",
"omnisharp.projectLoadTimeout": 120,
"csharp.semanticHighlighting.enabled": true,
"search.followSymlinks": false,
"search.exclude": {
"**/.git": true,
"**/.vs": true,
"**/bin": true,
"**/obj": true,
"**/node_modules": true,
"**/.idea": true,
"**/Publish": true,
},
"files.watcherExclude": {
"**/.git/**": true,
"**/.vs/**": true,
"**/bin/**": true,
"**/obj/**": true,
"**/node_modules/**": true,
"**/Publish": true,
},
"files.exclude": {
"**/.vs": true,
"**/bin": true,
"**/obj": true,
"**/Publish": true
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build-linux",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Daybreak.Linux/Daybreak.Linux.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-windows",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Daybreak.Windows/Daybreak.Windows.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
+113
View File
@@ -0,0 +1,113 @@
# Contributing to Daybreak
## Getting started
Install the prerequisites listed under
[Build Requirements](README.md#build-requirements), then clone with submodules:
```bash
git clone --recurse-submodules https://github.com/AlexMacocian/Daybreak.git
```
Read the [Architecture Overview](README.md#architecture-overview) before making
changes so you put code in the right project.
## Branching model
There is always exactly one active release branch, named `release/[version]`
(for example `release/0.9.10.20`). It is the integration branch: all work
targets it, and `master` only receives finished releases.
```mermaid
flowchart LR
subgraph cycle["Release cycle"]
direction LR
R["release/0.9.10.20<br/><i>active release branch</i>"]
F["feature branch<br/><i>one per change</i>"]
R -->|fork| F
F -->|"PR, squash merge"| R
end
R -->|"PR, rebase merge"| M["master"]
M --> CD["CD pipeline<br/>builds and publishes the release"]
CD -->|"job bumps the revision"| N["release/0.9.10.21<br/><i>next active release branch</i>"]
```
### Feature work
1. Branch off the current release branch.
2. Implement one feature or fix. Keep the branch scoped to a single change.
3. Open a PR against the release branch.
4. Merge with **squash**, so each feature lands as one commit.
### Releasing
1. Open a PR from the release branch onto `master`.
2. Merge with **rebase**, keeping full history. Each feature stays a single
commit on `master`.
3. The [CD pipeline](.github/workflows/cd.yaml) runs on the push to `master`,
builds the Windows and Linux bundles and publishes a GitHub release.
4. On success,
[create-revision-release-branch.yml](.github/workflows/create-revision-release-branch.yml)
bumps the revision and creates the next `release/[version]` branch
automatically. That becomes the new active release branch.
Do not push directly to `master` or create release branches by hand.
## Versions
The version lives in a single place,
[`Directory.Build.props`](Directory.Build.props), and follows
`major.minor.build.revision`. It is bumped by the release branch creation job,
not manually.
Revision bumps happen automatically after every release. For the other
components, run the matching workflow manually before starting the next cycle:
| Workflow | Bumps |
| ------------------------------------------------------------------------------------------ | --------------------- |
| [create-major-release-branch.yml](.github/workflows/create-major-release-branch.yml) | `^.0.0.0` |
| [create-minor-release-branch.yml](.github/workflows/create-minor-release-branch.yml) | `*.^.0.0` |
| [create-build-release-branch.yml](.github/workflows/create-build-release-branch.yml) | `*.*.^.0` |
| [create-revision-release-branch.yml](.github/workflows/create-revision-release-branch.yml) | `*.*.*.^` (automatic) |
All four delegate to
[create-release-branch-template.yml](.github/workflows/create-release-branch-template.yml).
## Pull requests
Title format follows the existing history: a short description, plus the issue
reference when it closes one.
```text
Setup stable computer name in Wine prefix (Closes #1609)
```
[label-merged-issues.yaml](.github/workflows/label-merged-issues.yaml) parses
`close/fix/resolve #N` from the title and body of PRs merged into a release
branch and labels the linked issues, so use those keywords.
## Checks
[CI](.github/workflows/ci.yaml) and
[version_check.yaml](.github/workflows/version_check.yaml) only trigger on PRs
targeting `master`, so a feature PR onto a release branch is not covered by
them. Run the equivalent locally before opening one:
```bash
dotnet build Daybreak.Linux/Daybreak.Linux.csproj # or Daybreak.Windows on Windows
dotnet test Daybreak.Tests/Daybreak.Tests.csproj
```
The version check fails a PR to `master` if the version in
`Directory.Build.props` is not ahead of the latest tag. This is why the bump
must come from the release branch job.
## Code style
Formatting rules are in [`.editorconfig`](.editorconfig) and are enforced by the
compiler and analyzers; keep the build warning-free.
Tests live in [`Daybreak.Tests`](Daybreak.Tests), use MSTest with
FluentAssertions and NSubstitute, and only reference `Daybreak.Core`. If you
want platform code covered by tests, put the testable part in
[`Daybreak.Shared`](Daybreak.Shared).
+11
View File
@@ -0,0 +1,11 @@
namespace Daybreak.API.Configuration;
public static class BuildInfo
{
public const string Configuration =
#if DEBUG
"Debug";
#else
"Release";
#endif
}
@@ -0,0 +1,35 @@
namespace Daybreak.API.Configuration;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithConfiguration(this WebApplicationBuilder builder)
{
var config = BuildInfo.Configuration;
var appSettingsStream = GetManifestResourceStream($"Daybreak.API.Configuration.appsettings.json") ?? throw new InvalidOperationException("Failed to load appsettings.json");
var appSettingsConfigStream = GetManifestResourceStream($"Daybreak.API.Configuration.appsettings.{config}.json") ?? throw new InvalidOperationException($"Failed to load appsettings.{config}.json");
// Clear default file-based configuration sources that cause unnecessary reload events
builder.Configuration.Sources.Clear();
builder.Configuration
.AddJsonStream(appSettingsStream)
.AddJsonStream(appSettingsConfigStream)
.AddEnvironmentVariables();
builder.WebHost.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true");
return builder;
}
private static Stream? GetManifestResourceStream(string name)
{
var assembly = typeof(WebApplicationBuilderExtensions).Assembly;
var resourceName = assembly.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith(name, StringComparison.OrdinalIgnoreCase));
if (resourceName is null)
{
return default;
}
return assembly.GetManifestResourceStream(resourceName);
}
}
@@ -0,0 +1,2 @@
{
}
@@ -0,0 +1,10 @@
{
"Logging": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning"
}
}
}
}
@@ -0,0 +1,2 @@
{
}
@@ -0,0 +1,13 @@
{
"Logging": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Debug"
}
}
},
"Correlation": {
"Header": "X-Correlation-Vector"
}
}
@@ -0,0 +1,51 @@
using System.Core.Extensions;
using Daybreak.API.Interop.GuildWars;
using Daybreak.API.Services;
using Daybreak.Shared.Models.Api;
using Microsoft.AspNetCore.Mvc;
using Net.Sdk.Web;
namespace Daybreak.API.Controllers.Rest;
[GenerateController("api/v1/rest/character-select")]
[Tags("Character Select")]
public sealed class CharacterSelectController(
CharacterSelectService characterSelectService)
{
private readonly CharacterSelectService characterSelectService = characterSelectService.ThrowIfNull();
[GenerateGet]
[EndpointName("GetCharacterSelectInformation")]
[EndpointSummary("Get Character Select Information")]
[EndpointDescription("Get the character select information for the current player. Returns a serialized CharacterSelectInformation object")]
[ProducesResponseType<CharacterSelectInformation>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetCharacterSelectInformation(
CancellationToken cancellationToken)
{
var characterSelectInfo = await this.characterSelectService.GetCharacterSelectInformation(cancellationToken);
return characterSelectInfo is null ?
Results.StatusCode(StatusCodes.Status503ServiceUnavailable) :
Results.Ok(characterSelectInfo);
}
[GeneratePost("{identifier}")]
[EndpointName("ChangeCharacter")]
[EndpointSummary("Change Character by identifier")]
[EndpointDescription("Logs out from the current character and tries to log in with the desired character. Identifier can be either uuid or character name")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> ChangeCharacter(
[FromRoute] string identifier,
CancellationToken cancellationToken)
{
var isUuid = Uuid.TryParse(identifier, out _);
var result = isUuid
? await this.characterSelectService.ChangeCharacterByUuid(identifier, cancellationToken)
: await this.characterSelectService.ChangeCharacterByName(identifier, cancellationToken);
return result ?
Results.Ok() :
Results.StatusCode(StatusCodes.Status500InternalServerError);
}
}
@@ -0,0 +1,28 @@
using Daybreak.API.Services;
using Daybreak.Shared.Models.Api;
using Microsoft.AspNetCore.Mvc;
using Net.Sdk.Web;
namespace Daybreak.API.Controllers.Rest;
[GenerateController("api/v1/rest/inventory")]
[Tags("Inventory")]
public sealed class InventoryController(
InventoryService inventoryService)
{
private readonly InventoryService inventoryService = inventoryService;
[GenerateGet]
[EndpointName("GetInventory")]
[EndpointSummary("Get Inventory information")]
[EndpointDescription("Get the inventory of the current player. Returns a serialized InventoryInformation object")]
[ProducesResponseType<InventoryInformation>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetInventory(CancellationToken cancellationToken)
{
var inventoryInformation = await this.inventoryService.GetInventoryInformation(cancellationToken);
return inventoryInformation is null ?
Results.StatusCode(StatusCodes.Status503ServiceUnavailable) :
Results.Ok(inventoryInformation);
}
}
@@ -0,0 +1,30 @@
using System.Core.Extensions;
using Daybreak.API.Services;
using Daybreak.Shared.Models.Api;
using Microsoft.AspNetCore.Mvc;
using Net.Sdk.Web;
namespace Daybreak.API.Controllers.Rest;
[GenerateController("api/v1/rest/login")]
[Tags("Login")]
public sealed class LoginController(
LoginService loginService)
{
private readonly LoginService loginService = loginService.ThrowIfNull();
[GenerateGet]
[EndpointName("GetLoginInfo")]
[EndpointSummary("Get Login Information")]
[EndpointDescription("Get the current login information. Returns a serialized LoginInfo object")]
[ProducesResponseType<LoginInfo>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetLoginInfo(
CancellationToken cancellationToken)
{
var loginInfo = await this.loginService.GetLoginInformation(cancellationToken);
return loginInfo is null ?
Results.StatusCode(StatusCodes.Status503ServiceUnavailable) :
Results.Ok(loginInfo);
}
}
@@ -0,0 +1,114 @@
using System.Core.Extensions;
using Daybreak.API.Services;
using Daybreak.Shared.Models.Api;
using Microsoft.AspNetCore.Mvc;
using Net.Sdk.Web;
namespace Daybreak.API.Controllers.Rest;
[GenerateController("api/v1/rest/main-player")]
[Tags("Main Player")]
public sealed class MainPlayerController(
MainPlayerService mainPlayerService,
ILogger<MainPlayerController> logger)
{
private readonly MainPlayerService mainPlayerService = mainPlayerService.ThrowIfNull();
private readonly ILogger<MainPlayerController> logger = logger.ThrowIfNull();
[GenerateGet("state")]
[EndpointName("GetMainPlayerState")]
[EndpointSummary("Get the current MainPlayerState")]
[EndpointDescription("Get the current MainPlayerState. Returns a json serialized MainPlayerState object. You can use the websocket endpoint to subscribe to MainPlayerState updates on each game thread proc")]
[ProducesResponseType<MainPlayerState>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetMainPlayerState(CancellationToken cancellationToken)
{
var mainPlayerState = await this.mainPlayerService.GetMainPlayerState(cancellationToken);
return mainPlayerState is not null ? Results.Ok(mainPlayerState) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GenerateGet("quest-log")]
[EndpointName("GetQuestLog")]
[EndpointSummary("Get the current Quest Log")]
[EndpointDescription("Get the current Quest Log. Returns a json serialized QuestLogInformation object")]
[ProducesResponseType<QuestLogInformation>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetQuestLog(CancellationToken cancellationToken)
{
var questLog = await this.mainPlayerService.GetQuestLog(cancellationToken);
return questLog is not null ? Results.Ok(questLog) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GenerateGet("info")]
[EndpointName("GetMainPlayerInformation")]
[EndpointSummary("Get the current MainPlayerInformation")]
[EndpointDescription("Get the current MainPlayerInformation. Returns a json serialized MainPlayerInformation object")]
[ProducesResponseType<MainPlayerInformation>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetMainPlayerInformation(CancellationToken cancellationToken)
{
var mainPlayerInformation = await this.mainPlayerService.GetMainPlayerInformation(cancellationToken);
return mainPlayerInformation is not null ? Results.Ok(mainPlayerInformation) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GenerateGet("build")]
[EndpointName("GetMainPlayerBuild")]
[EndpointSummary("Get the current build")]
[EndpointDescription("Get the current build. Returns a json serialized BuildEntry object")]
[ProducesResponseType<BuildEntry>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetMainPlayerBuild(CancellationToken cancellationToken)
{
var build = await this.mainPlayerService.GetCurrentBuild(cancellationToken);
return build is not null ? Results.Ok(build) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GeneratePost("build")]
[EndpointName("SetMainPlayerBuild")]
[EndpointSummary("Set the current build")]
[EndpointDescription("Set the current build. Returns 200 is the operation has succeeded")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> SetMainPlayerBuild([FromQuery(Name = "code")] string? code, CancellationToken cancellationToken)
{
var result = await this.mainPlayerService.SetCurrentBuild(code ?? string.Empty, cancellationToken);
return result ? Results.Ok() : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GenerateGet("instance-info")]
[EndpointName("GetMainPlayerInstanceInfo")]
[EndpointSummary("Get the current instance info")]
[EndpointDescription("Get the current instance info. Returns a json serialized MainPlayerInstanceInfo object")]
[ProducesResponseType<InstanceInfo>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetMainPlayerInstanceInfo(CancellationToken cancellationToken)
{
var instanceInfo = await this.mainPlayerService.GetMainPlayerInstance(cancellationToken);
return instanceInfo is not null ? Results.Ok(instanceInfo) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GenerateGet("title")]
[EndpointName("GetMainPlayerTitleInfo")]
[EndpointSummary("Get the current title info")]
[EndpointDescription("Get the current title info. Returns a json serialized TitleInfo object")]
[ProducesResponseType<TitleInfo>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetMainPlayerTitleInfo(CancellationToken cancellationToken)
{
var titleInfo = await this.mainPlayerService.GetTitleInfo(cancellationToken);
return titleInfo is not null ? Results.Ok(titleInfo) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GenerateGet("build-context")]
[EndpointName("GetMainPlayerBuildContext")]
[EndpointSummary("Get the current build context")]
[EndpointDescription("Get the current build context. Returns a json serialized MainPlayerBuildContext object")]
[ProducesResponseType<BuildEntry>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetMainPlayerBuildContext(CancellationToken cancellationToken)
{
var context = await this.mainPlayerService.GetMainPlayerBuildContext(cancellationToken);
return context is not null ? Results.Ok(context) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
}
@@ -0,0 +1,38 @@
using Daybreak.API.Services;
using Daybreak.Shared.Models.Api;
using Microsoft.AspNetCore.Mvc;
using Net.Sdk.Web;
namespace Daybreak.API.Controllers.Rest;
[GenerateController("api/v1/rest/party")]
[Tags("Party")]
public sealed class PartyController(PartyService partyService)
{
private readonly PartyService partyService = partyService;
[GenerateGet("loadout")]
[EndpointName("GetPartyLoadout")]
[EndpointSummary("Get the current party loadout")]
[EndpointDescription("Get the current party loadout. Returns a json serialized PartyLoadout object")]
[ProducesResponseType<PartyLoadout>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> GetPartyLoadout(
CancellationToken cancellationToken)
{
var partyLoadout = await this.partyService.GetPartyLoadout(cancellationToken);
return partyLoadout is not null ? Results.Ok(partyLoadout) : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
[GeneratePost("loadout")]
[EndpointName("SetPartyLoadout")]
[EndpointSummary("Set the current party loadout")]
[EndpointDescription("Set the current party loadout")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public async Task<IResult> SetPartyLoadout([FromBody] PartyLoadout partyLoadout, CancellationToken cancellationToken)
{
var result = await this.partyService.SetPartyLoadout(partyLoadout, cancellationToken);
return result ? Results.Ok() : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
}
}
+98
View File
@@ -0,0 +1,98 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>$(TargetFrameworkVersionWindows)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-x86</RuntimeIdentifier>
<StripSymbols>true</StripSymbols>
<InteropExports>true</InteropExports>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>
<PublishAot>true</PublishAot>
<SelfContained>true</SelfContained>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
<UseAppHost>false</UseAppHost>
<EnableIISSupport>false</EnableIISSupport>
<InvariantGlobalization>true</InvariantGlobalization>
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
<Platforms>x86</Platforms>
<!-- Prevent static web asset conflicts -->
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
<GenerateStaticWebAssetsManifest>false</GenerateStaticWebAssetsManifest>
<ResolveStaticWebAssetsInputsDependsOn>RemoveDuplicateStaticWebAssets;$(ResolveStaticWebAssetsInputsDependsOn)</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<!-- Remove duplicate static web assets coming from referenced projects -->
<Target Name="RemoveDuplicateStaticWebAssets" BeforeTargets="ResolveStaticWebAssetsInputs;GenerateStaticWebAssetsManifest">
<ItemGroup>
<!-- Remove all blazor webview static assets since this is a Web API project, not a Blazor Hybrid app -->
<StaticWebAsset Remove="@(StaticWebAsset)"
Condition="$([System.String]::new('%(RelativePath)').Contains('blazor.modules')) Or $([System.String]::new('%(RelativePath)').Contains('blazor.webview'))" />
</ItemGroup>
</Target>
<PropertyGroup>
<!--Ignore trim warnings. NativeAOT support is still experimental so it should be tested with a Publish build generated from Daybreak build-->
<NoWarn>IL2104</NoWarn>
<NoWarn>IL3053</NoWarn>
<!--MinHook.NET does not contain sources for .NET, only .Net Framework. This has no impact on the functionality of the project-->
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<Using Include="Daybreak.Generators" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Configuration\appsettings.Debug.json" />
<EmbeddedResource Include="Configuration\appsettings.json" />
<EmbeddedResource Include="Configuration\appsettings.Release.json" />
</ItemGroup>
<ItemGroup>
<None Include="..\Dependencies\GWCA\gwca.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>gwca.dll</Link>
</None>
<AdditionalFiles Include="..\Dependencies\GWCA\gwca.dll" />
</ItemGroup>
<ItemGroup>
<RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="MinHook.NET" />
<PackageReference Include="Net.Sdk.Web.Extensions" />
<PackageReference Include="Net.Sdk.Web.Extensions.SourceGenerators" />
<PackageReference Include="PeNet" />
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Extensions.Logging" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="Scalar.AspNetCore" />
<PackageReference Include="SystemExtensions.NetStandard.Generators" PrivateAssets="all" />
<PackageReference Include="ZLinq" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Daybreak.Shared\Daybreak.Shared.csproj" />
<ProjectReference Include="..\Daybreak.Generators\Daybreak.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
SkipGetTargetFrameworkProperties="true"
UndefineProperties="RuntimeIdentifier;PublishAot;SelfContained;TargetFramework" />
</ItemGroup>
</Project>
+258
View File
@@ -0,0 +1,258 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Extensions.Core;
using System.Logging;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.InteropServices;
using Daybreak.API.Configuration;
using Daybreak.API.Extensions;
using Daybreak.API.Health;
using Daybreak.API.Hosting;
using Daybreak.API.Interop;
using Daybreak.API.Logging;
using Daybreak.API.Serialization;
using Daybreak.API.Services;
using Daybreak.API.Swagger;
using Daybreak.API.WebSockets;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Net.Sdk.Web;
namespace Daybreak.API;
public class EntryPoint
{
private const int StartPort = 5080;
private const string GwcaDllName = "gwca.dll";
private static readonly TimeSpan InitializationTimeout = TimeSpan.FromSeconds(5);
private static readonly CancellationTokenSource CancellationTokenSource = new();
private static readonly object GwcaResolverLock = new();
private static IntPtr gwcaHandle;
private static bool gwcaResolverRegistered;
[UnmanagedCallersOnly(EntryPoint = "ThreadInit"), STAThread]
[RequiresUnreferencedCode("The handler uses a static method that gets referenced, so there's no unreferenced code to worry about")]
[RequiresDynamicCode("The handler uses a static method, so there's no dynamic code to worry about")]
[SuppressMessage("Trimming", "IL2123:The use of 'RequiresUnreferencedCodeAttribute' on entry points is disallowed since the method will be called from outside the visible app.", Justification = "<Pending>")]
[SuppressMessage("AOT", "IL3057:The use of 'RequiresDynamicCodeAttribute' on entry points is disallowed since the method will be called from outside the visible app.", Justification = "<Pending>")]
public static int ThreadInit(IntPtr _, int __)
{
Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", null, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("ASPNETCORE_PREVENTHOSTINGSTARTUP", "true", EnvironmentVariableTarget.Process);
#if DEBUG
ConsoleExtensions.AllocateAnsiConsole();
#endif
var port = FindAvailablePort(StartPort);
var app = CreateApplication(port);
var runTask = Task.Run(() => StartServer(app), CancellationTokenSource.Token);
var scopedLogger = app.Services.GetRequiredService<ILogger<EntryPoint>>().CreateScopedLogger();
var healthCheck = app.Services.GetRequiredService<HealthCheckService>();
var sw = Stopwatch.StartNew();
var healthy = false;
while (sw.Elapsed < InitializationTimeout)
{
var status = Task.Run(() => healthCheck.CheckHealthAsync()).Result;
scopedLogger.LogWarning("HealthCheck status: {status} in {duration}. Report: {report}", status.Status, status.TotalDuration, string.Join("\n", status.Entries.Select(e => $"{e.Key}: {e.Value.Status}")));
if (status.Status is HealthStatus.Healthy)
{
healthy = true;
break;
}
else
{
Thread.Sleep(100);
}
}
if (healthy)
{
scopedLogger.LogInformation("Daybreak API is healthy on port {port}. Initialization succeeded in {duration}", port, sw.Elapsed);
return port;
}
else
{
scopedLogger.LogError("Daybreak API failed to initialize in {duration}", sw.Elapsed);
CancellationTokenSource.Cancel();
return -1;
}
}
[RequiresUnreferencedCode("The handler uses a static method that gets referenced, so there's no unreferenced code to worry about")]
[RequiresDynamicCode("The handler uses a static method, so there's no dynamic code to worry about")]
private static WebApplication CreateApplication(int port)
{
var builder = WebApplication.CreateBuilder()
.WithConfiguration()
.WithHosting(port)
.WithSwagger()
.WithSerializationContext()
.WithLogging()
.WithDaybreakServices()
.WithWebSocketRoutes()
.WithRoutes()
.WithHealthChecks();
// Add CORS policy - allow localhost and trusted community sites
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.SetIsOriginAllowed(origin =>
{
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri))
{
// Always allow localhost
if (uri.Host is "localhost" or "127.0.0.1")
{
return true;
}
// Allow known Guild Wars community sites
return uri.Host.EndsWith("guildwars.app", StringComparison.OrdinalIgnoreCase)
|| uri.Host.EndsWith("gwmarket.net", StringComparison.OrdinalIgnoreCase);
}
return false;
})
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
var app = builder.Build();
app.UseCors();
app.UseWebSockets(new WebSocketOptions { KeepAliveInterval = TimeSpan.FromSeconds(30) });
return app
.UseHealthChecks()
.UseLogging()
.UseRoutes()
.UseWebSocketRoutes()
.UseSwaggerWithUI();
}
private static async Task StartServer(WebApplication app)
{
var scopedLogger = app.Services.GetRequiredService<ILogger<EntryPoint>>().CreateScopedLogger();
try
{
scopedLogger.LogDebug("Initializing GWCA");
PreloadNativeDependencies(scopedLogger);
var result = GWCA.GW.Initialize();
scopedLogger.LogDebug($"GWCA.GW.Initialize() returned: {result}");
await app.RunAsync();
}
catch (Exception ex)
{
scopedLogger.LogCritical(ex, $"Encountered fatal error while starting API server {ex}");
throw;
}
}
private static int FindAvailablePort(int startPort)
{
var port = startPort;
while (!IsPortAvailable(port))
{
port++;
if (port > 65535 || port < 0)
{
return -1;
}
}
return port;
}
private static bool IsPortAvailable(int port)
{
var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
var tcpListeners = ipProperties.GetActiveTcpListeners();
if (tcpListeners.Any(endpoint => endpoint.Port == port))
{
return false;
}
var tcpConnections = ipProperties.GetActiveTcpConnections();
if (tcpConnections.Any(conn => conn.LocalEndPoint.Port == port))
{
return false;
}
return true;
}
private static void PreloadNativeDependencies(ScopedLogger<EntryPoint> logger)
{
// First, check if gwca.dll is already loaded in the process (e.g. by GWToolbox).
// If so, reuse it via a ref-count bump on the existing path rather than loading
// a second copy from the Daybreak.API directory. Loading a second instance of
// gwca.dll would result in duplicate native modules in the process, which can
// lead to inconsistent state across mods that depend on it.
string? daybreakApiDir = default;
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
if (module.ModuleName?.Equals("gwca.dll", StringComparison.OrdinalIgnoreCase) is true)
{
var handle = NativeLibrary.Load(module.FileName);
RegisterGwcaResolver(handle, module.FileName, logger);
logger.LogDebug($"Reused already-loaded gwca.dll from {module.FileName}");
return;
}
if (daybreakApiDir is null &&
module.ModuleName?.Contains("Daybreak.API", StringComparison.OrdinalIgnoreCase) is true)
{
daybreakApiDir = Path.GetDirectoryName(module.FileName);
}
}
if (daybreakApiDir is null)
{
logger.LogError("Could not locate Daybreak.API module directory to preload gwca.dll");
return;
}
var gwcaPath = Path.Combine(daybreakApiDir, "gwca.dll");
if (File.Exists(gwcaPath))
{
var handle = NativeLibrary.Load(gwcaPath);
RegisterGwcaResolver(handle, gwcaPath, logger);
logger.LogDebug($"Preloaded gwca.dll from {gwcaPath}");
}
else
{
logger.LogError($"gwca.dll not found at {gwcaPath}");
}
}
private static void RegisterGwcaResolver(IntPtr handle, string path, ScopedLogger<EntryPoint> logger)
{
lock (GwcaResolverLock)
{
gwcaHandle = handle;
if (gwcaResolverRegistered)
{
logger.LogDebug($"Updated gwca.dll resolver handle for {path}");
return;
}
NativeLibrary.SetDllImportResolver(typeof(GWCA).Assembly, ResolveNativeLibrary);
gwcaResolverRegistered = true;
logger.LogDebug($"Registered gwca.dll import resolver for {path}");
}
}
private static IntPtr ResolveNativeLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (string.Equals(libraryName, GwcaDllName, StringComparison.OrdinalIgnoreCase))
{
return gwcaHandle;
}
return IntPtr.Zero;
}
}
@@ -0,0 +1,18 @@
using Daybreak.API.Interop.GuildWars;
using Daybreak.Shared.Models.Api;
namespace Daybreak.API.Extensions;
public static class AttributeContextExtensions
{
public static IEnumerable<AttributeEntry> GetAttributeEntryList(this AttributeStructArray54 attributes)
{
foreach (var attribute in attributes)
{
if (attribute.LevelBase > 0 && attribute.Level >= attribute.LevelBase)
{
yield return new AttributeEntry((uint)attribute.Id, attribute.LevelBase, attribute.Level);
}
}
}
}
@@ -0,0 +1,36 @@
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Extensions;
public static class CharacterInformationExtensions
{
public unsafe static Profession GetPrimaryProfession(this CharacterInformation characterInformation)
{
return (Profession)((characterInformation.Props[2] >> 20) & 0xF);
}
public unsafe static Profession GetSecondaryProfession(this CharacterInformation characterInformation)
{
return (Profession)((characterInformation.Props[7] >> 10) & 0xF);
}
public unsafe static uint GetLevel(this CharacterInformation characterInformation)
{
return (characterInformation.Props[7] >> 4) & 0x3F;
}
public unsafe static Campaign GetCampaign(this CharacterInformation characterInformation)
{
return (Campaign)(characterInformation.Props[7] & 0xF);
}
public unsafe static MapID GetMapId(this CharacterInformation characterInformation)
{
return (MapID)((characterInformation.Props[0] >> 16) & 0xFFFF);
}
public unsafe static bool IsPvP(this CharacterInformation characterInformation)
{
return ((characterInformation.Props[7] >> 9) & 0x1) == 0x1;
}
}
@@ -0,0 +1,19 @@
namespace Daybreak.API.Extensions;
public static class ConsoleExtensions
{
public static void AllocateAnsiConsole()
{
NativeMethods.AllocConsole();
var handle = NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE);
if (!NativeMethods.GetConsoleMode(handle, out var mode))
{
Console.WriteLine("Failed to get console mode");
}
if (!NativeMethods.SetConsoleMode(handle, mode | NativeMethods.ENABLE_VIRTUAL_TERMINAL_PROCESSING | NativeMethods.ENABLE_PROCESSED_OUTPUT))
{
Console.WriteLine("Failed to enable virtual terminal processing");
}
}
}
@@ -0,0 +1,103 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using System.Diagnostics.CodeAnalysis;
namespace Daybreak.API.Extensions;
public static unsafe class GameContextExtensions
{
public static bool TryGetPlayerId(
this WrappedPointer<GameContext> gameContext,
[NotNullWhen(true)] out uint? playerId)
{
playerId = 0;
if (gameContext.IsNull ||
gameContext.Pointer->World is null ||
gameContext.Pointer->World->PlayerControlledChar is null)
{
return false;
}
playerId = gameContext.Pointer->World->PlayerControlledChar->AgentId;
return true;
}
public static bool TryGetBuildContext(
this WrappedPointer<GameContext> gameContext,
[NotNullWhen(true)] out GuildWarsArray<SkillbarData>? skillbars,
[NotNullWhen(true)] out GuildWarsArray<PartyAttribute>? attributes,
[NotNullWhen(true)] out GuildWarsArray<ProfessionState>? professions,
[NotNullWhen(true)] out GuildWarsArray<uint>? unlockedSkills)
{
skillbars = default;
attributes = default;
professions = default;
unlockedSkills = default;
if (gameContext.IsNull ||
gameContext.Pointer->World is null)
{
return false;
}
skillbars = gameContext.Pointer->World->Skillbar.Value;
attributes = gameContext.Pointer->World->Attributes.Value;
professions = gameContext.Pointer->World->PartyProfessionStates;
unlockedSkills = gameContext.Pointer->World->UnlockedCharacterSkills;
return true;
}
public static bool TryGetPlayerParty(
this WrappedPointer<GameContext> gameContext,
[NotNullWhen(true)] out uint? partyId,
[NotNullWhen(true)] out GuildWarsArray<PlayerPartyMember>? players,
[NotNullWhen(true)] out GuildWarsArray<HeroPartyMember>? heroes,
[NotNullWhen(true)] out GuildWarsArray<HenchmanPartyMember>? henchmen)
{
partyId = default;
players = default;
heroes = default;
henchmen = default;
if (gameContext.IsNull ||
gameContext.Pointer->Party is 0)
{
return false;
}
var partyInfo = GWCA.GW.PartyMgr.GetPartyInfo(0);
partyId = partyInfo->PartyId;
players = partyInfo->Players;
heroes = partyInfo->Heroes;
henchmen = partyInfo->Henchmen;
return true;
}
public static bool TryGetHeroFlags(
this WrappedPointer<GameContext> gameContext,
[NotNullWhen(true)]out GuildWarsArray<HeroFlag>? heroFlags)
{
heroFlags = default;
if (gameContext.IsNull ||
gameContext.Pointer->World is null)
{
return false;
}
heroFlags = gameContext.Pointer->World->HeroFlags.Value;
return true;
}
public static bool TryGetAccountContext(
this WrappedPointer<GameContext> gameContext,
out WrappedPointer<AccountContext> accountContext)
{
accountContext = default;
if (gameContext.IsNull ||
gameContext.Pointer->Account is null)
{
return false;
}
accountContext = gameContext.Pointer->Account;
return true;
}
}
@@ -0,0 +1,16 @@
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Extensions;
public static class TitleExtensions
{
public static bool IsPercentageBased(this Title title)
{
return (title.Props & 1) != 0;
}
public static bool HasTiers(this Title title)
{
return (title.Props & 3) == 2;
}
}
@@ -0,0 +1,10 @@
namespace Daybreak.API.Health;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithHealthChecks(this WebApplicationBuilder builder)
{
builder.Services.AddHealthChecks();
return builder;
}
}
@@ -0,0 +1,48 @@
using Daybreak.API.Models;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using ZLinq;
namespace Daybreak.API.Health;
public static class WebApplicationExtensions
{
[RequiresUnreferencedCode("The handler uses a static method that gets referenced, so there's no unreferenced code to worry about")]
[RequiresDynamicCode("The handler uses a static method, so there's no dynamic code to worry about")]
public static WebApplication UseHealthChecks(this WebApplication app)
{
app.MapGet("/api/v1/rest/health", static async (HealthCheckService healthCheckService, CancellationToken cancellationToken) =>
{
var report = await healthCheckService.CheckHealthAsync(cancellationToken);
var response = new HealthCheckResponse
{
ProcessId = Environment.ProcessId,
Status = report.Status,
TotalDuration = report.TotalDuration,
Entries = report.Entries.ToDictionary(
e => e.Key,
e => new HealthCheckEntryResponse
{
Status = e.Value.Status,
Description = e.Value.Description ?? string.Empty,
Tags = [.. e.Value.Tags],
Data = e.Value.Data
.Where(kvp => kvp.Value is JsonElement je)
.ToDictionary(kvp => kvp.Key,
kvp => (JsonElement)kvp.Value)
})
};
return Results.Json(response);
})
.WithName("Health")
.WithSummary("Service health")
.WithDescription($"Current health status of the service. Returns a serialized object of type {nameof(HealthCheckResponse)}")
.WithTags("Service")
.Produces<HealthCheckResponse>(StatusCodes.Status200OK)
.WithOpenApi();
return app;
}
}
@@ -0,0 +1,11 @@
namespace Daybreak.API.Hosting;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithHosting(this WebApplicationBuilder builder, int port)
{
builder.WebHost.ConfigureKestrel(o => o.ListenAnyIP(port));
return builder;
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma warning disable CS0436 // Type conflicts with imported type
using System.Extensions;
[assembly: GenerateFixedArray<uint>(Size = 4)]
[assembly: GenerateFixedArray<char>(Size = 0x14)]
[assembly: GenerateFixedArray<char>(Size = 0x40)]
[assembly: GenerateFixedArray<byte>(Size = 0x18)]
[assembly: GenerateFixedArray<char>(Size = 256)]
[assembly: GenerateFixedArray<char>(Size = 32)]
[assembly: GenerateFixedArray<char>(Size = 5)]
[assembly: GenerateFixedArray<char>(Size = 8)]
[assembly: GenerateFixedArray<byte>(Size = 56)]
[assembly: GenerateFixedArray<uint>(Size = 17)]
[assembly: GenerateFixedArray<uint>(Size = 12)]
[assembly: GenerateFixedArray<uint>(Size = 13)]
[assembly: GenerateFixedArray<uint>(Size = 8)]
#pragma warning restore CS0436 // Type conflicts with imported type
namespace Daybreak.API.Interop;
public static class FixedArrays
{
}
+34
View File
@@ -0,0 +1,34 @@
using System.Runtime.InteropServices;
namespace Daybreak.API.Interop;
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x1AC)]
[GWCAEquivalent("Frame")]
public readonly struct Frame
{
[FieldOffset(0x0008)]
public readonly uint FrameLayout;
[FieldOffset(0x0018)]
public readonly uint VisibilityFlags;
[FieldOffset(0x0020)]
public readonly uint Type;
[FieldOffset(0x0024)]
public readonly uint TemplateType;
[FieldOffset(0x00b8)]
public readonly uint ChildOffsetId;
[FieldOffset(0x00bc)]
public readonly uint FrameId;
[FieldOffset(0x018C)]
public readonly uint FrameState;
public bool IsCreated => (this.FrameState & 0x4) != 0;
public bool IsHidden => (this.FrameState & 0x200) != 0;
public bool IsVisible => !this.IsHidden;
public bool IsDIsabled => (this.FrameState & 0x10) != 0;
}
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace Daybreak.API.Interop;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[GWCAEquivalent("HWND__")]
public readonly struct GWHwnd
{
public readonly nint Handle;
}
@@ -0,0 +1,90 @@
using System.Collections;
using System.Runtime.InteropServices;
namespace Daybreak.API.Interop.GuildWars;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[GWCAEquivalent("Array")]
public readonly unsafe struct GuildWarsArray<T> : IEnumerable<T>
where T : unmanaged
{
public readonly T* Buffer;
public readonly uint Capacity;
public readonly uint Size;
public readonly uint Param;
/// <summary>
/// Upper bound on a believable element count. Guild Wars' largest arrays hold at most a
/// few thousand entries, so anything beyond this is uninitialised or misresolved memory
/// rather than a real array.
/// </summary>
private const uint MaxPlausibleCapacity = 0x10000;
/// <summary>
/// Mirrors <c>GW::BaseArray::valid()</c> - the buffer must be null or aligned and the size
/// must fit within the capacity - plus a bound on the capacity itself. Guild Wars leaves
/// these fields transiently inconsistent while it (re)allocates an array, and a
/// misresolved GWCA scan can point this struct at arbitrary bytes that still satisfy
/// <c>size &lt;= capacity</c>.
/// </summary>
public bool IsValid =>
(this.Buffer is null || ((nuint)this.Buffer & 0x3) == 0) &&
this.Size <= this.Capacity &&
this.Capacity <= MaxPlausibleCapacity;
/// <summary>
/// Number of elements that can safely be read. Zero whenever the array is not backed by
/// a buffer, matching <c>GW::BaseArray::get()</c>, which returns null instead of
/// dereferencing. Reading past this is an access violation, and because NativeAOT cannot
/// throw <c>AccessViolationException</c> it fail-fasts and takes Guild Wars down with it.
/// </summary>
public uint Count => this.Buffer is not null && this.IsValid ? this.Size : 0;
public T this[int index]
{
get
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, this.Count);
return this.Buffer[index];
}
}
public Enumerator GetEnumerator() => new(this.Buffer, this.Count);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => this.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
public unsafe struct Enumerator : IEnumerator, IEnumerator<T>
{
private readonly T* buffer;
private readonly uint size;
private int index;
internal Enumerator(T* buffer, uint size)
{
this.buffer = buffer;
this.size = size;
this.index = -1;
}
public bool MoveNext()
{
int next = this.index + 1;
if (next >= this.size)
{
return false;
}
this.index = next;
return true;
}
public T Current => this.buffer[this.index];
object IEnumerator.Current => this.Current;
public void Reset() => this.index = -1;
public readonly void Dispose() { }
}
}
+108
View File
@@ -0,0 +1,108 @@
using System.Globalization;
namespace Daybreak.API.Interop.GuildWars;
public readonly struct Uuid(
uint timeLow,
ushort timeMid,
ushort timeHighAndVersion,
byte clockSqHi,
byte clockSqLow,
byte node0,
byte node1,
byte node2,
byte node3,
byte node4,
byte node5) : IEquatable<Uuid>
{
public const uint NodeLength = 6;
public readonly uint TimeLow = timeLow;
public readonly ushort TimeMid = timeMid;
public readonly ushort TimeHighAndVersion = timeHighAndVersion;
public readonly byte ClockSqHiAndRes = clockSqHi;
public readonly byte ClockSqLow = clockSqLow;
public readonly byte Node0 = node0;
public readonly byte Node1 = node1;
public readonly byte Node2 = node2;
public readonly byte Node3 = node3;
public readonly byte Node4 = node4;
public readonly byte Node5 = node5;
public bool Equals(Uuid other)
{
return this.TimeLow == other.TimeLow &&
this.TimeMid == other.TimeMid &&
this.TimeHighAndVersion == other.TimeHighAndVersion &&
this.ClockSqHiAndRes == other.ClockSqHiAndRes &&
this.ClockSqLow == other.ClockSqLow &&
this.Node0 == other.Node0 &&
this.Node1 == other.Node1 &&
this.Node2 == other.Node2 &&
this.Node3 == other.Node3 &&
this.Node4 == other.Node4 &&
this.Node5 == other.Node5;
}
public override bool Equals(object? obj)
{
return obj is Uuid uuid && this.Equals(uuid);
}
public static bool operator ==(Uuid left, Uuid right)
{
return left.Equals(right);
}
public static bool operator !=(Uuid left, Uuid right)
{
return !(left == right);
}
public static bool TryParse(string uuidString, out Uuid uuid)
{
if (uuidString.Length != 36)
{
uuid = default;
return false;
}
var parts = uuidString.Split('-');
if (parts.Length != 5)
{
uuid = default;
return false;
}
if (!uint.TryParse(parts[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var timeLow) ||
!ushort.TryParse(parts[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var timeMid) ||
!ushort.TryParse(parts[2], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var timeHighAndVersion) ||
!byte.TryParse(parts[3].AsSpan(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var clockSqHi) ||
!byte.TryParse(parts[3].AsSpan(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var clockSqLow) ||
!byte.TryParse(parts[4].AsSpan(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var node0) ||
!byte.TryParse(parts[4].AsSpan(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var node1) ||
!byte.TryParse(parts[4].AsSpan(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var node2) ||
!byte.TryParse(parts[4].AsSpan(6, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var node3) ||
!byte.TryParse(parts[4].AsSpan(8, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var node4) ||
!byte.TryParse(parts[4].AsSpan(10, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var node5))
{
uuid = default;
return false;
}
uuid = new Uuid(timeLow, timeMid, timeHighAndVersion, clockSqHi, clockSqLow, node0, node1, node2, node3, node4, node5);
return true;
}
public override int GetHashCode()
{
return HashCode.Combine<uint, ushort, ushort, byte, byte, byte[]>(this.TimeLow, this.TimeMid, this.TimeHighAndVersion, this.ClockSqHiAndRes, this.ClockSqLow, [this.Node0, this.Node1, this.Node2, this.Node3, this.Node4, this.Node5]);
}
public override string ToString()
{
return $"{this.TimeLow:X8}-{this.TimeMid:X4}-{this.TimeHighAndVersion:X4}-{this.ClockSqHiAndRes:X2}{this.ClockSqLow:X2}-{this.Node0:X2}{this.Node1:X2}{this.Node2:X2}{this.Node3:X2}{this.Node4:X2}{this.Node5:X2}";
}
public readonly static Uuid Zero = new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
@@ -0,0 +1,49 @@
namespace Daybreak.API.Interop.GuildWars;
public readonly unsafe struct WrappedPointer<T>(T* pointer)
where T : unmanaged
{
public readonly T* Pointer = pointer;
public bool IsNull => this.Pointer is null;
/// <summary>
/// True when the pointer is non-null and plausibly dereferenceable.
/// </summary>
/// <remarks>
/// Guild Wars structures are 4-byte aligned on x86, so a misaligned address is never a
/// real structure. When a GWCA pattern scan fails to resolve, it can hand back an
/// address inside Gw.exe's code section (for example 0x0048CA72); reading through that
/// yields nonsense field values and eventually an access violation. NativeAOT cannot
/// throw <c>AccessViolationException</c>, so it fail-fasts and terminates Guild Wars.
/// </remarks>
public bool IsValid => this.Pointer is not null && ((nuint)this.Pointer & 0x3) == 0;
public static implicit operator WrappedPointer<T>(T* pointer) => new(pointer);
public static implicit operator T*(WrappedPointer<T> wrappedPointer) => wrappedPointer.Pointer;
public static bool operator ==(T* left, WrappedPointer<T> right) => left == right.Pointer;
public static bool operator !=(T* left, WrappedPointer<T> right) => left != right.Pointer;
public static bool operator ==(WrappedPointer<T> left, T* right) => left.Pointer == right;
public static bool operator !=(WrappedPointer<T> left, T* right) => left.Pointer != right;
public static bool operator ==(WrappedPointer<T> left, WrappedPointer<T> right) => left.Pointer == right.Pointer;
public static bool operator !=(WrappedPointer<T> left, WrappedPointer<T> right) => left.Pointer != right.Pointer;
public override int GetHashCode() => this.Pointer is null ? 0 : this.Pointer->GetHashCode();
public override bool Equals(object? obj)
{
if (obj is WrappedPointer<T> wrappedPointer)
{
return this.Pointer == wrappedPointer.Pointer;
}
return false;
}
}
+9
View File
@@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Daybreak.API.Interop;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[GWCAEquivalent("HookEntry")]
public readonly struct HookEntry
{
}
+19
View File
@@ -0,0 +1,19 @@
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Interop;
public readonly struct ManagedFrame(
WrappedPointer<Frame> frame,
Action closeFrame) : IDisposable
{
private readonly Action closeFrame = closeFrame;
public static ManagedFrame Null => new(null, () => { });
public readonly WrappedPointer<Frame> Frame = frame;
public void Dispose()
{
this.closeFrame();
}
}
@@ -0,0 +1,31 @@
using MemoryPack;
using System.Buffers;
namespace Daybreak.API.Interop;
public static class MemoryPackExtensions
{
/// <summary>Serialize <paramref name="value"/> into <paramref name="destination"/>.</summary>
/// <returns>Number of bytes written.</returns>
public static int SerializeToSpan<T>(T value, Span<byte> destination)
{
var dummy = DummyBufferWriter.Instance;
using var state = MemoryPackWriterOptionalStatePool.Rent(null);
var writer = new MemoryPackWriter<DummyBufferWriter>(ref dummy, destination, state);
writer.WriteValue(value);
writer.Flush();
return writer.WrittenCount;
}
private sealed class DummyBufferWriter : IBufferWriter<byte>
{
public static readonly DummyBufferWriter Instance = new();
public void Advance(int count) { /* ignore we never overflow destination */ }
public Memory<byte> GetMemory(int sizeHint = 0) => throw new NotSupportedException();
public Span<byte> GetSpan(int sizeHint = 0) => throw new NotSupportedException();
}
}
+22
View File
@@ -0,0 +1,22 @@
using System.Runtime.InteropServices;
namespace Daybreak.API.Interop;
public sealed class UnmanagedStruct<T> : IDisposable
where T : struct
{
public nint Address { get; private set; }
public UnmanagedStruct(T value)
{
this.Address = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
Marshal.StructureToPtr(value, this.Address, false);
}
public void Dispose()
{
Marshal.DestroyStructure<T>(this.Address);
Marshal.FreeHGlobal(this.Address);
this.Address = 0x0;
}
}
@@ -0,0 +1,29 @@
using Microsoft.CorrelationVector;
using Microsoft.Extensions.Options;
using Net.Sdk.Web;
using Net.Sdk.Web.Options;
using Serilog.Context;
namespace Daybreak.API.Logging;
public sealed class LoggingEnrichmentMiddleware(IOptions<CorrelationVectorOptions> options)
: IMiddleware
{
private readonly CorrelationVectorOptions options = options.Value;
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (!context.Request.Headers.TryGetValue(this.options.Header, out var value) ||
value.FirstOrDefault() is not string cvString ||
CorrelationVector.Parse(cvString) is not CorrelationVector cv)
{
cv = new CorrelationVector();
}
cv = CorrelationVector.Extend(cv.ToString());
context.SetCorrelationVector(cv);
LogContext.PushProperty("CorrelationVector", cv);
HeaderDictionaryExtensions.Append(context.Response.Headers, this.options.Header, context.GetCorrelationVector().ToString());
return next(context);
}
}
@@ -0,0 +1,36 @@
using Serilog.Settings.Configuration;
using Serilog.Sinks.SystemConsole.Themes;
using Serilog;
using Net.Sdk.Web.Options;
namespace Daybreak.API.Logging;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithLogging(this WebApplicationBuilder builder)
{
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration, new ConfigurationReaderOptions
{
SectionName = "Logging"
})
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss}] {Level:u4}: [{SourceContext}] [{CorrelationVector}] {NewLine}{Message:lj}{NewLine}{Exception}",
theme: AnsiConsoleTheme.Sixteen)
.WriteTo.File(
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss}] {Level:u4}: [{SourceContext}] [{CorrelationVector}] {NewLine}{Message:lj}{NewLine}{Exception}",
path: "Daybreak.API.log",
rollingInterval: RollingInterval.Day,
rollOnFileSizeLimit: true,
fileSizeLimitBytes: 50 * 1024 * 1024,
retainedFileCountLimit: 1,
shared: true)
.CreateLogger());
builder.Services.AddScoped<LoggingEnrichmentMiddleware>();
builder.Services.AddOptions<CorrelationVectorOptions>()
.Bind(builder.Configuration.GetSection("Correlation"));
return builder;
}
}
@@ -0,0 +1,10 @@
namespace Daybreak.API.Logging;
public static class WebApplicationExtensions
{
public static WebApplication UseLogging(this WebApplication app)
{
app.UseMiddleware<LoggingEnrichmentMiddleware>();
return app;
}
}
+23
View File
@@ -0,0 +1,23 @@
using System.Core.Extensions;
namespace Daybreak.API.Models;
public sealed class ByteConsumerEntry(
Guid id, TimeSpan freq, Action<ReadOnlySpan<byte>> handler)
{
private readonly Action<ReadOnlySpan<byte>> handler = handler.ThrowIfNull();
private DateTime lastConsume = DateTime.MinValue;
public Guid Id { get; } = id;
public TimeSpan Frequency { get; } = freq;
public void TryConsume(DateTime currentTime, ReadOnlySpan<byte> value)
{
if (currentTime - this.lastConsume >= this.Frequency)
{
this.handler(value);
this.lastConsume = currentTime;
}
}
}
@@ -0,0 +1,14 @@
using System.Core.Extensions;
namespace Daybreak.API.Models;
public sealed class CallbackRegistration(Guid uid, Action onDispose) : IDisposable
{
private readonly Guid uid = uid;
private readonly Action onDispose = onDispose.ThrowIfNull();
public void Dispose()
{
this.onDispose();
}
}
@@ -0,0 +1,9 @@
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Models;
public struct DecodeStringContext(WrappedPointer<char> encodedString, TaskCompletionSource<string?> taskCompletionSource)
{
public WrappedPointer<char> EncodedString = encodedString;
public TaskCompletionSource<string?> TaskCompletionSource = taskCompletionSource;
}
@@ -0,0 +1,14 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Daybreak.API.Models;
public sealed class HealthCheckEntryResponse
{
[JsonConverter(typeof(JsonStringEnumConverter<HealthStatus>))]
public required HealthStatus Status { get; set; }
public required string Description { get; set; }
public required Dictionary<string, JsonElement> Data { get; set; }
public required List<string> Tags { get; set; }
}
@@ -0,0 +1,13 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Text.Json.Serialization;
namespace Daybreak.API.Models;
public sealed class HealthCheckResponse
{
public required int ProcessId { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter<HealthStatus>))]
public required HealthStatus Status { get; set; }
public required TimeSpan TotalDuration { get; set; }
public required Dictionary<string, HealthCheckEntryResponse> Entries { get; set; }
}
+9
View File
@@ -0,0 +1,9 @@
namespace Daybreak.API.Models;
public interface IWorkItem
{
CancellationToken CancellationToken { get; }
void Execute();
void Cancel();
void Exception(Exception ex);
}
+80
View File
@@ -0,0 +1,80 @@
namespace Daybreak.API.Models;
public sealed class WorkItem(
Action action,
TaskCompletionSource tcs,
CancellationToken token) : IWorkItem
{
private readonly Action action = action;
private readonly TaskCompletionSource tcs = tcs;
public CancellationToken CancellationToken { get; } = token;
public void Execute()
{
if (this.CancellationToken.IsCancellationRequested)
{
this.tcs.TrySetCanceled(this.CancellationToken);
return;
}
try
{
this.action();
this.tcs.TrySetResult();
}
catch (Exception ex)
{
this.tcs.TrySetException(ex);
}
}
public void Cancel()
{
this.tcs.TrySetCanceled(this.CancellationToken);
}
public void Exception(Exception ex)
{
this.tcs.TrySetException(ex);
}
}
public sealed class WorkItem<T>(
Func<T> func,
TaskCompletionSource<T> tcs,
CancellationToken token) : IWorkItem
{
private readonly Func<T> func = func;
private readonly TaskCompletionSource<T> tcs = tcs;
public CancellationToken CancellationToken { get; } = token;
public void Execute()
{
if (this.CancellationToken.IsCancellationRequested)
{
this.tcs.TrySetCanceled(this.CancellationToken);
return;
}
try
{
this.tcs.TrySetResult(this.func());
}
catch (Exception ex)
{
this.tcs.TrySetException(ex);
}
}
public void Cancel()
{
this.tcs.TrySetCanceled(this.CancellationToken);
}
public void Exception(Exception ex)
{
this.tcs.TrySetException(ex);
}
}
+68
View File
@@ -0,0 +1,68 @@
using System.Runtime.InteropServices;
namespace Daybreak.API;
internal unsafe static partial class NativeMethods
{
public const int STD_OUTPUT_HANDLE = -11;
public const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
public const uint ENABLE_PROCESSED_OUTPUT = 0x0001;
public const int WM_KEYDOWN = 0x0100;
public const int WM_KEYUP = 0x0101;
public const int WM_CHAR = 0x0102;
public const int VK_LEFT = 0x25;
public const int VK_RIGHT = 0x27;
public const uint MEM_COMMIT = 0x1000;
public const uint MEM_RESERVE = 0x2000;
public const uint PAGE_EXECUTE_READWRITE = 0x40;
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool AllocConsole();
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint GetConsoleWindow();
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint GetStdHandle(int nStdHandle);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetConsoleMode(nint hConsoleHandle, out uint lpMode);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool SetConsoleMode(nint hConsoleHandle, uint dwMode);
[LibraryImport("user32.dll")]
public static partial nint SendMessageW(nint hWnd, int Msg, nint wParam, nuint lParam);
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint VirtualAlloc(
nint lpAddress, nuint dwSize,
uint flAllocationType, uint flProtect);
[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool VirtualProtect(
void* lpAddress, nuint dwSize,
uint flNewProtect, out uint lpflOldProtect);
// Window enumeration
public delegate bool EnumWindowsProc(nint hwnd, nint lParam);
[LibraryImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool EnumWindows(EnumWindowsProc lpEnumFunc, nint lParam);
[LibraryImport("user32.dll", SetLastError = true)]
public static partial uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);
[LibraryImport("user32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)]
public static partial int GetClassNameA(nint hWnd, byte* lpClassName, int nMaxCount);
[LibraryImport("kernel32.dll")]
public static partial uint GetCurrentProcessId();
}
+4
View File
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
// Required for LibraryImport source generator to work with blittable types like Vector2, Vector3
[assembly: DisableRuntimeMarshalling]
@@ -0,0 +1,12 @@
{
"profiles": {
"Daybreak.API": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:51249;http://localhost:51250"
}
}
}
@@ -0,0 +1,39 @@
using Daybreak.API.Models;
using Daybreak.Shared.Models.Api;
using Daybreak.Shared.Models.Guildwars;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Daybreak.API.Serialization;
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(byte[]))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(uint))]
[JsonSerializable(typeof(nuint))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(HealthCheckResponse))]
[JsonSerializable(typeof(HealthCheckEntryResponse))]
[JsonSerializable(typeof(MainPlayerState))]
[JsonSerializable(typeof(QuestLogInformation))]
[JsonSerializable(typeof(QuestInformation))]
[JsonSerializable(typeof(MainPlayerInformation))]
[JsonSerializable(typeof(CharacterSelectInformation))]
[JsonSerializable(typeof(CharacterSelectEntry))]
[JsonSerializable(typeof(BuildEntry))]
[JsonSerializable(typeof(List<BuildEntry>))]
[JsonSerializable(typeof(PartyLoadoutEntry))]
[JsonSerializable(typeof(List<PartyLoadout>))]
[JsonSerializable(typeof(InstanceInfo))]
[JsonSerializable(typeof(TitleInfo))]
[JsonSerializable(typeof(LoginInfo))]
[JsonSerializable(typeof(MainPlayerBuildContext))]
[JsonSerializable(typeof(InventoryInformation))]
[JsonSerializable(typeof(ItemProperty))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
public partial class ApiJsonSerializerContext : JsonSerializerContext
{
}
@@ -0,0 +1,14 @@
namespace Daybreak.API.Serialization;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithSerializationContext(this WebApplicationBuilder builder)
{
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, new ApiJsonSerializerContext());
});
return builder;
}
}
@@ -0,0 +1,496 @@
using Daybreak.API.Extensions;
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using Daybreak.API.Services.Interop;
using Daybreak.Shared.Models.Api;
using System.Core.Extensions;
using System.Extensions;
using System.Extensions.Core;
using System.Runtime.InteropServices;
using ZLinq;
namespace Daybreak.API.Services;
public sealed class CharacterSelectService(
InstanceContextService instanceContextService,
PlatformContextService platformContextService,
UIContextService uiContextService,
GameThreadService gameThreadService,
GameContextService gameContextService,
ILogger<CharacterSelectService> logger)
{
private readonly InstanceContextService instanceContextService = instanceContextService.ThrowIfNull();
private readonly PlatformContextService platformContextService = platformContextService.ThrowIfNull();
private readonly UIContextService uiContextService = uiContextService.ThrowIfNull();
private readonly GameThreadService gameThreadService = gameThreadService.ThrowIfNull();
private readonly GameContextService gameContextService = gameContextService.ThrowIfNull();
private readonly ILogger<CharacterSelectService> logger = logger.ThrowIfNull();
public Task<CharacterSelectInformation?> GetCharacterSelectInformation(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull ||
gameContext.Pointer->World is null ||
gameContext.Pointer->Character is null)
{
scopedLogger.LogError("Game context is not initialized");
return default;
}
var availableCharsContext = this.gameContextService.GetAvailableChars();
if (!availableCharsContext.IsValid ||
!availableCharsContext.Pointer->IsValid)
{
scopedLogger.LogError(
"Available characters context is not initialized (array@0x{Array:X8} buffer=0x{Buffer:X8} capacity={Capacity} size={Size})",
(nuint)availableCharsContext.Pointer,
availableCharsContext.IsValid ? (nuint)availableCharsContext.Pointer->Buffer : 0,
availableCharsContext.IsValid ? availableCharsContext.Pointer->Capacity : 0,
availableCharsContext.IsValid ? availableCharsContext.Pointer->Size : 0);
return default;
}
var currentUuid = (*(Uuid*)gameContext.Pointer->Character->PlayerUuid).ToString();
var availableChars = new List<CharacterSelectEntry>((int)availableCharsContext.Pointer->Count);
foreach (var charContext in *availableCharsContext.Pointer)
{
var name = new string(charContext.Name);
availableChars.Add(new CharacterSelectEntry(
(*(Uuid*)charContext.Uuid).ToString(),
name,
(uint)charContext.GetPrimaryProfession(),
(uint)charContext.GetSecondaryProfession(),
(uint)charContext.GetCampaign(),
(uint)charContext.GetMapId(),
(uint)charContext.GetLevel(),
charContext.IsPvP()));
}
// TODO: Reforged sometimes returns Zero UUID even when in-game, need to investigate why
if (currentUuid != Uuid.Zero.ToString())
{
var currentCharacter = availableChars.FirstOrDefault(c => c.Uuid == currentUuid);
return new CharacterSelectInformation(currentCharacter, availableChars);
}
else
{
var currentCharacter = availableChars.FirstOrDefault();
return new CharacterSelectInformation(currentCharacter, availableChars);
}
}
}, cancellationToken);
}
public async Task<bool> ChangeCharacterByName(string characterName, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (await this.ValidateState(cancellationToken) is false)
{
scopedLogger.LogError("Cannot change character while in loading state");
return false;
}
await this.TriggerLogOut(cancellationToken);
var selectResult = await this.WaitForCharSelectAndSelectCharacter(characterName, cancellationToken);
if (!selectResult)
{
scopedLogger.LogError("Failed to select character {name}", characterName);
return false;
}
return true;
}
public async Task<bool> ChangeCharacterByUuid(string uuid, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (await this.ValidateState(cancellationToken) is false)
{
scopedLogger.LogError("Cannot change character while in loading state");
return false;
}
var desiredCharName = await this.GetCharNameByUuid(uuid, cancellationToken);
if (desiredCharName is null)
{
scopedLogger.LogError("Failed to find character with UUID: {uuid}", uuid);
return false;
}
await this.TriggerLogOut(cancellationToken);
var selectResult = await this.WaitForCharSelectAndSelectCharacter(desiredCharName, cancellationToken);
if (!selectResult)
{
scopedLogger.LogError("Failed to select character {name} with uuid {uuid}", desiredCharName, uuid);
return false;
}
return true;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private unsafe struct CharSelectButtonParam
{
public char* Name;
public uint Play;
}
[StructLayout(LayoutKind.Explicit, Pack = 1)]
private readonly unsafe struct CharSelectorContext
{
[FieldOffset(0x0000)]
public readonly uint Vtable;
[FieldOffset(0x0004)]
public readonly uint FrameId;
[FieldOffset(0x0008)]
public readonly GuildWarsArray<WrappedPointer<CharSelectorChar>> Chars;
}
[StructLayout(LayoutKind.Explicit, Pack = 1)]
private readonly struct CharSelectorChar
{
[FieldOffset(0x0020)]
public readonly Array20Char Name;
}
private async Task<bool> SelectCharacterToPlay(string characterName, bool play, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return await this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (!this.IsCharSelectReady())
{
scopedLogger.LogError("Character select is not ready");
return false;
}
var selectorFrame = this.GetCharSelectorFrame();
if (selectorFrame is null)
{
scopedLogger.LogError("Character selector frame not found");
return false;
}
var ctx = this.uiContextService.GetFrameContext<CharSelectorContext>(selectorFrame);
if (ctx.IsNull ||
(nuint)ctx.Pointer <= 0xFFFF)
{
scopedLogger.LogError("Character selector context not found");
return false;
}
var panesFrame = this.uiContextService.GetChildFrame(selectorFrame, 0);
if (panesFrame.IsNull)
{
scopedLogger.LogError("Character panes frame not found");
return false;
}
// Get current selected index
uint selectedIdx = 0;
this.uiContextService.SendFrameUIMessage(panesFrame, UIMessage.kFrameMessage_0x4a, null, &selectedIdx);
// Find target character index
uint targetIdx = 0xFFFF;
// Count (not Size) so a not-yet-allocated roster yields zero iterations
// instead of indexing through a null buffer.
var charCount = ctx.Pointer->Chars.Count;
for (uint i = 0; i < charCount; i++)
{
var c = ctx.Pointer->Chars.Buffer[i];
if (c.IsNull)
{
continue;
}
var charNameSpan = c.Pointer->Name.AsSpan();
var nullIdx = charNameSpan.IndexOf('\0');
if (nullIdx <= 0)
{
continue;
}
var charName = new string(charNameSpan[..nullIdx]);
if (!charName.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
continue;
}
targetIdx = i;
break;
}
if (targetIdx >= charCount)
{
scopedLogger.LogError("Character {name} not found in character list", characterName);
return false;
}
var parentFrame = this.uiContextService.GetParentFrame(selectorFrame);
if (parentFrame.IsNull)
{
scopedLogger.LogError("Parent frame not found");
return false;
}
// Helper function to select a character at a specific index
bool SelectChar(uint idx)
{
var charToSelect = ctx.Pointer->Chars.Buffer[idx];
if (charToSelect.IsNull)
{
return false;
}
fixed (char* namePtr = charToSelect.Pointer->Name.AsSpan())
{
var buttonParam = new CharSelectButtonParam
{
Name = namePtr,
Play = 0
};
var action = new kMouseAction
{
FrameId = selectorFrame->FrameId,
ChildOffsetId = selectorFrame->ChildOffsetId,
CurrentState = GWCA.GW.UI.UIPacket.ActionState.MouseClick,
Wparam = (nint)(&buttonParam)
};
if (!this.uiContextService.SendFrameUIMessage(parentFrame, UIMessage.kMouseClick2, &action))
{
return false;
}
uint newSelectedIdx = 0;
this.uiContextService.SendFrameUIMessage(panesFrame, UIMessage.kFrameMessage_0x4a, null, &newSelectedIdx);
return newSelectedIdx == idx;
}
}
// Navigate to target character by selecting previous/next until we reach it.
// Empty roster slots are represented by null entries in the character array, so
// step over them instead of bailing out (otherwise navigation gets stuck at the
// slot right before a gap and never reaches characters beyond it).
while (targetIdx < selectedIdx)
{
if (selectedIdx == 0)
{
break;
}
var prevIdx = selectedIdx - 1;
while (prevIdx > 0 && ctx.Pointer->Chars.Buffer[prevIdx].IsNull)
{
prevIdx--;
}
if (ctx.Pointer->Chars.Buffer[prevIdx].IsNull)
{
break;
}
if (!SelectChar(prevIdx))
{
scopedLogger.LogError("Failed to select previous character");
return false;
}
selectedIdx = prevIdx;
}
while (targetIdx > selectedIdx)
{
if (selectedIdx >= charCount - 1)
{
break;
}
var nextIdx = selectedIdx + 1;
while (nextIdx < charCount && ctx.Pointer->Chars.Buffer[nextIdx].IsNull)
{
nextIdx++;
}
if (nextIdx >= charCount)
{
break;
}
if (!SelectChar(nextIdx))
{
scopedLogger.LogError("Failed to select next character");
return false;
}
selectedIdx = nextIdx;
}
var chosen = selectedIdx == targetIdx;
if (!chosen)
{
scopedLogger.LogError("Failed to select character {name}", characterName);
return false;
}
if (!play)
{
return true;
}
var playButton = this.uiContextService.GetFrameByLabel("Play");
if (playButton.IsNull)
{
scopedLogger.LogError("Play button not found");
return false;
}
return this.uiContextService.ButtonClick(playButton);
}
}, cancellationToken);
}
private async Task<bool> WaitForCharSelectAndSelectCharacter(string characterName, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
// Wait for character select to be ready
while (!cancellationToken.IsCancellationRequested)
{
try
{
if (await this.gameThreadService.QueueOnGameThread(this.IsCharSelectReady, cancellationToken))
{
break;
}
await Task.Delay(100, cancellationToken);
}
catch (Exception e)
{
scopedLogger.LogError(e, "Error while waiting for character select to be ready");
throw;
}
}
if (cancellationToken.IsCancellationRequested)
{
return false;
}
// NOTE: Mirrors GWToolbox's GW::LoginMgr::SelectCharacterToPlay, which navigates the
// character carousel purely by stepping prev/next over the selector context's character
// array. It deliberately does NOT touch the in-game CharSortOrder preference: the
// navigation relies on the selector context order matching the visible carousel order,
// and forcing a re-sort here desyncs the two (the click-by-name steps then land on the
// wrong index, so navigation only appears to work in one direction).
return await this.SelectCharacterToPlay(characterName, play: true, cancellationToken);
}
private async Task<string?> GetCharNameByUuid(string uuid, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return await this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull)
{
scopedLogger.LogError("Game context is not initialized");
return default;
}
var availableCharsContext = this.gameContextService.GetAvailableChars();
if (!availableCharsContext.IsValid ||
!availableCharsContext.Pointer->IsValid)
{
scopedLogger.LogError("Available characters context is not initialized");
return default;
}
foreach (var charContext in *availableCharsContext.Pointer)
{
if ((*(Uuid*)charContext.Uuid).ToString() != uuid)
{
continue;
}
var name = new string(charContext.Name);
return name;
}
return default;
}
}, cancellationToken);
}
private async Task TriggerLogOut(CancellationToken cancellationToken)
{
await this.gameThreadService.QueueOnGameThread(() =>
{
var logoutMessage = new kLogout { Unknown = 0, CharacterSelect = 1 };
unsafe
{
this.uiContextService.SendMessage(UIMessage.kLogout, (uint)&logoutMessage, 0);
}
}, cancellationToken);
}
private async Task<bool> ValidateState(CancellationToken cancellationToken)
{
return await this.gameThreadService.QueueOnGameThread(() =>
{
return this.instanceContextService.GetInstanceType() is not API.Interop.GuildWars.InstanceType.Loading;
}, cancellationToken);
}
private unsafe bool IsCharSelectReady()
{
// GW UI state must report char select (2). kCheckUIState fills the value passed via lParam.
uint uiState = 10;
this.uiContextService.SendMessage(UIMessage.kCheckUIState, 0, (nuint)(&uiState));
var selectorFrame = this.GetCharSelectorFrame();
if (uiState != 2 ||
selectorFrame is null ||
!selectorFrame->IsVisible)
{
return false;
}
// The frame context must be fully populated (a real pointer) and belong to this frame.
var ctx = this.uiContextService.GetFrameContext<CharSelectorContext>(selectorFrame);
if (ctx.IsNull ||
(nuint)ctx.Pointer <= 0xFFFF)
{
return false;
}
return ctx.Pointer->FrameId == selectorFrame->FrameId;
}
private unsafe Frame* GetCharSelectorFrame()
{
var selectorFrame = this.uiContextService.GetFrameByLabel("Selector");
if (selectorFrame.IsNull)
{
return null;
}
return selectorFrame.Pointer;
}
}
+83
View File
@@ -0,0 +1,83 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using Daybreak.API.Services.Interop;
using System.Core.Extensions;
using System.Extensions;
namespace Daybreak.API.Services;
public sealed class ChatService(
InstanceContextService instanceContextService,
GameThreadService gameThreadService,
UIContextService uiContextService)
{
private readonly SemaphoreSlim semaphoreSlim = new(1);
private readonly InstanceContextService instanceContextService = instanceContextService.ThrowIfNull();
private readonly GameThreadService gameThreadService = gameThreadService.ThrowIfNull();
private readonly UIContextService uiContextService = uiContextService.ThrowIfNull();
/// <summary>
/// Adds a message to the chat. It does not send the message, it only shows it to the player.
/// </summary>
public async ValueTask AddMessageAsync(string message, string? sender, Channel channel, CancellationToken cancellationToken)
{
using var ctx = await this.semaphoreSlim.Acquire(cancellationToken);
await this.gameThreadService.QueueOnGameThread(() =>
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
return;
}
var encodedMessage = string.Create(
3 + message.Length,
message,
static (span, msg) =>
{
span[0] = '\u0108';
span[1] = '\u0107';
msg.AsSpan().CopyTo(span[2..]);
span[^1] = '\u0001';
});
var encodedSender = sender is not null
? string.Create(
3 + sender.Length,
sender,
static (span, msg) =>
{
span[0] = '\u0108';
span[1] = '\u0107';
msg.AsSpan().CopyTo(span[2..]);
span[^1] = '\u0001';
})
: default;
var encoded = encodedSender is null
? encodedMessage
: string.Create(
encodedMessage.Length + encodedSender.Length + 6,
(encodedMessage, encodedSender),
static (span, state) =>
{
span[0] = '\u076b';
span[1] = '\u010a';
state.encodedSender.AsSpan().CopyTo(span[2..]);
span[2 + state.encodedSender.Length] = '\u0001';
span[3 + state.encodedSender.Length] = '\u010b';
state.encodedMessage.AsSpan().CopyTo(span[(4 + state.encodedSender.Length)..]);
span[^2] = '\u0001';
});
unsafe
{
fixed (char* encodedPtr = encoded)
{
using var packet = new UnmanagedStruct<kPrintChatMessage>(new kPrintChatMessage { Channel = (GWCA.GW.Chat.Channel)channel, Message = (nint)encodedPtr });
this.uiContextService.SendMessage(UIMessage.kWriteToChatLog, (nuint)packet.Address, 0x0);
}
}
}, cancellationToken);
}
}
@@ -0,0 +1,11 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Services.Interop;
public unsafe sealed class AgentContextService
{
public uint GetPlayerAgentId() => GWCA.GW.PlayerMgr.GetPlayerAgentId(0);
public Agent* GetAgentById(uint agentId) => GWCA.GW.Agents.GetAgentByID(agentId);
}
@@ -0,0 +1,16 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Services.Interop;
public unsafe sealed class GameContextService
{
public WrappedPointer<GameContext> GetGameContext() => GWCA.GW.GetGameContext();
public WrappedPointer<PreGameContext> GetPreGameContext() => GWCA.GW.GetPreGameContext();
public WrappedPointer<GuildWarsArray<CharacterInformation>> GetAvailableChars() => GWCA.GW.GetAvailableChars();
public bool IsMapLoaded() => GWCA.GW.Map.GetIsMapLoaded();
}
@@ -0,0 +1,92 @@
using Daybreak.API.Interop;
using Daybreak.API.Models;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Security;
namespace Daybreak.API.Services.Interop;
public sealed class GameThreadService
{
[SuppressUnmanagedCodeSecurity]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void GameThreadCallback();
// Prevent GC of delegates passed to native code
private readonly ConcurrentDictionary<GameThreadCallback, bool> prevent_GC_callbacks = [];
public Task QueueOnGameThread(Action action, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource();
var item = new WorkItem(action, taskCompletionSource, cancellationToken);
GameThreadCallback callback = null!;
callback = () =>
{
try
{
if (item.CancellationToken.IsCancellationRequested)
{
item.Cancel();
return;
}
item.Execute();
}
catch (Exception ex)
{
item.Exception(ex);
}
finally
{
this.prevent_GC_callbacks.TryRemove(callback, out _);
}
};
// CRITICAL: Keep delegate alive until callback executes
this.prevent_GC_callbacks[callback] = true;
var funcPtr = Marshal.GetFunctionPointerForDelegate(callback);
GWCA.GW.GameThread.Enqueue(funcPtr, false);
return taskCompletionSource.Task;
}
public Task<T> QueueOnGameThread<T>(Func<T> action, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<T>();
var item = new WorkItem<T>(action, taskCompletionSource, cancellationToken);
GameThreadCallback callback = null!;
callback = () =>
{
try
{
if (item.CancellationToken.IsCancellationRequested)
{
item.Cancel();
return;
}
item.Execute();
}
catch (Exception ex)
{
item.Exception(ex);
}
finally
{
// Remove from prevent-GC set after execution
this.prevent_GC_callbacks.TryRemove(callback, out _);
}
};
// CRITICAL: Keep delegate alive until callback executes
this.prevent_GC_callbacks[callback] = true;
var funcPtr = Marshal.GetFunctionPointerForDelegate(callback);
GWCA.GW.GameThread.Enqueue(funcPtr, false);
return taskCompletionSource.Task;
}
}
@@ -0,0 +1,13 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Services.Interop;
public sealed unsafe class InstanceContextService
{
public WrappedPointer<AreaInfo> GetAreaInfo() => GWCA.GW.Map.GetMapInfo(0);
public ServerRegion GetServerRegion() => (Daybreak.API.Interop.GuildWars.ServerRegion)GWCA.GW.Map.GetRegion();
public InstanceType GetInstanceType() => (Daybreak.API.Interop.GuildWars.InstanceType)GWCA.GW.Map.GetInstanceType();
}
@@ -0,0 +1,439 @@
using PeNet;
using PeNet.Header.Pe;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions.Core;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
namespace Daybreak.API.Services.Interop;
public sealed unsafe class MemoryScanningService(
ILogger<MemoryScanningService> logger)
{
private readonly ILogger<MemoryScanningService> logger = logger.ThrowIfNull();
private readonly (nuint BaseAddress, ImageSectionHeader Section) textSection = GetSectionHeader(".text");
private readonly (nuint BaseAddress, ImageSectionHeader Section) dataSection = GetSectionHeader(".rdata");
public uint GameTlsIndex { get; } = GetGameTlsIndex();
public nuint FunctionFromNearCall(nuint callInstructionAddress, bool checkValidPtr = true)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (callInstructionAddress is 0)
{
scopedLogger.LogError("Invalid call instruction address: 0x{address:X8}", callInstructionAddress);
return 0;
}
var opcode = Marshal.ReadByte((nint)callInstructionAddress);
var functionAddress = opcode switch
{
0xE8 => (nuint)((nint)callInstructionAddress + 5 + Marshal.ReadInt32((nint)(callInstructionAddress + 1))),
0xE9 => (nuint)((nint)callInstructionAddress + 5 + Marshal.ReadInt32((nint)(callInstructionAddress + 1))),
0xEB => (nuint)((nint)callInstructionAddress + 2 + (sbyte)Marshal.ReadByte((nint)(callInstructionAddress + 1))),
_ => (nuint)0
};
if (checkValidPtr && functionAddress is 0)
{
scopedLogger.LogError("Invalid function address: 0x{address:X8}", functionAddress);
return 0;
}
// Recursively resolve nested JMPs
var nestedCall = this.FunctionFromNearCall(functionAddress, checkValidPtr);
if (nestedCall != 0)
{
scopedLogger.LogInformation("Resolved nested call to: 0x{address:X8}", nestedCall);
return nestedCall;
}
scopedLogger.LogInformation("Resolved function address: 0x{address:X8}", functionAddress);
return functionAddress;
}
public nuint FindAssertion(
string assertionFile,
string assertionMessage,
uint lineNumber = 0,
int offset = 0)
{
Span<byte> pattern =
[
0x68, 0,0,0,0, // push <line> / will be overwritten later
0xBA, 0,0,0,0, // mov edx,<file> / will be overwritten later
0xB9, 0,0,0,0 // mov ecx,<msg > / will be overwritten later
];
const string maskAll = "xxxxxxxxxxxxxxx"; // 15 × 'x'
var msgBytes = Encoding.ASCII.GetBytes(assertionMessage + '\0');
var msgMask = new string('x', msgBytes.Length);
var fileOrigBytes = Encoding.ASCII.GetBytes(assertionFile + '\0');
var fileOrigMask = new string('x', fileOrigBytes.Length);
var (rdataBase, rdataHdr) = this.dataSection;
var rdataStart = rdataBase + rdataHdr.VirtualAddress;
var rdataEnd = rdataStart + rdataHdr.VirtualSize;
nuint msgSearchPos = rdataStart;
while (true)
{
var msgPtr = FindInRange(msgBytes, msgMask, 0, msgSearchPos, rdataEnd);
if (msgPtr is 0)
{
break; // no more messages → we are done
}
msgSearchPos = msgPtr + 1; // next search starts just after the hit
// write ECX operand (little-endian) into the pattern
BitConverter.TryWriteBytes(pattern.Slice(11, 4), (uint)msgPtr);
var fileSearchPos = rdataStart;
while (true)
{
var filePtr = FindInRange(fileOrigBytes, fileOrigMask, 0, fileSearchPos, rdataEnd);
// fallbacks: lower-case and CamelCase, exactly like the C++
if (filePtr == 0)
{
var lower = assertionFile.ToLowerInvariant();
var lowerBytes = Encoding.ASCII.GetBytes(lower + '\0');
filePtr = FindInRange(lowerBytes, new string('x', lowerBytes.Length), 0, fileSearchPos, rdataEnd);
}
if (filePtr == 0)
{
var camel = ToCamelCase(assertionFile);
var camelBytes = Encoding.ASCII.GetBytes(camel + '\0');
filePtr = FindInRange(camelBytes, new string('x', camelBytes.Length), 0, fileSearchPos, rdataEnd);
}
if (filePtr == 0)
{
break; // try next message
}
fileSearchPos = filePtr + 1;
// If what we found is "…/file.hpp" (no drive letter) fix it
if (Marshal.ReadByte((nint)(filePtr + 1)) != (byte)':')
{
// look ≤128 bytes backward for ':' and back-up one char
nuint colon = FindInRange([(byte)':'], "x", -1, filePtr, filePtr - 128);
if (colon == 0)
{
continue; // failed → try next file hit
}
filePtr = colon;
}
// write EDX operand
BitConverter.TryWriteBytes(pattern.Slice(6, 4), (uint)filePtr);
nuint hit = 0;
if (lineNumber != 0 && (lineNumber & 0xff) == lineNumber)
{
// PUSH imm8 variant start matching at pattern[3]
pattern[3] = 0x6A; // opcode
pattern[4] = (byte)lineNumber;
hit = FindAddressInternal(
this.textSection,
pattern[3..].ToArray(),
maskAll[3..],
offset);
}
if (hit == 0 && lineNumber != 0)
{
// PUSH imm32 variant whole pattern
pattern[0] = 0x68;
BitConverter.TryWriteBytes(pattern.Slice(1, 4), lineNumber);
hit = FindAddressInternal(
this.textSection,
pattern.ToArray(),
maskAll,
offset);
}
if (hit == 0 && lineNumber == 0)
{
// No line number match only from MOV EDX onward
hit = FindAddressInternal(
this.textSection,
pattern[5..].ToArray(),
maskAll[5..],
offset);
}
if (hit != 0)
{
return hit; // found exactly what we wanted
}
}
}
return 0; // nothing matched
}
public nuint FindAndResolveAddress(byte[] pattern, string mask, int offset = 0)
{
var scopedLogger = this.logger.CreateScopedLogger();
var address = FindAddressInternal(this.textSection, pattern, mask, offset);
if (address is 0)
{
scopedLogger.LogError("Failed to find address");
return 0;
}
var ptr = (nuint)Marshal.ReadIntPtr((nint)address);
return ptr;
}
public nuint FindAddress(byte[] pattern, string mask, int offset = 0)
{
var address = FindAddressInternal(this.textSection, pattern, mask, offset);
return address;
}
public nuint FindAddress(byte[] pattern, string mask, int offset, nuint startAddress, nuint endAddress)
{
var address = FindAddressInternal(startAddress, endAddress, pattern, mask, offset);
return address;
}
public nuint ToFunctionStart(nuint callInstructionAddress, uint scanRange = 0x500)
{
if (callInstructionAddress == 0)
{
return 0;
}
// pattern: 55 8B EC mask: "xxx"
ReadOnlySpan<byte> prologue = [0x55, 0x8B, 0xEC];
const string mask = "xxx";
var start = callInstructionAddress; // begin at the CALL itself
var end = callInstructionAddress - scanRange; // scan backwards
return FindInRange(prologue, mask, 0, start, end);
}
public nuint FindNthUseOfString(string str, uint nth, int offset = 0, bool useTextSection = true)
{
var scopedLogger = this.logger.CreateScopedLogger();
var strBytes = Encoding.ASCII.GetBytes(str);
var mask = new string('x', strBytes.Length);
var foundStr = FindAddressInternal(this.dataSection, strBytes, mask, 0);
if (foundStr is 0)
{
scopedLogger.LogError("Failed to find string in .rdata section");
return 0;
}
var firstNullChar = FindInRange([0x0], "x", 1, foundStr, foundStr - 0x64);
if (firstNullChar is 0)
{
scopedLogger.LogError("Failed to find null terminator");
return 0;
}
return this.FindNthUseOfAddress(firstNullChar, nth, offset, useTextSection);
}
public nuint FindUseOfString(string str, int offset = 0, bool useTextSection = true)
{
return this.FindNthUseOfString(str, 0, offset, useTextSection);
}
public nuint FindNthUseOfWideString(string str, uint nth, int offset = 0, bool useTextSection = true)
{
var scopedLogger = this.logger.CreateScopedLogger();
var strBytes = Encoding.Unicode.GetBytes(str);
var mask = new string('x', strBytes.Length);
var foundStr = FindAddressInternal(this.dataSection, strBytes, mask, 0);
if (foundStr is 0)
{
scopedLogger.LogError("Failed to find wide string in .rdata section");
return 0;
}
var firstNullChar = FindInRange([0x0, 0x0], "xx", 2, foundStr, foundStr - 0x128);
if (firstNullChar is 0)
{
scopedLogger.LogError("Failed to find null terminator for wide string");
return 0;
}
return this.FindNthUseOfAddress(firstNullChar, nth, offset, useTextSection);
}
public nuint FindUseOfWideString(string str, int offset = 0, bool useTextSection = true)
{
return this.FindNthUseOfWideString(str, 0, offset, useTextSection);
}
private nuint FindNthUseOfAddress(nuint address, uint nth, int offset, bool useTextSection)
{
var (BaseAddress, Section) = useTextSection ? this.textSection : this.dataSection;
var addressBytes = BitConverter.GetBytes((uint)address);
var mask = "xxxx";
var currentAddress = BaseAddress + Section.VirtualAddress;
var endAddress = currentAddress + Section.VirtualSize;
uint foundCount = 0;
while (currentAddress < endAddress)
{
var found = FindAddressInternal(currentAddress, endAddress, addressBytes, mask, 0);
if (found is 0)
{
break;
}
if (foundCount == nth)
{
return (nuint)((nint)found + offset);
}
foundCount++;
currentAddress = found + 1;
}
return 0;
}
private static bool IsValidPtr(nuint address, (nuint BaseAddress, ImageSectionHeader Section) section)
{
return address > 0 &&
(ulong)address > section.Section.ImageBaseAddress &&
(ulong)address < (section.Section.ImageBaseAddress + section.Section.VirtualSize);
}
private static unsafe nuint FindInRange(
ReadOnlySpan<byte> pattern,
string mask,
int offset,
nuint start,
nuint end)
{
bool forward = start < end;
int patLength = pattern.Length;
if (forward)
{
end -= (uint)patLength; // forward scan ⇒ clamp tail
}
var cur = start;
while (forward ? cur <= end : cur >= end)
{
if (Marshal.ReadByte((nint)cur) == pattern[0])
{
var matched = true;
for (int i = 1; i < patLength && matched; ++i)
{
if (mask[i] == 'x' &&
Marshal.ReadByte((nint)(cur + (uint)i)) != pattern[i])
{
matched = false;
}
}
if (matched)
{
return (nuint)((long)cur + offset);
}
}
cur = forward ? cur + 1 : cur - 1;
if (!forward && cur == 0) // prevent unsigned wrap-around
{
break;
}
}
return 0;
}
private static string ToCamelCase(string text)
=> CultureInfo.InvariantCulture.TextInfo.ToTitleCase(text.ToLowerInvariant());
private static nuint FindAddressInternal((nuint BaseAddress, ImageSectionHeader Section) section, byte[] pattern, string mask, int offset = 0)
{
var textStart = section.BaseAddress + section.Section.VirtualAddress;
var textEnd = textStart + section.Section.VirtualSize - (uint)pattern.Length;
return FindAddressInternal(textStart, textEnd, pattern, mask, offset);
}
private static nuint FindAddressInternal(nuint startAddr, nuint endAddr, byte[] pattern, string mask, int offset = 0)
{
for (var cur = startAddr; cur <= endAddr; ++cur)
{
if (Marshal.ReadByte((nint)cur) != pattern[0])
{
continue;
}
var match = true;
for (var i = 1; i < pattern.Length && match; ++i)
{
if (mask[i] is 'x' && Marshal.ReadByte((nint)(cur + (uint)i)) != pattern[i])
{
match = false;
}
}
if (match)
{
return (nuint)((nint)cur + offset);
}
}
return 0;
}
private static (nuint, ImageSectionHeader) GetSectionHeader(string headerName)
{
var m = Process.GetCurrentProcess().MainModule ?? throw new InvalidOperationException("Failed to initialize memory scanner. Failed to find main module");
var baseAddr = (nuint)m.BaseAddress;
var pe = new PeFile(m.FileName);
var textHdr = pe.ImageSectionHeaders?.FirstOrDefault(h => h.Name == headerName);
return textHdr is null
? throw new InvalidOperationException($"Failed to initialize memory scanner. Failed to find {headerName} section")
: ((nuint, ImageSectionHeader))(baseAddr, textHdr);
}
private static uint GetGameTlsIndex()
{
var m = Process.GetCurrentProcess().MainModule ?? throw new InvalidOperationException("Failed to get TLS index. Failed to find main module");
var baseAddr = (nint)m.BaseAddress;
var pe = new PeFile(m.FileName);
// Get the TLS data directory entry (index 9 = IMAGE_DIRECTORY_ENTRY_TLS)
var tlsDirectory = pe.ImageNtHeaders?.OptionalHeader.DataDirectory[9];
if (tlsDirectory is null || tlsDirectory.VirtualAddress == 0)
{
throw new InvalidOperationException("Failed to get TLS index. No TLS directory found");
}
// Calculate the address of the TLS directory in memory
var tlsDirectoryAddress = baseAddr + (nint)tlsDirectory.VirtualAddress;
// Read the IMAGE_TLS_DIRECTORY structure
// AddressOfIndex is at offset 8 (after StartAddressOfRawData and EndAddressOfRawData)
var addressOfIndexPtr = Marshal.ReadIntPtr(tlsDirectoryAddress + 8);
// Read the TLS index from the location pointed to by AddressOfIndex
var tlsIndex = (uint)Marshal.ReadInt32(addressOfIndexPtr);
return tlsIndex;
}
}
@@ -0,0 +1,15 @@
using Daybreak.API.Interop;
using static Daybreak.API.Interop.GWCA.GW.Constants;
namespace Daybreak.API.Services.Interop;
public sealed class PartyContextService
{
public bool AddHero(uint heroId) => GWCA.GW.PartyMgr.AddHero((HeroID)heroId);
public bool KickHero(uint heroId) => GWCA.GW.PartyMgr.KickHero((HeroID)heroId);
public bool KickAllHeroes() => GWCA.GW.PartyMgr.KickAllHeroes();
public bool LeaveParty() => GWCA.GW.PartyMgr.LeaveParty();
}
@@ -0,0 +1,14 @@
using Daybreak.API.Interop;
namespace Daybreak.API.Services.Interop;
public sealed unsafe class PlatformContextService
{
public nint GetWindowHandle()
{
var hwnd = GWCA.GW.MemoryMgr.GetGWWindowHandle();
// HWND is defined as HWND__* in Windows - the pointer value itself IS the handle,
// not a pointer to a struct containing the handle. Cast directly to nint.
return (nint)hwnd;
}
}
@@ -0,0 +1,11 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Services.Interop;
public sealed class PreferencesService()
{
public uint? GetEnumPreference(EnumPreference pref) => GWCA.GW.UI.GetPreference((GWCA.GW.UI.EnumPreference)pref);
public bool SetEnumPreference(EnumPreference pref, uint value) => GWCA.GW.UI.SetPreference((GWCA.GW.UI.EnumPreference)pref, value);
}
@@ -0,0 +1,9 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
namespace Daybreak.API.Services.Interop;
public sealed class SkillbarContextService
{
public unsafe void LoadBuild(uint agentId, SkillTemplate* template) => GWCA.GW.SkillbarMgr.LoadSkillTemplate(agentId, template);
}
@@ -0,0 +1,146 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using System.Collections.Concurrent;
using System.Core.Extensions;
using System.Extensions.Core;
using System.Runtime.InteropServices;
using System.Security;
namespace Daybreak.API.Services.Interop;
public sealed class UIContextService(ILogger<UIContextService> logger)
{
[SuppressUnmanagedCodeSecurity]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate void AsyncDecodeStrCallback(void* param, ushort* decodedString);
// Prevent GC of delegates passed to native code
private readonly ConcurrentDictionary<AsyncDecodeStrCallback, bool> prevent_GC_callbacks = [];
private readonly ILogger<UIContextService> logger = logger.ThrowIfNull();
public unsafe WrappedPointer<T> GetFrameContext<T>(WrappedPointer<Frame> frame)
where T : unmanaged
{
return (T*)GWCA.GW.UI.GetFrameContext(frame.Pointer);
}
public unsafe WrappedPointer<Frame> GetChildFrame(WrappedPointer<Frame> parent, uint childOffset)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (parent.IsNull)
{
scopedLogger.LogError("Parent frame is null");
return null;
}
return GWCA.GW.UI.GetChildFrame(parent, childOffset);
}
public WrappedPointer<Frame> GetButtonActionFrame()
{
return this.GetChildFrame(this.GetFrameByLabel("Game"), 6);
}
public unsafe bool SendFrameUIMessage(WrappedPointer<Frame> frame, UIMessage messageId, void* arg1, void* arg2 = null)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (frame.IsNull)
{
scopedLogger.LogError("Frame is null");
return false;
}
return GWCA.GW.UI.SendFrameUIMessage(frame, (GWCA.GW.UI.UIMessage)messageId, arg1, (nint)arg2);
}
public unsafe bool SetFrameDisabled(WrappedPointer<Frame> frame, bool disabled) => GWCA.GW.UI.SetFrameDisabled(frame, disabled);
public unsafe bool SetFrameVisible(WrappedPointer<Frame> frame, bool visible) => GWCA.GW.UI.SetFrameVisible(frame, visible);
public unsafe bool KeyDown(GWCA.GW.UI.ControlAction action, WrappedPointer<Frame> frame) => GWCA.GW.UI.Keydown(action, frame);
public unsafe bool KeyUp(GWCA.GW.UI.ControlAction action, WrappedPointer<Frame> frame) => GWCA.GW.UI.Keyup(action, frame);
public unsafe WrappedPointer<Frame> GetFrameByLabel(string label)
{
fixed (char* labelPtr = label)
{
return GWCA.GW.UI.GetFrameByLabel((ushort*)labelPtr);
}
}
public unsafe WrappedPointer<Frame> GetFrameById(uint frameId) => GWCA.GW.UI.GetFrameById(frameId);
public unsafe WrappedPointer<Frame> GetParentFrame(WrappedPointer<Frame> frame)
{
if (frame.IsNull)
{
return null;
}
return GWCA.GW.UI.GetParentFrame(frame);
}
public unsafe bool ButtonClick(WrappedPointer<Frame> frame)
{
if (frame.IsNull)
{
return false;
}
return GWCA.GW.UI.ButtonClick(frame);
}
public unsafe void SendMessage(UIMessage message, nuint wParam, nuint lParam) => GWCA.GW.UI.SendUIMessage((GWCA.GW.UI.UIMessage)message, (void*)wParam, (nint)lParam);
public unsafe Task<string> AsyncDecodeStringAsync(ushort* encodedString, GWCA.GW.Constants.Language language = GWCA.GW.Constants.Language.Unknown, CancellationToken cancellationToken = default)
{
var taskCompletionSource = new TaskCompletionSource<string>();
cancellationToken.Register(() => taskCompletionSource.TrySetCanceled(cancellationToken));
void callback(void* param, ushort* decodedString)
{
try
{
if (cancellationToken.IsCancellationRequested)
{
taskCompletionSource.TrySetCanceled(cancellationToken);
return;
}
// Convert the decoded wide string to a managed string
var result = decodedString != null
? new string((char*)decodedString)
: string.Empty;
taskCompletionSource.TrySetResult(result);
}
catch (Exception ex)
{
taskCompletionSource.TrySetException(ex);
}
finally
{
this.prevent_GC_callbacks.TryRemove(callback, out _);
}
}
// CRITICAL: Keep delegate alive until callback executes
this.prevent_GC_callbacks[callback] = true;
var funcPtr = Marshal.GetFunctionPointerForDelegate(callback);
// GWCA's AsyncDecodeStr handles language switching internally (sets language, decodes, restores)
GWCA.GW.UI.AsyncDecodeStr(encodedString, funcPtr, 0, language);
return taskCompletionSource.Task;
}
public unsafe Task<string> AsyncDecodeStringAsync(string encodedString, GWCA.GW.Constants.Language language = GWCA.GW.Constants.Language.Unknown, CancellationToken cancellationToken = default)
{
// Pin the string and convert to ushort*
fixed (char* encodedPtr = encodedString)
{
return this.AsyncDecodeStringAsync((ushort*)encodedPtr, language, cancellationToken);
}
}
}
+136
View File
@@ -0,0 +1,136 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using Daybreak.API.Services.Interop;
using Daybreak.Shared.Models.Api;
using Daybreak.Shared.Models.Guildwars;
using System.Extensions.Core;
using System.Runtime.CompilerServices;
using System.Text;
namespace Daybreak.API.Services;
public sealed class InventoryService(
GameContextService gameContextService,
GameThreadService gameThreadService,
UIService uIService,
ILogger<InventoryService> logger)
{
private readonly GameContextService gameContextService = gameContextService;
private readonly GameThreadService gameThreadService = gameThreadService;
private readonly UIService uIService = uIService;
private readonly ILogger<InventoryService> logger = logger;
public async Task<InventoryInformation?> GetInventoryInformation(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
var itemTuples = await this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull)
{
scopedLogger.LogError("Game context is null");
return default;
}
var inventory = gameContext.Pointer->Items->Inventory;
if (inventory is null)
{
scopedLogger.LogError("Inventory is null");
return default;
}
var itemTuples = new List<(GWCA.GW.Constants.BagType Type, List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, Byte DyeTint, ushort Value)>)>();
for (var i = 0; i < 23; i++)
{
// Use GWCA helper to fetch bag pointers by index — safer than reading an inline array directly.
var bag = GWCA.GW.Items.GetBagByIndex((uint)i);
if (bag is null)
{
continue;
}
var retBag = new List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, Byte DyeTint, ushort Value)>();
itemTuples.Add((bag->BagType, retBag));
if (bag->ItemsCount is 0)
{
continue;
}
foreach (var itemPtr in bag->Items.Value)
{
if (itemPtr is 0)
{
continue;
}
var item = (ItemStruct*)itemPtr;
var singleItemName = new string((char*)item->SingleItemName);
var nameEncoded = new string((char*)item->NameEnc);
var completeNameEncoded = new string((char*)item->CompleteNameEnc);
var modifiers = new uint[item->ModStructSize];
for (var j = 0; j < item->ModStructSize; j++)
{
modifiers[j] = item->ModStruct[j].Mod;
}
retBag.Add((item->ModelId, completeNameEncoded, singleItemName, nameEncoded, (item->Interaction & 0x08000000) != 0, item->Quantity, modifiers, (ItemType)item->Type, item->Interaction, item->ModelFileId, item->Dye.DyeTint, item->Value));
}
}
return itemTuples;
}
}, cancellationToken);
if (itemTuples is null)
{
return default;
}
// Decode strings sequentially to avoid race conditions with the game's TextParser language field.
// Parallel decoding causes crashes because multiple operations race to set/restore the language.
var decodedItemTuples = new List<(GWCA.GW.Constants.BagType Type, List<(uint ModelId, string EncodedName, string? DecodedName, string EncodedSingleName, string? DecodedSingleName, string EncodedCompleteName, string? DecodedCompleteName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, byte DyeTint, ushort Value)> Items)>();
foreach (var tuple in itemTuples)
{
var decodedItems = new List<(uint ModelId, string EncodedName, string? DecodedName, string EncodedSingleName, string? DecodedSingleName, string EncodedCompleteName, string? DecodedCompleteName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType, uint Interaction, uint ModelFileId, byte DyeTint, ushort Value)>();
foreach (var item in tuple.Item2)
{
var decodedName = await this.uIService.DecodeString(item.EncodedName, (GWCA.GW.Constants.Language)Language.English, cancellationToken);
var decodedCompleteName = await this.uIService.DecodeString(item.EncodedCompleteName, (GWCA.GW.Constants.Language)Language.English, cancellationToken);
var decodedSingleName = await this.uIService.DecodeString(item.EncodedSingleName, (GWCA.GW.Constants.Language)Language.English, cancellationToken);
decodedItems.Add((item.ModelId, item.EncodedName, decodedName, item.EncodedSingleName, decodedSingleName, item.EncodedCompleteName, decodedCompleteName, item.Inscribable, item.Quantity, item.Modifiers, item.ItemType, item.Interaction, item.ModelFileId, item.DyeTint, item.Value));
}
decodedItemTuples.Add((tuple.Type, decodedItems));
}
return new InventoryInformation(
Bags: [.. decodedItemTuples.Select(tuple =>
new BagEntry(
BagType: tuple.Type.ToString(),
Items: [.. tuple.Items.Select(item =>
new ItemEntry(
ItemModelId: item.ModelId,
EncodedName: ToBase64(item.EncodedName),
DecodedName: item.DecodedName ?? string.Empty,
EncodedSingleName: ToBase64(item.EncodedSingleName),
DecodedSingleName: item.DecodedSingleName ?? string.Empty,
EncodedCompleteName: ToBase64(item.EncodedCompleteName),
DecodedCompleteName: item.DecodedCompleteName ?? string.Empty,
Inscribable: item.Inscribable,
Quantity: item.Quantity,
Modifiers: item.Modifiers,
Properties: [.. ItemProperty.ParseItemModifiers([.. item.Modifiers.Select(m => (Shared.Models.Guildwars.ItemModifier)m)])],
ItemType: item.ItemType.ToString(),
Interaction: item.Interaction,
ModelFileId: item.ModelFileId,
DyeTint: item.DyeTint,
Value: item.Value))]))]);
}
private static string ToBase64(string encoded)
{
return Convert.ToBase64String(Encoding.Unicode.GetBytes(encoded));
}
}
+37
View File
@@ -0,0 +1,37 @@
using Daybreak.API.Services.Interop;
using Daybreak.Shared.Models.Api;
using System.Core.Extensions;
using System.Extensions.Core;
namespace Daybreak.API.Services;
public sealed class LoginService(
GameThreadService gameThreadService,
GameContextService gameContextService,
ILogger<LoginService> logger)
{
private readonly GameThreadService gameThreadService = gameThreadService.ThrowIfNull();
private readonly GameContextService gameContextService = gameContextService.ThrowIfNull();
private readonly ILogger<LoginService> logger = logger.ThrowIfNull();
public Task<LoginInfo?> GetLoginInformation(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull || gameContext.Pointer->Character is null)
{
scopedLogger.LogError("Game context is not initialized");
return default;
}
var playerName = new string(gameContext.Pointer->Character->PlayerName);
var email = new string(gameContext.Pointer->Character->PlayerEmail);
return new LoginInfo(email, playerName);
}
}, cancellationToken);
}
}
+528
View File
@@ -0,0 +1,528 @@
using Daybreak.API.Extensions;
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using Daybreak.API.Models;
using Daybreak.API.Services.Interop;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.Api;
using Daybreak.Shared.Models.Builds;
using Daybreak.Shared.Services.BuildTemplates;
using System.Core.Extensions;
using System.Extensions;
using System.Extensions.Core;
using System.Runtime.CompilerServices;
using ZLinq;
using InstanceType = Daybreak.API.Interop.GuildWars.InstanceType;
namespace Daybreak.API.Services;
public sealed class MainPlayerService : IDisposable
{
private readonly ChatService chatService;
private readonly InstanceContextService instanceContextService;
private readonly IBuildTemplateManager buildTemplateManager;
private readonly SkillbarContextService skillbarContextService;
private readonly AgentContextService agentContextService;
private readonly GameContextService gameContextService;
private readonly GameThreadService gameThreadService;
private readonly ILogger<MainPlayerService> logger;
public MainPlayerService(
ChatService chatService,
InstanceContextService instanceContextService,
IBuildTemplateManager buildTemplateManager,
SkillbarContextService skillbarContextService,
GameContextService gameContextService,
AgentContextService agentContextService,
GameThreadService gameThreadService,
ILogger<MainPlayerService> logger)
{
this.chatService = chatService.ThrowIfNull();
this.instanceContextService = instanceContextService.ThrowIfNull();
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
this.skillbarContextService = skillbarContextService.ThrowIfNull();
this.gameThreadService = gameThreadService.ThrowIfNull();
this.agentContextService = agentContextService.ThrowIfNull();
this.gameContextService = gameContextService.ThrowIfNull();
this.logger = logger.ThrowIfNull();
}
public void Dispose()
{
}
public async Task<bool> SetCurrentBuild(string buildCode, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (!this.buildTemplateManager.TryDecodeTemplate(buildCode, out var build) ||
build is not SingleBuildEntry singleBuild)
{
scopedLogger.LogError("Failed to decode build template from code {buildCode}", buildCode);
return false;
}
var result = await this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading or InstanceType.Explorable)
{
scopedLogger.LogError("Not in outpost");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull ||
gameContext.Pointer->World is null)
{
scopedLogger.LogError("Failed to get game context");
return false;
}
var playerAgentId = this.agentContextService.GetPlayerAgentId();
if (playerAgentId is 0x0)
{
scopedLogger.LogError("Failed to get player agent id");
return false;
}
var playerAgent = this.agentContextService.GetAgentById(playerAgentId);
if (playerAgent is null ||
playerAgent->Type is not (uint)AgentType.Living ||
playerAgent->AgentId != playerAgentId)
{
scopedLogger.LogError("Player agent {playerAgentId} not found in agent array", playerAgentId);
return false;
}
var livingAgent = (AgentLiving*)playerAgent;
if (livingAgent->Level != gameContext.Pointer->World->Level)
{
scopedLogger.LogError("Player agent not found. Player level mismatch: {level} != {gameLevel}", livingAgent->Level, gameContext.Pointer->World->Level);
return false;
}
var agentProfession = gameContext.Pointer->World->PartyProfessionStates.AsValueEnumerable().FirstOrDefault(p => p.AgentId == playerAgentId);
if (agentProfession.AgentId != playerAgentId)
{
scopedLogger.LogError("Failed to find agent profession for player agent id {agentId}", playerAgentId);
return false;
}
var validationRequest = new BuildTemplateValidationRequest(
(uint)singleBuild.Primary.Id,
(uint)singleBuild.Secondary.Id,
singleBuild.Skills.AsValueEnumerable().Select(s => (uint)s.Id).ToArray(),
livingAgent->Tags->Primary,
agentProfession.UnlockedProfessions,
[.. gameContext.Pointer->World->UnlockedCharacterSkills]);
if (!this.buildTemplateManager.CanTemplateApply(validationRequest))
{
scopedLogger.LogError("Build template validation failed for player agent id {agentId}", playerAgentId);
return false;
}
var attributeIds = new AttributeArray12();
var attributeValues = new Array12Uint();
var skills = new SkillIDArray8();
for (var i = 0; i < singleBuild.Attributes.Count && i < 12; i++)
{
if (singleBuild.Attributes[i].Attribute is null)
{
continue;
}
attributeIds[i] = (GWCA.GW.Constants.Attribute)singleBuild.Attributes[i].Attribute!.Id;
attributeValues[i] = (uint)singleBuild.Attributes[i].Points;
}
for (var i = 0; i < singleBuild.Skills.Count && i < 8; i++)
{
skills[i] = (GWCA.GW.Constants.SkillID)singleBuild.Skills[i].Id;
}
var skillTemplate = new SkillTemplate
{
Primary = (GWCA.GW.Constants.Profession)singleBuild.Primary.Id,
Secondary = (GWCA.GW.Constants.Profession)singleBuild.Secondary.Id,
AttributesCount = (uint)Math.Min(singleBuild.Attributes.Count, 12),
AttributeIds = attributeIds,
Skills = skills,
};
Unsafe.CopyBlock(skillTemplate.AttributeValues, Unsafe.AsPointer(ref attributeValues), (uint)(12 * sizeof(uint)));
this.skillbarContextService.LoadBuild(playerAgentId, &skillTemplate);
return true;
}
}, cancellationToken);
if (result)
{
await this.chatService.AddMessageAsync("Build applied successfully.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
}
else
{
await this.chatService.AddMessageAsync("Failed to apply build.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
}
return result;
}
public Task<BuildEntry?> GetCurrentBuild(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
scopedLogger.LogError("Not loaded");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull)
{
scopedLogger.LogError("Failed to get game context");
return default;
}
var playerAgentId = this.agentContextService.GetPlayerAgentId();
if (playerAgentId is 0x0)
{
scopedLogger.LogError("Failed to get player agent id");
return default;
}
var skillbarContext = gameContext.Pointer->World->Skillbar.Value.AsValueEnumerable().FirstOrDefault(s => s.AgentId == playerAgentId);
if (skillbarContext.AgentId != playerAgentId)
{
scopedLogger.LogError("Failed to find skillbar context for player agent id {agentId}", playerAgentId);
return default;
}
var agentAttributes = gameContext.Pointer->World->Attributes.Value.AsValueEnumerable().FirstOrDefault(a => a.AgentId == playerAgentId);
if (agentAttributes.AgentId != playerAgentId)
{
scopedLogger.LogError("Failed to find agent attributes for player agent id {agentId}", playerAgentId);
return default;
}
var professionContext = gameContext.Pointer->World->PartyProfessionStates.AsValueEnumerable().FirstOrDefault(p => p.AgentId == playerAgentId);
if (professionContext.AgentId != playerAgentId)
{
scopedLogger.LogError("Failed to find agent profession for player agent id {agentId}", playerAgentId);
return default;
}
return new BuildEntry(
(int)professionContext.Primary,
(int)professionContext.Secondary,
[.. agentAttributes.Attribute.GetAttributeEntryList()],
[(uint)skillbarContext.Skills[0].SkillId, (uint)skillbarContext.Skills[1].SkillId, (uint)skillbarContext.Skills[2].SkillId, (uint)skillbarContext.Skills[3].SkillId,
(uint)skillbarContext.Skills[4].SkillId, (uint)skillbarContext.Skills[5].SkillId, (uint)skillbarContext.Skills[6].SkillId, (uint)skillbarContext.Skills[7].SkillId]);
}
}, cancellationToken);
}
public Task<QuestLogInformation?> GetQuestLog(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
scopedLogger.LogError("Not loaded");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull || gameContext.Pointer->World is null)
{
scopedLogger.LogError("Game context is not initialized");
return default;
}
return new QuestLogInformation(
(uint)gameContext.Pointer->World->ActiveQuestId,
gameContext.Pointer->World->QuestLog.Value.AsValueEnumerable().Select(q => new QuestInformation((uint)q.QuestId, (uint)q.MapFrom)).ToList());
}
}, cancellationToken);
}
public Task<MainPlayerInformation?> GetMainPlayerInformation(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
scopedLogger.LogError("Not loaded");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull || gameContext.Pointer->Character is null ||
gameContext.Pointer->World is null)
{
scopedLogger.LogError("Game context is not initialized");
return default;
}
var playerName = new string (gameContext.Pointer->Character->PlayerName);
var email = new string(gameContext.Pointer->Character->PlayerEmail);
var accountName = new string((char*)gameContext.Pointer->World->AccountInfo->AccountName);
return new MainPlayerInformation(
(*(Uuid*)gameContext.Pointer->Character->PlayerUuid).ToString(),
email,
playerName,
accountName,
gameContext.Pointer->World->AccountInfo->Wins,
gameContext.Pointer->World->AccountInfo->Losses,
gameContext.Pointer->World->AccountInfo->Rating,
gameContext.Pointer->World->AccountInfo->QualifierPoints,
gameContext.Pointer->World->AccountInfo->Rank,
gameContext.Pointer->World->AccountInfo->TournamentRewardPoints);
}
}, cancellationToken);
}
public Task<MainPlayerState?> GetMainPlayerState(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
scopedLogger.LogError("Not loaded");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
var playerAgentId = this.agentContextService.GetPlayerAgentId();
if (gameContext.IsNull ||
gameContext.Pointer->World is null ||
gameContext.Pointer->Character is null ||
playerAgentId is 0x0)
{
scopedLogger.LogDebug("Game data is not yet initialized");
return default;
}
var playerAgent = this.agentContextService.GetAgentById(playerAgentId);
if (playerAgent is null ||
playerAgent->Type is not (uint)AgentType.Living ||
playerAgent->AgentId != playerAgentId)
{
scopedLogger.LogError("Player agent {playerAgentId} not found in agent array", playerAgentId);
return default;
}
var livingAgent = (AgentLiving*)playerAgent;
if (livingAgent->Level != gameContext.Pointer->World->Level)
{
scopedLogger.LogError("Player agent not found. Player level mismatch: {level} != {gameLevel}", livingAgent->Level, gameContext.Pointer->World->Level);
return default;
}
return new MainPlayerState(
gameContext.Pointer->World->Experience,
gameContext.Pointer->World->Level,
gameContext.Pointer->World->CurrentLuxon,
gameContext.Pointer->World->CurrentKurzick,
gameContext.Pointer->World->CurrentImperial,
gameContext.Pointer->World->CurrentBalth,
gameContext.Pointer->World->MaxLuxon,
gameContext.Pointer->World->MaxKurzick,
gameContext.Pointer->World->MaxImperial,
gameContext.Pointer->World->MaxBalth,
gameContext.Pointer->World->TotalEarnedLuxon,
gameContext.Pointer->World->TotalEarnedKurzick,
gameContext.Pointer->World->TotalEarnedImperial,
gameContext.Pointer->World->TotalEarnedBalth,
// Energy and health are percentages of Max
livingAgent->Hp * livingAgent->MaxHp,
livingAgent->MaxHp,
livingAgent->Energy * livingAgent->MaxEnergy,
livingAgent->MaxEnergy,
livingAgent->Tags->Primary,
livingAgent->Tags->Secondary,
playerAgent->Pos.X,
playerAgent->Pos.Y
);
}
}, cancellationToken);
}
public Task<InstanceInfo> GetMainPlayerInstance(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
var instanceType = this.instanceContextService.GetInstanceType();
if (instanceType is InstanceType.Loading)
{
return new InstanceInfo(0, 0, 0, 0, Shared.Models.Api.InstanceType.Loading, DistrictRegionInfo.Unknown, LanguageInfo.Unknown, CampaignInfo.Unknown, ContinentInfo.Unknown, RegionInfo.Unknown, DifficultyInfo.Unknown);
}
var gameContext = this.gameContextService.GetGameContext();
var currentMapInfo = this.instanceContextService.GetAreaInfo();
var serverRegion = this.instanceContextService.GetServerRegion();
if (gameContext.IsNull ||
gameContext.Pointer->Character is null ||
gameContext.Pointer->World is null ||
gameContext.Pointer->Party is 0 ||
currentMapInfo.IsNull)
{
scopedLogger.LogError("Game context is not initialized");
return new InstanceInfo(0, 0, 0, 0, Shared.Models.Api.InstanceType.Loading, DistrictRegionInfo.Unknown, LanguageInfo.Unknown, CampaignInfo.Unknown, ContinentInfo.Unknown, RegionInfo.Unknown, DifficultyInfo.Unknown);
}
var charContext = *gameContext.Pointer->Character;
var worldContext = *gameContext.Pointer->World;
var mapInfo = *currentMapInfo.Pointer;
var mapId = charContext.MapId;
var language = charContext.Language;
var districtNumber = charContext.DistrictNumber;
var foesKilled = worldContext.FoesKilled;
var foesToKill = worldContext.FoesToKill;
return new InstanceInfo(
MapId: (uint)charContext.MapId,
DistrictNumber: charContext.DistrictNumber,
FoesKilled: worldContext.FoesKilled,
FoesToKill: worldContext.FoesToKill,
Type: (Shared.Models.Api.InstanceType)instanceType,
DistrictRegion: (DistrictRegionInfo)serverRegion,
Language: (LanguageInfo)charContext.Language,
Campaign: (CampaignInfo)mapInfo.Campaign,
Continent: (ContinentInfo)mapInfo.Continent,
Region: (RegionInfo)mapInfo.Region,
Difficulty: GWCA.GW.PartyMgr.GetIsPartyInHardMode() ? DifficultyInfo.Hard : DifficultyInfo.Normal);
}
}, cancellationToken);
}
public Task<TitleInfo?> GetTitleInfo(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
scopedLogger.LogError("Not loaded");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull || gameContext.Pointer->World is null)
{
scopedLogger.LogError("Game context is not initialized");
return default;
}
var playerIndex = gameContext.Pointer->Character->PlayerNumber;
var player = GWCA.GW.PlayerMgr.GetPlayerByID(playerIndex);
if (player is null)
{
scopedLogger.LogError("Failed to get player");
return default;
}
var currentTier = player->ActiveTitleTier;
Title? currentTitle = default;
int? id = -1;
for (var i = 0; i < gameContext.Pointer->World->Titles.Value.Size; i++)
{
var title = gameContext.Pointer->World->Titles.Value.Skip(i).FirstOrDefault();
if (title.CurrentTitleTierIndex == currentTier)
{
currentTitle = title;
id = i;
break;
}
}
if (!currentTitle.HasValue)
{
scopedLogger.LogError("Failed to find current title with tier {currentTier}", currentTier);
return default;
}
var titleTier = gameContext.Pointer->World->TitleTiers.Skip((int)currentTitle.Value.CurrentTitleTierIndex).FirstOrDefault();
return new TitleInfo
(
Id: (uint)id,
IsPercentage: currentTitle.Value.IsPercentageBased(),
PointsForCurrentRank: currentTitle.Value.PointsNeededCurrentRank,
PointsForNextRank: titleTier.TierNumber == currentTitle.Value.MaxTitleRank ?
currentTitle.Value.CurrentPoints :
currentTitle.Value.PointsNeededNextRank,
CurrentPoints: currentTitle.Value.CurrentPoints,
TierNumber: titleTier.TierNumber,
MaxTierNumber: currentTitle.Value.MaxTitleTierIndex
);
}
}, cancellationToken);
}
public Task<MainPlayerBuildContext?> GetMainPlayerBuildContext(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading)
{
scopedLogger.LogError("Not loaded");
return default;
}
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull ||
gameContext.Pointer->Account is null ||
gameContext.Pointer->World is null)
{
this.logger.LogError("Game context is not initialized");
return default;
}
var playerAgentId = this.agentContextService.GetPlayerAgentId();
if (playerAgentId is 0x0)
{
scopedLogger.LogError("Failed to get player agent id");
return default;
}
var agentProfession = gameContext.Pointer->World->PartyProfessionStates.AsValueEnumerable().FirstOrDefault(p => p.AgentId == playerAgentId);
if (agentProfession.AgentId != playerAgentId)
{
scopedLogger.LogError("Failed to find agent profession for player agent id {agentId}", playerAgentId);
return default;
}
return new MainPlayerBuildContext(
PrimaryProfessionId: (uint)agentProfession.Primary,
UnlockedProfessions: agentProfession.UnlockedProfessions,
UnlockedCharacterSkills: gameContext.Pointer->World->UnlockedCharacterSkills.AsValueEnumerable().ToArray());
}
}, cancellationToken);
}
}
+510
View File
@@ -0,0 +1,510 @@
using Daybreak.API.Extensions;
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using Daybreak.API.Models;
using Daybreak.API.Services.Interop;
using Daybreak.Shared.Models;
using Daybreak.Shared.Models.Api;
using Daybreak.Shared.Models.Guildwars;
using Daybreak.Shared.Services.BuildTemplates;
using System.Core.Extensions;
using System.Extensions;
using System.Extensions.Core;
using System.Runtime.CompilerServices;
using ZLinq;
using InstanceType = Daybreak.API.Interop.GuildWars.InstanceType;
namespace Daybreak.API.Services;
public sealed class PartyService(
ChatService chatService,
IBuildTemplateManager buildTemplateManager,
UIService uiService,
UIContextService uIContextService,
SkillbarContextService skillbarContextService,
InstanceContextService instanceContextService,
PartyContextService partyContextService,
GameThreadService gameThreadService,
GameContextService gameContextService,
ILogger<PartyService> logger)
{
private static readonly TimeSpan HeroSpawnDelay = TimeSpan.FromSeconds(1);
private readonly ChatService chatService = chatService.ThrowIfNull();
private readonly IBuildTemplateManager buildTemplateManager = buildTemplateManager.ThrowIfNull();
private readonly UIService uiService = uiService.ThrowIfNull();
private readonly UIContextService uIContextService = uIContextService.ThrowIfNull();
private readonly SkillbarContextService skillbarContextService = skillbarContextService.ThrowIfNull();
private readonly InstanceContextService instanceContextService = instanceContextService.ThrowIfNull();
private readonly PartyContextService partyContextService = partyContextService.ThrowIfNull();
private readonly GameThreadService gameThreadService = gameThreadService.ThrowIfNull();
private readonly GameContextService gameContextService = gameContextService.ThrowIfNull();
private readonly ILogger<PartyService> logger = logger.ThrowIfNull();
public async Task<bool> SetPartyLoadout(PartyLoadout partyLoadout, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (!await this.IsInValidOutpost(cancellationToken))
{
scopedLogger.LogError("Could not set party loadout. Not in a valid outpost");
await this.chatService.AddMessageAsync("Cannot set party loadout. Not in a valid outpost.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
return false;
}
if (!await this.KickAllHeroes(cancellationToken))
{
scopedLogger.LogError("Could not set party loadout. Could not leave party");
await this.chatService.AddMessageAsync("Cannot set party loadout. Could not leave party.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
return false;
}
if (!await this.gameThreadService.QueueOnGameThread(() => this.SpawnHeroes(partyLoadout), cancellationToken))
{
scopedLogger.LogError("Could not set party loadout. Could not spawn heroes");
await this.chatService.AddMessageAsync("Cannot set party loadout. Could not spawn heroes.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
return false;
}
await Task.Delay(HeroSpawnDelay, cancellationToken);
if (!await this.gameThreadService.QueueOnGameThread(() => this.ApplyBuilds(partyLoadout), cancellationToken))
{
scopedLogger.LogError("Could not set party loadout. Could not apply builds");
await this.chatService.AddMessageAsync("Cannot set party loadout. Could not apply builds.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
return false;
}
var heroBehaviorSetup = await this.gameThreadService.QueueOnGameThread(() => this.GetHeroBehaviorSetup(partyLoadout), cancellationToken);
foreach (var (AgentId, Behavior) in heroBehaviorSetup ?? [])
{
if (!await this.SetHeroBehavior(AgentId, Behavior, cancellationToken))
{
scopedLogger.LogWarning("Could not set hero behavior for agent {agentId} to {behavior}", AgentId, Behavior);
await this.chatService.AddMessageAsync($"Cannot set party loadout. Could not set behavior {Behavior} for agent {AgentId}.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
}
}
await this.chatService.AddMessageAsync("Party loadout set.", "Daybreak.API", Channel.CHANNEL_MODERATOR, cancellationToken);
return true;
}
public Task<PartyLoadout?> GetPartyLoadout(CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
return this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
if (this.instanceContextService.GetInstanceType() is InstanceType.Loading or InstanceType.Explorable)
{
scopedLogger.LogError("Not in outpost");
return default;
}
if (!this.gameContextService.GetGameContext().TryGetBuildContext(out var skillbars, out var attributes, out var professions, out _))
{
scopedLogger.LogError("Failed to get build context");
return default;
}
if (!this.gameContextService.GetGameContext().TryGetHeroFlags(out var heroFlags))
{
scopedLogger.LogError("Failed to get hero flags");
return default;
}
if (!this.gameContextService.GetGameContext().TryGetPlayerId(out var playerId))
{
scopedLogger.LogError("Failed to get player id");
return default;
}
if (!this.gameContextService.GetGameContext().TryGetPlayerParty(out _, out _, out var heroes, out _))
{
scopedLogger.LogError("Failed to get player party");
return default;
}
var buildTuples = professions.Value.AsValueEnumerable()
.Select(p => (p.AgentId, p, attributes.Value.FirstOrDefault(a => a.AgentId == p.AgentId), skillbars.Value.FirstOrDefault(s => s.AgentId == p.AgentId), heroFlags.Value.FirstOrDefault(f => f.AgentId == p.AgentId)))
.Select(t => (t.AgentId, GetBuildEntryById(t.p, t.Item4, t.Item3), t.Item5.HeroBehavior))
.Where(t => t.Item2 is not null)
.OfType<(uint AgentId, BuildEntry BuildEntry, API.Interop.GWCA.GW.HeroBehavior Behavior)>()
.ToList();
return new PartyLoadout(
[.. buildTuples.Select(t =>
{
if (t.AgentId == playerId)
{
return new PartyLoadoutEntry(0, Shared.Models.Api.HeroBehavior.Undefined, t.BuildEntry);
}
else if (heroes.Value.FirstOrDefault(h => h.AgentId == t.AgentId) is HeroPartyMember hero &&
hero.AgentId == t.AgentId)
{
return new PartyLoadoutEntry((int)hero.HeroId, (Shared.Models.Api.HeroBehavior)t.Behavior, t.BuildEntry);
}
return default;
})
.Where(t => t is not null)
.OfType<PartyLoadoutEntry>()]);
}
}, cancellationToken);
}
public async Task<bool> KickAllHeroes(CancellationToken cancellationToken)
{
var partySize = await this.GetPartySize(cancellationToken);
if (partySize is 1 or 0)
{
return true;
}
return await this.gameThreadService.QueueOnGameThread(this.partyContextService.KickAllHeroes, cancellationToken);
}
public async Task<uint> GetPartySize(CancellationToken cancellationToken)
{
return await this.gameThreadService.QueueOnGameThread<uint>(() =>
{
unsafe
{
if (!this.gameContextService.GetGameContext().TryGetPlayerParty(out _, out var players, out var heroes, out var henchmen))
{
this.logger.LogError("Failed to get player party");
return 0;
}
return players.Value.Size + heroes.Value.Size + heroes.Value.Size;
}
}, cancellationToken);
}
public async Task<bool> SetHeroBehavior(uint heroAgentId, Shared.Models.Api.HeroBehavior behavior, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (!await this.IsInValidOutpost(cancellationToken))
{
scopedLogger.LogError("Could not set hero behavior. Not in a valid outpost");
return false;
}
var heroCommanderFrameLabel = await this.HeroCommanderFrameLabel(heroAgentId, cancellationToken);
if (heroCommanderFrameLabel is null)
{
scopedLogger.LogError("Could not set hero behavior. Could not find hero commander frame label for agent {heroAgentId}", heroAgentId);
return false;
}
var maybeFrame = await this.uiService.GetManagedFrame(heroCommanderFrameLabel, cancellationToken);
if (!maybeFrame.HasValue)
{
scopedLogger.LogError("Could not set hero behavior. Could not get managed frame for label {heroCommanderFrameLabel}", heroCommanderFrameLabel);
return false;
}
using var frame = maybeFrame.Value;
var result = await this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
var btn = this.uIContextService.GetChildFrame(frame.Frame, (uint)behavior);
var packet = new kMouseClick { MouseButton = 0, IsDoubleclick = 0 };
if (btn.IsNull)
{
scopedLogger.LogError("Could not set hero behavior. Could not find button for behavior {behavior} in frame {frameLabel}", behavior, heroCommanderFrameLabel);
return false;
}
return this.uIContextService.SendFrameUIMessage(btn, UIMessage.kMouseClick, &packet);
}
}, cancellationToken);
if (!result)
{
scopedLogger.LogError("Failed to send UI message to set hero behavior {behavior} for agent {heroAgentId}", behavior, heroAgentId);
}
scopedLogger.LogInformation("Set hero behavior {behavior} for agent {agentId}", behavior, heroAgentId);
return result;
}
private async Task<bool> IsInValidOutpost(CancellationToken cancellationToken)
{
return await this.gameThreadService.QueueOnGameThread(() =>
{
return this.instanceContextService.GetInstanceType() is InstanceType.Outpost;
}, cancellationToken);
}
private unsafe bool SpawnHeroes(PartyLoadout partyLoadout)
{
var scopedLogger = this.logger.CreateScopedLogger();
scopedLogger.LogDebug("Spawning {heroCount} heroes for party loadout", partyLoadout.Entries.AsValueEnumerable().Count(c => c.HeroId != 0));
foreach (var entry in partyLoadout.Entries.AsValueEnumerable().OrderBy(e => e.Build.Primary))
{
if (entry.HeroId != 0 &&
Hero.TryParse(entry.HeroId, out var hero))
{
scopedLogger.LogInformation("Adding hero [{heroId}] [{heroName}] with behavior {behavior}", entry.HeroId, hero.Name, entry.HeroBehavior);
this.partyContextService.AddHero((uint)entry.HeroId);
}
else
{
scopedLogger.LogWarning("Invalid hero entry in party loadout: {heroId}", entry.HeroId);
continue;
}
}
return true;
}
private unsafe bool ApplyBuilds(PartyLoadout partyLoadout)
{
var scopedLogger = this.logger.CreateScopedLogger();
scopedLogger.LogDebug("Applying builds");
if (!this.gameContextService.GetGameContext().TryGetPlayerParty(out _, out var players, out var heroes, out var henchmen))
{
scopedLogger.LogError("Failed to get player party");
return false;
}
if (!this.gameContextService.GetGameContext().TryGetBuildContext(out _, out _, out var professions, out var unlockedSkills))
{
scopedLogger.LogError("Failed to get build context");
return false;
}
if (!this.gameContextService.GetGameContext().TryGetPlayerId(out var playerId))
{
scopedLogger.LogError("Failed to get player id");
return false;
}
if (!this.gameContextService.GetGameContext().TryGetAccountContext(out var accountContext) ||
accountContext.IsNull)
{
scopedLogger.LogError("Failed to get account context");
return false;
}
var playerProfessionContext = professions.Value.AsValueEnumerable().FirstOrDefault(p => p.AgentId == playerId);
if (playerProfessionContext.AgentId != playerId)
{
scopedLogger.LogError("Failed to get player profession context for player id {playerId}", playerId);
return false;
}
var parsedEntries = partyLoadout.Entries.AsValueEnumerable()
.Select(entry =>
{
if (entry.HeroId is 0)
{
return (entry, playerId);
}
else
{
var hero = heroes.Value.AsValueEnumerable().FirstOrDefault(h => (int)h.HeroId == entry.HeroId);
return (entry, hero.AgentId);
}
})
.Select(t => (t.entry, t.playerId, professions.Value.AsValueEnumerable().FirstOrDefault(p => p.AgentId == t.playerId)))
.Select(t =>
{
var validSkills = t.playerId == playerId
? unlockedSkills
: accountContext.Pointer->UnlockedAccountSkills;
var unlockedProfessionsFlags = t.playerId == playerId
? playerProfessionContext.UnlockedProfessions
: uint.MaxValue;
if (!this.buildTemplateManager.CanTemplateApply(
new BuildTemplateValidationRequest(
(uint)t.entry.Build.Primary,
(uint)t.entry.Build.Secondary,
[.. t.entry.Build.Skills],
(uint)t.Item3.Primary,
unlockedProfessionsFlags,
[.. validSkills])))
{
return (t.entry, t.playerId, t.Item3, false);
}
return (t.entry, t.playerId, t.Item3, true);
})
.Where(t =>
{
if (!t.Item4)
{
if (t.entry.HeroId is 0)
{
scopedLogger.LogError("Cannot apply build for player");
}
else
{
scopedLogger.LogError("Cannot apply build for hero {heroId}", t.entry.HeroId);
}
}
return t.Item4;
});
foreach (var (entry, maybeAgentId, _, _) in parsedEntries)
{
if (maybeAgentId is not uint agentId)
{
continue;
}
var attributeIds = new AttributeArray12();
var attributeValues = new Array12Uint();
for (var i = 0; i < entry.Build.Attributes.Count && i < 12; i++)
{
attributeIds[i] = (GWCA.GW.Constants.Attribute)entry.Build.Attributes[i].Id;
attributeValues[i] = (uint)entry.Build.Attributes[i].BasePoints;
}
var skills = new SkillIDArray8();
for (var i = 0; i < entry.Build.Skills.Count && i < 8; i++)
{
skills[i] = (GWCA.GW.Constants.SkillID)entry.Build.Skills[i];
}
var skillTemplate = new SkillTemplate
{
Primary = (GWCA.GW.Constants.Profession)entry.Build.Primary,
Secondary = (GWCA.GW.Constants.Profession)entry.Build.Secondary,
AttributesCount = (uint)entry.Build.Attributes.Count,
AttributeIds = attributeIds,
Skills = skills
};
Unsafe.CopyBlock(skillTemplate.AttributeValues, Unsafe.AsPointer(ref attributeValues), (uint)(12 * sizeof(uint)));
scopedLogger.LogInformation("Applying build for agent {agentId} with primary {primary} and secondary {secondary}", agentId, entry.Build.Primary, entry.Build.Secondary);
this.skillbarContextService.LoadBuild(agentId, &skillTemplate);
}
return true;
}
private unsafe List<(uint AgentId, Shared.Models.Api.HeroBehavior Behavior)>? GetHeroBehaviorSetup(PartyLoadout partyLoadout)
{
var scopedLogger = this.logger.CreateScopedLogger();
scopedLogger.LogDebug("Getting hero behavior setup");
if (!this.gameContextService.GetGameContext().TryGetPlayerParty(out _, out _, out var heroes, out _) ||
!heroes.HasValue)
{
scopedLogger.LogError("Failed to get player party");
return default;
}
return heroes.Value.AsValueEnumerable()
.Select(h =>
{
if (h.HeroId is API.Interop.GWCA.GW.Constants.HeroID.NoHero)
{
return default;
}
var entry = partyLoadout.Entries.AsValueEnumerable().FirstOrDefault(e => e.HeroId == (int)h.HeroId);
if (entry is null)
{
scopedLogger.LogWarning("No entry found for hero {heroId}", h.HeroId);
return default;
}
return (h.AgentId, entry.HeroBehavior);
})
.Where(t => t.AgentId is not 0)
.ToList();
}
private async Task<string?> HeroCommanderFrameLabel(uint heroAgentId, CancellationToken cancellationToken)
{
var heroOffset = await this.GetHeroNumber(heroAgentId, cancellationToken);
if (heroOffset > 6)
{
return default;
}
return heroOffset switch
{
0 => "AgentCommander0",
1 => "AgentCommander1",
2 => "AgentCommander2",
3 => "AgentCommander3",
4 => "AgentCommander4",
5 => "AgentCommander5",
6 => "AgentCommander6",
_ => default
};
}
private async Task<uint?> GetHeroNumber(uint agentId, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
var heroNumber = await this.gameThreadService.QueueOnGameThread(() =>
{
unsafe
{
var gameContext = this.gameContextService.GetGameContext();
if (gameContext.IsNull)
{
scopedLogger.LogError("Game context is null");
return -1;
}
var charContext = gameContext.Pointer->Character;
if (charContext is null)
{
scopedLogger.LogError("Char context is null");
return -1;
}
var playerParty = GWCA.GW.PartyMgr.GetPartyInfo(0);
var playerId = charContext->PlayerNumber;
var offset = 0;
foreach(var hero in playerParty->Heroes)
{
if (hero.OwnerPlayerId == playerId)
{
if (hero.AgentId == agentId)
{
return offset;
}
offset++;
}
}
return -1;
}
}, cancellationToken);
return heroNumber is -1 ? default : (uint)heroNumber;
}
private static BuildEntry? GetBuildEntryById(ProfessionState professionContext, SkillbarData skillbar, PartyAttribute attributes)
{
return new BuildEntry(
Primary: (int)professionContext.Primary,
Secondary: (int)professionContext.Secondary,
Attributes: attributes.Attribute.GetAttributeEntryList().ToList(),
Skills:
[
(uint)skillbar.Skills[0].SkillId,
(uint)skillbar.Skills[1].SkillId,
(uint)skillbar.Skills[2].SkillId,
(uint)skillbar.Skills[3].SkillId,
(uint)skillbar.Skills[4].SkillId,
(uint)skillbar.Skills[5].SkillId,
(uint)skillbar.Skills[6].SkillId,
(uint)skillbar.Skills[7].SkillId
]);
}
}
+108
View File
@@ -0,0 +1,108 @@
using Daybreak.API.Interop;
using Daybreak.API.Interop.GuildWars;
using Daybreak.API.Services.Interop;
using System.Extensions.Core;
namespace Daybreak.API.Services;
public sealed class UIService(
GameThreadService gameThreadService,
UIContextService uIContextService,
ILogger<UIService> logger)
{
private readonly GameThreadService gameThreadService = gameThreadService;
private readonly UIContextService uIContextService = uIContextService;
private readonly ILogger<UIService> logger = logger;
public async Task<bool> Keypress(ControlAction action, WrappedPointer<Frame> frame, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
var keyDownResult = await this.gameThreadService.QueueOnGameThread(() =>
{
if (!this.uIContextService.KeyDown((GWCA.GW.UI.ControlAction)action, frame))
{
scopedLogger.LogError("Failed to send keydown action {action}", action);
return false;
}
return true;
}, cancellationToken);
if (!keyDownResult)
{
return false;
}
var keyUpResult = await this.gameThreadService.QueueOnGameThread(() =>
{
if (!this.uIContextService.KeyUp((GWCA.GW.UI.ControlAction)action, frame))
{
scopedLogger.LogError("Failed to send keyup action {action}", action);
return false;
}
return true;
}, cancellationToken);
if (!keyUpResult)
{
return false;
}
return true;
}
public async Task<ManagedFrame?> GetManagedFrame(string label, CancellationToken cancellationToken)
{
var scopedLogger = this.logger.CreateScopedLogger();
if (string.IsNullOrEmpty(label))
{
scopedLogger.LogError("Frame label is null or empty");
return null;
}
var frame = await this.gameThreadService.QueueOnGameThread(() => this.uIContextService.GetFrameByLabel(label), cancellationToken);
if (!frame.IsNull)
{
// Frame was already open, we do not want to auto-close it
scopedLogger.LogInformation("Frame with label {label} is already open", label);
return new ManagedFrame(frame, () => { });
}
var uiAction = GetUIActionFromFrameLabel(label);
await this.Keypress(uiAction, null, cancellationToken);
frame = await this.gameThreadService.QueueOnGameThread(() => this.uIContextService.GetFrameByLabel(label), cancellationToken);
await this.gameThreadService.QueueOnGameThread(() => this.uIContextService.SetFrameVisible(frame, true), cancellationToken);
return new ManagedFrame(frame, async () =>
{
await this.gameThreadService.QueueOnGameThread(async () =>
{
if (!this.uIContextService.GetFrameByLabel(label).IsNull)
{
await this.Keypress(uiAction, null, CancellationToken.None);
}
}, CancellationToken.None);
});
}
public async Task<string?> DecodeString(string encoded, GWCA.GW.Constants.Language language, CancellationToken cancellationToken)
{
// GWCA's AsyncDecodeStr handles language switching internally (sets language, decodes, restores)
return await this.uIContextService.AsyncDecodeStringAsync(encoded, language, cancellationToken);
}
private static ControlAction GetUIActionFromFrameLabel(string frameLabel)
{
return frameLabel switch
{
"AgentCommander0" => ControlAction.ControlAction_OpenHeroCommander1,
"AgentCommander1" => ControlAction.ControlAction_OpenHeroCommander2,
"AgentCommander2" => ControlAction.ControlAction_OpenHeroCommander3,
"AgentCommander3" => ControlAction.ControlAction_OpenHeroCommander4,
"AgentCommander4" => ControlAction.ControlAction_OpenHeroCommander5,
"AgentCommander5" => ControlAction.ControlAction_OpenHeroCommander6,
"AgentCommander6" => ControlAction.ControlAction_OpenHeroCommander7,
_ => ControlAction.ControlAction_None
};
}
}
@@ -0,0 +1,34 @@
using Daybreak.API.Services.Interop;
using Daybreak.Shared.Services.BuildTemplates;
using Daybreak.Shared.Services.BuildTemplates.Parsers;
namespace Daybreak.API.Services;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithDaybreakServices(this WebApplicationBuilder builder)
{
builder.Services.AddSingleton<ITemplateParser, LegacySkillTemplateParser>();
builder.Services.AddSingleton<ITemplateParser, SkillTemplateParser>();
builder.Services.AddSingleton<ITemplateParser, PartyLoadoutTemplateParser>();
builder.Services.AddSingleton<IBuildTemplateManager, BuildTemplateManager>();
builder.Services.AddSingleton<MemoryScanningService>();
builder.Services.AddSingleton<ChatService>();
builder.Services.AddSingleton<MainPlayerService>();
builder.Services.AddSingleton<CharacterSelectService>();
builder.Services.AddSingleton<PartyService>();
builder.Services.AddSingleton<UIService>();
builder.Services.AddSingleton<LoginService>();
builder.Services.AddSingleton<InventoryService>();
builder.Services.AddSingleton<GameThreadService>();
builder.Services.AddSingleton<SkillbarContextService>();
builder.Services.AddSingleton<GameContextService>();
builder.Services.AddSingleton<InstanceContextService>();
builder.Services.AddSingleton<AgentContextService>();
builder.Services.AddSingleton<PlatformContextService>();
builder.Services.AddSingleton<PartyContextService>();
builder.Services.AddSingleton<PreferencesService>();
builder.Services.AddSingleton<UIContextService>();
return builder;
}
}
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Routing.Constraints;
namespace Daybreak.API.Swagger;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithSwagger(this WebApplicationBuilder builder)
{
builder.Services.AddEndpointsApiExplorer();
builder.Services.Configure<RouteOptions>(static options =>
{
options.SetParameterPolicy<RegexInlineRouteConstraint>("regex");
});
builder.Services.AddOpenApi();
return builder;
}
}
@@ -0,0 +1,13 @@
using Scalar.AspNetCore;
namespace Daybreak.API.Swagger;
public static class WebApplicationExtensions
{
public static WebApplication UseSwaggerWithUI(this WebApplication app)
{
app.MapOpenApi();
app.MapScalarApiReference();
return app;
}
}
@@ -0,0 +1,46 @@
using Net.Sdk.Web.Websockets;
using System.Buffers;
using System.Net.WebSockets;
namespace Daybreak.API.WebSockets;
public abstract class UpdateWebSocketRoute
: WebSocketRouteBase
{
private readonly byte[] sharedBuffer;
private readonly ArraySegment<byte> packetBuffer;
private int sendingOperations = 0;
protected abstract int BufferSize { get; }
public UpdateWebSocketRoute()
{
this.sharedBuffer = ArrayPool<byte>.Shared.Rent(this.BufferSize);
this.packetBuffer = new ArraySegment<byte>(this.sharedBuffer);
}
public override Task SocketClosed()
{
ArrayPool<byte>.Shared.Return(this.sharedBuffer);
return base.SocketClosed();
}
public void SendUpdate(ReadOnlySpan<byte> state)
{
if (Interlocked.CompareExchange(ref this.sendingOperations, 1, 0) != 0)
{
return;
}
if (this.WebSocket is null)
{
this.sendingOperations = 0;
return;
}
state.CopyTo(this.packetBuffer);
var packet = this.packetBuffer.Slice(0, state.Length);
this.WebSocket?.SendAsync(packet, WebSocketMessageType.Binary, true, this.Context?.RequestAborted ?? CancellationToken.None)
.ContinueWith(_ => this.sendingOperations = 0);
}
}
@@ -0,0 +1,9 @@
namespace Daybreak.API.WebSockets;
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder WithWebSocketRoutes(this WebApplicationBuilder builder)
{
return builder;
}
}
@@ -0,0 +1,13 @@
using System.Diagnostics.CodeAnalysis;
namespace Daybreak.API.WebSockets;
public static class WebApplicationExtensions
{
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code")]
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.")]
public static WebApplication UseWebSocketRoutes(this WebApplication app)
{
return app;
}
}
+7
View File
@@ -0,0 +1,7 @@
<Directives>
<Application>
<Assembly Name="Microsoft.Extensions.Primitives">
<Type Name="Microsoft.Extensions.Primitives.StringValues" Dynamic="Required All" />
</Assembly>
</Application>
</Directives>
@@ -0,0 +1,11 @@
using Daybreak.Shared.Attributes;
using Daybreak.Shared.Models.Themes;
namespace Daybreak.Attributes;
public sealed class DaybreakThemeOptionsAttribute : OptionValuesFactoryAttribute
{
public override List<object> ValuesFactory()
{
return [.. Theme.Themes.Cast<object>()];
}
}
@@ -0,0 +1,14 @@
using Daybreak.Shared.Attributes;
using Daybreak.Shared.Configuration.Options;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "Ascalon Trade Chat")]
[OptionsIgnore]
[OptionsSynchronizationIgnore]
public sealed class AscalonTradeChatOptions : ITradeChatOptions
{
public string HttpsUri { get; set; } = "https://ascalon.gwtoolbox.com/";
public string WssUri { get; set; } = "wss://ascalon.gwtoolbox.com/";
}
@@ -0,0 +1,13 @@
using Daybreak.Shared.Attributes;
using Daybreak.Shared.Models;
using System.Text.Json.Serialization;
namespace Daybreak.Configuration.Options;
[OptionsIgnore]
[OptionsName(Name = "Credentials")]
internal sealed class CredentialManagerOptions
{
[JsonPropertyName(nameof(ProtectedLoginCredentials))]
public List<ProtectedLoginCredentials> ProtectedLoginCredentials { get; set; } = [];
}
@@ -0,0 +1,13 @@
using Daybreak.Shared.Attributes;
using System.Text.Json.Serialization;
namespace Daybreak.Configuration.Options;
[OptionsName(Name = "DSOAL")]
[OptionsIgnore]
internal sealed class DSOALOptions
{
[JsonPropertyName(nameof(Enabled))]
[OptionName(Name = "Enabled", Description = "If true, the launcher will also launch DSOAL when launching GuildWars")]
public bool Enabled { get; set; }
}

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