rpkg_rs/resource/
resource_package.rs

1use crate::resource::resource_info::ResourceInfo;
2use crate::resource::resource_package::ReferenceType::{INSTALL, NORMAL, WEAK};
3use binrw::{binrw, parser, BinRead, BinReaderExt, BinResult};
4use bitfield_struct::bitfield;
5use indexmap::IndexMap;
6use itertools::Itertools;
7use lzzzz::lz4;
8use memmap2::Mmap;
9use std::fs::File;
10use std::io::{Cursor, Read, Seek};
11use std::iter::zip;
12use std::path::{Path, PathBuf};
13use std::{fmt, io};
14use thiserror::Error;
15
16use crate::resource::runtime_resource_id::RuntimeResourceID;
17
18#[derive(Debug, Error)]
19pub enum ResourcePackageError {
20    #[error("Error opening the file: {0}")]
21    IoError(#[from] io::Error),
22
23    #[error("Couldn't find the requested resource inside of the given resource package")]
24    ResourceNotFound,
25
26    #[error("Parsing error: {0}")]
27    ParsingError(#[from] binrw::Error),
28
29    #[error("Resource package has no source")]
30    NoSource,
31
32    #[error("LZ4 decompression error: {0}")]
33    Lz4DecompressionError(#[from] lzzzz::Error),
34}
35
36pub enum ResourcePackageSource {
37    File(PathBuf),
38    Memory(Vec<u8>),
39}
40
41/// The version of the package.
42///
43/// `RPKGv1` is the original version of the package format used in Hitman 2016 and Hitman 2.
44/// `RPKGv2` is the updated version of the package format used in Hitman 3.
45pub enum PackageVersion {
46    RPKGv1,
47    RPKGv2,
48}
49
50#[allow(dead_code)]
51#[binrw]
52#[brw(little, import(is_patch: bool))]
53pub struct ResourcePackage {
54    #[brw(ignore)]
55    pub(crate) source: Option<ResourcePackageSource>,
56
57    pub(crate) magic: [u8; 4],
58
59    #[br(if (magic == *b"2KPR"))]
60    #[bw(if (magic == b"2KPR"))]
61    pub(crate) metadata: Option<PackageMetadata>,
62
63    pub(crate) header: PackageHeader,
64
65    #[brw(if(is_patch))]
66    pub(crate) unneeded_resource_count: u32,
67
68    #[brw(if(is_patch))]
69    #[br(count = unneeded_resource_count, map = |ids: Vec<u64>| {
70    match unneeded_resource_count{
71        0 => None,
72        _ => Some(ids.into_iter().map(RuntimeResourceID::from).collect::<Vec<_>>()),
73    }
74    })]
75    pub(crate) unneeded_resources: Option<Vec<RuntimeResourceID>>,
76
77    #[br(parse_with = resource_parser, args(header.file_count))]
78    #[bw(write_with = empty_writer)]
79    pub(crate) resources: IndexMap<RuntimeResourceID, ResourceInfo>,
80}
81
82#[parser(reader: reader, endian)]
83fn resource_parser(file_count: u32) -> BinResult<IndexMap<RuntimeResourceID, ResourceInfo>> {
84    let mut map = IndexMap::new();
85    let mut resource_entries = vec![];
86    for _ in 0..file_count {
87        resource_entries.push(PackageOffsetInfo::read_options(reader, endian, ())?);
88    }
89
90    let mut resource_metadata = vec![];
91    for _ in 0..file_count {
92        resource_metadata.push(ResourceHeader::read_options(reader, endian, ())?);
93    }
94
95    let resources = zip(resource_entries, resource_metadata)
96        .map(|(entry, header)| ResourceInfo { entry, header })
97        .collect::<Vec<ResourceInfo>>();
98
99    for resource in resources {
100        map.insert(resource.entry.runtime_resource_id, resource);
101    }
102
103    Ok(map)
104}
105
106impl ResourcePackage {
107    /// Parses a ResourcePackage from a file.
108    ///
109    /// # Arguments
110    /// * `package_path` - The path to the file to parse.
111    pub fn from_file<P: AsRef<Path> + Copy>(package_path: P) -> Result<Self, ResourcePackageError> {
112        let file = File::open(package_path).map_err(ResourcePackageError::IoError)?;
113        let mmap = unsafe { Mmap::map(&file).map_err(ResourcePackageError::IoError)? };
114        let mut reader = Cursor::new(&mmap[..]);
115        
116        let package_path = package_path.as_ref();
117        
118        let is_patch = package_path
119            .file_name()
120            .and_then(|f| f.to_str())
121            .map(|s| s.contains("patch"))
122            .unwrap_or(false);
123
124        let mut package = reader
125            .read_ne_args::<ResourcePackage>((is_patch,))
126            .map_err(ResourcePackageError::ParsingError)?;
127
128        package.source = Some(ResourcePackageSource::File(package_path.to_path_buf()));
129
130        Ok(package)
131    }
132
133    /// Parses a ResourcePackage from a memory buffer.
134    ///
135    /// # Arguments
136    /// * `data` - The data to parse.
137    /// * `is_patch` - Whether the package is a patch package.
138    pub fn from_memory(data: Vec<u8>, is_patch: bool) -> Result<Self, ResourcePackageError> {
139        let mut reader = Cursor::new(&data);
140        let mut package = reader
141            .read_ne_args::<ResourcePackage>((is_patch,))
142            .map_err(ResourcePackageError::ParsingError)?;
143
144        package.source = Some(ResourcePackageSource::Memory(data));
145
146        Ok(package)
147    }
148
149    /// Returns the version of the package.
150    pub fn version(&self) -> PackageVersion {
151        match &self.magic {
152            b"GKPR" => PackageVersion::RPKGv1,
153            b"2KPR" => PackageVersion::RPKGv2,
154            _ => panic!("Unknown package version"),
155        }
156    }
157
158    /// Returns the source of the package.
159    pub fn source(&self) -> Option<&ResourcePackageSource> {
160        self.source.as_ref()
161    }
162
163    /// Returns a map of the RuntimeResourceIds and their resource information.
164    pub fn resources(&self) -> &IndexMap<RuntimeResourceID, ResourceInfo> {
165        &self.resources
166    }
167
168    /// Returns whether the package uses the legacy references format.
169    pub fn has_legacy_references(&self) -> bool {
170        self.resources.iter().any(|(_, resource)| {
171            resource.references().iter().any(|(_, flags)| match flags {
172                ResourceReferenceFlags::Legacy(_) => true,
173                ResourceReferenceFlags::Standard(_) => false,
174            })
175        })
176    }
177
178    /// Returns whether the given resource is an unneeded resource.
179    ///
180    /// # Arguments
181    /// * `rrid` - The resource ID to check.
182    pub fn has_unneeded_resource(&self, rrid: &RuntimeResourceID) -> bool {
183        if let Some(unneeded_resources) = &self.unneeded_resources {
184            unneeded_resources.contains(rrid)
185        } else {
186            false
187        }
188    }
189
190    /// Returns a vector of all unneeded resource IDs.
191    pub fn unneeded_resource_ids(&self) -> Vec<&RuntimeResourceID> {
192        match &self.unneeded_resources {
193            None => {
194                vec![]
195            }
196            Some(val) => val.iter().collect(),
197        }
198    }
199
200    /// Reads the data of a resource from the package into memory.
201    ///
202    /// # Arguments
203    /// * `rrid` - The resource ID of the resource to read.
204    pub fn read_resource(&self, rrid: &RuntimeResourceID) -> Result<Vec<u8>, ResourcePackageError> {
205        let resource = self
206            .resources
207            .get(rrid)
208            .ok_or(ResourcePackageError::ResourceNotFound)?;
209
210        let final_size = resource
211            .compressed_size()
212            .unwrap_or(resource.header.data_size);
213
214        let is_lz4ed = resource.is_compressed();
215        let is_scrambled = resource.is_scrambled();
216
217        // Extract the resource bytes from the resourcePackage
218        let mut buffer = match &self.source {
219            Some(ResourcePackageSource::File(package_path)) => {
220                let mut file = File::open(package_path).map_err(ResourcePackageError::IoError)?;
221                file.seek(io::SeekFrom::Start(resource.entry.data_offset))
222                    .map_err(ResourcePackageError::IoError)?;
223
224                let mut buffer = vec![0; final_size as usize];
225                file.read_exact(&mut buffer)
226                    .map_err(ResourcePackageError::IoError)?;
227                buffer
228            }
229
230            Some(ResourcePackageSource::Memory(data)) => {
231                let start_offset = resource.entry.data_offset as usize;
232                let end_offset = start_offset + final_size as usize;
233                data[start_offset..end_offset].to_vec()
234            }
235
236            None => return Err(ResourcePackageError::NoSource),
237        };
238
239        if is_scrambled {
240            let str_xor = [0xdc, 0x45, 0xa6, 0x9c, 0xd3, 0x72, 0x4c, 0xab];
241            buffer.iter_mut().enumerate().for_each(|(index, byte)| {
242                *byte ^= str_xor[index % str_xor.len()];
243            });
244        }
245
246        if is_lz4ed {
247            let mut decompressed_buffer = vec![0; resource.header.data_size as usize];
248            lz4::decompress(&buffer, &mut decompressed_buffer)?;
249            return Ok(decompressed_buffer);
250        }
251
252        Ok(buffer)
253    }
254}
255
256#[binrw]
257#[brw(repr(u8))]
258#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
259pub enum ChunkType {
260    #[default]
261    Standard,
262    Addon,
263}
264
265#[allow(dead_code)]
266#[binrw]
267pub struct PackageMetadata {
268    pub unknown: u32,
269    pub chunk_id: u8,
270    pub chunk_type: ChunkType,
271    pub patch_id: u8,
272    pub language_tag: [u8; 2], //this is presumably an unused language code, is always 'xx'
273}
274
275#[allow(dead_code)]
276#[binrw]
277pub struct PackageHeader {
278    pub file_count: u32,
279    pub offset_table_size: u32,
280    pub metadata_table_size: u32,
281}
282
283#[bitfield(u32)]
284#[binrw]
285#[derive(Eq, PartialEq)]
286pub struct PackageOffsetFlags {
287    #[bits(31)]
288    pub compressed_size: u32,
289    pub is_scrambled: bool,
290}
291
292#[allow(dead_code)]
293#[derive(Copy, Clone)]
294#[binrw]
295#[brw(little)]
296pub struct PackageOffsetInfo {
297    pub(crate) runtime_resource_id: RuntimeResourceID,
298    pub(crate) data_offset: u64,
299    pub(crate) flags: PackageOffsetFlags,
300}
301
302impl PackageOffsetInfo {
303    pub fn is_scrambled(&self) -> bool {
304        self.flags.is_scrambled()
305    }
306
307    pub fn compressed_size(&self) -> Option<u32> {
308        match self.flags.compressed_size() {
309            0 => None,
310            n => Some(n),
311        }
312    }
313}
314
315#[allow(dead_code)]
316#[derive(Clone, PartialEq, Eq)]
317#[binrw]
318#[brw(little)]
319pub struct ResourceHeader {
320    pub(crate) resource_type: [u8; 4],
321    pub(crate) references_chunk_size: u32,
322    pub(crate) states_chunk_size: u32,
323    pub(crate) data_size: u32,
324    pub(crate) system_memory_requirement: u32,
325    pub(crate) video_memory_requirement: u32,
326
327    #[br(if (references_chunk_size > 0), parse_with = read_references)]
328    #[bw(write_with = empty_writer)]
329    pub references: Vec<(RuntimeResourceID, ResourceReferenceFlags)>,
330}
331
332#[bitfield(u8)]
333#[binrw]
334#[bw(map = |&x| Self::into_bits(x))]
335#[derive(Eq, PartialEq)]
336pub struct ResourceReferenceFlagsLegacy {
337    pub __: bool,
338    pub runtime_acquired: bool,
339    pub weak_reference: bool,
340    pub __: bool,
341    pub type_of_streaming_entity: bool,
342    pub state_streamed: bool,
343    pub media_streamed: bool,
344    pub install_dependency: bool,
345}
346
347#[bitfield(u8)]
348#[binrw]
349#[derive(Eq, PartialEq)]
350#[bw(map = |&x: &Self| x.into_bits())]
351pub struct ResourceReferenceFlagsStandard {
352    #[bits(5, default = 0x1F)]
353    pub language_code: u8,
354    pub runtime_acquired: bool,
355    #[bits(2)]
356    pub reference_type: ReferenceType,
357}
358
359#[derive(Debug, Copy, Clone, Eq, PartialEq)]
360pub enum ReferenceType {
361    INSTALL = 0,
362    NORMAL = 1,
363    WEAK = 2,
364}
365
366impl ReferenceType {
367    const fn into_bits(self) -> u16 {
368        self as _
369    }
370    const fn from_bits(value: u8) -> Self {
371        match value {
372            0 => INSTALL,
373            1 => NORMAL,
374            2 => WEAK,
375            _ => NORMAL,
376        }
377    }
378}
379
380/// Reference flags for a given resource, defines the metadata of a reference
381#[derive(Copy, Clone, Eq, PartialEq, Debug)]
382pub enum ResourceReferenceFlags {
383    Legacy(ResourceReferenceFlagsLegacy),
384    Standard(ResourceReferenceFlagsStandard),
385}
386
387impl From<ResourceReferenceFlags> for ResourceReferenceFlagsLegacy {
388    fn from(value: ResourceReferenceFlags) -> Self {
389        match value {
390            ResourceReferenceFlags::Legacy(b) => b,
391            ResourceReferenceFlags::Standard(b) => ResourceReferenceFlagsLegacy::new()
392                .with_runtime_acquired(b.runtime_acquired())
393                .with_weak_reference(b.reference_type() == WEAK)
394                .with_install_dependency(b.reference_type() == INSTALL),
395        }
396    }
397}
398
399impl From<ResourceReferenceFlags> for ResourceReferenceFlagsStandard {
400    fn from(value: ResourceReferenceFlags) -> Self {
401        match value {
402            ResourceReferenceFlags::Standard(b) => b,
403            ResourceReferenceFlags::Legacy(b) => ResourceReferenceFlagsStandard::new()
404                .with_language_code(0x1F)
405                .with_runtime_acquired(b.runtime_acquired())
406                .with_reference_type(value.reference_type()),
407        }
408    }
409}
410
411impl ResourceReferenceFlags {
412    /// ```
413    /// # use rpkg_rs::resource::resource_package::*;
414    /// # fn main(){
415    ///     let flag_v1 = ResourceReferenceFlagsLegacy::new().with_install_dependency(true).with_runtime_acquired(true);
416    ///     let flag_v2 = ResourceReferenceFlagsStandard::new().with_reference_type(ReferenceType::INSTALL).with_runtime_acquired(true);
417    ///
418    ///     assert_eq!(flag_v1, ResourceReferenceFlags::Standard(flag_v2).to_legacy());
419    /// # }
420    /// ```
421    pub fn to_legacy(&self) -> ResourceReferenceFlagsLegacy {
422        (*self).into()
423    }
424
425    /// ```
426    /// # use rpkg_rs::resource::resource_package::*;
427    /// # fn main(){
428    ///     let flag_v1 = ResourceReferenceFlagsLegacy::new().with_install_dependency(true).with_runtime_acquired(true);
429    ///     let flag_v2 = ResourceReferenceFlagsStandard::new().with_reference_type(ReferenceType::INSTALL).with_runtime_acquired(true).with_language_code(0x1F);
430    ///
431    ///     assert_eq!(flag_v2, ResourceReferenceFlags::Legacy(flag_v1).to_standard());
432    /// # }
433    /// ```
434    pub fn to_standard(&self) -> ResourceReferenceFlagsStandard {
435        (*self).into()
436    }
437
438    pub fn as_byte(&self) -> u8 {
439        match self {
440            ResourceReferenceFlags::Legacy(x) => x.into_bits(),
441            ResourceReferenceFlags::Standard(x) => x.into_bits(),
442        }
443    }
444}
445
446impl ResourceReferenceFlags {
447    pub fn language_code(&self) -> u8 {
448        match self {
449            ResourceReferenceFlags::Legacy(_) => 0x1F,
450            ResourceReferenceFlags::Standard(b) => b.language_code(),
451        }
452    }
453
454    pub fn is_acquired(&self) -> bool {
455        match self {
456            ResourceReferenceFlags::Legacy(b) => b.runtime_acquired(),
457            ResourceReferenceFlags::Standard(b) => b.runtime_acquired(),
458        }
459    }
460
461    pub fn reference_type(&self) -> ReferenceType {
462        match self {
463            ResourceReferenceFlags::Legacy(b) => match b.install_dependency() {
464                true => INSTALL,
465                false if b.weak_reference() => WEAK,
466                false => NORMAL,
467            },
468            ResourceReferenceFlags::Standard(b) => b.reference_type(),
469        }
470    }
471}
472
473#[bitfield(u32)]
474#[binrw]
475#[derive(Eq, PartialEq)]
476#[bw(map = |&x: &Self| x.into_bits())]
477pub struct ResourceReferenceCountAndFlags {
478    #[bits(30)]
479    pub reference_count: u32,
480    pub is_new_format: bool,
481    pub always_true: bool,
482}
483
484#[parser(reader)]
485fn read_references() -> BinResult<Vec<(RuntimeResourceID, ResourceReferenceFlags)>> {
486    let reference_count_and_flag = reader.read_le::<ResourceReferenceCountAndFlags>()?;
487    let reference_count = reference_count_and_flag.reference_count();
488    let is_new_format = reference_count_and_flag.is_new_format();
489
490    let arrays = if is_new_format {
491        let flags: Vec<ResourceReferenceFlags> = (0..reference_count)
492            .map(|_| reader.read_le::<ResourceReferenceFlagsStandard>())
493            .map_ok(ResourceReferenceFlags::Standard)
494            .collect::<BinResult<Vec<_>>>()?;
495        let rrids: Vec<RuntimeResourceID> = (0..reference_count)
496            .map(|_| u64::read_le(reader))
497            .map_ok(RuntimeResourceID::from)
498            .collect::<BinResult<Vec<_>>>()?;
499        (rrids, flags)
500    } else {
501        let rrids: Vec<RuntimeResourceID> = (0..reference_count)
502            .map(|_| u64::read_le(reader))
503            .map_ok(RuntimeResourceID::from)
504            .collect::<BinResult<Vec<_>>>()?;
505        let flags: Vec<ResourceReferenceFlags> = (0..reference_count)
506            .map(|_| reader.read_le::<ResourceReferenceFlagsLegacy>())
507            .map_ok(ResourceReferenceFlags::Legacy)
508            .collect::<BinResult<Vec<_>>>()?;
509        (rrids, flags)
510    };
511
512    Ok(arrays.0.into_iter().zip(arrays.1).collect::<Vec<(_, _)>>())
513}
514
515impl fmt::Display for PackageOffsetInfo {
516    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
517        write!(
518            f,
519            "resource {} is {} bytes at {}",
520            self.runtime_resource_id.to_hex_string(),
521            self.flags.compressed_size(),
522            self.data_offset
523        )
524    }
525}
526
527impl fmt::Display for ResourceHeader {
528    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
529        let mut res_type = self.resource_type;
530        res_type.reverse();
531        write!(
532            f,
533            "type: {}, reference_num: {}, size: {}, num_reqs: ({} {})",
534            std::str::from_utf8(&res_type).unwrap(),
535            self.references_chunk_size,
536            self.data_size,
537            self.system_memory_requirement,
538            self.video_memory_requirement
539        )
540    }
541}
542
543#[binrw::writer]
544fn empty_writer<T>(_: &T) -> BinResult<()> {
545    // This does nothing because the actual implementation is in the `PackageBuilder` struct.
546    Ok(())
547}
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552    #[test]
553    fn test_flag_conversion_801f() {
554        let flag_v1 = ResourceReferenceFlagsLegacy::from_bits(0x80);
555        let flag_v2 = ResourceReferenceFlagsStandard::from_bits(0x1F);
556
557        assert_eq!(
558            flag_v1,
559            ResourceReferenceFlags::Standard(flag_v2).to_legacy()
560        );
561        assert_eq!(
562            flag_v2,
563            ResourceReferenceFlags::Legacy(flag_v1).to_standard()
564        );
565    }
566
567    #[test]
568    fn test_flag_conversion_005f() {
569        let flag_v1 = ResourceReferenceFlagsLegacy::from_bits(0x00);
570        let flag_v2 = ResourceReferenceFlagsStandard::from_bits(0x5F);
571
572        assert_eq!(
573            flag_v1,
574            ResourceReferenceFlags::Standard(flag_v2).to_legacy()
575        );
576        assert_eq!(
577            flag_v2,
578            ResourceReferenceFlags::Legacy(flag_v1).to_standard()
579        );
580    }
581}