WDATA File
Rusty Hearts .wdata File Format
.wdata
files are a binary container used by Rusty Hearts that bundle all of a map’s runtime data into a single package file that tell the engine what files to use (models, navmeshes, scripts), interactive volumes called EventBoxes (npc/enemies spawn points, triggers, portals, camera zones, etc.), animated background objects (AniBG), item containers (ItemBoxes), in-world gimmicks (traps, platforms), scripting triggers (events/conditions/actions), and cutscene data (Scenes and SceneResources). Together, these sections package all of a map’s resources into a single, versioned binary format that the engine can load and execute at runtime.
File Header and Versioning
Signature: Each .wdata
file begins with a UTF-16 little-endian string signature. The signature is `"stairwaygames."
Note: Each string in the .wdata
file is stored with a 2-byte length prefix. For example, "stairwaygames."
would be stored as 0x0F 0x00
(15 in little-endian) followed by the 15 UTF-16 characters.
private string ReadWString(BinaryReader br)
{
ushort len = br.ReadUInt16();
return len == 0 ? string.Empty
: Encoding.Unicode.GetString(br.ReadBytes(len * 2));
}
Version Fields: Immediately after the signature, a Main Version number is stored. This main version dictates the presence of additional sub-version fields and format variations:
Main Version (
int32
): Primary version of the.wdata
format. This field is always present and must be read first.EventBox Version (
int32
): Present ifMain Version >= 7
. Version number for the EventBox data structures (see EventBoxes section).AniBG Version (
int32
): Present ifMain Version >= 7
. Version for Animated Background (AniBG) structures.ItemBox Version (
int32
): Present ifMain Version >= 7
. Version for ItemBox structures.Gimmick Version (
int32
): Present ifMain Version >= 8
. Version for Gimmick structures.Reserved Field (v9) (
int32
): Present ifMain Version >= 9
. This 32-bit value is read but not used by the parser (likely reserved for trigger/event counts related to the scripting system).Reserved Field (v16) (
int32
): Present ifMain Version >= 16
. Another reserved 32-bit value (unused by current readers; possibly introduced for future extensions).Reserved Fields (v18) (
int32
× 2): Present ifMain Version >= 18
. Two reserved 32-bit integers (unused by current readers; possibly introduced for future extensions).
Paths Section
Following the header, the .wdata
file contains a series of file path strings. A placeholder string ".\"
(dot + backslash) is used to indicate an “empty” path;. All paths in this section use this format, and any ".\"
value should be interpreted as “no path” or not used.
The paths appear in the following order:
ModelPath (WString): Path to the main map model (.mmp).
NavMeshPath (WString): Path to the navigation mesh file for the map (walkable space navigation).
NavHeightPath (WString, if Main Version ≥ 2): Path to the navigation height-map file. This is present only in version 2 and above.
EventBoxPath (WString): Path to an external event box definition file. In practice, event boxes are embedded in the
.wdata
(for v7+), so this path is often empty (".\"
).
EventBoxes Section
The EventBoxes section contains definitions of various interactive or trigger volumes in the map (spawn points, triggers, portals, etc.). This section is present only if Main Version >= 7
, since EventBoxes were introduced in version 7 of the format. If the main version is below 7, no EventBox data will be present (the file proceeds directly to the next section).
The EventBoxes section begins with an index table that outlines the types of event boxes included and where their data is located. The structure is as follows:
uint32 typeCount;
struct EventBoxIndexEntry {
uint32 offset; // file offset where data for this type begins
uint32 count; // number of EventBox records of this type
} entries[typeCount];
Here, typeCount
is the number of distinct event box type slots included. Each EventBoxIndexEntry
corresponds to an EventBox type ID from 0 up to typeCount-1
in sequential order. The index of each entry implicitly represents the EventBox type (e.g. the first entry is type 0, second is type 1, etc.). The offset
is an absolute file position (from beginning of the file) where the data records for that type are stored, and count
is how many such records exist. The entries may include types with zero count – if count
is 0, it means no objects of that type are present (the offset
in that case can be ignored). The entries are not guaranteed to be sorted by offset in the file, so a reader should use the provided offsets to seek to each type’s block. (In practice, type entries often appear in ascending order of type ID, but the actual data blocks might not be in type ID order, hence the need for offsets and the sorting performed by the reader.)
After the index table, the EventBox records for each type follow, typically grouped by type. To read them, one would iterate through the index entries (often in order of increasing offset) and for each entry, seek to the given offset and read count
records of that type.
EventBox Types Overview
Each EventBox record begins with a common Oriented Bounding Box (OBB) header, which defines the volume in 3D space. This OBB header consists of:
Name –
WString
: The name of the event box.Position – 3×
float
: The 3D position (X, Y, Z) of the box’s center.Scale – 3×
float
: Scale factors applied to the box (typically 1,1,1 for standard size).Rotation – 4×
float
: Rotation quaternion (X, Y, Z, W) defining the box’s orientation in the map.Extents – 3×
float
: The half-dimensions of the box (extent in X, Y, Z from the center). This defines the size of the box along each local axis.
All EventBox types include these fields at the start. In the structures below, we denote this common header as OBB base
. After the OBB, each EventBox type may have additional fields specific to its function. The availability of some fields depends on the EventBox Version (the sub-version read from the header).
The EventBox types and their IDs are listed below:
enum EventBoxType
{
CameraBox = 0,
RespawnBox = 1,
StartPointBox = 2,
TriggerBox = 3,
SkidBox = 4,
EventHitBox = 5,
NpcBox = 6,
PortalBox = 7,
SelectMapPortalBox = 8,
InAreaBox = 9,
EtcBox = 10,
CameraBlockBox = 11,
CutoffBox = 12,
CameraTargetBox = 13,
UnusedBox = 14,
MiniMapIconBox = 15,
EnvironmentReverbBox = 16,
WaypointBox = 17,
ObstacleBox = 18
}
0
CameraBox
Camera trigger volume with predefined camera views and settings.
1
RespawnBox
Enemy spawn point volume (with spawn counts and respawn info).
2
StartPointBox
Map start positions marker.
3
TriggerBox
Generic trigger volume (can activate events, with state/motions).
4
SkidBox
Slide/knockback volume (imposes velocity and timing on entry).
5
EventHitBox
Trigger volume for damage events (inflicts damage or effects).
6
NpcBox
NPC spawn point (defines an NPC by name and IDs).
7
PortalBox
Map transition portal (warps to another map/portal ID).
8
SelectMapPortalBox
Portal that opens a dungeon map selection UI.
9
InAreaBox
Area trigger for map events (e.g. enters area to trigger event).
10
EtcBox
Miscellaneous trigger/marker (generic ID, no additional data).
11
CameraBlockBox
Region that blocks camera movement or line-of-sight.
12
CutoffBox
Navigation cutoff volume (e.g. for AI navmesh or event boundaries).
13
CameraTargetBox
Camera focus target volume (e.g. for boss or point of interest).
14
(Unused/Unknown)
No known usage; may be unused/reserved.
15
MiniMapIconBox
Position of a minimap icon (with icon type).
16
EnvironmentReverbBox
Region that changes audio environment reverb settings.
17
WaypointBox
Pathfinding waypoint node (with links to other waypoints).
18
ObstacleBox
Obstacle volume (associated with obstacle path, no extra data).
Notes: Type 14 does not appear in any known .wdata
files or the decompiled reader logic, indicating type 14 is likely unused or reserved. Each type’s specific data structure is detailed below. In the structures, EventBoxVersion
is abbreviated as evVer
, referring to the EventBox sub-version that was read from the header.
CameraBox (Type 0)
A CameraBox
defines a volume that, when the player or camera enters it, triggers specific camera viewpoints or cinematic camera behavior. It can contain up to 4 predefined camera configurations.
struct CameraBox {
OBB base; // Common OBB fields (Name, Position, Scale, Rotation, Extents)
uint32 Category; // Camera box category (only present if evVer >= 7)
CameraInfo CameraInfos[4];
};
struct CameraInfo {
// Camera target name (the object or bone the camera should track)
WString CameraTarget; // Present for each entry if evVer >= 3; if evVer < 3, only the first entry has a CameraTarget (others are omitted)
WString CameraName; // Name/identifier for this camera shot
// Camera transform and lens settings:
float CameraPosX, CameraPosY, CameraPosZ; // Camera position coordinates
float CameraRotX, CameraRotY, CameraRotZ; // Camera rotation (Euler angles or orientation vector)
float FOV; // Field of view (in degrees)
float Frustum[14]; // Additional frustum parameters (14 floats, usually related to view frustum extents/clipping planes)
bool BuildPVS; // If evVer >= 5, a boolean flag (stored as uint32) indicating to build PVS (Potentially Visible Set) for this camera
if (evVer >= 4) {
// Various object lists that control rendering and visibility when this camera is active:
uint32 PVS_BG_Count;
uint32 PVS_EventBox_Count;
uint32 RenderBg_Count;
uint32 RenderAni_Count;
uint32 RenderItem_Count;
uint32 RenderGimmick_Count;
uint32 NoRenderBg_Count;
uint32 NoRenderAni_Count;
uint32 NoRenderItem_Count;
uint32 NoRenderGimmick_Count;
uint32 BuildPos_Count; // Number of "build position" points
// PVS (Potentially Visible Set) lists:
uint32 PVS_BG_IDs[PVS_BG_Count]; // IDs of background objects always visible
if (evVer < 6) {
uint32 PVS_EventBox_IDs[PVS_EventBox_Count]; // IDs of event boxes visible (pre-v6)
} else {
struct { uint32 Category; WString Name; } PVS_EventEntries[PVS_EventBox_Count];
// Each entry has a category ID and name for event boxes visible (v6+ splits EventBox references into category+name)
}
// Render/NoRender lists – each entry is an object reference that should be forced rendered or hidden for this camera.
// The format of each entry depends on the Main Version (not the evVer):
// If Main Version >= 22, each entry has a uint32 Category ID followed by a WString Key (name).
// If Main Version < 22, each entry is just a WString (no category; implicitly category is 0 or not used).
struct Entry { uint32 Category; WString Key; }; // (Definition for clarity)
Entry RenderBgList[RenderBg_Count]; // Background objects to force render
Entry RenderAniList[RenderAni_Count]; // Animated BG objects to force render
Entry RenderItemList[RenderItem_Count]; // ItemBox objects to force render
Entry RenderGimmickList[RenderGimmick_Count]; // Gimmick objects to force render
Entry NoRenderBgList[NoRenderBg_Count]; // Background objects to hide
Entry NoRenderAniList[NoRenderAni_Count]; // AniBG objects to hide
Entry NoRenderItemList[NoRenderItem_Count]; // ItemBox objects to hide
Entry NoRenderGimmickList[NoRenderGimmick_Count]; // Gimmick objects to hide
float3 BuildPosList[BuildPos_Count]; // List of positions (each 3 floats) used for building PVS volumes or other camera-related reference points.
}
};
Explanation: The CameraBox
includes an optional Category
field (a 32-bit value) introduced in EventBoxVersion 7. The core of a CameraBox is the array of 4 CameraInfo
entries. Each CameraInfo
provides a camera’s target (only the first had a target in older versions) and a name, followed by 21 floats of camera parameters (position, rotation, FOV, and 14 frustum parameters). If the EventBox version is 5 or above, BuildPVS
is a flag indicating that entering this camera box should trigger PVS computation (the code reads a uint and treats nonzero as true).
For EventBoxVersion 4 and above, a series of counts is present, followed by multiple lists that control which objects are rendered or excluded when this camera view is active. The PVS_BG list (IDs of background models) and PVS_EventBox list define which static world and event objects are considered visible in this camera’s context. Notably, the format of the PVS EventBox list changed in version 6: earlier versions store only a list of IDs (integers), whereas v6+ stores a list of structures with a category and name (likely to allow more flexible identification). The Render/NoRender lists specify additional objects to force-show or force-hide for this camera. In main versions 22 and above, each entry in those lists carries an extra category identifier before the object’s name/key, whereas in earlier versions only the name is stored. Finally, BuildPos points (if any) are positions in space related to the camera setup (possibly used by the engine to build volumes for PVS or camera transitions).
A CameraBox record in the file will contain all the above fields in sequence. Not all camera info entries may be meaningfully used by the game (often only the first one might be used for simple camera triggers), but the file allocates space for 4 entries regardless. If fewer than 4 are configured, later entries may have default or empty values (e.g., empty strings, zeroes).
RespawnBox (Type 1)
A RespawnBox
defines an enemy spawn volume, used in dungeon maps to spawn waves of enemies.
struct RespawnBox {
OBB base; // OBB header
int32 TotalEnemyNum; // Total number of enemies that can spawn from this point
int32 EnemyNum; // Number of enemies to spawn at one time (spawn wave size)
WString EnemyName; // Enemy type name to spawn
float RespawnTime; // Respawn delay in seconds (time before respawn)
WString RespawnMotion; // Animation/motion to play on spawn
bool InCheck; // If true, possibly indicates "spawn when in area"
bool RandomDirection; // If true, spawned enemies face a random direction
bool Difficulty[5]; // Array of 5 booleans (each stored as int32) corresponding to difficulty flags (one for each difficulty level, e.g.,Normal, Hard, etc.)
};
StartPointBox (Type 2)
A StartPointBox
marks a start location of the player in a map.
struct StartPointBox {
OBB base;
int32 ID; // Identifier for the start point
};
The ID is used to differentiate multiple start points or to reference a start point in scripts.
TriggerBox (Type 3)
A generic TriggerBox
is a volume that triggers scripted events or actions. It often has an associated state and can play an animation or motion when activated.
struct TriggerBox {
OBB base;
int32 State; // Initial state or type of trigger
WString ActionMotion; // Name of a motion or action to perform when triggered
WString Motion; // Possibly another motion/animation name (or secondary action)
if (evVer >= 9) {
int32 SignpostTextID; // ID of a signpost text
float SignpostPosX, SignpostPosY, SignpostPosZ; // Coordinates for repositioning a signpost display (a 3D vector)
}
};
For EventBoxVersion 9 and above, TriggerBox
gained fields for a “signpost” – likely an on-screen prompt or indicator. The file stores an integer SignpostTextID
(which could reference a string table for the prompt text) and a 3D reposition vector for the signpost (perhaps to offset the prompt’s location). These fields are absent in older versions (evVer < 9).
SkidBox (Type 4)
A SkidBox
(sometimes called SlideBox) applies a force or velocity to entities entering it, possibly causing the player or object to slide.
struct SkidBox {
OBB base;
int32 State; // State or type
float3 Velocity; // A 3D velocity vector applied to the entity
float EndTime; // Duration (seconds) after entering before effect ends
float Duration; // Possibly total duration of the sliding effect (or time the velocity is applied)
};
The Velocity
is read as three floats (X, Y, Z) and likely represents an impulse direction and magnitude. EndTime
and Duration
are two float values, one might be the time at which the skid ends and the other the time over which it occurs (they might be used by the engine to control the easing of the effect).
EventHitBox (Type 5)
An EventHitBox
triggers a scripted event when a character or object is hit or enters the volume, often dealing damage or causing a specific effect (like a trap).
struct EventHitBox {
OBB base;
uint32 State; // State identifier
WString AniBGName; // Name of an associated AniBG (animated object) or effect to trigger
float Damage; // Damage value to apply (if this box causes damage)
float3 Direction; // Direction vector of the force/knockback applied
WString DamageMotion; // Motion to play on the entity when hit (e.g., stagger animation)
uint32 HitCount; // Number of hit timing entries that follow
uint32 TempHitCount; // Number of "temporary" hit timing entries (for secondary hits)
float HitTimes[HitCount]; // Array of hit timing values (in seconds)
float TempHitTimes[TempHitCount]; // Array of secondary hit timing values
};
The EventHitBox is one of the more complex event boxes. After the initial fields, two counts are read (HitCount
and TempHitCount
), followed by that many float values for each list. These likely define at what times the event deals damage or triggers effects after activation (e.g., a trap might hit multiple times). The TempHitTimes
could be used for a secondary effect or a temporary hit registration separate from the main hits. In summary, this structure allows an EventHitBox to inflict damage (Damage
) in a certain direction (Direction
) and possibly multiple times according to the timing arrays.
NpcBox (Type 6)
An NpcBox
designates an NPC spawn in the map.
struct NpcBox {
OBB base;
WString NpcName; // Name of the NPC (usually corresponds to an NPC template or type
int32 ID; // NPC ID
int32 InstanceID; // Instance ID (unique identifier if multiple of same NPC)
};
These IDs allow the game or scripts to reference the NPC. For example, ID
tie to a specific NPC type, and InstanceID
could differentiate multiple instances of the same NPC type.
PortalBox (Type 7)
A PortalBox
is a volume that teleports the player to a different map or location (a portal between maps).
struct PortalBox {
OBB base;
WString WarpMapName; // Name of the target map to warp to
int32 ID; // Portal ID in the current map
int32 MsgType; // Message type or portal type (controls how the portal behaves/UI)
int32 WarpMapID; // ID of the target map
int32 WarpPortalID; // ID of the target portal within the target map (to arrive at)
bool Active; // Whether the portal is active/enabled
};
The PortalBox contains both name and ID references for the destination. WarpMapName
is the internam name of the map, while WarpMapID
is a corresponding map ID. WarpPortalID
is the ID of a specific entry point in the target map (so you can have multiple portals within a map). MsgType
indicates the type of portal (for instance, whether it requires a confirmation). The Active
flag can disable a portal until some condition is met.
SelectMapPortalBox (Type 8)
A SelectMapPortalBox
is a portal that presents a selection (e.g., the UI for choosing a dungeon) rather than immediately warping to a fixed map.
struct SelectMapPortalBox {
OBB base;
int32 ID; // Identifier for this portal (current map context)
int32 MsgType; // Type of portal/message (likely defines the selection UI or behavior)
bool Active; // Whether this portal is active
};
InAreaBox (Type 9)
An InAreaBox
triggers an event when an entity (usually the player) is inside a certain area. Often used for events that should happen when you enter or stand in a region.
struct InAreaBox {
OBB base;
WString WarpMapName; // Name of a map or area (often used to designate a location or trigger target)
int32 ID; // An identifier for this area trigger
bool Active; // Whether the trigger is enabled
};
EtcBox (Type 10)
EtcBox
stands for "Etcetera" box – a catch-all volume for miscellaneous triggers or markers. It has no special fields beyond the OBB:
struct EtcBox {
OBB base;
int32 ID; // Generic identifier (meaning defined by scenario/scripts)
};
This can be used by scripts for various purposes (e.g., to mark locations for script events) when no dedicated type exists. The ID allows distinguishing multiple EtcBoxes.
CameraBlockBox (Type 11)
A CameraBlockBox
defines a region that blocks the camera or the camera’s line-of-sight (LOS), often to prevent the camera from clipping through walls or seeing out of bounds. It has no extra fields:
struct CameraBlockBox {
OBB base;
// No additional data; the presence of this box itself signals a camera blockage volume
};
The engine uses the OBB of this box to constrain or collide the camera against it.
CutoffBox (Type 12)
A CutoffBox
marks an area for navigation or AI cutoff. It likely tells the game AI or navigation mesh to treat an area as off-limits or triggers a special behavior when an entity crosses it.
struct CutoffBox {
OBB base;
int32 CutoffType; // Type or category of cutoff (the game may define meanings for certain values)
};
CutoffType
could correspond to predefined behaviors (e.g., 0 = no cut, 1 = block navmesh, 2 = trigger event cutscene, etc.)
CameraTargetBox (Type 13)
A CameraTargetBox
is used to signal a point or area that the camera should focus on (for example, when a boss appears, the camera might target this box).
struct CameraTargetBox {
OBB base;
// (No fixed fields directly after base in older versions)
if (evVer < 8) {
WString NameDeprecated; // In older versions, a name string was stored (now deprecated)
} else {
int32 NameTextID; // In v8+, an integer ID is used instead of a name string
}
bool TargetLocalPC; // If true, target the local player character (present if evVer >= 2)
};
Version differences: Originally (EventBoxVersion < 8), the file stored a WString for the target’s name (possibly the name of the entity to focus on). In EventBoxVersion 8 and above, this was replaced by a numeric NameTextID
(likely referencing a string or an object ID).
The TargetLocalPC
flag appears if EventBoxVersion >= 2. If true, it might indicate the camera target should dynamically follow the local player character (PC), rather than a fixed point. Essentially, this box can serve as a focus target for the camera system, with options to use a preset name/ID or just always focus the player.
MiniMapIconBox (Type 15)
A MiniMapIconBox
determine the position of a icon on the mini-map.
struct MiniMapIconBox {
OBB base;
int32 IconType; // Identifier for which icon type to show on the minimap
};
The IconType
corresponds to a specific icon graphic or indicator defined by the game (for example, a treasure chest icon, a warning sign, etc.).
EnvironmentReverbBox (Type 16)
An EnvironmentReverbBox
defines an area with special audio reverb properties.
struct EnvironmentReverbBox {
OBB base;
int32 ReverbType; // Identifier for the reverb/echo setting to apply in this area
};
The ReverbType
would map to audio environment presets (e.g., 0 = default, 1 = cave reverb, 2 = sewer echo, etc.). When the listener (player) is inside this volume, the audio engine changes the ambient reverb to the specified type.
WaypointBox (Type 17)
A WaypointBox
represents a node in a network of waypoints, often used for patrol paths or navigation of moving entities (or possibly triggers). It can link to other waypoints.
struct WaypointBox {
OBB base;
int32 ID; // Waypoint ID number
float Range; // Radius or range of this waypoint’s effect (how close something must be to count as reaching it)
int32 LinkCount; // Number of linked waypoints
int32 Links[LinkCount]; // Array of IDs of other WaypointBoxes that are linked to this one
float LinkDistances[LinkCount]; // Array of distance values for each link (float distance to the corresponding linked waypoint)
};
The WaypointBox essentially contains an ID and a connectivity list. Each link has an associated distance (which could be the actual distance or a weight for that path). These could be used for AI navigation or telling a moving platform how to travel between waypoints. The Range
might specify how close an entity must get to this waypoint to consider it “reached” or could define an area of influence.
ObstacleBox (Type 18)
An ObstacleBox
defines the position of obstacles that the player can collide.
struct ObstacleBox {
OBB base;
// No additional fields
};
ObstacleBoxes likely work in conjunction with the Obstacle Path (discussed in the next section) to define regions that obstacles occupy.
Summary of EventBoxes: The EventBox section provides a flexible way to embed a variety of gameplay triggers and volume-based effects in the map data. Many fields are conditionally present depending on the EventBoxVersion (which tracks format changes over game updates).
AniBG Section (Animated Backgrounds)
After EventBoxes, the .wdata
contains the AniBG section, which lists Animated Background objects in the map. These are typically looping animations (e.g. fire).
This section begins with a 32-bit AniBG count, followed by that many AniBG entries. Each entry has the following structure:
struct AniBG {
OBB base; // Name (identifier of the object) and transform of the animated object
WString Model; // Path to the model file for this animated object
WString Motion; // Name of the the animation (motion) to play on the model
bool Loop; // Whether the animation loops
int32 LightIndex; // Light index to use
int32 CoverIndex; // Cover index (possibly for layer ordering or occlusion grouping)
bool Shadow; // Cast shadow flag (present if AniBGVersion >= 3)
bool MoveWeight; // "Move weight" flag (present if AniBGVersion >= 4) – exact usage unclear, perhaps for physics or weighted movement
float PVSRad; // PVS radius (present if AniBGVersion >= 5) – radius used for visibility culling optimization
};
Field meanings: Model
is a path to a .mdata
file for the object. Motion
is the name of the animation that the model will play. Loop
indicates if the motion should loop continuously. LightIndex
and CoverIndex
are likely indices into lighting and cover (occlusion mask) tables used by the engine (they might determine which dynamic lights affect the object, and how it is occluded by world geometry). Shadow
indicates if the object should cast a shadow. MoveWeight
might indicate if the object’s movement influences characters (or has physics weight) – in some contexts, this is used to signal moving platforms or weighted movement. PVSRad
(Potentially Visible Set Radius) is used for performance: it might define a radius for when the object should load or be considered in view (similar to LOD distance or for clustering PVS computations).
ItemBox Section
ItemBox lists interactive item containers (like chests or crates that drop items).
struct ItemBox {
OBB base;
WString Model; // Path to the item box model file
WString Motion; // Path to an animation file (e.g., open animation)
WString TablePath; // Path to the item drop table
bool Loop; // Whether the motion loops
bool OpenEnable; // "Open enabled" flag (present if ItemBoxVersion >= 3)
};
Notes: The TablePath
usually points to a table defining what items the box contains (loot table). The OpenEnable
flag (introduced in ItemBoxVersion 3) is likely used by the game to mark whether the box can be opened/interacted with. In older versions, this field is absent, so item boxes might always be interactable or require different logic. The model and motion are similar to AniBG, except these typically represent a breakable or openable object. The OBB defines the physical area of the box (for collision and trigger).
Gimmick Section
The Gimmick section describes miscellaneous interactive objects or mechanisms (traps, levers, etc.) referred to in Rusty Hearts as “gimmicks.”
struct Gimmick {
OBB base;
WString Model; // Model path for the gimmick object
WString Motion; // Motion/animation name for the gimmick
int32 LoopFlag; // Loop flag or mode (0/1 or a mode value)
int32 LightIndex; // Light index (lighting group)
int32 CoverIndex; // Cover index (occlusion group)
int32 Shadow; // Cast shadow flag (0 or 1)
int32 MoveWeight; // Move weight flag (0 or 1)
int32 TemplateID; // Template/Type ID of the gimmick
};
Each Gimmick defines an object in the world with a model and (optionally) an animation. The TemplateID
refers to a predefined gimmick type in the gimmicktemplatetable.rh
table. The various flags and indices (LoopFlag, Shadow, MoveWeight) have similar meanings as in AniBG/ItemBox: whether the motion loops, whether it casts a shadow, and whether its movement has physical weight or influence.
Obstacle Path (v2+)
If the Main Version >= 2, the .wdata
includes an Obstacle Path string after the Gimmick section. This is a wide string (WString
) read with the same method as other paths, and it represents the path to an obstacle data file.
if (MainVersion >= 2) {
WString ObstaclePath;
}
ObstaclePath: Path to the obstacle definition file (if any). If this string is
".\"
or empty, it implies no external obstacle path is used. If provided, it might point to a file containing waypoints or spline paths for obstacles (e.g., moving platforms or hazards) in the map.
In the game’s loading logic, if ObstaclePath
is present and not empty, it would load that file to get additional obstacle movement information. The ObstacleBox event boxes (type 18) likely relate to this path – for example, marking where obstacles start or collide.
Additional Paths (Trailing Paths)
After the obstacle path (or directly after Gimmicks, if version < 2), there are two trailing path fields:
WString MocPath;
WString AniBGPath;
These are read unconditionally (for all versions) at the end of the sections:
MocPath: Usually a path to a MOC file. In Rusty Hearts,
.moc
files likely refer to motion cutscene or camera motion files. This could specify a scripted camera or event file associated with the map (possibly the intro or outro cutscene of the level). Often this field may be empty (".\"
), meaning no associated motion/cutscene file.AniBGPath: A path to an external Animated BG definition file. This might not be heavily used in practice, since AniBGs are listed in the file itself. It could be for specifying an alternate source of AniBG data or for editor organizational purposes. Usually found empty as well.
Triggers Section
The Triggers section contains data for scripted triggers and events tied to the level. This part of the format underwent changes in later versions of Rusty Hearts. We will describe the older format first (for versions prior to 9) and then the newer format (for version ≥ 9), as they differ significantly.
Older Trigger Format (Main Version < 9)
In the original format, the .wdata
directly includes a list of triggers and their associated event/condition/action names.
int32 reserved0; // always present (usually 0)
WString scriptDir; // a directory or path for script files (often ".\" indicating none)
int32 reserved1; // another reserved field (usually 0)
WString mainScript; // main script file name for the level (could be an entry script)
int32 triggerCount; // number of trigger entries in this file
// List of triggers:
for (int i = 0; i < triggerCount; ++i) {
WString Name; // name of the trigger (identifier used in scripts)
WString Comment; // a comment or description for the trigger (for editor use)
int32 eventCount; // number of "Event" script calls for this trigger
int32 conditionCount; // number of "Condition" script calls for this trigger
int32 actionCount; // number of "Action" script calls for this trigger
WString Events[eventCount]; // list of event function names (as strings)
WString Conditions[conditionCount]; // list of condition function names (strings)
WString Actions[actionCount]; // list of action function names (strings)
}
In this format, each trigger has a name and an comment, and then lists of script function names that define what events it listens to, what conditions must be met, and what actions to execute when triggered. These names correspond to Lua script functions. The mainScript
at the beginning is the filename of a main script for the level’s triggers . The scriptDir
might indicate the root directory where trigger scripts are located.
Example: In a level, you might have triggerCount = 2
. The first trigger could be named "TriggerDoor" with a comment "Opens the door when all enemies defeated", eventCount = 1
(Event "OnEnemiesDead"), conditionCount = 0
, actionCount = 1
(Action "OpenDoor"). The second trigger might be "TriggerChest", etc. These strings tie into the game’s scripting system; when the conditions are met, it calls the specified actions.
New Trigger Format (Main Version ≥ 9)
Starting with Main Version 9 (and refined through versions 16 and 18), the trigger data shifted to an externalized script system. Instead of listing each trigger’s events and actions in the .wdata
, the file now provides global script references.
int32 reserved0;
WString scriptDir;
int32 reserved1;
WString mainScript;
int32 eventScriptCount;
int32 conditionScriptCount;
int32 actionScriptCount;
WString EventScriptPaths[eventScriptCount];
WString ConditionScriptPaths[conditionScriptCount];
WString ActionScriptPaths[actionScriptCount];
After the mainScript
string, instead of a triggerCount
, the file directly lists how many distinct event, condition, and action script files are used in this level, followed by the paths (names) of those script files. Each of those script files contains the actual logic for triggers. The mainScript
coordinate them (like an entry point).
In the game’s loading code, we can see that for version≥9, it reads nEventNum
, nConditionNum
, nActionNum
(corresponding to the three counts) and then reads each path and loads the script (using something like M3dSWorldManager::FindLua
to load the Lua script file). It then registers each event, condition, and action with a trigger manager (M3dSTrigger::AddEvent
, etc.) as it reads them. This implies that the actual triggers (linking events to conditions and actions) are defined in those scripts rather than explicitly in the data file.
As a result, when Main Version >= 9
, the triggerCount
and per-trigger lists are omitted. The file no longer contains individual trigger entries at all – those are handled by the scripts referenced. The reserved fields remain for compatibility, but triggerCount
is not present; instead we have the three script count integers.
Summary (Trigger handling):
For version < 9: Use the older format – read
triggerCount
and then read each trigger’s event/condition/action lists.For version ≥ 9: Skip reading a trigger count (it’s not there). Instead, read the three script counts and then that many script path strings
Scenes Section
The Scenes section defines cutscene cameras and effects used in the map (commonly for cutscenes or special camera transitions). It is divided into two parts: first, a list of scene camera definitions, and second, for each scene, additional data about what to show/hide during that scene.
The section begins with a scene count, followed by that many scene definitions:
int32 sceneCount;
Scene scenes[sceneCount];
struct Scene {
WString File; // Path to the camera file `.mec`
float FadeInPreview; // Fade-in time for preview (camera fade in at scene start)
float FadeHoldPreview; // Time to hold after fade-in (preview)
float FadeOutPreview; // Fade-out time for preview (end of scene)
uint32 Category; // Scene category or type flag
// The next 6 floats are stored in an array and then assigned:
float SceneFadeIn; // Fade-in time for the scene itself (camera effect)
float SceneFadeHold; // Hold time during scene (camera effect)
float SceneFadeOut; // Fade-out time for the scene (camera effect)
float BlendTime; // Blend time between this scene camera and the next (camera transition blend)
float FogNear; // Fog near distance during scene
float FogFar; // Fog far distance during scene
float3 Position; // Camera position for the scene
float3 Rotation; // Camera rotation
float FOV; // Camera field of view for the scene
float AspectRatio; // Camera aspect ratio for the scene
// (After reading, this Scene is stored and later augmented with more info below)
};
Essentially, each scene has a camera setup (position, rotation, FOV, aspect) and a series of timing parameters for fades and blends. The FadeInPreview/Hold/Out
are often used for a preview window in the editor (or possibly the initial fade when the scene is triggered in-game). The SceneFadeIn/Hold/Out
are presumably the actual in-game camera fades when the scene plays. FogNear and FogFar define the fog distances to use during the scene (overriding the level’s normal fog), and BlendTime is the cross-fade duration used if transitioning between scenes or back to gameplay. The Category
could be used to classify scenes (for example, 0 for normal, 1 for boss intro, etc., or maybe it’s used as an ID to trigger the scene).
After reading the scene list, this next data includes names, indices, and lists of objects to manipulate during the scene:
for (int i = 0; i < sceneCount; ++i) {
WString Name; // Name of the scene
int32 eventEntryCount; // Number of event entries (scene events)
int32 sceneIdCount; // Number of scene indices in the index list
int32 renderBgCount_User; // Count of user-specified render BG entries
int32 renderAniCount_User; // Count of user-specified render AniBG entries
int32 renderItemCount_User; // Count of user-specified render ItemBox entries
int32 renderGimmickCount_User; // Count of user-specified render Gimmick entries
int32 noRenderBgCount_User; // Count of user-specified *no-render* BG entries
int32 noRenderAniCount_User; // Count of no-render AniBG entries
int32 noRenderItemCount_User; // Count of no-render ItemBox entries
int32 noRenderGimmickCount_User; // Count of no-render Gimmick entries
uint32 SceneIndices[sceneIdCount]; // List of indices (IDs) related to this scene
SceneElement EventEntries[eventEntryCount]; // Event scene entries triggered in this scene
// Each SceneElement is a pair: { uint32 Category; WString Key }.
// It could represent something like an event trigger or an object to manipulate.
// EventEntries are read from the file as Category + Key pairs
// Render/NoRender lists (user-specified):
SceneElement RenderBgUser[renderBgCount_User];
SceneElement RenderAniBgUser[renderAniCount_User];
SceneElement RenderItemBoxUser[renderItemCount_User];
SceneElement RenderGimmickUser[renderGimmickCount_User];
SceneElement NoRenderBgUser[noRenderBgCount_User];
SceneElement NoRenderAniBgUser[noRenderAniCount_User];
SceneElement NoRenderItemBoxUser[noRenderItemCount_User];
SceneElement NoRenderGimmickUser[noRenderGimmickCount_User];
};
Interpretation:
Name: A name for the scene
SceneIndices: An array of indices (IDs). These might refer to specific cameras or scene resources (for instance, linking to entries in the SceneResource section by ID). The game might use these IDs to quickly enable/disable objects relevant to the scene.
EventEntries (EventScenes in code): A list of scene events that should occur. Each has a Category and a Key. Category likely identifies the type of event (e.g., 0 = play animation, 1 = trigger effect, etc.) and Key is a string key or identifier (possibly the name of an animation, an event marker, or an entity name). These could instruct the engine to do certain things when the scene starts (e.g., play a certain cutscene or trigger an NPC action).
RenderXUser / NoRenderXUser: These eight lists allow overriding object visibility during the scene. For example,
RenderBgUser
contains background objects that should be forced visible,NoRenderBgUser
those that should be hidden, and so forth for AniBGs, ItemBoxes, and Gimmicks. Each entry has a Category and Key: likely the Category here corresponds to an object type or group, and the Key is the object’s name or identifier. Essentially, these lists fine-tune the environment for the scene (show or hide certain things so the scene looks correct).
It’s worth noting that the SceneIndices and EventEntries might correlate with the SceneResources section (discussed next). For example, an EventEntry might trigger a specific scene resource by key.
Scene Resources Section
The Scene Resources section defines reusable scene elements such as model animations, camera motions, and sound or effect cues that the scenes (above) can reference. This is essentially a database of cutscene content for the level, which scenes can pull from by key.
The section starts with a 32-bit entry count, followed by that many SceneResource entries. Each SceneResource has the following structure:
int32 sceneResourceCount;
SceneResource sceneResources[sceneResourceCount];
struct SceneResource {
WString Key; // Key name of this scene resource (identifier used by scenes)
int32 aliasCount;
WString Aliases[aliasCount]; // Alternate string keys that map to this resource (aliases)
int32 pathCount;
float Delay; // Delay before starting (only present if pathCount > 0, otherwise 0)
SceneData Paths[pathCount];
int32 cueCount;
Cue Cues[cueCount];
int32 soundCount;
SoundRecord Sounds[soundCount];
uint32 Unk1;
uint32 Unk2;
float Unk3;
uint32 Unk4;
int32 ambientCount;
AmbientRecord Ambients[ambientCount];
};
struct SceneData {
WString Model; // Model to use in this scene path element
WString Motion; // Motion/animation for the model
WString Name; // Name for this scene element (could be an object or actor name)
WString EventName; // Event name or marker for this element
uint32 Time; // Start time of this element in the scene (milliseconds or frames)
uint32 Hold; // Duration to hold this element (or duration of the motion)
float BlendTime; // Blend time into the next element (only present for all but the last element)
};
struct Cue {
float Delay; // Time delay before the cue (seconds)
WString Name; // Name of the cue (could be an event or effect identifier)
uint32 ID; // ID of the cue (likely ties to a specific effect or action)
float Start; // Start time of the cue (seconds, or an absolute timeline position)
};
struct SoundRecord {
float Start; // Start time of the sound (seconds)
float FadeIn; // Fade-in duration for the sound (seconds)
float FadeOut; // Fade-out duration (seconds)
float VolMax; // Maximum volume level
float VolMin; // Minimum volume level
WString Path; // Path to the sound file (sound effect or music)
};
struct AmbientRecord {
float Start; // Start time for the ambient sound (seconds)
WString Path; // Path to ambient sound
bool PlayOnStart; // If true, play immediately on scene start
bool Loop; // If true, loop the ambient sound
};
Now, let’s break down how this is stored and used, with references to how it’s read:
Key: A unique string key for the SceneResource. Scenes refer to resources by this key (for instance, a scene’s EventEntry might use a key to activate a particular SceneResource).
Aliases: A list of alternative names (strings) that also refer to this SceneResource. This allows multiple identifiers to map to the same resource (perhaps for convenience or versioning).
Paths: This is the core of the SceneResource – it defines an animated sequence or track.
pathCount
indicates how many segments or keyframes the sequence has. IfpathCount
is greater than 0, the next value in the file is a floatDelay
. ThisDelay
is a delay (in seconds) before starting this resource’s sequence (a way to offset the start of this resource relative to scene start). After the delay, it readspathCount
entries ofSceneData
:Each
SceneData
defines a model and an animation (Motion) to play, possibly representing a character or object performing an action in the scene. It also has aName
andEventName
which could tag this animation segment (for example, Name might be the character’s name, EventName could be a specific animation event label).Time
andHold
are unsigned ints, likely representing the start time and hold (duration) for this segment in the scene timeline (these could be in milliseconds given they are uint32).Importantly, BlendTime: for each
SceneData
except the last, a float is stored representing the blend duration into the next segment. This allows smooth interpolation between animations. The last segment doesn’t have a following blend (so no extra float after it). The reading code reflects this: it reads BlendTime only if the current indexp
is less thanpathCount - 1
.Thus, in the binary layout, each SceneData entry consists of 4 WStrings and 2 uint32s, and if it’s not the last entry, it is followed by a float (BlendTime). The next entry’s data comes after that float, or if it was the last entry, the sequence moves on.
Cues: After the Paths, an
int32 cueCount
is read, followed by that many Cue entries. A Cue might represent a timed trigger for a special effect or camera cut within the scene (e.g., an explosion or a camera shake). Each Cue has a delay (when to trigger), a name (identifier of what to trigger), an ID (possibly linking to a specific effect or function), and a start time (which might duplicate the concept of delay or be used differently – could be the absolute time or a parameter). The distinction between Delay and Start in the cue is subtle; possibly Delay is an offset from scene start, and Start could be duration or something needed for the cue's effect.Sounds: Next, an
int32 soundCount
and that many SoundRecord entries are read. These define sounds to play during the scene, such as voice lines or sound effects. Each SoundRecord includes when to start the sound, how to fade it in/out, volume range, etc., and the path to the sound file. The volume max/min might be used for random variance or distance effects. Typically, these would be one-shot sounds or background music for the scene.Unk1, Unk2, Unk3, Unk4: After the sounds, four additional fields are read: two uint32s, one float, and another uint32. These are currently unknown or reserved. We denote them as Unk1, Unk2, Unk3, Unk4.
Ambients: Finally, an
int32 ambientCount
is read, followed by that many AmbientRecord entries. AmbientRecords likely define ambient sounds or environmental loops. Each has a start time, a path (to an ambient loop or sound environment preset), and two booleans: PlayOnStart and Loop. PlayOnStart indicates the ambient sound should begin playing as soon as the scene starts (the Start time might then act as a volume fade or something), and Loop indicates whether the ambient sound should loop continuously once started. These could be used for background ambiance during the scene (like wind, crowd noise, etc.).
Collectively, a SceneResource encapsulates all the data needed for a particular sequence or element of a cutscene (an animated character, a moving prop, a sound sequence, etc.). The Key
is how the Scene knows which resource to invoke. For example, if a Scene has an EventEntry with Category X and Key "DragonBossIntro", that might correspond to a SceneResource with Key "DragonBossIntro" which contains the model, animation, sounds, etc., for the boss intro cutscene. The Scenes section’s indices and event entries likely orchestrate which SceneResources play when the scene is triggered.
Summary: The Scenes and SceneResources sections work together to define in-game cinematics or camera events. Scenes provide the camera and timing context, plus references (EventEntries/IndexList) that pick which resources (from SceneResources) to activate. SceneResources provide the actual content (models, animations, sounds) that will play out. This separation allows reusing the same resource in multiple scenes or triggers.
Conclusion
This covers the .wdata
file structure for Rusty Hearts, reflecting the analyzed decompiled C++ server reader logic. Each section of the file is delineated clearly in the binary format, and conditional fields are governed by version numbers stored in the header.
When implementing a parser:
Always read the header first and use the version fields to decide which parts of the format to expect (especially for EventBoxes and Triggers).
Use the provided counts and offsets to navigate sections like EventBoxes and Scenes.
Pay attention to conditional blocks (e.g., if a version is lower, do not attempt to read fields that were added in higher versions).
Strings are all wide (UTF-16) and often use
".\"
to denote empty path values.The file is mostly sequential; however, the EventBox data is out-of-line and must be sought via offsets.
By following the structures outlined above, one can reliably parse .wdata
files, extract map references, triggers, and cutscene information, and use them for analysis or modding of Rusty Hearts map data files.
Last updated