diff --git a/.github/workflows/codecov-analytics.yml b/.github/workflows/codecov-analytics.yml index 959abc1e..427ed318 100644 --- a/.github/workflows/codecov-analytics.yml +++ b/.github/workflows/codecov-analytics.yml @@ -25,5 +25,6 @@ jobs: sha: ${{ github.event.pull_request.head.sha || github.sha }} platform_repository: Prekzursil/quality-zero-platform platform_ref: main + runner: windows-latest secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/quality-zero-platform.yml b/.github/workflows/quality-zero-platform.yml index a99c1ac6..d8f641ca 100644 --- a/.github/workflows/quality-zero-platform.yml +++ b/.github/workflows/quality-zero-platform.yml @@ -37,3 +37,6 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} DEEPSCAN_API_TOKEN: ${{ secrets.DEEPSCAN_API_TOKEN }} + CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }} + CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }} diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 7687011c..dc13442c 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -48,11 +48,11 @@ jobs: env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - dotnet-sonarscanner begin \ - /k:"Prekzursil_SWFOC-Mod-Menu" \ - /o:"prekzursil" \ - /d:sonar.token="$SONAR_TOKEN" \ - /d:sonar.host.url="https://sonarcloud.io" + SONAR_ARGS="/k:Prekzursil_SWFOC-Mod-Menu /o:prekzursil /d:sonar.token=$SONAR_TOKEN /d:sonar.host.url=https://sonarcloud.io" + if [ "${{ github.event_name }}" = "pull_request" ]; then + SONAR_ARGS="$SONAR_ARGS /d:sonar.pullrequest.key=${{ github.event.pull_request.number }} /d:sonar.pullrequest.branch=${{ github.head_ref }} /d:sonar.pullrequest.base=${{ github.base_ref }}" + fi + dotnet-sonarscanner begin $SONAR_ARGS - name: Build if: ${{ secrets.SONAR_TOKEN != '' }} diff --git a/.qlty/qlty.toml b/.qlty/qlty.toml index f8f468d2..15224bf4 100644 --- a/.qlty/qlty.toml +++ b/.qlty/qlty.toml @@ -6,3 +6,11 @@ default = true [smells] mode = "block" + +[[smells.exclude]] +path = "tests/**" +reason = "Test files use repetitive patterns by design — duplicated setup/assert blocks are expected in xUnit test suites" + +[[smells.exclude]] +path = "tools/**" +reason = "Standalone Python scripts are utility tools — complexity thresholds are less actionable here" diff --git a/docs/re_integration_plan.md b/docs/re_integration_plan.md new file mode 100644 index 00000000..6a23a1b1 --- /dev/null +++ b/docs/re_integration_plan.md @@ -0,0 +1,227 @@ +# RE Integration Plan: swfoc_memory -> SWFOC Editor + +**Date:** 2026-04-04 +**Source:** Phase 1-2 RE knowledge base in `C:\Users\Prekzursil\Downloads\swfoc_memory` +**Target:** SWFOC Editor at `C:\Users\Prekzursil\Downloads\SWFOC editor` (Prekzursil/SWFOC-Mod-Menu) + +--- + +## Editor Architecture Summary + +The editor uses a **signature-first symbol resolution** pipeline: + +1. **Profile JSON** (`profiles/default/profiles/base_swfoc.json`) defines: + - `signatureSets` — AOB patterns with `addressMode` (HitPlusOffset, ReadRipRelative32AtOffset, ReadAbsolute32AtOffset) + - `fallbackOffsets` — hardcoded RVAs when signatures fail + - `actions` — named operations referencing resolved symbols + +2. **SignatureResolver** scans the live process module for AOB matches, resolves addresses via RIP-relative or absolute addressing, and builds a `SymbolMap`. + +3. **Ghidra Symbol Pack** hydration — `SignatureResolver.SymbolHydration.cs` loads JSON symbol packs from `profiles/default/sdk/ghidra/symbol-packs/`. Format: + ```json + { + "SchemaVersion": "1.0", + "BinaryFingerprint": { "FingerprintId": "_" }, + "BuildMetadata": { "GeneratedAtUtc": "..." }, + "Anchors": [ + { "Id": "", "Address": , "Confidence": 1.0 } + ] + } + ``` + These bypass AOB scanning entirely — direct RVA-based resolution with binary fingerprint validation. + +4. **Actions** execute via `executionKind`: `Memory` (direct read/write), `Sdk` (runtime hook), `Helper` (Lua/pipe bridge), `CodePatch` (byte patching), `Freeze` (continuous write), `Save` (save file edit). + +--- + +## Feature-by-Feature Integration Analysis + +### TIER 1: Now Unblockable (Phase 2 provides the missing data) + +#### 1. `set_credits` — **MISMATCH DETECTED** + +**Current state:** Profile has signature `credits` with AOB `8B 0D ?? ?? ?? ?? 41 B8 0C 00 00 00 48 8B 14 C8`, addressMode `ReadRipRelative32AtOffset`, valueType `Int32`, fallback offset `10882632` (0xA62148). + +**RE finding:** Credits are at `PlayerObject + 0x70` as **float32**, not Int32. The current signature resolves to a *static global* that mirrors the selected player's credits for UI display — NOT the authoritative player object field. + +**Mismatch:** The editor writes Int32 to a UI-facing mirror variable. This works for display but may not persist through game events. The authoritative path is: +- `PlayerArray (global 0xA16FF0)` -> index by player ID -> `PlayerObject + 0x70` (float32) +- Or call `AddCredits` at RVA `0x27F370` + +**Recommendation:** **Keep the existing approach for now** (it works). Add a new `set_credits_authoritative` action that reads PlayerArray + writes float to player+0x70. This is a moderate change. + +**Complexity:** Moderate (needs pointer chain traversal: global -> array -> player -> field) + +#### 2. `set_selected_speed` — **MISMATCH DETECTED** + +**Current state:** Signature `selected_speed` with AOB pointing to a UI-mirrored static variable, valueType `Float`. + +**RE finding:** Speed override is at `GameObjectClass + 0xA8 -> locomotor + 0x29C` (flag) + `+0x2A0` (float value). The native `Override_Max_Speed` Lua function or `SetSpeedOverride` at `0x3A8C90` is the proper path. + +**Mismatch:** Editor writes a UI-mirror float. RE shows the engine reads speed from the locomotor component, not a static. The UI mirror may work for display but the engine ignores it for actual movement. + +**Recommendation:** Add new action `set_speed_override` using pointer chain: selected_object -> +0xA8 -> locomotor -> +0x29C (set 1) + +0x2A0 (write speed). Or hook `SetSpeedOverride` at `0x3A8C90`. + +**Complexity:** Moderate (pointer chain through selected object) + +#### 3. `set_hero_respawn_timer` — **VALIDATED** + +**Current state:** Has signature `hero_respawn_timer` with AOB, fallback offset `1384560` (0x152070). + +**RE finding:** `Default_Hero_Respawn_Time` global at RVA `0xB169F0` (11495920 decimal). The `ScheduleHeroRespawn` function reads this when delay<=0. + +**Mismatch:** Fallback offset `0x152070` does NOT match RE finding `0xB169F0`. **The current offset appears wrong.** Needs validation. + +**Recommendation:** Update fallback offset to `11495920` (0xB169F0). Verify AOB pattern still matches. + +**Complexity:** Trivial (offset update in profile JSON) + +#### 4. `toggle_tactical_god_mode` / `toggle_tactical_one_hit_mode` — **VALIDATED** + +**Current state:** Working via AOB-resolved Bool writes. Already marked as `experimentalFeatures` in profile. + +**RE finding:** The invulnerability flag is at `GameObjectClass + 0x3A7`, confirmed in Phase 1. The god mode AOB likely patches the `Take_Damage_Outer` check. + +**Status:** Already working. Phase 2 confirms the mechanism. Can be promoted from experimental to stable. + +**Complexity:** Trivial (remove from `experimentalFeatures` list) + +### TIER 2: Quick Wins (Profile JSON changes only) + +#### 5. `set_tech_level` — **NEW ACTION NEEDED** + +**Current state:** No `set_tech_level` action exists in profile. Not in `signatureSets`. + +**RE finding:** Tech level at `PlayerObject + 0x84` (int32). `SetTechLevel` at RVA `0x288980`. Lua `Set_Tech_Level` at `0x604480`. + +**Recommendation:** Add new signature for `SetTechLevel` function prologue (AOB in `signatures_phase2.json`). Add action `set_tech_level` of kind `Memory` writing Int32 to resolved player+0x84. Or kind `Helper` calling Lua `Set_Tech_Level`. + +**Complexity:** Moderate (new action + signature + pointer chain to player object) + +#### 6. `income_multiplier` — **NEW ACTION NEEDED** + +**RE finding:** Income multiplier at `FUN_1404B0500() + 0x20`. Applied by `AddCredits` for positive values only. + +**Recommendation:** Add signature for the multiplier-source function, add action `set_income_multiplier` writing Float to resolved address + 0x20. + +**Complexity:** Moderate + +#### 7. `set_max_credits` — **NEW ACTION POSSIBLE** + +**RE finding:** `PlayerObject + 0x74` (float32). Set to negative to disable cap. + +**Complexity:** Same as credits — pointer chain through PlayerArray. + +### TIER 3: Features Requiring New Capabilities + +#### 8. Ability Triggering — **MAPPED BUT COMPLEX** + +**RE finding:** 91 ability classes recovered. Each has `vfunction2` (Activate). Triggering requires: get unit -> get ability list -> find specific ability -> call vfunction2. + +**Recommendation:** This needs a Helper execution path. Either inject via Lua (if ability has a Lua binding) or via native vtable call injection. + +**Complexity:** Complex (needs ability enumeration + vtable call injection) + +#### 9. `spawn_unit_helper` — **VALIDATED** + +**Current state:** Action exists with `executionKind: Helper`. Uses `helperHookId`. + +**RE finding:** `Spawn_Unit` Lua function at `0x898C28`, `Galactic_Spawn_Unit` implementation at `0x546C70`. 40 global Lua functions mapped. + +**Status:** The helper bridge likely already calls Spawn_Unit via Lua pipe. RE confirms the backing implementation exists and is stable. + +**Complexity:** Trivial (existing path validated) + +#### 10. `place_planet_building` — **MAPPED** + +**Current state:** Action exists with `executionKind: Helper`. + +**RE finding:** Planet garrison list at `planet + 0x978`, tech requirement at `planet + 0x89C`. Building placement likely goes through `Galactic_Spawn_Unit` with building type + planet target. + +**Status:** Helper path exists. RE data can improve error handling (check tech level before attempt). + +**Complexity:** Low (existing path, RE improves validation) + +--- + +## Mismatches & Corrections + +| Item | Current Value | RE Finding | Impact | +|------|--------------|-----------|--------| +| Credits type | Int32 | **float32** (PlayerObject+0x70) | Write works but type is wrong — may cause truncation | +| Credits fallback | 0xA62148 | PlayerObject+0x70 via PlayerArray (0xA16FF0) | Current path writes a UI mirror, not the authoritative field | +| Hero respawn fallback | 0x152070 | **0xB169F0** | **Likely wrong offset — needs immediate correction** | +| Speed path | Static mirror | Locomotor component +0x29C/0x2A0 | Current write doesn't affect engine movement calculation | +| Set_Hull Lua path | Assumed available | **Does NOT exist** as Lua function | Helper layer cannot use Lua for HP writes | + +--- + +## Quick-Win Ranking (effort-to-impact ratio) + +| Rank | Change | Effort | Impact | What to Do | +|------|--------|--------|--------|------------| +| 1 | Fix hero_respawn_timer fallback | 5 min | High | Change `1384560` to `11495920` in profile JSON | +| 2 | Promote god_mode/one_hit from experimental | 5 min | Medium | Remove from `experimentalFeatures` in profile JSON | +| 3 | Add tech_level signature + fallback | 30 min | High | New signature entry + new action in profile JSON | +| 4 | Add credits_authoritative action | 1 hr | High | New action using PlayerArray pointer chain | +| 5 | Add speed_override action | 1 hr | High | New action using locomotor pointer chain | +| 6 | Add max_credits action | 30 min | Medium | PlayerObject+0x74 float write (same chain as credits) | +| 7 | Add income_multiplier action | 1 hr | Medium | New signature + float write | +| 8 | Generate Ghidra Symbol Pack | 30 min | High | Convert all known RVAs to GhidraAnchorDto format | + +--- + +## Ghidra Symbol Pack Generation + +The editor's `SignatureResolver.SymbolHydration` system can consume a JSON symbol pack that provides **direct RVA-based symbol resolution** — bypassing AOB scanning entirely. This is the ideal delivery format for RE-derived knowledge. + +### Required Format + +```json +{ + "SchemaVersion": "1.0", + "BinaryFingerprint": { + "FingerprintId": "starwarsg.exe_" + }, + "BuildMetadata": { + "GeneratedAtUtc": "2026-04-04T00:00:00Z" + }, + "Anchors": [ + { "Id": "credits", "Address": , "Confidence": 1.0 }, + { "Id": "tech_level", "Address": , "Confidence": 1.0 }, + ... + ] +} +``` + +### Gap + +The current `GhidraAnchorDto` expects `Address` as an integer (absolute address at runtime). But our RE findings are **RVAs** (relative to image base). The adapter needs to add `image_base + RVA` to produce the absolute address. Since image base varies per launch (ASLR), the symbol pack should store the **module-relative offset** and the hydration code should add the runtime base address. + +**Looking at the code:** `TryBuildAnchorSymbol` in `SignatureResolver.SymbolHydration.cs` — need to verify if Address is treated as absolute or module-relative. If absolute, the pack needs regeneration per launch (impractical). If module-relative, we can generate it once. + +### Recommendation + +Check how `GhidraAnchorDto.Address` is consumed. If it's added to module base, generate a static pack. If it's used as-is, we need the fallbackOffsets path instead (which already supports module-relative offsets). + +--- + +## Still Blocked (Needs Phase 3 RE) + +| Feature | What's Missing | Phase 3 Work | +|---------|---------------|--------------| +| Struct-aware object enumeration | Need PooledObjectClass allocator understanding | Decompile PooledObjectClass vtable | +| Per-ability cooldown manipulation | Need ability countdown data pack field offsets | Decompile AbilityCountdownDataPackClass | +| Damage scaling (not just on/off) | Need damage multiplier field in combat component | Decompile damage path from Take_Damage to final calculation | +| Planet building slot manipulation | Need building slot struct layout | Decompile planet building management functions | +| Faction diplomacy changes | Need alliance/enemy relationship storage | Decompile FactionClass and alliance system | +| Save file hero state mapping | Need exact save format field mapping per mod | Validate schema against RE-derived struct layouts | + +--- + +## Files to Generate for Editor + +1. **`re_integration_plan.md`** — this document +2. **`ghidra_symbol_pack.json`** — Ghidra Symbol Pack for current binary +3. **`signatures_phase2_editor_format.json`** — Phase 2 signatures in editor profile format +4. **Updated `base_swfoc.json`** — corrected fallbacks + new actions (NOT in this deliverable — planning only) diff --git a/profiles/default/profiles/base_swfoc.json b/profiles/default/profiles/base_swfoc.json index 090cf094..d425e4f2 100644 --- a/profiles/default/profiles/base_swfoc.json +++ b/profiles/default/profiles/base_swfoc.json @@ -16,9 +16,7 @@ "hostPreference": "starwarsg_preferred", "experimentalFeatures": [ "game_speed", - "planet_owner", - "tactical_god_mode", - "tactical_one_hit_mode" + "planet_owner" ], "signatureSets": [ { @@ -42,7 +40,10 @@ { "name": "planet_owner", "pattern": "89 35 ?? ?? ?? ?? 48 C7 05 ?? ?? ?? ?? ?? ?? ?? ??", "offset": 2, "addressMode": "ReadRipRelative32AtOffset", "valueType": "Int32" }, { "name": "hero_respawn_timer", "pattern": "8B 35 ?? ?? ?? ?? 4C 8B 3D EE B5 10 00 48 B8 FF", "offset": 2, "addressMode": "ReadRipRelative32AtOffset", "valueType": "Int32" }, { "name": "unit_cap", "pattern": "48 8B 74 24 68 8B C7", "offset": 0, "addressMode": "HitPlusOffset", "valueType": "Byte" }, - { "name": "game_speed", "pattern": "F3 0F 11 05 ?? ?? ?? ?? C3 CC CC CC CC CC CC CC F3 0F 10 05", "offset": 4, "addressMode": "ReadRipRelative32AtOffset", "valueType": "Float" } + { "name": "game_speed", "pattern": "F3 0F 11 05 ?? ?? ?? ?? C3 CC CC CC CC CC CC CC F3 0F 10 05", "offset": 4, "addressMode": "ReadRipRelative32AtOffset", "valueType": "Float" }, + { "name": "add_credits_func", "pattern": "48 89 5C 24 08 57 48 83 EC 40 0F 29 74 24 30 41 0F B6 F8 0F 29 7C 24 20 0F 28 F1 0F 57 FF 48 8B D9", "offset": 0, "addressMode": "HitPlusOffset", "valueType": "Int32" }, + { "name": "set_tech_level_func", "pattern": "56 57 41 54 48 83 EC 40 8B C2 48 89 5C 24 60 48 8B F9 4C 89 7C 24 30 45 33 E4", "offset": 0, "addressMode": "HitPlusOffset", "valueType": "Int32" }, + { "name": "set_speed_override_func", "pattern": "48 8B 81 A8 00 00 00 48 85 C0 74 ?? 89 90 A0 02 00 00 C6 80 9C 02 00 00 01", "offset": 0, "addressMode": "HitPlusOffset", "valueType": "Int32" } ] } ], @@ -62,9 +63,16 @@ "tactical_god_mode": 1377912, "tactical_one_hit_mode": 1418854, "planet_owner": 1523152, - "hero_respawn_timer": 1384560, + "hero_respawn_timer": 11495920, "unit_cap": 2804047, - "game_speed": 0 + "game_speed": 0, + "player_array_global": 10579952, + "player_count_global": 10579960, + "default_hero_respawn_time_global": 11495920, + "add_credits_func": 2618224, + "set_tech_level_func": 2689408, + "set_speed_override_func": 3837072, + "clear_speed_override_func": 3733168 }, "actions": { "read_symbol": { @@ -89,7 +97,7 @@ }, "verifyReadback": true, "cooldownMs": 250, - "description": "Set player credits through managed memory lane (authoritative default path)." + "description": "Set player credits through managed memory lane. NOTE: authoritative path is PlayerObject+0x70 (float32) via PlayerArray global at RVA 0xA16FF0. Current symbol resolves to UI mirror (Int32). See re_integration_plan.md for migration path." }, "set_credits_extender_experimental": { "id": "set_credits_extender_experimental", @@ -221,7 +229,7 @@ }, "verifyReadback": true, "cooldownMs": 80, - "description": "Set selected unit hull HP" + "description": "Set selected unit hull HP (native hook only — no Set_Hull Lua binding exists; uses SetHP at RVA 0x3A89D0 writing float to GameObjectClass+0x5C)" }, "set_selected_shield": { "id": "set_selected_shield", @@ -245,7 +253,7 @@ }, "verifyReadback": true, "cooldownMs": 80, - "description": "Set selected unit speed" + "description": "Set selected unit speed. NOTE: current symbol resolves to UI display mirror. Authoritative path is locomotor component at GameObjectClass+0xA8 -> +0x29C (flag=1) + +0x2A0 (float speed). See re_integration_plan.md." }, "set_selected_damage_multiplier": { "id": "set_selected_damage_multiplier", diff --git a/profiles/default/sdk/re-knowledge/signatures_phase2_editor_format.json b/profiles/default/sdk/re-knowledge/signatures_phase2_editor_format.json new file mode 100644 index 00000000..1ee7c777 --- /dev/null +++ b/profiles/default/sdk/re-knowledge/signatures_phase2_editor_format.json @@ -0,0 +1,118 @@ +{ + "_meta": { + "generated": "2026-04-03T23:07:20.713606+00:00", + "source": "swfoc_memory Phase 2 RE findings", + "target": "SWFOC Editor profile system", + "format": "Editor-compatible signature and fallback definitions" + }, + "new_signatures_for_profile": [ + { + "name": "add_credits_func", + "pattern": "48 89 5C 24 08 57 48 83 EC 40 0F 29 74 24 30 41 0F B6 F8 0F 29 7C 24 20 0F 28 F1 0F 57 FF 48 8B D9", + "offset": 0, + "addressMode": "HitPlusOffset", + "valueType": "Int32", + "description": "AddCredits function prologue (player+0x70 credits, float)" + }, + { + "name": "set_tech_level_func", + "pattern": "56 57 41 54 48 83 EC 40 8B C2 48 89 5C 24 60 48 8B F9 4C 89 7C 24 30 45 33 E4", + "offset": 0, + "addressMode": "HitPlusOffset", + "valueType": "Int32", + "description": "SetTechLevel function prologue (player+0x84 tech, int32)" + }, + { + "name": "set_speed_override_func", + "pattern": "48 8B 81 A8 00 00 00 48 85 C0 74 ?? 89 90 A0 02 00 00 C6 80 9C 02 00 00 01", + "offset": 0, + "addressMode": "HitPlusOffset", + "valueType": "Int32", + "description": "SetSpeedOverride (obj+0xA8->locomotor, +0x29C flag, +0x2A0 speed float)" + } + ], + "new_fallback_offsets": { + "add_credits_func": 2618224, + "set_tech_level_func": 2656640, + "set_speed_override_func": 3837072, + "clear_speed_override_func": 3733680, + "schedule_hero_respawn_func": 4778768, + "player_array_global": 10579952, + "player_count_global": 10579960, + "default_hero_respawn_time": 11626992 + }, + "corrections": { + "hero_respawn_timer": { + "old_value": 1384560, + "new_value": 11626992, + "reason": "RE Phase 2 found Default_Hero_Respawn_Time at RVA 0xB169F0, not 0x152070" + } + }, + "field_offsets_reference": { + "PlayerObject": { + "credits": { + "offset": "0x70", + "type": "float32", + "access": "PlayerArray[player_id]+0x70" + }, + "max_credits": { + "offset": "0x74", + "type": "float32" + }, + "tech_level": { + "offset": "0x84", + "type": "int32" + }, + "max_tech_level": { + "offset": "0x88", + "type": "int32" + } + }, + "GameObjectClass": { + "hp": { + "offset": "0x5C", + "type": "float32" + }, + "owner_player_id": { + "offset": "0x58", + "type": "int32" + }, + "locomotor_ptr": { + "offset": "0xA8", + "type": "pointer" + }, + "game_object_type_ptr": { + "offset": "0x298", + "type": "pointer" + }, + "invulnerability_flag": { + "offset": "0x3A7", + "type": "uint8" + } + }, + "LocomotorComponent": { + "speed_override_enabled": { + "offset": "0x29C", + "type": "uint8" + }, + "speed_override_value": { + "offset": "0x2A0", + "type": "float32" + } + }, + "Globals": { + "PlayerArray": { + "rva": "0xA16FF0", + "decimal": 10579952 + }, + "PlayerCount": { + "rva": "0xA16FF8", + "decimal": 10579960 + }, + "Default_Hero_Respawn_Time": { + "rva": "0xB169F0", + "decimal": 11495920 + } + } + } +} \ No newline at end of file diff --git a/src/SwfocTrainer.App/Infrastructure/AsyncCommand.cs b/src/SwfocTrainer.App/Infrastructure/AsyncCommand.cs index aa63b669..5753616a 100644 --- a/src/SwfocTrainer.App/Infrastructure/AsyncCommand.cs +++ b/src/SwfocTrainer.App/Infrastructure/AsyncCommand.cs @@ -17,6 +17,7 @@ public AsyncCommand(Func execute) public AsyncCommand(Func execute, Func? canExecute) { + ArgumentNullException.ThrowIfNull(execute); _execute = execute; _canExecute = canExecute; } diff --git a/src/SwfocTrainer.App/MainWindow.xaml.cs b/src/SwfocTrainer.App/MainWindow.xaml.cs index 90fe6f2b..2c4cc2af 100644 --- a/src/SwfocTrainer.App/MainWindow.xaml.cs +++ b/src/SwfocTrainer.App/MainWindow.xaml.cs @@ -8,11 +8,16 @@ public partial class MainWindow : Window { public MainWindow(MainViewModel viewModel) { + ArgumentNullException.ThrowIfNull(viewModel); InitializeComponent(); DataContext = viewModel; Loaded += (_, _) => { - var vm = (MainViewModel)DataContext; + if (DataContext is not MainViewModel vm) + { + return; + } + vm.LoadProfilesCommand.Execute(null); vm.LoadHotkeysCommand.Execute(null); }; diff --git a/src/SwfocTrainer.App/Models/HotkeyBindingItem.cs b/src/SwfocTrainer.App/Models/HotkeyBindingItem.cs index 18166d95..1ff70c92 100644 --- a/src/SwfocTrainer.App/Models/HotkeyBindingItem.cs +++ b/src/SwfocTrainer.App/Models/HotkeyBindingItem.cs @@ -11,12 +11,13 @@ public string Gesture get => _gesture; set { - if (_gesture == value) + var safeValue = value ?? string.Empty; + if (_gesture == safeValue) { return; } - _gesture = value; + _gesture = safeValue; } } @@ -25,12 +26,13 @@ public string ActionId get => _actionId; set { - if (_actionId == value) + var safeValue = value ?? string.Empty; + if (_actionId == safeValue) { return; } - _actionId = value; + _actionId = safeValue; } } @@ -39,12 +41,13 @@ public string PayloadJson get => _payloadJson; set { - if (_payloadJson == value) + var safeValue = value ?? "{}"; + if (_payloadJson == safeValue) { return; } - _payloadJson = value; + _payloadJson = safeValue; } } } diff --git a/src/SwfocTrainer.App/Program.cs b/src/SwfocTrainer.App/Program.cs index 35a849f6..c3fbdac5 100644 --- a/src/SwfocTrainer.App/Program.cs +++ b/src/SwfocTrainer.App/Program.cs @@ -39,6 +39,7 @@ private static void Main() private static void ConfigureServices(IServiceCollection services) { + ArgumentNullException.ThrowIfNull(services); var appData = TrustedPathPolicy.GetOrCreateAppDataRoot(); var profilesRoot = Path.Combine(AppContext.BaseDirectory, "profiles", "default"); var remoteManifest = Environment.GetEnvironmentVariable("SWFOC_PROFILE_MANIFEST_URL"); diff --git a/src/SwfocTrainer.App/Properties/AssemblyInfo.cs b/src/SwfocTrainer.App/Properties/AssemblyInfo.cs index e00665a4..2f949b5a 100644 --- a/src/SwfocTrainer.App/Properties/AssemblyInfo.cs +++ b/src/SwfocTrainer.App/Properties/AssemblyInfo.cs @@ -1,3 +1,5 @@ +using System; using System.Runtime.CompilerServices; +[assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModel.cs b/src/SwfocTrainer.App/ViewModels/MainViewModel.cs index a82466df..e4891a2d 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModel.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModel.cs @@ -12,7 +12,7 @@ namespace SwfocTrainer.App.ViewModels; public sealed class MainViewModel : MainViewModelSaveOpsBase { public MainViewModel(MainViewModelDependencies dependencies) - : base(dependencies) + : base(dependencies ?? throw new ArgumentNullException(nameof(dependencies))) { (Profiles, Actions, CatalogSummary, Updates, SaveDiffPreview, Hotkeys, SaveFields, FilteredSaveFields, SavePatchOperations, SavePatchCompatibility, ActionReliability, SelectedUnitTransactions, SpawnPresets, LiveOpsDiagnostics, ModCompatibilityRows, ActiveFreezes) = MainViewModelFactories.CreateCollections(); @@ -250,7 +250,7 @@ private async Task> LoadResolvedProfilesForLaunchC } private async Task AttachAsync() { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -304,10 +304,11 @@ private async Task BuildAttachProcessHintAsync() var resolvedProfiles = await LoadResolvedProfilesForLaunchContextAsync(); var contexts = processes .Select(process => process.LaunchContext ?? _launchContextResolver.Resolve(process, resolvedProfiles)) + .Where(context => context is not null) .ToArray(); return contexts - .Where(context => !string.IsNullOrWhiteSpace(context.Recommendation.ProfileId)) + .Where(context => context.Recommendation is not null && !string.IsNullOrWhiteSpace(context.Recommendation.ProfileId)) .OrderByDescending(context => context.Recommendation.Confidence) .ThenByDescending(context => context.LaunchKind == LaunchKind.Workshop || context.LaunchKind == LaunchKind.Mixed) .Select(context => context.Recommendation.ProfileId) @@ -322,7 +323,8 @@ private async Task BuildAttachProcessHintAsync() var processes = await _processLocator.FindSupportedProcessesAsync(); var variant = await _profileVariantResolver.ResolveAsync(requestedProfileId, processes, CancellationToken.None); - return (variant.ResolvedProfileId, variant); + var effectiveId = variant?.ResolvedProfileId ?? requestedProfileId; + return (effectiveId, variant); } private async Task LaunchAndAttachAsync() @@ -343,10 +345,10 @@ private async Task LaunchAndAttachAsync() private async Task BuildLaunchRequestAsync() { - var target = LaunchTarget.Equals("Sweaw", StringComparison.OrdinalIgnoreCase) + var target = string.Equals(LaunchTarget, "Sweaw", StringComparison.OrdinalIgnoreCase) ? GameLaunchTarget.Sweaw : GameLaunchTarget.Swfoc; - var mode = ResolveLaunchMode(LaunchMode); + var mode = ResolveLaunchMode(LaunchMode ?? string.Empty); var workshopIds = BuildLaunchWorkshopIds(); if (mode == GameLaunchMode.SteamMod && workshopIds.Count == 0 && !string.IsNullOrWhiteSpace(SelectedProfileId)) @@ -453,6 +455,7 @@ private IReadOnlyList BuildLaunchWorkshopIds() } private void ApplyAttachSessionStatus(AttachSession session) { + ArgumentNullException.ThrowIfNull(session); RuntimeMode = session.Process.Mode; ResolvedSymbolsCount = session.Symbols.Symbols.Count; var signatureCount = session.Symbols.Symbols.Values.Count(x => x.Source == AddressSource.Signature); @@ -536,7 +539,7 @@ private async Task DetachAsync() } private async Task LoadActionsAsync() { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -574,7 +577,7 @@ private async Task LoadActionsAsync() } private async Task ExecuteActionAsync() { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -617,6 +620,8 @@ private async Task ExecuteActionAsync() private static string? ResolveProfileFeatureGateReason(string actionId, TrainerProfile profile) { + ArgumentNullException.ThrowIfNull(profile); + var featureFlag = actionId switch { "toggle_fog_reveal_patch_fallback" => "allow_fog_patch_fallback", diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelAttachHelpers.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelAttachHelpers.cs index 72fff938..4795af34 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelAttachHelpers.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelAttachHelpers.cs @@ -9,6 +9,8 @@ internal static class MainViewModelAttachHelpers { internal static string BuildAttachProcessHintSummary(IReadOnlyList processes, string unknownValue) { + ArgumentNullException.ThrowIfNull(processes); + var summary = string.Join(", ", processes .Take(3) .Select(process => BuildAttachProcessHintSegment(process, unknownValue))); @@ -52,6 +54,9 @@ internal static bool IsActionAvailableForCurrentSession( IReadOnlyDictionary defaultSymbolByActionId, out string? unavailableReason) { + ArgumentNullException.ThrowIfNull(spec); + ArgumentNullException.ThrowIfNull(session); + unavailableReason = ResolveActionUnavailableReason(actionId, spec, session, defaultSymbolByActionId); return string.IsNullOrWhiteSpace(unavailableReason); } @@ -81,11 +86,12 @@ private static string BuildAttachProcessHintSegment(ProcessMetadata process, str var mods = MainViewModelDiagnostics.ReadProcessMetadata(process, "steamModIdsDetected", string.Empty); var via = MainViewModelDiagnostics.ReadProcessMetadata(process, "detectedVia", unknownValue); var launch = launchContext?.LaunchKind.ToString() ?? "n/a"; - var recommended = launchContext?.Recommendation.ProfileId ?? string.Empty; - var reason = launchContext?.Recommendation.ReasonCode ?? unknownValue; - var confidence = launchContext is null + var recommendation = launchContext?.Recommendation; + var recommended = recommendation?.ProfileId ?? string.Empty; + var reason = recommendation?.ReasonCode ?? unknownValue; + var confidence = recommendation is null ? "0.00" - : launchContext.Recommendation.Confidence.ToString("0.00"); + : recommendation.Confidence.ToString("0.00"); return $"{process.ProcessName}:{process.ProcessId}:{process.ExeTarget}:cmd={cmd}:mods={mods}:launch={launch}:rec={recommended}:{reason}:{confidence}:via={via}"; } diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelCoreStateBase.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelCoreStateBase.cs index 67f67d4c..a9610f0a 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelCoreStateBase.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelCoreStateBase.cs @@ -150,6 +150,7 @@ public abstract class MainViewModelCoreStateBase : INotifyPropertyChanged protected MainViewModelCoreStateBase(MainViewModelDependencies dependencies) { + ArgumentNullException.ThrowIfNull(dependencies); (_profiles, _processLocator, _launchContextResolver, _profileVariantResolver, _gameLauncher, _runtime, _orchestrator, _catalog, _saveCodec, _savePatchPackService, _savePatchApplyService, _helper, _updates, _modOnboarding, _modCalibration, _supportBundles, _telemetry, _freezeService, _actionReliability, _selectedUnitTransactions, _spawnPresets) = CreateDependencyTuple(dependencies); diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelCreditsHelpers.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelCreditsHelpers.cs index 2e76993d..7571acd8 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelCreditsHelpers.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelCreditsHelpers.cs @@ -19,6 +19,7 @@ internal static bool TryParseCreditsValue(string creditsValue, out int value, ou internal static string ResolveCreditsStateTag(ActionExecutionResult result, bool creditsFreeze) { + ArgumentNullException.ThrowIfNull(result); var stateTag = MainViewModelDiagnostics.ReadDiagnosticString(result.Diagnostics, "creditsStateTag"); if (!string.IsNullOrWhiteSpace(stateTag)) { diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelDiagnostics.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelDiagnostics.cs index 97b00b2b..1f95f9bd 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelDiagnostics.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelDiagnostics.cs @@ -10,6 +10,7 @@ internal static class MainViewModelDiagnostics { internal static string BuildProcessDiagnosticSummary(ProcessMetadata process, string unknownValue) { + ArgumentNullException.ThrowIfNull(process); var dependencySegment = BuildProcessDependencySegment( ReadProcessMetadata(process, "dependencyValidation", "Pass"), ReadProcessMetadata(process, "dependencyValidationMessage", string.Empty)); @@ -63,7 +64,8 @@ internal static string FormatPatchValue(object? value) internal static string BuildPatchMetadataSummary(SavePatchPack pack) { - return $"Patch {(pack.Metadata.SchemaVersion)} | profile={pack.Metadata.ProfileId} | schema={pack.Metadata.SchemaId} | ops={pack.Operations.Count}"; + ArgumentNullException.ThrowIfNull(pack); + return $"Patch {pack.Metadata.SchemaVersion} | profile={pack.Metadata.ProfileId} | schema={pack.Metadata.SchemaId} | ops={pack.Operations.Count}"; } internal static string BuildDependencyDiagnostic(string dependency, string dependencyMessage) @@ -117,6 +119,7 @@ internal static string ResolveBundleGateResult(ActionReliabilityViewItem? reliab internal static string BuildDiagnosticsStatusSuffix(ActionExecutionResult result) { + ArgumentNullException.ThrowIfNull(result); if (result.Diagnostics is null) { return string.Empty; @@ -134,6 +137,7 @@ internal static string BuildDiagnosticsStatusSuffix(ActionExecutionResult result internal static string BuildQuickActionStatus(string actionId, ActionExecutionResult result) { + ArgumentNullException.ThrowIfNull(result); var diagnosticsSuffix = BuildDiagnosticsStatusSuffix(result); return result.Succeeded ? $"✓ {actionId}: {result.Message}{diagnosticsSuffix}" diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelHotkeyHelpers.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelHotkeyHelpers.cs index b76ac68b..5ab09fab 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelHotkeyHelpers.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelHotkeyHelpers.cs @@ -20,6 +20,7 @@ internal static string GetHotkeyFilePath() internal static JsonObject ParseHotkeyPayload(HotkeyBindingItem binding) { + ArgumentNullException.ThrowIfNull(binding); try { return JsonNode.Parse(binding.PayloadJson ?? "{}") as JsonObject @@ -89,9 +90,12 @@ internal static async Task LoadHotkeysAsync(ObservableCollection SaveHotkeysAsync(IReadOnlyCollection hotkeys) { + ArgumentNullException.ThrowIfNull(hotkeys); var hotkeyPath = GetHotkeyFilePath(); TrustedPathPolicy.EnsureSubPath(TrustedPathPolicy.GetOrCreateAppDataRoot(), hotkeyPath); - Directory.CreateDirectory(Path.GetDirectoryName(hotkeyPath)!); + var hotkeyDirectory = Path.GetDirectoryName(hotkeyPath) + ?? throw new InvalidOperationException("Hotkey file path has no parent directory."); + Directory.CreateDirectory(hotkeyDirectory); var json = JsonSerializer.Serialize(hotkeys, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(hotkeyPath, json); return $"Saved {hotkeys.Count} hotkey bindings"; @@ -102,6 +106,7 @@ internal static string BuildHotkeyStatus( string actionId, ActionExecutionResult result) { + ArgumentNullException.ThrowIfNull(result); var diagnosticsSuffix = MainViewModelDiagnostics.BuildDiagnosticsStatusSuffix(result); return result.Succeeded ? $"Hotkey {gesture}: {actionId} succeeded{diagnosticsSuffix}" diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelLiveOpsBase.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelLiveOpsBase.cs index 7ac09357..f55ae53d 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelLiveOpsBase.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelLiveOpsBase.cs @@ -17,7 +17,7 @@ protected MainViewModelLiveOpsBase(MainViewModelDependencies dependencies) protected async Task RefreshActionReliabilityAsync() { ActionReliability.Clear(); - if (SelectedProfileId is null || _runtime.CurrentSession is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId) || _runtime.CurrentSession is null) { return; } @@ -35,7 +35,13 @@ protected async Task RefreshActionReliabilityAsync() // Catalog is optional for reliability scoring. } - var reliability = _actionReliability.Evaluate(profile, _runtime.CurrentSession, catalog); + var currentSession = _runtime.CurrentSession; + if (currentSession is null) + { + return; + } + + var reliability = _actionReliability.Evaluate(profile, currentSession, catalog); foreach (var item in reliability) { ActionReliability.Add(new ActionReliabilityViewItem( @@ -134,7 +140,7 @@ protected async Task CaptureSelectedUnitBaselineAsync() protected async Task ApplySelectedUnitDraftAsync() { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -146,7 +152,8 @@ protected async Task ApplySelectedUnitDraftAsync() return; } - var result = await _selectedUnitTransactions.ApplyAsync(SelectedProfileId, draftResult.Draft!, RuntimeMode); + var draft = draftResult.Draft ?? throw new InvalidOperationException("Draft build succeeded but Draft is null."); + var result = await _selectedUnitTransactions.ApplyAsync(SelectedProfileId, draft, RuntimeMode); RefreshSelectedUnitTransactions(); if (result.Succeeded) { @@ -161,7 +168,7 @@ protected async Task ApplySelectedUnitDraftAsync() protected async Task RevertSelectedUnitTransactionAsync() { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -181,7 +188,7 @@ protected async Task RevertSelectedUnitTransactionAsync() protected async Task RestoreSelectedUnitBaselineAsync() { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -201,7 +208,7 @@ protected async Task RestoreSelectedUnitBaselineAsync() protected async Task LoadSpawnPresetsAsync() { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -240,7 +247,8 @@ protected async Task RunSpawnBatchAsync() return; } - var preset = batchInputs.SelectedPreset!.ToCorePreset(); + var selectedPreset = batchInputs.SelectedPreset ?? throw new InvalidOperationException("Spawn batch succeeded but preset is null."); + var preset = selectedPreset.ToCorePreset(); var plan = _spawnPresets.BuildBatchPlan( batchInputs.ProfileId, preset, @@ -258,6 +266,7 @@ protected async Task RunSpawnBatchAsync() protected void ApplyDraftFromSnapshot(SelectedUnitSnapshot snapshot) { + ArgumentNullException.ThrowIfNull(snapshot); SelectedUnitHp = snapshot.Hp.ToString(DecimalPrecision3); SelectedUnitShield = snapshot.Shield.ToString(DecimalPrecision3); SelectedUnitSpeed = snapshot.Speed.ToString(DecimalPrecision3); diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelPayloadHelpers.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelPayloadHelpers.cs index b5a1156c..edf4f121 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelPayloadHelpers.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelPayloadHelpers.cs @@ -11,6 +11,9 @@ internal static JsonObject BuildRequiredPayloadTemplate( IReadOnlyDictionary defaultSymbolByActionId, IReadOnlyDictionary defaultHelperHookByActionId) { + ArgumentNullException.ThrowIfNull(required); + ArgumentNullException.ThrowIfNull(defaultSymbolByActionId); + ArgumentNullException.ThrowIfNull(defaultHelperHookByActionId); var payload = new JsonObject(); foreach (var node in required) diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionHelpers.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionHelpers.cs index 6733a385..63745618 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionHelpers.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionHelpers.cs @@ -10,6 +10,9 @@ internal static void PopulateActiveFreezes( IEnumerable frozenSymbols, IEnumerable activeToggles) { + ArgumentNullException.ThrowIfNull(activeFreezes); + ArgumentNullException.ThrowIfNull(frozenSymbols); + ArgumentNullException.ThrowIfNull(activeToggles); activeFreezes.Clear(); foreach (var symbol in frozenSymbols) { diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionsBase.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionsBase.cs index 5bfe4ffb..6a6922b4 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionsBase.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelQuickActionsBase.cs @@ -18,11 +18,18 @@ protected MainViewModelQuickActionsBase(MainViewModelDependencies dependencies) protected async Task QuickRunActionAsync(string actionId, JsonObject payload, string? toggleKey = null) { + ArgumentNullException.ThrowIfNull(payload); + if (!CanRunQuickAction()) { return; } + if (string.IsNullOrWhiteSpace(actionId)) + { + return; + } + if (!await EnsureActionAvailableForCurrentSessionAsync(actionId, actionId)) { return; diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelRuntimeModeOverrideHelpers.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelRuntimeModeOverrideHelpers.cs index f868c3aa..8409bd5a 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelRuntimeModeOverrideHelpers.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelRuntimeModeOverrideHelpers.cs @@ -87,7 +87,9 @@ internal static void Save(string? modeOverride) var normalized = Normalize(modeOverride); var path = GetSettingsPath(); TrustedPathPolicy.EnsureSubPath(TrustedPathPolicy.GetOrCreateAppDataRoot(), path); - Directory.CreateDirectory(Path.GetDirectoryName(path)!); + var settingsDirectory = Path.GetDirectoryName(path) + ?? throw new InvalidOperationException("Runtime mode settings path has no parent directory."); + Directory.CreateDirectory(settingsDirectory); var data = new Dictionary(StringComparer.Ordinal) { [SettingsKeyModeOverride] = normalized diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelSaveOpsBase.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelSaveOpsBase.cs index 448a304a..16ca1161 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelSaveOpsBase.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelSaveOpsBase.cs @@ -38,7 +38,7 @@ protected Task LoadSaveAsync() => LoadSaveAsync(clearPatchSummary: true); protected async Task LoadSaveAsync(bool clearPatchSummary) { - if (SelectedProfileId is null) + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } @@ -136,7 +136,8 @@ protected async Task ExportPatchPackAsync() return; } - var outputPath = TrustedPathPolicy.NormalizeAbsolute(dialog.FileName); + var outputPath = TrustedPathPolicy.NormalizeAbsolute(dialog.FileName + ?? throw new InvalidOperationException("Save dialog returned a null file name.")); TrustedPathPolicy.EnsureAllowedExtension(outputPath, ".json"); var outputDirectory = Path.GetDirectoryName(outputPath); if (string.IsNullOrWhiteSpace(outputDirectory)) @@ -344,6 +345,7 @@ protected void RebuildSaveFieldRows() } protected IEnumerable FlattenNodes(SaveNode root) { + ArgumentNullException.ThrowIfNull(root); if (root.Children is null || root.Children.Count == 0) { if (!string.Equals(root.ValueType, "root", StringComparison.OrdinalIgnoreCase)) @@ -417,7 +419,7 @@ protected void AppendPatchArtifactRows(string? backupPath, string? receiptPath) protected async Task LoadCatalogAsync() { - if (SelectedProfileId is null) { return; } + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } CatalogSummary.Clear(); var catalog = await _catalog.LoadCatalogAsync(SelectedProfileId); @@ -428,7 +430,7 @@ protected async Task LoadCatalogAsync() protected async Task DeployHelperAsync() { - if (SelectedProfileId is null) { return; } + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } var path = await _helper.DeployAsync(SelectedProfileId); Status = $"Helper deployed to: {path}"; @@ -436,7 +438,7 @@ protected async Task DeployHelperAsync() protected async Task VerifyHelperAsync() { - if (SelectedProfileId is null) { return; } + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } var ok = await _helper.VerifyAsync(SelectedProfileId); Status = ok ? "Helper verification passed" : "Helper verification failed"; @@ -453,7 +455,7 @@ protected async Task CheckUpdatesAsync() protected async Task InstallUpdateAsync() { - if (SelectedProfileId is null) { return; } + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } var result = await _updates.InstallProfileTransactionalAsync(SelectedProfileId); if (!result.Succeeded) { @@ -470,7 +472,7 @@ protected async Task InstallUpdateAsync() protected async Task RollbackProfileUpdateAsync() { - if (SelectedProfileId is null) { return; } + if (string.IsNullOrWhiteSpace(SelectedProfileId)) { return; } var rollback = await _updates.RollbackLastInstallAsync(SelectedProfileId); if (!rollback.Restored) { @@ -507,7 +509,7 @@ protected async Task ScaffoldModProfileAsync() protected async Task ExportCalibrationArtifactAsync() { - var profileId = SelectedProfileId ?? OnboardingDraftProfileId; + var profileId = !string.IsNullOrWhiteSpace(SelectedProfileId) ? SelectedProfileId : OnboardingDraftProfileId; var outputDir = Path.Combine(SupportBundleOutputDirectory, "calibration"); Directory.CreateDirectory(outputDir); @@ -526,7 +528,7 @@ protected async Task ExportCalibrationArtifactAsync() protected async Task BuildCompatibilityReportAsync() { - var profileId = SelectedProfileId ?? OnboardingDraftProfileId; + var profileId = !string.IsNullOrWhiteSpace(SelectedProfileId) ? SelectedProfileId : OnboardingDraftProfileId; var profile = await _profiles.ResolveInheritedProfileAsync(profileId); var report = await _modCalibration.BuildCompatibilityReportAsync(profile, _runtime.CurrentSession); diff --git a/src/SwfocTrainer.App/ViewModels/MainViewModelSpawnHelpers.cs b/src/SwfocTrainer.App/ViewModels/MainViewModelSpawnHelpers.cs index d669199d..350aea7b 100644 --- a/src/SwfocTrainer.App/ViewModels/MainViewModelSpawnHelpers.cs +++ b/src/SwfocTrainer.App/ViewModels/MainViewModelSpawnHelpers.cs @@ -22,6 +22,8 @@ internal sealed record SpawnBatchInputResult( internal static SpawnBatchInputResult TryBuildBatchInputs(SpawnBatchInputRequest request) { + ArgumentNullException.ThrowIfNull(request); + if (request.SelectedProfileId is null || request.SelectedSpawnPreset is null) { return new SpawnBatchInputResult( diff --git a/src/SwfocTrainer.Catalog/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Catalog/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Catalog/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Catalog/Services/CatalogService.cs b/src/SwfocTrainer.Catalog/Services/CatalogService.cs index 820f44de..6104a6e5 100644 --- a/src/SwfocTrainer.Catalog/Services/CatalogService.cs +++ b/src/SwfocTrainer.Catalog/Services/CatalogService.cs @@ -35,6 +35,9 @@ public sealed class CatalogService : ICatalogService public CatalogService(CatalogOptions options, IProfileRepository profiles, ILogger logger) { + ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(profiles); + ArgumentNullException.ThrowIfNull(logger); _options = options; _profiles = profiles; _logger = logger; diff --git a/src/SwfocTrainer.Core/Contracts/ModOnboardingServiceExtensions.cs b/src/SwfocTrainer.Core/Contracts/ModOnboardingServiceExtensions.cs index 2e215601..6e52b05d 100644 --- a/src/SwfocTrainer.Core/Contracts/ModOnboardingServiceExtensions.cs +++ b/src/SwfocTrainer.Core/Contracts/ModOnboardingServiceExtensions.cs @@ -9,6 +9,7 @@ public static Task ScaffoldDraftProfileAsync( ModOnboardingRequest request) { ArgumentNullException.ThrowIfNull(service); + ArgumentNullException.ThrowIfNull(request); return service.ScaffoldDraftProfileAsync(request, CancellationToken.None); } @@ -17,6 +18,7 @@ public static Task ScaffoldDraftProfilesFromSeedsAsync ModOnboardingSeedBatchRequest request) { ArgumentNullException.ThrowIfNull(service); + ArgumentNullException.ThrowIfNull(request); return service.ScaffoldDraftProfilesFromSeedsAsync(request, CancellationToken.None); } } diff --git a/src/SwfocTrainer.Core/IO/TrustedPathPolicy.cs b/src/SwfocTrainer.Core/IO/TrustedPathPolicy.cs index 79373c58..1e28d5b8 100644 --- a/src/SwfocTrainer.Core/IO/TrustedPathPolicy.cs +++ b/src/SwfocTrainer.Core/IO/TrustedPathPolicy.cs @@ -20,6 +20,7 @@ public static string GetOrCreateAppDataRoot() public static string EnsureDirectory(string path) { + ArgumentNullException.ThrowIfNull(path); var normalized = NormalizeAbsolute(path); Directory.CreateDirectory(normalized); return normalized; @@ -37,6 +38,8 @@ public static string NormalizeAbsolute(string path) public static string CombineUnderRoot(string rootPath, params string[] segments) { + ArgumentNullException.ThrowIfNull(rootPath); + ArgumentNullException.ThrowIfNull(segments); var root = NormalizeAbsolute(rootPath); var current = root; foreach (var segment in segments) @@ -56,6 +59,8 @@ public static string CombineUnderRoot(string rootPath, params string[] segments) public static string EnsureSubPath(string rootPath, string candidatePath) { + ArgumentNullException.ThrowIfNull(rootPath); + ArgumentNullException.ThrowIfNull(candidatePath); var root = NormalizeAbsolute(rootPath); var candidate = NormalizeAbsolute(candidatePath); if (!IsSubPath(root, candidate)) @@ -68,6 +73,8 @@ public static string EnsureSubPath(string rootPath, string candidatePath) public static bool IsSubPath(string rootPath, string candidatePath) { + ArgumentNullException.ThrowIfNull(rootPath); + ArgumentNullException.ThrowIfNull(candidatePath); var root = NormalizeAbsolute(rootPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var candidate = NormalizeAbsolute(candidatePath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); @@ -82,6 +89,8 @@ public static bool IsSubPath(string rootPath, string candidatePath) public static void EnsureAllowedExtension(string path, params string[] allowedExtensions) { + ArgumentNullException.ThrowIfNull(path); + ArgumentNullException.ThrowIfNull(allowedExtensions); if (allowedExtensions.Length == 0) { return; @@ -101,6 +110,8 @@ public static void EnsureAllowedExtension(string path, params string[] allowedEx public static string BuildSiblingFilePath(string sourcePath, string suffix) { + ArgumentNullException.ThrowIfNull(sourcePath); + ArgumentNullException.ThrowIfNull(suffix); var source = NormalizeAbsolute(sourcePath); var directory = Path.GetDirectoryName(source); if (string.IsNullOrWhiteSpace(directory)) diff --git a/src/SwfocTrainer.Core/Logging/FileAuditLogger.cs b/src/SwfocTrainer.Core/Logging/FileAuditLogger.cs index 69da1f5f..ea70eb93 100644 --- a/src/SwfocTrainer.Core/Logging/FileAuditLogger.cs +++ b/src/SwfocTrainer.Core/Logging/FileAuditLogger.cs @@ -27,6 +27,7 @@ public FileAuditLogger(string? logDirectory) public async Task WriteAsync(ActionAuditRecord record, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(record); var fileName = $"audit-{record.Timestamp:yyyy-MM-dd}.jsonl"; var path = Path.Combine(_logDirectory, fileName); var line = JsonSerializer.Serialize(record, JsonOptions) + Environment.NewLine; diff --git a/src/SwfocTrainer.Core/Models/BackendRoutingModels.cs b/src/SwfocTrainer.Core/Models/BackendRoutingModels.cs index d04df286..2388e140 100644 --- a/src/SwfocTrainer.Core/Models/BackendRoutingModels.cs +++ b/src/SwfocTrainer.Core/Models/BackendRoutingModels.cs @@ -37,11 +37,13 @@ public sealed record CapabilityReport( { public static CapabilityReport Unknown(string profileId) { + ArgumentNullException.ThrowIfNull(profileId); return Unknown(profileId, RuntimeReasonCode.CAPABILITY_UNKNOWN); } public static CapabilityReport Unknown(string profileId, RuntimeReasonCode reasonCode) { + ArgumentNullException.ThrowIfNull(profileId); return new CapabilityReport( profileId, DateTimeOffset.UtcNow, @@ -51,6 +53,7 @@ public static CapabilityReport Unknown(string profileId, RuntimeReasonCode reaso public bool IsFeatureAvailable(string featureId) { + ArgumentNullException.ThrowIfNull(featureId); return Capabilities.TryGetValue(featureId, out var capability) && capability.Available; } } diff --git a/src/SwfocTrainer.Core/Models/ModMechanicModels.cs b/src/SwfocTrainer.Core/Models/ModMechanicModels.cs index ba130126..c5cbc275 100644 --- a/src/SwfocTrainer.Core/Models/ModMechanicModels.cs +++ b/src/SwfocTrainer.Core/Models/ModMechanicModels.cs @@ -15,12 +15,15 @@ public sealed record ModMechanicReport( IReadOnlyList ActionSupport, IReadOnlyDictionary Diagnostics) { - public static ModMechanicReport Empty(string profileId) => - new( + public static ModMechanicReport Empty(string profileId) + { + ArgumentNullException.ThrowIfNull(profileId); + return new ModMechanicReport( ProfileId: profileId, GeneratedAtUtc: DateTimeOffset.UtcNow, DependenciesSatisfied: false, HelperBridgeReady: false, ActionSupport: Array.Empty(), Diagnostics: new Dictionary()); + } } diff --git a/src/SwfocTrainer.Core/Models/ProfileModels.cs b/src/SwfocTrainer.Core/Models/ProfileModels.cs index 2325b245..e87db0ac 100644 --- a/src/SwfocTrainer.Core/Models/ProfileModels.cs +++ b/src/SwfocTrainer.Core/Models/ProfileModels.cs @@ -83,6 +83,7 @@ public sealed record SymbolMap(IReadOnlyDictionary Symbols) { public bool TryGetValue(string symbol, out SymbolInfo? info) { + ArgumentNullException.ThrowIfNull(symbol); if (Symbols.TryGetValue(symbol, out var value)) { info = value; @@ -116,12 +117,21 @@ public static class JsonProfileSerializer Converters = { new JsonStringEnumConverter() } }; - public static T? Deserialize(string json) => JsonSerializer.Deserialize(json, Options); + public static T? Deserialize(string json) + { + ArgumentNullException.ThrowIfNull(json); + return JsonSerializer.Deserialize(json, Options); + } - public static string Serialize(T value) => JsonSerializer.Serialize(value, Options); + public static string Serialize(T value) + { + ArgumentNullException.ThrowIfNull(value); + return JsonSerializer.Serialize(value, Options); + } public static JsonObject ToJsonObject(T value) { + ArgumentNullException.ThrowIfNull(value); var node = JsonSerializer.SerializeToNode(value, Options) as JsonObject; return node ?? new JsonObject(); } diff --git a/src/SwfocTrainer.Core/Models/SdkOperationCatalog.cs b/src/SwfocTrainer.Core/Models/SdkOperationCatalog.cs index 23075333..21b925cf 100644 --- a/src/SwfocTrainer.Core/Models/SdkOperationCatalog.cs +++ b/src/SwfocTrainer.Core/Models/SdkOperationCatalog.cs @@ -22,6 +22,7 @@ public static class SdkOperationCatalog public static bool TryGet(string operationId, out SdkOperationDefinition definition) { + ArgumentNullException.ThrowIfNull(operationId); return Operations.TryGetValue(operationId, out definition!); } @@ -64,6 +65,7 @@ public bool IsModeAllowed(RuntimeMode mode) public static SdkOperationDefinition ReadOnly(string operationId) { + ArgumentNullException.ThrowIfNull(operationId); return new SdkOperationDefinition( operationId, IsMutation: false, @@ -73,6 +75,8 @@ public static SdkOperationDefinition ReadOnly(string operationId) public static SdkOperationDefinition Mutation(string operationId, params RuntimeMode[] allowedModes) { + ArgumentNullException.ThrowIfNull(operationId); + ArgumentNullException.ThrowIfNull(allowedModes); return new SdkOperationDefinition( operationId, IsMutation: true, diff --git a/src/SwfocTrainer.Core/Models/TelemetryModels.cs b/src/SwfocTrainer.Core/Models/TelemetryModels.cs index f71bf98e..e1d8f0aa 100644 --- a/src/SwfocTrainer.Core/Models/TelemetryModels.cs +++ b/src/SwfocTrainer.Core/Models/TelemetryModels.cs @@ -8,12 +8,15 @@ public sealed record TelemetryModeResolution( DateTimeOffset? TimestampUtc, string? RawLine) { - public static TelemetryModeResolution Unavailable(string reasonCode) => - new( + public static TelemetryModeResolution Unavailable(string reasonCode) + { + ArgumentNullException.ThrowIfNull(reasonCode); + return new TelemetryModeResolution( Available: false, Mode: RuntimeMode.Unknown, ReasonCode: reasonCode, SourcePath: string.Empty, TimestampUtc: null, RawLine: null); + } } diff --git a/src/SwfocTrainer.Core/Models/TransplantModels.cs b/src/SwfocTrainer.Core/Models/TransplantModels.cs index ff604be0..347d4400 100644 --- a/src/SwfocTrainer.Core/Models/TransplantModels.cs +++ b/src/SwfocTrainer.Core/Models/TransplantModels.cs @@ -20,8 +20,10 @@ public sealed record TransplantValidationReport( IReadOnlyList Entities, IReadOnlyDictionary Diagnostics) { - public static TransplantValidationReport Empty(string targetProfileId) => - new( + public static TransplantValidationReport Empty(string targetProfileId) + { + ArgumentNullException.ThrowIfNull(targetProfileId); + return new TransplantValidationReport( TargetProfileId: targetProfileId, GeneratedAtUtc: DateTimeOffset.UtcNow, AllResolved: true, @@ -29,6 +31,7 @@ public static TransplantValidationReport Empty(string targetProfileId) => BlockingEntityCount: 0, Entities: Array.Empty(), Diagnostics: new Dictionary()); + } } public sealed record TransplantPlan( diff --git a/src/SwfocTrainer.Core/Models/WorkshopInventoryModels.cs b/src/SwfocTrainer.Core/Models/WorkshopInventoryModels.cs index 8c0ea6be..e7f9c126 100644 --- a/src/SwfocTrainer.Core/Models/WorkshopInventoryModels.cs +++ b/src/SwfocTrainer.Core/Models/WorkshopInventoryModels.cs @@ -40,11 +40,14 @@ public sealed record WorkshopInventoryGraph( { public static WorkshopInventoryGraph Empty() => Empty("32470"); - public static WorkshopInventoryGraph Empty(string appId) => - new( + public static WorkshopInventoryGraph Empty(string appId) + { + ArgumentNullException.ThrowIfNull(appId); + return new WorkshopInventoryGraph( AppId: appId, GeneratedAtUtc: DateTimeOffset.UtcNow, Items: Array.Empty(), Diagnostics: Array.Empty(), Chains: Array.Empty()); + } } diff --git a/src/SwfocTrainer.Core/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Core/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Core/Services/ActionReliabilityService.cs b/src/SwfocTrainer.Core/Services/ActionReliabilityService.cs index cd287cd8..d2bd29a6 100644 --- a/src/SwfocTrainer.Core/Services/ActionReliabilityService.cs +++ b/src/SwfocTrainer.Core/Services/ActionReliabilityService.cs @@ -54,6 +54,8 @@ public IReadOnlyList Evaluate( AttachSession session, IReadOnlyDictionary>? catalog) { + ArgumentNullException.ThrowIfNull(profile); + ArgumentNullException.ThrowIfNull(session); var disabledActions = ParseCsvSet(session.Process.Metadata, "dependencyDisabledActions"); var criticalSymbols = ParseCsvSet(profile.Metadata, "criticalSymbols"); var mechanicSupport = ResolveMechanicSupportMap(profile, session, catalog); diff --git a/src/SwfocTrainer.Core/Services/ActionSymbolRegistry.cs b/src/SwfocTrainer.Core/Services/ActionSymbolRegistry.cs index 994776bd..23823809 100644 --- a/src/SwfocTrainer.Core/Services/ActionSymbolRegistry.cs +++ b/src/SwfocTrainer.Core/Services/ActionSymbolRegistry.cs @@ -35,6 +35,7 @@ public static class ActionSymbolRegistry public static bool TryGetSymbol(string actionId, out string symbol) { + ArgumentNullException.ThrowIfNull(actionId); if (ActionSymbols.TryGetValue(actionId, out symbol!)) { return true; diff --git a/src/SwfocTrainer.Core/Services/ModCalibrationService.cs b/src/SwfocTrainer.Core/Services/ModCalibrationService.cs index 21f9c403..24bc5351 100644 --- a/src/SwfocTrainer.Core/Services/ModCalibrationService.cs +++ b/src/SwfocTrainer.Core/Services/ModCalibrationService.cs @@ -20,11 +20,13 @@ public sealed class ModCalibrationService : IModCalibrationService public ModCalibrationService(IActionReliabilityService actionReliability) { + ArgumentNullException.ThrowIfNull(actionReliability); _actionReliability = actionReliability; } public async Task ExportCalibrationArtifactAsync(ModCalibrationArtifactRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); ValidateArtifactRequest(request); Directory.CreateDirectory(request.OutputDirectory); @@ -56,6 +58,7 @@ public Task BuildCompatibilityReportAsync( IReadOnlyDictionary>? catalog, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profile); var runtimeMode = session?.Process.Mode ?? RuntimeMode.Unknown; var dependencyStatus = dependencyValidation?.Status ?? InferDependencyStatus(session); var actionRows = BuildActionCompatibilityRows(profile, session, catalog); diff --git a/src/SwfocTrainer.Core/Services/NullSdkDiagnosticsSink.cs b/src/SwfocTrainer.Core/Services/NullSdkDiagnosticsSink.cs index 81c898fb..e975a841 100644 --- a/src/SwfocTrainer.Core/Services/NullSdkDiagnosticsSink.cs +++ b/src/SwfocTrainer.Core/Services/NullSdkDiagnosticsSink.cs @@ -7,11 +7,15 @@ public sealed class NullSdkDiagnosticsSink : ISdkDiagnosticsSink { public Task WriteAsync(SdkOperationRequest request, SdkOperationResult result) { + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(result); return Task.CompletedTask; } public Task WriteAsync(SdkOperationRequest request, SdkOperationResult result, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(result); return Task.CompletedTask; } } diff --git a/src/SwfocTrainer.Core/Services/SdkOperationRouter.cs b/src/SwfocTrainer.Core/Services/SdkOperationRouter.cs index 0d2f2939..2df20f50 100644 --- a/src/SwfocTrainer.Core/Services/SdkOperationRouter.cs +++ b/src/SwfocTrainer.Core/Services/SdkOperationRouter.cs @@ -22,6 +22,12 @@ public SdkOperationRouter( ISdkExecutionGuard sdkExecutionGuard, ISdkDiagnosticsSink sdkDiagnosticsSink) { + ArgumentNullException.ThrowIfNull(sdkRuntimeAdapter); + ArgumentNullException.ThrowIfNull(profileVariantResolver); + ArgumentNullException.ThrowIfNull(binaryFingerprintService); + ArgumentNullException.ThrowIfNull(capabilityMapResolver); + ArgumentNullException.ThrowIfNull(sdkExecutionGuard); + ArgumentNullException.ThrowIfNull(sdkDiagnosticsSink); _sdkRuntimeAdapter = sdkRuntimeAdapter; _profileVariantResolver = profileVariantResolver; _binaryFingerprintService = binaryFingerprintService; @@ -48,11 +54,13 @@ public SdkOperationRouter( public Task ExecuteAsync(SdkOperationRequest request) { + ArgumentNullException.ThrowIfNull(request); return ExecuteAsync(request, CancellationToken.None); } public async Task ExecuteAsync(SdkOperationRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); if (!IsSdkFeatureGateEnabled()) { return await WriteAndReturnAsync(request, CreateFeatureGateDisabledResult(), cancellationToken); diff --git a/src/SwfocTrainer.Core/Services/SelectedUnitTransactionService.cs b/src/SwfocTrainer.Core/Services/SelectedUnitTransactionService.cs index afa0482b..ec371c6e 100644 --- a/src/SwfocTrainer.Core/Services/SelectedUnitTransactionService.cs +++ b/src/SwfocTrainer.Core/Services/SelectedUnitTransactionService.cs @@ -35,6 +35,8 @@ public sealed class SelectedUnitTransactionService : ISelectedUnitTransactionSer public SelectedUnitTransactionService(IRuntimeAdapter runtime, TrainerOrchestrator orchestrator) { + ArgumentNullException.ThrowIfNull(runtime); + ArgumentNullException.ThrowIfNull(orchestrator); _runtime = runtime; _orchestrator = orchestrator; } @@ -57,6 +59,8 @@ public async Task ApplyAsync( RuntimeMode runtimeMode, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profileId); + ArgumentNullException.ThrowIfNull(draft); var runtimeModeFailure = ValidateRuntimeMode(runtimeMode, "transaction"); if (runtimeModeFailure is not null) { @@ -106,6 +110,7 @@ public async Task RevertLastAsync( RuntimeMode runtimeMode, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profileId); if (_history.Count == 0) { return Failed("No selected-unit transaction history exists.", "history_empty"); @@ -128,6 +133,7 @@ public Task RestoreBaselineAsync( RuntimeMode runtimeMode, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profileId); if (Baseline is null) { return Task.FromResult(Failed("No baseline snapshot captured yet.", "baseline_missing")); diff --git a/src/SwfocTrainer.Core/Services/SpawnPresetService.cs b/src/SwfocTrainer.Core/Services/SpawnPresetService.cs index 6076ea59..aa130cc4 100644 --- a/src/SwfocTrainer.Core/Services/SpawnPresetService.cs +++ b/src/SwfocTrainer.Core/Services/SpawnPresetService.cs @@ -25,6 +25,10 @@ public SpawnPresetService( TrainerOrchestrator orchestrator, LiveOpsOptions options) { + ArgumentNullException.ThrowIfNull(profiles); + ArgumentNullException.ThrowIfNull(catalog); + ArgumentNullException.ThrowIfNull(orchestrator); + ArgumentNullException.ThrowIfNull(options); _profiles = profiles; _catalog = catalog; _orchestrator = orchestrator; @@ -33,6 +37,7 @@ public SpawnPresetService( public async Task> LoadPresetsAsync(string profileId, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profileId); var presetPath = BuildPresetPath(profileId); if (File.Exists(presetPath)) { @@ -58,6 +63,8 @@ public SpawnBatchPlan BuildBatchPlan( string? entryMarkerOverride, bool stopOnFailure) { + ArgumentNullException.ThrowIfNull(profileId); + ArgumentNullException.ThrowIfNull(preset); var normalizedQuantity = Math.Clamp(quantity <= 0 ? preset.DefaultQuantity : quantity, 1, 100); var normalizedDelay = Math.Clamp(delayMs < 0 ? preset.DefaultDelayMs : delayMs, 0, 5000); var faction = string.IsNullOrWhiteSpace(factionOverride) ? preset.Faction : factionOverride.Trim(); @@ -82,6 +89,8 @@ public async Task ExecuteBatchAsync( RuntimeMode runtimeMode, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profileId); + ArgumentNullException.ThrowIfNull(plan); if (runtimeMode == RuntimeMode.Unknown) { return BuildFailedBatchResult("Spawn batch blocked: runtime mode is unknown.", blockedByMode: true); diff --git a/src/SwfocTrainer.Core/Services/SupportBundleService.cs b/src/SwfocTrainer.Core/Services/SupportBundleService.cs index f3d95646..f637dfc0 100644 --- a/src/SwfocTrainer.Core/Services/SupportBundleService.cs +++ b/src/SwfocTrainer.Core/Services/SupportBundleService.cs @@ -19,12 +19,15 @@ public sealed class SupportBundleService : ISupportBundleService public SupportBundleService(IRuntimeAdapter runtime, ITelemetrySnapshotService telemetry) { + ArgumentNullException.ThrowIfNull(runtime); + ArgumentNullException.ThrowIfNull(telemetry); _runtime = runtime; _telemetry = telemetry; } public async Task ExportAsync(SupportBundleRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); if (string.IsNullOrWhiteSpace(request.OutputDirectory)) { throw new InvalidDataException("Output directory is required."); diff --git a/src/SwfocTrainer.Core/Services/TelemetrySnapshotService.cs b/src/SwfocTrainer.Core/Services/TelemetrySnapshotService.cs index a78ac11e..c685d998 100644 --- a/src/SwfocTrainer.Core/Services/TelemetrySnapshotService.cs +++ b/src/SwfocTrainer.Core/Services/TelemetrySnapshotService.cs @@ -20,6 +20,7 @@ public sealed class TelemetrySnapshotService : ITelemetrySnapshotService public void RecordAction(string actionId, AddressSource source, bool succeeded) { + ArgumentNullException.ThrowIfNull(actionId); if (string.IsNullOrWhiteSpace(actionId)) { return; diff --git a/src/SwfocTrainer.Core/Services/TrainerOrchestrator.cs b/src/SwfocTrainer.Core/Services/TrainerOrchestrator.cs index ac367179..29a2a559 100644 --- a/src/SwfocTrainer.Core/Services/TrainerOrchestrator.cs +++ b/src/SwfocTrainer.Core/Services/TrainerOrchestrator.cs @@ -26,6 +26,11 @@ public TrainerOrchestrator( IAuditLogger auditLogger, ITelemetrySnapshotService telemetry) { + ArgumentNullException.ThrowIfNull(profiles); + ArgumentNullException.ThrowIfNull(runtime); + ArgumentNullException.ThrowIfNull(freezeService); + ArgumentNullException.ThrowIfNull(auditLogger); + ArgumentNullException.ThrowIfNull(telemetry); _profiles = profiles; _runtime = runtime; _freezeService = freezeService; @@ -55,6 +60,9 @@ public async Task ExecuteAsync( IReadOnlyDictionary? context, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profileId); + ArgumentNullException.ThrowIfNull(actionId); + ArgumentNullException.ThrowIfNull(payload); var profile = await _profiles.ResolveInheritedProfileAsync(profileId, cancellationToken); if (!profile.Actions.TryGetValue(actionId, out var action)) { diff --git a/src/SwfocTrainer.Core/SwfocTrainer.Core.csproj b/src/SwfocTrainer.Core/SwfocTrainer.Core.csproj index 6e365eac..e93304fa 100644 --- a/src/SwfocTrainer.Core/SwfocTrainer.Core.csproj +++ b/src/SwfocTrainer.Core/SwfocTrainer.Core.csproj @@ -1,6 +1,7 @@ net8.0 + enable diff --git a/src/SwfocTrainer.Core/Validation/ActionPayloadValidator.cs b/src/SwfocTrainer.Core/Validation/ActionPayloadValidator.cs index 62ed3186..491edc8e 100644 --- a/src/SwfocTrainer.Core/Validation/ActionPayloadValidator.cs +++ b/src/SwfocTrainer.Core/Validation/ActionPayloadValidator.cs @@ -6,6 +6,8 @@ public static class ActionPayloadValidator { public static (bool IsValid, string Message) Validate(JsonObject schema, JsonObject payload) { + ArgumentNullException.ThrowIfNull(schema); + ArgumentNullException.ThrowIfNull(payload); if (schema.Count == 0) { return (true, "No payload schema defined"); diff --git a/src/SwfocTrainer.DataIndex/Properties/AssemblyInfo.cs b/src/SwfocTrainer.DataIndex/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.DataIndex/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Flow/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Flow/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Flow/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Flow/Services/LuaHarnessRunner.cs b/src/SwfocTrainer.Flow/Services/LuaHarnessRunner.cs index 560c1a1e..5b0b6f9b 100644 --- a/src/SwfocTrainer.Flow/Services/LuaHarnessRunner.cs +++ b/src/SwfocTrainer.Flow/Services/LuaHarnessRunner.cs @@ -14,6 +14,7 @@ public LuaHarnessRunner() public LuaHarnessRunner(string harnessScriptPath) { + ArgumentNullException.ThrowIfNull(harnessScriptPath); _harnessScriptPath = harnessScriptPath; } diff --git a/src/SwfocTrainer.Helper/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Helper/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Helper/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Helper/Services/HelperModService.cs b/src/SwfocTrainer.Helper/Services/HelperModService.cs index 61ae2410..e21bb5ad 100644 --- a/src/SwfocTrainer.Helper/Services/HelperModService.cs +++ b/src/SwfocTrainer.Helper/Services/HelperModService.cs @@ -14,6 +14,9 @@ public sealed class HelperModService : IHelperModService public HelperModService(IProfileRepository profiles, HelperModOptions options, ILogger logger) { + ArgumentNullException.ThrowIfNull(profiles); + ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(logger); _profiles = profiles; _options = options; _logger = logger; diff --git a/src/SwfocTrainer.Meg/MegEntry.cs b/src/SwfocTrainer.Meg/MegEntry.cs index a3f1f4bb..deccd4a7 100644 --- a/src/SwfocTrainer.Meg/MegEntry.cs +++ b/src/SwfocTrainer.Meg/MegEntry.cs @@ -2,8 +2,8 @@ namespace SwfocTrainer.Meg; public sealed record MegEntry( string Path, - uint Crc32, + long Crc32, int Index, int SizeBytes, int StartOffset, - ushort Flags = 0); + int Flags = 0); diff --git a/src/SwfocTrainer.Meg/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Meg/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Meg/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Profiles/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Profiles/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Profiles/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Profiles/Services/FileSystemProfileRepository.cs b/src/SwfocTrainer.Profiles/Services/FileSystemProfileRepository.cs index 04514dbd..f4cd80c5 100644 --- a/src/SwfocTrainer.Profiles/Services/FileSystemProfileRepository.cs +++ b/src/SwfocTrainer.Profiles/Services/FileSystemProfileRepository.cs @@ -21,6 +21,7 @@ public sealed class FileSystemProfileRepository : IProfileRepository public FileSystemProfileRepository(ProfileRepositoryOptions options) { + ArgumentNullException.ThrowIfNull(options); _options = options; } diff --git a/src/SwfocTrainer.Profiles/Services/GitHubProfileUpdateService.cs b/src/SwfocTrainer.Profiles/Services/GitHubProfileUpdateService.cs index 06286185..1872a9e7 100644 --- a/src/SwfocTrainer.Profiles/Services/GitHubProfileUpdateService.cs +++ b/src/SwfocTrainer.Profiles/Services/GitHubProfileUpdateService.cs @@ -25,6 +25,9 @@ public sealed class GitHubProfileUpdateService : IProfileUpdateService public GitHubProfileUpdateService(HttpClient httpClient, ProfileRepositoryOptions options, IProfileRepository repository) { + ArgumentNullException.ThrowIfNull(httpClient); + ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(repository); _httpClient = httpClient; _options = options; _repository = repository; diff --git a/src/SwfocTrainer.Profiles/Services/ModOnboardingService.cs b/src/SwfocTrainer.Profiles/Services/ModOnboardingService.cs index 95eca299..461bdd2e 100644 --- a/src/SwfocTrainer.Profiles/Services/ModOnboardingService.cs +++ b/src/SwfocTrainer.Profiles/Services/ModOnboardingService.cs @@ -36,6 +36,8 @@ public sealed class ModOnboardingService : IModOnboardingService public ModOnboardingService(IProfileRepository profiles, ProfileRepositoryOptions options) { + ArgumentNullException.ThrowIfNull(profiles); + ArgumentNullException.ThrowIfNull(options); _profiles = profiles; _options = options; } diff --git a/src/SwfocTrainer.Runtime/Interop/ProcessMemoryAccessor.cs b/src/SwfocTrainer.Runtime/Interop/ProcessMemoryAccessor.cs index cd8d4185..87077746 100644 --- a/src/SwfocTrainer.Runtime/Interop/ProcessMemoryAccessor.cs +++ b/src/SwfocTrainer.Runtime/Interop/ProcessMemoryAccessor.cs @@ -51,6 +51,7 @@ public void Write(nint address, T value) where T : unmanaged public void WriteBytes(nint address, byte[] buffer, bool executablePatch) { + ArgumentNullException.ThrowIfNull(buffer); if (buffer.Length == 0) { return; @@ -118,6 +119,11 @@ private void WriteProcessMemoryChecked(nint address, byte[] buffer) public byte[] ReadBytes(nint address, int count) { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Read count must be non-negative."); + } + var buffer = new byte[count]; if (!NativeMethods.ReadProcessMemory(_handle, address, buffer, count, out var read) || read.ToInt64() != count) { diff --git a/src/SwfocTrainer.Runtime/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Runtime/Properties/AssemblyInfo.cs index 19a3510d..8a757a58 100644 --- a/src/SwfocTrainer.Runtime/Properties/AssemblyInfo.cs +++ b/src/SwfocTrainer.Runtime/Properties/AssemblyInfo.cs @@ -1,4 +1,6 @@ +using System; using System.Runtime.CompilerServices; +[assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Runtime/Scanning/AobPattern.cs b/src/SwfocTrainer.Runtime/Scanning/AobPattern.cs index 74b1f8bf..56405741 100644 --- a/src/SwfocTrainer.Runtime/Scanning/AobPattern.cs +++ b/src/SwfocTrainer.Runtime/Scanning/AobPattern.cs @@ -11,6 +11,7 @@ private AobPattern(byte?[] bytes) public static AobPattern Parse(string pattern) { + ArgumentNullException.ThrowIfNull(pattern); var parts = pattern.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var bytes = new byte?[parts.Length]; for (var i = 0; i < parts.Length; i++) diff --git a/src/SwfocTrainer.Runtime/Scanning/AobScanner.cs b/src/SwfocTrainer.Runtime/Scanning/AobScanner.cs index 79f1b870..da4a1f32 100644 --- a/src/SwfocTrainer.Runtime/Scanning/AobScanner.cs +++ b/src/SwfocTrainer.Runtime/Scanning/AobScanner.cs @@ -6,11 +6,15 @@ internal static class AobScanner { public static nint FindPattern(Process process, byte[] memory, nint baseAddress, AobPattern pattern) { + ArgumentNullException.ThrowIfNull(memory); + ArgumentNullException.ThrowIfNull(pattern); return FindPattern(memory, baseAddress, pattern); } public static nint FindPattern(byte[] memory, nint baseAddress, AobPattern pattern) { + ArgumentNullException.ThrowIfNull(memory); + ArgumentNullException.ThrowIfNull(pattern); var sig = pattern.Bytes; if (sig.Length == 0) { diff --git a/src/SwfocTrainer.Runtime/Services/BackendRouter.cs b/src/SwfocTrainer.Runtime/Services/BackendRouter.cs index 6f49e5e1..c37da520 100644 --- a/src/SwfocTrainer.Runtime/Services/BackendRouter.cs +++ b/src/SwfocTrainer.Runtime/Services/BackendRouter.cs @@ -22,6 +22,10 @@ public BackendRouteDecision Resolve( ProcessMetadata process, CapabilityReport capabilityReport) { + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(profile); + ArgumentNullException.ThrowIfNull(process); + ArgumentNullException.ThrowIfNull(capabilityReport); var state = CreateRouteResolutionState(request, profile, process, capabilityReport); var requiredCapabilityDecision = TryResolveRequiredCapabilityContract( diff --git a/src/SwfocTrainer.Runtime/Services/BinaryFingerprintService.cs b/src/SwfocTrainer.Runtime/Services/BinaryFingerprintService.cs index a1096b45..f86d9b37 100644 --- a/src/SwfocTrainer.Runtime/Services/BinaryFingerprintService.cs +++ b/src/SwfocTrainer.Runtime/Services/BinaryFingerprintService.cs @@ -12,26 +12,31 @@ public sealed class BinaryFingerprintService : IBinaryFingerprintService public BinaryFingerprintService(ILogger logger) { + ArgumentNullException.ThrowIfNull(logger); _logger = logger; } public Task CaptureFromPathAsync(string modulePath) { + ArgumentNullException.ThrowIfNull(modulePath); return CaptureFromPathAsync(modulePath, null, CancellationToken.None); } public Task CaptureFromPathAsync(string modulePath, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(modulePath); return CaptureFromPathAsync(modulePath, null, cancellationToken); } public Task CaptureFromPathAsync(string modulePath, int processId) { + ArgumentNullException.ThrowIfNull(modulePath); return CaptureFromPathAsync(modulePath, processId, CancellationToken.None); } public Task CaptureFromPathAsync(string modulePath, int processId, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(modulePath); return CaptureFromPathAsync(modulePath, (int?)processId, cancellationToken); } @@ -107,7 +112,7 @@ private static Task> TryGetLoadedModulesAsync(int? process using var process = Process.GetProcessById(processId.Value); var modules = process.Modules .Cast() - .Select(x => x.ModuleName) + .Select(x => x.ModuleName ?? string.Empty) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) diff --git a/src/SwfocTrainer.Runtime/Services/CapabilityMapResolver.cs b/src/SwfocTrainer.Runtime/Services/CapabilityMapResolver.cs index 31c91a50..83000d3c 100644 --- a/src/SwfocTrainer.Runtime/Services/CapabilityMapResolver.cs +++ b/src/SwfocTrainer.Runtime/Services/CapabilityMapResolver.cs @@ -19,6 +19,8 @@ public sealed class CapabilityMapResolver : ICapabilityMapResolver public CapabilityMapResolver(string mapsRootPath, ILogger logger) { + ArgumentNullException.ThrowIfNull(mapsRootPath); + ArgumentNullException.ThrowIfNull(logger); _mapsRootPath = mapsRootPath; _logger = logger; } @@ -29,6 +31,8 @@ public Task ResolveAsync( string operationId, IReadOnlySet resolvedAnchors) { + ArgumentNullException.ThrowIfNull(fingerprint); + ArgumentNullException.ThrowIfNull(resolvedAnchors); return ResolveAsync(fingerprint, requestedProfileId, operationId, resolvedAnchors, CancellationToken.None); } @@ -39,6 +43,8 @@ public async Task ResolveAsync( IReadOnlySet resolvedAnchors, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(fingerprint); + ArgumentNullException.ThrowIfNull(resolvedAnchors); var map = await LoadMapAsync(fingerprint, cancellationToken); if (map is null) { @@ -81,11 +87,13 @@ public async Task ResolveAsync( public Task ResolveDefaultProfileIdAsync(BinaryFingerprint fingerprint) { + ArgumentNullException.ThrowIfNull(fingerprint); return ResolveDefaultProfileIdAsync(fingerprint, CancellationToken.None); } public async Task ResolveDefaultProfileIdAsync(BinaryFingerprint fingerprint, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(fingerprint); var map = await LoadMapAsync(fingerprint, cancellationToken); return map?.DefaultProfileId; } diff --git a/src/SwfocTrainer.Runtime/Services/GameLaunchService.cs b/src/SwfocTrainer.Runtime/Services/GameLaunchService.cs index 6b606da1..86117e78 100644 --- a/src/SwfocTrainer.Runtime/Services/GameLaunchService.cs +++ b/src/SwfocTrainer.Runtime/Services/GameLaunchService.cs @@ -23,6 +23,7 @@ public sealed class GameLaunchService : IGameLaunchService public Task LaunchAsync(GameLaunchRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); cancellationToken.ThrowIfCancellationRequested(); if (request.TerminateExistingTargets) { @@ -69,7 +70,10 @@ private static void TerminateKnownTargets() { foreach (var process in Process.GetProcessesByName(processName)) { - TryKillProcess(process); + using (process) + { + TryKillProcess(process); + } } } } diff --git a/src/SwfocTrainer.Runtime/Services/LaunchContextResolver.cs b/src/SwfocTrainer.Runtime/Services/LaunchContextResolver.cs index 3edff355..c811a3fb 100644 --- a/src/SwfocTrainer.Runtime/Services/LaunchContextResolver.cs +++ b/src/SwfocTrainer.Runtime/Services/LaunchContextResolver.cs @@ -19,6 +19,8 @@ public sealed class LaunchContextResolver : ILaunchContextResolver public LaunchContext Resolve(ProcessMetadata process, IReadOnlyList profiles) { + ArgumentNullException.ThrowIfNull(process); + ArgumentNullException.ThrowIfNull(profiles); var commandLineAvailable = !string.IsNullOrWhiteSpace(process.CommandLine); var steamModIds = ExtractSteamModIds(process).OrderBy(x => x, StringComparer.Ordinal).ToArray(); var modPathRaw = ExtractModPath(process.CommandLine); diff --git a/src/SwfocTrainer.Runtime/Services/ModDependencyValidator.cs b/src/SwfocTrainer.Runtime/Services/ModDependencyValidator.cs index d6db02be..50606a80 100644 --- a/src/SwfocTrainer.Runtime/Services/ModDependencyValidator.cs +++ b/src/SwfocTrainer.Runtime/Services/ModDependencyValidator.cs @@ -22,6 +22,8 @@ public sealed class ModDependencyValidator : IModDependencyValidator public DependencyValidationResult Validate(TrainerProfile profile, ProcessMetadata process) { + ArgumentNullException.ThrowIfNull(profile); + ArgumentNullException.ThrowIfNull(process); var marker = ReadMetadata(profile, DependencyMetadataMarkerKey); var markerFailure = ValidateMarkerMetadata(marker); if (markerFailure is not null) diff --git a/src/SwfocTrainer.Runtime/Services/ModMechanicDetectionService.cs b/src/SwfocTrainer.Runtime/Services/ModMechanicDetectionService.cs index 5d6bb7bb..67598853 100644 --- a/src/SwfocTrainer.Runtime/Services/ModMechanicDetectionService.cs +++ b/src/SwfocTrainer.Runtime/Services/ModMechanicDetectionService.cs @@ -36,6 +36,8 @@ public async Task DetectAsync( IReadOnlyDictionary>? catalog, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profile); + ArgumentNullException.ThrowIfNull(session); cancellationToken.ThrowIfCancellationRequested(); var disabledActions = ParseCsvSet(session.Process.Metadata, "dependencyDisabledActions"); diff --git a/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackend.cs b/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackend.cs index 9318a36d..3dff7834 100644 --- a/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackend.cs +++ b/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackend.cs @@ -74,6 +74,7 @@ public Task ProbeCapabilitiesAsync( string profileId, ProcessMetadata processContext) { + ArgumentNullException.ThrowIfNull(processContext); return ProbeCapabilitiesAsync(profileId, processContext, CancellationToken.None); } @@ -82,6 +83,7 @@ public async Task ProbeCapabilitiesAsync( ProcessMetadata processContext, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(processContext); var result = await SendAsync(CreateProbeCommand(profileId, processContext), cancellationToken); if (!result.Succeeded) { @@ -244,6 +246,8 @@ public Task ExecuteAsync( ActionExecutionRequest command, CapabilityReport capabilityReport) { + ArgumentNullException.ThrowIfNull(command); + ArgumentNullException.ThrowIfNull(capabilityReport); return ExecuteAsync(command, capabilityReport, CancellationToken.None); } @@ -252,6 +256,8 @@ public async Task ExecuteAsync( CapabilityReport capabilityReport, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(command); + ArgumentNullException.ThrowIfNull(capabilityReport); var commandContext = command.Context; var extenderCommand = new ExtenderCommand( CommandId: Guid.NewGuid().ToString("N"), diff --git a/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackendContextHelpers.cs b/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackendContextHelpers.cs index 8e0ebbf9..19656279 100644 --- a/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackendContextHelpers.cs +++ b/src/SwfocTrainer.Runtime/Services/NamedPipeExtenderBackendContextHelpers.cs @@ -12,6 +12,7 @@ internal static Dictionary ParseCapabilities( IReadOnlyDictionary? diagnostics, IEnumerable nativeAuthoritativeFeatureIds) { + ArgumentNullException.ThrowIfNull(nativeAuthoritativeFeatureIds); var capabilities = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!TryGetCapabilitiesElement(diagnostics, out var element)) { diff --git a/src/SwfocTrainer.Runtime/Services/NamedPipeHelperBridgeBackend.cs b/src/SwfocTrainer.Runtime/Services/NamedPipeHelperBridgeBackend.cs index e16f258b..58f38bd9 100644 --- a/src/SwfocTrainer.Runtime/Services/NamedPipeHelperBridgeBackend.cs +++ b/src/SwfocTrainer.Runtime/Services/NamedPipeHelperBridgeBackend.cs @@ -58,11 +58,13 @@ public sealed class NamedPipeHelperBridgeBackend : IHelperBridgeBackend public NamedPipeHelperBridgeBackend(IExecutionBackend backend) { + ArgumentNullException.ThrowIfNull(backend); _backend = backend; } public async Task ProbeAsync(HelperBridgeProbeRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); if (request.Process.ProcessId <= 0) { return CreateProcessUnavailableProbeResult(request.Process.ProcessId); @@ -80,6 +82,7 @@ public async Task ProbeAsync(HelperBridgeProbeRequest r public async Task ExecuteAsync(HelperBridgeRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); var probe = await ProbeForExecutionAsync(request, cancellationToken); if (!probe.Available) { diff --git a/src/SwfocTrainer.Runtime/Services/NoopSdkRuntimeAdapter.cs b/src/SwfocTrainer.Runtime/Services/NoopSdkRuntimeAdapter.cs index 42744304..83f354e3 100644 --- a/src/SwfocTrainer.Runtime/Services/NoopSdkRuntimeAdapter.cs +++ b/src/SwfocTrainer.Runtime/Services/NoopSdkRuntimeAdapter.cs @@ -7,11 +7,13 @@ public sealed class NoopSdkRuntimeAdapter : ISdkRuntimeAdapter { public Task ExecuteAsync(SdkOperationRequest request) { + ArgumentNullException.ThrowIfNull(request); return ExecuteAsync(request, CancellationToken.None); } public Task ExecuteAsync(SdkOperationRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); var result = new SdkOperationResult( false, "SDK runtime adapter is not implemented for this build.", diff --git a/src/SwfocTrainer.Runtime/Services/ProcessLocator.cs b/src/SwfocTrainer.Runtime/Services/ProcessLocator.cs index 431819dc..ae12ab94 100644 --- a/src/SwfocTrainer.Runtime/Services/ProcessLocator.cs +++ b/src/SwfocTrainer.Runtime/Services/ProcessLocator.cs @@ -53,7 +53,7 @@ public Task> FindSupportedProcessesAsync(Cancella } public async Task> FindSupportedProcessesAsync( - ProcessLocatorOptions options, + ProcessLocatorOptions? options, CancellationToken cancellationToken) { options ??= ProcessLocatorOptions.None; @@ -63,13 +63,16 @@ public async Task> FindSupportedProcessesAsync( foreach (var process in Process.GetProcesses()) { - var metadata = TryBuildProcessMetadata(process, wmiByPid, profiles, options); - if (metadata is null) + using (process) { - continue; - } + var metadata = TryBuildProcessMetadata(process, wmiByPid, profiles, options); + if (metadata is null) + { + continue; + } - list.Add(metadata); + list.Add(metadata); + } } return list; @@ -82,7 +85,7 @@ public async Task> FindSupportedProcessesAsync( public async Task FindBestMatchAsync( ExeTarget target, - ProcessLocatorOptions options, + ProcessLocatorOptions? options, CancellationToken cancellationToken) { options ??= ProcessLocatorOptions.None; diff --git a/src/SwfocTrainer.Runtime/Services/ProfileVariantResolver.cs b/src/SwfocTrainer.Runtime/Services/ProfileVariantResolver.cs index ea2fc179..358dcbac 100644 --- a/src/SwfocTrainer.Runtime/Services/ProfileVariantResolver.cs +++ b/src/SwfocTrainer.Runtime/Services/ProfileVariantResolver.cs @@ -30,6 +30,8 @@ public ProfileVariantResolver( IBinaryFingerprintService? fingerprintService, ICapabilityMapResolver? capabilityMapResolver) { + ArgumentNullException.ThrowIfNull(launchContextResolver); + ArgumentNullException.ThrowIfNull(logger); _launchContextResolver = launchContextResolver; _logger = logger; _profileRepository = profileRepository; diff --git a/src/SwfocTrainer.Runtime/Services/RuntimeAdapter.cs b/src/SwfocTrainer.Runtime/Services/RuntimeAdapter.cs index b3cc4a49..6182c36b 100644 --- a/src/SwfocTrainer.Runtime/Services/RuntimeAdapter.cs +++ b/src/SwfocTrainer.Runtime/Services/RuntimeAdapter.cs @@ -70,6 +70,11 @@ public RuntimeAdapter( ILogger logger, IServiceProvider serviceProvider) { + ArgumentNullException.ThrowIfNull(processLocator); + ArgumentNullException.ThrowIfNull(profileRepository); + ArgumentNullException.ThrowIfNull(signatureResolver); + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(serviceProvider); _processLocator = processLocator; _profileRepository = profileRepository; _signatureResolver = signatureResolver; diff --git a/src/SwfocTrainer.Runtime/Services/RuntimeModeProbeResolver.cs b/src/SwfocTrainer.Runtime/Services/RuntimeModeProbeResolver.cs index 3d1810cc..e89f57d4 100644 --- a/src/SwfocTrainer.Runtime/Services/RuntimeModeProbeResolver.cs +++ b/src/SwfocTrainer.Runtime/Services/RuntimeModeProbeResolver.cs @@ -30,6 +30,7 @@ public static class RuntimeModeProbeResolver public static RuntimeModeProbeResult Resolve(RuntimeMode modeHint, SymbolMap symbols) { + ArgumentNullException.ThrowIfNull(symbols); var tacticalSignalCount = CountSignals(symbols, TacticalIndicators); var galacticSignalCount = CountSignals(symbols, GalacticIndicators); if (TryResolveSingleSignalMode(modeHint, tacticalSignalCount, galacticSignalCount, out var singleSignalResult)) diff --git a/src/SwfocTrainer.Runtime/Services/SdkExecutionGuard.cs b/src/SwfocTrainer.Runtime/Services/SdkExecutionGuard.cs index 6678b163..8fa8b4ea 100644 --- a/src/SwfocTrainer.Runtime/Services/SdkExecutionGuard.cs +++ b/src/SwfocTrainer.Runtime/Services/SdkExecutionGuard.cs @@ -7,6 +7,7 @@ public sealed class SdkExecutionGuard : ISdkExecutionGuard { public SdkExecutionDecision CanExecute(CapabilityResolutionResult resolution, bool isMutation) { + ArgumentNullException.ThrowIfNull(resolution); if (resolution.State == SdkCapabilityStatus.Available) { return new SdkExecutionDecision(true, resolution.ReasonCode, "Capability available."); diff --git a/src/SwfocTrainer.Runtime/Services/SignatureResolver.Addressing.cs b/src/SwfocTrainer.Runtime/Services/SignatureResolver.Addressing.cs index efab6df7..834070b7 100644 --- a/src/SwfocTrainer.Runtime/Services/SignatureResolver.Addressing.cs +++ b/src/SwfocTrainer.Runtime/Services/SignatureResolver.Addressing.cs @@ -13,6 +13,8 @@ internal static bool TryResolveAddress( out nint resolvedAddress, out string? diagnostics) { + ArgumentNullException.ThrowIfNull(signature); + ArgumentNullException.ThrowIfNull(moduleBytes); resolvedAddress = nint.Zero; diagnostics = null; diff --git a/src/SwfocTrainer.Runtime/Services/SignatureResolver.Fallbacks.cs b/src/SwfocTrainer.Runtime/Services/SignatureResolver.Fallbacks.cs index eb2fd670..d8a399ab 100644 --- a/src/SwfocTrainer.Runtime/Services/SignatureResolver.Fallbacks.cs +++ b/src/SwfocTrainer.Runtime/Services/SignatureResolver.Fallbacks.cs @@ -70,6 +70,9 @@ internal static void HandleSignatureHit( nint hit, in SignatureHitContext context) { + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(signatureSet); + ArgumentNullException.ThrowIfNull(signature); if (SignatureResolverAddressing.TryResolveAddress( signature, hit, @@ -124,6 +127,11 @@ internal static void HandleSignatureMiss( nint baseAddress, IDictionary symbols) { + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(signature); + ArgumentNullException.ThrowIfNull(fallbackOffsets); + ArgumentNullException.ThrowIfNull(accessor); + ArgumentNullException.ThrowIfNull(symbols); if (fallbackOffsets.TryGetValue(signature.Name, out var fallback) && !symbols.ContainsKey(signature.Name)) { if (TryApplyFallback(new FallbackAttempt( @@ -171,6 +179,10 @@ internal static void ApplyStandaloneFallbacks( nint baseAddress, IDictionary symbols) { + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(fallbackOffsets); + ArgumentNullException.ThrowIfNull(accessor); + ArgumentNullException.ThrowIfNull(symbols); foreach (var fallback in fallbackOffsets) { if (symbols.ContainsKey(fallback.Key)) diff --git a/src/SwfocTrainer.Runtime/Services/SignatureResolver.SymbolHydration.cs b/src/SwfocTrainer.Runtime/Services/SignatureResolver.SymbolHydration.cs index 6da3c337..62185847 100644 --- a/src/SwfocTrainer.Runtime/Services/SignatureResolver.SymbolHydration.cs +++ b/src/SwfocTrainer.Runtime/Services/SignatureResolver.SymbolHydration.cs @@ -29,6 +29,10 @@ internal static void TryHydrateSymbolsFromGhidraPack( IReadOnlyList signatureSets, IDictionary symbols) { + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(module); + ArgumentNullException.ThrowIfNull(signatureSets); + ArgumentNullException.ThrowIfNull(symbols); if (!TryLoadGhidraSymbolPack(ghidraSymbolPackRoot, logger, module, out var fingerprintId, out var packPath, out var pack)) { return; @@ -343,6 +347,7 @@ private static bool TryBuildAnchorSymbol( private static bool TryBuildFingerprintId(ProcessModule module, out string fingerprintId, out string moduleName) { + ArgumentNullException.ThrowIfNull(module); fingerprintId = string.Empty; moduleName = Path.GetFileName(module.FileName ?? module.ModuleName ?? "module"); if (string.IsNullOrWhiteSpace(module.FileName) || !File.Exists(module.FileName)) diff --git a/src/SwfocTrainer.Runtime/Services/SignatureResolver.cs b/src/SwfocTrainer.Runtime/Services/SignatureResolver.cs index b854dc1b..39d4025c 100644 --- a/src/SwfocTrainer.Runtime/Services/SignatureResolver.cs +++ b/src/SwfocTrainer.Runtime/Services/SignatureResolver.cs @@ -19,6 +19,8 @@ public SignatureResolver(ILogger logger) public SignatureResolver(ILogger logger, string ghidraSymbolPackRoot) { + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(ghidraSymbolPackRoot); _logger = logger; _ghidraSymbolPackRoot = ghidraSymbolPackRoot; } @@ -29,6 +31,9 @@ public Task ResolveAsync( IReadOnlyDictionary fallbackOffsets, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(profileBuild); + ArgumentNullException.ThrowIfNull(signatureSets); + ArgumentNullException.ThrowIfNull(fallbackOffsets); // Signature scanning is CPU-bound and can take noticeable time. Run off the UI thread. return Task.Run(() => ResolveInternal(profileBuild, signatureSets, fallbackOffsets, cancellationToken), cancellationToken); } @@ -38,6 +43,9 @@ public Task ResolveAsync( IReadOnlyList signatureSets, IReadOnlyDictionary fallbackOffsets) { + ArgumentNullException.ThrowIfNull(profileBuild); + ArgumentNullException.ThrowIfNull(signatureSets); + ArgumentNullException.ThrowIfNull(fallbackOffsets); return ResolveAsync(profileBuild, signatureSets, fallbackOffsets, CancellationToken.None); } diff --git a/src/SwfocTrainer.Runtime/Services/SymbolHealthService.cs b/src/SwfocTrainer.Runtime/Services/SymbolHealthService.cs index f6b9cb02..4b3451e9 100644 --- a/src/SwfocTrainer.Runtime/Services/SymbolHealthService.cs +++ b/src/SwfocTrainer.Runtime/Services/SymbolHealthService.cs @@ -15,6 +15,8 @@ public sealed class SymbolHealthService : ISymbolHealthService public SymbolValidationResult Evaluate(SymbolInfo symbol, TrainerProfile profile, RuntimeMode mode) { + ArgumentNullException.ThrowIfNull(symbol); + ArgumentNullException.ThrowIfNull(profile); if (symbol.Address == nint.Zero) { return new SymbolValidationResult( diff --git a/src/SwfocTrainer.Runtime/Services/TelemetryLogTailService.cs b/src/SwfocTrainer.Runtime/Services/TelemetryLogTailService.cs index e707e085..862610df 100644 --- a/src/SwfocTrainer.Runtime/Services/TelemetryLogTailService.cs +++ b/src/SwfocTrainer.Runtime/Services/TelemetryLogTailService.cs @@ -55,12 +55,13 @@ public TelemetryModeResolution ResolveLatestMode( return null; } + var parentDirectory = Directory.GetParent(processDirectory)?.FullName ?? processDirectory; var candidates = new[] { Path.Combine(processDirectory, "_LogFile.txt"), Path.Combine(processDirectory, "LogFile.txt"), Path.Combine(processDirectory, "corruption", "LogFile.txt"), - Path.Combine(Directory.GetParent(processDirectory)?.FullName ?? processDirectory, "corruption", "LogFile.txt") + Path.Combine(parentDirectory, "corruption", "LogFile.txt") }; return candidates diff --git a/src/SwfocTrainer.Runtime/Services/ValueFreezeService.cs b/src/SwfocTrainer.Runtime/Services/ValueFreezeService.cs index c1b052c6..81902d86 100644 --- a/src/SwfocTrainer.Runtime/Services/ValueFreezeService.cs +++ b/src/SwfocTrainer.Runtime/Services/ValueFreezeService.cs @@ -32,6 +32,8 @@ public sealed class ValueFreezeService : IValueFreezeService public ValueFreezeService(IRuntimeAdapter runtime, ILogger logger, int pulseIntervalMs) { + ArgumentNullException.ThrowIfNull(runtime); + ArgumentNullException.ThrowIfNull(logger); _runtime = runtime; _logger = logger; _timer = new Timer(PulseCallback, null, pulseIntervalMs, pulseIntervalMs); @@ -49,6 +51,7 @@ public IReadOnlyCollection GetFrozenSymbols() public void FreezeInt(string symbol, int value) { + ArgumentNullException.ThrowIfNull(symbol); _entries[symbol] = new FreezeEntry(symbol, FreezeKind.Int32, IntValue: value); _logger.LogInformation("Freeze registered: {Symbol} = {Value} (int)", symbol, value); } @@ -60,6 +63,7 @@ public void FreezeInt(string symbol, int value) /// public void FreezeIntAggressive(string symbol, int value) { + ArgumentNullException.ThrowIfNull(symbol); // Remove from regular entries to avoid double-writing. _entries.TryRemove(symbol, out _); @@ -70,18 +74,21 @@ public void FreezeIntAggressive(string symbol, int value) public void FreezeFloat(string symbol, float value) { + ArgumentNullException.ThrowIfNull(symbol); _entries[symbol] = new FreezeEntry(symbol, FreezeKind.Float, FloatValue: value); _logger.LogInformation("Freeze registered: {Symbol} = {Value} (float)", symbol, value); } public void FreezeBool(string symbol, bool value) { + ArgumentNullException.ThrowIfNull(symbol); _entries[symbol] = new FreezeEntry(symbol, FreezeKind.Bool, BoolValue: value); _logger.LogInformation("Freeze registered: {Symbol} = {Value} (bool)", symbol, value); } public bool Unfreeze(string symbol) { + ArgumentNullException.ThrowIfNull(symbol); var removedRegular = _entries.TryRemove(symbol, out _); var removedAggressive = _aggressiveEntries.TryRemove(symbol, out _); var removed = removedRegular || removedAggressive; @@ -104,6 +111,7 @@ public void UnfreezeAll() public bool IsFrozen(string symbol) { + ArgumentNullException.ThrowIfNull(symbol); return _entries.ContainsKey(symbol) || _aggressiveEntries.ContainsKey(symbol); } diff --git a/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.Chains.cs b/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.Chains.cs index d9fe7f8a..2dfe2bc2 100644 --- a/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.Chains.cs +++ b/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.Chains.cs @@ -6,6 +6,7 @@ internal static class WorkshopInventoryChainResolver { internal static IReadOnlyList ResolveChains(IReadOnlyList items) { + ArgumentNullException.ThrowIfNull(items); if (items.Count == 0) { return Array.Empty(); diff --git a/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.cs b/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.cs index 8830f4be..faccc748 100644 --- a/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.cs +++ b/src/SwfocTrainer.Runtime/Services/WorkshopInventoryService.cs @@ -22,12 +22,14 @@ public sealed class WorkshopInventoryService : IWorkshopInventoryService private readonly HttpClient _httpClient; public WorkshopInventoryService(ILogger logger) - : this(logger, CreateHttpClient()) + : this(logger ?? throw new ArgumentNullException(nameof(logger)), CreateHttpClient()) { } internal WorkshopInventoryService(ILogger logger, HttpClient httpClient) { + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(httpClient); _logger = logger; _httpClient = httpClient; } @@ -36,6 +38,7 @@ public async Task DiscoverInstalledAsync( WorkshopInventoryRequest request, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(request); var appId = string.IsNullOrWhiteSpace(request.AppId) ? DefaultAppId : request.AppId.Trim(); var diagnostics = new List(); var installedIds = ReadInstalledWorkshopIds(request, appId, diagnostics); diff --git a/src/SwfocTrainer.Saves/Internal/SaveSchemaRepository.cs b/src/SwfocTrainer.Saves/Internal/SaveSchemaRepository.cs index db136c9d..eb0f358f 100644 --- a/src/SwfocTrainer.Saves/Internal/SaveSchemaRepository.cs +++ b/src/SwfocTrainer.Saves/Internal/SaveSchemaRepository.cs @@ -16,6 +16,7 @@ internal sealed class SaveSchemaRepository public SaveSchemaRepository(SaveOptions options) { + ArgumentNullException.ThrowIfNull(options); _options = options; } diff --git a/src/SwfocTrainer.Saves/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Saves/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Saves/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/src/SwfocTrainer.Saves/Services/BinarySaveCodec.cs b/src/SwfocTrainer.Saves/Services/BinarySaveCodec.cs index a05b73ff..50b5ae3b 100644 --- a/src/SwfocTrainer.Saves/Services/BinarySaveCodec.cs +++ b/src/SwfocTrainer.Saves/Services/BinarySaveCodec.cs @@ -20,6 +20,8 @@ public sealed class BinarySaveCodec : ISaveCodec public BinarySaveCodec(SaveOptions options, ILogger logger) { + ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(logger); _schemaRepository = new SaveSchemaRepository(options); _logger = logger; } diff --git a/src/SwfocTrainer.Saves/Services/SavePatchPackService.cs b/src/SwfocTrainer.Saves/Services/SavePatchPackService.cs index a98a32d1..d11172c6 100644 --- a/src/SwfocTrainer.Saves/Services/SavePatchPackService.cs +++ b/src/SwfocTrainer.Saves/Services/SavePatchPackService.cs @@ -24,6 +24,7 @@ public sealed class SavePatchPackService : ISavePatchPackService public SavePatchPackService(SaveOptions options) { + ArgumentNullException.ThrowIfNull(options); _schemaRepository = new SaveSchemaRepository(options); } diff --git a/src/SwfocTrainer.Transplant/Properties/AssemblyInfo.cs b/src/SwfocTrainer.Transplant/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2f949b5a --- /dev/null +++ b/src/SwfocTrainer.Transplant/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(true)] +[assembly: InternalsVisibleTo("SwfocTrainer.Tests")] diff --git a/tests/SwfocTrainer.Tests/SwfocTrainer.Tests.csproj b/tests/SwfocTrainer.Tests/SwfocTrainer.Tests.csproj index 587f6a27..a736f8f6 100644 --- a/tests/SwfocTrainer.Tests/SwfocTrainer.Tests.csproj +++ b/tests/SwfocTrainer.Tests/SwfocTrainer.Tests.csproj @@ -6,11 +6,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tools/visual-chromatic/package-lock.json b/tools/visual-chromatic/package-lock.json index 1e54cdfc..a927ad09 100644 --- a/tools/visual-chromatic/package-lock.json +++ b/tools/visual-chromatic/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "swfoc-mod-menu-visual-chromatic", "devDependencies": { - "@chromatic-com/playwright": "^0.12.8", + "@chromatic-com/playwright": "^0.12.3", "@playwright/test": "1.58.2", "chromatic": "^16.0.0" } @@ -47,19 +47,20 @@ } }, "node_modules/@chromatic-com/playwright": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@chromatic-com/playwright/-/playwright-0.12.8.tgz", - "integrity": "sha512-2oi8ghd+2N+hDI0Y65Ac/aYfsvDTyDHvEZMNS4Ln7VzyZ6SdGLoQLOvP80IGJlekTI68K8gQpzXewRV2chUaZQ==", + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@chromatic-com/playwright/-/playwright-0.12.3.tgz", + "integrity": "sha512-H/os4WuIq1RqkTnMmKJmKPXaguAhlIUkdeUyn/deNUtnab0RAgbIdChoa9blBqCEQ0pJzvl9EzVh11Cmr6AR7g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@chromaui/rrweb-snapshot": "2.0.0-alpha.18-noAbsolute", - "@segment/analytics-node": "2.1.3", - "@storybook/addon-essentials": "~8.5.8", + "@segment/analytics-node": "^1.1.0", + "@storybook/addon-essentials": "^8.6.0", "@storybook/csf": "^0.1.0", - "@storybook/manager-api": "~8.5.8", - "@storybook/server-webpack5": "~8.5.8", - "storybook": "~8.5.8", + "@storybook/manager-api": "^8.6.0", + "@storybook/server-webpack5": "^8.6.0", + "storybook": "^8.6.0", "ts-dedent": "^2.2.0" }, "bin": { @@ -619,6 +620,7 @@ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.58.2" }, @@ -630,22 +632,22 @@ } }, "node_modules/@segment/analytics-core": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.7.0.tgz", - "integrity": "sha512-0DHSriS/oAB/2bIgOMv3fFV9/ivp39ibdOTTf+dDOhf+vlciBv0+MHw47k/6PRobbuls27cKkKZAKc4DDC2+gw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.4.1.tgz", + "integrity": "sha512-kV0Pf33HnthuBOVdYNani21kYyj118Fn+9757bxqoksiXoZlYvBsFq6giNdCsKcTIE1eAMqNDq3xE1VQ0cfsHA==", "dev": true, "license": "MIT", "dependencies": { "@lukeed/uuid": "^2.0.0", - "@segment/analytics-generic-utils": "1.2.0", - "dset": "^3.1.4", + "@segment/analytics-generic-utils": "1.1.1", + "dset": "^3.1.2", "tslib": "^2.4.1" } }, "node_modules/@segment/analytics-generic-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz", - "integrity": "sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.1.1.tgz", + "integrity": "sha512-THTIzBPHnvu1HYJU3fARdJ3qIkukO3zDXsmDm+kAeUks5R9CBXOQ6rPChiASVzSmwAIIo5uFIXXnCraojlq/Gw==", "dev": true, "license": "MIT", "dependencies": { @@ -653,28 +655,27 @@ } }, "node_modules/@segment/analytics-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-2.1.3.tgz", - "integrity": "sha512-xwMkyXgr7xgPsP0w79nzCwRHYi9jzj9ps4Im7xWGK8AKKE4eox39tMZOdRtpDbvXQlrs9fh64ZC0w/yZZDM/9g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-1.3.0.tgz", + "integrity": "sha512-lRLz1WZaDokMoUe299yP5JkInc3OgJuqNNlxb6j0q22umCiq6b5iDo2gRmFn93reirIvJxWIicQsGrHd93q8GQ==", "dev": true, "license": "MIT", "dependencies": { "@lukeed/uuid": "^2.0.0", - "@segment/analytics-core": "1.7.0", - "@segment/analytics-generic-utils": "1.2.0", + "@segment/analytics-core": "1.4.1", + "@segment/analytics-generic-utils": "1.1.1", "buffer": "^6.0.3", - "jose": "^5.1.0", "node-fetch": "^2.6.7", "tslib": "^2.4.1" }, "engines": { - "node": ">=18" + "node": ">=14" } }, "node_modules/@storybook/addon-actions": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.5.8.tgz", - "integrity": "sha512-7J0NAz+WDw1NmvmKIh0Qr5cxgVRDPFC5fmngbDNxedk147TkwrgmqOypgEi/SAksHbTWxJclbimoqdcsNtWffA==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.6.14.tgz", + "integrity": "sha512-mDQxylxGGCQSK7tJPkD144J8jWh9IU9ziJMHfB84PKpI/V5ZgqMDnpr2bssTrUaGDqU5e1/z8KcRF+Melhs9pQ==", "dev": true, "license": "MIT", "dependencies": { @@ -689,13 +690,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-backgrounds": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.5.8.tgz", - "integrity": "sha512-TsQFagQ95+d7H3/+qUZKI2B0SEK8iu6CV13cyry9Dm59nn2bBylFrwx4I3xDQUOWMiSF6QIRjCYzxKQ/jJ5OEg==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.6.14.tgz", + "integrity": "sha512-l9xS8qWe5n4tvMwth09QxH2PmJbCctEvBAc1tjjRasAfrd69f7/uFK4WhwJAstzBTNgTc8VXI4w8ZR97i1sFbg==", "dev": true, "license": "MIT", "dependencies": { @@ -708,13 +709,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-controls": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.5.8.tgz", - "integrity": "sha512-3iifI8mBGPsiPmV9eAYk+tK9i+xuWhVsa+sXz01xTZ/0yoOREpp972hka86mtCqdDTOJIpzh1LmxvB218OssvQ==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.6.14.tgz", + "integrity": "sha512-IiQpkNJdiRyA4Mq9mzjZlvQugL/aE7hNgVxBBGPiIZG6wb6Ht9hNnBYpap5ZXXFKV9p2qVI0FZK445ONmAa+Cw==", "dev": true, "license": "MIT", "dependencies": { @@ -727,20 +728,20 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-docs": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.5.8.tgz", - "integrity": "sha512-zKVUqE0UGiq1gZtY2TX57SYB4RIsdlbTDxKW2JZ9HhZGLvZ5Qb7AvdiKTZxfOepGhuw3UcNXH/zCFkFCTJifMw==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.6.14.tgz", + "integrity": "sha512-Obpd0OhAF99JyU5pp5ci17YmpcQtMNgqW2pTXV8jAiiipWpwO++hNDeQmLmlSXB399XjtRDOcDVkoc7rc6JzdQ==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/blocks": "8.5.8", - "@storybook/csf-plugin": "8.5.8", - "@storybook/react-dom-shim": "8.5.8", + "@storybook/blocks": "8.6.14", + "@storybook/csf-plugin": "8.6.14", + "@storybook/react-dom-shim": "8.6.14", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -750,25 +751,25 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-essentials": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.5.8.tgz", - "integrity": "sha512-sCNvMZqL6dywnyHuZBrWl4f6QXsvpJHOioL3wJJKaaRMZmctbFmS0u6J8TQjmgZhQfyRzuJuhr1gJg9oeqp6AA==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.6.14.tgz", + "integrity": "sha512-5ZZSHNaW9mXMOFkoPyc3QkoNGdJHETZydI62/OASR0lmPlJ1065TNigEo5dJddmZNn0/3bkE8eKMAzLnO5eIdA==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/addon-actions": "8.5.8", - "@storybook/addon-backgrounds": "8.5.8", - "@storybook/addon-controls": "8.5.8", - "@storybook/addon-docs": "8.5.8", - "@storybook/addon-highlight": "8.5.8", - "@storybook/addon-measure": "8.5.8", - "@storybook/addon-outline": "8.5.8", - "@storybook/addon-toolbars": "8.5.8", - "@storybook/addon-viewport": "8.5.8", + "@storybook/addon-actions": "8.6.14", + "@storybook/addon-backgrounds": "8.6.14", + "@storybook/addon-controls": "8.6.14", + "@storybook/addon-docs": "8.6.14", + "@storybook/addon-highlight": "8.6.14", + "@storybook/addon-measure": "8.6.14", + "@storybook/addon-outline": "8.6.14", + "@storybook/addon-toolbars": "8.6.14", + "@storybook/addon-viewport": "8.6.14", "ts-dedent": "^2.0.0" }, "funding": { @@ -776,13 +777,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-highlight": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.5.8.tgz", - "integrity": "sha512-kkldtFrY0oQJY/vfNLkV66hVgtp66OO8T68KoZFsmUz4a3iYgzDS8WF+Av2/9jthktFvMchjFr8NKOno9YBGIg==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.6.14.tgz", + "integrity": "sha512-4H19OJlapkofiE9tM6K/vsepf4ir9jMm9T+zw5L85blJZxhKZIbJ6FO0TCG9PDc4iPt3L6+aq5B0X29s9zicNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -793,13 +794,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-measure": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.5.8.tgz", - "integrity": "sha512-xf84ByTRkFPoNSck6Z5OJ0kXTYAYgmg/0Ke0eCY/CNgwh7lfjYQBrcjuKiYZ6jyRUMLdysXzIfF9/2MeFqLfIg==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.6.14.tgz", + "integrity": "sha512-1Tlyb72NX8aAqm6I6OICsUuGOP6hgnXcuFlXucyhKomPa6j3Eu2vKu561t/f0oGtAK2nO93Z70kVaEh5X+vaGw==", "dev": true, "license": "MIT", "dependencies": { @@ -811,13 +812,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-outline": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.5.8.tgz", - "integrity": "sha512-NAC9VWZFg2gwvduzJRVAtxPeQfJjB8xfDDgcGjgLOCSQkZDDOmGVdLXf78pykMQKyuu/0YZ989KufAac6kRG5g==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.6.14.tgz", + "integrity": "sha512-CW857JvN6OxGWElqjlzJO2S69DHf+xO3WsEfT5mT3ZtIjmsvRDukdWfDU9bIYUFyA2lFvYjncBGjbK+I91XR7w==", "dev": true, "license": "MIT", "dependencies": { @@ -829,13 +830,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-toolbars": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.5.8.tgz", - "integrity": "sha512-AfGdMNBp+vOjyiFKlOyUFLIU0kN1QF4PhVBqd0vYkWAk2w9n6a/ZlG0TcJGe7K5+bcvmZDAerYMKbDMSeg9bAw==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.6.14.tgz", + "integrity": "sha512-W/wEXT8h3VyZTVfWK/84BAcjAxTdtRiAkT2KAN0nbSHxxB5KEM1MjKpKu2upyzzMa3EywITqbfy4dP6lpkVTwQ==", "dev": true, "license": "MIT", "funding": { @@ -843,13 +844,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/addon-viewport": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.5.8.tgz", - "integrity": "sha512-SdoRb4bH99Knj2R+rTcMQQxHrtcIO1GLzTFitAefxBE1OUkq8FNLHMHd0Ip/sCQGLW/5F03U70R2uh7SkhBBYA==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.6.14.tgz", + "integrity": "sha512-gNzVQbMqRC+/4uQTPI2ZrWuRHGquTMZpdgB9DrD88VTEjNudP+J6r8myLfr2VvGksBbUMHkGHMXHuIhrBEnXYA==", "dev": true, "license": "MIT", "dependencies": { @@ -860,17 +861,16 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/blocks": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.5.8.tgz", - "integrity": "sha512-O6tJDJM83fDm3ZP1+lTf24l7HOTzSRXkkMDD7zB/JHixzlj9p6wI4UQc2lplLadDCa5ya1IwyE7zUDN/0UfC5Q==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.6.14.tgz", + "integrity": "sha512-rBMHAfA39AGHgkrDze4RmsnQTMw1ND5fGWobr9pDcJdnDKWQWNRD7Nrlxj0gFlN3n4D9lEZhWGdFrCbku7FVAQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf": "0.1.12", "@storybook/icons": "^1.2.12", "ts-dedent": "^2.0.0" }, @@ -881,7 +881,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^8.5.8" + "storybook": "^8.6.14" }, "peerDependenciesMeta": { "react": { @@ -892,24 +892,14 @@ } } }, - "node_modules/@storybook/blocks/node_modules/@storybook/csf": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.12.tgz", - "integrity": "sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^2.19.0" - } - }, "node_modules/@storybook/builder-webpack5": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.5.8.tgz", - "integrity": "sha512-QaBIMyqWX/eQs4laQBXvAW9M/ylk73WljJySPlTl+8PNVuDtHli24oBJXwx5aV1NT53BLsaKAn/vb2QNL4+G1Q==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.6.18.tgz", + "integrity": "sha512-rg73TpqIUzXc66c/AaQ4kuc8yiZ+tStvy5fb1OnFYZ9rAeYQejDD0OIIaI2rqtX5XYuxC+yQEGitMntlIMV0og==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "8.5.8", + "@storybook/core-webpack": "8.6.18", "@types/semver": "^7.3.4", "browser-assert": "^1.2.1", "case-sensitive-paths-webpack-plugin": "^2.4.0", @@ -939,7 +929,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.18" }, "peerDependenciesMeta": { "typescript": { @@ -948,9 +938,9 @@ } }, "node_modules/@storybook/components": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.5.8.tgz", - "integrity": "sha512-PPEMqWPXn7rX+qISaOOv9CDSuuvG538f0+4M5Ppq2LwpjXecgOG5ktqJF0ZqxmTytT+RpEaJmgjGW0dMAKZswA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.18.tgz", + "integrity": "sha512-55yViiZzPS/cPBuOeW4QGxGqrusjXVyxuknmbYCIwDtFyyvI/CgbjXRHdxNBaIjz+IlftxvBmmSaOqFG5+/dkA==", "dev": true, "license": "MIT", "funding": { @@ -962,13 +952,13 @@ } }, "node_modules/@storybook/core": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.5.8.tgz", - "integrity": "sha512-OT02DQhkGpBgn5P+nZOZmbzxqubC4liVqbhpjp/HOGi5cOA3+fCJzDJeSDTu+pPh7dZnopC4XnR+5dWjtOJHdA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.18.tgz", + "integrity": "sha512-dRBP2TnX6fGdS0T2mXBHjkS/3Nlu1ra1huovZVFuM67CYMzrhM/3hX/zru1vWSC5rqY93ZaAhjMciPW4pK5mMQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf": "0.1.12", + "@storybook/theming": "8.6.18", "better-opn": "^3.0.2", "browser-assert": "^1.2.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", @@ -994,9 +984,9 @@ } }, "node_modules/@storybook/core-webpack": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.5.8.tgz", - "integrity": "sha512-M2LNQdYp0br8fgKMVtBh7YIo8mQsgALLc4i9PEXRS7wrp+bhvVnA9qhd5xDPzb0Rl4CHYbs4Yvkzo7ZQMibeIQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.6.18.tgz", + "integrity": "sha512-M+y/DFbiT3CJYQ90wJdXT4WxYImphof1f11StZSxJGo0u5PnCCdCze1qchXubApXRDO2T8HGxurXfhTEMqaGsA==", "dev": true, "license": "MIT", "dependencies": { @@ -1007,17 +997,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" - } - }, - "node_modules/@storybook/core/node_modules/@storybook/csf": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.12.tgz", - "integrity": "sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^2.19.0" + "storybook": "^8.6.18" } }, "node_modules/@storybook/csf": { @@ -1031,9 +1011,9 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.5.8.tgz", - "integrity": "sha512-9p+TFutbvtPYEmg14UsvqBDWKP/p/+OkIdi+gkwCMw0yiJF/+7ErMHDB0vr5SpJpU7SFQmfpY2c/LaglEtaniw==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.6.14.tgz", + "integrity": "sha512-dErtc9teAuN+eelN8FojzFE635xlq9cNGGGEu0WEmMUQ4iJ8pingvBO1N8X3scz4Ry7KnxX++NNf3J3gpxS8qQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1044,7 +1024,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/global": { @@ -1069,9 +1049,9 @@ } }, "node_modules/@storybook/manager-api": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.5.8.tgz", - "integrity": "sha512-ik3yikvYxAJMDFg0s3Pm7hZWucAlkFaaO7e2RlfOctaJFdaEi3evR4RS7GdmS38uKBEk31RC7x+nnIJkqEC59A==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.18.tgz", + "integrity": "sha512-BjIp12gEMgzFkEsgKpDIbZdnSWTZpm2dlws8WiPJCpgJtG+HWSxZ0/Ms30Au9yfwzQEKRSbV/5zpsKMGc2SIJw==", "dev": true, "license": "MIT", "funding": { @@ -1083,15 +1063,15 @@ } }, "node_modules/@storybook/preset-server-webpack": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/preset-server-webpack/-/preset-server-webpack-8.5.8.tgz", - "integrity": "sha512-0QA23bmhchmec6zI0JzoXWIA723n8XhEtJndUQDS96cTyDGyb3AuedNgwu5xQw9Zv+EJZjO84uGcNYinPEnmKA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/preset-server-webpack/-/preset-server-webpack-8.6.18.tgz", + "integrity": "sha512-MAZTClcWuDQ7Z6pUWuWPANgs7ep+PLUR3uv+/0hFh8uhJ2DZMrynomPVO5bE+RWY/TtdmcfGy4k9mZUmVOMCMQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "8.5.8", + "@storybook/core-webpack": "8.6.18", "@storybook/global": "^5.0.0", - "@storybook/server": "8.5.8", + "@storybook/server": "8.6.18", "safe-identifier": "^0.4.1", "ts-dedent": "^2.0.0", "yaml-loader": "^0.8.0" @@ -1104,13 +1084,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.18" } }, "node_modules/@storybook/preview-api": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.5.8.tgz", - "integrity": "sha512-HJoz2o28VVprnU5OG6JO6CHrD3ah6qVPWixbnmyUKd0hOYF5dayK5ptmeLyUpYX56Eb2KoYcuVaeQqAby4RkNw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.18.tgz", + "integrity": "sha512-joXRXh3GdVvzhbfIgmix1xs90p8Q/nja7AhEAC2egn5Pl7SKsIYZUCYI6UdrQANb2myg9P552LKXfPect8llKg==", "dev": true, "license": "MIT", "funding": { @@ -1122,9 +1102,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.5.8.tgz", - "integrity": "sha512-UT/kGJHPW+HLNCTmI1rV1to+dUZuXKUTaRv2wZ2BUq2/gjIuePyqQZYVQeb0LkZbuH2uviLrPfXpS5d3/RSUJw==", + "version": "8.6.14", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.14.tgz", + "integrity": "sha512-0hixr3dOy3f3M+HBofp3jtMQMS+sqzjKNgl7Arfuj3fvjmyXOks/yGjDImySR4imPtEllvPZfhiQNlejheaInw==", "dev": true, "license": "MIT", "funding": { @@ -1134,22 +1114,21 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.5.8" + "storybook": "^8.6.14" } }, "node_modules/@storybook/server": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/server/-/server-8.5.8.tgz", - "integrity": "sha512-DB3IR7CCUklTXtytbOOX7Vig1h9ohQ3BPauvf7WlYXLv4JlkO54oTu37rL0wbqgmmh4FOF41ZSX8z4h4w41How==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/server/-/server-8.6.18.tgz", + "integrity": "sha512-AbmorgUMeK86cenpsY9L/p3OwTqQ+u00dOKuO9TUm2kz2pwWOAaoaHLueBCKj6i6E4nToqxgl8s496uMC3+/Vg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/components": "8.5.8", - "@storybook/csf": "0.1.12", + "@storybook/components": "8.6.18", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "8.5.8", - "@storybook/preview-api": "8.5.8", - "@storybook/theming": "8.5.8", + "@storybook/manager-api": "8.6.18", + "@storybook/preview-api": "8.6.18", + "@storybook/theming": "8.6.18", "ts-dedent": "^2.0.0", "yaml": "^2.3.1" }, @@ -1161,19 +1140,19 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" + "storybook": "^8.6.18" } }, "node_modules/@storybook/server-webpack5": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/server-webpack5/-/server-webpack5-8.5.8.tgz", - "integrity": "sha512-bdRxp0kUGBstDdCEIfJEbsmM7vvyBES4Hjmj0jXcOefQYic5QTWTiDMrHgWPp8XWGjIoO+wOfWm2IXPP5Uceog==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/server-webpack5/-/server-webpack5-8.6.18.tgz", + "integrity": "sha512-vac5w4patPJGGtjx/9SfUC685I1YoGNerhrVzHwC4/eZ7MIuKq6kDQyUiDrtU7KjoQq6VqVDc5KofNpSKBeuFw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-webpack5": "8.5.8", - "@storybook/preset-server-webpack": "8.5.8", - "@storybook/server": "8.5.8" + "@storybook/builder-webpack5": "8.6.18", + "@storybook/preset-server-webpack": "8.6.18", + "@storybook/server": "8.6.18" }, "engines": { "node": ">=18.0.0" @@ -1183,23 +1162,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.5.8" - } - }, - "node_modules/@storybook/server/node_modules/@storybook/csf": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.12.tgz", - "integrity": "sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^2.19.0" + "storybook": "^8.6.18" } }, "node_modules/@storybook/theming": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.5.8.tgz", - "integrity": "sha512-/Rm6BV778sCT+3Ok861VYmw9BlEV5zcCq2zg5TOVuk8HqZw7H7VHtubVsjukEuhveYCs+oF+i2tv/II6jh6jdg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.18.tgz", + "integrity": "sha512-n6OEjEtHupa2PdTwWzRepr7cO8NkDd4rgF6BKLitRbujOspLxzMBEqdphs+QLcuiCIgf33SqmEA64QWnbSMhPw==", "dev": true, "license": "MIT", "funding": { @@ -1261,9 +1230,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", "dev": true, "license": "MIT", "dependencies": { @@ -1483,6 +1452,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1509,6 +1479,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1683,9 +1654,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", - "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "version": "2.10.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.14.tgz", + "integrity": "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1769,9 +1740,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -1788,12 +1759,13 @@ } ], "license": "MIT", + "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -1906,9 +1878,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001782", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz", - "integrity": "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==", + "version": "1.0.30001784", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", + "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", "dev": true, "funding": [ { @@ -2194,8 +2166,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", @@ -2369,9 +2340,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.328", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", - "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", "dev": true, "license": "ISC" }, @@ -2466,6 +2437,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -3250,16 +3222,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jose": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", - "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3354,9 +3316,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, @@ -3536,9 +3498,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, "license": "MIT" }, @@ -3777,6 +3739,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3933,6 +3896,7 @@ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3943,6 +3907,7 @@ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4214,13 +4179,14 @@ } }, "node_modules/storybook": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.5.8.tgz", - "integrity": "sha512-k3QDa7z4a656oO3Mx929KNm+xIdEI2nIDCKatVl1mA6vt+ge+uwoiG+ro182J9LOEppR5XXD2mQQi4u1xNsy6A==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.18.tgz", + "integrity": "sha512-p8seiSI6FiVY6P3V0pG+5v7c8pDMehMAFRWEhG5XqIBSQszzOjDnW2rNvm3odoLKfo3V3P6Cs6Hv9ILzymULyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@storybook/core": "8.5.8" + "@storybook/core": "8.6.18" }, "bin": { "getstorybook": "bin/index.cjs", @@ -4356,6 +4322,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4648,6 +4615,7 @@ "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -4726,6 +4694,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4812,6 +4781,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/tools/visual-chromatic/package.json b/tools/visual-chromatic/package.json index 6c5dc262..aaf2fd0a 100644 --- a/tools/visual-chromatic/package.json +++ b/tools/visual-chromatic/package.json @@ -6,7 +6,7 @@ "e2e:chromatic": "playwright test --config ./playwright.chromatic.config.js ./e2e/visual-pack.spec.js" }, "devDependencies": { - "@chromatic-com/playwright": "^0.12.8", + "@chromatic-com/playwright": "^0.12.3", "@playwright/test": "1.58.2", "chromatic": "^16.0.0" }