Merge pull request 'rework' (#1) from rework into wizeman
Reviewed-on: #1
This commit is contained in:
commit
b48f1bc11d
|
@ -1,2 +1,6 @@
|
|||
/target
|
||||
/.idea
|
||||
/Cargo.lock
|
||||
**/*.rs.bk
|
||||
*.one
|
||||
*.one.d
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "prs-rust"]
|
||||
path = prs-rust
|
||||
url = ssh://git@vvn.space:2222/lifning/prs-rust.git
|
|
@ -1,4 +0,0 @@
|
|||
[[package]]
|
||||
name = "one-rust"
|
||||
version = "0.1.0"
|
||||
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,8 +1,2 @@
|
|||
[package]
|
||||
name = "one-rust"
|
||||
version = "0.1.0"
|
||||
authors = ["lifning <lifning+git@pm.me>"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1"
|
||||
prs-rust = { path = "../prs-rust" }
|
||||
[workspace]
|
||||
members = ["one-rust", "prs-rust"]
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "one-rust"
|
||||
version = "0.1.0"
|
||||
authors = ["lifning <lifning+git@pm.me>"]
|
||||
|
||||
[dependencies]
|
||||
argh = "0.1"
|
||||
binread = "1"
|
||||
binwrite = "0.2"
|
||||
log = "0.4"
|
||||
prs-rust = { path = "../prs-rust" }
|
|
@ -0,0 +1,98 @@
|
|||
#[macro_use] extern crate argh;
|
||||
extern crate binread;
|
||||
extern crate one_rust;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use binread::prelude::*;
|
||||
use one_rust::JodOne;
|
||||
|
||||
/// Unpack .one archives from NiGHTS: Journey of Dreams
|
||||
#[derive(FromArgs)]
|
||||
struct Args {
|
||||
#[argh(subcommand)]
|
||||
action: Action,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand)]
|
||||
enum Action {
|
||||
ExtractFile(ExtractFile),
|
||||
CreateFile(CreateFile),
|
||||
DescribeFile(DescribeFile),
|
||||
}
|
||||
|
||||
/// Unpack and decompress the files inside a .one archive.
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "xf")]
|
||||
struct ExtractFile {
|
||||
/// path to the .one file
|
||||
#[argh(positional)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
/// Describe the metadata of a .one archive.
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "tf")]
|
||||
struct DescribeFile {
|
||||
/// path to the .one file
|
||||
#[argh(positional)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
/// Create a .one archive from the given files.
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "cf")]
|
||||
struct CreateFile {
|
||||
/// path of the .one file to create
|
||||
#[argh(positional)]
|
||||
file: PathBuf,
|
||||
/// paths of the files to pack into the archive
|
||||
#[argh(positional)]
|
||||
contents: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Args = argh::from_env();
|
||||
match args.action {
|
||||
Action::ExtractFile(subcmd) => {
|
||||
let mut infile = File::open(&subcmd.file)?;
|
||||
let archive: JodOne = infile.read_le()?;
|
||||
let entries = archive.unpack();
|
||||
let dir = PathBuf::from(format!("{}.d", subcmd.file.to_string_lossy()));
|
||||
for (name, data) in entries {
|
||||
let full_path = dir.join(name);
|
||||
if let Some(path) = full_path.parent() {
|
||||
std::fs::create_dir_all(path)?;
|
||||
}
|
||||
File::create(full_path)?.write_all(&data)?;
|
||||
}
|
||||
}
|
||||
Action::DescribeFile(subcmd) => {
|
||||
let mut infile = File::open(&subcmd.file)?;
|
||||
let archive: JodOne = infile.read_le()?;
|
||||
println!("{:?}", archive);
|
||||
for entry in archive.entries {
|
||||
println!("{:?}", entry);
|
||||
}
|
||||
}
|
||||
Action::CreateFile(subcmd) => {
|
||||
if subcmd.file.exists() {
|
||||
return Err(format!("{:?} already exists, refusing to overwrite", subcmd.file).into());
|
||||
}
|
||||
|
||||
let mut entries = Vec::new();
|
||||
for path in subcmd.contents {
|
||||
let mut data = Vec::new();
|
||||
File::open(&path)?.read_to_end(&mut data)?;
|
||||
entries.push((path, data));
|
||||
}
|
||||
|
||||
let archive = JodOne::pack(entries, None);
|
||||
let outfile = File::create(&subcmd.file)?;
|
||||
archive.write(outfile)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
//#![feature(fixed_size_array)]
|
||||
#[macro_use] extern crate binread;
|
||||
extern crate binwrite;
|
||||
#[macro_use] extern crate log;
|
||||
extern crate prs_rust;
|
||||
|
||||
mod one;
|
||||
pub use one::*;
|
|
@ -0,0 +1,159 @@
|
|||
use binread::{FilePtr, NullString};
|
||||
use prs_rust::prs;
|
||||
use std::fmt::Formatter;
|
||||
use std::path::PathBuf;
|
||||
use std::mem::size_of;
|
||||
use std::io::{Write, Seek};
|
||||
use binwrite::BinWrite;
|
||||
|
||||
fn string_binread_helper(x: NullString) -> String {
|
||||
x.into_string()
|
||||
}
|
||||
|
||||
fn string_binwrite_helper(pad_to_len: usize) -> impl Fn(&String) -> Vec<u8> {
|
||||
move |x: &String| {
|
||||
let mut v = x.as_bytes().to_vec();
|
||||
v.resize(pad_to_len, 0);
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
// stats on _unknown1, _unknown2, and category:
|
||||
// $ find . -name \*.one -exec bash -c "onear tf "{}" > "{}".txt" \;
|
||||
// $ fd one.txt | xargs cat | sed 's/count: .*/..}/' | grep '^JodOne{' | sort | uniq -c
|
||||
// 1 JodOne{_unknown1: 0xca, _unknown2: " ", category: Default, ..}
|
||||
// 11 JodOne{_unknown1: 0xca, _unknown2: " ", category: landData, ..}
|
||||
// 4 JodOne{_unknown1: 0xcb, _unknown2: " ", category: landData, ..}
|
||||
// 988 JodOne{_unknown1: 0xcc, _unknown2: " ", category: Default, ..}
|
||||
// 1579 JodOne{_unknown1: 0xcc, _unknown2: " ", category: landData, ..}
|
||||
|
||||
const JOD_ONE_MAGIC: &'static [u8; 16] = b"ThisIsOneFile\0\0\0";
|
||||
|
||||
const SIZE_OF_JOD_ONE_HEADER: usize = JOD_ONE_MAGIC.len() + 2 * size_of::<u32>() + 32 + 132;
|
||||
const SIZE_OF_JOD_ONE_ENTRY: usize = 6 * size_of::<u32>() + 192;
|
||||
|
||||
#[derive(BinRead)]
|
||||
#[derive(BinWrite)]
|
||||
#[br(little, magic = b"ThisIsOneFile\0\0\0")]
|
||||
#[binwrite(little)]
|
||||
pub struct JodOne {
|
||||
// usually 0xCC... archive creator version maybe?
|
||||
// but i've seen 0xCA in Test04/disp/Test04.one and 0xCB in Test05/disp/stg2010_sun.one
|
||||
_unknown1: u32,
|
||||
// "Default" or "landData"
|
||||
#[br(pad_size_to = 32, map = string_binread_helper)]
|
||||
#[binwrite(preprocessor(string_binwrite_helper(32)))]
|
||||
pub category: String,
|
||||
pub count: u32,
|
||||
// always " "
|
||||
#[br(pad_size_to = 132, map = string_binread_helper)]
|
||||
#[binwrite(preprocessor(string_binwrite_helper(132)))]
|
||||
_unknown2: String,
|
||||
#[br(count = count)]
|
||||
pub entries: Vec<JodOneEntry>
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
#[derive(BinWrite)]
|
||||
#[br(little, assert(id_again == id))]
|
||||
#[binwrite(little)]
|
||||
pub struct JodOneEntry {
|
||||
pub id: u32,
|
||||
pub size_cmp: u32,
|
||||
pub is_compressed: u32,
|
||||
pub size_dec: u32,
|
||||
pub id_again: u32,
|
||||
#[br(count = size_cmp)]
|
||||
#[binwrite(preprocessor(|x: &FilePtr<u32, Vec<u8>>| x.ptr))]
|
||||
pub data: FilePtr<u32, Vec<u8>>,
|
||||
#[br(pad_size_to = 192, map = string_binread_helper)]
|
||||
#[binwrite(preprocessor(string_binwrite_helper(192)))]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for JodOne {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "JodOne{{_unknown1: 0x{:x}, _unknown2: {:?}, category: {}, count: {}, ..}}", self._unknown1, self._unknown2, self.category, self.count)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for JodOneEntry {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"JodOneEntry{{id: {}, is_compressed: {}, name: {:?}, size_cmp: {}, size_dec: {}, ..}}",
|
||||
self.id, self.is_compressed != 0, self.name.to_string(), self.size_cmp, self.size_dec,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl JodOne {
|
||||
pub fn write(&self, mut writer: impl Write + Seek) -> std::io::Result<()> {
|
||||
writer.write(JOD_ONE_MAGIC)?;
|
||||
BinWrite::write(&self, &mut writer)?;
|
||||
for entry in &self.entries {
|
||||
let loc = writer.seek(std::io::SeekFrom::Current(0)).unwrap() as usize;
|
||||
let padding = vec![0; entry.data.ptr as usize - loc];
|
||||
writer.write(&padding)?;
|
||||
writer.write(entry.data.value.as_ref().unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pack(inputs: impl IntoIterator<Item = (PathBuf, Vec<u8>)>, category: Option<&str>) -> Self {
|
||||
let mut entries = Vec::new();
|
||||
for (id, (path, data)) in inputs.into_iter().enumerate() {
|
||||
let cmp = prs::Compressor::new(&data, None).compress();
|
||||
let name = path.to_string_lossy().replace(std::path::MAIN_SEPARATOR, "\\");
|
||||
if cmp.len() < data.len() {
|
||||
entries.push(JodOneEntry {
|
||||
id: id as u32,
|
||||
size_cmp: cmp.len() as u32,
|
||||
is_compressed: 1,
|
||||
size_dec: data.len() as u32,
|
||||
id_again: id as u32,
|
||||
data: FilePtr { ptr: 0, value: Some(cmp) },
|
||||
name,
|
||||
});
|
||||
} else {
|
||||
entries.push(JodOneEntry {
|
||||
id: id as u32,
|
||||
size_cmp: data.len() as u32,
|
||||
is_compressed: 0,
|
||||
size_dec: 0,
|
||||
id_again: id as u32,
|
||||
data: FilePtr { ptr: 0, value: Some(data) },
|
||||
name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut loc = (SIZE_OF_JOD_ONE_HEADER + SIZE_OF_JOD_ONE_ENTRY * entries.len()) as u32;
|
||||
for entry in &mut entries {
|
||||
loc = ((loc + 15) / 16) * 16; // round up to 16 byte alignment
|
||||
entry.data.ptr = loc;
|
||||
loc += entry.data.value.as_ref().unwrap().len() as u32;
|
||||
}
|
||||
|
||||
JodOne {
|
||||
_unknown1: 0xCC,
|
||||
category: category.unwrap_or("Default").to_string(),
|
||||
count: entries.len() as u32,
|
||||
_unknown2: " ".to_string(),
|
||||
entries,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unpack(self) -> impl Iterator<Item = (PathBuf, Vec<u8>)> {
|
||||
self.entries.into_iter().map(|entry| {
|
||||
debug!("{:?}", entry);
|
||||
let key = PathBuf::from(entry.name.replace('\\', std::path::MAIN_SEPARATOR.encode_utf8(&mut [0u8; 4])));
|
||||
if entry.is_compressed == 0 || entry.size_dec == 0 {
|
||||
(key, entry.data.into_inner())
|
||||
} else {
|
||||
let dec_buf = prs::Decompressor::new(entry.data.into_inner(), Some(entry.size_dec as usize)).decompress();
|
||||
(key, dec_buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c2757ca1ba88ea51b386d1f48af0b9fa01829cc7
|
|
@ -1,8 +0,0 @@
|
|||
use std::fs::File;
|
||||
|
||||
pub mod one;
|
||||
|
||||
fn main() {
|
||||
let mut infile = File::open("foo.one").unwrap();
|
||||
one::OneJod::from_archive(&mut infile).extract();
|
||||
}
|
142
src/one/mod.rs
142
src/one/mod.rs
|
@ -1,142 +0,0 @@
|
|||
extern crate byteorder;
|
||||
extern crate prs_rust;
|
||||
|
||||
use self::byteorder::{ReadBytesExt, WriteBytesExt, BigEndian, LittleEndian};
|
||||
use self::prs_rust::prs;
|
||||
use std::str;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
pub struct OneJod<R: Read + Seek> {
|
||||
read_handle: R,
|
||||
header: OneJodHead,
|
||||
entries: Vec<OneJodEntry>,
|
||||
}
|
||||
|
||||
impl<R> OneJod<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
pub fn from_archive(mut input: R) -> OneJod<R> {
|
||||
let mut head = OneJodHead {
|
||||
magic: [0; 16],
|
||||
_unknown1: 0,
|
||||
category: [0; 32],
|
||||
file_count: 0,
|
||||
reserved: [0; 128],
|
||||
};
|
||||
input.read_exact(&mut head.magic);
|
||||
assert_eq!(head.magic(), b"ThisIsOneFile\0\0\0");
|
||||
head._unknown1 = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
input.read_exact(&mut head.category);
|
||||
head.file_count = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
input.read_exact(&mut head.reserved);
|
||||
|
||||
let mut entries = Vec::new();
|
||||
for i in 0..head.file_count() {
|
||||
let mut entry = OneJodEntry {
|
||||
_unknown1: 0,
|
||||
id: 0,
|
||||
size_cmp: 0,
|
||||
_unknown2: 0,
|
||||
size_dec: 0,
|
||||
id_again: 0,
|
||||
offset: 0,
|
||||
file_name: [0; 188],
|
||||
};
|
||||
entry._unknown1 = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
entry.id = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
entry.size_cmp = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
entry._unknown2 = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
entry.size_dec = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
entry.id_again = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
entry.offset = input.read_u32::<byteorder::LittleEndian>().unwrap();
|
||||
input.read_exact(&mut entry.file_name);
|
||||
assert_eq!(entry.id(), i);
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
OneJod{
|
||||
read_handle: input,
|
||||
header: head,
|
||||
entries,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract(&mut self) {
|
||||
for entry in &self.entries {
|
||||
let mut cmp_buf = vec![0u8; entry.size_cmp() as usize];
|
||||
self.read_handle.seek(SeekFrom::Start(entry.offset() as u64));
|
||||
self.read_handle.read_exact(cmp_buf.as_mut_slice());
|
||||
let mut dec_buf = prs::decompress::Decompressor::new(&cmp_buf).decompress();
|
||||
println!("{}", entry.file_name());
|
||||
let mut file_out = File::create(entry.file_name()).unwrap();
|
||||
file_out.write_all(&dec_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OneJodHead {
|
||||
magic: [u8; 16],
|
||||
_unknown1: u32,
|
||||
category: [u8; 32],
|
||||
file_count: u32,
|
||||
reserved: [u8; 128],
|
||||
}
|
||||
|
||||
impl OneJodHead {
|
||||
fn magic(&self) -> &[u8] { &self.magic }
|
||||
fn magic_mut(&mut self) -> &mut [u8] { &mut self.magic }
|
||||
|
||||
fn _unknown1(&self) -> u32 { self._unknown1 }
|
||||
fn set_unknown1(&mut self, n: u32) { self._unknown1 = n; }
|
||||
|
||||
// NB: may not really be utf8? should probably be ascii, though...
|
||||
fn category(&self) -> &str { str::from_utf8(&self.category).unwrap() }
|
||||
fn category_mut(&mut self) -> &mut str { str::from_utf8_mut(&mut self.category).unwrap() }
|
||||
fn set_category(&mut self, n: &str) { self.category.copy_from_slice(n.as_bytes()); }
|
||||
|
||||
fn file_count(&self) -> u32 { self.file_count }
|
||||
fn set_file_count(&mut self, n: u32) { self.file_count = n; }
|
||||
|
||||
fn reserved(&self) -> &[u8] { &self.reserved }
|
||||
fn reserved_mut(&mut self) -> &mut [u8] { &mut self.reserved }
|
||||
}
|
||||
|
||||
struct OneJodEntry {
|
||||
_unknown1: u32,
|
||||
id: u32,
|
||||
size_cmp: u32,
|
||||
_unknown2: u32,
|
||||
size_dec: u32,
|
||||
id_again: u32,
|
||||
offset: u32,
|
||||
file_name: [u8; 188],
|
||||
}
|
||||
|
||||
impl OneJodEntry {
|
||||
fn _unknown1(&self) -> u32 { self._unknown1 }
|
||||
fn set_unknown1(&mut self, n: u32) { self._unknown1 = n; }
|
||||
|
||||
fn id(&self) -> u32 { assert_eq!(self.id, self.id_again); self.id }
|
||||
fn set_id(&mut self, n: u32) { self.id = n; self.id_again = n; }
|
||||
|
||||
fn size_cmp(&self) -> u32 { self.size_cmp }
|
||||
fn set_size_cmp(&mut self, n: u32) { self.size_cmp = n; }
|
||||
|
||||
fn _unknown2(&self) -> u32 { self._unknown2 }
|
||||
fn set_unknown2(&mut self, n: u32) { self._unknown2 = n; }
|
||||
|
||||
fn size_dec(&self) -> u32 { self.size_dec }
|
||||
fn set_size_dec(&mut self, n: u32) { self.size_dec = n; }
|
||||
|
||||
fn offset(&self) -> u32 { self.offset }
|
||||
fn set_offset(&mut self, n: u32) { self.offset = n; }
|
||||
|
||||
fn file_name(&self) -> &str {
|
||||
let idx = self.file_name.iter().position(|x| *x == 0u8).unwrap();
|
||||
str::from_utf8(&self.file_name[0..idx]).unwrap()
|
||||
}
|
||||
fn file_name_mut(&mut self) -> &mut str { str::from_utf8_mut(&mut self.file_name).unwrap() }
|
||||
fn set_file_name(&mut self, n: &str) { self.file_name.copy_from_slice(n.as_bytes()); }
|
||||
}
|
Loading…
Reference in New Issue