iNES header parsing implemented
parent
aec5fa05c8
commit
f4384b6514
|
|
@ -3,14 +3,34 @@
|
||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
mod nes_parser;
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
use nes_parser::INesVersion;
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize)]
|
#[derive(Clone, serde::Serialize)]
|
||||||
struct Payload {
|
struct Payload {
|
||||||
message: String,
|
message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize)]
|
||||||
|
struct INesData {
|
||||||
|
filename: String,
|
||||||
|
prg_rom_size: u32,
|
||||||
|
chr_rom_size: u32,
|
||||||
|
mirroring_type: bool,
|
||||||
|
has_battery: bool,
|
||||||
|
has_trainer: bool,
|
||||||
|
has_four_screen_vram: bool,
|
||||||
|
is_vs_unisystem: bool,
|
||||||
|
is_playchoice_10: bool,
|
||||||
|
ines_version_identifier: u8,
|
||||||
|
mapper: u8,
|
||||||
|
prg_ram_size: u8,
|
||||||
|
tv_system: bool,
|
||||||
|
}
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn select_file(app: tauri::AppHandle) {
|
fn select_file(app: tauri::AppHandle) {
|
||||||
|
|
@ -39,7 +59,57 @@ fn select_file(app: tauri::AppHandle) {
|
||||||
app.emit_all("error", Payload { message: "Unable to read file".into() }).unwrap();
|
app.emit_all("error", Payload { message: "Unable to read file".into() }).unwrap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
println!("{}", contents.len());
|
let version = nes_parser::parse_version(&contents);
|
||||||
|
if version.is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match version {
|
||||||
|
Ok(INesVersion::INes20) => {
|
||||||
|
let header = nes_parser::INes20Header::from_rom(&contents);
|
||||||
|
match header {
|
||||||
|
Ok(header) => {
|
||||||
|
println!("PRG ROM size: {}", header.get_prg_rom_size());
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
app.emit_all("error", Payload { message: nes_parser::get_error_message(error).into() }).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(INesVersion::INes) => {
|
||||||
|
let header = nes_parser::INesHeader::from_rom(&contents);
|
||||||
|
match header {
|
||||||
|
Ok(header) => {
|
||||||
|
app.emit_all("loaded_ines", INesData {
|
||||||
|
filename: path_buf.file_name().unwrap().to_os_string().into_string().unwrap(),
|
||||||
|
prg_rom_size: header.prg_rom_size(),
|
||||||
|
chr_rom_size: header.chr_rom_size(),
|
||||||
|
mirroring_type: header.mirroring_type(),
|
||||||
|
has_battery: header.has_battery(),
|
||||||
|
has_trainer: header.has_trainer(),
|
||||||
|
has_four_screen_vram: header.has_four_screen_vram(),
|
||||||
|
is_vs_unisystem: header.is_vs_unisystem(),
|
||||||
|
is_playchoice_10: header.is_playchoice_10(),
|
||||||
|
ines_version_identifier: header.ines_version_identifier(),
|
||||||
|
mapper: header.mapper(),
|
||||||
|
prg_ram_size: header.prg_ram_size(),
|
||||||
|
tv_system: header.tv_system(),
|
||||||
|
}).unwrap();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
app.emit_all("error", Payload { message: nes_parser::get_error_message(error).into() }).unwrap();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(INesVersion::INesArchaic) => {
|
||||||
|
app.emit_all("error", Payload { message: "Gotta parse an old iNES version huh".into() }).unwrap();
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
app.emit_all("error", Payload { message: nes_parser::get_error_message(error).into() }).unwrap();
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum INesVersion {
|
||||||
|
INes,
|
||||||
|
INes20,
|
||||||
|
INesArchaic,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum INesError {
|
||||||
|
NotAnINesFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct INesHeader {
|
||||||
|
prg_rom_size: u8,
|
||||||
|
chr_rom_size: u8,
|
||||||
|
mirroring_type: bool,
|
||||||
|
has_battery: bool,
|
||||||
|
has_trainer: bool,
|
||||||
|
has_four_screen_vram: bool,
|
||||||
|
is_vs_unisystem: bool,
|
||||||
|
is_playchoice_10: bool,
|
||||||
|
ines_version_identifier: u8,
|
||||||
|
mapper: u8,
|
||||||
|
prg_ram_size: u8,
|
||||||
|
tv_system: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl INesHeader {
|
||||||
|
|
||||||
|
pub fn from_rom(rom_bytes: &Vec<u8>) -> Result<INesHeader, INesError> {
|
||||||
|
if rom_bytes[0] != 0x4e || rom_bytes[1] != 0x45 || rom_bytes[2] != 0x53 || rom_bytes[3] != 0x1a {
|
||||||
|
return Err(INesError::NotAnINesFile);
|
||||||
|
}
|
||||||
|
Ok(
|
||||||
|
INesHeader {
|
||||||
|
prg_rom_size: rom_bytes[4],
|
||||||
|
chr_rom_size: rom_bytes[5],
|
||||||
|
mirroring_type: rom_bytes[6] & 0x01 != 0,
|
||||||
|
has_battery: rom_bytes[6] & 0x02 != 0,
|
||||||
|
has_trainer: rom_bytes[6] & 0x04 != 0,
|
||||||
|
has_four_screen_vram: rom_bytes[6] & 0x08 != 0,
|
||||||
|
is_vs_unisystem: rom_bytes[7] & 0x01 != 0,
|
||||||
|
is_playchoice_10: rom_bytes[7] & 0x02 != 0,
|
||||||
|
ines_version_identifier: (rom_bytes[7] & 0x0c) >> 2,
|
||||||
|
mapper:
|
||||||
|
((rom_bytes[6] & 0xf0) >> 4) |
|
||||||
|
(rom_bytes[7] & 0xf0),
|
||||||
|
prg_ram_size: rom_bytes[8],
|
||||||
|
tv_system: rom_bytes[9] & 0x01 != 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prg_rom_size(&self) -> u32 {
|
||||||
|
(self.prg_rom_size as u32) * 16384
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chr_rom_size(&self) -> u32 {
|
||||||
|
(self.chr_rom_size as u32) * 8192
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mirroring_type(&self) -> bool {
|
||||||
|
self.mirroring_type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_battery(&self) -> bool {
|
||||||
|
self.has_battery
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_trainer(&self) -> bool {
|
||||||
|
self.has_trainer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_four_screen_vram(&self) -> bool {
|
||||||
|
self.has_four_screen_vram
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_vs_unisystem(&self) -> bool {
|
||||||
|
self.is_vs_unisystem
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_playchoice_10(&self) -> bool {
|
||||||
|
self.is_playchoice_10
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ines_version_identifier(&self) -> u8 {
|
||||||
|
self.ines_version_identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapper(&self) -> u8 {
|
||||||
|
self.mapper
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prg_ram_size(&self) -> u8 {
|
||||||
|
self.prg_ram_size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tv_system(&self) -> bool {
|
||||||
|
self.tv_system
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct INes20Header {
|
||||||
|
prg_rom_size: u16,
|
||||||
|
chr_rom_size: u16,
|
||||||
|
mirroring_type: bool,
|
||||||
|
has_battery: bool,
|
||||||
|
has_trainer: bool,
|
||||||
|
has_four_screen_vram: bool,
|
||||||
|
console_type: u8,
|
||||||
|
mapper: u16,
|
||||||
|
submapper: u8,
|
||||||
|
prg_ram_shift_count: u8,
|
||||||
|
prg_nvram_shift_count: u8,
|
||||||
|
chr_ram_shift_count: u8,
|
||||||
|
chr_nvram_shift_count: u8,
|
||||||
|
cpu_type: u8,
|
||||||
|
console_specific_type: u8,
|
||||||
|
miscellaneous_roms: u8,
|
||||||
|
default_expansion_device: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl INes20Header {
|
||||||
|
|
||||||
|
pub fn from_rom(rom_bytes: &Vec<u8>) -> Result<INes20Header, INesError> {
|
||||||
|
if rom_bytes[0] != 0x4e || rom_bytes[1] != 0x45 || rom_bytes[2] != 0x53 || rom_bytes[3] != 0x1a {
|
||||||
|
return Err(INesError::NotAnINesFile);
|
||||||
|
}
|
||||||
|
Ok(
|
||||||
|
INes20Header {
|
||||||
|
prg_rom_size: (rom_bytes[4] as u16) | (((rom_bytes[9] as u16) << 8) & 0x0f00),
|
||||||
|
chr_rom_size: (rom_bytes[5] as u16) | (((rom_bytes[9] as u16) << 4) & 0x0f00),
|
||||||
|
mirroring_type: (rom_bytes[6] & 0x01) != 0,
|
||||||
|
has_battery: (rom_bytes[6] & 0x02) != 0,
|
||||||
|
has_trainer: (rom_bytes[6] & 0x04) != 0,
|
||||||
|
has_four_screen_vram: (rom_bytes[6] & 0x08) != 0,
|
||||||
|
console_type: rom_bytes[7] & 0x03,
|
||||||
|
mapper:
|
||||||
|
(((rom_bytes[6] as u16) >> 4) & 0x000f) |
|
||||||
|
(((rom_bytes[7] as u16) ) & 0x00f0) |
|
||||||
|
(((rom_bytes[8] as u16) << 8) & 0x0f00),
|
||||||
|
submapper: (rom_bytes[8] >> 4) & 0x0f,
|
||||||
|
prg_ram_shift_count: rom_bytes[10] & 0x0f,
|
||||||
|
prg_nvram_shift_count: (rom_bytes[10] >> 4) & 0x0f,
|
||||||
|
chr_ram_shift_count: rom_bytes[11] & 0x0f,
|
||||||
|
chr_nvram_shift_count: (rom_bytes[11] >> 4) & 0x0f,
|
||||||
|
cpu_type: rom_bytes[12] & 0x03,
|
||||||
|
console_specific_type: rom_bytes[13],
|
||||||
|
miscellaneous_roms: rom_bytes[14] & 0x03,
|
||||||
|
default_expansion_device: rom_bytes[16] & 0x3f,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_prg_rom_size(self) -> u16 {
|
||||||
|
self.prg_rom_size
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_error_message(error: INesError) -> &'static str {
|
||||||
|
match error {
|
||||||
|
INesError::NotAnINesFile => "Not an iNES file",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_version(rom_bytes: &Vec<u8>) -> Result<INesVersion, INesError> {
|
||||||
|
if rom_bytes[0] != 0x4e || rom_bytes[1] != 0x45 || rom_bytes[2] != 0x53 || rom_bytes[3] != 0x1a {
|
||||||
|
return Err(INesError::NotAnINesFile);
|
||||||
|
}
|
||||||
|
let version: u8 = rom_bytes[7] & 0x0c;
|
||||||
|
if version == 0x08 {
|
||||||
|
Ok(INesVersion::INes20)
|
||||||
|
} else if version == 0x04 {
|
||||||
|
Ok(INesVersion::INesArchaic)
|
||||||
|
} else {
|
||||||
|
Ok(INesVersion::INes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,10 @@
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-properties-wrapper">
|
<div class="file-properties-wrapper">
|
||||||
|
<ul id="fileProperties"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="error-wrapper">
|
||||||
|
<p id="errorMessage"></p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,55 @@ const { listen } = window.__TAURI__.event;
|
||||||
const { invoke } = window.__TAURI__.tauri;
|
const { invoke } = window.__TAURI__.tauri;
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", (e) => {
|
window.addEventListener("DOMContentLoaded", (e) => {
|
||||||
console.log(fileUploader);
|
|
||||||
fileUploader.addEventListener("click", (e) => {
|
fileUploader.addEventListener("click", (e) => {
|
||||||
invoke("select_file");
|
invoke("select_file");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
listen("loading", e => {
|
listen("loaded_ines", e => {
|
||||||
document.body.classList.remove([
|
console.log(e);
|
||||||
|
document.body.classList.remove(
|
||||||
"no-file-selected",
|
"no-file-selected",
|
||||||
]);
|
"is-loading",
|
||||||
document.body.classList.add(["is-loading"]);
|
"has-error",
|
||||||
|
);
|
||||||
|
document.body.classList.add("file-loaded");
|
||||||
|
fileProperties.innerHTML = "";
|
||||||
|
[
|
||||||
|
["File name", e.payload.filename],
|
||||||
|
["PRG ROM size", e.payload.prg_rom_size+" bytes"],
|
||||||
|
["CHR ROM size", e.payload.chr_rom_size+" bytes"],
|
||||||
|
["Mirroring Type", e.payload.mirroring_type ? "Vertical" : "Horizontal"],
|
||||||
|
["Has persistent memory", e.payload.has_battery ? "Yes" : "No"],
|
||||||
|
["Has trainer", e.payload.has_trainer ? "Yes" : "No"],
|
||||||
|
["Has four-screen VRAM", e.payload.has_four_screen_vram ? "Yes" : "No"],
|
||||||
|
["Is a VS Unisystem ROM", e.payload.is_vs_unisystem ? "Yes" : "No"],
|
||||||
|
["Is a PlayChoice-10 ROM", e.payload.is_playchoice_10 ? "Yes" : "No"],
|
||||||
|
["iNES version identifier", e.payload.ines_version_identifier],
|
||||||
|
["Mapper", e.payload.mapper],
|
||||||
|
["PRG RAM size", e.payload.prg_ram_size],
|
||||||
|
["TV system", e.payload.tv_system ? "PAL" : "NTSC"],
|
||||||
|
].forEach(el => {
|
||||||
|
let domElement = document.createElement("li");
|
||||||
|
domElement.innerHTML = "<strong>"+el[0]+"</strong>: "+el[1];
|
||||||
|
fileProperties.appendChild(domElement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
listen("loading", e => {
|
||||||
|
document.body.classList.remove(
|
||||||
|
"no-file-selected",
|
||||||
|
"has-error",
|
||||||
|
"file-loaded",
|
||||||
|
);
|
||||||
|
document.body.classList.add("is-loading");
|
||||||
|
});
|
||||||
|
|
||||||
|
listen("error", e => {
|
||||||
|
document.body.classList.remove(
|
||||||
|
"is-loading",
|
||||||
|
"file-loaded",
|
||||||
|
);
|
||||||
|
document.body.classList.add("no-file-selected", "has-error");
|
||||||
|
errorMessage.innerText = e.payload.message;
|
||||||
});
|
});
|
||||||
|
|
@ -14,7 +14,7 @@ h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-file-wrapper {
|
.select-file-wrapper {
|
||||||
display: none;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -24,8 +24,8 @@ h1 {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.no-file-selected .select-file-wrapper {
|
body.file-loaded .select-file-wrapper {
|
||||||
display: flex;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
|
|
@ -39,4 +39,18 @@ body.is-loading .loader {
|
||||||
|
|
||||||
main {
|
main {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-wrapper {
|
||||||
|
display: none;
|
||||||
|
background-color: #eb8c95;
|
||||||
|
color: #66121a;
|
||||||
|
border: 1px solid #66121a;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.5rem 2rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.has-error .error-wrapper {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue