diff --git a/Cargo.toml b/Cargo.toml index 7ef5102..adf5acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,6 @@ name = "projtest" [[example]] name = "projload" + +[[example]] +name = "animtest" diff --git a/README.md b/README.md index 4e7103a..c937736 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ Rust library and tool for opening various graphics-related Nintendo DS files ## Credits * Contributors: me! (patataofcourse) * Documentation on file formats: - - [This](https://www.romhacking.net/documents/%5b469%5dnds_formats.htm) document in romhacking.net + - [This](https://www.romhacking.net/documents/469/) document in romhacking.net - [Tinke](https://www.github.com/pleonex/tinke) source code + - [NitroPaint](https://github.com/Garhoogin/NitroPaint) source code - [gbatek](https://problemkaputt.de/gbatek.htm) * ThePurpleAnon for the very cool name idea \ No newline at end of file diff --git a/examples/animtest.rs b/examples/animtest.rs new file mode 100644 index 0000000..b2dbc93 --- /dev/null +++ b/examples/animtest.rs @@ -0,0 +1,14 @@ +use std::{fs::File, io::Read}; + +use nuclear::{img::ncer::NCER, ndsfile::NDSFileType}; + +pub fn main() -> nuclear::Result<()> { + let mut ncer = ::from_file( + "test_files/rocker.NCER", + &mut File::open("test_files/rocker.NCER")?, + )?; + + println!("{:?}", ncer); + + Ok(()) +} diff --git a/examples/projtest.rs b/examples/projtest.rs index 0d31593..6ded9c8 100644 --- a/examples/projtest.rs +++ b/examples/projtest.rs @@ -11,28 +11,23 @@ fn main() -> nuclear::error::Result<()> { )?; let mut f = File::open("test_files/rocker_bg.NCLR")?; - let nds = nuclear::ndsfile::NDSFile::from_file("rocker_bg.NCLR", &mut f)?; - let clr = nuclear::img::NCLR::from_ndsfile(&nds)?; + let clr = nuclear::img::NCLR::from_file("rocker_bg.NCLR", &mut f)?; let mut f = File::open("test_files/rocker_bg.NCGR")?; - let nds = nuclear::ndsfile::NDSFile::from_file("rocker_bg.NCGR", &mut f)?; - let cgr = nuclear::img::NCGR::from_ndsfile(&nds)?; + let cgr = nuclear::img::NCGR::from_file("rocker_bg.NCGR", &mut f)?; let mut f = File::open("test_files/rocker_bg.NSCR")?; - let nds = nuclear::ndsfile::NDSFile::from_file("rocker_bg.NSCR", &mut f)?; - let scr = nuclear::img::NSCR::from_ndsfile(&nds)?; + let scr = nuclear::img::NSCR::from_file("rocker_bg.NSCR", &mut f)?; proj.insert_nclr("rocker_bg", &clr)?; proj.insert_ncgr("rocker_bg", &cgr)?; proj.insert_nscr("rocker_bg", &scr)?; let mut f = File::open("test_files/rocker.NCLR")?; - let nds = nuclear::ndsfile::NDSFile::from_file("rocker.NCLR", &mut f)?; - let clr = nuclear::img::NCLR::from_ndsfile(&nds)?; + let clr = nuclear::img::NCLR::from_file("rocker.NCLR", &mut f)?; let mut f = File::open("test_files/rocker.NCBR")?; - let nds = nuclear::ndsfile::NDSFile::from_file("rocker.NCBR", &mut f)?; - let cgr = nuclear::img::NCGR::from_ndsfile(&nds)?; + let cgr = nuclear::img::NCGR::from_file("rocker.NCBR", &mut f)?; proj.insert_nclr("rocker", &clr)?; proj.insert_ncgr("rocker", &cgr)?; diff --git a/examples/test.rs b/examples/test.rs index 18febe5..bcc7c52 100644 --- a/examples/test.rs +++ b/examples/test.rs @@ -2,7 +2,7 @@ use bytestream::ByteOrder; use nuclear::ndsfile::NDSFileType; use std::fs::File; -const FOLDER_NAME: &str = "ver2"; +const FOLDER_NAME: &str = "."; const NAME: &str = "rocker"; const TILES_EXTENSION: &str = "NCBR"; const NSCR: bool = false; @@ -10,31 +10,28 @@ const NSCR: bool = false; fn main() -> nuclear::error::Result<()> { // Open NCLR file let mut f = File::open(format!("test_files/{}/{}.NCLR", FOLDER_NAME, NAME))?; - let nds = nuclear::ndsfile::NDSFile::from_file(&format!("{}.NCLR", NAME), &mut f)?; // Export NCLR to palette set - let clr = nuclear::img::nclr::NCLR::from_ndsfile(&nds)?; + let clr = nuclear::img::nclr::NCLR::from_file(&format!("{}.NCLR", NAME), &mut f)?; nuclear::img::export::export_palettes( &clr, format!("test_files/out/{}/{}_pal", FOLDER_NAME, NAME).into(), )?; // Re-export NCLR file - let nds = clr.to_ndsfile(format!("{}.NCLR", NAME), ByteOrder::LittleEndian)?; let mut f_w = File::create(format!("test_files/out/{}/{}.NCLR", FOLDER_NAME, NAME))?; - nds.to_file(&mut f_w)?; + clr.to_file(&mut f_w, format!("{}.NCLR", NAME), ByteOrder::LittleEndian)?; // Open NCGR/NCBR file let mut f = File::open(format!( "test_files/{}/{}.{}", FOLDER_NAME, NAME, TILES_EXTENSION ))?; - let nds = - nuclear::ndsfile::NDSFile::from_file(&format!("{}.{}", NAME, TILES_EXTENSION), &mut f)?; // Export NCGR to tilesheet let f_w = &mut File::create(format!("test_files/out/{}/{}.tiles.png", FOLDER_NAME, NAME))?; - let cgr = nuclear::img::ncgr::NCGR::from_ndsfile(&nds)?; + let cgr = + nuclear::img::ncgr::NCGR::from_file(&format!("{}.{}", NAME, TILES_EXTENSION), &mut f)?; nuclear::img::export::export_tilesheet(f_w, &clr.palettes[&0], &cgr, 32, false)?; // Re-export NCGR file @@ -51,10 +48,9 @@ fn main() -> nuclear::error::Result<()> { if NSCR { // Open NSCR file let mut f = File::open(format!("test_files/{}/{}.NSCR", FOLDER_NAME, NAME))?; - let nds = nuclear::ndsfile::NDSFile::from_file(&format!("{}.NSCR", NAME), &mut f)?; + let scr = nuclear::img::nscr::NSCR::from_file(&format!("{}.NSCR", NAME), &mut f)?; // Export NSCR to image let f_w = &mut File::create(format!("test_files/out/{}/{}.png", FOLDER_NAME, NAME))?; - let scr = nuclear::img::nscr::NSCR::from_ndsfile(&nds)?; nuclear::img::export::export_tilemap(f_w, &clr, &cgr, &scr)?; // Re-export NSCR file let nds = scr.to_ndsfile(format!("{}.NSCR", NAME), ByteOrder::LittleEndian)?; diff --git a/src/error.rs b/src/error.rs index bfbc17b..ed3d8ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,6 +43,10 @@ pub enum Error { #[error("Error when reading {0}: {1}")] FileFormatWrong(PathBuf, String), + /// Unimplemented feature + #[error("Unimplemented feature: {0}")] + UnimplementedFeature(String), + // // Wrappers // diff --git a/src/img/mod.rs b/src/img/mod.rs index e151c2a..9aa9550 100644 --- a/src/img/mod.rs +++ b/src/img/mod.rs @@ -5,8 +5,8 @@ pub mod ncgr; pub mod nclr; pub mod nscr; -//pub mod nanr; -//pub mod ncer; +pub mod nanr; +pub mod ncer; /// Only kept for the examples, renders different formats to .png pub mod export; diff --git a/src/img/ncer.rs b/src/img/ncer.rs index bf6a370..3d109e9 100644 --- a/src/img/ncer.rs +++ b/src/img/ncer.rs @@ -1,59 +1,85 @@ +use std::io::{Cursor, Read, Seek, SeekFrom}; + use crate::{ error::{Error, Result}, - ndsfile::NDSFile, + ndsfile::{NDSFile, NDSFileType}, }; +use bytestream::StreamReader; #[derive(Debug, Clone)] pub struct NCER { - pub sprites: Vec>, + pub cells: Vec, + pub mapping_mode: u32, pub bounds: u32, + pub labels: Vec, + pub uext: Vec, +} + +#[derive(Debug, Clone)] +pub struct NCERCell { + pub objects: Vec, + pub bounding_box: Option, + pub attrs: u16, +} + +#[derive(Debug, Clone)] +pub struct CellBounds { + pub min_x: u16, + pub max_x: u16, + pub min_y: u16, + pub max_y: u16, } #[derive(Debug, Clone)] -pub enum NCERCell { - Packed([u8; 3]), - RotScaleEnabled(RotScaleCell), - RotScaleDisabled(CellInternals), +pub enum OAMObject { + RotScaleDisabled(ObjectInternals), + RotScaleEnabled(RotScaleObject), } -// most likely not used in NCER due to the rotation/scaling attribute missing +// TODO: is this even possible with NCER??? delete if not #[derive(Debug, Clone)] -pub struct RotScaleCell { +pub struct RotScaleObject { _do_not_construct: (), } #[derive(Debug, Clone)] -pub struct CellInternals { - pub x_coord: u8, - pub y_coord: u8, +pub struct ObjectInternals { + pub tile: u16, // 10-bit + pub palette: u8, // 4-bit, unused in 256-color + pub is_8_bit: bool, + + pub pos_x: u16, // 9-bit + pub pos_y: u8, + pub shape: ObjectShape, // 2-bit + 2-bit + pub flip_x: bool, + pub flip_y: bool, + + pub priority: u8, // 2-bit pub disable: bool, pub mode: u8, // 2-bit pub mosaic: bool, - pub is_8_bit: bool, - pub shape: CellShape, // 2-bit + 2-bit + pub unused_attr1: u8, // 3-bit - pub flip_x: bool, - pub flip_y: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CellShape { - Cell8x8, - Cell16x8, - Cell8x16, - Cell16x16, - Cell32x8, - Cell8x32, - Cell32x32, - Cell32x16, - Cell16x32, - Cell64x64, - Cell64x32, - Cell32x64, +pub enum ObjectShape { + Object8x8, + Object16x8, + Object8x16, + Object16x16, + Object32x8, + Object8x32, + Object32x32, + Object32x16, + Object16x32, + Object64x64, + Object64x32, + Object32x64, } -impl NCER { - pub fn from_ndsfile(file: &NDSFile) -> Result { +impl NDSFileType for NCER { + fn from_ndsfile(file: &NDSFile) -> Result { if file.magic != "RECN" { Err(Error::WrongFileKind { file: file.fname.to_string(), @@ -64,19 +90,106 @@ impl NCER { } //let mut sprites = vec![]; - let mut bounds = 0; + let o = file.byteorder; + let mut labels = vec![]; + let mut uext = vec![]; + let mut cells = vec![]; for section in &file.sections { - let mut data: &[u8] = §ion.contents; + let mut data = Cursor::new(section.contents.as_slice()); match section.magic.as_ref() { "KBEC" => { - todo!("CEBK section") + let num_cells = u16::read_from(&mut data, o)?; + let has_bounding_box = u16::read_from(&mut data, o)? & 1 == 1; + let cell_data_offset = u32::read_from(&mut data, o)?; + let mapping_mode = u32::read_from(&mut data, o)?; + let dma_data_offset = u32::read_from(&mut data, o)?; + u32::read_from(&mut data, o)?; // unused + let ucat_data_offset = u32::read_from(&mut data, o)?; + + let oam_data_offset = cell_data_offset + + (num_cells as u32 * (if has_bounding_box { 16 } else { 8 })); + + data.seek(SeekFrom::Start(cell_data_offset as u64))?; + + for _ in 0..num_cells { + let num_oams = u16::read_from(&mut data, o)?; + let cell_attrs = u16::read_from(&mut data, o)?; + let objects_offset = u32::read_from(&mut data, o)?; + + let bounding_box = if has_bounding_box { + Some(CellBounds { + max_x: u16::read_from(&mut data, o)?, + max_y: u16::read_from(&mut data, o)?, + min_x: u16::read_from(&mut data, o)?, + min_y: u16::read_from(&mut data, o)?, + }) + } else { + None + }; + + let cur_pos = data.stream_position()?; + + data.seek(SeekFrom::Start((oam_data_offset + objects_offset) as u64))?; + + let mut objects = vec![]; + + for _j in 0..num_oams { + let oam = [ + u16::read_from(&mut data, o)?, + u16::read_from(&mut data, o)?, + u16::read_from(&mut data, o)?, + ]; + + let object = ObjectInternals::from_attributes(&oam)?; + objects.push(OAMObject::RotScaleDisabled(object)); + } + + data.seek(SeekFrom::Start(cur_pos))?; + + cells.push(NCERCell { + objects, + bounding_box, + attrs: cell_attrs, + }); + } + + todo!("CEBK section: dma, ucat") } "LBAL" => { - todo!("LABL section") + let mut offsets = vec![]; + let mut ptr = u32::read_from(&mut data, o)?; + + while ptr <= 0xFFFF { + offsets.push(ptr); + ptr = u32::read_from(&mut data, o)?; + } + + data.seek(SeekFrom::Current(-4))?; + + let pos = data.stream_position()?; + + for offset in offsets { + data.seek(SeekFrom::Start(pos + offset as u64))?; + + let mut string = String::new(); + + loop { + let c = u8::read_from(&mut data, o)?; + if c == 0 { + break; + } + + string.push(char::from_u32(c.into()).unwrap()); + } + + labels.push(string); + } + + println!("{:?}", labels); } "TXEU" => { - todo!("UEXT section") + data.read_to_end(&mut uext)?; } c => Err(Error::UnknownSection { file: file.fname.clone(), @@ -87,24 +200,28 @@ impl NCER { todo!(); } + + fn to_ndsfile(&self, fname: String, order: bytestream::ByteOrder) -> Result { + todo!() + } } -impl CellShape { +impl ObjectShape { pub fn new(shape: u8, size: u8) -> Result { - use CellShape::*; + use ObjectShape::*; Ok(match (shape, size) { - (0, 0) => Cell8x8, - (1, 0) => Cell16x8, - (2, 0) => Cell8x16, - (0, 1) => Cell16x16, - (1, 1) => Cell32x8, - (2, 1) => Cell8x32, - (0, 2) => Cell32x32, - (1, 2) => Cell32x16, - (2, 2) => Cell16x32, - (0, 3) => Cell32x32, - (1, 3) => Cell32x16, - (2, 3) => Cell16x32, + (0, 0) => Object8x8, + (1, 0) => Object16x8, + (2, 0) => Object8x16, + (0, 1) => Object16x16, + (1, 1) => Object32x8, + (2, 1) => Object8x32, + (0, 2) => Object32x32, + (1, 2) => Object32x16, + (2, 2) => Object16x32, + (0, 3) => Object32x32, + (1, 3) => Object32x16, + (2, 3) => Object16x32, _ => Err(Error::Generic(format!( "Invalid values for CellFullShape: shape={}, size={}", shape, size @@ -112,3 +229,53 @@ impl CellShape { }) } } + +impl ObjectInternals { + pub fn from_attributes(attr: &[u16; 3]) -> Result { + if attr[0] & 0x100 != 0 { + return Err(Error::UnimplementedFeature( + "Rot/Scale OAM attributes".to_string(), + )); + } + + // attr 0 + let pos_y = attr[0] as u8; + let disable = attr[0] & 0x200 != 0; + let mode = ((attr[0] >> 10) & 3) as u8; + let mosaic = attr[0] & 0x1000 != 0; + let is_8_bit = attr[0] & 0x2000 != 0; + let shape = (attr[0] >> 14) as u8; + + // attr 1 + let pos_x = attr[1] & 0x1FF; + let unused = ((attr[1] >> 9) & 7) as u8; + let flip_x = attr[1] & 0x1000 != 0; + let flip_y = attr[1] & 0x2000 != 0; + let size = (attr[1] >> 14) as u8; + + // attr 2 + let tile = attr[2] & 0x3FF; + let priority = ((attr[2] >> 10) & 3) as u8; + let palette = (attr[2] >> 12) as u8; + + Ok(ObjectInternals { + tile, + palette, + is_8_bit, + pos_x, + pos_y, + shape: ObjectShape::new(shape, size)?, + flip_x, + flip_y, + priority, + disable, + mode, + mosaic, + unused_attr1: unused, + }) + } + + pub fn to_attributes(&self) -> [u16; 3] { + todo!(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 6ffee8c..a0ee815 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,3 +3,5 @@ pub mod extend; pub mod img; pub mod ndsfile; pub mod proj; + +pub use error::{Error, Result};