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
Contains information about files that are specific to each character.
def create_rsf(plaintext):
deflated_bytes = zlib.compress(plaintext, level=9)
# Server stores this part and will send it to the client when zoning-in
header = deflated_bytes[:64]
# Client stores this part
body = deflated_bytes[64:]RSV is a placeholder value used for masking the actual value until the player is actually zoned in.
// sent for each rsv string
struct RsvPkt {
UInt32 value_size; // len(without_nul_char(value))
char key_str[32]; // null terminated
char value_str[1024]; // null terminated
}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;
}
}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];
}void 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;
}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);
}<D|H><YEAR>.<MONTH>.<DAY>.<PART>.<REVISION>.patchH2017.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
}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;

struct FFXIVARR_PACKET_HEADER
{
uint64_t magic[2];
uint64_t timestamp;
uint32_t size;
uint16_t connectionType;
uint16_t segmentCount;
uint8_t unknown_20;
uint8_t isCompressed;
uint32_t unknown_24;
};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 same[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;
}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...
}Name: 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: Zsqpack/
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;
}Excel 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;