mirror of
https://github.com/encounter/nod-rs.git
synced 2025-06-16 19:43:28 +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)
|
||||
- NFS (Wii U VC)
|
||||
- GCZ
|
||||
- TGC
|
||||
|
||||
## CLI tool
|
||||
|
||||
|
@ -25,7 +25,7 @@ pub struct Node {
|
||||
kind: u8,
|
||||
// u24 big-endian
|
||||
name_offset: [u8; 3],
|
||||
offset: U32,
|
||||
pub(crate) offset: 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::DiscIOWIA::new(path)?
|
||||
}
|
||||
crate::io::tgc::TGC_MAGIC => crate::io::tgc::DiscIOTGC::new(path)?,
|
||||
_ => crate::io::iso::DiscIOISO::new(path)?,
|
||||
};
|
||||
if io.block_size_internal() < SECTOR_SIZE as u32
|
||||
|
@ -10,6 +10,7 @@ pub(crate) mod iso;
|
||||
pub(crate) mod nfs;
|
||||
pub(crate) mod nkit;
|
||||
pub(crate) mod split;
|
||||
pub(crate) mod tgc;
|
||||
pub(crate) mod wbfs;
|
||||
pub(crate) mod wia;
|
||||
|
||||
@ -40,6 +41,8 @@ pub enum Format {
|
||||
Wbfs,
|
||||
/// WIA
|
||||
Wia,
|
||||
/// TGC
|
||||
Tgc,
|
||||
}
|
||||
|
||||
impl fmt::Display for Format {
|
||||
@ -52,6 +55,7 @@ impl fmt::Display for Format {
|
||||
Format::Rvz => write!(f, "RVZ"),
|
||||
Format::Wbfs => write!(f, "WBFS"),
|
||||
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