Exportable CHR ROM
parent
80d6305299
commit
147e19865b
|
|
@ -6,8 +6,7 @@
|
||||||
mod nes_parser;
|
mod nes_parser;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Error;
|
use std::io::{Error, Read, Seek, Write};
|
||||||
use std::io::Read;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use nes_parser::INesVersion;
|
use nes_parser::INesVersion;
|
||||||
|
|
@ -35,7 +34,6 @@ struct INesData {
|
||||||
tv_system: bool,
|
tv_system: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
tauri::api::dialog::FileDialogBuilder::default()
|
tauri::api::dialog::FileDialogBuilder::default()
|
||||||
|
|
@ -68,18 +66,6 @@ fn select_file(app: tauri::AppHandle) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match version {
|
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) => {
|
Ok(INesVersion::INes) => {
|
||||||
let header = nes_parser::INesHeader::from_rom(&contents);
|
let header = nes_parser::INesHeader::from_rom(&contents);
|
||||||
match header {
|
match header {
|
||||||
|
|
@ -110,8 +96,11 @@ fn select_file(app: tauri::AppHandle) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Ok(INesVersion::INes20) => {
|
||||||
|
app.emit_all("error", Payload { message: "Can't parse iNES 2.0 ROMs yet. Sorry!".into() }).unwrap();
|
||||||
|
},
|
||||||
Ok(INesVersion::INesArchaic) => {
|
Ok(INesVersion::INesArchaic) => {
|
||||||
app.emit_all("error", Payload { message: "Gotta parse an old iNES version huh".into() }).unwrap();
|
app.emit_all("error", Payload { message: "Can't parse Archaic iNES ROMs yet. Sorry!".into() }).unwrap();
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
app.emit_all("error", Payload { message: nes_parser::get_error_message(error).into() }).unwrap();
|
app.emit_all("error", Payload { message: nes_parser::get_error_message(error).into() }).unwrap();
|
||||||
|
|
@ -123,7 +112,7 @@ fn select_file(app: tauri::AppHandle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn download_chr_ram(filepath: &str, app: tauri::AppHandle) {
|
fn download_chr_rom(filepath: &str, app: tauri::AppHandle) {
|
||||||
let path_buf: PathBuf = PathBuf::from(filepath);
|
let path_buf: PathBuf = PathBuf::from(filepath);
|
||||||
if !path_buf.is_file() {
|
if !path_buf.is_file() {
|
||||||
app.emit_all("error", Payload { message: "File not found".into() }).unwrap();
|
app.emit_all("error", Payload { message: "File not found".into() }).unwrap();
|
||||||
|
|
@ -154,11 +143,49 @@ fn download_chr_ram(filepath: &str, app: tauri::AppHandle) {
|
||||||
app.emit_all("error", Payload { message: "This ROM has no CHR ROM".into() }).unwrap();
|
app.emit_all("error", Payload { message: "This ROM has no CHR ROM".into() }).unwrap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let chr_rom_pointer: u32 =
|
let chr_rom_pointer: u64 =
|
||||||
16 + // header size
|
16 + // header size
|
||||||
(if header.has_trainer() { 512 } else { 0 }) +
|
(if header.has_trainer() { 512 } else { 0 }) +
|
||||||
header.prg_rom_size();
|
(header.prg_rom_size() as u64);
|
||||||
|
let chr_rom_size: u32 = header.chr_rom_size();
|
||||||
let mut chr_rom: Vec<u8> = Vec::new();
|
let mut chr_rom: Vec<u8> = Vec::new();
|
||||||
|
let seek_result: Result<u64, Error> = file.seek(std::io::SeekFrom::Start(chr_rom_pointer as u64));
|
||||||
|
if seek_result.is_err() {
|
||||||
|
app.emit_all("error", Payload { message: "Unable to find CHR ROM".into() }).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let read_result: Result<usize, Error> = file.take(chr_rom_size as u64).read_to_end(&mut chr_rom);
|
||||||
|
if read_result.is_err() {
|
||||||
|
app.emit_all("error", Payload { message: "Unable to read CHR ROM".into() }).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tauri::api::dialog::FileDialogBuilder::default()
|
||||||
|
.set_file_name("chr_rom.bin")
|
||||||
|
.save_file(move |file_path: Option<PathBuf>| {
|
||||||
|
match file_path {
|
||||||
|
Some(file_path) => {
|
||||||
|
let output_file: Result<File, Error> = File::create(file_path);
|
||||||
|
match output_file {
|
||||||
|
Ok(mut output_file) => {
|
||||||
|
let write_result: Result<(), Error> = output_file.write_all(&chr_rom);
|
||||||
|
if write_result.is_err() {
|
||||||
|
app.emit_all("error", Payload { message: "Unable to write file".into() }).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.emit_all("success", Payload { message: "CHR ROM exported successfully!".into() }).unwrap();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
app.emit_all("error", Payload { message: "Unable to write file".into() }).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
/* TODO find CHR ROM and download it */
|
/* TODO find CHR ROM and download it */
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -178,7 +205,7 @@ fn download_chr_ram(filepath: &str, app: tauri::AppHandle) {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![select_file, download_chr_ram])
|
.invoke_handler(tauri::generate_handler![select_file, download_chr_rom])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,64 +101,6 @@ impl INesHeader {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
pub fn get_error_message(error: INesError) -> &'static str {
|
||||||
match error {
|
match error {
|
||||||
INesError::NotAnINesFile => "Not an iNES file",
|
INesError::NotAnINesFile => "Not an iNES file",
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,14 @@
|
||||||
<ul id="fileProperties"></ul>
|
<ul id="fileProperties"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions-wrapper">
|
<div class="actions-wrapper">
|
||||||
<ul id="actions"></ul>
|
<div id="actions"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="error-wrapper">
|
<div class="error-wrapper">
|
||||||
<p id="errorMessage"></p>
|
<p id="errorMessage"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="success-wrapper">
|
||||||
|
<p id="successMessage"></p>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -19,7 +19,7 @@ listen("loaded_ines", e => {
|
||||||
["File path", e.payload.filepath],
|
["File path", e.payload.filepath],
|
||||||
["File name", e.payload.filename],
|
["File name", e.payload.filename],
|
||||||
["PRG ROM size", e.payload.prg_rom_size+" bytes"],
|
["PRG ROM size", e.payload.prg_rom_size+" bytes"],
|
||||||
["CHR ROM size", e.payload.chr_rom_size+" bytes"],
|
["CHR ROM size", e.payload.chr_rom_size+" bytes"+(0 < e.payload.chr_rom_size ? "" : " (uses CHR RAM)")],
|
||||||
["Mirroring Type", e.payload.mirroring_type ? "Vertical" : "Horizontal"],
|
["Mirroring Type", e.payload.mirroring_type ? "Vertical" : "Horizontal"],
|
||||||
["Has persistent memory", e.payload.has_battery ? "Yes" : "No"],
|
["Has persistent memory", e.payload.has_battery ? "Yes" : "No"],
|
||||||
["Has trainer", e.payload.has_trainer ? "Yes" : "No"],
|
["Has trainer", e.payload.has_trainer ? "Yes" : "No"],
|
||||||
|
|
@ -35,13 +35,15 @@ listen("loaded_ines", e => {
|
||||||
domElement.innerHTML = "<strong>"+el[0]+"</strong>: "+el[1];
|
domElement.innerHTML = "<strong>"+el[0]+"</strong>: "+el[1];
|
||||||
fileProperties.appendChild(domElement);
|
fileProperties.appendChild(domElement);
|
||||||
});
|
});
|
||||||
|
actions.innerHTML = '';
|
||||||
if(e.payload.chr_rom_size > 0) {
|
if(e.payload.chr_rom_size > 0) {
|
||||||
let chrRomDownloadButton = document.createElement("button");
|
let chrRomDownloadButton = document.createElement("button");
|
||||||
chrRomDownloadButton.type = "button";
|
chrRomDownloadButton.type = "button";
|
||||||
chrRomDownloadButton.innerText = "Download CHR RAM";
|
chrRomDownloadButton.innerText = "Download CHR ROM";
|
||||||
|
chrRomDownloadButton.classList.add("download-chr-rom-button")
|
||||||
let filepath = e.payload.filepath;
|
let filepath = e.payload.filepath;
|
||||||
chrRomDownloadButton.addEventListener("click", e => {
|
chrRomDownloadButton.addEventListener("click", e => {
|
||||||
invoke("download_chr_ram", {
|
invoke("download_chr_rom", {
|
||||||
filepath: filepath,
|
filepath: filepath,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -58,11 +60,19 @@ listen("loading", e => {
|
||||||
document.body.classList.add("is-loading");
|
document.body.classList.add("is-loading");
|
||||||
});
|
});
|
||||||
|
|
||||||
listen("error", e => {
|
listen("error", e => {
|
||||||
document.body.classList.remove(
|
document.body.classList.remove(
|
||||||
"is-loading",
|
"is-loading",
|
||||||
"file-loaded",
|
|
||||||
);
|
);
|
||||||
document.body.classList.add("no-file-selected", "has-error");
|
document.body.classList.add("no-file-selected", "has-error");
|
||||||
errorMessage.innerText = e.payload.message;
|
errorMessage.innerText = e.payload.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
listen("success", e => {
|
||||||
|
document.body.classList.remove(
|
||||||
|
"is-loading",
|
||||||
|
"has-error",
|
||||||
|
);
|
||||||
|
document.body.classList.add("has-success");
|
||||||
|
successMessage.innerText = e.payload.message;
|
||||||
|
});
|
||||||
|
|
@ -54,3 +54,26 @@ main {
|
||||||
body.has-error .error-wrapper {
|
body.has-error .error-wrapper {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.success-wrapper {
|
||||||
|
display: none;
|
||||||
|
background-color: #a3cfbb;
|
||||||
|
color: #0a3622;
|
||||||
|
border: 1px solid #0a3622;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.5rem 2rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.has-success .success-wrapper {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-wrapper .download-chr-rom-button {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #aaa;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue