mirror of
https://github.com/encounter/nod-rs.git
synced 2025-06-17 03:53:29 +00:00
Add TGC support
This commit is contained in:
parent
da8d5fda79
commit
551f966c80
@ -20,6 +20,7 @@ Currently supported file formats:
|
|||||||
- CISO (+ NKit 2 lossless)
|
- CISO (+ NKit 2 lossless)
|
||||||
- NFS (Wii U VC)
|
- NFS (Wii U VC)
|
||||||
- GCZ
|
- GCZ
|
||||||
|
- TGC
|
||||||
|
|
||||||
## CLI tool
|
## CLI tool
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ pub struct Node {
|
|||||||
kind: u8,
|
kind: u8,
|
||||||
// u24 big-endian
|
// u24 big-endian
|
||||||
name_offset: [u8; 3],
|
name_offset: [u8; 3],
|
||||||
offset: U32,
|
pub(crate) offset: U32,
|
||||||
length: U32,
|
length: U32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +114,7 @@ pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
|
|||||||
crate::io::wia::WIA_MAGIC | crate::io::wia::RVZ_MAGIC => {
|
crate::io::wia::WIA_MAGIC | crate::io::wia::RVZ_MAGIC => {
|
||||||
crate::io::wia::DiscIOWIA::new(path)?
|
crate::io::wia::DiscIOWIA::new(path)?
|
||||||
}
|
}
|
||||||
|
crate::io::tgc::TGC_MAGIC => crate::io::tgc::DiscIOTGC::new(path)?,
|
||||||
_ => crate::io::iso::DiscIOISO::new(path)?,
|
_ => crate::io::iso::DiscIOISO::new(path)?,
|
||||||
};
|
};
|
||||||
if io.block_size_internal() < SECTOR_SIZE as u32
|
if io.block_size_internal() < SECTOR_SIZE as u32
|
||||||
|
@ -10,6 +10,7 @@ pub(crate) mod iso;
|
|||||||
pub(crate) mod nfs;
|
pub(crate) mod nfs;
|
||||||
pub(crate) mod nkit;
|
pub(crate) mod nkit;
|
||||||
pub(crate) mod split;
|
pub(crate) mod split;
|
||||||
|
pub(crate) mod tgc;
|
||||||
pub(crate) mod wbfs;
|
pub(crate) mod wbfs;
|
||||||
pub(crate) mod wia;
|
pub(crate) mod wia;
|
||||||
|
|
||||||
@ -40,6 +41,8 @@ pub enum Format {
|
|||||||
Wbfs,
|
Wbfs,
|
||||||
/// WIA
|
/// WIA
|
||||||
Wia,
|
Wia,
|
||||||
|
/// TGC
|
||||||
|
Tgc,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Format {
|
impl fmt::Display for Format {
|
||||||
@ -52,6 +55,7 @@ impl fmt::Display for Format {
|
|||||||
Format::Rvz => write!(f, "RVZ"),
|
Format::Rvz => write!(f, "RVZ"),
|
||||||
Format::Wbfs => write!(f, "WBFS"),
|
Format::Wbfs => write!(f, "WBFS"),
|
||||||
Format::Wia => write!(f, "WIA"),
|
Format::Wia => write!(f, "WIA"),
|
||||||
|
Format::Tgc => write!(f, "TGC"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
141
nod/src/io/tgc.rs
Normal file
141
nod/src/io/tgc.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
io::{Read, Seek, SeekFrom},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use zerocopy::{big_endian::U32, AsBytes, FromBytes, FromZeroes};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
disc::SECTOR_SIZE,
|
||||||
|
io::{
|
||||||
|
block::{Block, BlockIO, PartitionInfo},
|
||||||
|
split::SplitFileReader,
|
||||||
|
Format, MagicBytes,
|
||||||
|
},
|
||||||
|
util::read::{read_box_slice, read_from},
|
||||||
|
DiscHeader, DiscMeta, Error, Node, PartitionHeader, Result, ResultContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TGC_MAGIC: MagicBytes = [0xae, 0x0f, 0x38, 0xa2];
|
||||||
|
|
||||||
|
/// TGC header (big endian)
|
||||||
|
#[derive(Clone, Debug, PartialEq, FromBytes, FromZeroes, AsBytes)]
|
||||||
|
#[repr(C, align(4))]
|
||||||
|
struct TGCHeader {
|
||||||
|
magic: MagicBytes,
|
||||||
|
unk1: U32,
|
||||||
|
header_size: U32,
|
||||||
|
disc_area_header_size: U32,
|
||||||
|
fst_offset: U32,
|
||||||
|
fst_size: U32,
|
||||||
|
fst_max_size: U32,
|
||||||
|
dol_offset: U32,
|
||||||
|
dol_size: U32,
|
||||||
|
file_area: U32,
|
||||||
|
file_area_size: U32,
|
||||||
|
banner_offset: U32,
|
||||||
|
banner_size: U32,
|
||||||
|
file_offset_base: U32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DiscIOTGC {
|
||||||
|
inner: SplitFileReader,
|
||||||
|
header: TGCHeader,
|
||||||
|
fst: Box<[u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiscIOTGC {
|
||||||
|
pub fn new(filename: &Path) -> Result<Box<Self>> {
|
||||||
|
let mut inner = SplitFileReader::new(filename)?;
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
let header: TGCHeader = read_from(&mut inner).context("Reading TGC header")?;
|
||||||
|
if header.magic != TGC_MAGIC {
|
||||||
|
return Err(Error::DiscFormat("Invalid TGC magic".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read FST and adjust offsets
|
||||||
|
inner
|
||||||
|
.seek(SeekFrom::Start(header.fst_offset.get() as u64))
|
||||||
|
.context("Seeking to TGC FST")?;
|
||||||
|
let mut fst = read_box_slice(&mut inner, header.fst_size.get() as usize)
|
||||||
|
.context("Reading TGC FST")?;
|
||||||
|
let root_node = Node::ref_from_prefix(&fst)
|
||||||
|
.ok_or_else(|| Error::DiscFormat("Invalid TGC FST".to_string()))?;
|
||||||
|
let node_count = root_node.length() as usize;
|
||||||
|
let (nodes, _) = Node::mut_slice_from_prefix(&mut fst, node_count)
|
||||||
|
.ok_or_else(|| Error::DiscFormat("Invalid TGC FST".to_string()))?;
|
||||||
|
for node in nodes {
|
||||||
|
if node.is_file() {
|
||||||
|
node.offset =
|
||||||
|
node.offset - header.file_offset_base + header.file_area - header.header_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(Self { inner, header, fst }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockIO for DiscIOTGC {
|
||||||
|
fn read_block_internal(
|
||||||
|
&mut self,
|
||||||
|
out: &mut [u8],
|
||||||
|
block: u32,
|
||||||
|
_partition: Option<&PartitionInfo>,
|
||||||
|
) -> io::Result<Block> {
|
||||||
|
let offset = self.header.header_size.get() as u64 + block as u64 * SECTOR_SIZE as u64;
|
||||||
|
let total_size = self.inner.len();
|
||||||
|
if offset >= total_size {
|
||||||
|
// End of file
|
||||||
|
return Ok(Block::Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.seek(SeekFrom::Start(offset))?;
|
||||||
|
if offset + SECTOR_SIZE as u64 > total_size {
|
||||||
|
// If the last block is not a full sector, fill the rest with zeroes
|
||||||
|
let read = (total_size - offset) as usize;
|
||||||
|
self.inner.read_exact(&mut out[..read])?;
|
||||||
|
out[read..].fill(0);
|
||||||
|
} else {
|
||||||
|
self.inner.read_exact(out)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust internal GCM header
|
||||||
|
if block == 0 {
|
||||||
|
let partition_header = PartitionHeader::mut_from(
|
||||||
|
&mut out[size_of::<DiscHeader>()
|
||||||
|
..size_of::<DiscHeader>() + size_of::<PartitionHeader>()],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
partition_header.dol_offset = self.header.dol_offset - self.header.header_size;
|
||||||
|
partition_header.fst_offset = self.header.fst_offset - self.header.header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy modified FST to output
|
||||||
|
if offset + out.len() as u64 > self.header.fst_offset.get() as u64
|
||||||
|
&& offset < self.header.fst_offset.get() as u64 + self.header.fst_size.get() as u64
|
||||||
|
{
|
||||||
|
let out_offset = (self.header.fst_offset.get() as u64).saturating_sub(offset) as usize;
|
||||||
|
let fst_offset = offset.saturating_sub(self.header.fst_offset.get() as u64) as usize;
|
||||||
|
let copy_len =
|
||||||
|
(out.len() - out_offset).min(self.header.fst_size.get() as usize - fst_offset);
|
||||||
|
out[out_offset..out_offset + copy_len]
|
||||||
|
.copy_from_slice(&self.fst[fst_offset..fst_offset + copy_len]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Block::Raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_size_internal(&self) -> u32 { SECTOR_SIZE as u32 }
|
||||||
|
|
||||||
|
fn meta(&self) -> DiscMeta {
|
||||||
|
DiscMeta {
|
||||||
|
format: Format::Tgc,
|
||||||
|
lossless: true,
|
||||||
|
disc_size: Some(self.inner.len() - self.header.header_size.get() as u64),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user