Skip to content

fix(lgpe): stop SAV7b party crashes + encounter NRE (#942–#946, #948, #949)#950

Merged
codemonkey85 merged 4 commits into
mainfrom
dev
Jun 1, 2026
Merged

fix(lgpe): stop SAV7b party crashes + encounter NRE (#942–#946, #948, #949)#950
codemonkey85 merged 4 commits into
mainfrom
dev

Conversation

@codemonkey85
Copy link
Copy Markdown
Owner

Summary

Fixes a cluster of crash reports in Let's Go (SAV7b) saves plus a separate Encounter Database NRE.

Root cause (LGPE party crash — #942, #943, #944, #945, #946, #948)

Let's Go (SAV7b) stores the party as a pointer list into unified storage (PokeListHeader), not a contiguous buffer (HasParty is false). An empty party slot holds the SLOT_EMPTY sentinel, so reading or writing it through PKHeX's index API (GetPartySlotAtIndex / SetPartySlotAtIndex / PartyData) throws ArgumentOutOfRangeException.

The reported saves (emulator / JKSM dumps) report a PartyCount larger than the number of populated pointer slots — verified against the attached save files: each is SAV7b with only slot 0 (partner Pikachu) valid and slots 1–5 holding the sentinel. PKHeX's SetPartySlotAtIndex bumps PartyCount before it dereferences the empty pointer and throws, so a single bad write (editing/adding an empty party slot) corrupts the in-memory count and cascades into repeated render crashes.

Separate bug (#949)

EncounterDatabaseTab.EnsureTargetSlotSelectedAsync dereferenced EditFormPokemon without a null check when a slot was selected but no occupant was loaded — NullReferenceException while generating an encounter (a Navel Rock Lugia).

Changes

  • Pkmds.Core/Extensions/SaveFileExtensions.cs — new safe helpers:
    • TryGetPartySlot(sav, index)PKM? (null instead of throwing)
    • GetSafePartyCount(sav) → readable leading-slot count (== PartyCount for non-SAV7b)
    • TrySetPartySlot(sav, pkm, index)false for unwritable LGPE empty-pointer slots
    • CompactParty is now a no-op for SAV7b (the list is self-maintained; iterating it would throw)
  • PartyGrid.razor — render via TryGetPartySlot
  • PokemonSlotComponent.razor.cs — battle-ready count + drag-drop party write via the safe helpers
  • AppService.SavePokemon — party write guarded with TrySetPartySlot
  • EncounterDatabaseTab.razor.cs — null-guard the occupant lookup ([Bug] [Crash] NullReferenceException: Arg_NullReferenceException Stack trace:… #949)
  • Pkmds.Tests/LgpePartySafetyTests.cs — regression tests covering the malformed-party state

LegalityReportTab and ExportPartyAsShowdown were already guarded via HasParty, so they were unaffected.

Verification

  • Diagnosed against the three attached user save files (all SAV7b, party-pointer sentinel confirmed).
  • dotnet format clean; Web Debug build green (0 warnings/errors); test project builds clean.

Follow-up (not in this PR)

~60 other party call sites (TradeTab/TradePane, Bulk import/export dialogs, BatchEditorService, SaveFileRepairDialog, TrainerInfoTab, PokemonBankTab, AddToBankDialog, AppService move/swap) still use the raw accessors and are latent SAV7b crashes. Scoped this PR to the reported paths; the rest can be swept in a separate PR.

Fixes #942, #943, #944, #945, #946, #948, #949.

🤖 Generated with Claude Code

codemonkey85 and others added 3 commits May 27, 2026 10:09
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…encounter NRE

Let's Go (SAV7b) stores the party as a pointer list into unified storage; an
empty party slot holds the SLOT_EMPTY sentinel, so reading or writing it through
PKHeX's index API throws ArgumentOutOfRangeException. Emulator / JKSM save dumps
can report a PartyCount larger than the number of populated pointer slots, and
PKHeX's SetPartySlotAtIndex bumps PartyCount *before* it throws — corrupting the
in-memory count so a single bad write cascades into repeated render crashes.

Add safe extension helpers and route the party read/write/compact paths through
them:
- SaveFileExtensions: TryGetPartySlot / GetSafePartyCount / TrySetPartySlot;
  CompactParty is now a no-op for SAV7b (the list is self-maintained and reading
  phantom slots would throw).
- PartyGrid render + PokemonSlotComponent battle-ready count: TryGetPartySlot.
- AppService.SavePokemon party case + PokemonSlotComponent drag-drop: guard with
  TrySetPartySlot (skip unwritable LGPE slots instead of crashing).
- EncounterDatabaseTab: null-guard the occupant lookup (separate NRE when
  generating into a slot with no loaded occupant).

Adds LgpePartySafetyTests covering the malformed-party state.

Fixes #942, #943, #944, #945, #946, #948 (LGPE party crash) and #949 (encounter NRE).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Azure Static Web Apps: Your stage site is ready! Visit it here: https://black-coast-0df43410f-950.eastus2.2.azurestaticapps.net

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses crash paths triggered by malformed Let’s Go (SAV7b) party metadata (pointer-list party with sentinel “empty” pointers) and fixes a separate EncounterDatabaseTab null dereference during encounter generation. It introduces safe party read/write helpers to prevent ArgumentOutOfRangeException crashes and adds regression tests for the malformed-party scenario.

Changes:

  • Add safe party helpers (TryGetPartySlot, TrySetPartySlot, GetSafePartyCount) and make CompactParty a no-op for SAV7b.
  • Update party UI rendering and party write paths (save + file drop) to use the safe helpers.
  • Add an EncounterDatabaseTab occupant null-guard and introduce LGPE party safety regression tests.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Pkmds.Core/Extensions/SaveFileExtensions.cs Adds safe party read/write helpers; makes CompactParty no-op for SAV7b.
Pkmds.Rcl/Components/PartyGrid.razor Uses TryGetPartySlot to avoid render crashes on phantom SAV7b party slots.
Pkmds.Rcl/Components/PokemonSlotComponent.razor.cs Uses safe helpers for battle-ready counting and party slot file-drop writes (with user warning on failure).
Pkmds.Rcl/Services/AppService.cs Guards party writes via TrySetPartySlot; includes unrelated switch formatting changes in WB8 import.
Pkmds.Rcl/Components/MainTabPages/EncounterDatabaseTab.razor.cs Fixes NRE by null-guarding the selected-slot occupant lookup.
Pkmds.Rcl/Components/MainTabPages/TrainerInfoTab.razor.cs Comment indentation adjustment only.
Pkmds.Tests/LgpePartySafetyTests.cs Adds regression tests for malformed SAV7b party-count / sentinel-pointer scenarios.

Comment thread Pkmds.Rcl/Services/AppService.cs
Comment thread Pkmds.Core/Extensions/SaveFileExtensions.cs Outdated
…false baseline

- TrySetPartySlot: wrap SetPartySlotAtIndex in try/catch for ArgumentOutOfRangeException
  so the helper never throws, matching its XML docs (a malformed pointer list could
  still resolve to an out-of-range offset past the guard).
- SavePokemon: return early (instead of break) when the party write fails, so
  SnapshotEditFormBaseline doesn't mark the form saved for a write that never happened.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codemonkey85 codemonkey85 enabled auto-merge June 1, 2026 02:02
@codemonkey85 codemonkey85 merged commit b3783ff into main Jun 1, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] [Crash] ArgumentOutOfRangeException: Arg_ArgumentOutOfRangeException Arg…

2 participants