Level Entity (LENT) Section - Troodon80/Summoner-Save-File-Editor GitHub Wiki
Contains data for player characters and other creatures:
Field | Offset | Size | Description |
---|---|---|---|
Signature | 0x00027667 | 4 bytes | "LENT" ASCII string |
FirstEntityOffset | +0x04 | 4 bytes | Relative offset to first entity? |
EndRegionOffset | +0x08 | 4 bytes | Relative offset to end of entity region? |
Status Bitfield | +0x0C | 4 bytes | Bitfield for status (bit 26 indicates death/invalid/no longer loaded) |
Summon Name | +0x10 | 4 bytes | Possibly a global save ID/hash? |
EntityCount | +0x30 | 4 bytes | Number of characters and creatures |
Character/Creature Data | +0x34 | Variable | Individual character and creature data starts here |
Each entity contains:
Both character and creature entities begin with the same header structure:
Field | Offset | Size | Description |
---|---|---|---|
Entity Hash | +0x00 | 4 bytes | String-hash identifier |
Entity Type | +0x04 | 4 bytes | Type identifier (7 = Character, other values = Creature/NPC) |
Entity block data follows | +0x08 | 4 bytes | See tables for type 7 and non-type 7 immediately following |
Characters (Joseph, Flece, Rosalind, Jekhar, and current Summon) have the following structure:
Field | Offset | Size | Description |
---|---|---|---|
Position X | 0x00 | 4 bytes | X coordinate (float) |
Position Z | 0x04 | 4 bytes | Z coordinate (float) |
Position Y | 0x08 | 4 bytes | Y coordinate (float) |
Rotation | 0x0C | 4 bytes | Rotation angle (float) |
Unknown Int 1 | 0x10 | 4 bytes | First unknown integer |
Unknown Int 2 | 0x14 | 4 bytes | Second unknown integer |
State Flags | 0x18 | 4 bytes | State flags (bit 26 indicates death/invalid/no longer loaded) |
Status Effects | 0x1C | 76 bytes | 4 sets of data, relating to status effects. Each containing: |
- Flag? (1 byte) Possibly self-cast or buff or detrimental debuff? | |||
- Type (1 byte) Type or Category? | |||
- SubType (1 byte) | |||
- Value 1 (4 bytes) Timing? | |||
- Value 2 (4 bytes) Timing? | |||
- Value 3 (4 bytes) Timing? | |||
- Value 4 (4 bytes) Timing? | |||
Equipped Items | 0x68 | 36 bytes | 9 equipment slots (4 bytes each): |
- Amulet | |||
- Ring 1 | |||
- Ring 2 | |||
- Gauntlets | |||
- Boots | |||
- Weapon | |||
- Shield | |||
- Torso | |||
- Leggings | |||
Unmapped Block | 0x8C | 144 bytes | 36 integers of unmapped data |
Hand to Hand | 0x11C | 1 byte | Hand to Hand skill level (used by the player's Summon, e.g. Blackfire Elemental) |
Unknown Skill 1 | 0x11D | 1 byte | Unknown character skill |
Character Speed | 0x11E | 1 byte | Movement/action speed |
Sword | 0x11F | 1 byte | Sword skill level |
Axe | 0x120 | 1 byte | Axe skill level |
Blunt | 0x121 | 1 byte | Blunt weapon skill level |
Staff | 0x122 | 1 byte | Staff skill level |
Bow | 0x123 | 1 byte | Bow skill level |
Heavy Arms | 0x124 | 1 byte | Heavy weapons skill level |
Parry | 0x125 | 1 byte | Parry skill level |
Counter Attack | 0x126 | 1 byte | Counter Attack skill level |
Dodge | 0x127 | 1 byte | Dodge skill level |
Critical Hit | 0x128 | 1 byte | Critical Hit skill level |
Double Attack | 0x129 | 1 byte | Double Attack skill level |
Push | 0x12A | 1 byte | Push skill level |
Kick | 0x12B | 1 byte | Kick skill level |
Trip | 0x12C | 1 byte | Trip skill level |
Aimed Attack | 0x12D | 1 byte | Aimed Attack skill level |
Backstab | 0x12E | 1 byte | Backstab skill level |
Assess | 0x12F | 1 byte | Assess skill level |
Appraise | 0x130 | 1 byte | Appraise skill level |
Picklock | 0x131 | 1 byte | Picklock skill level |
Sneak | 0x132 | 1 byte | Sneak skill level |
Hide | 0x133 | 1 byte | Hide skill level |
Unknown Skills | 0x134 | 3 bytes | 3 unknown skill bytes |
Summon | 0x137 | 1 byte | Summon skill level |
Heal | 0x138 | 1 byte | Heal magic skill level |
Dark | 0x139 | 1 byte | Dark magic skill level |
Energy | 0x13A | 1 byte | Energy magic skill level |
Holy | 0x13B | 1 byte | Holy magic skill level |
Fire | 0x13C | 1 byte | Fire magic skill level |
Ice | 0x13D | 1 byte | Ice magic skill level |
Unknown Skill 3 | 0x13E | 1 byte | Unknown skill |
Magic Resist | 0x13F | 1 byte | Magic resistance skill level |
Character Level | 0x140 | 2 bytes | Character level (short) |
Experience | 0x142 | 4 bytes | Character experience points |
Current HP | 0x146 | 4 bytes | Current hit points (float) |
Base HP | 0x14A | 4 bytes | Maximum hit points (float) |
Current AP | 0x14E | 2 bytes | Current ability points (short) |
Base AP | 0x150 | 2 bytes | Maximum ability points (short) |
Unknown Value | 0x152 | 4 bytes | Unknown purpose |
AI Value | 0x156 | 4 bytes | AI behaviour control value (Melee, Support, Range, Healer, caster, Healer/Caster) |
Unmapped Character Data | 0x15A | 34 bytes | Additional unmapped character data |
Skill Points | 0x17C | 4 bytes | Available skill points |
Note: Skills are 0-10 in-game, but stored as a multiple of 10. 5 becomes 50, 10 becomes 100, 25 becomes 250 (which would be the maximum here since it's only a byte). When editing, it can go to 255, which gets rounded to 26 in-game.
Non-player creatures (enemies, monsters, NPCs) have the following structure:
Field | Offset | Size | Description |
---|---|---|---|
Position X | 0x00 | 4 bytes | X coordinate (float) |
Position Z | 0x04 | 4 bytes | Z coordinate (float) |
Position Y | 0x08 | 4 bytes | Y coordinate (float) |
Rotation | 0x0C | 4 bytes | Rotation angle (float) |
Unknown Int 1 | 0x10 | 4 bytes | Unknown integer |
Unknown Int 2 | 0x14 | 4 bytes | Unknown integer |
State Flags | 0x18 | 4 bytes | State flags (bit 26 indicates death/invalid/no longer loaded) |
Status Effects | 0x1C | 76 bytes | 4 sets of data, relating to status effects. Each containing: |
- Flag? (1 byte) Possibly self-cast or buff or detrimental debuff? | |||
- Type (1 byte) Type or Category? | |||
- SubType (1 byte) | |||
- Value 1 (4 bytes) Timing? | |||
- Value 2 (4 bytes) Timing? | |||
- Value 3 (4 bytes) Timing? | |||
- Value 4 (4 bytes) Timing? | |||
Creature Level | 0x68 | 2 bytes | Creature level (short) |
Current HP | 0x6A | 4 bytes | Current hit points (float) |
Base HP | 0x6E | 4 bytes | Maximum hit points (float) |
Current AP | 0x72 | 2 bytes | Current ability points (short) |
Base AP | 0x74 | 2 bytes | Maximum ability points (short) |
Unknown Value | 0x76 | 4 bytes | Integer, unknown purpose |
AI Value | 0x7A | 4 bytes | AI behaviour control value |
Unmapped | 0x7E | 248 bytes | Unmapped |
Unknown/Padding | 0x176 | 2 bytes | Unknown end value (short) |
Note: As previously mentioned, there is a section immediately following the main series of entity blocks that follows a pattern of count+hash and appears to be entity linking. I'm not doing anything with this block, but a proof of concept parser is as follows:
Click to expand...
public class LentRelatedBlock
{
public static long CurrentPosition = 0;
public class Entry
{
public uint EntityHash { get; set; }
public List<uint> RelatedHashes { get; set; } = new();
public int TrailingInt { get; set; }
}
public static List<Entry> ReadFromStream(BinaryReader reader, int offset, int baseOffset)
{
var entries = new List<Entry>();
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
int outerCount = reader.ReadInt32();
for (int i = 0; i < outerCount; i++)
{
uint entityHash = reader.ReadUInt32();
if (entityHash == 0)
continue;
int innerCount = reader.ReadInt32();
var related = new List<uint>();
for (int j = 0; j < innerCount; j++)
{
uint relatedHash = reader.ReadUInt32();
if (relatedHash != 0)
related.Add(relatedHash);
}
int unknownTrailingInt = reader.ReadInt32();
entries.Add(new Entry
{
EntityHash = entityHash,
RelatedHashes = related,
UnknownInt = unknownTrailingInt
});
}
CurrentPosition = reader.BaseStream.Position;
return entries;
}
public static List<Entry> ReadFromFile(string path, int offset, int baseOffset)
{
using var stream = File.OpenRead(path);
using var reader = new BinaryReader(stream);
return ReadFromStream(reader, offset, baseOffset);
}
}
Not that this does not parse to the beginning of the CMRA block. There is still more data to be read. I just don't know how yet or what it does; it's covered by le_size, so I'd guess it's more entity-related data or maybe it's just junk data not zeroed out. If it is additional used data and not junk data, then it doesn't appear to be read sequentially but rather as a block and then parsed later.