Skip to content

Internals

The overlay system

The game is divided into separate modules called overlays. Overlays (OVL in short) are chunk of compiled code that are loaded into a specific address based on different situations in the game. This allows to fit more content in the game.

Memory mapping (Alucard)

Address

End addr.

File name

Description

80010000

8009FFFF

main.exe

Hardware API, bootloader, shared memory

800A0000

8013BFFF

DRA.BIN

Game engine

8013C000

8016FFFF

BIN/ARC_F.BIN

Alucard spritesheet

80170000

80179FFF

SERVANT/TT_*.BIN

Familiar program

8017A000

8017CFFF

BIN/WEAPON0.BIN

Right-hand equip program

8017D000

8017FFFF

BIN/WEAPON1.BIN

Left-hand equip program

80180000

801DFFFF

ST/*/*.BIN

Stages program

801E0000

801E8000

F_MAP.BIN

Castle map graphics

801F0000

801F7800

Heap

801F????

801FFFF0

Stack

80280000

80??????

Used to load debug assets

Memory mapping (Richter)

Address

End addr.

File name

Description

80010000

8009FFFF

main.exe

Hardware API, bootloader, shared memory

800A0000

8013BFFF

DRA.BIN

Game engine

8013C000

8017FFFF

BIN/RIC.BIN

Richter program

80180000

801DFFFF

ST/*/*.BIN

Stages program

801E0000

801E8000

F_MAP.BIN

Castle map graphics

801F0000

801F7800

Heap

801F????

801FFFF0

Stack

main.exe

Loaded by the PlayStation 1 BIOS. On the game disk the name is SLUS_000.67 and it is described by the boot configuration file SYSTEM.CNF. In the absence of a configuration file, this executable can be named as PSX.EXE. The name main.exe is after its counterpart found in the PSP version.

It contains all the hardware API (eg. gamepad, CD, memory card, GPU renderer) of the PlayStation 1 console from the PSY-Q SDK. It does not contain any game logic. The entry point main is just responsible to load DRA.BIN and F_MAP.BIN from the disk and execute the entry point of DRA.BIN immediately.

This executable is also responsible of statically allocate a shared portion of memory that all the overlays can access to. One typical example is the GameApi structure. Overlays never communicate between one another with the exception of this portion of memory.

Game engine

Named as DRA.BIN. It contains all the business logic such as the game loop, the game API, logic to draw maps, load stages, render the graphics, handles the menu, defines monsters, equipment and much more. There is a state-machine that decides what the game engine is supposed to do (eg. load a new stage, spawn Alucard entity and move him across the stage, pause the game into the menu, play FMV). It is the most critical file of the game.

Stages

Contains all the unique logic to handle map specific events, cut-scenes, enemy AI, collisions and more. It also contains the rooms and entities layout. Each overlay can be considered as its own mini-game. The title screen SEL.BIN is an example of how a stage overlay can act very differently. It exposes a small portion of endpoints into the game API including the room location list or the sprite definitions.

The following is a list of the many stage overlays found in the binary of the game:

ID

PSX

PSP

Description

00

NO0

STP00

Marble Gallery

01

NO1

STP01

Outer Wall

02

LIB

STP02

Long Library

03

CAT

STP03

Catacombs

04

NO2

STP04

Olrox's Quarters

05

CHI

STP05

Abandoned Mine

06

DAI

STP06

Royal Chapel

07

NP3

STP07

Castle Entrance

08

CEN

STP08

Castle Center

09

NO4

STP09

Underground Caverns

0A

ARE

STP10

Colosseum

0B

TOP

STP11

Castle Keep (Hallway Entrance)

0C

NZ0

STP12

Alchemy Laboratory

0D

NZ1

STP13

Clock Tower

0E

WRP

STP14

Warp Room

0F

NO1

STP15

10

NO0

STP16

11

NO1

STP17

12

DRE

STP18

Nightmare

13

NZ0

STP19

14

NZ1

STP20

15

LIB

STP21

16

BO7

STP22

Cerberos (Boss)

17

MAR

STP23

Maria cutscene (clock room)

18

BO6

STP24

Richter (Boss)

19

BO5

STP25

Hippogryph (Boss)

1A

BO4

STP26

Doppleganger10 (Boss)

1B

BO3

STP27

Scylla (Boss)

1C

BO2

STP28

Minotaurus / Werewolf (Boss)

1D

BO1

STP29

Granfaloon (Boss)

1E

BO0

STP30

Olrox (Boss)

1F

ST0

STP31

Final Stage: Bloodlines

20

RNO0

STP32

Black Marble Gallery

21

RNO1

STP33

Reverse Outer Wall

22

RLIB

STP34

Forbidden Library

23

RCAT

STP35

Floating Catacombs

24

RNO2

STP36

Death Wing's Lair

25

RCHI

STP37

Cave (Reverse Abandoned Mine)

26

RDAI

STP38

Anti-Chapel

27

RNO3

STP39

Reverse Castle Entrance

28

RCEN

STP40

Reverse Castle Center (Shaft Boss Fight)

29

RNO4

STP41

Reverse Caverns

2A

RARE

STP42

Reverse Colosseum

2B

RTOP

STP43

Reverse Castle Keep (Hallway Entrance)

2C

RNZ0

STP44

Necromancy Laboratory

2D

RNZ1

STP45

Reverse Clock Tower

2E

RWRP

STP46

Reverse Warp Room

2F

NO1

STP47

30

NO1

STP48

31

NO1

STP49

32

NO1

STP50

33

NO1

STP51

34

NO1

STP52

35

RNZ1

STP53

36

RBO8

STP54

Galamoth (Boss)

37

RBO7

STP55

Akmodan II (Boss)

38

RBO6

STP56

Dracula (Boss)

39

RBO5

STP57

Doppleganger40 (Boss)

3A

RBO4

STP58

The Creature (Boss)

3B

RBO3

STP59

Medusa (Boss)

3C

RBO2

STP60

Death (Boss)

3D

RBO1

STP61

Beezelbub (Boss)

3E

RBO0

STP62

Fake Trevor / Fake Grant / Fake Sypha (Boss)

3F

NO1

STP63

40

MAD

STP64

Debug Room

41

NO3

STP65

Castle Entrance (Intro / Death Cutscene)

42

DAI

STP66

43

LIB

STP67

44

NO1

STP68

45

SEL

STP69

Title Screen / Main Menu

46

TE1

STP70

Test Room 1

47

TE2

STP71

Test Room 2

48

TE3

STP72

Test Room 3

49

TE4

STP73

Test Room 4

4A

TE5

STP74

Test Room 5

4B

TOP

STP75

4C

TE2

STP76

4D

TE2

STP77

4E

TE2

STP78

4F

TE2

STP79

Please note, at the time of writing, not all stage overlays are extracted and available for decompilation. This was a deliberate decision made in an effort to keep the scope of the project manageable during the beginning phase. As the project progresses, more overlays will likely be extracted and made available for decompilation.

Game API

Exposes endpoints from different overlays in a shared memory area, so they can communicate each other indirectly.

File handling

The game has three ways to load the game files.

LBA system

This is the main way the game loads files. To greatly improve loading times, files are accessed by directly specifying to the CD-ROM system the absolute position of the file to load in the disk. This saves time as the disk head would not need to fetch the file location and size from the main table searching by the name. The location is a 32-bit integer that represents the sector index of where the file is physically located on the disk. The size of a sector is physically 2352 bytes long, but only 2048 of them are used to store the actual game data (aside from the XA tracks and the STR videos).

There are mainly two tables in DRA.BIN that stores all the game data. One is at 800A3C40 which stores every stage offsets and size. The other table is at 800ACC74 and it is used to load specific files in different part of the memory based on their type. The stage ST/SEL/SEL.BIN contains a duplicate table of 800A3C40, probably to have some kind of stage select built in.

As the location of the files is hard-coded, their position has to be known before building the actual game. It is believed the PSY-Q SDK has tools to offer this specific functionality. In alternative the tool sotn-disk can be used to patch those binaries after the image is being built.

PSY-Q open

The classic way of opening files with the UNIX function open. This is only used by main.exe to bootstrap DRA.BIN and F_MAP.BIN. It is known to be slower than using an LBA table as it has to move the head at the beginning of the disk, look for the Primary Volume Descriptor offset and walk through the directories to resolve the chosen sector offset and file length.

Simulator

Used during the development. The file path prefix is sim: (eg. sim:c:\bin\f_title1.bin). The PlayStation 1 offers drivers for the file I/O. Most likely the developers registered the driver sim to allow the PlayStation to load overlays on the fly from a physical hard drive to quickly iterate between new builds. The main.exe initially attempts to load DRA.BIN and DRA.BIO (formal name of F_MAP.BIN) from the simulator. If it succeeded it sets the flag at 800978AC to a value different than 0, effectively indicating the game needs to use the simulator instead of the LBA for all the following files.

Graphics rendering

This is a write-up to break down how the game renders graphics on screen. In short the game has a primitive list that gets consolidated into a drawing scene every frame. When graphics contains sprites or textures the resources are loaded from the VRAM.

Primitive list

TODO

Drawing scene

TODO

Sprite banks

TODO: anim set, anim frame, clut index, tpage.

VRAM

image

This write-up focuses on how the Video RAM (VRAM) is managed by a stage. The VRAM can be divided into four quadrants, each measuring 512x256 pixels.

When working with graphics that have a 4 bits per pixel (4bpp) format, a Color Look-Up Table (CLUT) is utilized. In the following examples, a grey CLUT will be used as a reference.

To transfer graphics from the main memory to the VRAM, a DMA operation is performed using the PSY-Q SDK function LoadImage(RECT*, u_long*). The first parameter RECT specifies the location in the VRAM where the graphics will be placed. The second parameter u_long* is a pointer to the main memory location from which the graphics will be loaded. It is important to note that the coordinates and size mentioned in the RECT parameter are based on a 16-bit image. For example, to load a 256x256 4bpp texture, the RECT values will be set as w=64 and h=256.

Rendering targets

The upper-left quadrant of the VRAM serves as a double buffer and acts as the render target for creating the final picture. Utilizing a double buffer is crucial to prevent tearing artifacts in the displayed image. While one buffer is used to transfer its content to the video output, the other buffer can be populated to prepare for the next frame. The game employs two buffer descriptors, namely GpuBuffer g_GpuBuffers[2] and the variable g_CurrentBuffer to keep track of the currently active buffer for rendering the scene. When the frame is ready to be displayed on the screen, g_CurrentBuffer = g_CurrentBuffer->next is called to flip the buffers, ensuring that the rendering occurs on the appropriate buffer. During the rendering of stages, each buffer is represented as two 256x240 targets one next to the other. These buffers are stored in the RGBA5551 format, where each pixel is represented by a 16-bit value.

To achieve effects like the photo burning at the end of the prologue, the game creates a duplicate copy of one of the buffers and uses it as a texture. This technique is commonly used in PlayStation 1 games and subsequent game platforms.

Stage graphics

image

The second quadrant in the upper-right corner of the VRAM is dedicated to storing stage graphics. It specifically contains everything related to a given stage, including the tileset and decorative entities (e.g. the moving trees in parallax outside the castle or the clock tower in the prologue). Stage graphics are loaded from ST/{stage}/F_{stage}.BIN which is always a 256KB file consisting of 32 images of 128x128 pixels and a 4bpp format. Typically the graphics at the far left is used for the tile map and the one at the far right is for the entities.

The first few 256x256 tpages of the stage graphics are 256x240 of the actual tiles and the bottom 16x256 is utilised as a palette collection in the RGBA5551 format. All the stage graphics exclusively use the CLUT at that location to render the tile map. As a result the last row of the tileset is reserved for the palette, often resulting to either uncomprehensive graphics or left blank.

The tiles within the tileset are referenced by an index, where each tile occupies a space of 16x16 pixels. Tile 0 is always empty and it is never rendered. Tile 1 is located at (16, 0) while the tile 15 is is at (240, 0). Following this pattern, tile 16 can be located at (0, 16). It is worth noting that tile maps are crafted in a way where tiles from 240 to 255 are never used, as they contain the CLUT for the stage. Typically, after tile 239, the next tile would be tile 256, positioned at (256, 0).

DRA entities

image

The fourth quadrant at the bottom-right is dedicated to the game engine DRA.BIN. It stores the graphics that are shared across stages and can be reused without the need to load them every time. This is true even if the shared entities code (e.g. the candle or the item prize) is store inside each stage overlay and not in DRA. For Alucard, these graphics are loaded from BIN/F_GAME.BIN, while for Richter, they are loaded from BIN/F_GAME2.BIN. This section of VRAM contains a series of 32 images, each with a size of 128x128 pixels and a 4bpp format.

The file from which these graphics are loaded is 264KB in size. The last 8KB of the file is allocated for the CLUT, which is loaded at the very bottom of the first quadrant, just below the two rendering targets.

Dynamic entities

image

This last section is still work-in-progress. It is currently unknown how the game dynamically loads this graphics and where it comes from. It seems to be dynamically loaded from memory based on the room where the player is into.

Beta content

ST0

TODO

MAD

The debug room overlay ST/MAD.BIN was compiled earlier than the first retail release of the game. All the offsets that refers to DRA.BIN points to invalid portions of data or to the wrong API calls, effectively breaking the majority of its original functionalities. That is why the debug room does not contain any object. By compiling the debug room with make mad_fix you can restore it by redirecting the old pointers to the retail version of the game.

The factory system

Many entities in the game spawn other entities in a roundabout, indirect manner. For example, let's consider the function DoGravityJump, which is used when the player activates their gravity boots. It accelerates the player rapidly into the air, but it also creates a factory, using this syntax:

CreateEntFactoryFromEntity(g_CurrentEntity, FACTORY(0, 2), 0);

This will create a factory. The factory is a short-lived entity which exists solely for the purpose of spawning another entity. We pass it a basis entity (in this case g_CurrentEntity) which will act as the parent of our new entity. By default, our new entity gets spawned into the same location as the parent.

The FACTORY() macro is important. It controls the behavior of the factory to determine what child is made, and any parameters related to that.

Factory Blueprints

When a factory is created, it has the purpose of spawning a child. It builds that child (hence being called a factory), and does so based on a set of parameters. We call these parameters the "blueprint", the design for building the child. In this case, the gravity jump is creating a factory to use blueprint 2.

To see what blueprints are doing, we can look to assets/dra/factory_blueprint.json (note: Richter has his own set of blueprints, so any functions in RIC should look to assets/ric for those blueprints). We can then look at blueprint 2, as the blueprint with ID of 2. That is:

{
            "id": 2,
            "id_hex": "2",
            "ram_addr": "800AD1E0",
            "childId": 3,
            "unk1": 1,
            "unk2": {
                "bit7": true,
                "bit6": true,
                "bottom6": 1
            },
            "unk3": 0,
            "unk4": {
                "topHalf": 2,
                "bottomHalf": 0
            },
            "unk5": 0
        },

Everything from childId down is controlling how the factory behaves. Some factories will spawn multiple child entities, for example. A lot of these fields are not understood, and should be researched. Look to EntityEntFactory's first block of lines to see how the data is loaded into the fields of the factory.

The most important element of the blueprint is the childId, which tells us what child is made. In this case, the child is entity #3.

Child entities

When looking at these child entities, they are numbered and are pulled from a few different tables. Factories in the main game and overlays will pull from g_DraEntityTbl (and in Richter's code, g_RicEntityTbl). In this case, we will look at the #3 entity (counting from 0 of course) in the DRA entity table, and we find EntityGravityBootBeam. Lines right up! This factory will create the blue and white beam from Alucard's feet when gravity jumping.

Weapons are different. You will usually see weapon calls like

g_api.CreateEntFactoryFromEntity(self, ((g_HandId + 1) << 0xE) + FACTORY(0x100, 88), 0);

The use of g_HandId is crucial for knowing this is a weapon call. You can look to func_8011A4D0 to understand how entity update functions are assigned to new entities. Once we identify this as a weapon call, we can look to the blueprints. We have blueprint 88, which says it has child 9 (again, in assets/dra/factory_blueprint.json). But this is not the 9th entity in g_DraEntityTbl! This is the 9th element in the Weapon struct in include/weapon.h. We can count these down and see func_ptr_80170024. This factory call will create an entity whose update function is func_ptr_80170024.

This process of identifying what a factory does can be a little confusing. Anywhere in the code which uses a CreateEntFactoryFromEntity call should ideally have a comment saying what entity is being made from the chosen blueprint.

It would also be nice if we had a macro for something like WPN_FACTORY() which would bake in the g_HandId logic, which is somewhat bulky.