DAT/PRELOAD holds 139 BINPAC packs (PAC_FORMAT.md)
plus DUMMY.DAT and CVS/. Every entry in them is a copy of a file that
also exists elsewhere on the disc: a loose file, or a MES.PAC entry for
messages. A screen loads its pack in one read instead of fetching dozens
of small files. While a pack is in memory, the game reads its copies,
not the originals. An edit therefore has to reach both, which is what
patch_disc.py --copies does (REBUILD.md).
python SRC/preload.py info DAT checks every pack against its sources
and gives its free room for a rebuild.
python SRC/preload.py lists ISO prints the load lists from the game
code. python SRC/preload.py who DAT <name> says which packs hold a file
and what loads them.
| Address | Symbol | What it shows |
|---|---|---|
0x14eb4c |
(file-manager setup) | the folder ids: 0 0SYSTEM, 1 SEQ, 2 CSE, 3 MESSAGE, 4 PARAM, 6 SOUND, 7 TEST3D, 8 EMBLEM, 9 PLAYER, 10 STADIUM, 11 EVENT, 12 GAME, 13 ACROBATA, 14 BG, 15 NEWS, 16 PRELOAD. 5 is the disc root (version.dat) |
0x10c9a0 |
CFcEuro_FileResource::CommonSetup |
a folder of 100 or more means folder − 100 with the language digit put before the dot (simfile.pac → SIMFILE1.PAC). The resource name keeps the plain file name |
0x10e000 |
fcEuroFile_GetFileResourceName |
a whole-file resource is named F%02d_%04d_%04d_ + file name, with the folder and two zeros: F04_0000_0000_Regulation.tbb |
0x104ce0 |
CFcEuro_Common::SetupResource |
the load-list record (below) and the resource each kind creates |
0x104ed0 |
CFcEuro_Common::ExecuteBase |
the records are created in list order, and each one must finish loading (CheckPrepareEnd) before the next is created |
0x10d848–0x10d9dc |
CFcEuro_FileResource::Execute |
when a pack finishes loading, each entry becomes a child resource (0x10d218) named from its column 3 folder and its name, and is registered with fcEuroRsrc_EntryResource |
0x10d8fc–0x10d974 |
same | an entry with column 4 = 1 is registered without the character before the dot: gp_icon0.csp becomes gp_icon.csp |
0x112040 |
fcEuroRsrc_EntryResource |
registering a name that already exists deletes the new resource and returns the old one, so the first copy registered wins |
0x10cc98 |
CFcEuro_FileResource::ExistingSetup |
a request for an entry of a pack first looks for the pack among the resident resources |
0x10f1c0 |
CFcEuro_MsgResource constructor |
a message request first looks up F03_0000_0000_<cat>_<lang>.mbb. Only if that doesn't exist does Execute (0x10f4b0) read the file from MES.PAC, through the executable's own 3,738-entry offset table at 0x34df50 (MBB_FORMAT.md) |
0x10bf58, 0x10c008 |
FC_EURO_EVCOM sequencer commands (table 0x34de68) |
the first starts loading stationmes.pac (folder 0x74 = 116). The second waits until it has loaded, then creates the 10 global message categories from the table at 0x51a528 |
0x307f18–0x307f9c |
CFileManagerRofs::FileUpdateCore |
a whole-file request (sector count 0) takes its length from ADXF_GetFsizeSct, allocates that many sectors × 0x800 (0x307f2c, the buffer call at 0x307f54), seeks to 0 and reads them all with ADXF_ReadNw |
0x1bd234–0x1bd244 |
inside ADXF_GetFsizeSct (0x1bd1e0) |
the sector count is (size + 0x7ff) >> 11 of the byte size from 0x1bd290 (the open ROFS file; not traced further). The ISO9660 directory record is the only place the disc stores a file's size |
A module's GetPreLoadData returns an array of 0x28-byte records,
ended by kind 5.
| Offset | Field |
|---|---|
+0x00 |
kind: 0 file, 1 CSE, 2 message, 3 texture list, 4 common texture, 5 end |
+0x04 |
folder (+100 for a language digit) |
+0x08 |
file name pointer (kinds 0, 1, 3) |
+0x10 |
message category (kind 2) |
+0x18 |
stored in the file resource's flags (CommonSetup 0x10cb48). 3 for the *LARGE packs, 5 for the *LOCALMEM packs, 4 for most other files; meaning not decoded |
+0x24 |
1 = register the pack's entries (it becomes bit 8 of +0x125, set at 0x10cc10 and tested by Execute at 0x10d83c). Set in every record naming a PRELOAD pack. plresourcesim.pac has 0: its entries are read through plResource, not by name |
+0x1c, +0x20 |
kind 4 only: passed to CFcEuro_CommonTexture (0x104e2c). The season list has 12 such records, +0x20 = 1, 2, 3, 75, 147, ... 651 |
+0x14 |
passed on to the file-resource constructor; 0 in every list |
PRELOAD packs are BINPAC version 3, so each entry has two extra
columns:
| Column | Meaning |
|---|---|
| 3 | the folder the entry's original lives in (the ids above) |
| 4 | 1 if the name ends in the language digit, which is dropped when the entry is registered |
A request for a file goes by name. If a resource of that name is already
registered, it is used, and the disc isn't touched. Every load list that
names a PRELOAD pack puts the pack first and then requests the files
inside it. The pack has finished loading and registered its entries before
those requests are made, so they are served from the pack. The
original is read only when a file is requested while none of the packs
holding it is resident.
The loaders, from python SRC/preload.py lists ISO (addresses in the
file named, SLES = SLES_541.51):
| Pack | Loaded by | Resident while |
|---|---|---|
STATIONFILE |
root module list SLES 0x51ab98 (CFcEuro_RootModule::GetPreLoadData, 0x112a08) |
the whole game |
STATIONMES<n> |
the two EVCOM commands above |
the whole game. Holds the 10 global message categories 1, 3–11 (club names are category 3) |
SIMFILE<n>, SIMLARGE, SIMLOCALMEM<n> |
SIMPRG.REL list 0x214448, SIMROOT_MODULE (0x7a18; 0x214d80 in the demo) |
the season mode |
TOPMENU<n> |
SIMPRG.REL list 0x213cf8 (0x5870, after pSetupSideMenuModule) |
the side menu |
MAIL<n> |
SIMPRG.REL list 0x21bd58, MAIL_MODULE (0x36d60) |
the mail screen |
NEWS<n> |
SIMPRG.REL lists 0x2193c8 (0x23ee0, the public relations module) and 0x2195a0 (0x24440, the news module) |
those screens |
TALK_*<n> |
SIMPRG.REL lists 0x215190 (contract), 0x215660 (dismiss), 0x2162a8 (move), 0x217088 (normal, CTalkNormalManager), 0x217220 (player retire), 0x217700 (promise level 2), 0x217a40 (promise result), 0x218028 (withdraw) |
that talk scene |
TACTICSFILE<n>, TACTICSLARGE<n> |
SLES 0x55b120, CTacticsManagerImplement::GetPreLoadData (0x2e3420) outside a match |
the tactics screen |
TACTICSMATCHFILE<n>, TACTICSMATCHLARGE<n> |
SLES 0x55b3a8, the same function when its mode (+0x44) is 2 or 3 |
the tactics screen in a match |
GAMEFILE<n>, GAMELOCALMEM |
GAMEPRG.REL list 0x265590, GAME_MODULE (0xbd0) |
a match |
TACTICSPITCH |
CLoader::loadFileRequest from WP::CTacticsPitch's constructor (SLES 0x2a89cc) |
the tactics pitch. Loaded but never read: its entries aren't registered, and the pitch reads the originals in CSE/ (below) |
<n> is the language digit, 0–6 (0 Japanese, 1 English, then French,
German, Italian, Spanish and an unused seventh slot, as in MES.PAC).
TACTICSFILE.PAC and TACTICSMATCHFILE.PAC, without a digit, are not
loaded: the tactics lists ask for folder 116, which always adds the
digit.
Some copies are resident all game (STATIONFILE, STATIONMES), and
others only on one screen. A few files are also requested from places
that don't load a pack. The welcome mail's text (563_1.mbb) is an
example. The event code puts up a new mail with its own message request
(SIMPRG.REL 0x136384, through 0x12e0c8), while no mail pack is
resident, so it reads MES.PAC. The Mail screen loads MAIL<n>.PAC
first and reads the pack's copy. Both copies are read, on different
screens.
The tactics pitch (WP::CTacticsPitch) loads tacticspitch.pac and
then never reads it. To edit the pitch, change the originals in CSE/;
the pack's copies can stay as they are. Confirmed from the code, not
yet seen in the game:
| Address | Symbol | What it shows |
|---|---|---|
0x2a8a04 |
WP::CTacticsPitch constructor |
asks its CLoader (+0xb0) for tacticspitch.pac (folder 16) into pack slot 1 |
0x2a8b74–0x2a8cb8 |
WP::CTacticsPitch::Execute |
once slot 1 has loaded, asks for pitch.sno, cam_int.snd, pitch.sna, cam_int_in.snd and cam_int_attack.snd by name from folder 2 (CSE/) into slot 0, entries 0–4 |
0x2a8cf4–0x2a92e8 |
the same class | every later read (getModelTexture, getCameraMotion, getChunkSingle) takes slot 0. Slot 1 is only waited on |
0x118908, 0x101620 |
CLoader::loadFileRequest, CDataHandle::OpenReq (vtable +0x24) |
each request becomes a new CFcEuro_FileResource through CDataLoadMgr::OpenReq (0x102450) |
0x1024b8, 0x10cc10 |
CDataLoadMgr::OpenReq, CommonSetup |
the constructor's 8th argument, which sets the "register the entries" bit (+0x125 bit 8), is 0 here. So the pack's entries never become resources |
0x11a1b0 |
CLoader::l_realize_binarypack |
when a pack arrives, CLoader only lists its entries' addresses; no code in CLoader (0x117b18–0x11b000) calls fcEuroRsrc_EntryResource |
So the five requests find no registered resource by their names
(F02_0000_0000_pitch.sno and so on; no other PRELOAD pack holds
them) and read CSE/PITCH.SNO, CAM_INT.SND, PITCH.SNA,
CAM_INT_IN.SND and CAM_INT_ATTACK.SND from the disc.
sc_tac_pch_00.svr, the pitch's texture, isn't requested by name
here: it comes with the model (type 0xc, read with
getModelTexture), and how that load finds it isn't traced. It can't
be the pack's copy, which is never registered. Probably the pack was
meant to be registered, as the load lists' packs are, and the flag
was left at 0.
A test disc was patched with different text in each copy:
| File | In MES.PAC |
In the PRELOAD copy |
|---|---|---|
3_1.mbb, all 540 English club names |
MES |
STA (STATIONMES1.PAC#2) |
563_1.mbb, the welcome mail's subject and body |
Copy A: MES.PAC |
Copy B: PRELOAD (MAIL1.PAC#6) |
- VS mode, Team Selection: Birmingham showed as
STA. The club names come fromSTATIONMES1.PAC. - Loading a 2006–2007 save and opening the Mail screen (Check e-mails →
Others): the welcome mail's subject and body both read
Copy B: PRELOAD. The Mail screen readsMAIL1.PAC. - An earlier test (
MBB_FORMAT.md) showed theMES.PACtext when the same mail popped up in a new game. That is the event code's request, made whileMAIL1.PACwasn't loaded.
python SRC/preload.py info DAT:
- 139 packs, 1,749 entries. Every entry's column 3 is a known folder,
and its original exists under that folder (or in
MES.PAC). - 1,747 entries are byte-identical to their original. The other two
are
gp_practiceicon.cspin the unusedTACTICSFILE.PACandTACTICSMATCHFILE.PAC. - 63 entries have column 4 = 1, all
CSEscreen layouts, and each ends in its pack's language digit. - Entries by folder: 861
MESSAGE, 394CSE, 177GAME, 100PLAYER, 64BG, 62PARAM, 49NEWS, 21EVENT, 140SYSTEM, 7EMBLEM.
- Edit both copies.
patch_disc.py --copiesfinds them by name and bytes, andpreload.py whoshows which screens read each one. It isn't safe to skip a copy because a quick look in the game shows the other one. - A club-name edit made only in
MES.PACdoesn't show: the names are read fromSTATIONMES<n>.PACall game. - A message file that grows in
MES.PAC(MBB_FORMAT.md) also grows in itsPRELOADcopies:patch_disc.py --copiesrebuilds the packs that hold it (below), and moves a pack that outgrows its last sector (REBUILD.md).
A pack is read whole, in sectors: (size + 0x7ff) >> 11 of them, from
the size in its directory record (confirmed above). Its entries are then
found through its own header (Execute, 0x10d848), and nothing else
on the disc holds their offsets. So a pack can be rebuilt with entries
of new sizes, as long as it still ends in the same sector. Only the
pack's bytes and the size in its directory record change; no other file
moves.
How patch_disc.py does it
(REBUILD.md):
- Every edit to an entry of a
PRELOADpack is collected per pack: a copy found by--copies, or aPRELOAD/<pack>#<entry>target. - The pack is read from the image with the header it has there now, so a second run works on a pack an earlier run rebuilt.
- If every entry keeps its size, the entries are written in place.
Otherwise the pack is rebuilt with
pac.build_binpac, which lays it out exactly as the original packer did (PAC_FORMAT.md; all 139 packs rebuild byte for byte). A pack that would need another sector is moved to the end ofDATA.ISO(REBUILD.md). - An entry that grows but stays inside its
0x40gap doesn't move anything: the pack keeps its size and only the entry's size field changes.
Room. python SRC/preload.py info DAT gives each pack's free bytes
to the end of its last sector. Over the 139 packs they run from 4
(TALK_MOVE6.PAC) to 2,032, median 1,244. Two have under 64 and 19
under 256. STATIONMES1.PAC (English club names and other global
messages) has 1,840, and MAIL1.PAC 760. A grown entry also uses up to
0x3f bytes of gap after it before the later entries move.
Tested on a copy of DATA.CVM. mbb.py set renamed Birmingham
(3:2003, English) to a 63-character test name, which grew 3_1.mbb
from 6,672 to 6,724 bytes in its MES.PAC slot. patch_disc.py patch --copies rebuilt STATIONMES1.PAC from 45,264 to 45,328 bytes (entries
3–9 moved by 0x40) and rewrote its directory record. Reading the
patched image back through the decrypted table of contents gave a pack
of 45,328 bytes whose 10 entries all equal their new MES.PAC sources.
A second run changed nothing. Patching the original MES.PAC and
STATIONMES1.PAC back gave a DATA.CVM byte-identical to the original.
Tested in PCSX2. Birmingham (3:2003) and Blackburn (3:2004) were
renamed "Birmingham City FC (rebuilt pack)" and "Blackburn Rovers FC
(rebuilt pack)". That grew 3_1.mbb from 6,672 to 6,720 bytes, one byte
past its 0x40 gap. patch --copies rebuilt STATIONMES1.PAC from
45,264 to 45,328 bytes, moving entries 3–9 by 0x40, and rewrote its
directory record. In VS mode, Team Selection showed both new names in
full. The game loaded the larger pack, through the new size in the
re-encrypted directory record, and found the entries at their new
offsets.
- What record field
+0x18selects (3, 4 or 5). DUMMY.DAT(not a pack).