Characters and bullets at runtime
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 player character
Section titled “The player character”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
Launching a bullet
Section titled “Launching a bullet”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
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 executable’s data
Section titled “The executable’s data”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