Skip to main content

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