Creating Mods - freefrank/LostOdysseyRecomp GitHub Wiki
Source: 298b3e8f49b4. Check the source page for feature scope.
Read current support first. This page covers three kinds of mod:
| I want to... | Go to |
|---|---|
| Repaint or upscale the textures the game draws | Make a texture pack |
| Translate the game's text, or add a language to Settings | Translate the game's text |
| Change the artwork of the settings menu | Settings-menu artwork |
Install mods as mod folders in the game's mods folder. Textures and text start from the export of your own game data.
The tools are Python scripts in tools/modding of the LostOdysseyRecomp repository; get them by cloning or downloading the repository, and run the commands below from its root. Python 3.10 or newer is required; Pillow (python -m pip install Pillow) is needed for PNG files. Mods work on Windows only (see Modding).
Ship a mod as one folder with its own overlay/:
mods/<id>/mod.ini
mods/<id>/overlay/textures/fp-<fingerprint>.lotex2
mods/<id>/overlay/images/key-fnv1a64-<16 hex digits>.lotex
mods/<id>/overlay/text/<member path>.json
api_version=2
id=my-textures
name=My Texture Pack
version=1.0
author=Your name
description=One line about the mod.The game finds the files under overlay/ by name, so mod.ini needs no file list. name, version, author and description are optional. texture-pack --layout standalone and pack (its default layout) write this layout. A translation that adds a new language is a language pack instead, a folder with a language.ini (make a language pack); both kinds of folder sit side by side in mods/.
Zip it with mods/ at the top of the archive: mods/<id>/mod.ini, mods/<id>/overlay/.... Players extract the archive into the game folder; Mod Organizer 2 installs it as it is. Mod folders never conflict with each other. When two of them replace the same file, the in-game mod order decides, then priority in mod.ini (see Modding API). Changes take effect after a restart.
Players manage installed mods in Settings → System → Mods. The page lists each mod with its name, version and author. The confirm button (A, or B if the game's confirm and cancel buttons are swapped) switches the selected mod On or Off, LB / RB move it up or down, and Save mod list (or Start) saves the order and applies it while the game runs: texture mods change at once, model mods from the next area you enter. Until then the help line says so. Party members' models, and the letters a language pack adds to the fonts, are loaded once at startup, so a change to them needs a restart; an area the game still holds in memory, such as one you just left, can show the old models until it is loaded again. The selected mod's description and folder appear in the help line. A mod with a broken mod.ini shows an error there with its first problem. Language packs are listed below the mods; choose one in Settings → System → Game language.
To turn a top-level overlay/ folder (an overlay pack) into a mod folder without packing it again:
python tools/modding/lo_mod.py overlay-to-mod mods --id my-textures --name "My Texture Pack" --version 1.0 --dry-run
python tools/modding/lo_mod.py overlay-to-mod mods --id my-textures --name "My Texture Pack" --version 1.0This moves mods/overlay/textures (and images, text and the other kind folders) into mods/my-textures/overlay/ and writes mod.ini. Folders are renamed, not copied, so even a large pack moves at once; across drives the command fails instead of copying. It refuses when mods/my-textures exists, and --dry-run only prints what it would do. For a mod already installed in Mod Organizer 2, pass that mod's folder (the one that holds overlay/).
A mod with thousands of files under overlay/ (a full texture pack) is slow to copy, unzip and install. overlay.loarc holds all of them in one file that sits in the mod folder; the game reads the files from it as if they were loose. Only api_version=2 mod folders can use it.
-
Build the mod folder as usual:
mod.iniplusoverlay/. -
Run, with the mod folder first and a new folder second:
python tools/modding/lo_mod.py archive mods/my-textures packed/my-textures
The command copies
mod.iniand every other file outsideoverlay/, and writesoverlay.loarcinto the new folder with every overlay file the game loads. Files it does not load (wrong name, wrong folder) are skipped with a warning. Two files that end up under the same name (FP-…andfp-…) are an error and nothing is written. The new folder must not exist or must be empty;--forcewrites into a non-empty one. -
The command re-reads the archive and compares every file with its source.
--quickchecks only the archive's header and file list, which is much faster for a mod of tens of gigabytes. -
Install
packed/my-textureslike any mod folder:mods/my-textures/mod.iniandmods/my-textures/overlay.loarc. Do not leave theoverlay/folder in it unless you want loose files to override the archive.
Things to know:
-
mod.inialways stays a loose file; the game reads it before it opens the archive. - A loose file in the same mod's
overlay/folder wins over the archive entry with the same name. Between different mods the usual order andprioritystill decide. - One archive can be larger than 4 GB, so it cannot sit on a FAT32 drive.
- To look inside an archive:
python tools/modding/lo_mod.py extract mods/my-textures/overlay.loarc out --listprintsnameandsizeof every file. Without--listit writes the files toout/overlay/...(and refuses to overwrite existing files). Extracting gives back exactly the files that were archived.
All numbers are little endian. The header is 64 bytes:
| Offset | Size | Field |
|---|---|---|
| 0 | 8 | magic LOARC1\r\n
|
| 8 | 4 | header size, 64 |
| 12 | 4 | entry count |
| 16 | 8 | index offset |
| 24 | 8 | index size in bytes (at most 64 MiB) |
| 32 | 4 | kind mask: bit 1 << kind for each kind present (Image 1, Font 2, Model 3, Movie 4, Texture 5, Text 6) |
| 36 | 28 | zero |
Each file's data starts at a multiple of 4096 (at least 4096) and ends before the index offset; data ranges do not overlap. The index follows the last file's data. It holds one record per file, sorted by name bytes, names unique: u64 offset, u64 size, u32 name length (1 to 1100), then the UTF-8 name. A name is the file's path inside the mod folder with / separators and lower case: overlay/textures/fp-<16 hex>.lotex2, overlay/images/key-fnv1a64-<16 hex>.lotex, overlay/<fonts|models>/key-fnv1a64-<16 hex>.loasset, overlay/movies/<name>.mp4 (<name> is 1 to 64 of a-z, 0-9, _), or overlay/text/<path>.json.
The game can export its own textures, movies, text, 3D models and animations from your game data as a starting point. The export is for your reference only: it comes from your copy of the game and must not be redistributed or bundled in a mod.
LostOdysseyRecomp.exe --export-assets my-export --export-kinds textures,movies --export-filter UI_MAINThe output folder must not exist or must be empty, and it must be outside the game data. Optional arguments:
-
--export-kinds:textures,movies,text,models,animations,fonts(each font's size, pages and letters as JSON, for adding letters), orfingerprints(the texture index without PNG files). The default is textures, movies and text; models and animations are not in the default because of their size. -
--export-filter: only names containing this text. -
--export-language: the text of one language only:int,jpn,deu,fra,spa,ita,kor,chiorsch. -
--export-language-pack <id>: only text, written as a language pack to translate (see make a language pack). -
--export-model-textures: withmodels, writes each material's colour texture as a PNG file to atexturesfolder beside the models and links it from the.glbfiles. -
--export-select <file>: exports only the names listed in a UTF-8 text file, one per line (see models and animations). -
--export-preview: withmodelsoranimations, writes a 512×512 PNG picture of each model, or of the character an animation set moves, in place of its.glbfile;index.csvlists the pictures. The MO2 tool shows these.
No window opens; the program prints progress and exits (exit code 1 on a fatal error). With Mod Organizer 2, the Export Lost Odyssey assets tool does the same from a window; that page has step-by-step instructions.
my-export/textures/<package path>/<object>.<export index>.png
my-export/textures/index.csv key,file,width,height,format,fingerprint,fingerprint_tiled
my-export/movies/<name>.wmv original videos, unchanged
my-export/text/<file path>.json {"key": "text"} for one game text file
my-export/text/index.csv path,language,format,source,entries,round_trip
my-export/models/<package path>/<object>.<export index>.glb 3D models (glTF)
my-export/models/index.csv key,file,class,vertices,triangles,bones
my-export/animations/<package path>/<set>.<index>.glb animation sets (glTF)
my-export/animations/index.csv key,file,sequences,tracks,frames,model
my-export/fonts/<package path>/<font>.json font size, kerning, pages and letters
my-export/export-summary.txt
textures/index.csv holds the mod key and the fingerprint of every texture, so no catalog is needed.
Models and animations are exported as glTF binaries (.glb). Blender opens them with File → Import → glTF 2.0; Open models in Blender has the steps. Models, map pieces included, can be edited and put back with a mod (see Replace a model); animations are for reference only.
-
Export the models, the animations or both. Add
--export-model-texturesto link each material's colour texture:LostOdysseyRecomp.exe --export-assets my-export --export-kinds models,animations --export-model-textures
In the MO2 tool, tick Models (with With textures) and Animations. Both are off by default because they are large.
-
Open
my-export/models/index.csvormy-export/animations/index.csvto find a file by itskey. A model file holds the highest-detail level with its skeleton, normals, UV sets, vertex colours and material slot names. An animation file holds the character model whose skeleton the set moves (without textures), and every sequence of the set as its own glTF animation; Blender lists them as Actions (use the NLA editor to combine them). Some animation sets, such as weapon props in cutscenes and camera boxes, have no matching model and are skipped;export-summary.txtlists them. -
To export only some of them, first write the list. Run the export with
--export-listand the same kinds:LostOdysseyRecomp.exe --export-assets my-list --export-kinds models,animations --export-list
my-list/list.csvhas a row for each model and animation set, with itsname. Itsnamerows give the game's own names: a map code (u36_0) or character code (pc_000,en_043a0) inname, the language inlanguageand the name inlabel. Copy thenamevalues you want into a UTF-8 text file, one per line, for exampleselected.txt. Then export into a new folder:LostOdysseyRecomp.exe --export-assets my-selection --export-kinds models,animations --export-select selected.txt
Text covers the menus, item and skill names and descriptions, battle messages, field dialogue, cutscene subtitles, the credits and the engine's own messages, for every language on your discs. Each game file becomes one UTF-8 JSON file with its own path plus .json, for example text/bin/xenon/scr/mes/int/u3b_0_scrw.jmd.json. Codes that are not text appear as tokens such as {E001} (a line break in dialogue) or {E10D:0500} (a code with its value); keep them where they are. source in text/index.csv is base, or the DLC whose version the game uses instead of the disc's (lodlc002, lodlc003). round_trip is ok when the file was rebuilt from its exported text byte for byte; only those files can take translations. The Thousand Years of Dreams stories and place names in name_data.xmb are not exported yet.
A mod can replace a cutscene with an upscaled or re-rendered video. This works in the Windows build; other platforms play the original video. The replacement takes the place of the picture only: the game keeps playing the original audio and subtitles. The video is drawn at the game's render resolution, so a 4K video shows its full detail only when Settings → Graphics → Render resolution is as large as your screen.
The video is named after the original. Export the movies to see the names: my-export/movies/mv_00v.wmv is replaced by the key mv_00v. A key has 1 to 64 characters from a-z, 0-9 and _.
What the video must be:
- MP4 container with the
moovbox at the front (ffmpeg-movflags +faststart). Fragmented MP4, which Topaz Video AI writes, also works. - H.264 High profile, 8-bit
yuv420p, progressive, BT.709 limited range, constant frame rate. - Any frame rate from 24 to 120 fps. The game picks frames by time, so the rate does not have to match the original. Twice the original's rate is easiest when a tool doubles the frames: 60 fps for a 30 fps original, 60000/1001 for 29.97.
ffprobeon the exported video shows its rate (most are 30 or 29.97). - 16:9. Use 3840x2160 for desktop packs (H.264 level 5.2) and 1920x1080 for Steam Deck and phones (level 4.2).
- No audio track is needed. An audio track in the file is ignored.
- The same length as the original picture, lined up from the first frame. A video up to 1% longer or shorter is stretched or squeezed evenly to the original's length, which covers a tool that labels 29.97 fps as 30 or drops a few frames. A bigger difference plays as it is, so check the length when a tool changes the frame rate: a video that runs longer or shorter drifts away from the sound.
Example ffmpeg command:
ffmpeg -i mv_00v_upscaled.mov -c:v libx264 -profile:v high -pix_fmt yuv420p -r 60000/1001 -crf 18 -preset slow -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -an -movflags +faststart mv_00v.mp4Install it as a mod:
-
Make the folder
mods/my-cutscenes/overlay/movies/in the game folder and putmv_00v.mp4in it. -
Make
mods/my-cutscenes/mod.iniwith this text:api_version=2 id=my-cutscenes name=My Cutscenes
-
Start the game and play the cutscene.
Instead of the overlay/movies/ folder, a mod.ini line can point at the file anywhere in the mod folder (api_version=1 also works):
movie:mv_00v=movies/mv_00v.mp4python tools/modding/lo_mod.py archive packs overlay/movies/*.mp4 into the .loarc like other files. It checks each video before packing: if ffprobe is installed it must be H.264, yuv420p and progressive, or the file is skipped with a message. It warns when the frame rate is outside 24-120 fps, the picture is not 16:9, or moov is after the video data.
A mod can replace a 3D model with one you edited: a character, monster or moving object (a SkeletalMesh), or a map piece or other fixed object (StaticMesh, StaticMeshRPG), such as a building or a stretch of ground made more detailed. The new model keeps the original's materials, so it uses the original textures, or those of a texture pack. A character model also keeps the original's skeleton, so it moves with the game's own animations. Map pieces have more to keep in mind; see Map pieces.
-
Export the model. In the MO2 tool, select its row and press Open model, or run:
LostOdysseyRecomp.exe --export-assets my-export --export-kinds models --export-model-textures --export-filter pc_000a0_m
The file is
my-export/models/<package path>/<object>.<export index>.glb. Kaim's ismy-export/models/bin/xenon/chr/pc/pc_000a0_m.xxx/pc_000a0_m.0.glb. -
In Blender, choose File → Import → glTF 2.0 and open the file.
-
Edit the mesh: move, add or delete vertices and faces, change the UV map, paint the weights. Keep these as they are:
- Character models: the armature and its bone names. Weight the mesh only to bones that are already there. Do not add, rename or delete bones.
- The material names. Every face needs one of the original materials, such as
MAT_pc_000a0_bodyMT. New materials do not work. Names that Blender gives a.001ending are fine. - A UV map.
-
Choose File → Export → glTF 2.0, set Format to glTF Binary (.glb), and export. The other options can stay as they are. Textures and animations in the file are not used.
-
Put the file in a mod at the same path it was exported to, under
overlay/models/:mods/my-models/overlay/models/bin/xenon/chr/pc/pc_000a0_m.xxx/pc_000a0_m.0.glb -
Make
mods/my-models/mod.iniwith this text:api_version=2 id=my-models name=My Models
-
Start the game. The newest
logs/runtime-*.login the game folder shows the result:-
[mods] model bin/xenon/chr/pc/pc_000a0_m.xxx: pc_000a0_m: … verticesmeans the model was replaced. -
kept originalmeans it was not, followed by the reason (for example a bone or material the original does not have).
-
Limits of character models:
- At most 65,535 vertices. The game splits the model into parts of up to 75 bones, and vertices on the seams count twice.
- Up to 4 bone weights per vertex. Extra weights are dropped and the largest 4 are kept.
- The replacement is shown at every distance; the original's lower-detail versions are not used.
- A few models are in more than one package, such as a boss in its battle and event packages. Export and replace each copy.
Buildings, ground, walls and other fixed parts of a map are in the map's _map.xxx package. A magic car in Uhra, for example, is my-export/models/bin/xenon/field/u3b_0/u3b_0_map.xxx/StaticMesh_467.61.glb. The file names do not say what a piece is: export a whole map with --export-filter u3b_0_map and open the files in Blender to find the one you want.
The steps are the same as above. In addition:
- Keep the model where the original is. The map's light and shadow were worked out in advance for each vertex of the original. When the game starts, each vertex of your model takes them from the point of the original's surface nearest to it, so a more detailed version of the same shape is lit like the original. Parts far from the original's surface take the light of the nearest original part.
- Faces with a material that is not one of the original's, or with none, take the material of the original surface beneath them. This keeps the materials of a model that an upscaling tool merged or renamed.
- In Blender's glTF export, open Data → Mesh and set Vertex Color to Active. With the default, some models are exported with white vertex colours.
- Where characters can walk and where they stop is set by the map's own collision, which stays as it was. Keep floors and walls where they were, or characters will walk through the new shapes or float above them.
-
[mods] model lighting … follow the new verticesin the log means the light and shadow were carried over.
Limits of map pieces:
- At most 65,536 vertices per model. Most map pieces have a few hundred to a few thousand, so there is room for many times more detail.
- The first start after a mod is installed or changed, or after a game update that changes how mods are applied, rebuilds the game files it changes: about half a minute to a minute per disc on an 8-core PC for a mod that replaces every map's pieces, longer on fewer cores. Meanwhile the window shows "Preparing mods" with a progress bar. The rebuilt files are kept in the
cache/modsfolder (in the game folder, or next to the shader cache when the game folder cannot be written) and later starts read them from there. For such a mod they take up to about 6 GB per disc. Rebuilt files that no longer fit in the disc's own 4 GB archives go into extraxenon_mod0.fpd,xenon_mod1.fpd… files there. While you play, the other discs' files are rebuilt in the background, the next disc first, on one low-priority thread that rests half the time (about 6 to 15 minutes per disc on an 8-core PC), so switching discs later only reads them; a switch that comes before that waits with the same progress window.LO_MODS_PREBUILD=0turns the background rebuild off (on Android it is off unlessLO_MODS_PREBUILD=1), andLO_MODS_PREBUILD_THREADSgives it more threads. The game keeps the files of your current and your previous set of mods, also while it runs with no mods, so turning a mod off and on again does not rebuild; older sets are removed. Deletecache/modsto free the space.
python tools/modding/lo_mod.py archive packs overlay/models/ into the .loarc like other files.
A texture pack replaces the textures the game draws, at the original size or 2x, 4x or 8x larger. The steps are: export the textures, repaint or upscale the PNG files, pack them, and put the pack in mods/. This works on Windows only.
-
Export the textures (
--export-kinds textures; add--export-filterfor a sample first). - Repaint the PNG files yourself, or upscale them in bulk (next section).
-
Pack them. Point
--outputat the game'smodsfolder to try the pack right away, or at a work folder and zip it asmods/<id>/...to share (see mod folders). - Start the game. A texture shows the original for a few frames until its replacement has been read. With Settings → System → Debug log on (or
LO_DEBUG_LOG=1), the run log has one[mods] texture <fingerprint> replacedline for each texture it replaced; failures are always logged.
tools/modding/texture_prep.py upscales an export in bulk with AI upscaling models. Run it with a Python that has PyTorch, spandrel and Pillow (ComfyUI's Python works); it runs on a CUDA (NVIDIA) GPU by default, and --device selects another device. Download the models it names into one folder:
-
1x_DEDXT.pth(removes DXT block artifacts) -
4x-PBRify_RPLKSRd_V3.pth(material textures) -
4x-Normal-RG0-BC1.pth(normal maps) -
1x-BC1-smooth2.pthand4xNomos2_realplksr_dysample.safetensors(UI and effects)
All of them are on OpenModelDB.
python tools/modding/texture_prep.py --export my-export --output my-upscale --dry-run
python tools/modding/texture_prep.py --export my-export --output my-upscale --models <model folder> --review 8Each texture is sorted into a class: color, normal, data, ui, vfx, or skipped (light and shadow maps, engine icons, tiny images). plan.csv lists the class of every texture.
- Color, UI and effects: artifact removal, then the 4x model, then a color fix so the result scales back down to the original.
- Normal maps: the model upscales only X and Y. The large-scale slopes are kept, half of the added detail is used, and Z is rebuilt.
- Data maps (specular, masks): plain resampling only.
- Edges: tiling textures wrap at the edges, so no seams appear.
- Alpha: upscaled separately; cutouts stay hard.
Results go to my-upscale/textures/ with the same file names, plus index.csv (new and original sizes) and review/<class>.png, which shows before and after crops. Runs resume where they stopped. Use --classes, --filter and --limit for a sample first. --write-config writes the defaults (models per class, scale, maximum size) for editing; pass the edited file back with --config.
lo_mod.py texture-pack turns PNGs into .lotex2 files for runtime texture replacement. Textures are matched by fingerprint (the fingerprint column of the export's index.csv), not by key, so one file covers every package that cooks the same image. Replacements can be the original size or 2x, 4x or 8x larger (see Modding API).
# Mod folder (recommended): <output>/<id>/mod.ini (api_version=2) plus <id>/overlay/textures/
python tools/modding/lo_mod.py texture-pack --index my-export/textures/index.csv --images my-upscale/textures --images-index my-upscale/index.csv --output mods --layout standalone --id my-textures --name "My Texture Pack" --version 1.0
# Overlay layout (the default), for a manager: files go to <output>/overlay/textures/fp-<fingerprint>.lotex2
python tools/modding/lo_mod.py texture-pack --index my-export/textures/index.csv --images my-upscale/textures --images-index my-upscale/index.csv --output mods-
--name,--version,--authorand--descriptiongo into the mod folder's newmod.ini. Packing into an existing mod folder keeps itsmod.ini, which must sayapi_version=2. -
--images-indexmaps keys to PNG files (default:index.csvin the--imagesfolder). -
--filter <text>keeps keys that contain the text.--fingerprints <log.csv>keeps only fingerprints a game run logged. -
--payload dds(recommended) compresses the textures to BC1, BC4 or BC7 and always writes the full mip chain. It needs Microsoft'stexconv(Windows; download it from the DirectXTex releases). Pass--texconv <path>or put it onPATH. DXT1 originals become BC1, DXT3 and DXT5 originals BC7, A8R8G8B8 originals BC7 (red and blue are swapped first, as the game stores B, G, R, A) and G8 originals BC4.--bc7-alluses BC7 for DXT1 originals too, which looks better but is twice the size. Textures whose size is not a multiple of 4 are skipped. -
--mipswrites the whole mip chain with the default--payload rgba8; by default one level is written and the game builds the rest. - Existing files are never overwritten, so you can add to an existing mods folder.
-
--test tint(red up, green and blue down) and--test nearest4(plain 4x enlargement, which must look unchanged in game) transform the original exported PNGs, to check in game that replacement works. -
lo_mod.py inspect <file>prints and validates a.lotex2header.
The default --payload rgba8 stores uncompressed pixels, so 4x packs are large (a 2048x2048 texture is 16 MiB) and use as much video memory. DDS payloads are about 4 to 8 times smaller on disk and in video memory than RGBA8 (and a full mip chain adds only a third), so a 4x upscale of the textures used in one play session shrinks from about 1.9 GB to about 340 MB. Levels larger than about 72 MiB (above 4096x4096) are skipped for RGBA8.
A translation is either a new language that players pick in Settings (a language pack) or a change to a language the game already has. This works on Windows only.
- Export a pack to translate:
LostOdysseyRecomp.exe --export-assets my-export --export-language-pack pt-br(or typept-brin Language pack in the MO2 tool). Use a short id of letters, digits,-,_or.; no two packs may use the same one. You get the foldermy-export/pt-br/with the English text of your discs intext/(581 files in our export) and alanguage.ini, plusmy-export/original/, an untouched copy for step 6; keep it next to the pack. English (int) is the same in the Asia and USA/Europe editions, and its fonts have every Latin-1 letter; add--export-language jpn(or another code) to start from another language. Export from a game with the DLC installed if you have it: without it, the DLC dungeon text is the discs' unfinished version (the exporter warns about this). - Open
my-export/pt-br/language.iniin a text editor and changename=pt-brto the name Settings should show, for examplename=Português (Brasil). Save it as UTF-8. Leaveidandbaseas they are. - Translate the values in the files in
my-export/pt-br/text/and keep the keys. Keep tokens such as{E001}(a line break in dialogue),{E10E}…{E10F}(a speaker name) and{E10D:0500}(a code with its value), as well as%s,%dand$500$(icons), where they are. Lines you do not change stay English. - Try it: copy the
pt-brfolder into the game'smodsfolder, so the file ismods/pt-br/language.ini. - Start the game, open Settings → System → Game language, pick your language, choose Save settings and restart.
- Before you share the pack, clean it:
python tools/modding/lo_mod.py language-clean my-export/pt-br(or Clean language pack... in the MO2 tool). It copies only the lines you changed tomy-export/share/pt-br. Share that folder as a ZIP of the folder itself; players install it like step 4, or with Mod Organizer 2 like any other mod. The lines you did not change are the game's own text, which must not be shared, and from a game without the DLC they would put the discs' old lines over the DLC's for players who have it.
The log shows mods: language pack pt-br (Português (Brasil), base int) at startup, then [mods] text: N translation files (M from language pack pt-br) and [mods] text: K translated files in ..., where K counts the game files your changes went into. If language.ini has a mistake, the log has a mods: warning that names the file.
The game runs as the base language underneath: voices, artwork with words in it and everything you have not translated stay in that language (English for int). While the pack is selected, its files replace the same files from other text mods. Players who pick another language do not see the pack.
The game draws text with bitmap fonts: each letter is a small picture on a texture page, white with a dark outline, at one fixed size. A language that needs letters those fonts do not have, such as Polish, Russian or Arabic, can add them to a language pack: lo_mod.py font-glyphs draws every letter the pack's text uses and the fonts lack from a TrueType or OpenType font, at each game font's size and in its style, and the game adds them to its fonts while the pack is selected.
-
Export the fonts and their pages:
LostOdysseyRecomp.exe --export-assets my-fonts --export-kinds textures,fonts --export-filter rpfonts. Besides the page PNGs intextures/,my-fonts/fonts/<package path>/<font>.jsonhas each font's size, kerning, pages and letters. -
Translate the pack's text first: the letters drawn are the ones it uses.
--charsadds more. -
Install what the command needs:
python -m pip install Pillow fonttools freetype-py uharfbuzz. -
Draw the letters with a font whose licence lets you share it, such as an OFL font:
python tools/modding/lo_mod.py font-glyphs my-export/pt-br --export my-fonts --ttf MyFont-Regular.ttf
For each font of the pack's base language (
rpfontscommon_int.xxx,rpfontsdream_int.xxx,rpfontstitle_int.xxxforint) it writesmy-export/pt-br/fonts/<package path>/<font>.lofontand prints how many letters it drew and at which size. The size matches the TTF'sHto the game'sH, then shrinks until every new letter fits the font's line height;--scalemakes them larger or smaller,--fonts Maru23,Meiyo18limits the fonts. A heavier weight of the TTF reads better at the small sizes. A font withoutH, such as the JapaneseYume24(kana and kanji only), is measured by a letter it shares with a font that has one (口,あ,ア, a digit…): itsHwould stand where it does relative to that letter there. Fonts without letters, such asIcon, are skipped. -
Start the game with the pack selected. The log shows
[mods] font: N glyph files in M packages from language pack pt-brand one[mods] font <package>: <font>: … glyphsline per font; the first start after a change rebuilds the font packages with the other mod files. The game reads its fonts once, at startup, so changed glyph files need a restart; saving the Mods page does not apply them.
language-clean copies the pack's fonts/ folder with the text. A letter the game's font already has keeps the game's own glyph unless fonts/ draws it again.
The game draws every line from left to right and does not join letters. Arabic, Persian and other right-to-left scripts work when the text in the pack is already shaped and in display order: each letter as its presentation form (U+FB50–U+FEFF, which font-glyphs draws by shaping the letter with its neighbours) and each line reversed, with codes such as {E001} kept in place. Lines the game wraps on its own, such as long subtitles, wrap from the wrong end; break them by hand.
- Export the text:
LostOdysseyRecomp.exe --export-assets my-export --export-kinds text --export-language int(or tick Text in the MO2 tool). Without--export-languageyou get every language on your discs. - Copy the files you change into the mods folder at the same path under
overlay/, for examplemy-export/text/bin/xenon/loc/int/menu/menu_int.dat.jsontomods/overlay/text/bin/xenon/loc/int/menu/menu_int.dat.json. To share the change, put the files in a mod folder instead, undermods/<id>/overlay/text/...next to itsmod.ini. A v1 mod lists each file in itsmod.ini:text:bin/xenon/loc/int/menu/menu_int.dat=text/bin/xenon/loc/int/menu/menu_int.dat.json. - Change the values as in step 3 above. Entries you leave out keep the original text.
- Start the game with the language of the files you changed (English for
int). The log names the translated files:[mods] text: K translated files in .... Restart after changing text.
Translated text can be longer than the original. Menus and message boxes do not grow, so long lines may need a manual break. Use only characters the game's font has (see Modding API); a language pack can add the letters it lacks. The Thousand Years of Dreams stories and the place names in name_data.xmb are not exported yet.
The native settings menu draws its own artwork (the atlas UI_MAIN_00, 512x1024 in each of the five language packages) and the texture pages of its fonts. These are not game textures, so a texture pack does not change them; they have their own mod format (LOTEX1). The menu atlas is the confirmed case. The font pages are wired in the runtime but not confirmed as init consumers, and neither changes character mapping or font metrics.
Select the package for the language used by the game. int, chi, jpn, kor and sch are separate localized package namespaces.
The simplest start is the export: LostOdysseyRecomp.exe --export-assets my-export --export-kinds textures --export-filter UI_MAIN. Select one texture and copy its PNG into a new mod:
python tools/modding/lo_mod.py init --export-index my-export/textures/index.csv --object UI_MAIN_00 --package bin/xenon/loc/int/menu/rpmenurescommon_int.xxx --id my-menu --output my-menu/mod.jsonThis writes mod.json and copies the PNG to my-menu/art/<object>.png (use --image to pick another path inside the mod folder). It never overwrites an existing file. init accepts only confirmed consumers, such as UI_MAIN_00, unless you pass --allow-unwired (experimental). Edit the copied PNG, keep its dimensions, alpha channel and atlas layout, and continue with step 3. Other exported textures are not settings-menu artwork: replace them with a texture pack.
Instead of the export, catalog and init can read the completed asset inventory SQLite catalog, or the older CSV manifest:
python -m pip install Pillow
python tools/modding/lo_mod.py catalog --database catalog.sqlite --runtime-only --limit 100--database, --manifest and --export-index are mutually exclusive. A database must be schema 1 with complete=true; it is opened read-only. By default catalog output is limited to 100 rows and includes the canonical key, dimensions, content SHA-256, consumer label, overlay path and source packages. --runtime-only includes the native menu and font-page candidate labels, but init accepts only a confirmed consumer by default. Use --object, --package and --content-sha256 to narrow a result. The last filter selects a package content variant used to derive the authoring identity and dimensions; the key and LOTEX1 payload do not bind a mod to that SHA at runtime. Do not guess an export index or use a CSV row number as one.
The catalog labels the native settings atlas UI_MAIN_00 and a native font-page candidate consumer for the Maru23, LocTit1 and Abc font owners. Native font references are not indexed as complete runtime support: init rejects the font-page candidates by default, and catalog output must not be read as proof that 63 font resources are already replaceable. Other catalog entries are marked no_runtime_consumer; --allow-unwired is an explicit experimental escape hatch.
Copy your own edited PNG into an authoring directory, for example my-menu/art/UI_MAIN_00.png. The inventory contains metadata and dimensions only; it does not export artwork. Keep the original image dimensions, alpha channel and atlas layout. The native menu's verified UI_MAIN_00 layout is 512x1024; larger atlases are rejected by this consumer.
If you did not start from the export, generate the specification from your own catalog rather than copying an example index:
python tools/modding/lo_mod.py init --database catalog.sqlite --object UI_MAIN_00 --package bin/xenon/loc/int/menu/rpmenurescommon_int.xxx --image art/UI_MAIN_00.png --id my-menu --output my-menu/mod.json--image is relative to the new specification, not the shell's working directory. init requires exactly one identity and one content variant, and refuses to overwrite an existing specification. A zero-match result requires checking the object/package/key filters; multiple content variants require --content-sha256 to select one. The same --database/--manifest/--export-index, filter, and --allow-unwired rules apply to every form of init.
The generated mod.json contains api_version, id, priority and an images array; you may add name, version, author and description. Each image has key, source, width and height. Add more image records using keys and original dimensions from the catalog. Sources must stay inside the specification directory; absolute paths, .. and escaping symlinks are rejected.
Mod folder package (default):
python tools/modding/lo_mod.py pack my-menu/mod.json --output my-menu.zip --name "My menu art" --version 1.0External-manager package:
python tools/modding/lo_mod.py pack my-menu/mod.json --layout overlay --output my-menu-overlay.zipThe tool compiles non-animated PNGs into identity-bearing LOTEX1 files. The game does not decode mod PNG files directly. Incorrect dimensions, duplicate identities and invalid input fail the build. Existing output ZIPs are never overwritten; select a new name or deliberately remove the previous build.
Mod folder ZIP layout:
mods/my-menu/mod.ini
mods/my-menu/overlay/images/key-fnv1a64-<16 lowercase hex digits>.lotex
The generated mod.ini has api_version=2, id, the optional name, version, author and description, priority and enabled=true. The metadata comes from the same fields in mod.json or from --name, --version, --author and --description, which win. mod.json is an authoring input; the runtime reads mod.ini, not JSON.
Overlay ZIP layout:
mods/overlay/images/key-fnv1a64-<16 lowercase hex digits>.lotex
No shared manifest is included in an overlay ZIP. Competing mods deliberately provide the same destination path for the same identity. See external managers.
For a portable installation, extract the ZIP beside the executable so that mods/my-menu/mod.ini is under the runtime's mod root. For another layout, copy the ZIP's mods/ contents to LO_MODS_DIR or the application's selected mods directory.
Use LO_MODS_MODE=standalone to let the mod folder win over the top-level overlay while testing it. Restart, open the native settings menu in the chosen language, and compare an obvious artwork change. Set enabled=false in that mod's mod.ini and restart to verify restoration. Removing the mod folder also uninstalls it.
To inspect an extracted payload:
python tools/modding/lo_mod.py inspect path/to/replacement.lotexThis validates the file structure and prints its embedded identity and expected overlay path; it does not prove that a game draw uses that resource. For troubleshooting, check [mods] diagnostics, the selected language, original dimensions, root placement and whether the requested resource has an active consumer.