Conversation
Release/1.1.0
fix(ci): replace main with master in workflow branch filters and README
Release/1.1.0
Release/1.1.0
fix(ci): merge deploy-appcast into release.yml
* fix: avoid crash on Wine/Proton game directories in statistics * fix(statistics): avoid crash on inaccessible Proton prefix directories PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and IOException when enumerating game folders that contain a Proton prefix. The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /) that lead to /proc entries for dead processes. Fix: switch to a manual recursive helper that: - Skips the proton_pfx directory entirely (not part of the game) - Skips symlinks via AttributesToSkip = ReparsePoint - Ignores inaccessible entries (IgnoreInaccessible = true) - Catches any residual UnauthorizedAccessException / IOException per directory * test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(md5-utils): add tests for both ComputeMd5Hash overloads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(datetime-extensions): annotate Average and Sum parameters as nullable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(value-converters): add BooleanToIconConverter tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(value-converters): add tests for remaining 20 converters Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(axaml): replace deleted converters with direct bindings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [CodeFactor] Apply fixes --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: codefactor-io <support@codefactor.io> * fix(ci): skip tag creation if tag already exists Allows re-running the release pipeline without failing on duplicate tags. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: codefactor-io <support@codefactor.io>
* fix: avoid crash on Wine/Proton game directories in statistics * fix(statistics): avoid crash on inaccessible Proton prefix directories PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and IOException when enumerating game folders that contain a Proton prefix. The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /) that lead to /proc entries for dead processes. Fix: switch to a manual recursive helper that: - Skips the proton_pfx directory entirely (not part of the game) - Skips symlinks via AttributesToSkip = ReparsePoint - Ignores inaccessible entries (IgnoreInaccessible = true) - Catches any residual UnauthorizedAccessException / IOException per directory * test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(md5-utils): add tests for both ComputeMd5Hash overloads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(datetime-extensions): annotate Average and Sum parameters as nullable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(value-converters): add BooleanToIconConverter tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(value-converters): add tests for remaining 20 converters Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(axaml): replace deleted converters with direct bindings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [CodeFactor] Apply fixes --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: codefactor-io <support@codefactor.io> * fix(ci): skip tag creation if tag already exists Allows re-running the release pipeline without failing on duplicate tags. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): pass --repo to gh release download in deploy-appcast job The deploy-appcast job has no checkout step, so gh cannot infer the repository from the git context. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: codefactor-io <support@codefactor.io>
* fix: avoid crash on Wine/Proton game directories in statistics * fix(statistics): avoid crash on inaccessible Proton prefix directories PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and IOException when enumerating game folders that contain a Proton prefix. The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /) that lead to /proc entries for dead processes. Fix: switch to a manual recursive helper that: - Skips the proton_pfx directory entirely (not part of the game) - Skips symlinks via AttributesToSkip = ReparsePoint - Ignores inaccessible entries (IgnoreInaccessible = true) - Catches any residual UnauthorizedAccessException / IOException per directory * test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(md5-utils): add tests for both ComputeMd5Hash overloads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(datetime-extensions): annotate Average and Sum parameters as nullable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(value-converters): add BooleanToIconConverter tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(value-converters): add tests for remaining 20 converters Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(axaml): replace deleted converters with direct bindings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * [CodeFactor] Apply fixes --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: codefactor-io <support@codefactor.io> * fix(ci): skip tag creation if tag already exists Allows re-running the release pipeline without failing on duplicate tags. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): pass --repo to gh release download in deploy-appcast job The deploy-appcast job has no checkout step, so gh cannot infer the repository from the git context. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): add --public-key-override to appcast generation steps netsparkle-generate-appcast requires both private and public key to sign artifacts, even when using --private-key-override. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: codefactor-io <support@codefactor.io>
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): upload appcast.xml.signature alongside appcast.xml
NetSparkle in Strict mode verifies the appcast using the .signature
file — without it, the signature check fails and no updates are detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists (#76)
Allows re-running the release pipeline without failing on duplicate tags.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix deploy-appcast job
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Master (#85)
* Release/1.1.0 (#77)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix appcast-deploy job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#83)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#84)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): upload appcast.xml.signature alongside appcast.xml
NetSparkle in Strict mode verifies the appcast using the .signature
file — without it, the signature check fails and no updates are detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* refactor: migrate away from automapper
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update src/TombLauncher.Data/Mapping/GameLinkMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* Update src/TombLauncher/Mappers/LaunchOptionsMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* chore: align namespace and remove stale TombLauncher.Mapping usings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: remove AutoMapper configuration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* feat(compatibility): add Proton support and per-game launcher settings (#109)
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
# Conflicts:
# src/TombLauncher.Data/Database/Services/GameDataService.cs
# src/TombLauncher/App.axaml.cs
# src/TombLauncher/Extensions/ViewModelsServiceCollectionExtensions.cs
# src/TombLauncher/Factories/Mapping/MappingUtils.cs
# src/TombLauncher/Factories/Profiles/LaunchOptionsProfile.cs
# src/TombLauncher/Services/GameDetailsService.cs
# src/TombLauncher/Services/GameWithStatsService.cs
# src/TombLauncher/ViewModels/Dialogs/LaunchOptionsDialogViewModel.cs
# src/TombLauncher/ViewModels/Pages/GameDetailsViewModel.cs
# src/TombLauncher/Views/GameBar.axaml
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* fix(compatibility): wire per-game CompatibilityTool, EnvVars and prefix through all layers
- Add IEnvironmentVariable contract and EnvironmentVariableMapper (ToDto/ToEntity)
- Extend IGameMetadata, GameMetadataDto, Game entity with CompatibilityTool,
CompatibilityToolPath and ExtraEnvVars
- GameMapper: map new fields in both ToDto and ToGame; CompatibilityTool now
defaults to Unspecified in CompatibilityConfig so layered merge works correctly
- GameDataService.UpdateLaunchOptions: add missing SetupExecutable block, persist
ExtraEnvVars via EnvironmentVariableMapper.ToEntities, fix wrong executable added
when GameExecutable backup was absent
- Add .Include(g => g.EnvironmentVariables) to all EF queries that call GameMapper
- GameMetadataViewModel / LaunchOptionsViewModel: expose CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars; InitFromGame populates them from the game VM
- LaunchOptionsMapper.ToDto: map CompatibilityPrefixPath, CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars
- GameWithStatsService.LaunchProcess: select launcher from per-game CompatibilityTool
(falling back to global factory when Unspecified); pass ExtraEnvVars to context
- LayeredAppConfiguration: fix CompatibilityTool merge — check both User and Defaults
before falling back to Wine; add "CompatibilityTool": "Wine" to appsettings.json
- Register EnvironmentVariableMapper in DI; restore GetGameById on GameDataService
- Update GameLauncherTests to use GameLaunchContext; fix CompatibilityConfigTests using
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(mappers,data): add mapper unit tests and UpdateLaunchOptions integration tests
- EnvironmentVariableMapperTests: ToDto/ToEntity/ToDtos/ToEntities
- GameMapperTests: ToDto and ToGame for compatibility fields and env vars
- LaunchOptionsMapperTests: compatibility fields, ExtraEnvVars, optional executables
- UpdateLaunchOptionsTests (EF Core InMemory): CompatibilityTool/Path/Prefix
persistence, env var add/replace/clear, setup executable add/update/remove
- SearchMapperTests: TitlePic propagated through ToDto overloads
- Add Microsoft.EntityFrameworkCore.InMemory 10.0.2 to test project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(platform): add ExpandPath to IPlatformSpecificFeatures for tilde expansion
Linux expands leading ~ to $HOME; Windows returns the path unchanged.
GameWithStatsService applies ExpandPath before passing PrefixPath to the launcher,
so users can type ~/... in the UI and have it resolve correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add website link to sidebar navigation (#111)
Adds a link to tomblauncher.app in the main sidebar, alongside the existing GitHub link. Includes WebsiteLink in the config layer, ApplicationCoreSettings DTO, and both localization files (en-US / it-IT). Also moves CoreSettings.cs to the Dtos/Configuration subfolder.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add About page with acknowledgements (#110) (#112)
* feat(ui): add About page with app info and acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ui): extract SidebarNavigationItem control to reduce sidebar duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): populate About page acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(release): bump version to 1.2.0 (#113)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists (#76)
Allows re-running the release pipeline without failing on duplicate tags.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix deploy-appcast job
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Master (#85)
* Release/1.1.0 (#77)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix appcast-deploy job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#83)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#84)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): upload appcast.xml.signature alongside appcast.xml
NetSparkle in Strict mode verifies the appcast using the .signature
file — without it, the signature check fails and no updates are detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* refactor: migrate away from automapper
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update src/TombLauncher.Data/Mapping/GameLinkMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* Update src/TombLauncher/Mappers/LaunchOptionsMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* chore: align namespace and remove stale TombLauncher.Mapping usings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: remove AutoMapper configuration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* feat(compatibility): add Proton support and per-game launcher settings (#109)
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
# Conflicts:
# src/TombLauncher.Data/Database/Services/GameDataService.cs
# src/TombLauncher/App.axaml.cs
# src/TombLauncher/Extensions/ViewModelsServiceCollectionExtensions.cs
# src/TombLauncher/Factories/Mapping/MappingUtils.cs
# src/TombLauncher/Factories/Profiles/LaunchOptionsProfile.cs
# src/TombLauncher/Services/GameDetailsService.cs
# src/TombLauncher/Services/GameWithStatsService.cs
# src/TombLauncher/ViewModels/Dialogs/LaunchOptionsDialogViewModel.cs
# src/TombLauncher/ViewModels/Pages/GameDetailsViewModel.cs
# src/TombLauncher/Views/GameBar.axaml
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* fix(compatibility): wire per-game CompatibilityTool, EnvVars and prefix through all layers
- Add IEnvironmentVariable contract and EnvironmentVariableMapper (ToDto/ToEntity)
- Extend IGameMetadata, GameMetadataDto, Game entity with CompatibilityTool,
CompatibilityToolPath and ExtraEnvVars
- GameMapper: map new fields in both ToDto and ToGame; CompatibilityTool now
defaults to Unspecified in CompatibilityConfig so layered merge works correctly
- GameDataService.UpdateLaunchOptions: add missing SetupExecutable block, persist
ExtraEnvVars via EnvironmentVariableMapper.ToEntities, fix wrong executable added
when GameExecutable backup was absent
- Add .Include(g => g.EnvironmentVariables) to all EF queries that call GameMapper
- GameMetadataViewModel / LaunchOptionsViewModel: expose CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars; InitFromGame populates them from the game VM
- LaunchOptionsMapper.ToDto: map CompatibilityPrefixPath, CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars
- GameWithStatsService.LaunchProcess: select launcher from per-game CompatibilityTool
(falling back to global factory when Unspecified); pass ExtraEnvVars to context
- LayeredAppConfiguration: fix CompatibilityTool merge — check both User and Defaults
before falling back to Wine; add "CompatibilityTool": "Wine" to appsettings.json
- Register EnvironmentVariableMapper in DI; restore GetGameById on GameDataService
- Update GameLauncherTests to use GameLaunchContext; fix CompatibilityConfigTests using
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(mappers,data): add mapper unit tests and UpdateLaunchOptions integration tests
- EnvironmentVariableMapperTests: ToDto/ToEntity/ToDtos/ToEntities
- GameMapperTests: ToDto and ToGame for compatibility fields and env vars
- LaunchOptionsMapperTests: compatibility fields, ExtraEnvVars, optional executables
- UpdateLaunchOptionsTests (EF Core InMemory): CompatibilityTool/Path/Prefix
persistence, env var add/replace/clear, setup executable add/update/remove
- SearchMapperTests: TitlePic propagated through ToDto overloads
- Add Microsoft.EntityFrameworkCore.InMemory 10.0.2 to test project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(platform): add ExpandPath to IPlatformSpecificFeatures for tilde expansion
Linux expands leading ~ to $HOME; Windows returns the path unchanged.
GameWithStatsService applies ExpandPath before passing PrefixPath to the launcher,
so users can type ~/... in the UI and have it resolve correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add website link to sidebar navigation (#111)
Adds a link to tomblauncher.app in the main sidebar, alongside the existing GitHub link. Includes WebsiteLink in the config layer, ApplicationCoreSettings DTO, and both localization files (en-US / it-IT). Also moves CoreSettings.cs to the Dtos/Configuration subfolder.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add About page with acknowledgements (#110) (#112)
* feat(ui): add About page with app info and acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ui): extract SidebarNavigationItem control to reduce sidebar duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): populate About page acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(release): bump version to 1.2.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists (#76)
Allows re-running the release pipeline without failing on duplicate tags.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix deploy-appcast job
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Master (#85)
* Release/1.1.0 (#77)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix appcast-deploy job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#83)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#84)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): upload appcast.xml.signature alongside appcast.xml
NetSparkle in Strict mode verifies the appcast using the .signature
file — without it, the signature check fails and no updates are detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* refactor: migrate away from automapper
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update src/TombLauncher.Data/Mapping/GameLinkMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* Update src/TombLauncher/Mappers/LaunchOptionsMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* chore: align namespace and remove stale TombLauncher.Mapping usings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: remove AutoMapper configuration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* feat(compatibility): add Proton support and per-game launcher settings (#109)
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
# Conflicts:
# src/TombLauncher.Data/Database/Services/GameDataService.cs
# src/TombLauncher/App.axaml.cs
# src/TombLauncher/Extensions/ViewModelsServiceCollectionExtensions.cs
# src/TombLauncher/Factories/Mapping/MappingUtils.cs
# src/TombLauncher/Factories/Profiles/LaunchOptionsProfile.cs
# src/TombLauncher/Services/GameDetailsService.cs
# src/TombLauncher/Services/GameWithStatsService.cs
# src/TombLauncher/ViewModels/Dialogs/LaunchOptionsDialogViewModel.cs
# src/TombLauncher/ViewModels/Pages/GameDetailsViewModel.cs
# src/TombLauncher/Views/GameBar.axaml
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* fix(compatibility): wire per-game CompatibilityTool, EnvVars and prefix through all layers
- Add IEnvironmentVariable contract and EnvironmentVariableMapper (ToDto/ToEntity)
- Extend IGameMetadata, GameMetadataDto, Game entity with CompatibilityTool,
CompatibilityToolPath and ExtraEnvVars
- GameMapper: map new fields in both ToDto and ToGame; CompatibilityTool now
defaults to Unspecified in CompatibilityConfig so layered merge works correctly
- GameDataService.UpdateLaunchOptions: add missing SetupExecutable block, persist
ExtraEnvVars via EnvironmentVariableMapper.ToEntities, fix wrong executable added
when GameExecutable backup was absent
- Add .Include(g => g.EnvironmentVariables) to all EF queries that call GameMapper
- GameMetadataViewModel / LaunchOptionsViewModel: expose CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars; InitFromGame populates them from the game VM
- LaunchOptionsMapper.ToDto: map CompatibilityPrefixPath, CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars
- GameWithStatsService.LaunchProcess: select launcher from per-game CompatibilityTool
(falling back to global factory when Unspecified); pass ExtraEnvVars to context
- LayeredAppConfiguration: fix CompatibilityTool merge — check both User and Defaults
before falling back to Wine; add "CompatibilityTool": "Wine" to appsettings.json
- Register EnvironmentVariableMapper in DI; restore GetGameById on GameDataService
- Update GameLauncherTests to use GameLaunchContext; fix CompatibilityConfigTests using
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(mappers,data): add mapper unit tests and UpdateLaunchOptions integration tests
- EnvironmentVariableMapperTests: ToDto/ToEntity/ToDtos/ToEntities
- GameMapperTests: ToDto and ToGame for compatibility fields and env vars
- LaunchOptionsMapperTests: compatibility fields, ExtraEnvVars, optional executables
- UpdateLaunchOptionsTests (EF Core InMemory): CompatibilityTool/Path/Prefix
persistence, env var add/replace/clear, setup executable add/update/remove
- SearchMapperTests: TitlePic propagated through ToDto overloads
- Add Microsoft.EntityFrameworkCore.InMemory 10.0.2 to test project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(platform): add ExpandPath to IPlatformSpecificFeatures for tilde expansion
Linux expands leading ~ to $HOME; Windows returns the path unchanged.
GameWithStatsService applies ExpandPath before passing PrefixPath to the launcher,
so users can type ~/... in the UI and have it resolve correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add website link to sidebar navigation (#111)
Adds a link to tomblauncher.app in the main sidebar, alongside the existing GitHub link. Includes WebsiteLink in the config layer, ApplicationCoreSettings DTO, and both localization files (en-US / it-IT). Also moves CoreSettings.cs to the Dtos/Configuration subfolder.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add About page with acknowledgements (#110) (#112)
* feat(ui): add About page with app info and acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ui): extract SidebarNavigationItem control to reduce sidebar duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): populate About page acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(release): bump version to 1.2.0 (#113)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(patchers): add tombpc.dat gameflow reader and GameflowDto mapper (#116)
Introduces TombLauncher.Patchers with a complete pipeline for reading
TR2/TR3 tombpc.dat script files and converting them into a UI-friendly
GameflowDto.
- GameflowReader: binary reader for the full tombpc.dat structure
(header, counts, flags, XOR key, string arrays, sequences, item strings)
- GameflowMapper: converts Gameflow → GameflowDto, decoding XOR-encrypted
strings, splitting sequences per level, and mapping BAM angles to degrees
- Shared models: GameflowDto, LevelInfo, SequenceInfo, Tr2Item, Tr3Item
- TRX skeleton: TrxGameflow, Level, SequenceItem models and patcher stub
- Utilities: AngleExtensions (BAM↔degrees), SplitAt, FromGameTicks
- Tests: full reader coverage on two custom levels (Silver Machine TR2,
Escapade in Utah TR3) and mapper coverage
* docs(readme): add screenshots section from tomblauncher.app (#117)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ai): add AI-assisted troubleshooting (Laura)
Introduces an experimental AI chat feature ("Talk to Laura") backed by a
RAG pipeline, disabled by default and hidden from the sidebar when off.
Core AI pipeline
- RagService: streaming RAG with Semantic Kernel, per-request Kernel.Clone
for thread safety, documentation injected into user message (not system
prompt) to avoid accumulation across turns
- VectorSearchService: sqlite-vec vector search with distance threshold 0.20
- GameDiagnosticsPlugin: SK plugin exposing crash log, exit code and engine
info to the model via auto function calling
- TroubleshootingContextService: builds per-game context from the database
- Support for Ollama and LM Studio backends (OllamaBackendService,
LmStudioBackendService) with endpoint normalization and model listing
Settings UI
- AiSettingsView: backend picker, endpoint/embedding model health checks
with flashing indicator, dynamic model list via ComboBox, KB update button
- ServiceCheckView: reusable status indicator extracted from duplicated code
Chat UI
- AiChatView: streaming message bubbles, tool-use indicator, empty state,
auto-scroll, Enter to send; Laura menu item hidden when AI disabled
- Open chat from game details page with pre-loaded troubleshooting context
Knowledge base
- KnowledgeBase.Embedder: console tool that chunks markdown docs, generates
embeddings via OpenAI-compatible API and writes sqlite-vec DB
- SFTP upload (--upload flag): SHA-256 manifest, atomic rename on server,
private key or password auth
- KbUpdateService: client-side auto-update on startup — fetches manifest,
checks schema version and generatedAt, downloads + verifies + swaps DB
- CryptoUtils: unified MD5/SHA-256 helpers (renamed from Md5Utils)
Bug fixes
- Fix sync/async stream mix crash on game exit (BeginOutputReadLine +
ReadToEndAsync incompatibility)
- Fix IHttpClientFactory CI build failure (HintPath → PackageReference)
* feat(platform): add game support matrix (#89) (#122)
Introduces a per-OS engine support matrix to IPlatformSpecificFeatures:
- Add EngineSupportState enum (NoSupport, SupportedWithCompatibilityLayer,
NativePatchingAvailable, FullSupport)
- Add ISupportMatrix interface with IReadOnlyDictionary<GameEngine, EngineSupportState>
- Implement LinuxSupportMatrix, WindowsSupportMatrix, MacOsSupportMatrix
- Wire ISupportMatrix into IPlatformSpecificFeatures and both platform implementations
- Expose EngineSupportState in GameDetailsViewModel via GameDetailsService
- Show a status icon in GameBar with danger class for unsupported engines
- Add EngineSupportStateToIconConverter and EngineSupportStateToTooltipConverter
- Add localization strings for all four states (en-US, it-IT)
* fix(launch-options): populate executables correctly and handle IO errors (#124)
Use GetEnumerationOptions() (case-insensitive, skips reparse points) instead
of SearchOption.AllDirectories when scanning the install directory, fixing
controls not populated on page open. Catch IOException in LoadAsync and show
a localized error messagebox before navigating back. Remove orphan
GetGameLaunchStartInfo from LinuxPlatformSpecificFeatures.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ai): integrate support matrix into SK as a plugin (#125)
Add SupportMatrixPlugin exposing ISupportMatrix to the Semantic Kernel
so the AI can report engine support state during troubleshooting.
Adopt null-object pattern on TroubleshootingContext (IsSet sentinel) to
remove nullable parameters from plugin constructors and AskAsync.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(readme): document AI-assisted troubleshooting feature (Laura) (#… (#126)
docs(readme): document AI-assisted troubleshooting feature (Laura) (#121)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ai): correct remote KB manifest URL (#128)
The manifest file is at kb_manifest.json, not manifest.json.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(patchers): add widescreen patcher with backup/restore pipeline (#129)
* feat(patchers): port widescreen aspect ratio patcher from old branch
Brings over the widescreen patcher logic (contracts, patcher, test asset)
originally developed on feature/patchers, adjusted for the src/tests layout.
IPatcher.EngineDetector still references TombRaiderEngineDetector — needs
resolution before the project compiles (see issue #88).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(patchers): move EngineDetectorResult to Contracts, wire IEngineDetector
- EngineDetectorResult moved…
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists (#76)
Allows re-running the release pipeline without failing on duplicate tags.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix deploy-appcast job
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Master (#85)
* Release/1.1.0 (#77)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix appcast-deploy job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#83)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#84)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): upload appcast.xml.signature alongside appcast.xml
NetSparkle in Strict mode verifies the appcast using the .signature
file — without it, the signature check fails and no updates are detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* refactor: migrate away from automapper
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update src/TombLauncher.Data/Mapping/GameLinkMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* Update src/TombLauncher/Mappers/LaunchOptionsMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* chore: align namespace and remove stale TombLauncher.Mapping usings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: remove AutoMapper configuration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* feat(compatibility): add Proton support and per-game launcher settings (#109)
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
# Conflicts:
# src/TombLauncher.Data/Database/Services/GameDataService.cs
# src/TombLauncher/App.axaml.cs
# src/TombLauncher/Extensions/ViewModelsServiceCollectionExtensions.cs
# src/TombLauncher/Factories/Mapping/MappingUtils.cs
# src/TombLauncher/Factories/Profiles/LaunchOptionsProfile.cs
# src/TombLauncher/Services/GameDetailsService.cs
# src/TombLauncher/Services/GameWithStatsService.cs
# src/TombLauncher/ViewModels/Dialogs/LaunchOptionsDialogViewModel.cs
# src/TombLauncher/ViewModels/Pages/GameDetailsViewModel.cs
# src/TombLauncher/Views/GameBar.axaml
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* fix(compatibility): wire per-game CompatibilityTool, EnvVars and prefix through all layers
- Add IEnvironmentVariable contract and EnvironmentVariableMapper (ToDto/ToEntity)
- Extend IGameMetadata, GameMetadataDto, Game entity with CompatibilityTool,
CompatibilityToolPath and ExtraEnvVars
- GameMapper: map new fields in both ToDto and ToGame; CompatibilityTool now
defaults to Unspecified in CompatibilityConfig so layered merge works correctly
- GameDataService.UpdateLaunchOptions: add missing SetupExecutable block, persist
ExtraEnvVars via EnvironmentVariableMapper.ToEntities, fix wrong executable added
when GameExecutable backup was absent
- Add .Include(g => g.EnvironmentVariables) to all EF queries that call GameMapper
- GameMetadataViewModel / LaunchOptionsViewModel: expose CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars; InitFromGame populates them from the game VM
- LaunchOptionsMapper.ToDto: map CompatibilityPrefixPath, CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars
- GameWithStatsService.LaunchProcess: select launcher from per-game CompatibilityTool
(falling back to global factory when Unspecified); pass ExtraEnvVars to context
- LayeredAppConfiguration: fix CompatibilityTool merge — check both User and Defaults
before falling back to Wine; add "CompatibilityTool": "Wine" to appsettings.json
- Register EnvironmentVariableMapper in DI; restore GetGameById on GameDataService
- Update GameLauncherTests to use GameLaunchContext; fix CompatibilityConfigTests using
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(mappers,data): add mapper unit tests and UpdateLaunchOptions integration tests
- EnvironmentVariableMapperTests: ToDto/ToEntity/ToDtos/ToEntities
- GameMapperTests: ToDto and ToGame for compatibility fields and env vars
- LaunchOptionsMapperTests: compatibility fields, ExtraEnvVars, optional executables
- UpdateLaunchOptionsTests (EF Core InMemory): CompatibilityTool/Path/Prefix
persistence, env var add/replace/clear, setup executable add/update/remove
- SearchMapperTests: TitlePic propagated through ToDto overloads
- Add Microsoft.EntityFrameworkCore.InMemory 10.0.2 to test project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(platform): add ExpandPath to IPlatformSpecificFeatures for tilde expansion
Linux expands leading ~ to $HOME; Windows returns the path unchanged.
GameWithStatsService applies ExpandPath before passing PrefixPath to the launcher,
so users can type ~/... in the UI and have it resolve correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add website link to sidebar navigation (#111)
Adds a link to tomblauncher.app in the main sidebar, alongside the existing GitHub link. Includes WebsiteLink in the config layer, ApplicationCoreSettings DTO, and both localization files (en-US / it-IT). Also moves CoreSettings.cs to the Dtos/Configuration subfolder.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add About page with acknowledgements (#110) (#112)
* feat(ui): add About page with app info and acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ui): extract SidebarNavigationItem control to reduce sidebar duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): populate About page acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(release): bump version to 1.2.0 (#113)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(patchers): add tombpc.dat gameflow reader and GameflowDto mapper (#116)
Introduces TombLauncher.Patchers with a complete pipeline for reading
TR2/TR3 tombpc.dat script files and converting them into a UI-friendly
GameflowDto.
- GameflowReader: binary reader for the full tombpc.dat structure
(header, counts, flags, XOR key, string arrays, sequences, item strings)
- GameflowMapper: converts Gameflow → GameflowDto, decoding XOR-encrypted
strings, splitting sequences per level, and mapping BAM angles to degrees
- Shared models: GameflowDto, LevelInfo, SequenceInfo, Tr2Item, Tr3Item
- TRX skeleton: TrxGameflow, Level, SequenceItem models and patcher stub
- Utilities: AngleExtensions (BAM↔degrees), SplitAt, FromGameTicks
- Tests: full reader coverage on two custom levels (Silver Machine TR2,
Escapade in Utah TR3) and mapper coverage
* docs(readme): add screenshots section from tomblauncher.app (#117)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ai): add AI-assisted troubleshooting (Laura)
Introduces an experimental AI chat feature ("Talk to Laura") backed by a
RAG pipeline, disabled by default and hidden from the sidebar when off.
Core AI pipeline
- RagService: streaming RAG with Semantic Kernel, per-request Kernel.Clone
for thread safety, documentation injected into user message (not system
prompt) to avoid accumulation across turns
- VectorSearchService: sqlite-vec vector search with distance threshold 0.20
- GameDiagnosticsPlugin: SK plugin exposing crash log, exit code and engine
info to the model via auto function calling
- TroubleshootingContextService: builds per-game context from the database
- Support for Ollama and LM Studio backends (OllamaBackendService,
LmStudioBackendService) with endpoint normalization and model listing
Settings UI
- AiSettingsView: backend picker, endpoint/embedding model health checks
with flashing indicator, dynamic model list via ComboBox, KB update button
- ServiceCheckView: reusable status indicator extracted from duplicated code
Chat UI
- AiChatView: streaming message bubbles, tool-use indicator, empty state,
auto-scroll, Enter to send; Laura menu item hidden when AI disabled
- Open chat from game details page with pre-loaded troubleshooting context
Knowledge base
- KnowledgeBase.Embedder: console tool that chunks markdown docs, generates
embeddings via OpenAI-compatible API and writes sqlite-vec DB
- SFTP upload (--upload flag): SHA-256 manifest, atomic rename on server,
private key or password auth
- KbUpdateService: client-side auto-update on startup — fetches manifest,
checks schema version and generatedAt, downloads + verifies + swaps DB
- CryptoUtils: unified MD5/SHA-256 helpers (renamed from Md5Utils)
Bug fixes
- Fix sync/async stream mix crash on game exit (BeginOutputReadLine +
ReadToEndAsync incompatibility)
- Fix IHttpClientFactory CI build failure (HintPath → PackageReference)
* feat(platform): add game support matrix (#89) (#122)
Introduces a per-OS engine support matrix to IPlatformSpecificFeatures:
- Add EngineSupportState enum (NoSupport, SupportedWithCompatibilityLayer,
NativePatchingAvailable, FullSupport)
- Add ISupportMatrix interface with IReadOnlyDictionary<GameEngine, EngineSupportState>
- Implement LinuxSupportMatrix, WindowsSupportMatrix, MacOsSupportMatrix
- Wire ISupportMatrix into IPlatformSpecificFeatures and both platform implementations
- Expose EngineSupportState in GameDetailsViewModel via GameDetailsService
- Show a status icon in GameBar with danger class for unsupported engines
- Add EngineSupportStateToIconConverter and EngineSupportStateToTooltipConverter
- Add localization strings for all four states (en-US, it-IT)
* fix(launch-options): populate executables correctly and handle IO errors (#124)
Use GetEnumerationOptions() (case-insensitive, skips reparse points) instead
of SearchOption.AllDirectories when scanning the install directory, fixing
controls not populated on page open. Catch IOException in LoadAsync and show
a localized error messagebox before navigating back. Remove orphan
GetGameLaunchStartInfo from LinuxPlatformSpecificFeatures.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ai): integrate support matrix into SK as a plugin (#125)
Add SupportMatrixPlugin exposing ISupportMatrix to the Semantic Kernel
so the AI can report engine support state during troubleshooting.
Adopt null-object pattern on TroubleshootingContext (IsSet sentinel) to
remove nullable parameters from plugin constructors and AskAsync.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(readme): document AI-assisted troubleshooting feature (Laura) (#… (#126)
docs(readme): document AI-assisted troubleshooting feature (Laura) (#121)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ai): correct remote KB manifest URL (#128)
The manifest file is at kb_manifest.json, not manifest.json.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(patchers): add widescreen patcher with backup/restore pipeline (#129)
* feat(patchers): port widescreen aspect ratio patcher from old branch
Brings over the widescreen patcher logic (contracts, patcher, test asset)
originally developed on feature/patchers, adjusted for the src/tests layout.
IPatcher.EngineDetector still references TombRaiderEngineDetector — needs
resolution before the project compiles (see issue #88).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(patchers): move EngineDetectorResult to Contracts, wire IEngineDetector
- EngineDetectorResult moved…
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists (#76)
Allows re-running the release pipeline without failing on duplicate tags.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix deploy-appcast job
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Master (#85)
* Release/1.1.0 (#77)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix appcast-deploy job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): fix deploy-appcast job
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#83)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* Release/1.1.0 (#84)
* fix: avoid crash on Wine/Proton game directories in statistics
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* test(path-utils): add tests for symlink, proton_pfx skip, and inaccessible directory handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(datetime-extensions): add tests for DateTimeExtensions and DayOfWeekCultureSensitiveComparer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(md5-utils): add tests for both ComputeMd5Hash overloads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datetime-extensions): annotate Average and Sum parameters as nullable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add BooleanToIconConverter tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(value-converters): remove unused converters and fix BooleanToStringConverter.ConvertBack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(value-converters): add tests for remaining 20 converters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(axaml): replace deleted converters with direct bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(tier-2): add tests for SavegameHeaderReader, Tr1xSavegameHeaderReader, ZipManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [CodeFactor] Apply fixes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* fix(ci): skip tag creation if tag already exists
Allows re-running the release pipeline without failing on duplicate tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): pass --repo to gh release download in deploy-appcast job
The deploy-appcast job has no checkout step, so gh cannot infer the
repository from the git context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): add --public-key-override to appcast generation steps
netsparkle-generate-appcast requires both private and public key to
sign artifacts, even when using --private-key-override.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix AppImage filename and remove space from portable ZIP name
- AppImage: add package release suffix (-1) to match PupNet output
(tomb-launcher-{version}-1.x86_64.AppImage)
- Portable ZIP: use hyphens instead of spaces to avoid glob and shell
quoting issues throughout the pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): replace placeholder AppDescription in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): fix PublisherLinkUrl in pupnet.conf
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(deploy): clean up pupnet.conf metadata
- SetupMinWindowsVersion: 6.2 -> 10 (.NET 10 requires Windows 10+)
- PublisherLinkName: use display name instead of URL
- SetupCommandPrompt: clear (GUI app, no CLI entry needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ci): use bash array for appcast common args
Reduces code duplication and avoids long backslash continuation chains
that may cause parsing issues on certain runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): fix scp port flag and ssh-keyscan port for non-standard SSH port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): upload appcast.xml.signature alongside appcast.xml
NetSparkle in Strict mode verifies the appcast using the .signature
file — without it, the signature check fails and no updates are detected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codefactor-io <support@codefactor.io>
* refactor: migrate away from automapper
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(deps): add Riok.Mapperly 4.3.1
Initial step of the AutoMapper → Mapperly migration tracked on this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: remove unused PlaySessionDto
PlaySessionDto and its two consumer methods on PlaySessionDataService
(GetLastPlaySession, GetPlaySessionsByGameId) were not called by anyone.
Drops one mapping pair from the AutoMapper → Mapperly migration scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(deps): add Riok.Mapperly 4.3.1"
This reverts commit 31847b590b1841fa7fecc38db8440d5348eea7ea.
* refactor(mapping): switch language mapping from AutoMapper to manual
First step of the migration to manual mapping. AvailableLanguageDto ↔
ApplicationLanguageViewModel is now handled by a SettingsMapper instance
class (registered as DI singleton) instead of AutoMapper. The two unused
dead methods on SettingsPageService (GetApplicationLanguage,
GetApplicationTheme) are dropped along the way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): complete SettingsMapper migration
Migrates DownloaderConfiguration ↔ DownloaderViewModel and
UnzipBackendDto ↔ UnzipBackendViewModel to manual mapping in
SettingsMapper. SettingsProfile is now empty and deleted.
DownloaderSettingsViewModel drops MapperConfiguration injection
in favour of SettingsMapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate AppCrash mapping to manual mapper
Introduces TombLauncher.Data/Mapping/ as the location for entity↔DTO
mappers. AppCrashMapper lives there, keeping entities contained within
the data layer. AppCrashProfile deleted, SettingsPageViewModel drops
its MapperConfiguration dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate statistics mapping to manual mapper
Introduces StatisticsMapper for the three statistics mappings.
DailyStatisticsAverageGameDurationViewModel renamed to
DailyGameDurationViewModel (used for both average and total),
its AverageGameDuration property renamed to GameDuration.
DailyStatisticsViewModel deleted (superseded by DailyGameDurationViewModel).
StatisticsProfile retains only the SavegameViewModel mapping (Fase 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mapping): migrate GameLink mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameHash mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate Game and GameMetadata mapping to manual mappers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate DownloaderSearchPayload mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate LaunchOptions mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate GameWithStats mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate search result mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(mapping): migrate savegame and FileBackup mapping to manual mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md to reflect manual mapper convention
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove stale TombLauncher.Mapping using directive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update src/TombLauncher.Data/Mapping/GameLinkMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* Update src/TombLauncher/Mappers/LaunchOptionsMapper.cs
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* chore: align namespace and remove stale TombLauncher.Mapping usings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: remove AutoMapper configuration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
* feat(compatibility): add Proton support and per-game launcher settings (#109)
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(statistics): avoid crash on inaccessible Proton prefix directories
PathUtils.GetDirectorySize crashed with UnauthorizedAccessException and
IOException when enumerating game folders that contain a Proton prefix.
The prefix includes root-owned subdirs and symlinks (e.g. dosdevices/z: -> /)
that lead to /proc entries for dead processes.
Fix: switch to a manual recursive helper that:
- Skips the proton_pfx directory entirely (not part of the game)
- Skips symlinks via AttributesToSkip = ReparsePoint
- Ignores inaccessible entries (IgnoreInaccessible = true)
- Catches any residual UnauthorizedAccessException / IOException per directory
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* feat(settings,launch-options): add Wine compatibility section and convert launch options to page
- New Compatibility settings section (WinePath, WinePrefix) visible on Linux only
- Wine detection at startup: WINE env var -> WINEPATH env var -> PATH scan
- Warning notification (GobletBrokenLine) if Wine not found
- LaunchOptionsDialogView/ViewModel replaced by LaunchOptionsView/ViewModel (PageViewModel)
- LaunchOptions promoted to direct button in GameBar (TerminalLine icon)
- Per-game WinePrefix override in LaunchOptionsView, visible on Linux only
- WinePrefix added to LaunchOptionsDto, Game entity and UpdateLaunchOptions
- DB migration: AddGameWinePrefix
- SettingsPageService.PersistCurrentConfigAsync() extracted as reusable method
- GameWithStatsService: WinePath read from IAppConfiguration.Compatibility (no hardcoded fallback)
# Conflicts:
# src/TombLauncher.Data/Database/Services/GameDataService.cs
# src/TombLauncher/App.axaml.cs
# src/TombLauncher/Extensions/ViewModelsServiceCollectionExtensions.cs
# src/TombLauncher/Factories/Mapping/MappingUtils.cs
# src/TombLauncher/Factories/Profiles/LaunchOptionsProfile.cs
# src/TombLauncher/Services/GameDetailsService.cs
# src/TombLauncher/Services/GameWithStatsService.cs
# src/TombLauncher/ViewModels/Dialogs/LaunchOptionsDialogViewModel.cs
# src/TombLauncher/ViewModels/Pages/GameDetailsViewModel.cs
# src/TombLauncher/Views/GameBar.axaml
* docs: fix add-page workflow view naming convention (View suffix, Pages/ folder)
* loc: simplify Wine-related UI strings for non-technical users
* style(ui): use text-muted class on LabeledTextBox label
* feat(ui): add CardSectionHeader control to LaunchOptionsView
- New CardSectionHeader control in TombLauncher.Controls (IconKind + Title)
- LaunchOptionsView: icon+title header on each card
(PlayLargeLine/Launch, HammerLine/Setup, GroupLine/CommunityPatch, Goblet2Line/Compatibility)
- Add GENERAL and COMMUNITY_PATCH localization keys (en-US, it-IT)
- Community patch tooltip: 'some versions of TRX' instead of 'TRX'
- LabeledTextBox label now uses text-muted class
* fix(ui,navigation): sticky save button in LaunchOptionsView; fix GameWithStats refresh on back
- LaunchOptionsView: restructured with Grid (ScrollViewer row * + buttons row Auto)
Save button now uses IconButton btn-primary/Save3Line, always visible outside scroll area
- Fix: GameWithStatsViewModel data not refreshing when navigating back from LaunchOptions
* fix(data): rewrite UpdateLaunchOptions using EF change tracker
Replace ExecuteUpdateAsync/ExecuteDeleteAsync (bulk ops that bypass EF tracking)
with a single tracked ToListAsync query + direct property assignment.
This ensures GetGameById returns fresh data after saving launch options,
fixing the stale executable path shown in GameDetailsViewModel on back navigation.
* feat(stats,settings): implement Wine version detection
- IPlatformSpecificFeatures: add GetWineVersion(string winePath)
- LinuxPlatformSpecificFeatures: runs wine --version, captures stdout
- WindowsPlatformSpecificFeatures: returns null (Wine not supported)
- CompatibilitySettingsViewModel: WineVersion auto-refreshed via OnWinePathChanged
- CompatibilitySettingsView: read-only Wine version row below WINEPREFIX
- StatisticsPageViewModel: WineVersion populated on init via IAppConfiguration.Compatibility.WinePath
- StatisticsPageView: row 7 with Goblet2Line icon, visible only on Linux (IsWineSupported)
- Localization: WINE_VERSION added in en-US and it-IT
* fix(linux): wrap wine launch in bash + wineserver -w for correct process tracking
wine exits immediately after spawning the Windows process into wineserver,
causing play time tracking and save file monitoring to stop prematurely.
Wrapping with 'bash -c "wine ...; wineserver -w"' keeps the monitored process
alive until wineserver shuts down (i.e. until the game truly exits).
* feat(proton): add Proton launcher support with compatibility settings
- Add ProtonGameLauncher: sets STEAM_COMPAT_DATA_PATH (with default),
STEAM_COMPAT_INSTALL_PATH, STEAM_COMPAT_CLIENT_INSTALL_PATH (derived from
Proton binary path), STEAM_APPID=0; strips NVIDIA PRIME vars inherited from
parent process that break Wine rendering; uses bash login shell (-lc)
- Add WineGameLauncher, WindowsGameLauncher, IGameLauncher abstractions
- Add ProtonInstallationDto, CompatibilityTool enum
- Add CompatibilityToolToIconConverter
- LinuxPlatformSpecificFeatures: FindAvailableProtonInstallations(),
CollectSteamRoots() (parses libraryfolders.vdf), GetProtonVersion()
- CompatibilitySettingsView/ViewModel: Wine/Proton tool selector, Proton
installation ComboBox with auto-detect from steamapps, manual fallback,
STEAM_COMPAT_DATA_PATH override, Proton version display
- GameWithStatsService: capture stdout/stderr on non-zero exit (Linux only)
- SavegameCardView: split MARK_UNMARK key into MARK_/UNMARK_ for better i18n,
use BooleanToStringConverter for context-aware tooltip
- Localization: WINE_VERSION, MARK_AS_START_OF_LEVEL_SAVE,
UNMARK_AS_START_OF_LEVEL_SAVE added in en-US and it-IT
- Add unit tests for CompatibilityConfig, GameLauncher, LinuxPlatformFeatures
* refactor(settings): remove WinePath from GameDetails config
WinePath was migrated to CompatibilityConfig in a previous session.
Now that the one-time migration path in App.axaml.cs is no longer
needed, remove the residual property from IGameDetailsConfig,
GameDetailsConfig, LayeredAppConfiguration, GameDetailsCoreSettings
and its construction in SettingsProvider.
* fix(settings): compatibility tool change now takes effect without restart
IGameLauncher is now resolved on each game launch via Func<IGameLauncher>
instead of being captured at construction time in GameWithStatsService.
Also refactors InitializeServices into a fluent chain of IServiceCollection
extension methods, each with a single responsibility:
- AddAppConfiguration: loads JSON layers, registers ILayeredAppConfiguration/IAppConfiguration
- AddPlatformSpecificFeatures: registers IPlatformSpecificFeatures, IGameLauncher and its factory
- AddSaveGameManagement: savegame header provider, processor and query/command services
- AddTheming, AddFileOperations, AddLocalization, AddNavigation: focused singletons
- AddGameManagement: installer, engine detector, game merger
- AddPopups: IPopupService
- AddDownloaders: HTTP clients, downloader implementations, download manager
- AddNotifications, AddUpdater: notification and update services
- AddDatabaseAccess now resolves IAppConfiguration from sp internally
* refactor(app): move CheckCompatibilityToolAsync to AppUtils
Eliminates the private wrapper method in App, calling AppUtils directly
from ShowMainWindow. Unused usings (IconPacks, Configuration, Sections)
removed from App.axaml.cs.
* refactor(app): move ApplyInitialSettings to AppUtils
App.axaml.cs now delegates both startup helpers to AppUtils,
leaving it with only wiring/orchestration logic.
* style(app): use unqualified ChangeTheme call within AppUtils
* refactor(app): move OnUnhandledException to AppUtils
Registered directly as AppUtils.OnUnhandledException on the Dispatcher
event — no disadvantages vs instance method in this context (singleton
event source, no instance capture needed).
Removed unused usings from App.axaml.cs.
* refactor(navigation): move INavigationTarget and ITopBarCommand to Contracts, introduce BusyScope pattern
- Move INavigationTarget and ITopBarCommand from TombLauncher.Core.Navigation to TombLauncher.Contracts.Navigation
- ITopBarCommand.Icon changed from PackIconRemixIconKind to Enum to remove icon library dependency from Contracts
- CommandViewModel.Icon likewise changed to Enum
- INavigationTarget simplified: SetBusy(bool,string) removed from public interface, BusyScope(string) introduced as the primary RAII pattern
- SetBusy(string) kept/added for fire-and-forget cases (PlayGame, LaunchSetup, LaunchCommunitySetup)
- GameWithStatsService, StatisticsPageViewModel, GameListViewModel, SavegameListViewModel refactored to BusyScope pattern
- async void Uninstall fixed to async Task
- Unused usings and IoC.Default references removed
* fix(ui): fix BusyMessage not visible in RingLoader overlay
- RingLoader: move Width/Height constraint from UserControl to inner Viewbox,
so the message TextBlock below the ring is no longer clipped to 80x80 bounds
- RingLoader: restore RelativeSource binding (was working, just clipped)
- GameListViewModel: add missing .GetLocalizedString() on FETCHING_GAMES key
- MainWindow: remove redundant debug TextBlock for BusyMessage
* refactor(compatibility): move CompatibilityTool enum to TombLauncher.Contracts
Moved CompatibilityTool from TombLauncher.Configuration.Sections to
TombLauncher.Contracts.Enums, alongside the other shared enums.
This removes the circular dependency that would have blocked adding
CompatibilityTool? fields to GameMetadataDto (TombLauncher.Core) and
the Game entity (TombLauncher.Data), both of which cannot reference
the main TombLauncher project.
Updated all consumers:
- CompatibilityConfig.cs, ICompatibilityConfig.cs
- LayeredAppConfiguration.cs
- PlatformSpecificFeaturesCollectionExtensions.cs
- CompatibilitySettingsViewModel.cs
- CompatibilityToolToIconConverter.cs
- AppUtils.cs
- CompatibilitySettingsView.axaml (xmlns:cfg alias)
* refactor(compatibility): introduce GameLaunchContext, per-game CompatibilityTool and ExtraEnvVars
- IGameLauncher.GetLaunchStartInfo now takes GameLaunchContext instead of 4 loose params
- GameLaunchContext carries ExecutableFileName, Arguments, WorkingDirectory, PrefixPath, ExtraEnvVars
- Wine/Proton/Windows launchers apply ExtraEnvVars last (per-game overrides)
- Rename WinePrefix -> CompatibilityPrefixPath across config, DTOs, models, ViewModels and Views
- Add CompatibilityTool (int) and CompatibilityToolPath to Game entity and DTOs
- Add GameEnvironmentVariable entity + EnvironmentVariableDto
- Add migration AddPerGameCompatibilitySettings
- LaunchOptionsProfile: map CompatibilityPrefixPath
* fix(compatibility): wire per-game CompatibilityTool, EnvVars and prefix through all layers
- Add IEnvironmentVariable contract and EnvironmentVariableMapper (ToDto/ToEntity)
- Extend IGameMetadata, GameMetadataDto, Game entity with CompatibilityTool,
CompatibilityToolPath and ExtraEnvVars
- GameMapper: map new fields in both ToDto and ToGame; CompatibilityTool now
defaults to Unspecified in CompatibilityConfig so layered merge works correctly
- GameDataService.UpdateLaunchOptions: add missing SetupExecutable block, persist
ExtraEnvVars via EnvironmentVariableMapper.ToEntities, fix wrong executable added
when GameExecutable backup was absent
- Add .Include(g => g.EnvironmentVariables) to all EF queries that call GameMapper
- GameMetadataViewModel / LaunchOptionsViewModel: expose CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars; InitFromGame populates them from the game VM
- LaunchOptionsMapper.ToDto: map CompatibilityPrefixPath, CompatibilityTool,
CompatibilityToolPath, ExtraEnvVars
- GameWithStatsService.LaunchProcess: select launcher from per-game CompatibilityTool
(falling back to global factory when Unspecified); pass ExtraEnvVars to context
- LayeredAppConfiguration: fix CompatibilityTool merge — check both User and Defaults
before falling back to Wine; add "CompatibilityTool": "Wine" to appsettings.json
- Register EnvironmentVariableMapper in DI; restore GetGameById on GameDataService
- Update GameLauncherTests to use GameLaunchContext; fix CompatibilityConfigTests using
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(mappers,data): add mapper unit tests and UpdateLaunchOptions integration tests
- EnvironmentVariableMapperTests: ToDto/ToEntity/ToDtos/ToEntities
- GameMapperTests: ToDto and ToGame for compatibility fields and env vars
- LaunchOptionsMapperTests: compatibility fields, ExtraEnvVars, optional executables
- UpdateLaunchOptionsTests (EF Core InMemory): CompatibilityTool/Path/Prefix
persistence, env var add/replace/clear, setup executable add/update/remove
- SearchMapperTests: TitlePic propagated through ToDto overloads
- Add Microsoft.EntityFrameworkCore.InMemory 10.0.2 to test project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(platform): add ExpandPath to IPlatformSpecificFeatures for tilde expansion
Linux expands leading ~ to $HOME; Windows returns the path unchanged.
GameWithStatsService applies ExpandPath before passing PrefixPath to the launcher,
so users can type ~/... in the UI and have it resolve correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add website link to sidebar navigation (#111)
Adds a link to tomblauncher.app in the main sidebar, alongside the existing GitHub link. Includes WebsiteLink in the config layer, ApplicationCoreSettings DTO, and both localization files (en-US / it-IT). Also moves CoreSettings.cs to the Dtos/Configuration subfolder.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add About page with acknowledgements (#110) (#112)
* feat(ui): add About page with app info and acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ui): extract SidebarNavigationItem control to reduce sidebar duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): populate About page acknowledgements section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(release): bump version to 1.2.0 (#113)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(patchers): add tombpc.dat gameflow reader and GameflowDto mapper (#116)
Introduces TombLauncher.Patchers with a complete pipeline for reading
TR2/TR3 tombpc.dat script files and converting them into a UI-friendly
GameflowDto.
- GameflowReader: binary reader for the full tombpc.dat structure
(header, counts, flags, XOR key, string arrays, sequences, item strings)
- GameflowMapper: converts Gameflow → GameflowDto, decoding XOR-encrypted
strings, splitting sequences per level, and mapping BAM angles to degrees
- Shared models: GameflowDto, LevelInfo, SequenceInfo, Tr2Item, Tr3Item
- TRX skeleton: TrxGameflow, Level, SequenceItem models and patcher stub
- Utilities: AngleExtensions (BAM↔degrees), SplitAt, FromGameTicks
- Tests: full reader coverage on two custom levels (Silver Machine TR2,
Escapade in Utah TR3) and mapper coverage
* docs(readme): add screenshots section from tomblauncher.app (#117)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ai): add AI-assisted troubleshooting (Laura)
Introduces an experimental AI chat feature ("Talk to Laura") backed by a
RAG pipeline, disabled by default and hidden from the sidebar when off.
Core AI pipeline
- RagService: streaming RAG with Semantic Kernel, per-request Kernel.Clone
for thread safety, documentation injected into user message (not system
prompt) to avoid accumulation across turns
- VectorSearchService: sqlite-vec vector search with distance threshold 0.20
- GameDiagnosticsPlugin: SK plugin exposing crash log, exit code and engine
info to the model via auto function calling
- TroubleshootingContextService: builds per-game context from the database
- Support for Ollama and LM Studio backends (OllamaBackendService,
LmStudioBackendService) with endpoint normalization and model listing
Settings UI
- AiSettingsView: backend picker, endpoint/embedding model health checks
with flashing indicator, dynamic model list via ComboBox, KB update button
- ServiceCheckView: reusable status indicator extracted from duplicated code
Chat UI
- AiChatView: streaming message bubbles, tool-use indicator, empty state,
auto-scroll, Enter to send; Laura menu item hidden when AI disabled
- Open chat from game details page with pre-loaded troubleshooting context
Knowledge base
- KnowledgeBase.Embedder: console tool that chunks markdown docs, generates
embeddings via OpenAI-compatible API and writes sqlite-vec DB
- SFTP upload (--upload flag): SHA-256 manifest, atomic rename on server,
private key or password auth
- KbUpdateService: client-side auto-update on startup — fetches manifest,
checks schema version and generatedAt, downloads + verifies + swaps DB
- CryptoUtils: unified MD5/SHA-256 helpers (renamed from Md5Utils)
Bug fixes
- Fix sync/async stream mix crash on game exit (BeginOutputReadLine +
ReadToEndAsync incompatibility)
- Fix IHttpClientFactory CI build failure (HintPath → PackageReference)
* feat(platform): add game support matrix (#89) (#122)
Introduces a per-OS engine support matrix to IPlatformSpecificFeatures:
- Add EngineSupportState enum (NoSupport, SupportedWithCompatibilityLayer,
NativePatchingAvailable, FullSupport)
- Add ISupportMatrix interface with IReadOnlyDictionary<GameEngine, EngineSupportState>
- Implement LinuxSupportMatrix, WindowsSupportMatrix, MacOsSupportMatrix
- Wire ISupportMatrix into IPlatformSpecificFeatures and both platform implementations
- Expose EngineSupportState in GameDetailsViewModel via GameDetailsService
- Show a status icon in GameBar with danger class for unsupported engines
- Add EngineSupportStateToIconConverter and EngineSupportStateToTooltipConverter
- Add localization strings for all four states (en-US, it-IT)
* fix(launch-options): populate executables correctly and handle IO errors (#124)
Use GetEnumerationOptions() (case-insensitive, skips reparse points) instead
of SearchOption.AllDirectories when scanning the install directory, fixing
controls not populated on page open. Catch IOException in LoadAsync and show
a localized error messagebox before navigating back. Remove orphan
GetGameLaunchStartInfo from LinuxPlatformSpecificFeatures.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ai): integrate support matrix into SK as a plugin (#125)
Add SupportMatrixPlugin exposing ISupportMatrix to the Semantic Kernel
so the AI can report engine support state during troubleshooting.
Adopt null-object pattern on TroubleshootingContext (IsSet sentinel) to
remove nullable parameters from plugin constructors and AskAsync.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(readme): document AI-assisted troubleshooting feature (Laura) (#… (#126)
docs(readme): document AI-assisted troubleshooting feature (Laura) (#121)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ai): correct remote KB manifest URL (#128)
The manifest file is at kb_manifest.json, not manifest.json.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(patchers): add widescreen patcher with backup/restore pipeline (#129)
* feat(patchers): port widescreen aspect ratio patcher from old branch
Brings over the widescreen patcher logic (contracts, patcher, test asset)
originally developed on feature/patchers, adjusted for the src/tests layout.
IPatcher.EngineDetector still references TombRaiderEngineDetector — needs
resolution before the project compiles (see issue #88).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(patchers): move EngineDetectorResult to Contracts, wire IEngineDetector
- EngineDetectorResult move…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.