ra2_mix/xcc_package/
mod.rs

1use crate::{Ra2Error, CncGame, checksum::ra2_crc, constants::*, crypto::{decrypt_blowfish_key, decrypt_mix_header, get_decryption_block_sizing}, MixDatabase};
2use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
3use std::{
4    collections::HashMap,
5    fs::File,
6    io::{Seek, SeekFrom, Write},
7    path::Path,
8};
9
10pub mod reader;
11pub mod writer;
12
13/// MIX package
14#[derive(Default, Debug)]
15pub struct MixPackage {
16    /// The game version of the MIX package
17    pub game: CncGame,
18    /// A map of file names to file data
19    pub files: HashMap<String, Vec<u8>>,
20}
21
22
23
24/// MIX file header
25#[derive(Copy, Debug, Clone)]
26struct MixHeader {
27    /// Flags (None for old format)
28    pub flags: Option<u32>,
29    /// Number of files in the MIX
30    pub file_count: u16,
31    /// Total size of file data
32    pub data_size: u32,
33}
34
35/// MIX file entry
36#[derive(Debug, Clone, Copy)]
37struct FileEntry {
38    /// File ID (CRC of filename)
39    pub id: u32,
40    /// Offset in the body data
41    pub offset: i32,
42    /// Size of the file
43    pub size: i32,
44}
45/// File information for MIX file creation
46#[derive(Debug, Clone)]
47struct FileInfo {
48    /// File ID (CRC of filename)
49    file_id: u32,
50    /// File data
51    data: Vec<u8>,
52}
53
54impl MixPackage {
55    /// Add any file to the MIX package, no matter if it is valid or not.
56    ///
57    /// # Arguments
58    ///
59    /// * `name`: the file name with extension
60    /// * `data`: the file bytes
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// let mut mix = ra2_mix::MixPackage::default();
66    /// mix.add_any("hello.txt".to_string(), b"Hello, World!".to_vec());
67    /// ```
68    pub fn add_any(&mut self, name: String, data: Vec<u8>) {
69        self.files.insert(name, data);
70    }
71
72    /// Add a file from filesystem to the package
73    ///
74    /// # Arguments
75    /// * `data` - Path to the file to add
76    ///
77    /// # Returns
78    /// Size of the added file in bytes on success, or error if file not found
79    ///
80    /// # Examples
81    /// ```no_run
82    /// use ra2_mix::MixPackage;
83    /// use std::path::Path;
84    ///
85    /// let mut package = MixPackage::default();
86    /// package.add_file(Path::new("test.txt")).unwrap();
87    /// ```
88    pub fn add_file(&mut self, data: &Path) -> Result<usize, Ra2Error> {
89        if !data.is_file() {
90            return Err(Ra2Error::FileNotFound("must file".to_string()));
91        }
92        let name = data.file_name().and_then(|s| s.to_str()).ok_or(Ra2Error::FileNotFound("".to_string()))?;
93        let data = std::fs::read(data)?;
94        let size = data.len();
95        self.files.insert(name.to_string(), data);
96        Ok(size)
97    }
98}
99
100/// Extract single file from the MIX file to a folder
101///
102/// # Arguments
103///
104/// * `input`:
105/// * `output`:
106///
107/// returns: Result<(), MixError>
108///
109/// # Examples
110///
111/// ```
112/// ```
113pub fn extract(input: &Path, output: &Path) -> Result<(), Ra2Error> {
114    let xcc = MixPackage::load(input, &MixDatabase::default())?;
115    let file_map = xcc.files;
116    std::fs::create_dir_all(output)?;
117    for (filename, file_data) in file_map {
118        let file_path = output.join(filename);
119        let mut file = File::create(file_path)?;
120        file.write_all(&file_data)?;
121    }
122    Ok(())
123}
124/// Patch a folder into the MIX file
125///
126/// # Arguments
127///
128/// * `input`:
129/// * `output`:
130///
131/// returns: Result<(), MixError>
132///
133/// # Examples
134///
135/// ```
136/// ```
137pub fn patch(input: &Path, output: &Path) -> Result<(), Ra2Error> {
138    let mut xcc = MixPackage::load(input, &MixDatabase::default())?;
139    for entry in std::fs::read_dir(input)? {
140        let entry = entry?;
141        xcc.add_file(&entry.path())?;
142    }
143    xcc.save(output)?;
144    Ok(())
145}