1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-04-21 20:27:46 +00:00
physis/src/index.rs

163 lines
4.4 KiB
Rust
Raw Normal View History

// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
2023-09-22 19:17:24 -04:00
#![allow(clippy::identity_op)]
use std::io::SeekFrom;
2022-07-19 19:29:41 -04:00
use binrw::BinRead;
use binrw::binrw;
2023-09-22 19:17:24 -04:00
use modular_bitfield::prelude::*;
use crate::common::Platform;
use crate::crc::Jamcrc;
2022-07-19 19:29:41 -04:00
#[binrw]
#[br(magic = b"SqPack")]
pub struct SqPackHeader {
#[br(pad_before = 2)]
platform_id: Platform,
2022-07-19 19:29:41 -04:00
#[br(pad_before = 3)]
size: u32,
version: u32,
file_type: u32,
}
#[binrw]
pub struct SqPackIndexHeader {
size: u32,
file_type: u32,
index_data_offset: u32,
index_data_size: u32,
}
2023-09-22 19:17:24 -04:00
#[bitfield]
2022-07-19 19:29:41 -04:00
#[binrw]
2023-09-22 19:17:24 -04:00
#[br(map = Self::from_bytes)]
#[derive(Clone, Copy, Debug)]
2022-07-19 19:29:41 -04:00
pub struct IndexHashBitfield {
2023-09-22 19:17:24 -04:00
pub size: B1,
pub data_file_id: B3,
pub offset: B28,
2022-07-19 19:29:41 -04:00
}
#[binrw]
pub struct IndexHashTableEntry {
pub hash: u64,
2022-07-19 19:29:41 -04:00
#[br(pad_after = 4)]
pub(crate) bitfield: IndexHashBitfield,
}
// The only difference between index and index2 is how the path hash is stored.
// The folder name and the filename are split in index1 (hence why it's 64-bits and not 32-bit)
// But in index2, its both the file and folder name in one single CRC hash.
#[binrw]
#[derive(Debug)]
pub struct Index2HashTableEntry {
pub hash: u32,
pub(crate) bitfield: IndexHashBitfield,
}
2022-07-19 19:29:41 -04:00
#[derive(Debug)]
pub struct IndexEntry {
pub hash: u64,
pub data_file_id: u8,
pub offset: u32,
}
#[binrw]
#[br(little)]
2022-07-19 19:29:41 -04:00
pub struct IndexFile {
sqpack_header: SqPackHeader,
#[br(seek_before = SeekFrom::Start(sqpack_header.size.into()))]
index_header: SqPackIndexHeader,
#[br(seek_before = SeekFrom::Start(index_header.index_data_offset.into()))]
// +4 because of padding
#[br(count = index_header.index_data_size / core::mem::size_of::<IndexHashTableEntry>() as u32 + 4)]
2022-07-19 19:29:41 -04:00
pub entries: Vec<IndexHashTableEntry>,
}
#[binrw]
#[br(little)]
pub struct Index2File {
sqpack_header: SqPackHeader,
#[br(seek_before = SeekFrom::Start(sqpack_header.size.into()))]
index_header: SqPackIndexHeader,
#[br(seek_before = SeekFrom::Start(index_header.index_data_offset.into()))]
#[br(count = index_header.index_data_size / core::mem::size_of::<Index2HashTableEntry>() as u32)]
pub entries: Vec<Index2HashTableEntry>,
}
const CRC: Jamcrc = Jamcrc::new();
2022-07-19 19:29:41 -04:00
impl IndexFile {
/// Creates a new reference to an existing index file.
pub fn from_existing(path: &str) -> Option<Self> {
let mut index_file = std::fs::File::open(path).ok()?;
2022-07-19 19:29:41 -04:00
Self::read(&mut index_file).ok()
}
/// Calculates a partial hash for a given path
pub fn calculate_partial_hash(path: &str) -> u32 {
let lowercase = path.to_lowercase();
CRC.checksum(lowercase.as_bytes())
}
/// Calculates a hash for `index` files from a game path.
pub fn calculate_hash(path: &str) -> u64 {
let lowercase = path.to_lowercase();
if let Some(pos) = lowercase.rfind('/') {
let (directory, filename) = lowercase.split_at(pos);
let directory_crc = CRC.checksum(directory.as_bytes());
let filename_crc = CRC.checksum(filename[1..filename.len()].as_bytes());
(directory_crc as u64) << 32 | (filename_crc as u64)
} else {
CRC.checksum(lowercase.as_bytes()) as u64
}
}
// TODO: turn into traits?
pub fn exists(&self, path: &str) -> bool {
let hash = IndexFile::calculate_hash(path);
self.entries.iter().any(|s| s.hash == hash)
}
pub fn find_entry(&self, path: &str) -> Option<&IndexHashTableEntry> {
let hash = IndexFile::calculate_hash(path);
self.entries.iter().find(|s| s.hash == hash)
2022-07-19 19:29:41 -04:00
}
2022-08-16 11:52:07 -04:00
}
impl Index2File {
/// Creates a new reference to an existing index2 file.
pub fn from_existing(path: &str) -> Option<Self> {
let mut index_file = std::fs::File::open(path).ok()?;
Some(Self::read(&mut index_file).unwrap())
}
/// Calculates a hash for `index2` files from a game path.
pub fn calculate_hash(path: &str) -> u32 {
let lowercase = path.to_lowercase();
CRC.checksum(lowercase.as_bytes())
}
pub fn exists(&self, path: &str) -> bool {
let hash = Index2File::calculate_hash(path);
self.entries.iter().any(|s| s.hash == hash)
}
pub fn find_entry(&self, path: &str) -> Option<&Index2HashTableEntry> {
let hash = Index2File::calculate_hash(path);
self.entries.iter().find(|s| s.hash == hash)
}
}