fix(lgpe): stop SAV7b party crashes + encounter NRE (#942–#946, #948, #949)#950
Merged
Conversation
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>
Contributor
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://black-coast-0df43410f-950.eastus2.2.azurestaticapps.net |
Contributor
There was a problem hiding this comment.
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 makeCompactPartya no-op forSAV7b. - Update party UI rendering and party write paths (save + file drop) to use the safe helpers.
- Add an
EncounterDatabaseTaboccupant 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. |
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 (HasPartyis false). An empty party slot holds theSLOT_EMPTYsentinel, so reading or writing it through PKHeX's index API (GetPartySlotAtIndex/SetPartySlotAtIndex/PartyData) throwsArgumentOutOfRangeException.The reported saves (emulator / JKSM dumps) report a
PartyCountlarger than the number of populated pointer slots — verified against the attached save files: each isSAV7bwith only slot 0 (partner Pikachu) valid and slots 1–5 holding the sentinel. PKHeX'sSetPartySlotAtIndexbumpsPartyCountbefore 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.EnsureTargetSlotSelectedAsyncdereferencedEditFormPokemonwithout a null check when a slot was selected but no occupant was loaded —NullReferenceExceptionwhile 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 (== PartyCountfor non-SAV7b)TrySetPartySlot(sav, pkm, index)→falsefor unwritable LGPE empty-pointer slotsCompactPartyis now a no-op forSAV7b(the list is self-maintained; iterating it would throw)PartyGrid.razor— render viaTryGetPartySlotPokemonSlotComponent.razor.cs— battle-ready count + drag-drop party write via the safe helpersAppService.SavePokemon— party write guarded withTrySetPartySlotEncounterDatabaseTab.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 stateLegalityReportTabandExportPartyAsShowdownwere already guarded viaHasParty, so they were unaffected.Verification
SAV7b, party-pointer sentinel confirmed).dotnet formatclean; 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,AppServicemove/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