use std::{
fmt, fs,
io::{self, BufWriter, Write},
path::Path,
str,
sync::Arc,
};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SM64Version {
JP,
US,
EU,
SH,
}
impl SM64Version {
pub fn all() -> &'static [SM64Version] {
&[Self::JP, Self::US, Self::EU, Self::SH]
}
fn crc_code(self) -> u32 {
match self {
SM64Version::JP => 0x0e3daa4e,
SM64Version::US => 0xff2b5a63,
SM64Version::EU => 0x36f03ca0,
SM64Version::SH => 0xa8a4fbd6,
}
}
fn country_code(self) -> u8 {
match self {
SM64Version::JP => b'J',
SM64Version::US => b'E',
SM64Version::EU => b'P',
SM64Version::SH => b'J',
}
}
}
impl fmt::Display for SM64Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SM64Version::JP => write!(f, "JP"),
SM64Version::US => write!(f, "US"),
SM64Version::EU => write!(f, "EU"),
SM64Version::SH => write!(f, "SH"),
}
}
}
#[derive(Debug, Clone)]
pub struct M64Metadata {
crc_code: u32,
country_code: u8,
author: String,
description: String,
rerecords: u32,
}
impl M64Metadata {
pub fn new(crc_code: u32, country_code: u8) -> Self {
Self {
crc_code,
country_code,
author: String::new(),
description: String::new(),
rerecords: 0,
}
}
pub fn with_version(version: SM64Version) -> Self {
Self::new(version.crc_code(), version.country_code())
}
pub fn crc_code(&self) -> u32 {
self.crc_code
}
pub fn set_crc_code(&mut self, crc_code: u32) -> &mut Self {
self.crc_code = crc_code;
self
}
pub fn country_code(&self) -> u8 {
self.country_code
}
pub fn set_country_code(&mut self, country_code: u8) -> &mut Self {
self.country_code = country_code;
self
}
pub fn version(&self) -> Option<SM64Version> {
SM64Version::all().iter().copied().find(|version| {
version.crc_code() == self.crc_code && version.country_code() == self.country_code
})
}
pub fn set_version(&mut self, version: SM64Version) -> &mut Self {
self.crc_code = version.crc_code();
self.country_code = version.country_code();
self
}
pub fn author(&self) -> &str {
&self.author
}
#[track_caller]
pub fn set_author(&mut self, author: &str) -> &mut Self {
match self.try_set_author(author) {
Ok(this) => this,
Err(error) => panic!("Error:\n {}\n", error),
}
}
pub fn try_set_author(&mut self, author: &str) -> Result<&mut Self, Error> {
if author.len() > 222 {
return Err(Error::M64AuthorTooLong);
}
self.author = author.to_string();
Ok(self)
}
pub fn description(&self) -> &str {
&self.description
}
#[track_caller]
pub fn set_description(&mut self, description: &str) -> &mut Self {
match self.try_set_description(description) {
Ok(this) => this,
Err(error) => panic!("Error:\n {}\n", error),
}
}
pub fn try_set_description(&mut self, description: &str) -> Result<&mut Self, Error> {
if description.len() > 256 {
return Err(Error::M64DescriptionTooLong);
}
self.description = description.to_string();
Ok(self)
}
pub fn rerecords(&self) -> u32 {
self.rerecords
}
pub fn set_rerecords(&mut self, rerecords: u32) -> &mut Self {
self.rerecords = rerecords;
self
}
pub fn add_rerecords(&mut self, rerecords: u32) -> &mut Self {
self.rerecords = self.rerecords.saturating_add(rerecords);
self
}
}
impl fmt::Display for M64Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "M64Metadata(")?;
writeln!(f, " crc_code = {:#010X},", self.crc_code)?;
writeln!(f, " country_code = {:?},", self.country_code as char)?;
writeln!(f, " author = {:?},", self.author)?;
writeln!(f, " description = {:?},", self.description)?;
writeln!(f, " rerecords = {},", self.rerecords)?;
write!(f, ")")?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Input {
pub buttons: u16,
pub stick_x: i8,
pub stick_y: i8,
}
impl fmt::Display for Input {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Input(buttons = {:#04X}, stick_x = {}, stick_y = {})",
self.buttons, self.stick_x, self.stick_y
)
}
}
#[track_caller]
pub fn load_m64(filename: &str) -> (M64Metadata, Vec<Input>) {
match try_load_m64(filename) {
Ok(result) => result,
Err(error) => panic!("Error:\n {}\n", error),
}
}
pub fn try_load_m64(filename: &str) -> Result<(M64Metadata, Vec<Input>), Error> {
let f = fs::read(filename).map_err(|error| Error::M64ReadError {
filename: filename.to_string(),
error: Arc::new(error),
})?;
if f.len() < 0x400 {
return Err(Error::InvalidM64Error {
filename: filename.to_string(),
});
}
let rerecords = u32::from_le_bytes([f[0x10], f[0x11], f[0x12], f[0x13]]);
let crc_code = u32::from_le_bytes([f[0xe4], f[0xe5], f[0xe6], f[0xe7]]);
let country_code = f[0xe8];
let author = String::from_utf8(f[0x222..0x222 + 222].to_vec())
.map_err(|_| Error::InvalidM64Error {
filename: filename.to_string(),
})?
.trim_end_matches('\x00')
.to_string();
let description = String::from_utf8(f[0x300..0x300 + 256].to_vec())
.map_err(|_| Error::InvalidM64Error {
filename: filename.to_string(),
})?
.trim_end_matches('\x00')
.to_string();
let metadata = M64Metadata {
crc_code,
country_code,
author,
description,
rerecords,
};
let mut inputs: Vec<Input> = Vec::new();
for chunk in f[0x400..].chunks_exact(4) {
inputs.push(Input {
buttons: u16::from_be_bytes([chunk[0], chunk[1]]),
stick_x: chunk[2] as i8,
stick_y: chunk[3] as i8,
});
}
Ok((metadata, inputs))
}
#[track_caller]
pub fn save_m64(filename: &str, metadata: &M64Metadata, inputs: &[Input]) {
if let Err(error) = try_save_m64(filename, metadata, inputs) {
panic!("Error:\n {}\n", error);
}
}
pub fn try_save_m64(filename: &str, metadata: &M64Metadata, inputs: &[Input]) -> Result<(), Error> {
save_m64_impl(filename, metadata, inputs).map_err(|error| Error::M64WriteError {
filename: filename.to_string(),
error: Arc::new(error),
})
}
fn save_m64_impl(filename: &str, metadata: &M64Metadata, inputs: &[Input]) -> io::Result<()> {
if let Some(dir) = Path::new(filename).parent() {
fs::create_dir_all(dir)?;
}
let mut f = BufWriter::new(fs::File::create(filename)?);
f.write_all(&[0x4d, 0x36, 0x34, 0x1a])?; f.write_all(&[0x03, 0x00, 0x00, 0x00])?; f.write_all(&[0x00, 0x00, 0x00, 0x00])?; f.write_all(&[0xff, 0xff, 0xff, 0xff])?; f.write_all(&metadata.rerecords.to_le_bytes())?; f.write_all(&[0x3c])?; f.write_all(&[0x01])?; f.write_all(&[0x00, 0x00])?; f.write_all(&(inputs.len() as u32).to_le_bytes())?; f.write_all(&[0x02, 0x00])?; f.write_all(&[0x00, 0x00])?; f.write_all(&[0x01, 0x00, 0x00, 0x00])?; f.write_all(&[0x00; 160])?; let mut game_name = b"SUPER MARIO 64".to_vec();
game_name.resize(32, 0x00);
f.write_all(&game_name)?; f.write_all(&metadata.crc_code.to_le_bytes())?; f.write_all(&[metadata.country_code, 0x00])?; f.write_all(&[0x00; 56])?; f.write_all(&[0x00; 64])?; f.write_all(&[0x00; 64])?; f.write_all(&[0x00; 64])?; f.write_all(&[0x00; 64])?; let mut author = metadata.author.as_bytes().to_vec();
author.resize(222, 0x00);
f.write_all(&author)?; let mut description = metadata.description.as_bytes().to_vec();
description.resize(256, 0x00);
f.write_all(&description)?; for &input in inputs {
f.write_all(&input.buttons.to_be_bytes())?;
f.write_all(&[input.stick_x as u8])?;
f.write_all(&[input.stick_y as u8])?;
}
Ok(())
}