Files
gopher64/src/device.rs
T
Logan McNaughton 93bf103f03 move around RA code (#894)
* move around RA code

* more

* more

* more
2026-05-15 12:36:11 +00:00

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,
},
}
}
}