From 4e195ec38657605657dcbcc5b76581cbf5b8c404 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sun, 17 Sep 2023 02:35:58 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BE=D0=BA=20=D0=B2?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=80=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Cargo.lock | 105 ++++++++++++++++++++++++++ Cargo.toml | 7 ++ packer/Cargo.toml | 9 +++ packer/README.md | 0 packer/src/main.rs | 170 +++++++++++++++++++++++++++++++++++++++++++ unpacker/Cargo.toml | 9 +++ unpacker/README.md | 0 unpacker/src/main.rs | 124 +++++++++++++++++++++++++++++++ 9 files changed, 425 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 packer/Cargo.toml create mode 100644 packer/README.md create mode 100644 packer/src/main.rs create mode 100644 unpacker/Cargo.toml create mode 100644 unpacker/README.md create mode 100644 unpacker/src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5d240a1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,105 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "packer" +version = "0.1.0" +dependencies = [ + "byteorder", + "serde", + "serde_json", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unpacker" +version = "0.1.1" +dependencies = [ + "byteorder", + "serde", + "serde_json", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3fc2b87 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +members = ["packer", "unpacker"] + +[profile.release] +codegen-units = 1 +lto = true +strip = true diff --git a/packer/Cargo.toml b/packer/Cargo.toml new file mode 100644 index 0000000..cbf418c --- /dev/null +++ b/packer/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "packer" +version = "0.1.0" +edition = "2021" + +[dependencies] +byteorder = "1.4.3" +serde = { version = "1.0.160", features = ["derive"] } +serde_json = "1.0.96" diff --git a/packer/README.md b/packer/README.md new file mode 100644 index 0000000..e69de29 diff --git a/packer/src/main.rs b/packer/src/main.rs new file mode 100644 index 0000000..ae4e02a --- /dev/null +++ b/packer/src/main.rs @@ -0,0 +1,170 @@ +use std::env; +use std::{ + fs::{self, File}, + io::{BufReader, Read}, +}; + +use byteorder::{ByteOrder, LittleEndian}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct ImportListElement { + pub extension: String, + pub index: u32, + pub name: String, + pub unknown0: u32, + pub unknown1: u32, + pub unknown2: u32, +} + +#[derive(Debug)] +pub struct ListElement { + pub extension: String, + pub index: u32, + pub name: String, + pub position: u32, + pub size: u32, + pub unknown0: u32, + pub unknown1: u32, + pub unknown2: u32, +} + +fn main() { + let args: Vec = env::args().collect(); + + let input = &args[1]; + let output = &args[2]; + + pack(String::from(input), String::from(output)); +} + +fn pack(input: String, output: String) { + // Загружаем индекс-файл + let index_file = format!("{}/{}", input, "index.json"); + let data = fs::read_to_string(index_file).unwrap(); + let list: Vec = serde_json::from_str(&data).unwrap(); + + // Общий буфер хранения файлов + let mut content_buffer: Vec = Vec::new(); + let mut list_buffer: Vec = Vec::new(); + + // Общее количество файлов + let total_files: u32 = list.len() as u32; + + for (index, item) in list.iter().enumerate() { + // Открываем дескриптор файла + let path = format!("{}/{}", input, item.name); + let file = File::open(path).unwrap(); + let metadata = file.metadata().unwrap(); + + // Считываем файл в буфер + let mut reader = BufReader::new(file); + let mut file_buffer: Vec = Vec::new(); + reader.read_to_end(&mut file_buffer).unwrap(); + + // Выравнивание буфера + if index != 0 { + while content_buffer.len() % 8 != 0 { + content_buffer.push(0); + } + } + + // Получение позиции файла + let position = content_buffer.len() + 16; + + // Записываем файл в буфер + content_buffer.extend(file_buffer); + + // Формируем элемент + let element = ListElement { + extension: item.extension.to_string(), + index: item.index, + name: item.name.to_string(), + position: position as u32, + size: metadata.len() as u32, + unknown0: item.unknown0, + unknown1: item.unknown1, + unknown2: item.unknown2, + }; + + // Создаем буфер из элемента + let mut element_buffer: Vec = Vec::new(); + + // Пишем тип файла + let mut extension_buffer: [u8; 4] = [0; 4]; + let mut file_extension_buffer = element.extension.into_bytes(); + file_extension_buffer.resize(4, 0); + extension_buffer.copy_from_slice(&file_extension_buffer); + element_buffer.extend(extension_buffer); + + // Пишем неизвестное значение #1 + let mut unknown0_buffer: [u8; 4] = [0; 4]; + LittleEndian::write_u32(&mut unknown0_buffer, element.unknown0); + element_buffer.extend(unknown0_buffer); + + // Пишем неизвестное значение #2 + let mut unknown1_buffer: [u8; 4] = [0; 4]; + LittleEndian::write_u32(&mut unknown1_buffer, element.unknown1); + element_buffer.extend(unknown1_buffer); + + // Пишем размер файла + let mut file_size_buffer: [u8; 4] = [0; 4]; + LittleEndian::write_u32(&mut file_size_buffer, element.size); + element_buffer.extend(file_size_buffer); + + // Пишем неизвестное значение #3 + let mut unknown2_buffer: [u8; 4] = [0; 4]; + LittleEndian::write_u32(&mut unknown2_buffer, element.unknown2); + element_buffer.extend(unknown2_buffer); + + // Пишем название файла + let mut name_buffer: [u8; 36] = [0; 36]; + let mut file_name_buffer = element.name.into_bytes(); + file_name_buffer.resize(36, 0); + name_buffer.copy_from_slice(&file_name_buffer); + element_buffer.extend(name_buffer); + + // Пишем позицию файла + let mut position_buffer: [u8; 4] = [0; 4]; + LittleEndian::write_u32(&mut position_buffer, element.position); + element_buffer.extend(position_buffer); + + // Пишем индекс файла + let mut index_buffer: [u8; 4] = [0; 4]; + LittleEndian::write_u32(&mut index_buffer, element.index); + element_buffer.extend(index_buffer); + + // Добавляем итоговый буфер в буфер элементов списка + list_buffer.extend(element_buffer); + } + + let mut header_buffer: Vec = Vec::new(); + + // Пишем первый тип файла + let mut header_type_1 = [0; 4]; + LittleEndian::write_u32(&mut header_type_1, 1936020046_u32); + header_buffer.extend(header_type_1); + + // Пишем второй тип файла + let mut header_type_2 = [0; 4]; + LittleEndian::write_u32(&mut header_type_2, 256_u32); + header_buffer.extend(header_type_2); + + // Пишем количество файлов + let mut header_total_files = [0; 4]; + LittleEndian::write_u32(&mut header_total_files, total_files); + header_buffer.extend(header_total_files); + + // Пишем общий размер файла + let mut header_total_size = [0; 4]; + let total_size: u32 = ((content_buffer.len() + 16) as u32) + (total_files * 64); + LittleEndian::write_u32(&mut header_total_size, total_size); + header_buffer.extend(header_total_size); + + let mut result_buffer: Vec = Vec::new(); + result_buffer.extend(header_buffer); + result_buffer.extend(content_buffer); + result_buffer.extend(list_buffer); + + fs::write(output, result_buffer).unwrap(); +} diff --git a/unpacker/Cargo.toml b/unpacker/Cargo.toml new file mode 100644 index 0000000..adb64ec --- /dev/null +++ b/unpacker/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "unpacker" +version = "0.1.1" +edition = "2021" + +[dependencies] +byteorder = "1.4.3" +serde = { version = "1.0.160", features = ["derive"] } +serde_json = "1.0.96" diff --git a/unpacker/README.md b/unpacker/README.md new file mode 100644 index 0000000..e69de29 diff --git a/unpacker/src/main.rs b/unpacker/src/main.rs new file mode 100644 index 0000000..9d27385 --- /dev/null +++ b/unpacker/src/main.rs @@ -0,0 +1,124 @@ +use std::env; +use std::fs::File; +use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}; + +use byteorder::{ByteOrder, LittleEndian}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct FileHeader { + pub size: u32, + pub total: u32, + pub type1: u32, + pub type2: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ListElement { + pub extension: String, + pub index: u32, + pub name: String, + #[serde(skip_serializing)] + pub position: u32, + #[serde(skip_serializing)] + pub size: u32, + pub unknown0: u32, + pub unknown1: u32, + pub unknown2: u32, +} + +fn main() { + let args: Vec = env::args().collect(); + + let input = &args[1]; + let output = &args[2]; + + unpack(String::from(input), String::from(output)); +} + +fn unpack(input: String, output: String) { + let file = File::open(input).unwrap(); + let metadata = file.metadata().unwrap(); + + let mut reader = BufReader::new(file); + let mut list: Vec = Vec::new(); + + // Считываем заголовок файла + let mut header_buffer = [0u8; 16]; + reader.seek(SeekFrom::Start(0)).unwrap(); + reader.read_exact(&mut header_buffer).unwrap(); + + let file_header = FileHeader { + size: LittleEndian::read_u32(&header_buffer[12..16]), + total: LittleEndian::read_u32(&header_buffer[8..12]), + type1: LittleEndian::read_u32(&header_buffer[0..4]), + type2: LittleEndian::read_u32(&header_buffer[4..8]), + }; + + if file_header.type1 != 1936020046 || file_header.type2 != 256 { + panic!("this isn't NRes file"); + } + + if metadata.len() != file_header.size as u64 { + panic!("incorrect size") + } + + // Считываем список файлов + let list_files_start_position = file_header.size - (file_header.total * 64); + let list_files_size = file_header.total * 64; + + let mut list_buffer = vec![0u8; list_files_size as usize]; + reader + .seek(SeekFrom::Start(list_files_start_position as u64)) + .unwrap(); + reader.read_exact(&mut list_buffer).unwrap(); + + if list_buffer.len() % 64 != 0 { + panic!("invalid files list") + } + + for i in 0..(list_buffer.len() / 64) { + let from = i * 64; + let to = (i * 64) + 64; + let chunk: &[u8] = &list_buffer[from..to]; + + let element_list = ListElement { + extension: String::from_utf8_lossy(&chunk[0..4]) + .trim_matches(char::from(0)) + .to_string(), + index: LittleEndian::read_u32(&chunk[60..64]), + name: String::from_utf8_lossy(&chunk[20..56]) + .trim_matches(char::from(0)) + .to_string(), + position: LittleEndian::read_u32(&chunk[56..60]), + size: LittleEndian::read_u32(&chunk[12..16]), + unknown0: LittleEndian::read_u32(&chunk[4..8]), + unknown1: LittleEndian::read_u32(&chunk[8..12]), + unknown2: LittleEndian::read_u32(&chunk[16..20]), + }; + + list.push(element_list) + } + + // Распаковываем файлы в директорию + for element in &list { + let path = format!("{}/{}", output, element.name); + let mut file = File::create(path).unwrap(); + + let mut file_buffer = vec![0u8; element.size as usize]; + reader + .seek(SeekFrom::Start(element.position as u64)) + .unwrap(); + reader.read_exact(&mut file_buffer).unwrap(); + + file.write_all(&file_buffer).unwrap(); + file_buffer.clear(); + } + + // Выгрузка списка файлов в JSON + let path = format!("{}/{}", output, "index.json"); + let file = File::create(path).unwrap(); + let mut writer = BufWriter::new(file); + serde_json::to_writer_pretty(&mut writer, &list).unwrap(); + writer.flush().unwrap(); +}