Newer
Older
//! System save state (BOS and [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) formats) functions and structures.
io::{Cursor, Read, Seek, SeekFrom, Write},
mem::size_of,
use crate::{
gb::{GameBoy, GameBoySpeed},
info::Info,
ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_SIZE},
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
/// Magic string for the BOS (Boytacean Save) format.
pub const BOS_MAGIC: &str = "BOS\0";
/// Magic string ("BOS\0") in little endian unsigned 32 bit format.
pub const BOS_MAGIC_UINT: u32 = 0x00534f42;
/// Current version of the BOS (Boytacean Save) format.
pub const BOS_VERSION: u8 = 1;
/// Magic number for the BESS file format.
pub const BESS_MAGIC: u32 = 0x53534542;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub enum SaveStateFormat {
Bos,
Bess,
}
Unknown = 0xff,
}
impl BosBlockKind {
fn from_u8(value: u8) -> Self {
match value {
0x02 => Self::ImageBuffer,
_ => Self::Unknown,
}
}
/// Writes the data from the internal structure into the
/// provided buffer.
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>);
/// Reads the data from the provided buffer and populates
/// the internal structure with it.
fn read(&mut self, data: &mut Cursor<Vec<u8>>);
/// Obtains a new instance of the state from the provided
/// `GameBoy` instance and returns it.
fn from_gb(gb: &mut GameBoy) -> Result<Self, String>
where
Self: Sized;
/// Applies the state to the provided `GameBoy` instance.
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String>;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Default)]
pub struct BosState {
magic: u32,
version: u8,
image_buffer: Option<BosImageBuffer>,
bess: BessState,
}
impl BosState {
/// Checks if the data contained in the provided
/// buffer represents a valid BOS (Boytacean Save)
/// file structure, thought magic string validation.
pub fn is_bos(data: &mut Cursor<Vec<u8>>) -> bool {
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
let magic = u32::from_le_bytes(buffer);
magic == BOS_MAGIC_UINT
}
pub fn verify(&self) -> Result<(), String> {
if self.magic != BOS_MAGIC_UINT {
return Err(String::from("Invalid magic"));
}
self.bess.verify()?;
Ok(())
}
pub fn save_image_bmp(&self, file_path: &str) -> Result<(), String> {
if let Some(image_buffer) = &self.image_buffer {
image_buffer.save_bmp(file_path)?;
Ok(())
} else {
Err(String::from("No image buffer found"))
}
}
fn build_block_count(&self) -> u8 {
let mut count = 0_u8;
if self.info.is_some() {
count += 1;
}
if self.image_buffer.is_some() {
count += 1;
}
count
}
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl BosState {
pub fn timestamp(&self) -> Result<u64, String> {
if let Some(info) = &self.info {
Ok(info.timestamp)
} else {
Err(String::from("No timestamp available"))
}
}
pub fn agent(&self) -> Result<String, String> {
if let Some(info) = &self.info {
Ok(format!("{}/{}", info.agent, info.agent_version))
} else {
Err(String::from("No agent available"))
}
}
pub fn model(&self) -> Result<String, String> {
if let Some(info) = &self.info {
Ok(info.model.clone())
} else {
Err(String::from("No model available"))
}
}
pub fn image_eager(&self) -> Result<Vec<u8>, String> {
if let Some(image_buffer) = &self.image_buffer {
Ok(image_buffer.image.to_vec())
} else {
Err(String::from("No image available"))
}
}
}
impl Serialize for BosState {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
self.block_count = self.build_block_count();
buffer.write_all(&self.magic.to_le_bytes()).unwrap();
buffer.write_all(&self.version.to_le_bytes()).unwrap();
buffer.write_all(&self.block_count.to_le_bytes()).unwrap();
if let Some(info) = &mut self.info {
info.write(buffer);
}
if let Some(image_buffer) = &mut self.image_buffer {
image_buffer.write(buffer);
}
self.bess.write(buffer);
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.magic = u32::from_le_bytes(buffer);
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
self.version = u8::from_le_bytes(buffer);
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
self.block_count = u8::from_le_bytes(buffer);
for _ in 0..self.block_count {
let block = BosBlock::from_data(data);
let offset = -((size_of::<u8>() + size_of::<u32>()) as i64);
data.seek(SeekFrom::Current(offset)).unwrap();
match block.kind {
BosBlockKind::Info => {
self.info = Some(BosInfo::from_data(data));
}
BosBlockKind::ImageBuffer => {
self.image_buffer = Some(BosImageBuffer::from_data(data));
}
_ => {
data.seek(SeekFrom::Current(-offset)).unwrap();
data.seek(SeekFrom::Current(block.size as i64)).unwrap();
}
}
}
self.block_count = self.build_block_count();
self.bess.read(data);
}
}
impl State for BosState {
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
Ok(Self {
magic: BOS_MAGIC_UINT,
version: BOS_VERSION,
block_count: 2,
info: Some(BosInfo::from_gb(gb)?),
image_buffer: Some(BosImageBuffer::from_gb(gb)?),
bess: BessState::from_gb(gb)?,
})
}
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String> {
self.verify()?;
self.bess.to_gb(gb)?;
Ok(())
}
}
pub struct BosBlock {
kind: BosBlockKind,
size: u32,
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
}
impl BosBlock {
pub fn new(kind: BosBlockKind, size: u32) -> Self {
Self { kind, size }
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance.read(data);
instance
}
}
impl Serialize for BosBlock {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&(self.kind as u8).to_le_bytes()).unwrap();
buffer.write_all(&self.size.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; 1];
data.read_exact(&mut buffer).unwrap();
self.kind = BosBlockKind::from_u8(u8::from_le_bytes(buffer));
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.size = u32::from_le_bytes(buffer);
}
}
impl Default for BosBlock {
fn default() -> Self {
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
Self::new(BosBlockKind::Info, 0)
}
}
pub struct BosInfo {
header: BosBlock,
timestamp: u64,
agent: String,
agent_version: String,
model: String,
}
impl BosInfo {
pub fn new(model: String, timestamp: u64, agent: String, agent_version: String) -> Self {
Self {
header: BosBlock::new(
BosBlockKind::Info,
(size_of::<u64>()
+ size_of::<u8>() * agent.len()
+ size_of::<u8>() * agent_version.len()
+ size_of::<u8>() * model.len()
+ size_of::<u32>() * 4) as u32,
),
model,
timestamp,
agent,
agent_version,
}
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance.read(data);
instance
}
}
impl Serialize for BosInfo {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
self.header.write(buffer);
buffer
.write_all(&(size_of::<u64>() as u32).to_le_bytes())
.unwrap();
buffer.write_all(&self.timestamp.to_le_bytes()).unwrap();
buffer
.write_all(&(self.agent.as_bytes().len() as u32).to_le_bytes())
.unwrap();
buffer.write_all(self.agent.as_bytes()).unwrap();
buffer
.write_all(&(self.agent_version.as_bytes().len() as u32).to_le_bytes())
.unwrap();
buffer.write_all(self.agent_version.as_bytes()).unwrap();
buffer
.write_all(&(self.model.as_bytes().len() as u32).to_le_bytes())
.unwrap();
buffer.write_all(self.model.as_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.timestamp = u64::from_le_bytes(buffer.try_into().unwrap());
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.agent = String::from_utf8(buffer).unwrap();
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.agent_version = String::from_utf8(buffer).unwrap();
let mut buffer = [0x00; size_of::<u32>()];
data.read_exact(&mut buffer).unwrap();
let mut buffer = vec![0x00; u32::from_le_bytes(buffer) as usize];
data.read_exact(&mut buffer).unwrap();
self.model = String::from_utf8(buffer).unwrap();
}
}
impl State for BosInfo {
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
let timestamp = get_timestamp();
Ok(Self::new(
gb.mode().to_string(Some(true)),
timestamp,
Info::name(),
Info::version(),
))
}
fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), String> {
Ok(())
}
}
impl Default for BosInfo {
fn default() -> Self {
Self::new(String::from(""), 0, String::from(""), String::from(""))
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
}
}
pub struct BosImageBuffer {
header: BosBlock,
image: [u8; FRAME_BUFFER_SIZE],
}
impl BosImageBuffer {
pub fn new(image: [u8; FRAME_BUFFER_SIZE]) -> Self {
Self {
header: BosBlock::new(
BosBlockKind::ImageBuffer,
(size_of::<u8>() * FRAME_BUFFER_SIZE) as u32,
),
image,
}
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance.read(data);
instance
}
pub fn save_bmp(&self, file_path: &str) -> Result<(), String> {
save_bmp(
file_path,
&self.image,
DISPLAY_WIDTH as u32,
DISPLAY_HEIGHT as u32,
)?;
Ok(())
}
}
impl Serialize for BosImageBuffer {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
self.header.write(buffer);
buffer.write_all(&self.image).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
data.read_exact(&mut self.image).unwrap();
}
}
impl State for BosImageBuffer {
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
Ok(Self::new(*gb.ppu_i().frame_buffer))
}
fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), String> {
Ok(())
}
}
impl Default for BosImageBuffer {
fn default() -> Self {
Self::new([0x00; FRAME_BUFFER_SIZE])
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct BessState {
footer: BessFooter,
name: BessName,
info: BessInfo,
core: BessCore,
mbc: BessMbc,
end: BessBlock,
/// Checks if the data contained in the provided
/// buffer represents a valid BESS (Best Effort Save State)
/// file structure, thought magic string validation.
pub fn is_bess(data: &mut Cursor<Vec<u8>>) -> bool {
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
let magic = u32::from_le_bytes(buffer);
pub fn description(&self, column_length: usize) -> String {
let emulator_l = format!("{:width$}", "Emulator", width = column_length);
let title_l: String = format!("{:width$}", "Title", width = column_length);
let version_l: String = format!("{:width$}", "Version", width = column_length);
let model_l: String = format!("{:width$}", "Model", width = column_length);
let ram_l: String = format!("{:width$}", "RAM", width = column_length);
let vram_l: String = format!("{:width$}", "VRAM", width = column_length);
let pc_l: String = format!("{:width$}", "PC", width = column_length);
let sp_l: String = format!("{:width$}", "SP", width = column_length);
"{} {}\n{} {}\n{} {}.{}\n{} {}\n{} {}\n{} {}\n{} 0x{:04X}\n{} 0x{:04X}\n",
emulator_l,
self.name.name,
title_l,
self.info.title(),
version_l,
self.core.major,
self.core.minor,
model_l,
self.core.model,
ram_l,
self.core.ram.size,
vram_l,
self.core.vram.size,
pc_l,
self.core.pc,
sp_l,
self.core.sp
pub fn verify(&self) -> Result<(), String> {
/// Dumps the core data into the provided buffer and returns.
/// This will effectively populate the majority of the save
/// file with the core emulator contents.
fn dump_core(&mut self, buffer: &mut Cursor<Vec<u8>>) {
let mut buffers = vec![
&mut self.core.ram,
&mut self.core.vram,
&mut self.core.mbc_ram,
&mut self.core.oam,
&mut self.core.hram,
&mut self.core.background_palettes,
&mut self.core.object_palettes,
];
for item in buffers.iter_mut() {
item.offset = buffer.position() as u32;
buffer.write_all(&item.buffer).unwrap();
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
self.dump_core(buffer);
self.footer.start_offset = buffer.position() as u32;
self.name.write(buffer);
self.info.write(buffer);
self.core.write(buffer);
self.mbc.write(buffer);
self.end.write(buffer);
self.footer.write(buffer);
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
// moves the cursor to the end of the file
// to read the footer, and then places the
// according to the footer information
data.seek(SeekFrom::End(-8)).unwrap();
data.seek(SeekFrom::Start(self.footer.start_offset as u64))
.unwrap();
// reads the block header information and then moves the
// cursor back to the original position to be able to
// re-read the block data
let offset = -((size_of::<u32>() * 2) as i64);
data.seek(SeekFrom::Current(offset)).unwrap();
"NAME" => self.name = BessName::from_data(data),
"INFO" => self.info = BessInfo::from_data(data),
"CORE" => self.core = BessCore::from_data(data),
"MBC " => self.mbc = BessMbc::from_data(data),
"END " => self.end = BessBlock::from_data(data),
if block.is_end() {
break;
}
}
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
Ok(Self {
footer: BessFooter::default(),
name: BessName::from_gb(gb)?,
info: BessInfo::from_gb(gb)?,
core: BessCore::from_gb(gb)?,
mbc: BessMbc::from_gb(gb)?,
end: BessBlock::from_magic(String::from("END ")),
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String> {
self.verify()?;
self.name.to_gb(gb)?;
self.info.to_gb(gb)?;
self.core.to_gb(gb)?;
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description(9))
magic: String,
size: u32,
pub fn new(magic: String, size: u32) -> Self {
Self { magic, size }
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance
}
pub fn is_end(&self) -> bool {
self.magic == "END "
}
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.size.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.magic = String::from_utf8(Vec::from(buffer)).unwrap();
fn default() -> Self {
Self::new(String::from(" "), 0)
}
}
pub struct BessBlock {
header: BessBlockHeader,
impl BessBlock {
pub fn new(header: BessBlockHeader, buffer: Vec<u8>) -> Self {
Self { header, buffer }
}
pub fn from_magic(magic: String) -> Self {
Self::new(BessBlockHeader::new(magic, 0), vec![])
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
instance
}
pub fn magic(&self) -> &String {
&self.header.magic
}
pub fn is_end(&self) -> bool {
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.buffer).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
self.buffer.reserve_exact(self.header.size as usize);
data.read_exact(&mut self.buffer).unwrap();
size: u32,
offset: u32,
pub fn new(size: u32, offset: u32, buffer: Vec<u8>) -> Self {
Self {
size,
offset,
buffer,
}
}
/// Fills the buffer with new data and updating the size
/// value accordingly.
fn fill_buffer(&mut self, data: &[u8]) {
self.size = data.len() as u32;
self.buffer = data.to_vec();
}
/// Loads the internal buffer structure with the provided
/// data according to the size and offset defined.
fn load_buffer(&self, data: &mut Cursor<Vec<u8>>) -> Vec<u8> {
let mut buffer = vec![0x00; self.size as usize];
let position = data.position();
data.seek(SeekFrom::Start(self.offset as u64)).unwrap();
data.read_exact(&mut buffer).unwrap();
data.set_position(position);
buffer
}
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.size.to_le_bytes()).unwrap();
buffer.write_all(&self.offset.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
self.buffer = self.load_buffer(data);
}
}
fn default() -> Self {
Self::new(0, 0, vec![])
start_offset: u32,
magic: u32,
pub fn new(start_offset: u32, magic: u32) -> Self {
Self {
start_offset,
magic,
}
}
pub fn verify(&self) -> Result<(), String> {
return Err(String::from("Invalid magic"));
}
Ok(())
}
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.start_offset.to_le_bytes()).unwrap();
buffer.write_all(&self.magic.to_le_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
let mut buffer = [0x00; 4];
data.read_exact(&mut buffer).unwrap();
pub struct BessName {
header: BessBlockHeader,
pub fn new(name: String) -> Self {
Self {
header: BessBlockHeader::new(String::from("NAME"), name.len() as u32),
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(self.name.as_bytes()).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
let mut buffer = vec![0x00; self.header.size as usize];
fn from_gb(_gb: &mut GameBoy) -> Result<Self, String> {
Ok(Self::new(format!("{} v{}", Info::name(), Info::version())))
fn to_gb(&self, _gb: &mut GameBoy) -> Result<(), String> {
Ok(())
}
fn default() -> Self {
Self::new(String::from(""))
}
}
pub struct BessInfo {
header: BessBlockHeader,
title: [u8; 16],
checksum: [u8; 2],
pub fn new(title: &[u8], checksum: &[u8]) -> Self {
Self {
String::from("INFO"),
title.len() as u32 + checksum.len() as u32,
),
title: title.try_into().unwrap(),
checksum: checksum.try_into().unwrap(),
}
}
pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
let mut instance = Self::default();
for (offset, byte) in self.title.iter().enumerate() {
final_index = offset;
break;
}
// in we're at the final byte of the title and the value
// is one that is reserved for CGB compatibility testing
// then we must ignore it for title processing purposes
if offset > 14
&& (*byte == CgbMode::CgbCompatible as u8 || *byte == CgbMode::CgbOnly as u8)
{
final_index = offset;
String::from_utf8(Vec::from(&self.title[..final_index]))
.unwrap()
.trim_matches(char::from(0))
.trim(),
)
fn write(&mut self, buffer: &mut Cursor<Vec<u8>>) {
buffer.write_all(&self.title).unwrap();
buffer.write_all(&self.checksum).unwrap();
}
fn read(&mut self, data: &mut Cursor<Vec<u8>>) {
self.header.read(data);
data.read_exact(&mut self.title).unwrap();
data.read_exact(&mut self.checksum).unwrap();
fn from_gb(gb: &mut GameBoy) -> Result<Self, String> {
Ok(Self::new(
&gb.cartridge_i().rom_data()[0x0134..=0x0143],
&gb.cartridge_i().rom_data()[0x014e..=0x014f],
fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String> {
if self.title() != gb.rom_i().title() {
return Err(format!(
"Invalid ROM loaded, expected '{}' (len {}) got '{}' (len {})",
self.title(),
self.title().len(),
gb.rom_i().title(),
gb.rom_i().title().len(),
));
}
Ok(())
}
fn default() -> Self {
Self::new(&[0_u8; 16], &[0_u8; 2])
}
}
pub struct BessCore {
header: BessBlockHeader,
major: u16,
minor: u16,
pc: u16,
af: u16,
bc: u16,
de: u16,
hl: u16,
sp: u16,
// 0 = running; 1 = halted; 2 = stopped
io_registers: [u8; 128],
ram: BessBuffer,
vram: BessBuffer,
mbc_ram: BessBuffer,
oam: BessBuffer,
hram: BessBuffer,
background_palettes: BessBuffer,
object_palettes: BessBuffer,
pub fn new(
model: String,
pc: u16,
af: u16,
bc: u16,
de: u16,
hl: u16,
sp: u16,
ime: bool,
ie: u8,
execution_mode: u8,
io_registers: [u8; 128],
) -> Self {
String::from("CORE"),
((size_of::<u16>() * 2)
+ size_of::<u32>()
+ (size_of::<u16>() * 6)
+ (size_of::<u8>() * 4)
+ (size_of::<u8>() * 128)
+ ((size_of::<u32>() + size_of::<u32>()) * 7)) as u32,
),
major: 1,
minor: 1,
model,
pc,
af,