XIV Dev Wiki
Search…
ZiPatch
ZiPatch(.patch) is a file format used to update online games made by SQEX.
We'll only cover version 3 for now.

Intro

This patch format has been extant since FFXIV 1.0, but has had a few modifications since then, especially from 2.0 onward.
A patch file name generally starts with either H or D, for HIST and DIFF, respectively, followed by a version string, such as 2013.08.09.0001.0000. This is optionally followed by a lowercase letter (starting at 'a') in case the patch file corresponding to that version string would be larger than 1.5GB for HIST files. e.g.: H2013.08.09.0001.0000a.patch, D2019.05.29.0000.0000.patch.

Patch Naming Convention

Patch files follow the following naming convention:
1
<D|H><YEAR>.<MONTH>.<DAY>.<PART>.<REVISION>.patch
Copied!
Where D|H indicates that a patch is a delta patch or a history patch respectively.
Some example patch names are as follows:
1
H2017.06.06.0000.0001a.patch
2
H2017.06.06.0000.0001b.patch
3
D2017.07.11.0000.0001.patch
4
D2019.04.16.0000.0000.patch
Copied!

Notes

    Byte order, unless explicitly mentioned, is big endian order. That is, 0x00AB_CDEF in hexadecimal will be encoded as 00 AB CD EF in file - not EF CD AB 00 which is little endian.

File Signature

All patch files start with
1
91 5A 49 50 41 54 43 48 0D 0A 1A 0A
Copied!

Chunks

The ZiPatch signature is followed by a contiguous array of chunks with the following structure
1
struct ChunkEntry
2
{
3
ChunkHeader Header;
4
Chunk Payload; // TODO: explain this
5
UInt32BE Crc32;
6
}
Copied!
1
[StructLayout(LayoutKind.Explicit, Size = 8)]
2
struct ChunkHeader
3
{
4
[FieldOffset(0)] UInt32BE Size;
5
[FieldOffset(4)] char Name[4];
6
}
Copied!
Where Header.Size indicates the size of Chunk.Payload, and Header.Name is a 4-character string indicating the type of the chunk, e.g., "FHDR", "APLY", and "SQPK".
Following Header, there's a Chunk describing the chunk contents, which will be detailed in the following pages. Following the chunk payload, there is a CRC32 of Header.Name with Payload.
This contiguous array of chunks terminates with a chunk of Name "EOF_".

FHDR - File Header

1
[StructLayout(LayoutKind.Explicit, Size = 256)]
2
struct FhdrChunk
3
{
4
[FieldOffset(0x2)] byte Version; // ZIPATCH version (3)
5
[FieldOffset(0x4)] char Name[4];
6
// TODO...
7
[FieldOffset(0x20)] UInt32BE DepotHash; // also observable in url
8
}
Copied!
FHDR is always the first chunk in the file, containing a few unused fields and some information about the current patch file (such as number of commands, etc.)
FhdrChunk.Name has been observed to be HIST for history patches, or DIFF for delta patches.

APLY - Apply Option

1
[StructLayout(LayoutKind.Explicit, Size = 12)]
2
struct AplyChunk
3
{
4
// 1 -> Ignore Missing; 2 -> Ignore Mismatch
5
[FieldOffset(0x0)] UInt32BE Option;
6
// Believed to be length (in bytes) of Value, but it doesn't currently change
7
[FieldOffset(0x4)] UInt32BE Reserved;
8
// Non-zero Value sets Option to true for this .patch file
9
[FieldOffset(0x8)] UInt32BE Value;
10
}
Copied!
The two currently known Options, Ignore Missing and Ignore Mismatch are normally set to false via two APLY chunks set right after the FHDR. This means that the official patcher fails if a file it expects there is missing or some data it expects to be there is not.

ADIR - Add Directory

1
[StructLayout(LayoutKind.Explicit)]
2
struct AdirChunk
3
{
4
[FieldOffset(0x0)] UInt32BE PathLength;
5
[FieldOffset(0x4)] char Path[PathLength]; // TODO: This isn't valid C#
6
}
Copied!
Creates directory at Path. Path is relative to the game folder plus the patch type being applied (boot or game).
e.g.: If Path = "movie\ex1", and we are patching 'game' at "C:\Games\SquareEnix\FINAL FANTASY XIV - A Realm Reborn\", we would create the folder "ex1" at location "C:\Games\SquareEnix\FINAL FANTASY XIV - A Realm Reborn\game\movie\"

DELD - Delete Directory

1
[StructLayout(LayoutKind.Explicit)]
2
struct DeldChunk
3
{
4
[FieldOffset(0x0)] UInt32BE PathLength;
5
[FieldOffset(0x4)] char Path[PathLength]; // TODO: This isn't valid C#
6
}
Copied!
Similar to ADIR, but the folder at Path is deleted instead. It only deletes empty folders.

SQPK

todo: path resolving, alignment, little endian...
1
[StructLayout(LayoutKind.Explicit, Size = 5)]
2
struct SqpkChunk
3
{
4
[FieldOffset(0x0)] Uint32BE Size;
5
[FieldOffset(0x4)] SqpkOperation Opcode;
6
[FieldOffset(0x5)] SqpkPayload Payload;
7
}
8
9
enum SqpkOperation : byte
10
{
11
A, // Add data. Used to overwrite blocks in dat files. Can also delete old data
12
D, // Delete data. Used to delete blocks (overwrite with 0x00) in dat files
13
E, // Expand data. Used to insert empty blocks into dat files
14
F, // File operations. Adding files, deleting files, etc.
15
H, // Header Update. Updates headers of dat or index files
16
I, // Index Add/Delete. Present in patch files but unused
17
X, // Patch Info. Present in patch files but unused
18
T, // Target Info. Present in patch files but only one field is used
19
}
Copied!
SqpkChunk is a 'container' chunk which executes one of several operations in SqpkOperation. These will be detailed in the following subsections.
Important to note that any fields prepended with "Block" will mean that the number is in blocks, or units of 128-bytes. e.g., BlockOffset means that the offset in bytes would be calculated by BlockOffset << 7 or BlockOffset * 128.
These operations often contain fields indicating which file the operation will apply to. These adhere to the following structure:
1
[StructLayout(LayoutKind.Explicit, Size = 8)]
2
struct SqpkFile
3
{
4
[FieldOffset(0x0)] UInt16BE MainId;
5
[FieldOffset(0x2)] UInt16BE SubId;
6
[FieldOffset(0x4)] UInt32BE FileId;
7
8
// Expansion => (byte)(SubId >> 8);
9
// DatFileName => quot;{MainId:x2}{SubId:x4}.win32.dat{FileId}"
10
// IndexFileName => quot;{MainId:x2}{SubId:x4}.win32.index{(FileId == 0 ? string.Empty : FileId.ToString())}"
11
}
Copied!
When referring to block headers, the following struct will be relevant:
1
[StructLayout(LayoutKind.Explicit, Size = 128)]
2
struct BlockHeader
3
{
4
[FieldOffset(0x0)] UInt32LE Size;
5
[FieldOffset(0x4)] UInt32LE Type;
6
[FieldOffset(0x8)] UInt32LE FileSize;
7
[FieldOffset(0xC)] UInt32LE TotalBlocks;
8
[FieldOffset(0x10)] UInt32LE UsedBlocks;
9
}
Copied!

Type A - Add Data

1
[StructLayout(LayoutKind.Explicit)]
2
struct SqpkAddData
3
{
4
[FieldOffset(0x0)] byte reserved[3];
5
[FieldOffset(0x3)] SqpkFile File; // Dat file
6
[FieldOffset(0xB)] UInt32BE BlockOffset;
7
[FieldOffset(0xF)] UInt32BE BlockCount;
8
[FieldOffset(0x13)] UInt32BE BlockDeleteCount;
9
[FieldOffset(0x17)] byte Data[BlockCount << 7]; // TODO: Not valid C#
10
}
Copied!
This operation writes SqpkAddData.Data to the dat File, at BlockOffset. It will then write BlockDeleteCount blocks of empty (0x00) 128-bytes.

Type D - Delete Data

1
[StructLayout(LayoutKind.Explicit, Size = 19)]
2
struct SqpkDeleteData
3
{
4
[FieldOffset(0x0)] byte reserved[3];
5
[FieldOffset(0x3)] SqpkFile File; // Dat file
6
[FieldOffset(0xB)] UInt32BE BlockOffset;
7
[FieldOffset(0xF)] UInt32BE BlockCount;
8
}
Copied!
This operation writes BlockCount empty (0x00) 128-byte blocks at BlockOffset, with the first of those blocks containing a BlockHeader struct with:Size = 128, Type = FileSize = UsedBlocks = 0, TotalBlocks = BlockCount
If Delete Data encounters EOF while writing or seeking to BlockOffset, it will fail.

Type E - Expand Data

Expand Data follows the same structure as Delete Data, but the behaviour differs. When Expand Data seeks past the end of file or writes past the end of file, it does not fail, since this is its intended use.

Type F - File Operation

todo: file block which is shared with .sqpk

Type H - Header Update

Type I - Index Update

Type X - Patch Info

Type T - Target Info

unused?

_EOF

Indicates no chunks should be processed after this.
1
[StructLayout(LayoutKind.Explicit, Size = 32)]
2
struct EofChunk
3
{
4
// zero padded
5
}
Copied!

Links

Last modified 1yr ago