Skip to content

Support PS2 content - #49

Open
nolffan wants to merge 19 commits into
thief-modfrom
ps2
Open

nolffan wants to merge 19 commits into
thief-modfrom
ps2

Conversation

@nolffan

@nolffan nolffan commented Oct 27, 2026

Copy link
Copy Markdown
Owner

No description provided.

nolffan and others added 19 commits October 27, 2026 09:21
The PlayStation 2 release of No One Lives Forever has levels the PC game
never had (the three "Nine years ago" flashbacks; a secret basement in
"A Man of Influence"). These tools, in build/ps2/, are the start of
adding them to the modern build from the player's own PS2 disc; nothing
from the disc is committed.

- ps2rez.py reads LITH_PS2.REZ: its directory tables and the hash its
  file names are looked up by.
- ps2world.py reads a PS2 world (.ltb, version 66): the PC's BSPs with
  the PS2's own polygon, lightmap-page and texture data, its static
  lights and objects. spec-ps2-world.md is the format.
- ps2dtx.py turns the PS2's 8-bit palettised textures (BPP_32P) into PC
  32-bit textures with mipmaps.
- pcworld.py reads and checks a PC world (.dat, version 66), every byte
  accounted for; spec-pc-world.md is the format, from our engine's loader.
- ps2topc.py converts a PS2 world into a PC world: one surface per
  polygon, texture mapping solved from the PS2 texture coordinates and
  the PC texture's size (the PC's own, higher-resolution texture wherever
  one has the name), the PS2's baked lightmaps resampled into PC
  lightmaps, the lights as Light objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ps2model.py reads a PS2 model (.ltb version 16: rigid and skinned
pieces from their VU DMA chains, LODs, skeleton, weight sets, child
models, animations, sockets) and writes a PC model (.abc version 12)
with abcmodel.py, a reader and writer of PC models that rewrites every
.abc of the retail archives byte for byte. spec-models.md is both
formats.

The PS2 keeps names only as hashes, with each model's own multiplier;
they are looked up in a dictionary of candidate names (collect_names:
every name in the PC models, the identifiers in the PS2 executable and
attribute files), collected from the discs and never committed. A
multiplier can be as small as 2, so names collide ("Report" and
"Select" hash the same): the names the game code asks for come first.

A PC vertex weight holds the position in the bone's space times the
weight; the PS2's skinned pieces are converted so. Young Cate's model
(hero_thief) has the PC player's skeleton, so it uses the PC's own
animation models. Checked against PS2 models with PC twins (a hat, a
purse, the lighter): same faces, coordinates, weights and animation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PS2 keeps a level's sounds in a bank (lps/<level>.lps) and its
voice-over in streams (stream/.../vo_*.lps): one container, a header,
72-byte sound records, a tree of groups that gives each sound its PC
path, mono PS-ADPCM data, lip-sync bytes and an SPU preload image.
spec-sound.md is the format.

ps2sound.py decodes them to 16-bit WAV. ps2soundpc.py finds the sounds
the PS2-only levels need that no PC archive has -- from the levels'
objects, their dialogue IDs and the PS2 attribute files -- and writes
sound-map.txt, the list the installer extracts: the thief levels'
74 lines of dialogue (VOICE\14001-14074), their new events and the
safe cracker's sounds. Checked against sounds both releases have: the
same length, and in step once the PS2's leading silent frame is
skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PS2 string table (LTSR: a count, ID/length/offset records, Latin-1
strings; five languages in the archive) has the text the PS2-only levels
use: 103 strings of dialogue, objectives, mission-failed messages, intel
texts, the gadgets' names and the loading-screen line. ps2strings.py
decodes it and writes it as a resource script, to be compiled into the
modern build's CRes.dll at build time (the text is the disc's, so it is
generated, never committed, as the Game of the Year text is).

19 of the PS2's IDs are taken on the PC by other text, so they and the
gadgets' move into package P8's range, 31700-31799 (ps2-string-map.txt,
IDs only); remap_objects rewrites them in a converted level's commands.
The rest keep their PS2 IDs, free on the PC: 5326, 6535-6537 and the
dialogue's 14001-14074, which name its voice files. src/modern/cres/p8.h
names the moved ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What the PS2 release's client does for its "Nine years ago" levels, in
the modern build (M-P8-04, M-P8-05):

- In a thief level (Worlds\thiefmission*) the loading screen shows
  "Nine years ago...", the scene of the mission it is played in, and the
  thief photo, and the first-person hands are young Cate's
  (guns\skins_pv\ThiefHands_pv.dtx). The PS2 knew the levels by
  mission and level; here they are known by world name, which also
  covers +runworld. cshell/p8_thief.*, one call each from LoadWorld and
  the two places the weapon model picks its hands.
- The levels' handbags, moneybags and money are three new intel types,
  with popups ScaleFX261-263; the intel folder lists them. An intel
  item's type is stored as a byte, so the folder compares against
  261-263 as bytes.

The release build is unchanged (make progress).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With PS2=1 and the PS2 disc image (USA or European: the same files) in
rez/isos/ps2/, the modern build gets the PS2 release's three "Nine years
ago" levels; without it, nothing changes.

- build/ps2/ps2install.py writes everything the levels need into the
  game directory's custom/, from the disc image (its own ISO9660 reader;
  the PC files from the game's archives), in about 25 seconds: the three
  worlds (string IDs remapped, a door sound's typo fixed), their PS2-only
  textures, the models and skins (the gadgets, young Cate, the police
  hat, the intel popups), 81 sounds, the gadget icons and the loading
  photo as PCX. It ends with a check that every file the levels and the
  attribute recipes name is there.
- build/ps2/data/ holds the attribute recipes: MISSIONS (the flashbacks
  inside missions 3, 5 and 13, as on the PS2; the gadgets one-time),
  WEAPONS (the Thief Safe Cracker and Thief Lock Pick, appended, and the
  safe cracker's ammo), INTELITEMS and FX (the new intel), MODELBUTES
  (the "Thief" style), ATTACHMENTS (the police hats). They need the
  converted content, so build/ps2/ps2data.py applies them on top of
  src/modern/data's, not tools/moddata.py.
- build/all.sh step 2d writes the levels' text into CRes.dll
  (ps2strings.py, now reading the ISO too); build/variant.sh installs
  the rest after the modern data; build/ps2/ps2build.sh does both, and
  takes out what an earlier install wrote (custom/PS2-DATA.txt lists it).
- build/package.sh makes no public archive of a game directory with the
  PS2 levels: they come from the PS2 disc, which the public archive's
  install scripts cannot read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PS2 release's "A Man of Influence" has a secret basement under the
Baron's front courtyard that the PC's has not: a kennel off the side
yard with a guard dog, a lift down, a shooting gallery with three rounds
of targets and a hidden rocket launcher, an indoor "safari" range with a
painted sky, a pond and moving targets, a row of cells, a store room,
and a second lift that comes up through the courtyard statue. It is
optional, with no objective or text of its own, and open only before
Cate rings the doorbell.

It is in the PS2's first half of the third scene (m07s03_A; the second,
m07s03_B, the interview, is the PC's polygon for polygon). With no world
compiler or level source to add it to the PC world, the mission plays
the PS2's two halves instead of M07S03 (M-P8-09):

- the installer converts both (their strings are the PC's: no remap)
  and the sliding doors' sound, and leaves out two PS2 inventory
  resets -- the PS2 has no loadout screen, and on the PC they would
  throw away the player's -- making the basement's crossbow and rocket
  launcher the mission's one-time weapons instead;
- a save made in the PC's M07S03 goes on as the last half, whose end is
  its end: a world in no mission whose <world>_B is in one is that
  level (MissionMgr, M-P8-08). The release build is unchanged (make
  progress).

The install check now knows references the retail game is missing too
(a dog head skin, a sky texture, eight interview questions with no
voice) and does not count them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PS2 model's piece has a colour it is drawn in and a glow added on top:
the safe cracker's wires, buttons and terminals are red, blue, green and
orange, and its two LEDs glow red and green. A PC model has neither, so
they came out in the skin's plain white and grey.

ps2model.bake_colours puts them into the skin: a copy twice as tall,
where each coloured piece gets a patch of its own -- the skin under it
times its colour, plus its glow -- and its texture coordinates move
there. The first-person safe cracker gets that skin
(Guns\Skins_PV\PS2SafeCracker_pv_leds.dtx, its PVSkin); the in-hand
model, whose pieces are plain, keeps the PS2's. The LEDs are bright, but
lit by the room as the rest is: the PC has no glow per piece.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Interface options gain "Main menu Cate": Seasonal (the day's, as
before, and the default), Original, Casual, Winter and -- with the PS2
levels -- Young Cate, the PS2 release's young Cate in her catsuit,
holding the P38 as the others do (MainMenuCate, M-P8-10).

Her menu model is made at install time from the converted in-game one:
its mesh, which has the menu model's 25 nodes, on the retail
IntHero_Action's one animation, interface4 -- as the menu's casual and
winter Cates are the in-game outfits on the menu skeleton -- installed
as the IntHero model in the "Thief" style, with her skins under the same
name. Her main menu is a fourth main-menu folder (FOLDER_ID_MAIN_THIEF,
[FolderMainThief], character CateThief); its layout recipe is the PS2
levels', so without them the choice is not offered and MainMenuCate 4
means seasonal.

Backing out of the options to the main menu asks the setting again
(the folder history holds the main menu that was showing), so a new
choice shows at once. The menu's strings are ours (cres/p8.rc). The
release build is unchanged (make progress).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MODERN.md gets package P8 (strings 31700-31799) and its ten items:
the conversions (levels, models, sounds, images), the thief safe, the
thief levels' look, the new intel, the strings, the game data, saves in
a level the PS2 splits, the Baron's training rooms and the main menu's
Cate, plus the MainMenuCate setting. BUILDING.md says what
make modern PS2=1 does; the changelog lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
P9_HaveThiefMenu (her menu model, and the layout section master's recipe
now adds) replaces P8_HaveYoungCateMenu, and the PS2 data's own LAYOUT
patch goes. The seasonal choice includes the mod's night-time young Cate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The converted worlds' keypads with CanDamage (the PS2's mark) get the
property the game reads (M-P9-04).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the mod installed too, its versions take their place, and a save in
the PC world goes on in the last half (M-P9-07).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An intel item's type is a byte: at 261-263 the PS2 build's handbags,
moneybags and money were stored as 5-7, and the gallery showed ScaleFX5-7
for them. The retail effects there move to 261-263 (looked up by name).
The gallery names them with the mod's strings, else the PS2 build's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S2 build's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every world texture the PS2 disc has is installed from it under PS2\ and
the converted worlds name those; the PC levels keep the PC's textures of
the same names. Before, a texture the PC had was drawn with the PC's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iles leave

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant