Heavy Rain: Engine Remake

Project Details

Technologies used: Java, Drain Engine


As Heavy Rain is one of my favourite games, I was always interested in how the game works on a deeper level. After stumbling upon Hugo Peters's work on Beyond, I was inspired to do something similar and started working on researching and remaking the game engine used for Heavy Rain.

It is really fascinating to see how the data is structured in such a large interactive experience, and exploring it allowed me to understand various concepts of game engines much better. It is also incredibly rewarding being able to see a character pop up on screen for the first time, or getting some other system of the game figured out, and that has driven me to spend countless hours on this project.

The port is an emulation of the original game engine, loading the original game files and executing the original scripts. Currently implemented features include data reading, communicator instance Type/ID system, a Lua framework for executing game scripts, sequence / movie playback, dialog choices, model loading, and more.

Disclaimer: The project is unreleased and it’s purpose is purely educational

"BigFile" Data Structure

The data of the game is split among multiple BigFile files, which are indexed and categorised, also providing a database of every exported "Communicator" - game object. The entries are grouped based on their type id, which I later turned into the type name by dumping RAM from a running console.
Most of the elements feature a custom segmented Zlib based compression, which seems to be optimised to be quickly decompressed by the SPU of the PS3.
The first tool I wrote took these entries and displayed them in a nicely organised tree, also including a hex editor to view the raw data of the selected entry. This allowed me to easily navigate the internal data structure and figure out the data layouts of the individual elements.
The data of the game is being loaded by referencing the ID's of the communicators in it's header, and may be extended by a LoadingZone or a reference to a DataContainer, which simply put acts as a pack of multiple communicators. For example, after booting the game, the engine loads the GameManager, which references a LoadingZone that requests a DataContainer to be loaded.

Storyboard

The Storyboard links all the game data with the respective scene of the game and manages the story progression. After being able to link these entries, I wrote a tool which initialised the game manager and launched the selected scene from the list by executing it's Lua script.

Gameplay Scripts

Most of the game logic is implemented in Lua. This was pretty apparant, as the first entry in the BigFile tree is the GameManager, which is a long plain text Lua script initialising the state variables and starting the main, or the E3 demo storyboard depending on the config.
All the other scripts were precompiled, but it was possible to decompile them rather easily. The scripts seem to be partially generated, most likely from some sort of a visual graph scripting tool. After implementing a Lua framework based on these scripts, it was possible to execute them and get the game to launch into the first scene, displaying the splash screens and going into the menu.
The Lua framework binds the Lua functions of each communicator to the Java implementations, for example calling Play() on a VIDEO_PLAYER_CONTROLLER communicator would call the Java method Play() defined in the VideoPlayerController class:

										
    if QDR._0901_SCHRINK_Var[_sCtx].SC_1026 == nil then
      QDT.VIDEO_PLAYER_CONTROLLER.NewPlacedCtx("_0901_SCHRINK_Var", _sCtx, "SC_1026", 0)
    end
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026.OnFinish = function(A0_702)
      QDR._0901_SCHRINK_Var.SC_1026_OnFinish()
    end
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026.OnStop = nil
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026.OnKill = function(A0_703)
      QDR._0901_SCHRINK_Var[_sCtx].SC_1026 = nil
      if _sCtx ~= "default_ctx" and TableSize(QDR._0901_SCHRINK_Var[_sCtx]) == 0 then
        QDR._0901_SCHRINK_Var[_sCtx] = nil
      end
    end
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026:SetGameManager(NewOS("HEAVY_RAIN", 4216, 2))
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026:SetFileName("HR_0901_UNDERWATER.BIK")
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026:SetViewport(1)
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026:SetInstanceContext(_sCtx)
    QDR._0901_SCHRINK_Var[_sCtx].SC_1026:SetRootController(QDR._0901_SCHRINK)
    if QDR._0901_SCHRINK_Var[_sCtx].SC_1026 ~= nil then
      QDR._0901_SCHRINK_Var[_sCtx].SC_1026:Play()
    end
										
										
	public void Play() {
		DrainEntity videoEntity = new DrainEntity();
		VideoPlayer player = new VideoPlayer(Game.getGameFile(VIDEO_PATH + fileName).getAbsolutePath());
		player.onFinished = ()->{
			QdEngine.logger.debug("Video %s playback ended", fileName);
			QDR.execute(OnFinish);
		};
		videoEntity.addComponent(player);
		QdEngine.drain.getCurrentScene().addEntity(videoEntity);
	}
										
									

Sequences / Movies

The interactive cutscenes pose a major role in the game, and thus feature rather complex structures. The scene contains sequences, which are a sequence of movies that link to each other via different conditions, allowing for player interaction. A movie can contain different types of events, such as playback of sounds, soundtrack, animations, camera shots, it can also call script events at a certain time point. In data this results in a huge collection of different events, and properly implementing each one took quite a lot of time and several iterations of the code.

Sound

One of the first things I implemented was sound support. The audio is usually streamed from a CommunicatorPartition, a container for streamable data. The audio itself is MP3 encoded, with surround tracks for soundtrack. It also features the facial animation which I later found out was being streamed with the audio of the dialog. My implementation uses the OpenAL spatial audio library and the FFmpeg decoder for playback.

Scene Entity Tree

The scenes of the game consist of a node tree structure for the entities in the area. After figuring out the correct node relations, I was able to build a tool for viewing and highlighting the different nodes in a specific scene.

Meshes and Textures

All the characters, objects and the area mesh are loaded onto a scene graph of catalogs linking to mesh data. The textures are stored in standard DXT compression. The rendering uses forward-shading with multiple render passes rendering different types of lights. The game uses precompiled CGB shaders that are compressed using a propriatary dictionary-based compression algorithm. I was able to reconstruct their rendering pipeline, using decompressed, then decompiled shaders converted to GLSL code.

Skeleton and Skinning

The characters in sequences are animated using streamed animations, compressed with two different propriatary algorithms. The facial animations are using a type of curve-based encoding, while the rest of the animation is stored using a delta-based keyframe bitstream compression technique.