Skip to content

Characters and bullets at runtime

FromSoftware engineRuntime and limitsApplies toElden Ring

The game keeps its characters in a world character manager, a static singleton reached from the executable’s data. Each character instance holds modules (data, physics and others) by pointer, and a projectile is a Bullet param row the bullet manager launches by its id. The offsets below are those of the 1.17.2 executable (file version 2.7.2.0); static addresses move between versions, so they are found by byte pattern. Observed

The static pointer at offset 0x3d6d018 of the image holds a CS::WorldChrManImp (named by its RTTI in a memory dump). The player’s character instance is at +0x1E508 of it, and equally the first entry of the array at +0x10EF8. Observed

Value Path from the player instance P Confidence
Position in the physics frame (x, y, z floats; y is up) [[P+0x190]+0x68]+0x70 Writing y raised the player by the amount added. Observed
No gravity (byte; 1 holds the character where it is put) [[P+0x190]+0x68]+0x1D3 Set to 1, the player stayed 12 m above the ground for over 30 s. Observed
Cannot die (bit 0), takes no damage (bit 1) [[P+0x190]+0]+0x19B Sourced · The Grand Archives table
Map-relative position (x, y, z), facing, map id P+0x6C0 to +0x6D0 The map id is 0xAABBCC00 for mAA_BB_CC_00, and reads 0xFFFFFFFF while the world is still loading, before it reads the map. Observed

P+0x190 is the module table: its entry 0 is the character’s data module and its +0x68 the physics module. Sourced · The Grand Archives table The physics position and the map-relative position differ by the map’s origin: the same moment read y 5.81 in the physics frame and 45.81 on the map, and both changed by the same amount when the physics y was written. Observed The physics position is not a steady record of where the character is: after the player was raised 80 m and held there, the physics y read back the earlier height while the screen showed the player still high above the castle, so the map-relative position is the one to read. Observed · one run The physics frame likely takes a new origin there. Inferred

The function at image offset 0x3a2cb0 launches a bullet: the game calls it with the bullet manager (the static at 0x3d697c8) as its first argument, a handle to fill as its second, a request as its third and a further block as its fourth. The request holds the Bullet row id at +0x14 and the launch position, in the physics frame, at +0x80. Observed · the game’s own calls Called with a zeroed 0x200-byte request whose id and position are set, the second and fourth arguments at +0x180 and +0x1C0 of it, it returns a handle and the bullet flies and draws its effect, without a cast animation or an owner: Bullet rows 10400000 and 10400100, the projectiles of the Magic rows 4000 (Glintstone Pebble) and 4001 (Great Glintstone Shard), both drew in a frame captured after the call. Observed · Elden Ring · two rows, from a thread of its own A spell’s Magic row names its bullets (Magic and Bullet), so any spell’s projectile can be drawn this way. Inferred

The game makes a request with a constructor (image offset 0x38c580 in 1.17.2) before it sets the id and the shot, and that constructor’s defaults are not zero: Observed · the game’s code

Offset Default What the game’s own launch puts there
+0x00, +0x08 -1 (none), 8 bytes each +0x00: the launching character’s handle
+0x10 0
+0x14 -1 the Bullet row id
+0x1C, +0x20 -1 (4 and 8 bytes)
+0x28, +0x2C 12
+0x30 four floats, each 1.0
+0x40 0
+0x44 flags: bits 3 to 5 cleared, then bit 3 set bit 0 set, bit 5 from the caller
+0x50 a 4x4 matrix, identity the shot’s matrix: its rows the orientation, its last row (+0x80) the position, w 1
+0x90 to +0xA7 0
+0xA8 0xFF (byte)
+0xB0 a block of its own constructor: a handle -1 at +0xB8, a counted reference at +0xC0, vectors from the executable’s data copied from the caller’s

A request zeroed instead of constructed has a zero matrix (no orientation, and w 0 in the position’s row), an owner handle of 0 where the constructor’s means none, and 0 in the four floats at +0x30. That is what the launches above used. Inferred

The two requests fly differently. Bullet 10400100 is the Great Glintstone Shard’s projectile: life 1.25 s, initVellocity 13, accelOutRange 40 after accelTime 0.32, maxVellocity 25, minVellocity 6. Its speeds come from its effect’s tracers, which keep a point every 1/120 s.

  • From a zeroed request: it held still about 0.4 s, then started at about 6 m/s (its minVellocity) and peaked near 18 to 19 m/s. Observed

  • From a request the constructor made, with only the id and position set: it reached exactly 25 m/s (its maxVellocity) and held it for the last 28 samples, approaching at about 40 to 44 m/s². Observed

  • The zeroed request’s motion cannot fit a constructed one. A 0.4 s hold, then 6 m/s, would reach 25 m/s only at 1.195 s, too late for the samples seen inside the 1.25 s life. So the zeroed fields, among them the four 1.0 floats at +0x30, change the flight. Inferred

  • The constructed request holds initVellocity for accelTime, then accelerates to maxVellocity. In the frame whose tracers reached 25 m/s, the caster’s root (the first object matrix of its skinned draws) stands on the bullet’s line of flight at z 0.209, 1.5 m below it. Observed · one captured frame Fitting the tracers’ points with 13 m/s for 0.32 s, then 40 m/s² up to 25 m/s, puts the launch at z 0.21, the caster’s own z. A start at 6 m/s with no hold puts it at z 0.79. Inferred The bullet’s widest tracer reaches back to its first speeds: steps of 12.7, 14.3, 15.4 and 16.6 m/s, each 1.33 m/s faster than the one before, which is 40 m/s² over 1/30 s. Observed · the same frame

What stops the shard’s two spinning tracers before the bullet ends?

By that law the frame is 1.12 s after the launch. The effect’s two thin tracers that spin around the line of flight end 6.5 m behind the bullet (0.26 s of its flight), while its widest tracer reaches the place its node stands, 0.75 m behind the bullet (the node is translated 0.75 m back), and its model particles and spinning sprites reach the bullet. In a frame of a second launch, 1.23 s after it, neither spinning tracer is drawn. The effect file declares no life for those tracers’ particles, and its one state ends the effect only when external value 0 reaches 1.

The page of the executable’s .data section holding these statics is mapped PAGE_EXECUTE_READWRITE while the game runs, though the section header does not mark it executable. Observed A tool that refuses to write executable pages refuses the game’s own data; telling data from code needs the section headers. Inferred