Skip to Content
API referencePack file formats

Pack file formats

An .nrm is a zip. What it does depends on the files at its top. Studio writes all of these; this page is for reading them, checking a build, or making a pack by hand.

FileMakes the pack…AppliedStatus
mod.jsona mod: id, version, name, authors, game_id, minimum_recomp_version, dependenciesalwaysbuilt
jfg_assets.jsona data mod: replaces, appends and patches game datagame startbuilt
jfg_world.jsonadd planets and galaxies to the planet mapgame startbuilt
jfg_music.jsonplay its own OGG music in levelsgame startbuilt
jfg_scripts.jsonrun Lua scriptsgame startbuilt
rt64.json and imagesa texture pack (any resolution)at once; switchable while playingbuilt
patch.bpspatch the whole ROM (one such mod at a time; Studio uses it only for planets that take over a stock map point)game startbuilt
code (mod_binary.bin, mod_syms.bin)a code mod (from RecompModTool)game startbuilt

One pack can be several kinds at once. A data-only pack needs no code and no RecompModTool: a zip with mod.json and its files is enough.

mod.json

{ "game_id": "jet-force-gemini", "id": "yourname.my_planet", "version": "0.1.0", "display_name": "My Planet", "authors": ["you"], "minimum_recomp_version": "1.1.8", "dependencies": [] }

jfg_assets.json

The game reads all its data through one table of 71 “assets”. Some assets are sets: a table asset that lists offsets into a data asset (levels, tracks, object lists, models, textures, object definitions). At game start, before the game’s first instruction, the port lays the data out again with every pack’s changes and points the game at it. The game never sees the difference.

{ "assets": [ { "asset": 34, "file": "assets/asset34.bin" } ], "entries": [ { "table": 1, "data": 0, "entry": 291, "file": "assets/tex3d_291.bin" }, { "table": 30, "data": 31, "append": "level_gemini", "expect": 412, "file": "assets/level_gemini.bin", "fixups": [ { "at": 84, "type": "s16", "ref": "objlist_gemini" } ] }, { "table": 46, "data": 47, "append": "objdef_fire_ant", "copyOf": 120, "set": [ { "at": 0, "hex": "3f800000" } ] } ], "patches": [ { "table": 28, "data": 29, "entry": 424, "objlist": [ { "op": "set", "index": 12, "id": 120, "at": 4, "hex": "00960000ff6a" }, { "op": "delete", "index": 40, "id": 233 }, { "op": "append", "hex": "00a9000e..." } ] } ], "objtypes": [ { "append": "objtype_fire_ant", "def": "objdef_fire_ant", "like": 120 } ], "animRows": [ { "model": "model_fire_ant", "from": 904 } ], "regions": [ { "append": "region_zephyra", "name": "Zephyra", "world": "Zephyra Expanse" } ] }

Replacing

  • "assets" replaces a whole asset.
  • "entries" with "entry": N replaces one entry of a set; "table" and "data" name the two assets. The 3D textures are table 1 over data 0, the 2D textures table 3 over data 2. "packed": true marks a packed model or track.
  • When two packs replace the same asset or entry, the one higher in the Mods list wins (logged).

Appending and the content linker

  • "append": "name" adds a new entry after the set’s last one. The port numbers it at start-up: the number the pack was made for ("expect") if it’s free, else the lowest free one.
  • Numbers are kept between starts in bin/mod_ids.json by mod:name, so a save that names a pack’s level still finds it as other packs come and go. Numbers of packs that aren’t loaded stay reserved.
  • "fixups" write numbers the pack can’t know into its bytes once they’re given out: at, type, ref, add. Types u8 u16 s16 u32, and lohi for an exit’s target level. ref is one of the pack’s own names or "othermod:name".
  • jfg_mods.log says which number each name got.

Copies of the player’s own data

"copyOf": N in place of "file" builds the entry from entry N of the same set in the player’s own game, with "set" byte edits and fixups on top. A reskinned enemy needs only its new textures this way: its model and definition are copies.

Patches

"patches" change an entry instead of replacing it, so several packs can change one stock level: "set" writes bytes at an offset; "objlist" sets inside, deletes or appends object-list entries. Each set and delete names the object id it expects at that index; if it doesn’t match, the operation is skipped and logged (the pack was made for other data).

New object types

"objtypes" add object ids for appended definitions; "like" is the stock id the squad code treats the new one as. "animRows" give an appended model a stock model’s animations and joint slots.

Regions

"regions" add Tribal and stats regions (a level header’s byte 0x102). The port numbers them 31–39 like appended entries (kept in mod_ids.json); past 39 a region is refused and its levels keep the region their headers have. A level joins one with a fixup:

{ "at": 258, "type": "u8", "ref": "region_zephyra" }

A level in a region gets the game’s region behaviour: the stats screen when an exit with bit 0 of its 0x1A byte set leads into another region, the area title with "name" and "world" when it’s entered, and continues. The port lengthens the game’s Tribal table so mods’ regions have 0 Tribals. Each level of a region needs its own index inside it (header byte 0x103, 0–31). Studio writes all of this from the levels’ Planet.

jfg_world.json

{ "format": 2, "galaxies": [ { "id": "expanse", "name": "Zephyra Expanse", "home": "zephyra" } ], "planets": [ { "id": "glacia", "name": "Glacia", "galaxy": "expanse", "levels": ["level_glacia", "level_glacia", "level_glacia"], "disc": { "looks": 4, "x": 300, "y": 420, "radius": 14, "colour": [190, 225, 255] }, "unlock": { "completed": "zephyra" }, "routes": [ { "from": "planet:zephyra", "dir": "right", "back": "left" } ] } ] }
FieldMeaning
galaxy"main" (the game’s), a galaxy of this pack, or "othermod:galaxy"
levelsEntry level for Vela, Juno, Lupus: a level the pack appends ("level_x") or "stock:N"
disclooks: the stock disc (0–10) whose textures it wears; position, radius, top colour
unlock"start", "stock" (with the stock point its route leaves; game’s galaxy only), "script", {"entered": p}, {"completed": p}. {"entered": itself} hides a planet until its level is first entered
routesfrom: a stock map point number (game’s galaxy) or "planet:id"; dir and back: stick directions (0 left, 1 right, 2 up, 3 down, or the names); optional corners

Opened, entered and completed planets are kept per save file in saves/<save>_worlds.json, written when the game saves. Format 1 (planets on the game’s galaxy only) still loads.

jfg_music.json

{ "tracks": { "zephyra_landing": "music/zephyra_landing.ogg" }, "levels": { "level_landing": "zephyra_landing", "stock:21": "zephyra_landing" } }

OGG Vorbis files that loop from their LOOPSTART / LOOPLENGTH comments (in samples), or whole. The game’s tune is silenced while a pack track plays, and the track follows the music volume, pauses and fades.

jfg_scripts.json

{ "scripts": ["scripts/_triggers.lua", "scripts/_weapons.lua", "scripts/main.lua"] }

See Lua scripting.

Texture packs (rt64.json)

An RT64 texture pack: rt64.json plus images named by the 16-digit hash RT64 gives each texture. As a mod (.rtz or inside an .nrm) or as a folder in bin/texture_packs/, chosen in Options › Textures or with F6. See An HD texture pack.

Locales

Translations are JSON files in locales/: menu strings (menu.NNN), subtitle lines (subtitle.EE.L) and hints (hint.SSS.K) by key. Missing keys fall back to English. Code mods can read the language with jfg_get_language and listen to jfg_on_language_change.

Last updated on