1#![cfg_attr(feature = "no_std", no_std)]
46use binread::{derive_binread, BinRead, BinReaderExt, NullString, io::{self, SeekFrom}};
47use binread::file_ptr::{FilePtr, FilePtr32, IntoSeekFrom};
48use binread::helpers::read_bytes;
49use core::fmt;
50
51#[cfg(feature = "no_std")]
52mod std;
53
54#[cfg(feature = "no_std")]
55use crate::std::{string::String, vec::Vec};
56
57#[derive(BinRead, Debug)]
59#[br(big)]
60struct GcmTop (
61 #[br(seek_before = SeekFrom::Start(0x1c))]
63 pub GcmFile,
64);
65
66#[derive(BinRead)]
68pub struct GameId(pub [u8; 6]);
69
70#[derive_binread]
72#[derive(Debug)]
73#[br(magic = 0xc2339f3d_u32)]
74pub struct GcmFile {
75 #[br(seek_before = SeekFrom::Start(0))]
76 pub game_id: GameId,
77 pub disc_number: u8,
78 pub revision: u8,
79
80 #[br(seek_before = SeekFrom::Start(0x20))]
81 #[br(map = NullString::into_string)]
82 pub internal_name: String,
83
84 #[br(seek_before = SeekFrom::Start(0x420))]
87 pub dol_offset: u32,
88
89 #[br(seek_before = SeekFrom::Start(0x420))]
90 #[br(parse_with = FilePtr32::parse)]
91 pub dol: DolFile,
92
93 fs_offset: u32,
94 fs_size: u32,
95 max_fs_size: u32,
96
97 #[br(seek_before = SeekFrom::Start(fs_offset as u64))]
98 #[br(args(fs_offset, fs_size))]
99 pub filesystem: FileSystem,
100
101 #[br(seek_before = SeekFrom::Start(0))]
104 #[br(parse_with = read_bytes)]
105 #[br(count = 0x440)]
106 pub boot_bin: Vec<u8>,
107
108 #[br(seek_before = SeekFrom::Start(0x440))]
109 #[br(parse_with = read_bytes)]
110 #[br(count = 0x2000)]
111 pub bi2_bin: Vec<u8>,
112
113 #[br(seek_before = SeekFrom::Start(0x2440))]
114 pub apploader_header: ApploaderHeader,
115
116 #[br(seek_before = SeekFrom::Start(0x2440))]
117 #[br(parse_with = read_bytes)]
118 #[br(count = apploader_header.size + apploader_header.trailer_size + ApploaderHeader::SIZE)]
119 pub apploader: Vec<u8>,
120
121 #[br(seek_before = SeekFrom::Start(fs_offset as u64))]
122 #[br(parse_with = read_bytes)]
123 #[br(count = fs_size)]
124 pub fst_bytes: Vec<u8>,
125}
126
127#[derive(BinRead, Debug)]
128pub struct ApploaderHeader {
129 date: [u8; 0x10],
130 entrypoint_ptr: u32,
131 size: u32,
132 trailer_size: u32,
133 padding: [u8; 4],
134}
135
136impl ApploaderHeader {
137 const SIZE: u32 = 0x20;
138}
139
140#[derive(BinRead, Debug)]
142#[br(import(offset: u32, size: u32))]
143pub struct FileSystem {
144 pub root: RootNode,
145
146 #[br(args(
147 offset as u64, (offset + (root.total_node_count * FsNode::SIZE)) as u64 ))]
150 #[br(count = root.total_node_count - 1)]
151 pub files: Vec<FsNode>,
152}
153
154#[derive(BinRead, Debug)]
156#[br(magic = 1u8)]
157pub struct RootNode {
158 #[br(map = U24::into)]
159 pub name_offset: u32,
160 pub node_start_index: u32,
161 pub total_node_count: u32,
162}
163
164type FilePtr24<T> = FilePtr<U24, T>;
165
166#[br(import(root_offset: u64, name_offset: u64))]
168#[derive(BinRead, Debug)]
169pub enum FsNode {
170 #[br(magic = 0u8)]
171 File {
172 #[br(offset = name_offset)]
173 #[br(parse_with = FilePtr24::parse)]
174 #[br(map = NullString::into_string)]
175 name: String,
176 offset: u32,
177 size: u32,
178 },
179
180 #[br(magic = 1u8)]
181 Directory {
182 #[br(offset = name_offset)]
183 #[br(parse_with = FilePtr24::parse)]
184 #[br(map = NullString::into_string)]
185 name: String,
186 parent_index: u32,
187 end_index: u32,
188 },
189}
190
191impl FsNode {
192 const SIZE: u32 = 0xC;
193}
194
195#[derive(BinRead, Clone, Copy)]
196struct U24([u8; 3]);
197
198impl IntoSeekFrom for U24 {
199 fn into_seek_from(self) -> SeekFrom {
200 u32::from(self).into_seek_from()
201 }
202}
203
204impl From<U24> for u32 {
205 fn from(U24(x): U24) -> Self {
206 u32::from_be_bytes([0, x[0], x[1], x[2]])
207 }
208}
209
210impl fmt::Debug for GameId {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 match core::str::from_utf8(&self.0[..]) {
213 Ok(id) => fmt::Debug::fmt(id, f),
214 Err(_) => write!(f, "GameId({:02x?})", &self.0[..])
215 }
216 }
217}
218
219mod dol;
220mod error;
221mod dir_listing;
222pub use error::GcmError;
223pub use dir_listing::*;
224pub use dol::*;
225
226impl GcmFile {
227 pub fn from_reader<R>(reader: &mut R) -> Result<Self, GcmError>
229 where R: io::Read + io::Seek,
230 {
231 Ok(reader.read_be::<GcmTop>()?.0)
232 }
233}
234
235#[cfg(not(feature = "no_std"))]
236use ::std::path::Path;
237
238#[cfg(not(feature = "no_std"))]
239impl GcmFile {
240 pub fn open<P>(path: P) -> Result<Self, GcmError>
242 where P: AsRef<Path>,
243 {
244 let mut reader = ::std::io::BufReader::new(::std::fs::File::open(path)?);
245 Ok(reader.read_be::<GcmTop>()?.0)
246 }
247}