physis/sqpack/
mod.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use std::io::{Read, Seek, SeekFrom, Write};
5
6use binrw::{BinRead, BinWrite, binrw};
7use data::{BlockHeader, CompressionMode};
8
9use crate::common::{Platform, Region};
10use crate::compression::no_header_decompress;
11
12mod data;
13pub use data::SqPackData;
14
15mod db;
16pub use db::SqPackDatabase;
17
18mod index;
19pub use index::{Hash, IndexEntry, SqPackIndex};
20
21/// The type of this SqPack file.
22#[binrw]
23#[brw(repr = u8)]
24#[derive(Debug)]
25pub(crate) enum SqPackFileType {
26    /// FFXIV Explorer says "SQDB", whatever that is.
27    SQDB = 0x0,
28    /// Dat files.
29    Data = 0x1,
30    /// Index/Index2 files.
31    Index = 0x2,
32}
33
34#[binrw]
35#[brw(magic = b"SqPack\0\0")]
36#[derive(Debug)]
37pub(crate) struct SqPackHeader {
38    #[brw(pad_size_to = 4)]
39    platform_id: Platform,
40    pub size: u32,
41    // Have only seen version 1
42    version: u32,
43    #[brw(pad_size_to = 4)]
44    file_type: SqPackFileType,
45
46    // some unknown value, zeroed out for index files
47    // XivAlexandar says date/time, where does that come from?
48    unk1: u32,
49    unk2: u32,
50
51    #[br(pad_size_to = 4)]
52    region: Region,
53
54    #[brw(pad_before = 924)]
55    #[brw(pad_after = 44)]
56    // The SHA1 of the bytes immediately before this
57    sha1_hash: [u8; 20],
58}
59
60pub(crate) fn read_data_block<T: Read + Seek>(
61    mut buf: T,
62    starting_position: u64,
63) -> Option<Vec<u8>> {
64    buf.seek(SeekFrom::Start(starting_position)).ok()?;
65
66    let block_header = BlockHeader::read(&mut buf).unwrap();
67
68    match block_header.compression {
69        CompressionMode::Compressed {
70            compressed_length,
71            decompressed_length,
72        } => {
73            let mut compressed_data: Vec<u8> = vec![0; compressed_length as usize];
74            buf.read_exact(&mut compressed_data).ok()?;
75
76            let mut decompressed_data: Vec<u8> = vec![0; decompressed_length as usize];
77            if !no_header_decompress(&mut compressed_data, &mut decompressed_data) {
78                return None;
79            }
80
81            Some(decompressed_data)
82        }
83        CompressionMode::Uncompressed { file_size } => {
84            let mut local_data: Vec<u8> = vec![0; file_size as usize];
85            buf.read_exact(&mut local_data).ok()?;
86
87            Some(local_data)
88        }
89    }
90}
91
92/// A fixed version of read_data_block accounting for differing compressed block sizes in ZiPatch files.
93pub(crate) fn read_data_block_patch<T: Read + Seek>(mut buf: T) -> Option<Vec<u8>> {
94    let block_header = BlockHeader::read(&mut buf).unwrap();
95
96    match block_header.compression {
97        CompressionMode::Compressed {
98            compressed_length,
99            decompressed_length,
100        } => {
101            let compressed_length: usize =
102                ((compressed_length as usize + 143) & 0xFFFFFF80) - (block_header.size as usize);
103
104            let mut compressed_data: Vec<u8> = vec![0; compressed_length];
105            buf.read_exact(&mut compressed_data).ok()?;
106
107            let mut decompressed_data: Vec<u8> = vec![0; decompressed_length as usize];
108            if !no_header_decompress(&mut compressed_data, &mut decompressed_data) {
109                return None;
110            }
111
112            Some(decompressed_data)
113        }
114        CompressionMode::Uncompressed { file_size } => {
115            let new_file_size: usize = (file_size as usize + 143) & 0xFFFFFF80;
116
117            let mut local_data: Vec<u8> = vec![0; file_size as usize];
118            buf.read_exact(&mut local_data).ok()?;
119
120            buf.seek(SeekFrom::Current(
121                (new_file_size - block_header.size as usize - file_size as usize) as i64,
122            ))
123            .ok()?;
124
125            Some(local_data)
126        }
127    }
128}
129
130pub(crate) fn write_data_block_patch<T: Write + Seek>(mut writer: T, data: Vec<u8>) {
131    let new_file_size: usize = (data.len() + 143) & 0xFFFFFF80;
132
133    // This only adds uncompressed data for now, to simplify implementation
134    // TODO: write compressed blocks
135    let block_header = BlockHeader {
136        size: (new_file_size - data.len()) as u32, // TODO: i have no idea what this value is from
137        compression: CompressionMode::Uncompressed {
138            file_size: data.len() as i32,
139        },
140    };
141    block_header.write(&mut writer).unwrap();
142
143    data.write(&mut writer).unwrap();
144}