-- Engine-owned contract for generated ROM caches. -- -- A cache is playable only when its versioned marker matches the ROM and every -- required output for that version exists. Extraction writers may differ by -- platform, but they must publish through this contract so partial staging -- cannot look ready to the runtime. local GameVersion = require("src.core.GameVersion") local CacheContract = {} -- engine/battle/animations.asm:2418 CacheContract.FORMAT = "rom-cache-v12-gen1:" CacheContract.VERSION_FORMAT = { -- v11: Gen 2 maps carry their object list's ROM address, which a .sav -- export re-anchoring a save onto another map writes back into -- wCurMapObjectEventsPointer. A v10 cache has no address to write, and -- such an export is refused until the ROM re-imports. gold = "rom-cache-v13:", silver = "rom-cache-v13:", crystal = "rom-cache-v13-crystal6:", -- engine/overworld/map_sprites.asm:181, engine/battle/animations.asm:2600 -- data/pikachu/pikachu_pic_animation.asm:340 yellow = "rom-cache-v12-yellow2:", -- v8: M4A tracks retain reachable patterns and explicit entry offsets. firered = "rom-cache-v24-firered:", leafgreen = "rom-cache-v9-leafgreen:", emerald = "rom-cache-v4-emerald:", -- pokeruby/src/string_util.c:408 ruby = "rom-cache-v6-ruby:", sapphire = "rom-cache-v6-sapphire:", } CacheContract.MARKER_PATH = "rom-cache.complete" CacheContract.REQUIRED_FILES = { "data/generated/constants.lua", "data/generated/maps.lua", "data/generated/text.lua", "data/generated/field.lua", "data/generated/battle_anims.lua", "assets/generated/title/pokemon_logo.png", "assets/generated/fonts/font.png", "assets/generated/battle/front/pikachu.png", "assets/generated/battle/anims/move_anim_0.png", "assets/generated/battle/anims/move_anim_1.png", "assets/generated/audio/programs.bin", "assets/generated/trade/game_boy.png", -- engine/items/town_map.asm:296, 150 "assets/generated/townmap/nest.png", "assets/generated/townmap/up_arrow.png", } CacheContract.VERSION_REQUIRED_FILES = { yellow = { "assets/generated/battle/trainers/jessie_james.png", "assets/generated/battle/profoakb.png", "assets/generated/pikachu/pikapic_1.png", "assets/generated/pikachu/pikapic_2.png", "assets/generated/pikachu/pikapic_3.png", "assets/generated/pikachu/pikapic_4.png", "assets/generated/pikachu/pikapic_5.png", "assets/generated/pikachu/pikapic_6.png", "assets/generated/pikachu/pikapic_7.png", "assets/generated/pikachu/pikapic_8.png", "assets/generated/pikachu/pikapic_9.png", "assets/generated/pikachu/pikapic_10.png", "assets/generated/pikachu/pikapic_11.png", "assets/generated/pikachu/pikapic_12.png", "assets/generated/pikachu/pikapic_13.png", "assets/generated/pikachu/pikapic_14.png", "assets/generated/pikachu/pikapic_15.png", "assets/generated/pikachu/pikapic_16.png", "assets/generated/pikachu/pikapic_17.png", "assets/generated/pikachu/pikapic_18.png", "assets/generated/pikachu/pikapic_19.png", "assets/generated/pikachu/pikapic_20.png", "assets/generated/pikachu/pikapic_21.png", "assets/generated/pikachu/pikapic_22.png", "assets/generated/pikachu/pikapic_23.png", "assets/generated/pikachu/pikapic_24.png", "assets/generated/pikachu/pikapic_25.png", "assets/generated/pikachu/pikapic_26.png", "assets/generated/pikachu/pikapic_27.png", "assets/generated/pikachu/pikapic_28.png", "assets/generated/pikachu/gfx_e4841.png", "assets/generated/pikachu/gfx_e4ce0.png", "assets/generated/pikachu/gfx_e4e70.png", "assets/generated/pikachu/gfx_e50af.png", "assets/generated/pikachu/gfx_e52fe.png", "assets/generated/pikachu/gfx_e5541.png", "assets/generated/pikachu/gfx_e5794.png", "assets/generated/pikachu/gfx_e59ed.png", "assets/generated/pikachu/gfx_e5c4d.png", "assets/generated/pikachu/gfx_e5e90.png", "assets/generated/pikachu/gfx_e6020.png", "assets/generated/pikachu/gfx_e61b0.png", "assets/generated/pikachu/gfx_e63f7.png", "assets/generated/pikachu/gfx_e6646.png", "assets/generated/pikachu/gfx_e682f.png", "assets/generated/pikachu/gfx_e69bf.png", "assets/generated/pikachu/gfx_e6b4f.png", "assets/generated/pikachu/gfx_e6cdf.png", "assets/generated/pikachu/gfx_e6e6f.png", "assets/generated/pikachu/gfx_e6fff.png", "assets/generated/pikachu/gfx_e718f.png", "assets/generated/pikachu/gfx_e731f.png", "assets/generated/pikachu/gfx_e74af.png", "assets/generated/pikachu/gfx_e763f.png", "assets/generated/pikachu/gfx_e7863.png", "assets/generated/pikachu/gfx_e79f3.png", "assets/generated/pikachu/gfx_e7b83.png", "assets/generated/pikachu/gfx_e7d13.png", "assets/generated/pikachu/gfx_f0b64.png", "assets/generated/pikachu/gfx_f0d82.png", "assets/generated/minigame/surf_1a.png", "assets/generated/minigame/surf_1b.png", "assets/generated/minigame/surf_1c.png", "assets/generated/minigame/title_bg.png", "assets/generated/minigame/intro_pika_0.png", }, } CacheContract.VERSION_REQUIRED_FILES_OVERRIDE = { gold = { "data/generated/constants.lua", "data/generated/maps.lua", "data/generated/roofs.lua", "data/generated/sprites.lua", "data/generated/scripts.lua", "data/generated/text.lua", -- The engine's label-keyed strings are separate from Gen 2 script text. -- Caches made before RomExtractorGen2:extractText must be rebuilt so -- src/core/RomText.lua does not silently fall back to built-in wording. "data/generated/rom_text.lua", "data/generated/pokemon.lua", "data/generated/tilesets.lua", "data/generated/audio.lua", "data/generated/marts.lua", "assets/generated/fonts/font.png", "assets/generated/fonts/frames.png", "assets/generated/title/pokemon_logo.png", "assets/generated/title/title_screen.png", "assets/generated/title/hooh.png", "assets/generated/title/hooh_5.png", "assets/generated/title/clouds.png", "assets/generated/title/copyright_splash.png", "data/generated/oak_speech.lua", "assets/generated/intro/oak.png", "assets/generated/intro/cal.png", "assets/generated/tilesets/johto.png", "assets/generated/tilesets/roofs/new_bark.png", "assets/generated/sprites/chris.png", "assets/generated/battle/front/chikorita.png", "assets/generated/battle/front/pikachu.png", "assets/generated/battle/front/marill.png", "assets/generated/battle/trainers/falkner.png", "assets/generated/battle/hud/balls.png", "assets/generated/audio/programs.bin", "assets/generated/slots/gold_slots_1.png", "assets/generated/card_flip/card_flip_1.png", "assets/generated/pc/mail_item.png", -- engine/events/fishing_gfx.asm:23 "assets/generated/emotes/fishing.png", -- data/sprites/emotes.asm:19, engine/events/field_moves.asm:390 "assets/generated/emotes/jump_shadow.png", "assets/generated/emotes/cut_grass.png", -- engine/pokegear/pokegear.asm:2298 "assets/generated/pokegear/nest_icon.png", }, crystal = { "data/generated/constants.lua", "data/generated/maps.lua", "data/generated/roofs.lua", "data/generated/sprites.lua", "data/generated/scripts.lua", "data/generated/text.lua", "data/generated/rom_text.lua", "data/generated/events.lua", "data/generated/pokemon.lua", "data/generated/encounters.lua", "data/generated/tilesets.lua", "data/generated/landmarks.lua", "data/generated/audio.lua", "data/generated/marts.lua", "data/generated/oak_speech.lua", "data/generated/title.lua", "data/generated/intro.lua", "assets/generated/fonts/font.png", "assets/generated/fonts/frames.png", -- ../pokecrystal/gfx/font.asm:60 "assets/generated/fonts/map_entry_sign.png", "assets/generated/title/crystal_logo.png", "assets/generated/title/crystal_wordmark.png", "assets/generated/title/crystal_suicune.png", "assets/generated/title/copyright_splash.png", "assets/generated/splash/ditto.png", "assets/generated/intro/chris.png", "assets/generated/intro/kris.png", "assets/generated/intro/suicune_run_sprites.png", "assets/generated/intro/unowns_tiles.png", "assets/generated/intro/oak.png", "assets/generated/tilesets/johto.png", "assets/generated/tilesets/roofs/new_bark.png", "assets/generated/sprites/chris.png", "assets/generated/sprites/kris.png", "assets/generated/battle/front/chikorita.png", "assets/generated/battle/front/wooper.png", "assets/generated/battle/front/pikachu.png", "assets/generated/battle/trainers/falkner.png", "assets/generated/battle/hud/balls.png", "assets/generated/audio/programs.bin", "assets/generated/slots/gold_slots_1.png", "assets/generated/card_flip/card_flip_1.png", "assets/generated/pc/mail_item.png", "assets/generated/trainer_card/card_f.png", "data/generated/mobile_gfx.lua", "assets/generated/battle/player_back_female.png", "assets/generated/battle/trainers/kris.png", "assets/generated/battle/trainers/chris.png", -- ../pokecrystal/engine/events/fishing_gfx.asm:38-42 "assets/generated/emotes/fishing.png", -- data/sprites/emotes.asm:19, engine/events/field_moves.asm:390 "assets/generated/emotes/jump_shadow.png", "assets/generated/emotes/cut_grass.png", -- engine/pokegear/pokegear.asm:2298 "assets/generated/pokegear/nest_icon.png", }, firered = { "data/generated/gba/help/pack.lua", "data/generated/gba/quest_log/pack.lua", "data/generated/gba/objects/pack.lua", "data/generated/gba/meta.json", "data/generated/gba/maps.json", "data/generated/gba/audio/meta.json", "data/generated/intro.lua", "data/generated/gba/intro/meta.json", "data/generated/gba/intro/oak.png", "data/generated/gba/intro/boy.png", "data/generated/gba/intro/girl.png", "data/generated/gba/intro/rival.png", "data/generated/gba/intro/title_screen.png", "data/generated/gba/intro/title_logo.png", "data/generated/gba/intro/box_art_mon.png", "data/generated/gba/intro/press_start.png", "data/generated/gba/intro/platform.png", "data/generated/gba/intro/oak_speech_bg.png", "data/generated/gba/intro/controls_page1.png", "data/generated/gba/intro/pikachu_intro_bg.png", "data/generated/gba/intro/nidoran_f.png", "data/generated/gba/naming/manifest.lua", "data/generated/gba/ow/manifest.lua", "data/generated/gba/ow/palette_manifest.lua", "data/generated/gba/ow/0.rgba", "data/generated/gba/ow/7.rgba", "data/generated/gba/pokemon/manifest.lua", "data/generated/gba/pokemon/names.lua", "data/generated/gba/pokemon/front/1.rgba", "data/generated/gba/pokemon/front/200.rgba", "data/generated/gba/pokemon/front/411.rgba", "data/generated/gba/pokemon/back/1.rgba", "data/generated/gba/pokemon/back/200.rgba", "data/generated/gba/pokemon/back/411.rgba", "data/generated/gba/pokemon/icons/1.rgba", "data/generated/gba/pokemon/icons/200.rgba", "data/generated/gba/pokemon/icons/411.rgba", -- src/pokedex_screen.c:930 "data/generated/gba/pokemon/pokedex/paper_bg.rgba", -- src/pokedex_screen.c:2901 "data/generated/gba/pokemon/pokedex/footprints/1.rgba", "data/generated/gba/pokemon/pokedex/footprints/bulbasaur.rgba", "data/generated/gba/pokemon/pokedex/footprints/question_mark.rgba", "data/generated/gba/region_map/kanto_map.png", "data/generated/gba/region_map/cursor.png", "data/generated/gba/region_map/player_red.png", -- src/region_map.c:425 "data/generated/gba/region_map/fly_icon.rgba", "data/generated/gba/region_map/fly_icon.png", "data/generated/gba/region_map/map_sections.lua", -- src/heal_location.c:62, src/region_map.c:4023 "data/generated/gba/region_map/heal_locations.lua", "data/generated/gba/region_map/fly_destinations.lua", "data/generated/gba/scripts/multichoice.lua", "data/generated/gba/pokemon/stats.lua", "data/generated/gba/pokemon/learnsets.lua", "data/generated/gba/pokemon/move_names.lua", "data/generated/gba/pokemon/battle_moves.lua", "data/generated/gba/pokemon/battle/manifest.lua", "data/generated/gba/pokemon/battle/healthbox_player.rgba", "data/generated/gba/pokemon/battle/healthbox_doubles_player.rgba", "data/generated/gba/pokemon/battle/healthbox_doubles_opponent.rgba", "data/generated/gba/pokemon/battle/hp_bold_digits.rgba", "data/generated/gba/pokemon/battle/terrain_building.rgba", -- src/battle_bg.c:439 "data/generated/gba/pokemon/battle/terrain_grass.rgba", "data/generated/gba/pokemon/battle/terrain_cave.rgba", "data/generated/gba/pokemon/battle/terrain_water.rgba", "data/generated/gba/pokemon/battle/terrain_champion.rgba", "data/generated/gba/pokemon/battle/terrain_bg_cave.rgba", "data/generated/gba/pokemon/battle/ball_open/manifest.lua", "data/generated/gba/pokemon/battle/ball_open/particles.rgba", "data/generated/gba/pokemon/battle/ball_open/balls.rgba", "data/generated/gba/pokemon/battle_transition/manifest.lua", "data/generated/gba/pokemon/battle_transition/big_pokeball.rgba", "data/generated/gba/pokemon/battle_transition/sliding_pokeball.rgba", "data/generated/gba/pokemon/party/slot_main.rgba", -- src/party_menu.c:2273 "data/generated/gba/pokemon/party/slot_main_multi.rgba", "data/generated/gba/pokemon/party/slot_main_multi_selected.rgba", "data/generated/gba/pokemon/party/slot_wide_multi.rgba", "data/generated/gba/pokemon/party/slot_wide_multi_selected.rgba", -- src/data/party_menu.h:664 "data/generated/gba/pokemon/party/hold_icons.rgba", "data/generated/gba/items/bag/manifest.lua", "data/generated/gba/items/bag/bg.rgba", "data/generated/gba/items/bag/bg_female.rgba", "data/generated/gba/items/bag/list.rgba", "data/generated/gba/items/bag/list_female.rgba", "data/generated/gba/items/bag/list_blank.rgba", "data/generated/gba/items/bag/list_blank_female.rgba", "data/generated/gba/items/bag/desc_sel.rgba", "data/generated/gba/items/bag/red_arrow.rgba", -- src/item_menu.c:569, src/item_menu_icons.c:150 "data/generated/gba/items/bag/bg_itempc.rgba", "data/generated/gba/items/bag/bg_itempc_female.rgba", "data/generated/gba/items/bag/swap_line.rgba", -- src/item_pc.c:435 "data/generated/gba/items/item_pc/bg.rgba", "data/generated/gba/items/item_pc/bg_submenu.rgba", -- src/pokedex_screen.c:1161 "data/generated/gba/pokemon/pokedex/chrome.lua", "data/generated/gba/items/shop/manifest.lua", "data/generated/gba/items/shop/bg.rgba", "data/generated/gba/doors/manifest.lua", "data/generated/gba/doors/pallet.rgba", "data/generated/gba/native/manifest.lua", -- src/scrcmd.c:711, include/constants/layouts.h:253,267,268,308 "data/generated/gba/native/layouts/alt_264.mid", "data/generated/gba/native/layouts/alt_278.mid", "data/generated/gba/native/layouts/alt_279.mid", "data/generated/gba/native/layouts/alt_319.mid", "data/generated/gba/pokemon/summary/manifest.lua", "data/generated/gba/pokemon/summary/menu_info.rgba", -- src/pokemon_summary_screen.c:1862 "data/generated/gba/pokemon/summary/bg3_info.rgba", "data/generated/gba/pokemon/summary/bg3_moves.rgba", "data/generated/gba/pokemon/summary/layer_moves_info.rgba", "data/generated/gba/pokemon/summary/progress_moves_info_select.rgba", "data/generated/gba/pokemon/summary/markings.rgba", "data/generated/gba/pokemon/storage/manifest.lua", "data/generated/gba/pokedex/manifest.lua", "data/generated/gba/chrome/manifest.lua", "data/generated/gba/chrome/menu_message_rgba.rgba", "data/generated/gba/chrome/std_rgba.rgba", "data/generated/gba/chrome/signpost_rgba.rgba", "data/generated/gba/chrome/user_frame_0.rgba", "data/generated/gba/chrome/user_frame_9.rgba", "data/generated/gba/chrome/fonts/latin_normal_fg.rgba", "data/generated/gba/chrome/fonts/latin_widths.lua", -- src/text.c:100 "data/generated/gba/chrome/fonts/latin_small_fg.rgba", "data/generated/gba/chrome/fonts/latin_small_shadow.rgba", "data/generated/gba/chrome/fonts/latin_small_widths.lua", -- src/text.c:141, :227, :228 (the Japanese fonts) "data/generated/gba/chrome/fonts/japanese_normal_fg.rgba", "data/generated/gba/chrome/fonts/japanese_normal_shadow.rgba", "data/generated/gba/chrome/fonts/japanese_widths.lua", "data/generated/gba/chrome/fonts/japanese_small_fg.rgba", "data/generated/gba/chrome/fonts/japanese_small_shadow.rgba", -- src/braille_text.c:15 "data/generated/gba/chrome/fonts/braille_fg.rgba", "data/generated/gba/chrome/fonts/braille_shadow.rgba", "data/generated/gba/chrome/fonts/braille.lua", -- src/seagallop.c:41 "data/generated/gba/seagallop/manifest.lua", "data/generated/gba/seagallop/water.4bpp", "data/generated/gba/seagallop/ferry.4bpp", "data/generated/gba/seagallop/wake.4bpp", "data/generated/gba/seagallop/wb_tilemap.bin", "data/generated/gba/seagallop/eb_tilemap.bin", "data/generated/gba/seagallop/wb.rgba", "data/generated/gba/seagallop/eb.rgba", "data/generated/gba/trainers.lua", "data/generated/gba/trainers/back_0.rgba", "data/generated/gba/trainers/back_1.rgba", "data/generated/gba/trainer_card/manifest.lua", "data/generated/gba/trainer_card/bg.rgba", "data/generated/gba/field_effects/tall_grass.rgba", "data/generated/gba/field_effects/cut_grass.rgba", "data/generated/gba/field_effects/rock_smash.rgba", "data/generated/gba/field_effects/surf_blob.rgba", "data/generated/gba/field_effects/fly_bird.rgba", "data/generated/gba/field_effects/ripple.rgba", -- src/field_effect.c:3963 (Birth Island meteorite shatter shards) "data/generated/gba/field_effects/deoxys_rock_fragments.rgba", -- src/data/field_effects/field_effect_objects.h:565,1203 "data/generated/gba/field_effects/splash.rgba", "data/generated/gba/field_effects/hot_springs_water.rgba", "data/generated/gba/field_effects/emoticons.rgba", -- src/slot_machine.c:399, :739 "data/generated/gba/slot_machine/manifest.lua", "data/generated/gba/slot_machine/reel_icons.rgba", "data/generated/gba/slot_machine/clefairy.rgba", "data/generated/gba/slot_machine/digits.rgba", "data/generated/gba/slot_machine/bg.rgba", "data/generated/gba/slot_machine/payout_lights.rgba", "data/generated/gba/slot_machine/match_lines.rgba", "data/generated/gba/slot_machine/button_pressed.rgba", "data/generated/gba/slot_machine/combos_window.rgba", -- src/trade_scene.c:151 "data/generated/gba/trade/manifest.lua", "data/generated/gba/trade/gba_screen.rgba", "data/generated/gba/trade/gba_screen_wireless.rgba", "data/generated/gba/trade/gba_screen_flash.rgba", "data/generated/gba/trade/cable_closeup.rgba", "data/generated/gba/trade/cable_end.rgba", "data/generated/gba/trade/link_mon_glow.rgba", "data/generated/gba/trade/link_mon_shadow.rgba", -- src/trade_scene.c:1121 "data/generated/gba/trade/mon_shadow_bg.rgba", "data/generated/gba/trade/ball.rgba", "data/generated/gba/trade/ball_spin.rgba", -- src/trade.c:1368 "data/generated/gba/trade/menu_bg1.rgba", "data/generated/gba/trade/stripes_bg2.rgba", "data/generated/gba/trade/stripes_bg3.rgba", "data/generated/gba/trade/party_box.rgba", "data/generated/gba/trade/moves_box.rgba", "data/generated/gba/trade/mon_box.rgba", "data/generated/gba/trade/menu_tiles.rgba", "data/generated/gba/trade/cursor.rgba", -- src/link_rfu_3.c:34, src/union_room_chat_objects.c:30 "data/generated/gba/union_room/manifest.lua", "data/generated/gba/union_room/wireless_icon.rgba", "data/generated/gba/union_room/chat_bg.rgba", "data/generated/gba/union_room/chat_panel.rgba", "data/generated/gba/union_room/chat_icons.rgba", "data/generated/gba/union_room/chat_selector_cursor.rgba", "data/generated/gba/union_room/chat_text_entry_cursor.rgba", "data/generated/gba/union_room/chat_char_select_cursor.rgba", "data/generated/gba/union_room/chat_r_button.rgba", -- src/wireless_communication_status_screen.c:50 "data/generated/gba/wireless_status/manifest.lua", "data/generated/gba/wireless_status/bg.rgba", "data/generated/gba/wireless_status/bg_index.bin", "data/generated/gba/wireless_status/palettes.pal", -- src/fame_checker.c:119, src/graphics.c:1230 "data/generated/gba/fame_checker/manifest.lua", "data/generated/gba/fame_checker/bg.rgba", "data/generated/gba/fame_checker/pick_panel.rgba", "data/generated/gba/fame_checker/0.rgba", "data/generated/gba/fame_checker/1.rgba", "data/generated/gba/fame_checker/13.rgba", "data/generated/gba/fame_checker/14.rgba", "data/generated/gba/fame_checker/cursor.rgba", "data/generated/gba/fame_checker/question_mark.rgba", "data/generated/gba/fame_checker/silhouette.pal", "data/generated/gba/fame_checker/pack.lua", -- src/graphics.c:1117, src/teachy_tv.c:526 "data/generated/gba/teachy_tv/manifest.lua", "data/generated/gba/teachy_tv/screen.rgba", "data/generated/gba/teachy_tv/title.rgba", "data/generated/gba/teachy_tv/end.rgba", -- src/teachy_tv.c:637, :1218 "data/generated/gba/teachy_tv/static.rgba", "data/generated/gba/teachy_tv/bg3.rgba", -- src/mystery_gift_show_card.c:150, src/mystery_gift_show_news.c:99 "data/generated/gba/mystery_gift/manifest.lua", "data/generated/gba/mystery_gift/card_bg0.rgba", "data/generated/gba/mystery_gift/card_bg7.rgba", "data/generated/gba/mystery_gift/news_bg0.rgba", "data/generated/gba/mystery_gift/news_bg7.rgba", -- src/trainer_tower_sets.c:8956 "data/generated/gba/trainer_tower.lua", -- src/data/pokemon/tutor_learnsets.h:22 "data/generated/gba/pokemon/tutor.lua", -- src/trainer_tower.c:554, include/constants/layouts.h:355, :363 "data/generated/gba/native/layouts/alt_366.mid", "data/generated/gba/native/layouts/alt_373.mid", "data/generated/gba/native/layouts/alt_374.mid", "data/generated/gba/native/layouts/alt_381.mid", -- src/script_menu.c:1161 "data/generated/gba/museum/manifest.lua", "data/generated/gba/museum/kabutops.rgba", "data/generated/gba/museum/aerodactyl.rgba", -- src/region_map.c:790 sAnim_DungeonIconVisited "data/generated/gba/region_map/dungeon_icon_visited.rgba", "data/generated/gba/region_map/dungeon_icon_visited.png", -- src/learn_move.c:403 "data/generated/gba/move_relearner/manifest.lua", "data/generated/gba/move_relearner/bg.rgba", -- src/daycare.c:137, :138, :139 "data/generated/gba/pokemon/egg/manifest.lua", "data/generated/gba/pokemon/egg/hatch.rgba", "data/generated/gba/pokemon/egg/shard.rgba", "data/generated/gba/pokemon/front/412.rgba", -- src/party_menu.c:2655 "data/generated/gba/pokemon/icons/412.rgba", -- src/battle_records.c:563 "data/generated/gba/trainer_tower/manifest.lua", "data/generated/gba/trainer_tower/records_bg.rgba", -- src/trainer_card.c:265, :1454, :1560 "data/generated/gba/trainer_card/front_0.rgba", "data/generated/gba/trainer_card/front_4_female.rgba", "data/generated/gba/trainer_card/back_0.rgba", "data/generated/gba/trainer_card/back_4_female.rgba", "data/generated/gba/trainer_card/screen_0.rgba", "data/generated/gba/trainer_card/screen_4_female.rgba", "data/generated/gba/trainer_card/star.rgba", "data/generated/gba/trainer_card/stickers.rgba", -- src/credits.c:815, :1101, :1230 "data/generated/gba/credits/manifest.lua", "data/generated/gba/credits/pack.lua", "data/generated/gba/credits/copyright.rgba", "data/generated/gba/credits/the_end.rgba", "data/generated/gba/credits/circle.rgba", "data/generated/gba/credits/pokeball_0.rgba", "data/generated/gba/credits/mon_0_1.rgba", "data/generated/gba/credits/player_male.rgba", "data/generated/gba/credits/rival.rgba", "data/generated/gba/credits/ground_grass.rgba", -- src/field_specials.c:2133 "data/generated/gba/league/lighting.lua", -- src/diploma.c:119 "data/generated/gba/diploma/manifest.lua", "data/generated/gba/diploma/kanto.rgba", "data/generated/gba/diploma/national.rgba", -- src/hall_of_fame.c:1163, :1181 "data/generated/gba/hall_of_fame/manifest.lua", "data/generated/gba/hall_of_fame/bands.rgba", "data/generated/gba/hall_of_fame/stripes.rgba", "data/generated/gba/hall_of_fame/confetti.rgba", -- src/data/field_effects/field_effect_objects.h:288, src/itemfinder.c:39 "data/generated/gba/field_effects/ground_impact_dust.rgba", "data/generated/gba/field_effects/itemfinder_arrow_star.rgba", "data/generated/gba/field_effects/arrow.rgba", -- src/field_effect.c:73, :77 "data/generated/gba/field_effects/field_move_streaks_outdoors.rgba", "data/generated/gba/field_effects/field_move_streaks_indoors.rgba", -- src/pokemon.c:5904, src/decompress.c:83 "data/generated/gba/pokemon/front_shiny/1.rgba", "data/generated/gba/pokemon/back_shiny/1.rgba", "data/generated/gba/pokemon/front_shiny/385_3.rgba", "data/generated/gba/pokemon/front/413.rgba", "data/generated/gba/pokemon/back/439.rgba", "data/generated/gba/pokemon/front_shiny/439.rgba", "data/generated/gba/pokemon/back_shiny/439.rgba", "data/generated/gba/pokemon/icons/439.rgba", -- src/pokemon.c:1350, :5339 "data/generated/gba/pokemon/spinda/front.4bpp", "data/generated/gba/pokemon/spinda/normal.gbapal", "data/generated/gba/pokemon/spinda/shiny.gbapal", "data/generated/gba/pokemon/spinda/spots.bin", -- src/ss_anne.c:21-22 "data/generated/gba/field_effects/ss_anne_wake.rgba", "data/generated/gba/field_effects/ss_anne_smoke.rgba", -- src/data/trainer_graphics/front_pic_tables.h:153 "data/generated/gba/trainers/front/0.rgba", "data/generated/gba/trainers/front/147.rgba", -- src/pokeball.c:61 "data/generated/gba/intro/ball_poke.png", -- src/battle_message.c:517, src/item_menu.c:183, src/oak_speech.c:588 "data/generated/gba/scripts/text_tables.lua", -- src/easy_chat.c:41, src/data/easy_chat/easy_chat_groups.h:26 "data/generated/gba/easy_chat/words.lua", -- src/fldeff_flash.c:157-162 "data/generated/gba/cave_transition/screen.bin", "data/generated/gba/cave_transition/palettes.lua", -- src/data/ingame_trades.h:1, :184 "data/generated/gba/trades/ingame_trades.lua", -- src/pokemon.c:1666, :6206 "data/generated/gba/trainers/union_room_classes.lua", -- src/region_map.c:393-427, :527, :3158-3171, :3359 "data/generated/gba/region_map/manifest.lua", "data/generated/gba/region_map/layouts.lua", "data/generated/gba/region_map/section_geometry.lua", "data/generated/gba/region_map/sevii123_map.png", "data/generated/gba/region_map/sevii45_map.png", "data/generated/gba/region_map/sevii67_map.png", "data/generated/gba/region_map/switch_button.png", "data/generated/gba/region_map/navel_rock_patch.png", "data/generated/gba/region_map/birth_island_patch.png", "data/generated/gba/region_map/frame_normal.png", "data/generated/gba/region_map/frame_fly.png", "data/generated/gba/region_map/switch_menu_123.png", "data/generated/gba/region_map/switch_menu_all.png", "data/generated/gba/region_map/switch_cursor_left.png", "data/generated/gba/region_map/switch_cursor_right.png", "data/generated/gba/region_map/edge_top_left.png", "data/generated/gba/region_map/edge_top_right.png", "data/generated/gba/region_map/edge_mid_left.png", "data/generated/gba/region_map/edge_mid_right.png", "data/generated/gba/region_map/edge_bottom_left.png", "data/generated/gba/region_map/edge_bottom_right.png", -- data/battle_ai_scripts.s:17 "data/generated/gba/battle_ai/pack.lua", -- src/menu_indicators.c:258 "data/generated/gba/chrome/scroll_arrows.rgba", "data/generated/gba/chrome/red_arrow_cursor.rgba", "data/generated/gba/chrome/selector_outline.rgba", "data/generated/gba/chrome/red_arrow.pal", -- src/minigame_countdown.c:213, src/pokemon_jump.c:454, src/digit_obj_util.c:67 "data/generated/gba/link/countdown_321.pal", "data/generated/gba/link/countdown_321.rgba", "data/generated/gba/link/manifest.lua", "data/generated/gba/link/minigame_countdown.pal", "data/generated/gba/link/minigame_countdown_numbers.rgba", "data/generated/gba/link/minigame_countdown_start.rgba", "data/generated/gba/link/minigame_digits.pal", "data/generated/gba/link/minigame_digits.rgba", -- src/mystery_gift_show_card.c:115, src/mystery_gift_menu.c:33 "data/generated/gba/mystery_gift/border.pal", "data/generated/gba/mystery_gift/border_tiles.rgba", "data/generated/gba/mystery_gift/menu_bg.rgba", "data/generated/gba/mystery_gift/stamp_shadow.pal", "data/generated/gba/mystery_gift/stamp_shadow_0.rgba", "data/generated/gba/mystery_gift/stamp_shadow_1.rgba", "data/generated/gba/mystery_gift/stamp_shadow_2.rgba", "data/generated/gba/mystery_gift/stamp_shadow_3.rgba", "data/generated/gba/mystery_gift/stamp_shadow_4.rgba", "data/generated/gba/mystery_gift/stamp_shadow_5.rgba", "data/generated/gba/mystery_gift/stamp_shadow_6.rgba", "data/generated/gba/mystery_gift/stamp_shadow_7.rgba", -- src/union_room_player_avatar.c:33 "data/generated/gba/union_room/avatars.lua", -- src/berry_crush.c:647, src/graphics.c:1378 "data/generated/gba/berry_crush/bg.bin", "data/generated/gba/berry_crush/bg.rgba", "data/generated/gba/berry_crush/container_cap.bin", "data/generated/gba/berry_crush/container_cap.rgba", "data/generated/gba/berry_crush/core.pal", "data/generated/gba/berry_crush/crusher.4bpp", "data/generated/gba/berry_crush/crusher.pal", "data/generated/gba/berry_crush/crusher.rgba", "data/generated/gba/berry_crush/crusher_base.rgba", "data/generated/gba/berry_crush/crusher_top.bin", "data/generated/gba/berry_crush/crusher_top.rgba", "data/generated/gba/berry_crush/effect.pal", "data/generated/gba/berry_crush/impact.rgba", "data/generated/gba/berry_crush/manifest.lua", "data/generated/gba/berry_crush/sparkle.rgba", "data/generated/gba/berry_crush/tables.lua", "data/generated/gba/berry_crush/text_windows.bin", "data/generated/gba/berry_crush/text_windows.rgba", "data/generated/gba/berry_crush/timer.pal", "data/generated/gba/berry_crush/timer_digits.rgba", -- src/dodrio_berry_picking.c:3323 "data/generated/gba/dodrio_berry_picking/berries.pal", "data/generated/gba/dodrio_berry_picking/berries.rgba", "data/generated/gba/dodrio_berry_picking/bg.bin", "data/generated/gba/dodrio_berry_picking/bg.pal", "data/generated/gba/dodrio_berry_picking/bg.rgba", "data/generated/gba/dodrio_berry_picking/cloud.pal", "data/generated/gba/dodrio_berry_picking/cloud.rgba", "data/generated/gba/dodrio_berry_picking/dodrio.pal", "data/generated/gba/dodrio_berry_picking/dodrio.rgba", "data/generated/gba/dodrio_berry_picking/dodrio_shiny.pal", "data/generated/gba/dodrio_berry_picking/dodrio_shiny.rgba", "data/generated/gba/dodrio_berry_picking/manifest.lua", "data/generated/gba/dodrio_berry_picking/scenery.4bpp", "data/generated/gba/dodrio_berry_picking/status.pal", "data/generated/gba/dodrio_berry_picking/status.rgba", "data/generated/gba/dodrio_berry_picking/tables.lua", "data/generated/gba/dodrio_berry_picking/tree_border.4bpp", "data/generated/gba/dodrio_berry_picking/tree_border_left.bin", "data/generated/gba/dodrio_berry_picking/tree_border_left.rgba", "data/generated/gba/dodrio_berry_picking/tree_border_right.bin", "data/generated/gba/dodrio_berry_picking/tree_border_right.rgba", -- src/pokemon_jump.c:2901 "data/generated/gba/pokemon_jump/bg.4bpp", "data/generated/gba/pokemon_jump/bg.bin", "data/generated/gba/pokemon_jump/bg.pal", "data/generated/gba/pokemon_jump/bg.rgba", "data/generated/gba/pokemon_jump/bonuses.4bpp", "data/generated/gba/pokemon_jump/bonuses.bin", "data/generated/gba/pokemon_jump/bonuses.rgba", "data/generated/gba/pokemon_jump/manifest.lua", "data/generated/gba/pokemon_jump/pal1.pal", "data/generated/gba/pokemon_jump/pal2.pal", "data/generated/gba/pokemon_jump/star.rgba", "data/generated/gba/pokemon_jump/tables.lua", "data/generated/gba/pokemon_jump/venusaur.4bpp", "data/generated/gba/pokemon_jump/venusaur.bin", "data/generated/gba/pokemon_jump/venusaur.rgba", "data/generated/gba/pokemon_jump/vine1.rgba", "data/generated/gba/pokemon_jump/vine1_pal2.rgba", "data/generated/gba/pokemon_jump/vine2.rgba", "data/generated/gba/pokemon_jump/vine2_pal2.rgba", "data/generated/gba/pokemon_jump/vine3.rgba", "data/generated/gba/pokemon_jump/vine3_pal2.rgba", "data/generated/gba/pokemon_jump/vine4.rgba", "data/generated/gba/pokemon_jump/vine4_pal2.rgba", }, } do local frlg = CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.firered for _, path in ipairs(require("src.import.gba.battle_anim_extract").FRLG_REQUIRED) do frlg[#frlg + 1] = "data/generated/gba/" .. path end -- src/trainer_card.c:155, :293 local hoenn = "data/generated/gba/rse/trainer_card/" frlg[#frlg + 1] = hoenn .. "manifest.lua" frlg[#frlg + 1] = hoenn .. "badges.png" frlg[#frlg + 1] = hoenn .. "star.png" for stars = 0, 4 do for _, side in ipairs({ "screen", "front", "back" }) do frlg[#frlg + 1] = string.format("%s%s_%d.png", hoenn, side, stars) frlg[#frlg + 1] = string.format("%s%s_%d_female.png", hoenn, side, stars) end end -- pokefirered/src/battle_script_commands.c:9693 local terrainKeys = require("src.import.gba.battle_chrome_extract").TERRAIN_KEYS for id = 0, 19 do frlg[#frlg + 1] = "data/generated/gba/pokemon/battle/terrain_" .. terrainKeys[id] .. "_post_dex.rgba" end end CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.leafgreen = {} for i, path in ipairs(CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.firered) do CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.leafgreen[i] = path end table.insert(CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.leafgreen, "data/generated/gba/intro/title_streak.png") CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.silver = CacheContract.VERSION_REQUIRED_FILES_OVERRIDE.gold local SEMANTIC_MODULES = { [1] = { "constants", "maps", "tilesets", "text", "text_pointers", "trainer_headers", "font", "sprites", "pokemon", "moves", "items", "type_chart", "trainers", "encounters", "field", "battle_anims", }, [2] = { "pokemon", "moves", "items", "type_chart", "audio", "font", "maps", "tilesets", "text", "rom_text", "trainers", "encounters", "sprites", "palettes", "icons", "battle_anims", "constants", "landmarks", }, [3] = { "maps", "intro", "audio" }, } local OPTIONAL_SEMANTIC_MODULES = { [1] = { "audio", "palettes", "icons" }, [2] = {}, [3] = {}, } local function copy(values) local out = {} for index, value in ipairs(values or {}) do out[index] = value end return out end CacheContract.PLAN_CORE_FILES = { "data/generated/gba/meta.json", "data/generated/gba/maps.json", "data/generated/gba/audio/meta.json", "data/generated/gba/intro/meta.json", "data/generated/maps.lua", "data/generated/intro.lua", "data/generated/audio.lua", } local composed = {} function CacheContract.planFilesFor(version) if composed[version] then return composed[version] end local Plans = require("src.import.gba.plans.registry") local CachePaths = require("src.core.game3.cache_paths") local files, seen = {}, {} local function add(path) if not seen[path] then seen[path] = true files[#files + 1] = path end end for _, path in ipairs(CacheContract.PLAN_CORE_FILES) do add(path) end for _, path in ipairs(Plans.required(Plans.of(version), CachePaths.CACHE_ROOT)) do add(path) end if version == "emerald" then local cfg = require("src.import.gba.games.emerald").BATTLE_UI local keys = require("src.import.gba.battle_chrome_extract").TERRAIN_KEYS -- pokeemerald/src/battle_script_commands.c:10133 local function postDex(key) add(CachePaths.CACHE_ROOT .. "/pokemon/battle/terrain_" .. key .. "_post_dex.rgba") end for id = 0, cfg.terrain_count - 1 do postDex(keys[id]) end for _, scene in ipairs(cfg.scenes) do postDex(scene.key) end end composed[version] = files return files end local function planComposed(version) return CacheContract.VERSION_REQUIRED_FILES_OVERRIDE[version] == nil and CacheContract.VERSION_FORMAT[version] ~= nil and GameVersion.VERSIONS[version] ~= nil and GameVersion.generation(version) == 3 and GameVersion.layout(version) ~= nil end function CacheContract.requiredFilesFor(version) local override = CacheContract.VERSION_REQUIRED_FILES_OVERRIDE[version] if override then return override, true end if planComposed(version) then return CacheContract.planFilesFor(version), true end return CacheContract.REQUIRED_FILES, false end -- A detached, version-specific inventory for read-only consumers. Semantic -- consumers additionally require every generated module that backs the public -- registry surface; optional semantic roots are discovered lazily. function CacheContract.requiredFiles(version, semantic) local base, isOverride = CacheContract.requiredFilesFor(version) local files = copy(base) if not isOverride then for _, path in ipairs(CacheContract.VERSION_REQUIRED_FILES[version] or {}) do files[#files + 1] = path end end if semantic then for _, name in ipairs(SEMANTIC_MODULES[GameVersion.generation(version)] or {}) do files[#files + 1] = "data/generated/" .. name .. ".lua" end end local seen, out = {}, {} for _, path in ipairs(files) do if not seen[path] then seen[path] = true out[#out + 1] = path end end return out end function CacheContract.semanticModules(version) return copy(SEMANTIC_MODULES[GameVersion.generation(version)]) end function CacheContract.optionalSemanticModules(version) return copy(OPTIONAL_SEMANTIC_MODULES[GameVersion.generation(version)]) end function CacheContract.formatFor(version) return CacheContract.VERSION_FORMAT[version] or CacheContract.FORMAT end function CacheContract.markerFor(version, sha1) return CacheContract.formatFor(version) .. (sha1 or GameVersion.info(version).sha1) end function CacheContract.markerMatches(version, marker) for _, revision in ipairs(GameVersion.revisions(version)) do if marker == CacheContract.markerFor(version, revision.sha1) then return true end end return false end -- Keep the process-global CacheFs prefix isolated even when a filesystem -- adapter raises while probing or publishing. The real CacheFs methods -- return errors, but this also makes the contract safe for platform adapters -- that surface I/O failures as Lua errors. local function withVersionPrefix(version, fs, action) local saved = fs.prefix fs.prefix = GameVersion.cachePrefix(version) local ok, first, second = pcall(action) fs.prefix = saved if not ok then return false, first end return true, first, second end local function nativeRs(version) return version == "ruby" or version == "sapphire" end local function nativeMeta(fs) local raw = fs.read and fs.read("data/generated/gba/meta.json") if type(raw) ~= "string" then return nil end local ok, meta = pcall(require("src.link.Json").decode, raw) if ok and type(meta) == "table" then return meta end end local function nativeDataReady(version, fs, sha1) if not nativeRs(version) then return true end local path = "data/generated/gba/meta.json" if sha1 ~= nil and type(sha1) ~= "string" then return false, path end local meta = nativeMeta(fs) local V = require("src.import.gba.versions").forGame(version) if not meta or meta.version ~= version or meta.cache_version ~= V.CACHE_VERSION or meta.native_version ~= V.NATIVE_VERSION or type(meta.romSha1) ~= "string" then return false, path end if meta.md5 ~= nil and (type(meta.md5) ~= "string" or meta.md5:lower() ~= meta.romSha1:lower()) then return false, path end local expected = type(sha1) == "string" and sha1:lower() or meta.romSha1:lower() if meta.romSha1:lower() ~= expected then return false, path end return require("src.import.gba.rs.cache_readiness").check(version, fs, expected) end function CacheContract.allRequiredFilesExist(version, fs, sha1) fs = fs or require("src.import.CacheFs") local ok, complete, missing = withVersionPrefix(version, fs, function() local required, isOverride = CacheContract.requiredFilesFor(version) local missingPath for _, path in ipairs(required) do if not fs.exists(path) then missingPath = path; break end end if not missingPath and not isOverride then for _, path in ipairs(CacheContract.VERSION_REQUIRED_FILES[version] or {}) do if not fs.exists(path) then missingPath = path; break end end end if missingPath then return false, missingPath end return nativeDataReady(version, fs, sha1) end) if not ok then return false, complete end return complete, missing end function CacheContract.readMarker(version, fs) fs = fs or require("src.import.CacheFs") local ok, marker, readError = withVersionPrefix(version, fs, function() return fs.read(CacheContract.MARKER_PATH) end) if not ok then return nil, marker end -- LÖVE may return contents plus a byte count; only a nil contents result -- makes the auxiliary value an error. CacheFs' portable reader returns -- just the contents, so this remains adapter-neutral. if marker == nil then return nil, readError end return marker end function CacheContract.cacheVersionCurrent(version, fs) if GameVersion.generation(version) ~= 3 then return true end fs = fs or require("src.import.CacheFs") local okV, Versions = pcall(function() return require("src.import.gba.versions").forGame(version) end) if not okV or not Versions or not Versions.CACHE_VERSION then return true end local ok, raw = withVersionPrefix(version, fs, function() return fs.read("data/generated/gba/meta.json") end) if not ok or type(raw) ~= "string" then return false end local stamped = tonumber(raw:match('"cache_version"%s*:%s*(%d+)')) return stamped == Versions.CACHE_VERSION end function CacheContract.isReady(version, fs) fs = fs or require("src.import.CacheFs") if CacheContract.sourceTreeHasData(version) then return true end local marker, readError = CacheContract.readMarker(version, fs) if readError or not CacheContract.markerMatches(version, marker) then return false end if not CacheContract.cacheVersionCurrent(version, fs) then return false end local sha1 = nativeRs(version) and marker:match(":([%x]+)$") or nil return CacheContract.allRequiredFilesExist(version, fs, sha1) end function CacheContract.publish(version, fs, sha1) fs = fs or require("src.import.CacheFs") if nativeRs(version) then if sha1 == nil then local ok, meta = withVersionPrefix(version, fs, function() return nativeMeta(fs) end) sha1 = ok and meta and meta.romSha1 or nil end if type(sha1) == "string" then sha1 = sha1:lower() end end local complete, missing = CacheContract.allRequiredFilesExist(version, fs, sha1) if not complete then -- A caller may be retrying over a partially replaced cache. Do not -- leave its old marker advertising readiness after this failed check. local removed, removeError = withVersionPrefix(version, fs, function() if not fs.remove then error("cache filesystem cannot remove the completion marker") end return fs.remove(CacheContract.MARKER_PATH) end) if not removed then return false, "cache is incomplete; missing " .. tostring(missing) .. "; could not remove completion marker: " .. tostring(removeError) end return false, "cache is incomplete; missing " .. tostring(missing) end local changed, ok, err = withVersionPrefix(version, fs, function() return fs.write(CacheContract.MARKER_PATH, CacheContract.markerFor(version, sha1)) end) if not changed then return false, tostring(ok) end return ok, err end function CacheContract.sourceTreeHasData(version) if not (love and love.filesystem and love.filesystem.getInfo and love.filesystem.getRealDirectory and love.filesystem.getSource) then return false end local prefix = version == "red" and "" or GameVersion.cachePrefix(version) local required, isOverride = CacheContract.requiredFilesFor(version) local source = love.filesystem.getSource() for _, path in ipairs(required) do local fullPath = prefix .. path if love.filesystem.getInfo(fullPath, "file") == nil or love.filesystem.getRealDirectory(fullPath) ~= source then return false end end if not isOverride then for _, path in ipairs(CacheContract.VERSION_REQUIRED_FILES[version] or {}) do local fullPath = prefix .. path if love.filesystem.getInfo(fullPath, "file") == nil or love.filesystem.getRealDirectory(fullPath) ~= source then return false end end end if nativeRs(version) then local exact = { read = function(path) local full = prefix .. path if love.filesystem.getRealDirectory(full) == source then return love.filesystem.read(full) end end, exists = function(path) local full = prefix .. path return love.filesystem.getInfo(full, "file") ~= nil and love.filesystem.getRealDirectory(full) == source end, } return nativeDataReady(version, exact) end return true end -- Inspect another version without mutating CacheFs.prefix, GameVersion, mounts, -- or the active Data table. The injected filesystem addresses exact paths. local function readAt(fs, path) if fs.readAt then return fs.readAt(path) end if fs.read then return fs.read(path) end return nil end local function isFileAt(fs, path) if fs.getInfo then local info = fs.getInfo(path, "file") return info ~= nil and (info.type == nil or info.type == "file") end if fs.existsAt then return fs.existsAt(path) == true end return false end local function hasExactFiles(version, fs, prefix, semantic) for _, path in ipairs(CacheContract.requiredFiles(version, semantic)) do if not isFileAt(fs, prefix .. path) then return false, path end end return true end local function sourceReady(version, fs, semantic) if not (fs.getInfo and fs.getRealDirectory and fs.getSource) then return nil end local prefix = version == "red" and "" or GameVersion.cachePrefix(version) local complete = hasExactFiles(version, fs, prefix, semantic) if not complete then return nil end local first = prefix .. CacheContract.requiredFiles(version, semantic)[1] if fs.getRealDirectory(first) ~= fs.getSource() then return nil end if nativeRs(version) then local source = fs.getSource() for _, path in ipairs(CacheContract.requiredFiles(version, semantic)) do if fs.getRealDirectory(prefix .. path) ~= source then return nil end end local relative = { read = function(path) local full = prefix .. path if fs.getRealDirectory(full) == source then return readAt(fs, full) end end, exists = function(path) local full = prefix .. path return isFileAt(fs, full) and fs.getRealDirectory(full) == source end, } if not nativeDataReady(version, relative) then return nil end end return { kind = "source", prefix = prefix } end function CacheContract.inspect(version, fs, opts) opts = opts or {} if not (GameVersion.VERSIONS[version] and fs) then return nil, "not_imported", "unsupported cache inspection" end if opts.allowSource then local source = sourceReady(version, fs, opts.semantic) if source then return source end end local prefix = GameVersion.cachePrefix(version) local marker = readAt(fs, prefix .. CacheContract.MARKER_PATH) if not CacheContract.markerMatches(version, marker) then return nil, "not_imported", "completion marker is missing or stale" end local complete, missing = hasExactFiles(version, fs, prefix, opts.semantic) if not complete then return nil, "not_imported", "required cache file is missing: " .. tostring(missing) end if nativeRs(version) then local relative = { read = function(path) return readAt(fs, prefix .. path) end, exists = function(path) return isFileAt(fs, prefix .. path) end, } local ready, invalid = nativeDataReady(version, relative, marker:match(":([%x]+)$")) if not ready then return nil, "not_imported", "native cache data is missing or stale: " .. tostring(invalid) end end return { kind = "cache", prefix = prefix, marker = marker } end return CacheContract