Обновление структуры проекта
This commit is contained in:
9
tools/unpacker/Cargo.toml
Normal file
9
tools/unpacker/Cargo.toml
Normal file
@ -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"
|
41
tools/unpacker/README.md
Normal file
41
tools/unpacker/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# NRes Game Resource Unpacker
|
||||
|
||||
At the moment, this is a demonstration of the NRes game resource unpacking algorithm in action.
|
||||
It unpacks 100% of the NRes game resources for the game "Parkan: Iron Strategy".
|
||||
The unpacked resources can be packed again using the [packer](../packer) utility and replace the original game files.
|
||||
|
||||
__Attention!__
|
||||
This is a test version of the utility.
|
||||
It overwrites existing files without asking.
|
||||
|
||||
## Building
|
||||
|
||||
To build the tools, you need to run the following command in the root directory:
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
You can run the utility with the following command:
|
||||
|
||||
```bash
|
||||
./target/release/unpacker /path/to/file.ex /path/to/output
|
||||
```
|
||||
|
||||
- `/path/to/file.ex`: This is the file containing the game resources that will be unpacked.
|
||||
- `/path/to/output`: This is the directory where the unpacked files will be placed.
|
||||
|
||||
## How it Works
|
||||
|
||||
The structure describing the packed game resources is not fully understood yet.
|
||||
Therefore, the utility saves unpacked files in the format `file_name.file_index` because some files have the same name.
|
||||
|
||||
Additionally, an `index.json` file is created, which is important for re-packing the files.
|
||||
This file lists all the fields that game resources have in their packed form.
|
||||
It is essential to preserve the file index for the game to function correctly, as the game engine looks for the necessary files by index.
|
||||
|
||||
Files can be replaced and packed back using the [packer](../packer).
|
||||
The newly obtained game resource files are correctly processed by the game engine.
|
||||
For example, sounds and 3D models of warbots' weapons were successfully replaced.
|
124
tools/unpacker/src/main.rs
Normal file
124
tools/unpacker/src/main.rs
Normal file
@ -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<String> = 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<ListElement> = 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, element.index);
|
||||
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();
|
||||
}
|
Reference in New Issue
Block a user