mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 01:25:20 +02:00
93bf103f03
* move around RA code * more * more * more
532 lines
18 KiB
Rust
532 lines
18 KiB
Rust
#![allow(non_camel_case_types)]
|
|
#![allow(improper_ctypes)]
|
|
|
|
#[cfg(target_arch = "aarch64")]
|
|
#[derive(Copy, Clone)]
|
|
#[allow(dead_code)]
|
|
pub struct __m128i(std::arch::aarch64::int64x2_t);
|
|
#[cfg(target_arch = "aarch64")]
|
|
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
|
#[cfg(target_arch = "x86_64")]
|
|
use std::arch::x86_64::*;
|
|
|
|
const N64_EXTENSIONS_UNCOMPRESSED: [&str; 8] =
|
|
["n64", "v64", "z64", "bin", "N64", "V64", "Z64", "BIN"];
|
|
|
|
use rand::{Rng, SeedableRng};
|
|
|
|
use crate::{cheats, netplay, retroachievements, ui};
|
|
use std::{collections::HashMap, fs, io::Read};
|
|
|
|
pub mod ai;
|
|
pub mod cache;
|
|
pub mod cart;
|
|
pub mod controller;
|
|
pub mod cop0;
|
|
pub mod cop1;
|
|
pub mod cop2;
|
|
pub mod cpu;
|
|
pub mod cpu_instructions;
|
|
pub mod events;
|
|
pub mod exceptions;
|
|
pub mod fpu_instructions;
|
|
pub mod is_viewer;
|
|
pub mod memory;
|
|
pub mod mi;
|
|
pub mod pi;
|
|
pub mod pif;
|
|
pub mod rdp;
|
|
pub mod rdram;
|
|
pub mod ri;
|
|
pub mod rsp_cpu;
|
|
pub mod rsp_interface;
|
|
pub mod rsp_su_instructions;
|
|
pub mod rsp_vu_instructions;
|
|
pub mod si;
|
|
pub mod tlb;
|
|
pub mod unmapped;
|
|
pub mod vi;
|
|
|
|
pub async fn run_game(
|
|
device: &mut Device,
|
|
rom_contents: &[u8],
|
|
game_settings: ui::GameSettings,
|
|
discord_rich_presence: bool,
|
|
) {
|
|
device.cpu.overclock = game_settings.overclock;
|
|
if game_settings.disable_expansion_pak {
|
|
device.rdram.size = 0x400000;
|
|
}
|
|
if let Some(slot) = game_settings.load_savestate_slot {
|
|
device.ui.storage.save_state_slot = slot;
|
|
device.load_state = true;
|
|
}
|
|
|
|
init_rng(device);
|
|
|
|
cart::rom::init(device, rom_contents); // cart needs to come before rdram
|
|
|
|
// rdram pointer is shared with parallel-rdp and retroachievements
|
|
rdram::init(device);
|
|
|
|
ui::video::init(device);
|
|
ui::audio::init(device);
|
|
ui::input::init(&mut device.ui);
|
|
|
|
// must be after video init
|
|
let (discord_watch_tx, discord_handle) =
|
|
retroachievements::load_game(rom_contents, rom_contents.len(), discord_rich_presence).await;
|
|
|
|
mi::init(device);
|
|
pif::init(device);
|
|
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
|
|
controller::vru::init(device);
|
|
}
|
|
memory::init(device);
|
|
cache::init(device);
|
|
rsp_interface::init(device);
|
|
rdp::init(device);
|
|
vi::init(device);
|
|
cpu::init(device);
|
|
|
|
ui::storage::init(&mut device.ui, &device.cart.rom);
|
|
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
|
|
ui::storage::format_saves(device);
|
|
|
|
if !retroachievements::get_hardcore() {
|
|
cheats::init(device, game_settings.cheats);
|
|
}
|
|
|
|
cpu::run(device);
|
|
|
|
retroachievements::unload_game(discord_watch_tx, discord_handle).await;
|
|
|
|
ui::storage::write_saves(device);
|
|
ui::input::close(&mut device.ui);
|
|
ui::audio::close(&mut device.ui);
|
|
ui::video::close(&device.ui);
|
|
}
|
|
|
|
fn set_rng() -> rand::rngs::Xoshiro256PlusPlus {
|
|
rand::rngs::Xoshiro256PlusPlus::try_from_rng(&mut rand::rngs::SysRng).unwrap()
|
|
}
|
|
|
|
fn init_rng(device: &mut Device) {
|
|
let mut rng_seed = set_rng().next_u64();
|
|
if let Some(netplay) = &mut device.netplay {
|
|
if netplay.player_number == 0 {
|
|
netplay::send_rng(netplay, rng_seed);
|
|
} else {
|
|
rng_seed = netplay::receive_rng(netplay);
|
|
}
|
|
}
|
|
device.rng = rand::rngs::Xoshiro256PlusPlus::seed_from_u64(rng_seed);
|
|
}
|
|
|
|
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
|
|
let test = u32::from_be_bytes(contents[0..4].try_into().unwrap());
|
|
if test == 0x80371240 {
|
|
// z64
|
|
Some(contents)
|
|
} else if test == 0x37804012 {
|
|
// v64
|
|
let mut data: Vec<u8> = vec![0; contents.len()];
|
|
for i in (0..contents.len()).step_by(2) {
|
|
let temp = u16::from_ne_bytes(contents[i..i + 2].try_into().unwrap());
|
|
data[i..i + 2].copy_from_slice(&temp.to_be_bytes());
|
|
}
|
|
Some(data)
|
|
} else if test == 0x40123780 {
|
|
// n64
|
|
let mut data: Vec<u8> = vec![0; contents.len()];
|
|
for i in (0..contents.len()).step_by(4) {
|
|
let temp = u32::from_ne_bytes(contents[i..i + 4].try_into().unwrap());
|
|
data[i..i + 4].copy_from_slice(&temp.to_be_bytes());
|
|
}
|
|
Some(data)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
|
|
let mut contents = vec![];
|
|
if file_path
|
|
.extension()
|
|
.unwrap_or_default()
|
|
.eq_ignore_ascii_case("zip")
|
|
{
|
|
let zip_file = fs::File::open(file_path).unwrap();
|
|
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
|
|
for i in 0..archive.len() {
|
|
let mut file = archive.by_index(i).unwrap();
|
|
if let Some(extension) = file.enclosed_name().unwrap().extension()
|
|
&& N64_EXTENSIONS_UNCOMPRESSED.contains(&extension.to_str().unwrap())
|
|
{
|
|
file.read_to_end(&mut contents)
|
|
.expect("could not read zip file");
|
|
break;
|
|
}
|
|
}
|
|
} else if file_path
|
|
.extension()
|
|
.unwrap_or_default()
|
|
.eq_ignore_ascii_case("7z")
|
|
{
|
|
let mut archive =
|
|
sevenz_rust2::ArchiveReader::open(file_path, sevenz_rust2::Password::empty()).unwrap();
|
|
|
|
let mut found = false;
|
|
archive
|
|
.for_each_entries(
|
|
&mut |entry: &sevenz_rust2::ArchiveEntry, reader: &mut dyn std::io::Read| {
|
|
if !found
|
|
&& let Some(extension) = std::path::PathBuf::from(entry.name()).extension()
|
|
&& N64_EXTENSIONS_UNCOMPRESSED.contains(&extension.to_str().unwrap())
|
|
{
|
|
reader
|
|
.read_to_end(&mut contents)
|
|
.expect("could not read zip file");
|
|
found = true;
|
|
} else {
|
|
//skip other files
|
|
std::io::copy(reader, &mut std::io::sink())?;
|
|
}
|
|
Ok(true)
|
|
},
|
|
)
|
|
.expect("ok");
|
|
} else {
|
|
contents = fs::read(file_path)
|
|
.unwrap_or_else(|_| panic!("Could not read ROM file: {}", file_path.display()));
|
|
}
|
|
|
|
if contents.is_empty() {
|
|
None
|
|
} else {
|
|
swap_rom(contents)
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
pub struct Device {
|
|
#[serde(skip)]
|
|
pub netplay: Option<netplay::Netplay>,
|
|
#[serde(skip, default = "ui::Ui::new")]
|
|
pub ui: ui::Ui,
|
|
pub byte_swap: usize,
|
|
pub save_state: bool,
|
|
pub load_state: bool,
|
|
pub cpu: cpu::Cpu,
|
|
pub pif: pif::Pif,
|
|
pub cart: cart::Cart,
|
|
pub memory: memory::Memory,
|
|
pub rsp: rsp_interface::Rsp,
|
|
pub rdp: rdp::Rdp,
|
|
pub rdram: rdram::Rdram,
|
|
pub mi: mi::Mi,
|
|
pub pi: pi::Pi,
|
|
pub vi: vi::Vi,
|
|
pub ai: ai::Ai,
|
|
pub si: si::Si,
|
|
pub ri: ri::Ri,
|
|
#[serde(skip, default = "set_rng")]
|
|
pub rng: rand::rngs::Xoshiro256PlusPlus,
|
|
pub vru: controller::vru::Vru,
|
|
pub transferpaks: [controller::transferpak::TransferPak; 4],
|
|
pub cheats: cheats::Cheats,
|
|
}
|
|
|
|
pub fn zero_m128i() -> __m128i {
|
|
unsafe { _mm_setzero_si128() }
|
|
}
|
|
|
|
impl Device {
|
|
pub fn new() -> Device {
|
|
let mut byte_swap: usize = 0;
|
|
let test: [u8; 4] = [1, 2, 3, 4];
|
|
// if the host computer is little endian, that means the RDRAM will be stored as little endian
|
|
// when accessing bytes in RDRAM, we need to swap them by XORing with the byte_swap value (3)
|
|
if u32::from_le_bytes(test) == u32::from_ne_bytes(test) {
|
|
byte_swap = 3;
|
|
}
|
|
Device {
|
|
netplay: None,
|
|
ui: ui::Ui::new(),
|
|
byte_swap,
|
|
save_state: false,
|
|
load_state: false,
|
|
cpu: cpu::Cpu {
|
|
cop0: cop0::Cop0 {
|
|
regs: [0; cop0::COP0_REGS_COUNT],
|
|
reg_write_masks: [0; cop0::COP0_REGS_COUNT],
|
|
reg_latch: 0,
|
|
is_event: false,
|
|
instrs: [cop0::reserved; 32],
|
|
instrs2: [cop0::reserved; 32],
|
|
tlb_lut_w: vec![
|
|
tlb::TlbLut {
|
|
address: 0,
|
|
cached: false,
|
|
};
|
|
0x100000
|
|
],
|
|
tlb_lut_r: vec![
|
|
tlb::TlbLut {
|
|
address: 0,
|
|
cached: false,
|
|
};
|
|
0x100000
|
|
],
|
|
tlb_entries: [tlb::TlbEntry {
|
|
mask: 0,
|
|
vpn2: 0,
|
|
region: 0,
|
|
g: 0,
|
|
asid: 0,
|
|
pfn_even: 0,
|
|
c_even: 0,
|
|
d_even: 0,
|
|
v_even: 0,
|
|
pfn_odd: 0,
|
|
c_odd: 0,
|
|
d_odd: 0,
|
|
v_odd: 0,
|
|
start_even: 0,
|
|
end_even: 0,
|
|
phys_even: 0,
|
|
start_odd: 0,
|
|
end_odd: 0,
|
|
phys_odd: 0,
|
|
}; 32],
|
|
},
|
|
cop1: cop1::Cop1 {
|
|
fcr0: 0,
|
|
fcr31: 0,
|
|
//#[cfg(target_arch = "x86_64")]
|
|
//flush_mode: 0,
|
|
fgr32: [[0; 4]; 32],
|
|
fgr64: [[0; 8]; 32],
|
|
instrs: [cop1::reserved; 32],
|
|
b_instrs: [cop1::reserved; 4],
|
|
s_instrs: [cop1::reserved; 64],
|
|
d_instrs: [cop1::reserved; 64],
|
|
w_instrs: [cop1::reserved; 64],
|
|
l_instrs: [cop1::reserved; 64],
|
|
},
|
|
cop2: cop2::Cop2 {
|
|
instrs: [cop2::reserved; 32],
|
|
reg_latch: 0,
|
|
},
|
|
branch_state: cpu::BranchState {
|
|
state: cpu::State::Step,
|
|
pc: 0,
|
|
},
|
|
gpr: [0; 32],
|
|
clock_rate: 0,
|
|
pc: 0xBFC00000,
|
|
pc_phys: 0,
|
|
llbit: false,
|
|
overclock: false,
|
|
lo: 0,
|
|
hi: 0,
|
|
running: false,
|
|
instrs: [cop0::reserved; 64],
|
|
special_instrs: [cop0::reserved; 64],
|
|
regimm_instrs: [cop0::reserved; 32],
|
|
events: [events::Event {
|
|
enabled: false,
|
|
count: u64::MAX,
|
|
}; events::EVENT_TYPE_COUNT],
|
|
next_event_count: u64::MAX,
|
|
next_event: events::EVENT_TYPE_NONE,
|
|
},
|
|
pif: pif::Pif {
|
|
rom: [0; 1984],
|
|
ram: [0; 64],
|
|
channels: [pif::PifChannel {
|
|
tx: None,
|
|
tx_buf: None,
|
|
rx: None,
|
|
rx_buf: None,
|
|
process: None,
|
|
pak_handler: None,
|
|
change_pak: controller::PakType::None,
|
|
}; 5],
|
|
},
|
|
cart: cart::Cart {
|
|
rom: Vec::new(),
|
|
is_viewer_buffer: vec![0; 0xFFFF],
|
|
pal: false,
|
|
latch: 0,
|
|
cic_seed: 0,
|
|
rtc_timestamp: 0,
|
|
rtc: cart::AfRtc { control: 0x0200 },
|
|
sc64: cart::sc64::Sc64 {
|
|
regs: [0; cart::sc64::SC64_REGS_COUNT],
|
|
regs_locked: true,
|
|
cfg: [0; cart::sc64::SC64_CFG_COUNT],
|
|
sector: 0,
|
|
buffer: vec![0; 8192],
|
|
writeback_sector: vec![0; 256],
|
|
usb_buffer: vec![],
|
|
},
|
|
flashram: cart::sram::Flashram {
|
|
status: 0,
|
|
erase_page: 0,
|
|
page_buf: [0xff; 128],
|
|
silicon_id: [cart::sram::FLASHRAM_TYPE_ID, cart::sram::MX29L1100_ID],
|
|
mode: cart::sram::FlashramMode::ReadArray,
|
|
},
|
|
},
|
|
memory: memory::Memory {
|
|
fast_read: [unmapped::read_mem_fast; 0x2000],
|
|
memory_map_read: [unmapped::read_mem; 0x2000],
|
|
memory_map_write: [unmapped::write_mem; 0x2000],
|
|
icache: [cache::ICache {
|
|
valid: false,
|
|
index: 0,
|
|
tag: 0,
|
|
words: [0; 8],
|
|
instruction: [cop0::reserved; 8],
|
|
}; 512],
|
|
dcache: [cache::DCache {
|
|
valid: false,
|
|
dirty: false,
|
|
tag: 0,
|
|
index: 0,
|
|
words: [0; 4],
|
|
}; 512],
|
|
},
|
|
rdram: rdram::Rdram {
|
|
mem: vec![],
|
|
size: 0x800000,
|
|
regs: [[0; rdram::RDRAM_REGS_COUNT]; 4],
|
|
},
|
|
rsp: rsp_interface::Rsp {
|
|
cpu: rsp_cpu::Cpu {
|
|
instructions: [rsp_cpu::Instructions {
|
|
func: rsp_su_instructions::reserved,
|
|
opcode: 0,
|
|
}; 0x1000 / 4],
|
|
last_instruction_type: rsp_cpu::InstructionType::Su,
|
|
instruction_type: rsp_cpu::InstructionType::Su,
|
|
pipeline_full: false,
|
|
branch_state: rsp_cpu::BranchState {
|
|
state: cpu::State::Step,
|
|
pc: 0,
|
|
},
|
|
broken: false,
|
|
running: false,
|
|
halted: false,
|
|
sync_point: false,
|
|
cycle_counter: 0,
|
|
gpr: [0; 32],
|
|
vpr: unsafe { [_mm_setzero_si128(); 32] },
|
|
reciprocals: [0; 512],
|
|
inverse_square_roots: [0; 512],
|
|
divdp: false,
|
|
divin: 0,
|
|
divout: 0,
|
|
shuffle: unsafe { [_mm_setzero_si128(); 16] },
|
|
vcol: unsafe { _mm_setzero_si128() },
|
|
vcoh: unsafe { _mm_setzero_si128() },
|
|
vccl: unsafe { _mm_setzero_si128() },
|
|
vcch: unsafe { _mm_setzero_si128() },
|
|
vce: unsafe { _mm_setzero_si128() },
|
|
accl: unsafe { _mm_setzero_si128() },
|
|
accm: unsafe { _mm_setzero_si128() },
|
|
acch: unsafe { _mm_setzero_si128() },
|
|
special_instrs: [rsp_su_instructions::reserved; 64],
|
|
regimm_instrs: [rsp_su_instructions::reserved; 32],
|
|
cop0_instrs: [rsp_su_instructions::reserved; 32],
|
|
cop2_instrs: [rsp_su_instructions::reserved; 32],
|
|
lwc2_instrs: [rsp_su_instructions::reserved; 32],
|
|
swc2_instrs: [rsp_su_instructions::reserved; 32],
|
|
instrs: [rsp_su_instructions::reserved; 64],
|
|
vec_instrs: [rsp_vu_instructions::reserved; 64],
|
|
},
|
|
regs: [0; rsp_interface::SP_REGS_COUNT],
|
|
regs2: [0; rsp_interface::SP_REGS2_COUNT],
|
|
mem: [0; 0x2000],
|
|
last_status_value: 0,
|
|
run_after_dma: false,
|
|
fifo: [rsp_interface::RspDma {
|
|
dir: rsp_interface::DmaDir::None,
|
|
length: 0,
|
|
memaddr: 0,
|
|
dramaddr: 0,
|
|
}; 2],
|
|
},
|
|
rdp: rdp::Rdp {
|
|
regs_dpc: [0; rdp::DPC_REGS_COUNT],
|
|
regs_dps: [0; rdp::DPS_REGS_COUNT],
|
|
wait_frozen: false,
|
|
last_status_value: 0,
|
|
},
|
|
mi: mi::Mi {
|
|
regs: [0; mi::MI_REGS_COUNT],
|
|
},
|
|
pi: pi::Pi {
|
|
regs: [0; pi::PI_REGS_COUNT],
|
|
},
|
|
ai: ai::Ai {
|
|
regs: [0; ai::AI_REGS_COUNT],
|
|
last_read: 0,
|
|
freq: 33600,
|
|
delayed_carry: false,
|
|
fifo: [ai::AiDma {
|
|
address: 0,
|
|
length: 0,
|
|
duration: 0,
|
|
}; 2],
|
|
},
|
|
si: si::Si {
|
|
regs: [0; si::SI_REGS_COUNT],
|
|
dma_dir: si::DmaDir::None,
|
|
},
|
|
ri: ri::Ri {
|
|
regs: [0; ri::RI_REGS_COUNT],
|
|
ram_init: false,
|
|
},
|
|
vi: vi::Vi {
|
|
regs: [0; vi::VI_REGS_COUNT],
|
|
clock: 0,
|
|
field: 0,
|
|
delay: 0,
|
|
count_per_scanline: 0,
|
|
enable_speed_limiter: true,
|
|
next_pace_deadline: None,
|
|
vi_counter: 0,
|
|
min_wait_time: std::time::Duration::from_secs(1),
|
|
frame_time: 0.0,
|
|
elapsed_time: 0.0,
|
|
limit_freq: 2,
|
|
limit_freq_check: std::time::Instant::now(),
|
|
},
|
|
vru: controller::vru::Vru {
|
|
status: 0,
|
|
voice_state: 0,
|
|
load_offset: 0,
|
|
voice_init: 0,
|
|
word_buffer: [0; 40],
|
|
words: Vec::new(),
|
|
talking: false,
|
|
word_mappings: HashMap::new(),
|
|
},
|
|
rng: set_rng(),
|
|
transferpaks: [
|
|
controller::transferpak::TransferPak::default(),
|
|
controller::transferpak::TransferPak::default(),
|
|
controller::transferpak::TransferPak::default(),
|
|
controller::transferpak::TransferPak::default(),
|
|
],
|
|
cheats: cheats::Cheats {
|
|
cheats: vec![],
|
|
boot: true,
|
|
enabled: false,
|
|
},
|
|
}
|
|
}
|
|
}
|