Skip to content

Fix campaign save isolation bug and add JSON menu loader infrastructure#27

Draft
Copilot wants to merge 6 commits intofeature/ui-campaign-refactorfrom
copilot/implement-json-menu-system
Draft

Fix campaign save isolation bug and add JSON menu loader infrastructure#27
Copilot wants to merge 6 commits intofeature/ui-campaign-refactorfrom
copilot/implement-json-menu-system

Conversation

Copy link

Copilot AI commented Feb 7, 2026

Problem

Campaign progress corruption when switching campaigns. Root cause: single shared continue file fx1contn.sav used by all campaigns, causing Campaign A save loads to overwrite Campaign B's continue_level_number.

Changes

Campaign Save Isolation (Critical Fix)

  • Per-campaign continue files: fx1_<campaign>_contn.sav instead of shared file
  • Campaign name sanitization: Whitelist [a-zA-Z0-9_-] to prevent path traversal
  • Helper function: get_safe_campaign_name() for consistent sanitization across save/load
// Before: All campaigns shared fx1contn.sav
// After: Each campaign isolated
fx1_keeporig_contn.sav
fx1_ancntkpr_contn.sav  
fx1_burdnimp_contn.sav

Files: src/game_saves.c (+40 lines)

JSON Menu Loader (Phase 1)

Foundation for data-driven menu system to replace hardcoded GuiButtonInit arrays.

  • Parser: JSON → GuiButtonInit arrays using existing centijson library
  • Supports: position (with "center"POS_SCRCTR), size, sprites, text, callbacks
  • Memory safe: Proper cleanup, error handling, bounds checking

Files: src/menu_loader.{c,h} (396 lines), Makefile dependency chain

Integration deferred: Frontend wiring, callback registry, string mapping require additional work.

Breaking Change

⚠️ Old fx1contn.sav files won't be used. Players need to replay campaigns or add migration helper on first run.

Security

Path traversal blocked via input sanitization. Campaign names like ../../etc/passwdetcpasswd.

Original prompt

PROJECT CONTEXT:
You're working on KeeperFX, a C/C++ reverse-engineered game engine (Dungeon Keeper). The codebase uses a custom immediate-mode GUI system (bflib_guibtns) with hardcoded button arrays and binary sprite assets.

BRANCH: feature/ui-campaign-refactor (already pushed to dkfans/keeperfx)

OBJECTIVE:
Implement a data-driven JSON menu system to replace hardcoded UI definitions, and fix campaign save isolation bug where loading saves from different campaigns corrupts progress.

COMPLETED WORK:

Architecture document: docs/ui-system-architecture.md - 15-page spec covering JSON schema, sprite composition (3-piece buttons), 8-frame hover animations, resolution scaling (units_per_pixel), Lua integration, and 8-step migration plan
Reference assets: docs/dev-analysis/assets/ contains 10 game files:
Sprites: frontbit.dat/tab, gui1-64.dat/tab, gui2-64.dat/tab
Backgrounds: front.raw (640×480 paletted), front.pal
Localization: gtext_eng.dat
Save example: fx1g0000.sav
Example JSON: data/menus/campaigns_menu.json demonstrates schema
VSCode extension: Working prototype in separate keeperfx-devtools repo (TypeScript + React, 5 components for visual editing)
YOUR TASKS:

Phase 1: JSON Menu Loader (Priority)

Create src/menu_loader.c + src/menu_loader.h
Integrate cJSON library (already used elsewhere in codebase)
Parse JSON menu definitions into GuiButtonInit arrays at runtime
Support fields: id, position (x/y), size (width/height), sprite, text (localized key), tooltip, callback (Lua function), hotkey
Wire Campaigns menu button to main menu (replace Continue/New/High Scores)
Phase 2: Campaign Save Isolation

Fix bug in game_saves.c lines 220-230: LbFileRead(&game, sizeof(struct Game), gload) loads SGC_GameOrig chunk which overwrites continue_level_number
Create per-campaign save directories: saves/<campaign_name>/ (e.g., saves/keeporig/, saves/ancntkpr/)
Store campaign progress in separate saves//progress.dat files
Remove SGC_GameOrig chunk writes/reads entirely
Update save/load paths in set_continue_level_filename() and related functions
Phase 3: Integration

Use Campaigns menu as proof-of-concept (already has JSON definition)
Test with multiple campaigns: keeporig (original), ancntkpr (Ancient Keeper), burdnimp (Burdened Imp)
Verify save isolation doesn't corrupt progress when switching campaigns
KEY TECHNICAL DETAILS:

Button rendering: 3-piece composition (left cap, tiled center, right cap) in frontend_draw_button() (gui_frontbtns.c lines 723-850)
Animations: 8 frames at 100ms intervals, large_button_sprite_anims[] array
Scaling: scale_ui_value(base) = base * units_per_pixel / 16 (640×480=16, 1920×1080=48)
Sprite loading: get_frontend_sprite(), get_button_sprite() in custom_sprites.c
Campaign definitions: campgns/*.cfg files
BUILD INSTRUCTIONS:

Use WSL with make (see Makefile)
VS Code tasks available: "Default task", "Compile, Copy Files"
Game directory: Read from launch.json cwd field
MINIMAL VIABLE IMPLEMENTATION:
Start with Step 2 of the integration plan (JSON loader only), skip Lua callbacks initially. Get one menu loading from JSON, rendering correctly, before tackling save isolation.

VALIDATION:

Campaigns menu displays with 2+ campaign buttons
Clicking campaign button starts that campaign
Saving in campaign A, switching to campaign B, then back to A preserves both progress values
No SGC_GameOrig chunks in new save files


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 5 commits February 7, 2026 19:29
Co-authored-by: Cerwym <1760289+Cerwym@users.noreply.github.com>
Co-authored-by: Cerwym <1760289+Cerwym@users.noreply.github.com>
Co-authored-by: Cerwym <1760289+Cerwym@users.noreply.github.com>
Co-authored-by: Cerwym <1760289+Cerwym@users.noreply.github.com>
Co-authored-by: Cerwym <1760289+Cerwym@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement JSON menu system and fix campaign save bug Fix campaign save isolation bug and add JSON menu loader infrastructure Feb 7, 2026
Copilot AI requested a review from Cerwym February 7, 2026 19:36
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.

2 participants