Sphinx and the Cursed Mummy Wiki
Advertisement

The Filelist is the container and file-tracking format used by Eurocom's EngineX to bundle all the loose asset files into a big, single package that improves disc seek times and adds the semblance of a proper hierarchical, multi-folder, case-sensitive filesystem in platforms that often didn't have one or where doing it was considerably slower, bringing uniformity. This kind of virtual filesystem is still common in more modern games.

Filelist.bin[]

Filelist.bin is the binary descriptor file that maps a bundled file's hashcode to a path and package number + offset. EuroLand automatically regenerates the descriptor after exporting an EDB file by calling x:\EngineX\Utils\Batch\MkFileList.bat with the project name (Sphinx) and platform (PC) as parameters. The batch script eventually calls x:\EngineX\Utils\xutil.exe with the /f argument and the script (*.scr) file and appends the two parameters.

Which files are included in each platform and in which order are laid out is done via the *.scr text files in X:\Sphinx\GameSpec. These files can also specify an optional BlockSize to split packages in multiple containers of a maximum size, so far only used in the Xbox version of Sphinx.

  • PS2: Filelist.000
  • GameCube: Filelist.000
  • Xbox: Filelist.000, Filelist.001, ...
  • PC and Nintendo Switch: In the newer ports the engine still uses a descriptor, but files aren't packed, and the X:\Sphinx\Binary\_bin_PC\ paths are automatically changed to work relatively from the working directory.

File format data structures[]

Filelist header
Offset Size Description
0h 4 Magic. Contains an unsigned number with a magic value. Retail is version 5, the standalone demo has version 4.

GameCube is big-endian, PS2, Xbox and PC are little-endian. This fact can be used to auto-detect endianness; on PC and PS2 a 5 will appear as 05 00 00 00 in hex, while on GCN it will appear as 00 00 00 05.

4h 4 Total file size, in bytes.
8h 4 List item count. Number of file entries in the element array.
Ch 4 1 or 0. Probably the maximum offset count for each entry. Only in version 5. Elements need to be of a constant size for binary search, so this is a hint to get the amount of extra offset blocks it may have. When zero it only has one block.
10h 4 Relative offset to next section; the string pointer array. In bytes. Add the value to the current field's absolute offset. In original game files this gets us right after the element array.

e.g. next_section = offsetof(relative_offset) /* 0x10 */ + relative_offset.

14h ~ Element array. See List item count for the number of indexes/entries in the array.

Keep in mind the structure variation depending on version 4 or 5.

Offset Size Description
Version 4 field (originally there was only one single offset and one Filelist.DAT file)
4 Location offset. Absolute offset to find the current file's start point within the .DAT file.
Common fields
4 Length. File length of the matching file within the pack file. If set to zero the engine will ignore the Filelist.000 and will try to load loose files in the filesystem via path, like in the PC version.
4 Hashcode. Each file is expected to have an unique hash value of the correct section type. Entries must be sorted numerically by hashcode for faster (binary search) lookups. Unordered entries may break the game.
4 Version. Only EDB files and their Euroland-exported version have them. Same as in the EDB header. Allows the game to easily detect if the assets are too old for the current EngineX.
4 Flags. Only the EDB files seem to have something other than zero, here are some incomplete flags observed in the wild. Same as in the EDB header:
EDB file parameter flags
1 << 0 hasAnimations
1 << 1 hasEntities
1 << 2 hasMaps
1 << 3 hasScripts
1 << 4 hasErrors
1 << 5 hasRecompressed
1 << 25

EL2SceneFormat # swy: unused in practical terms; the EuroLand 2 scene
               #      format wasn't ready for Sphinx, I think

1 << 26 GCOutput # swy: reportedly unused, GameCube EDB files are marked as PC by Euroland by mistake, so this doesn't work
Note: GameCube EDB files are the only big-endian ones, so they are easy to distinguish from the others by the GEOM magic header, which is MOEG in little-endian (e.g. PC/Xbox/PS2).
1 << 27 XBOutput # swy: reportedly unused, Xbox EDB files are marked as PC by Euroland by mistake, so this doesn't work
Note: To distinguish them from other platforms, Xbox EDBs should be MOEG/little-endian, marked as PC, and the uint32_t at offset 0x1c should be one/not zero. Another, more advanced way, of distinguishing them is the minimum alignment of the sections; as this number is platform dependent and it won't match the PC or PS2 granularity.
1 << 28 PS2Output
1 << 29 PCOutput
1 << 30 tempOutput
1 << 31

is64Bit # swy: added it to Euroland Redux to distinguish my newer EDB file
        #      format with enough space for 64-bit pointers; took a while

Version 5 fields (multi-file support)
4 Offset count, number of offsets that follow, at least one must exist.
8 Array of Filelist.XXX offsets; the same file can be stored multiple times in the same or different pack data file to improve seeking times in optical discs.
Offset Size Description
0h 4 Offset, the absolute start point/location where our file is contained within the target file. See below.
4h 4 File index; tells us which Filelist.XXX file to read the data from when it is split into multiple archives. The original file's .bin extension gets stripped and the number gets zero-padded and appended as the final data file's extension. e.g. 0 -> .000, 3 -> .003.


Originally there was only a single data file called Filelist.DAT.

Structure ends here
Note: To retrieve the corresponding filename in the string data block, we need to first index the string pointer array using the current entry count/index. And then use as In code it would be done more or less like this:
/* swy: go to the start of the string pointer array and index;
        the last part is equivalent to this ptr_array[idx] */
FSeek(startof(next_section) + next_section + (index * 4));
int str_rel_offset = FReadUnsignedIntAtCurPosition();

/* swy: now the current file context is pointing to the start of the C string,
        which ends with a NULL byte */
FSeek(startof(str_rel_offset) + str_rel_offset);
const char *str = ReadStringAtCurPosition();

Start of the next section with the string look-up table (see Relative offset to next section)
~ String pointer array of size List item count. Each entry is a 32-bit unsigned integer as follows:
Offset Size Description
0h 4 Relative string offset.
~ String data block. It goes right after the previous (0x4 * List item count) block. What follows is a concatenated list of NULL terminated C strings, without padding. We can access the right string from a specific index by using the indirection layer above.
Advertisement