pst-codebase
DevelopmentPalworldSaveTools (PST) architecture map — repo layout, entry chain, the palsav SAV<->GVAS<->JSON pipeline, the palworld_aio GUI structure, game data locations, and the custom pytest dynamic-import registry. Load FIRST whenever working anywhere in this repo so you orient on real structure, not guesses.
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/deafdudecomputers/PalworldSaveTools/blob/HEAD/.opencode/skills/pst-codebase/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/pst-codebase, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
PalworldSaveTools (PST) — Codebase Map
Maintain this as the source of truth. When the layout changes, patch this skill. Project: PalworldSaveTools v2.0.0, GitHub deadafdudecomputers/PalworldSaveTools, author Pylar. MIT. PySide6 desktop GUI + CLI save tools for Palworld.
Active branch (cyrix's dev): upd/ProjRefactor. Python >=3.11. Package mgr: uv (workspace). venv at .venv.
Entry chain
start.py(repo root) — creates/ensures uv venv at.venv, deletes staleuv.lock, then runssrc/bootup.pywith the venv python.src/bootup.py— splash screen + dependency/version checks; launches the GUI. GUI deps are optional: set envPST_NO_GUI=1(or any truthy value) to skip PySide6 import — essential for headless/test/CI runs.PST_DEBUG=1enables verbose bootup logging.src/palworld_aio/main.py— the actual PySide6 "All-in-One" app +ui/main_window.py.
Bootup path constants live in src/boot_paths.py: ROOT_DIR, CONFIG_DIR, RESOURCES_DIR, GUI_DIR.
Source layout (src/)
src/
palsav/ # SERIALIZATION ENGINE (workspace pkg "palsav-flex")
palsav/
core.py # decompress_sav_to_gvas / compress_gvas_to_sav
gvas.py # GvasFile.read/load/write/dump (UE GVAS container)
archive.py # FArchiveReader / FArchiveWriter (UE binary archive)
paltypes.py # PALWORLD_CUSTOM_PROPERTIES, PALWORLD_TYPE_HINTS
json_tools.py # orjson-backed dump/load
_cityhash.py # city hash for save integrity
compressor/ # oozlib (Oodle, default) + zlib backends, enums
rawdata/ # PER-ENTITY raw decoders (character, group, base_camp,
# map_data, map_model, item_container, work*, etc.)
commands/ # auto_update, backup, convert, diag, resave_test,
# roundtrip_validation (CLI subcommands)
cli.py # `palsav` CLI entry (SAV<->JSON)
palworld_aio/ # THE GUI APP ("All-in-One Tools") — RESTRUCTURED into sub-packages
main.py # app entry -> ui/main_window.py
constants.py # global state hub (50+ importers, STAYS at root)
utils.py # shared helpers (STAYS at root, external importers)
updater.py # update checking
managers/ # ★ business logic (operates on constants.loaded_level_json)
save_manager, player_manager, guild_manager, base_manager,
data_manager, func_manager, zone_manager, backup_manager
inventory/ # ★ inventory + container subsystem
inventory_manager, base_inventory_manager, standardized_container,
dynamic_item_manager, dynamic_item, container_ownership
editor/ # ★ pal + world editing engine
pal_editor/ (17 modules + shim, partitioned from edit_pals.py), dialogs, worldoption_editor
map/ # map generation
map_generator
ui/ # views (sub-organized)
main_window.py # QMainWindow god-class (2144 lines)
tabs/ # tools_tab, base_inventory_tab, inventory_tab, pal_editor_tab, map_tab, container_ids_tab
dialogs/ # container_selector_dialog, player_item_dialog, player_pal_dialog, player_technology_dialog, skill_picker, tab_guide_dialog
chrome/ # header_widget, sidebar_widget, results_widget, styled_combo, styles (ThemeManager), menus
map_view/ # map_view (MapGraphicsView), map_markers, map_items, map_effects
widgets/ # reusable Qt widgets (unchanged): search_panel, stats_panel, menu_popup, scrollable_context_menu, hover overlays, collapsible_splitter, loading_popup, tree_widgets
palworld_toolsets/ # CLI tools: fix_host_save, game_pass_save_fix,
# character_transfer, convert_generic, convertids,
# modify_save, restore_map, slot_injector, xgp_save_extract
palworld_xgp_import/ # Xbox Game Pass save import (gamepass_manager, container_types)
palworld_coord/ # coordinate transforms
(top-level) bootup.py, boot_paths.py, common.py, palobject.py,
qt_imports.py, resource_resolver.py, path_setup.py,
loading_manager.py, import_libs.py, i18n/__init__.py, nerd_btn.py
The palsav pipeline (the heart of the project)
SAV bytes <-> GVAS (UE struct) <-> Python dict/JSON
- SAV->JSON:
decompress_sav_to_gvas(raw)->GvasFile.read(gvas, PALWORLD_TYPE_HINTS, PALWORLD_CUSTOM_PROPERTIES)->.dump()->json_tools.dump. - JSON->SAV:
json_tools.load->GvasFile.load(data)->.write(PALWORLD_CUSTOM_PROPERTIES)->compress_gvas_to_sav(gvas_bytes, compression_level). PALWORLD_CUSTOM_PROPERTIESmaps property type names -> (read_fn, write_fn) decoders. Therawdata/modules implement the per-entity decode/encode for each Palworld property type (CharacterSaveParameterMap, GroupSaveDataMap, MapObjectSaveData, ItemContainerSaveData, work collections, base camps, map models, etc.).- Default compression is Oodle (
compressor/oozlib.py, via pyoozle). zlib is the fallback (--library zlib). orjsonfor speed; GC is disabled during parsing then re-enabled.
Resources & data
resources/game_data/*.json— static game reference:characters(pal data + passives),items,skills,boss_mapping,pal_exp_table,friendship,relic_data,reference_unlock_data,uidata,world,world_map_areas.resources/i18n/*.json— 8 langs (en_US, zh_CN, de_DE, es_ES, fr_FR, ru_RU, ja_JP, ko_KR);resources/i18n/config.jsonis the lang registry. i18n loader atsrc/i18n/__init__.py.src/data/configs/*.json— app presets:base_inventory_loadouts,equipment_loadouts,inventory_loadouts,passive_loadouts,zone_exclusions, plusconfig.json.resources/tab_guide/— in-app help.resources/readme/— localized READMEs.
Testing (cyrix's preferred route)
Custom dynamic-import registry — tests NEVER hardcode import paths:
tests/test_registry.py->MODULE_MAPmaps logical name ->{import_as, parent}(or{installed: True}for installed pkgs like palsav).- Tests do:
from tests.dynamic_importer import import_fromthenX = import_from('palsav.archive', 'FArchiveReader'). tests/conftest.py->pytest_sessionstartinjects parent dirs into sys.path.- To move a source module: update only MODULE_MAP. Zero test files change. This is the restructure-safe design of this repo — respect it.
Commands (run from repo root, venv active):
pytest— fast suite (~186 tests, ~0.6s; excludes@pytest.mark.slow).pytest -m slow— only the save-file roundtrip (~40s, decompresses/recompresses a 3.2MB V1 beta save intests/save_test/).pytest -m ""— everything (~206 tests).python scripts/scrs/test_interactively.py— menu runner (--quick/--all).
Structural audit auto-runs at session start via conftest (tests/harness/: file_pairer, graph_validator, resource_auditor, structural_cache). It gates the suite: file pairing + import graph + resource path integrity + relative-import resolution check (catches broken from .X/from ..X that lazy/in-function code hides from runtime tests). Flags:
--skip-structural— skip ALL structural checks.--no-deep-audit— skip file pairing + import graph (keep resource audit).--no-strict-paths— skip the deep AST resource-path audit.--dump-structural— print full report without aborting.
Test layout: tests/unit/{core_logic,palsav_core,palworld_aio_tests,scripts}, tests/integration/{test_imports,test_palworld_save,test_resource_integrity}.
Repo composition (measured)
~2900 tracked files, but 2467 are in resources/game_data (mostly 2470 .webp icons — pals/npcs/items/passives/structures/technologies/elements/ui). Real source: 213 files in src/, 35 tests, 31 scripts. Total Python LOC ~50,643. Heaviest files: pal_editor/ (partitioned from edit_pals.py, now 17 modules + shim), func_manager.py (2657), base_inventory_tab.py (2606), update_game_data.py (2362), inventory_tab.py (2203), main_window.py (2144), map_tab.py (1916). palsav engine (archive.py 809) is dwarfed by the GUI layer.
Structural audit findings (known issues)
- STALE .gitattributes: references
src/palsav/pyooz/(deleted in 1e3eff6, renamed to palooz). The 15 SIMDe .h headers now under palooz are NOT marked linguist-vendored → GitHub C++ language stats inflated. Fix: update path tosrc/palsav/palooz/ooz/dep/ooz/simde/**. - Icon storage split 3 ways:
resources/assets/icons(16, app icons),resources/game_data/icons(2456, game entity icons),src/data/icon/(2 .ico — paladius/xenolord, look misplaced/orphaned). - src/ top-level flat namespace: 10 loose .py files (boot_paths, bootup, common, import_libs, loading_manager, nerd_btn, palobject, path_setup, qt_imports, resource_resolver) mixing boot/Qt/app concerns. import_libs.py is a star-import re-export hub (namespace pollution risk).
- .gitignore
lib/is global: could shadow a future legit lib dir. palsav/palsav/lib/ (compiled palooz .so, platform-specific) is gitignored — on-disk build output in the source tree. - 9 .cmd wrappers in scripts/ duplicate the uv-venv bootstrap logic each (Windows launcher pattern).
Gotchas
- The repo has a
dist/andbuild/dir full of Nuitka/cx_Freeze build artifacts and a.venv— always exclude these when scanning files (findwithout filters drowns in noise). Scan only real source: exclude.git,.venv,dist,build,__pycache__,*.egg-info,.pytest_cache. - There is no
AGENTS.md/CLAUDE.md/.cursorrulesyet — repo conventions live in README + tests/README.md + this skill. palsav(the engine) andpaloozare installed packages; everything else undersrc/is path-imported.- Save format versioning: tests target V1 beta saves. Mind format changes across Palworld updates (see
commands/auto_update.py,commands/diag.py).
How to grow this knowledge
Build focused per-section skills as we actually work there, e.g. pst-save-pipeline (deep palsav internals), pst-pal-editor (pal_editor_tab + edit_pals + characters.json schema), pst-game-data-json (the resources/game_data schemas), pst-map-tab, pst-inventory. Patch THIS skill's layout map whenever a module moves or a new package appears.