Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Documentation for the VFX system used by FFXIV
Internal ping calculation
Documentation for files located within the games internal filesystem.
Contains information about files that are specific to each character.
BinderIndex = -1BinderIndex = 0FFXIV_CHRxxxxxxxxvoid SendPingRequest() {
int64_t t;
PingRequest req;
QueryPerformanceCounter(&t);
req.timestamp = (int32_t)(t / 10000);
SendMessage(req);
}
int32_t HandlePingResponse(PingResponse resp) {
int64_t currT;
int32_t prevMs = resp.timestamp;
QueryPerformanceCounter(&currT);
int32_t currMs = (int32_t)(currT / 10000);
return currMs - prevMs;
}Various open source resources and projects
A channel is a specific connection to a game server: lobby, chat and world.
void OnRsvPktreceived(RsvPkt pkt) {
// e.g.) "_rsv_29752_-1_1_C0_0Action"
var key = ReadCString(pkt.key_str);
// e.g.) "Alternative End"
var val = ReadCString(pkt.value_str);
rsvMap[key] = val;
}
string GetActionName(int row) {
var val = table[row, col];
if (rsvMap.Contains(val)) {
var key = val;
return rsvMap[key];
} else {
return val;
}
}//**sqex0003VGhpcyBpcyBqdXN0IGZvciBleGFtcGxlIHB1cnBvc2U7IGl0IGFjdHVhbGx5IG5ldmVyIGxvb2sgbGlrZSB0aGlzA**//char[] ChecksumTable = {
'f', 'X', '1', 'p', 'G', 't', 'd', 'S',
'5', 'C', 'A', 'P', '4', '_', 'V', 'L'
};
char GetChecksum(uint key) {
// mask the nibble we're looking for
var value = key & 0x000F_0000;
return ChecksumTable[value >> 16];
}int32_t CalculateTarget(int32_t unixSeconds) {
// Get Eorzea hour for weather start
int32_t bell = unixSeconds / 175;
// For these calculations, 16:00 is 0, 00:00 is 8 and 08:00 is 16.
// This rotates the time accordingly and holds onto the leftover time
// to add back in later on.
uint32_t increment = ((uint32_t)(bell + 8 - (bell % 8))) % 24;
// Take Eorzea days since the unix epoch
uint32_t totalDays = (uint32_t)(unixSeconds / 4200);
uint32_t calcBase = (totalDays * 0x64) + increment;
uint32_t step1 = (calcBase << 0xB) ^ calcBase;
uint32_t step2 = (step1 >> 8) ^ step1;
return (int32_t)(step2 % 0x64);
}Race = u8 enum;
Gender = u8 enum;
Age = u8 enum;
Subrace = u8 enum;
FFXIVid = u8;
FFXIVColor = u8;
FaceFeature = u8 bitfield;
EyeShape = u8;
LightDarkColor = u8;
FacePaint = u8; Race race;
Gender gender;
Age age;
u8 height_pct;
Subrace subrace;
FFXIVid face_id;
FFXIVid hair;
match (race){
(Race::Hrothgar): bool enable_fur_pattern;
(_): bool enable_hair_highlights;
}
FFXIVColor skin_tone;
FFXIVColor right_eye_color;
FFXIVColor hair_color;
match (race){
(Race::Hrothgar): FFXIVColor pattern_fur_color;
(_): FFXIVColor hair_highlights_color;
}
FaceFeatures facial_features;
match (race){
(Race::AuRa): FFXIVColor limbal_ring_color;
(Subrace::Wildwood): FFXIVColor ear_clasp_color;
(Subrace::Keepers_of_the_Moon & Gender::female): FFXIVColor ear_clasp_color;
(_): FFXIVColor tattoo_color;
}
FFXIVid eyebrows;
FFXIVColor left_eye_color;
EyeShape eyes_shape;
FFXIVid nose;
FFXIVid jaw;
FFXIVid mouth;
match (race){
(Race::Hrothgar): FFXIVid fur_pattern_id;
(_): LightDarkColor lips_color;
}
match (race){
(Race::Hyur | Race::Roegadyn): u8 muscle_tone_pct;
(Race::Miquote | Race::AuRa | Race::Hrothgar): u8 tail_length_pct;
(Race::Lalafell | Race::Elzen | Race::Viera): u8 ear_length_pct;
(_): u8 _tbd;
}
FFXIVid tail_shape;
u8 bust_pct;
FacePaint face_paint;
LightDarkColor face_paint_color;
FFXIVid voice;
u8 _space2;

H2017.06.06.0000.0001a.patch
H2017.06.06.0000.0001b.patch
D2017.07.11.0000.0001.patch
D2019.04.16.0000.0000.patchstruct ChunkEntry
{
ChunkHeader Header;
Chunk Payload; // TODO: explain this
UInt32BE Crc32;
}[StructLayout(LayoutKind.Explicit, Size = 8)]
struct ChunkHeader
{
[FieldOffset(0)] UInt32BE Size;
[FieldOffset(4)] char Name[4];
}[StructLayout(LayoutKind.Explicit, Size = 256)]
struct FhdrChunk
{
[FieldOffset(0x2)] byte Version; // ZIPATCH version (3)
[FieldOffset(0x4)] char Name[4];
// TODO...
[FieldOffset(0x20)] UInt32BE DepotHash; // also observable in url
}[StructLayout(LayoutKind.Explicit, Size = 12)]
struct AplyChunk
{
// 1 -> Ignore Missing; 2 -> Ignore Mismatch
[FieldOffset(0x0)] UInt32BE Option;
// Believed to be length (in bytes) of Value, but it doesn't currently change
[FieldOffset(0x4)] UInt32BE Reserved;
// Non-zero Value sets Option to true for this .patch file
[FieldOffset(0x8)] UInt32BE Value;
}[StructLayout(LayoutKind.Explicit)]
struct AdirChunk
{
[FieldOffset(0x0)] UInt32BE PathLength;
[FieldOffset(0x4)] char Path[PathLength]; // TODO: This isn't valid C#
}[StructLayout(LayoutKind.Explicit)]
struct DeldChunk
{
[FieldOffset(0x0)] UInt32BE PathLength;
[FieldOffset(0x4)] char Path[PathLength]; // TODO: This isn't valid C#
}[StructLayout(LayoutKind.Explicit, Size = 32)]
struct EofChunk
{
// zero padded
}[StructLayout(LayoutKind.Explicit, Size = 5)]
struct SqpkChunk
{
[FieldOffset(0x0)] Uint32BE Size;
[FieldOffset(0x4)] SqpkOperation Opcode;
[FieldOffset(0x5)] SqpkPayload Payload;
}
enum SqpkOperation : byte
{
A, // Add data. Used to overwrite blocks in dat files. Can also delete old data
D, // Delete data. Used to delete blocks (overwrite with 0x00) in dat files
E, // Expand data. Used to insert empty blocks into dat files
F, // File operations. Adding files, deleting files, etc.
H, // Header Update. Updates headers of dat or index files
I, // Index Add/Delete. Present in patch files but unused
X, // Patch Info. Present in patch files but unused
T, // Target Info. Present in patch files but only one field is used
}[StructLayout(LayoutKind.Explicit, Size = 8)]
struct SqpkFile
{
[FieldOffset(0x0)] UInt16BE MainId;
[FieldOffset(0x2)] UInt16BE SubId;
[FieldOffset(0x4)] UInt32BE FileId;
// Expansion => (byte)(SubId >> 8);
// DatFileName => $"{MainId:x2}{SubId:x4}.win32.dat{FileId}"
// IndexFileName => $"{MainId:x2}{SubId:x4}.win32.index{(FileId == 0 ? string.Empty : FileId.ToString())}"
}[StructLayout(LayoutKind.Explicit, Size = 128)]
struct BlockHeader
{
[FieldOffset(0x0)] UInt32LE Size;
[FieldOffset(0x4)] UInt32LE Type;
[FieldOffset(0x8)] UInt32LE FileSize;
[FieldOffset(0xC)] UInt32LE TotalBlocks;
[FieldOffset(0x10)] UInt32LE UsedBlocks;
}struct BlockCount {
[FieldOffset(0x00)] UInt32BE Value;
UInt32 InBytes => Value * 128;
}struct SqpkFilePath {
[FieldOffset(0x00)] UInt16BE Main;
[FieldOffset(0x02)] UInt32BE Sub;
[FieldOffset(0x04)] UInt32BE File;
}struct SqpkFileRange {
[FieldOffset(0x00)] BlockCount Offset;
[FieldOffset(0x04)] BlockCount Count;
}[StructLayout(LayoutKind.Explicit)]
struct SqpkAddData
{
[FieldOffset(0x0)] byte reserved[3];
[FieldOffset(0x3)] SqpkFile File; // Dat file
[FieldOffset(0xB)] UInt32BE BlockOffset;
[FieldOffset(0xF)] UInt32BE BlockCount;
[FieldOffset(0x13)] UInt32BE BlockDeleteCount;
[FieldOffset(0x17)] byte Data[BlockCount << 7]; // TODO: Not valid C#
}[StructLayout(LayoutKind.Explicit, Size = 19)]
struct SqpkDeleteData
{
[FieldOffset(0x0)] byte reserved[3];
[FieldOffset(0x3)] SqpkFile File; // Dat file
[FieldOffset(0xB)] UInt32BE BlockOffset;
[FieldOffset(0xF)] UInt32BE BlockCount;
}Game packet structures
This page contains the file structure itself. What each of the structures listed here are used for can be on other pages.
Uint64 AlignBlockSize(UInt64 size) {
return (size + 0x8F) & 0xFFFFFF80;
}
[StructLayout(Size = 0x1B)]
struct FileHeader
{
[FieldOffset(0x00)] byte Operation;
// padding: [u8; 2]
[FieldOffset(0x03)] UInt64BE Offset;
[FieldOffset(0x0B)] UInt64BE Size;
[FieldOffset(0x13)] UInt32BE FilePathSize;
[FieldOffset(0x17)] UInt16BE ExpansionId;
// padding: [u8; 2]
}
[StructLayout(Size = 0x10)]
struct FileBlockHeader
{
[FieldOffset(0x00)] UInt32LE Size;
// padding: [u8; 4]
[FieldOffset(0x08)] UInt32LE CompressedSize;
[FieldOffset(0x0C)] UInt32LE DecompressedSize;
bool IsBlockCompressed => CompressedSize != 32000;
long BlockSize => IsBlockCompressed switch {
true => CompressedSize,
false => DecompressedSize,
};
long AlignedBlockSize => AlignBlockSize(BlockSize);
}FileChunk:
+------------+==========+~~~~~~~~~~~~~~~~+
| FileHeader | FilePath | SqpkFileBlocks |
+------------+==========+~~~~~~~~~~~~~~~~+SqpkFileBlocks:
+-------------+==============+=======+-------------+==============+
| BlkHeader#0 | BlkPayload#0 | pad#0 | BlkHeader#1 | BlkPayload#1 |
+-------------+==============+=======+-------------+==============+
<------- BlockSize#0 -------> <---- BlockSize#1 ---------->
<------- AlignedBlockSize#0 -------> <---- AlignedBlockSize#1 ----
+=======+~~~~~+-------------+==============+=======+
| pad#1 | ... | BlkHeader#N | BlkPayload#N | pad#N |
+=======+~~~~~+-------------+==============+=======+
<------- BlockSize#N ------>
------> <-------- AlignedBlockSize#N ------->void ApplyChunk(FileHeader header)
{
var filePath = ReadCString(header.FilePathSize);
var file = OpenFile(filePath);
file.SeekTo(header.Offset);
// assuming:
// chunkCurrent = beginning of blkHeader#0
// chunkEnd = end of pad#N
var (chunkCurrent, chunkEnd) = GetChunkPosition();
while chunkCurrent < chunkEnd {
patchFile.SeekTo(chunkCurrent);
var blockHeader = ReadBlockHeader();
var payload = ReadBlockPayload(header.BlockSize);
var stream = blockHeader.IsCompressed switch {
true => new DeflateStream(payload, CompressionMode.Decompress);
false => payload,
};
stream.CopyTo(file);
chunkCurrent += blockHeader.AlignedBlockSize;
}
}enum FileKind : byte
{
Dat = b'D', // id.platform.datN
Index = b'I', // id.platform.indexN
}
enum HeaderKind : byte
{
Version = b'V',
Data = b'D',
Index = b'I',
}
struct HeaderChunkHeader
{
[FieldOffset(0x00)] FileKind FileKind;
[FieldOffset(0x01)] HeaderKind HeaderKind;
[FieldOffset(0x03)] SqpkFilePath Path;
}+-------------------+---------------------+
| HeaderChunkHeader | Payload: [u8; 1024] |
+-------------------+---------------------+void ApplyChunk(HeaderChunkHeader header, byte payload[1024])
{
var path = ResolvePath(header.Path, header.FileKind);
var file = OpenFile(path);
var writeOffset = header.HeaderKind switch {
HeaderKind.Version => 0,
HeaderKind.Data => 1024,
HeaderKind.Index => 1024,
};
file.Write(writeOffset, payload);
}struct TargetHeader
{
// ..platform: ps3, ps4, win32...
}struct FFXIVARR_PACKET_SEGMENT_HEADER
{
uint32_t size;
uint32_t source_actor;
uint32_t target_actor;
uint16_t type;
uint16_t padding;
};enum FFXIVARR_SEGMENT_TYPE
{
SEGMENTTYPE_SESSIONINIT = 1,
SEGMENTTYPE_IPC = 3,
SEGMENTTYPE_KEEPALIVE = 7,
//SEGMENTTYPE_RESPONSE = 8,
SEGMENTTYPE_ENCRYPTIONINIT = 9,
};struct FFXIVARR_IPC_HEADER
{
uint16_t reserved;
uint16_t type;
uint16_t padding;
uint16_t serverId;
uint32_t timestamp;
uint32_t padding1;
};if buf.size < sizeof(FFXIVARR_PACKET_HEADER):
return
header = &buf[0] as FFXIVARR_PACKET_HEADER:
if buf.size < header.size:
return
data = slice buf from sizeof(FFXIVARR_PACKET_HEADER) to end of buf
if buf.isCompressed:
data = zlib_inflate(data)
pos = 0
while true:
segment = &data[pos] as FFXIVARR_PACKET_SEGMENT_HEADER
if segment.size >= buf.size
burn them
if segment.size >= data.size:
also burn them
pos = segment.size
seg_hdr_size = sizeof(FFXIVARR_PACKET_SEGMENT_HEADER)
if segment.type == SEGMENTTYPE_IPC:
ipc_size = segment.size - seg_hdr_size
ipc_data = slice segment from seg_hdr_size to ipc_size
ipc_hdr = &ipc_data[0] as FFXIVARR_IPC_HEADER
ipc_hdr_size = sizeof(FFXIVARR_IPC_HEADER)
packet_data = slice ipc_data from ipc_hdr_size to remaining size
process_channel_packet(ipc_hdr.type, packet_data)
// other segment types depend on the type of channel, but it's more of the sameName: 4 bytes
Size: 4 bytes
Contents: [Size] bytesAVFX:
Ver: the AVFX version used?
...parameters....
ScCn: number of schedulers in this file (always 1)
TlCn: number of timelines
EmCn: number of emitters
PrCn: number of particles
EfCn: number of effectors
BdCn: number of binders
TxCn: number of textures
MdCn: number of models
Schd: a scheduler block
TmLn[]: a list of timeline blocks
Emit[]: a list of emitter blocks
Ptcl[]: a list of particle blocks
Efct[]: a list of effector blocks
Bind[]: a list of binder blocks
Tex[]: a list of texture blocks
Modl[]: a list of model blocksSchd:
ItCn: item count
TrCn: trigger count, always 12
Item[]: a list of items
Trgr[]: a list of triggers, always 12 Trgr blocksItem or Trgr:
bEna: enabled
StTm: start time
TlNo: timeline indexItem:
bEna
StTm
TlNo
Item:
bEna : from item #0
StTm : from item #0
TlNo : from item #0
bEna
StTm
TlNo
Item:
bEna : from item #0
StTm : from item #0
TlNo : from item #0
bEna : from item #1
StTm : from item #1
TlNo : from item #1
bEna
StTm
TlNoTmLn:
...parameters...
TICn: number of items
CpCn: number of clips
Item[]: list of item blocks
Clip[]: list of clip blocks4-byte string, reversed
4 4-byte ints
4 4-byte floats
4 32-byte stringsEmit:
SdNm: the path to a sound file (.sdm)
...parameters....
PrCn: number of particles
EmCn: number of emitters
...animation curves...
ItEm[]: list of emitter items
ItPr[]: list of particles items
Data: depends on the emitter typeItEm:
[ItEm data #0]
ItEm:
[ItEm data #0]
[ItEm data #1]
ItEm:
[ItEm data #0]
[ItEm data #1]
[ItEm data #2]
ItPr:
[ItEm data #0]
[ItEm data #1]
[ItEm data #2]
[ItPr data #0]
ItPr:
[ItEm data #0]
[ItEm data #1]
[ItEm data #2]
[ItPr data #0]
[ItPr data #1]Ptcl:
...parameters...
UvSN: number of UV Sets
...more parameters...
...animation curves...
Smpl: Simple animations (optional)
UVSet[]: a list of UVSet blocks
Data: depends on particle type
TC1: texture color 1
TC2: texture color 2 (optional)
TC3: (optional)
TC4: (optional)
TN: texture normal
TR: texture reflection
TD: texture distortion
TP: texture paletteEfct:
...parameters...
DataBind:
...parameters...
PrpS: Start binder properties
Prp1: binder properties 1
Prp2: binder properties 2
PrpG: Goal binder properties
Data4 2-byte floats: position
4 1-byte ints: normal
4 1-byte ints: tangent
4 1-byte ints: color
4 2-byte floats: uv1
4 2-byte floats: uv23 4-byte floats: position
3 4-byte floats: normal
4 1-byte ints: color[Curve Name]:
....parameters...
Keys2-byte integer: time
2-byte integer: type
4-byte float: X
4-byte float: Y
4-byte float: ZExcel is a binary data format for storing relational data. Contains info for EXL, EXH and EXD files.
EXLT,2
Achievement,209
Action,4
content/DeepDungeon2Achievement,-1
content/DeepDungeon2Gacha,-1struct ExhHeader
{
char magic[0x4];
uint16_t unknown;
uint16_t dataOffset;
uint16_t columnCount;
uint16_t pageCount;
uint16_t languageCount;
uint16_t unknown1;
uint8_t u2;
uint8_t variant;
uint16_t u3;
uint32_t rowCount;
uint32_t u4[2];
};[StructLayout( LayoutKind.Sequential )]
public unsafe struct ExcelHeaderHeader
{
public fixed byte Magic[4];
// todo: not sure? maybe?
public ushort Version;
public ushort DataOffset;
public ushort ColumnCount;
public ushort PageCount;
public ushort LanguageCount;
private ushort _unknown1;
private byte _unknown2;
public ExcelVariant Variant;
private ushort _unknown3;
public uint RowCount;
private fixed uint U4[2];
}enum ExcelColumnDataType : uint16_t
{
String = 0x0,
Bool = 0x1,
Int8 = 0x2,
UInt8 = 0x3,
Int16 = 0x4,
UInt16 = 0x5,
Int32 = 0x6,
UInt32 = 0x7,
Float32 = 0x9,
Int64 = 0xA,
UInt64 = 0xB,
// 0 is read like data & 1, 1 is like data & 2, 2 = data & 4, etc...
PackedBool0 = 0x19,
PackedBool1 = 0x1A,
PackedBool2 = 0x1B,
PackedBool3 = 0x1C,
PackedBool4 = 0x1D,
PackedBool5 = 0x1E,
PackedBool6 = 0x1F,
PackedBool7 = 0x20,
};struct ExcelColumnDefinition
{
ExcelColumnDataType type;
uint16_t offset;
};[StructLayout( LayoutKind.Sequential )]
public struct ExcelColumnDefinition
{
public ExcelColumnDataType Type;
public ushort Offset;
}struct ExcelDataPagination
{
uint32_t startId;
uint32_t rowCount;
}[StructLayout( LayoutKind.Sequential )]
public struct ExcelDataPagination
{
public uint StartId;
public uint RowCount;
}enum Language : uint16_t
{
None,
// ja
Japanese,
// en
English,
// de
German,
// fr
French,
// chs
ChineseSimplified,
// cht
ChineseTraditional,
// ko
Korean
}enum ExcelVariant : uint8_t
{
Unknown,
Default,
SubRows
}exd/<name>_<page.startId>.exdexd/<name>_<page.startId>_<languageCode>.exdstruct ExcelDataHeader
{
char magic[0x4];
uint16_t version;
uint16_t unknown1;
uint32_t indexSize;
uint32_t unknown2[5];
};[StructLayout( LayoutKind.Sequential )]
public unsafe struct ExcelDataHeader
{
public fixed byte Magic[4];
public UInt16 Version;
private UInt16 U1;
public UInt32 IndexSize;
private fixed UInt16 U2[10];
}struct ExcelDataOffset
{
uint32_t rowId;
uint32_t offset;
};[StructLayout( LayoutKind.Sequential )]
public struct ExcelDataOffset
{
public UInt32 RowId;
public UInt32 Offset;
}struct ExcelDataRowHeader
{
uint32_t dataSize;
uint16_t rowCount;
};[StructLayout(LayoutKind.Sequential)]
public struct ExcelDataRowHeader
{
public uint DataSize;
public ushort RowCount;
}rowOffset + 6 + ( subRowId * header.dataOffset + 2 * ( subRowId + 1 ) );enum ExcelColumnDataType : uint16_t
{
String = 0x0,
Bool = 0x1,
Int8 = 0x2,
UInt8 = 0x3,
Int16 = 0x4,
UInt16 = 0x5,
Int32 = 0x6,
UInt32 = 0x7,
Float32 = 0x9,
Int64 = 0xA,
UInt64 = 0xB,
// 0 is read like data & 1, 1 is like data & 2, 2 = data & 4, etc...
PackedBool0 = 0x19,
PackedBool1 = 0x1A,
PackedBool2 = 0x1B,
PackedBool3 = 0x1C,
PackedBool4 = 0x1D,
PackedBool5 = 0x1E,
PackedBool6 = 0x1F,
PackedBool7 = 0x20,
};rowOffset + header.dataOffset + columnValuevar shift = (int)column.type - (int)ExcelColumnDataType.PackedBool0;
var bit = 1 << shift;
return (data & bit) == bit;Everything SqPack: indexes, dat files
sqpack/
ex1/
ex2/
ex3/
ffxiv/enum PlatformId : uint8_t
{
Win32,
PS3,
PS4
};
// https://github.com/SapphireServer/Sapphire/blob/develop/deps/datReader/SqPack.cpp#L5
struct SqPackHeader
{
char magic[0x8];
PlatformId platformId;
uint8_t padding0[3];
uint32_t size;
uint32_t version;
uint32_t type;
};public enum PlatformId : byte
{
Win32,
PS3,
PS4
}
// https://github.com/NotAdam/Lumina/blob/master/Lumina/Data/Structs/SqPackHeader.cs
[StructLayout( LayoutKind.Sequential )]
public unsafe struct SqPackHeader
{
public fixed byte magic[8];
public PlatformId platformId;
public fixed byte __unknown[3];
public UInt32 size;
public UInt32 version;
public UInt32 type;
}struct SqPackIndexHeader
{
uint32_t size;
uint32_t type;
uint32_t indexDataOffset;
uint32_t indexDataSize;
};[StructLayout( LayoutKind.Sequential )]
public unsafe struct SqPackIndexHeader
{
public UInt32 size;
public UInt32 version;
public UInt32 indexDataOffset;
public UInt32 indexDataSize;
}struct IndexHashTableEntry
{
uint64_t hash;
uint32_t unknown : 1;
uint32_t dataFileId : 3;
uint32_t offset : 28;
uint32_t _padding;
};[StructLayout( LayoutKind.Sequential )]
public struct IndexHashTableEntry
{
public UInt64 hash;
public UInt32 data;
private UInt32 _padding;
public byte DataFileId => (byte) ( ( data & 0b1110 ) >> 1 );
public uint Offset => (uint) ( data & ~0xF ) * 0x08;
}struct Index2HashTableEntry
{
uint32_t hash;
uint32_t unknown : 1;
uint32_t dataFileId : 3;
uint32_t offset : 28;
};[StructLayout( LayoutKind.Sequential )]
public struct Index2HashTableEntry
{
public UInt32 hash;
public UInt32 data;
public byte DataFileId => (byte) ( ( data & 0b1110 ) >> 1 );
public uint Offset => (uint) ( data & ~0xF ) * 0x08;
}