This commit is contained in:
2024-04-21 16:02:50 +02:00
parent 2dea132d7f
commit 6fa0fbdb77
10 changed files with 601 additions and 10 deletions

View File

@@ -5,3 +5,6 @@ edition = "2021"
[dependencies]
binrw = "0.13"
modular-bitfield = "0.11.2"
clap = { version = "4.5.4", features = ["derive"] }
flate2 = "1.0.28"

View File

@@ -0,0 +1,2 @@
pub mod nut;
pub mod xmd;

172
nucompat/src/formats/nut.rs Normal file
View File

@@ -0,0 +1,172 @@
use binrw::{args, binrw, BinRead, BinResult};
use modular_bitfield::{bitfield, specifiers::B9};
#[binrw]
#[brw(little)]
#[derive(Debug)]
pub enum NuNut {
#[brw(magic = b"NTP3", big)]
NTP3(NuNutData),
#[brw(magic = b"NTWD", little)]
NTWD(NuNutData),
#[brw(magic = b"NTLX", little)]
NTLX(NuNutData),
}
#[binrw]
#[brw(repr = u32)]
#[derive(Debug)]
pub enum NuTextureType {
DDS = 0,
GXT = 1,
}
#[binrw]
#[brw(big, repr = u16)]
#[derive(Debug)]
pub enum NuPixelFormat {
DXT1 = 0x00,
DXT3 = 0x01,
DXT5 = 0x02,
RGB16 = 0x08,
RGBA16 = 0x0C,
RGBA = 0x0E,
ABGR = 0x0F,
RGBA2 = 0x10,
RGBA1 = 0x11,
RGBA0 = 0x15,
CompressedRgRGTC2 = 0x16,
}
#[binrw]
#[br(assert(version == 2, "Unsupported version {}", version))]
#[derive(Debug)]
pub struct NuNutData {
pub version: u16,
#[br(temp)]
#[bw(calc(textures.len() as u16))]
count: u16,
#[brw(align_before = 0x10)]
#[br(count = count)]
pub textures: Vec<NuNutTexture>,
#[brw(align_before = 0x10)]
#[br(parse_with = parse_surfaces, args(&textures))]
pub surfaces: Vec<Vec<u8>>,
}
#[binrw::parser(reader, endian)]
fn parse_surfaces(textures: &Vec<NuNutTexture>) -> BinResult<Vec<Vec<u8>>> {
let mut surfaces = Vec::with_capacity(textures.len());
for texture in textures {
surfaces.push(Vec::<u8>::read_options(
reader,
endian,
args! {count: texture.data_size as usize},
)?);
}
Ok(surfaces)
}
#[binrw]
#[br(assert(header_size == self.header_size()))]
#[br(assert(size == self.size()))]
#[derive(Debug)]
pub struct NuNutTexture {
#[br(temp)]
#[bw(calc(self.size()))]
pub size: u64,
pub data_size: u32, // TODO: dynamic writing
#[brw(align_after = 0x10)]
#[br(temp)]
#[bw(calc(self.header_size()))]
pub header_size: u16,
#[brw(big)]
#[br(temp, dbg)]
#[bw(calc(mipmap_sizes.len() as u16))]
pub mipmap_count: u16,
pub pixel_format: NuPixelFormat,
pub width: u16,
pub height: u16,
pub texture_type: NuTextureType,
pub cubemap: NuNutCubemap,
#[brw(align_before = 0x10)]
pub data_offset: u32, // TODO: dynamic writing
#[brw(if(cubemap.is_cubemap()), align_before = 0x10)]
pub cubemap_sizes: [u16; 4],
#[brw(align_before = 0x10)]
#[br(count = mipmap_count)]
pub mipmap_sizes: Vec<u32>, // TODO: dynamic writing
#[brw(align_before = 0x10)]
pub ext: NuNutExt,
#[brw(align_before = 0x10, align_after = 0x10)]
pub gidx: NuNutGidx,
}
impl NuNutTexture {
fn header_size(&self) -> u16 {
if self.cubemap.is_cubemap() {
0x70
} else {
0x60
}
}
fn size(&self) -> u64 {
self.data_size as u64 + self.header_size() as u64
}
}
#[binrw]
#[brw(magic = b"eXt\0")]
#[derive(Debug)]
pub struct NuNutExt {
pub version: u32,
pub version2: u32,
pub unknown: u32,
}
#[binrw]
#[brw(magic = b"GIDX")]
#[derive(Debug)]
pub struct NuNutGidx {
pub unknown2: u32,
pub hash_id: u32,
}
#[bitfield]
#[binrw]
#[br(map = Self::from_bytes)]
#[bw(map = |&x| Self::into_bytes(x))]
#[derive(Debug, Clone, Copy)]
pub struct NuNutCubemap {
#[skip]
__: B9,
pub x_plus: bool,
pub x_minus: bool,
pub y_plus: bool,
pub y_minus: bool,
pub z_plus: bool,
pub z_minus: bool,
pub is_cubemap: bool,
}
impl NuNutCubemap {
pub fn cubemap_count(&self) -> u8 {
self.x_plus() as u8
+ self.x_minus() as u8
+ self.y_plus() as u8
+ self.y_minus() as u8
+ self.z_plus() as u8
+ self.z_minus() as u8
}
pub fn surface_count(&self) -> u8 {
self.cubemap_count().min(1)
}
}
#[binrw]
#[derive(Debug)]
pub struct NuNutSurface {}

View File

@@ -0,0 +1,26 @@
use binrw::binrw;
#[binrw]
#[brw(repr = u32)]
#[derive(Debug)]
pub enum NuXmdListCounts {
LmbTexturesResources = 1,
PosLenId = 3,
}
#[binrw]
#[brw(little, magic = b"XMD\0001\0")]
#[derive(Debug)]
pub struct NuXmd {
pub layout: NuXmdListCounts,
pub count: u32,
#[br(count = count)]
#[brw(align_after = 0x10)]
pub positions: Vec<u32>,
#[br(count = count)]
#[brw(align_after = 0x10)]
pub lengths: Vec<u32>,
#[br(count = count)]
#[brw(align_after = 0x10)]
pub ids: Vec<u32>,
}

View File

@@ -0,0 +1,55 @@
use std::io::{BufReader, Cursor, Read, Seek};
use binrw::{
binrw,
meta::{EndianKind, ReadEndian},
BinRead, BinResult, Endian,
};
use flate2::bufread::GzDecoder;
use formats::{nut::NuNut, xmd::NuXmd};
pub mod formats;
#[derive(Debug)]
pub enum NuFile {
Plain(Nu),
Gz(Nu),
}
impl ReadEndian for NuFile {
const ENDIAN: EndianKind = EndianKind::Endian(Endian::Little);
}
impl BinRead for NuFile {
type Args<'a> = ();
fn read_options<R: Read + Seek>(
reader: &mut R,
endian: binrw::Endian,
args: Self::Args<'_>,
) -> BinResult<Self> {
let mut signature = [0u8; 2];
reader.read_exact(&mut signature)?;
reader.seek(std::io::SeekFrom::Start(0))?;
if signature == [0x1F, 0x8B] {
let mut reader = BufReader::new(reader);
let mut decoder = GzDecoder::new(&mut reader);
let mut buf = Vec::new();
decoder.read_to_end(&mut buf)?;
Ok(NuFile::Gz(Nu::read_options(
&mut Cursor::new(buf),
endian,
args,
)?))
} else {
Ok(NuFile::Plain(Nu::read_options(reader, endian, args)?))
}
}
}
#[binrw]
#[derive(Debug)]
pub enum Nu {
Nut(NuNut),
Xmd(NuXmd),
}

View File

@@ -1 +1,20 @@
fn main() {}
use binrw::BinRead;
use clap::{command, Parser};
use nucompat::NuFile;
use std::fs::File;
#[derive(Parser)]
#[command(version, about)]
struct Args {
#[clap(short, long)]
path: std::path::PathBuf,
}
fn main() {
let args = Args::parse();
let file = File::open(&args.path).expect("Failed to open file");
let mut reader = std::io::BufReader::new(file);
let file = NuFile::read(&mut reader).unwrap();
println!("{:#?}", file);
}