Compare commits

...

56 Commits

Author SHA1 Message Date
Logan McNaughton 421d533479 update cargo packages (#231) 2025-02-09 11:45:36 +01:00
Logan McNaughton ab5ef57106 further function cleanup 2025-02-09 11:43:10 +01:00
Logan McNaughton 37b41c0f66 cleanup vi calc functions 2025-02-08 22:41:28 +01:00
Logan McNaughton 4c50203ad3 modify rdp sync code (#230) 2025-02-08 21:39:48 +01:00
Logan McNaughton 271e4be68c disable rdp sync optimization to be safe (#229) 2025-02-08 21:20:59 +01:00
Logan McNaughton a9afd6bb5c Improve RDP emulation performance (#228)
* Improve RDP emulation performance

* more

* more

* more

* more
2025-02-07 22:29:20 +01:00
Logan McNaughton cd2b3dd143 update cargo lock 2025-02-06 14:31:11 +01:00
Logan McNaughton ab31b44aac Bump to 1.0.4 (#227) 2025-02-05 22:19:47 +01:00
Logan McNaughton 58eaad1742 remove some dependencies (#226)
* remove some dependencies

* more

* more

* more

* more
2025-02-05 19:31:38 +01:00
Logan McNaughton a8e7c3c136 add Cargo.lock (#225)
* add Cargo.lock

* more

* more
2025-02-05 17:31:17 +01:00
Logan McNaughton f4099cfcce further sc64 cleanup (#224)
* further sc64 cleanup

* further sc64 cleanup
2025-02-05 13:39:24 +01:00
Logan McNaughton 5b3e2a25de Fix out-of-bounds ROM dma (#223)
* Fix out-of-bounds ROM dma

* Fix out-of-bounds ROM dma

* more
2025-02-05 13:18:44 +01:00
Logan McNaughton 55ab1cb60b Fix address masking in sc64 read/write (#222)
* fix address masking for sc64

* more

* more
2025-02-05 10:29:07 +01:00
dependabot[bot] 778a0a4015 Update eframe requirement from 0.30 to 0.31 (#221)
Updates the requirements on [eframe](https://github.com/emilk/egui) to permit the latest version.
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.30.0...0.31.0)

---
updated-dependencies:
- dependency-name: eframe
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-05 06:40:50 +01:00
Logan McNaughton 96d2a72850 compress sd and rom saves during netplay (#220)
* compress sd and rom saves during netplay

* compress sd and rom saves during netplay
2025-02-04 21:21:47 +01:00
Logan McNaughton 4c7361f963 No need to check for ROM bounds on write (#219)
* resize ROM when writing if needed

* more
2025-02-04 19:50:19 +01:00
Logan McNaughton 80fc5f8ea2 collapse variable 2025-02-04 15:42:29 +01:00
Logan McNaughton f0eb5764c8 emulate summercart64 ROM_WRITE_ENABLE flag (#218)
* emulate summercart64 ROM_WRITE_ENABLE flag

* clean up names
2025-02-04 15:37:55 +01:00
Logan McNaughton 480af8d36e Ignore summercart64 USB commands (#217) 2025-02-04 13:14:57 +01:00
Logan McNaughton ad4eddb408 Fix crash when game name cannot be parsed (#216) 2025-02-04 13:01:33 +01:00
Logan McNaughton 166b7daa9d Bump to 1.0.3 (#215) 2025-02-03 18:50:20 +01:00
Logan McNaughton 920a0323c0 cleanup 2025-02-03 16:49:42 +01:00
Logan McNaughton 14e45be300 SD card emulation (#214)
* SD card emulation

* more

* more

* more

* more

* more

* more

* more

* better save name

* better save name

* better save name

* more
2025-02-03 16:41:46 +01:00
Logan McNaughton fba6a4a6d4 support for Advanced Homebrew ROM Header (#213)
* support for Advanced Homebrew ROM Header

* more

* more
2025-02-03 10:22:21 +01:00
Logan McNaughton 1efdeafc31 bump to 1.0.2 (#212) 2025-02-02 12:54:48 +01:00
Logan McNaughton a7c2c26d78 minor tweak to interrupt system (#211) 2025-02-02 11:23:32 +01:00
Logan McNaughton 7d63a694cc fix compare interrupt (#210) 2025-02-01 20:47:26 +01:00
Logan McNaughton 5c98a08fe4 remove schedule_rcp_interrupt (#208)
* remove schedule_rcp_interrupt

* more

* more

* more
2025-02-01 19:16:38 +01:00
Logan McNaughton 39ffad3af3 Bump to 1.0.1 (#206) 2025-01-30 21:08:05 +01:00
Logan McNaughton ca2a3d423b Bump to 1.0.0 (#205) 2025-01-30 19:33:47 +01:00
Logan McNaughton fe5346235e fix freeze in JFG (#204)
* fix freeze in JFG

* more
2025-01-30 19:32:56 +01:00
Logan McNaughton f84444a12e remove rdp_full_sync (#203)
* remove rdp_full_sync

* swap

* swap
2025-01-30 14:42:04 +01:00
Logan McNaughton 026e4c0e74 Bump to 0.1.26 (#202) 2025-01-29 21:06:36 +01:00
Logan McNaughton 363ed0953a set specific version for apt cache 2025-01-29 20:49:21 +01:00
Logan McNaughton cb9309d7e4 RDP sync updates (#201)
* RDP sync updates

* rename

* rename
2025-01-29 20:21:49 +01:00
Logan McNaughton 1b1313003c dont run rsp task when dma is busy (#200) 2025-01-29 12:50:29 +01:00
Logan McNaughton 7219a4445c add another sync point for RSP (#199) 2025-01-29 11:14:50 +01:00
Logan McNaughton 687badc142 Modify RSP sync points (#198)
* modify RSP sync points

* more

* more

* more

* more

* more

* more

* more
2025-01-29 10:49:29 +01:00
Logan McNaughton 3e2189463f static link MoltenVK (#196)
* static link MoltenVK

* static link MoltenVK

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test
2025-01-28 20:56:42 +01:00
Logan McNaughton ed8b4e8e02 Bump to 0.1.25 (#195) 2025-01-28 14:32:54 +01:00
Logan McNaughton 611121568e cache apt packages in github workflow (#194) 2025-01-28 14:21:28 +01:00
Logan McNaughton 27922fbf9c use TCP_RECEIVE_SAVE_WITH_SIZE for netplay (#193)
* use TCP_RECEIVE_SAVE_WITH_SIZE for netplay

* more

* more

* more
2025-01-28 13:30:59 +01:00
Logan McNaughton 71b7baf613 fix masking pi regs (#192) 2025-01-27 22:50:49 +01:00
Logan McNaughton f4714a7813 add rdram reg that libdragon seems to access (#191)
* add rdram reg that libdragon seems to access

* more
2025-01-27 19:42:36 +01:00
Logan McNaughton 4730b7f293 Bump to 0.1.24 (#190) 2025-01-27 18:12:03 +01:00
Logan McNaughton 6f33934180 on mac, run game in new process (#189)
* on mac, run game in new process

* more

* more

* more

* remove tokio

* clippy

* formatting

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-01-27 18:02:06 +01:00
Logan McNaughton cbc9045ad4 print error message when sdl fails 2025-01-27 12:02:42 +01:00
Logan McNaughton 2148b1c198 Use github arm runner (#188)
* Use github arm runner

* more
2025-01-27 08:52:29 +01:00
Logan McNaughton 3dc28cc603 Don't create UI in same thread as egui (#187) 2025-01-27 07:36:53 +01:00
Logan McNaughton 1f746c5b36 Use SDL_Vulkan_GetVkGetInstanceProcAddr (#186)
* static link MoltenVK

* static link MoltenVK

* test

* test2

* test2

* more

* more

* more
2025-01-26 22:17:50 +01:00
Logan McNaughton a2ba5baaa8 move sse2neon to versioned folder (#185)
* move sse2neon to versioned folder

* more
2025-01-26 19:13:19 +01:00
Logan McNaughton 333865e7de dont create new UI on close (#184)
* dont create new UI on close

* dont create new UI on close

* more

* more

* more

* more

* more
2025-01-26 17:47:17 +01:00
Logan McNaughton b1bb96c7d2 use binary format for rom saves (#183)
* use binary format for rom saves

* more
2025-01-26 15:00:49 +01:00
Logan McNaughton 9e88343c6b check accept value of netplay begin_game message (#182) 2025-01-26 12:26:24 +01:00
Logan McNaughton 75e59ee051 cleanup dir detection (#181)
* cleanup dir detection

* more

* more
2025-01-26 11:27:33 +01:00
Logan McNaughton 952f951620 Bump to 0.1.23 (#180) 2025-01-25 19:14:20 +01:00
37 changed files with 6245 additions and 890 deletions
+20 -83
View File
@@ -11,11 +11,15 @@ env:
jobs:
build-linux:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu]
include:
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
with:
@@ -26,86 +30,16 @@ jobs:
rustflags: ""
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
cat <<EOF > deb822sources
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble-updates
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble-backports
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble
Components: main multiverse universe
Architectures: arm64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble-security
Components: main multiverse universe
Architectures: arm64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble-updates
Components: main multiverse universe
Architectures: arm64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble-backports
Components: main multiverse universe
Architectures: arm64
EOF
sudo mv deb822sources /etc/apt/sources.list.d/ubuntu.sources
sudo dpkg --add-architecture arm64
sudo apt-get update
sudo apt-get -y dist-upgrade
sudo apt-get -y install aptitude
sudo aptitude -y install build-essential:arm64 git:arm64 make:arm64 \
pkg-config:arm64 cmake:arm64 ninja-build:arm64 gnome-desktop-testing:arm64 libasound2-dev:arm64 libpulse-dev:arm64 \
libaudio-dev:arm64 libjack-dev:arm64 libsndio-dev:arm64 libx11-dev:arm64 libxext-dev:arm64 \
libxrandr-dev:arm64 libxcursor-dev:arm64 libxfixes-dev:arm64 libxi-dev:arm64 libxss-dev:arm64 \
libxkbcommon-dev:arm64 libdrm-dev:arm64 libgbm-dev:arm64 libgl1-mesa-dev:arm64 libgles2-mesa-dev:arm64 \
libegl1-mesa-dev:arm64 libdbus-1-dev:arm64 libibus-1.0-dev:arm64 libudev-dev:arm64 fcitx-libs-dev:arm64 \
libpipewire-0.3-dev:arm64 libwayland-dev:arm64 libdecor-0-dev:arm64 liburing-dev:arm64 g++-aarch64-linux-gnu
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get -y dist-upgrade
sudo apt-get -y install build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
- name: Set aarch64 linker
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
version: ${{ matrix.target }}-1.0
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
@@ -139,7 +73,7 @@ jobs:
path: target/${{ matrix.target }}/release/gopher64.exe
build-macos:
runs-on: macos-latest
runs-on: macos-14
strategy:
fail-fast: false
matrix:
@@ -148,12 +82,15 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
- name: Build
run: |
brew install molten-vk
cargo build --release -v --target=${{ matrix.target }}
- name: Upload file
uses: actions/upload-artifact@v4
+10 -9
View File
@@ -21,15 +21,16 @@ jobs:
components: clippy, rustfmt
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
run: |
sudo apt-get update
sudo apt-get -y install build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
version: lint-1.0
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
- name: Lint
-4
View File
@@ -3,10 +3,6 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
Generated
+5146
View File
File diff suppressed because it is too large Load Diff
+7 -13
View File
@@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "0.1.22"
version = "1.0.4"
edition = "2021"
rust-version = "1.82"
@@ -14,28 +14,22 @@ sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
eframe = "0.30"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "wgpu"] }
sha2 = "0.10"
rusttype = "0.9"
tokio = { version = "1.43", features = ["rt-multi-thread", "macros"] }
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.43", features = ["rt-multi-thread", "macros"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
mimalloc = "0.1"
regex = "1.11"
tungstenite = "0.26"
socket2 = "0.5"
fatfs = "0.3"
postcard = { version = "1.1", features = ["use-std"] }
clap = { version = "4.4", features = ["derive"] }
[dependencies.reqwest]
version = "0.12"
default-features = false
features = ["http2","rustls-tls","charset","json"]
[dependencies.rfd]
version = "0.15"
default-features = false
features = ["xdg-portal","tokio"]
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
[build-dependencies]
winres = "0.1"
+9 -1
View File
@@ -109,7 +109,7 @@ fn main() {
if arch == "aarch64" {
let simd_bindings = bindgen::Builder::default()
.header("src/compat/sse2neon.h")
.header("src/compat/sse2neon/v1.8.0/sse2neon.h")
.allowlist_function("_mm_setzero_si128")
.allowlist_function("_mm_set_epi8")
.allowlist_function("_mm_movemask_epi8")
@@ -158,4 +158,12 @@ fn main() {
simd_build.include(".");
simd_build.compile("simd");
}
if os == "macos" {
// Add Homebrew lib path for Apple Silicon Macs
println!("cargo:rustc-link-search=native=/opt/homebrew/lib");
// Add Homebrew lib path for Intel Macs
println!("cargo:rustc-link-search=native=/usr/local/lib");
println!("cargo:rustc-link-lib=static=MoltenVK");
}
}
+9 -24
View File
@@ -3,6 +3,7 @@
#include "rdp_device.hpp"
#include "interface.hpp"
#include "spirv.hpp"
#include <SDL3/SDL_vulkan.h>
using namespace Vulkan;
@@ -175,7 +176,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
wsi->set_present_mode(window_vsync ? PresentMode::SyncToVBlank : PresentMode::UnlockedMaybeTear);
wsi->set_backbuffer_srgb(false);
Context::SystemHandles handles = {};
if (!::Vulkan::Context::init_loader(nullptr))
if (!::Vulkan::Context::init_loader((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()))
{
rdp_close();
}
@@ -329,40 +330,29 @@ bool rdp_update_screen()
return emu_running;
}
static uint32_t viCalculateHorizonalWidth(uint32_t hstart, uint32_t xscale, uint32_t width)
static uint32_t viCalculateHorizonalWidth(uint32_t hstart, uint32_t xscale)
{
if (xscale == 0)
return 320;
uint32_t start = ((hstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t start = (hstart >> 16) & 0x3FF;
uint32_t end = (hstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
delta = 0;
uint32_t scale = (xscale & 0xFFF);
if (delta == 0)
{
delta = width;
}
return (delta * scale) / 0x400;
}
static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
{
if (yscale == 0)
return 240;
uint32_t start = ((vstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t start = (vstart >> 16) & 0x3FF;
uint32_t end = (vstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
delta = 0;
uint32_t scale = (yscale & 0xFFF);
return (delta * scale) / 0x800;
@@ -382,8 +372,6 @@ uint64_t rdp_process_commands()
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
return interrupt_timer;
*gfx_info.DPC_STATUS_REG |= DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK;
uint32_t offset = DP_CURRENT;
if (*gfx_info.DPC_STATUS_REG & DP_STATUS_XBUS_DMA)
{
@@ -434,7 +422,7 @@ uint64_t rdp_process_commands()
{
processor->wait_for_timeline(processor->signal_timeline());
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG, *gfx_info.VI_WIDTH_REG);
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG);
if (width == 0)
{
width = 320;
@@ -444,9 +432,7 @@ uint64_t rdp_process_commands()
{
height = 240;
}
interrupt_timer = width * height * 4;
*gfx_info.DPC_STATUS_REG &= ~(DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK);
interrupt_timer = width * height;
}
cmd_cur += cmd_length;
@@ -455,7 +441,6 @@ uint64_t rdp_process_commands()
cmd_ptr = 0;
cmd_cur = 0;
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
*gfx_info.DPC_STATUS_REG |= DP_STATUS_CBUF_READY;
return interrupt_timer;
}
-1
View File
@@ -20,7 +20,6 @@ extern "C"
uint32_t *VI_V_START_REG;
uint32_t *VI_X_SCALE_REG;
uint32_t *VI_Y_SCALE_REG;
uint32_t *VI_WIDTH_REG;
} GFX_INFO;
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen);
+1 -1
View File
@@ -1,4 +1,4 @@
#include "src/compat/sse2neon.h"
#include "src/compat/sse2neon/v1.8.0/sse2neon.h"
// Static wrappers
+26 -20
View File
@@ -19,7 +19,6 @@ use std::io::Read;
pub mod ai;
pub mod cache;
pub mod cart;
pub mod cart_rom;
pub mod controller;
pub mod cop0;
pub mod cop1;
@@ -42,18 +41,12 @@ pub mod rsp_interface;
pub mod rsp_su_instructions;
pub mod rsp_vu_instructions;
pub mod si;
pub mod sram;
pub mod tlb;
pub mod unmapped;
pub mod vi;
pub fn run_game(
rom_contents: Vec<u8>,
data_dir: std::path::PathBuf,
device: &mut Device,
fullscreen: bool,
) {
cart_rom::init(device, rom_contents); // cart needs to come before rdram
pub fn run_game(rom_contents: Vec<u8>, device: &mut Device, fullscreen: bool) {
cart::rom::init(device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
rdram::init(device);
@@ -73,9 +66,9 @@ pub fn run_game(
vi::init(device);
cpu::init(device);
ui::storage::init(&mut device.ui, data_dir);
ui::storage::init(&mut device.ui, &device.cart.rom);
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
cart_rom::load_rom_save(device);
cart::rom::load_rom_save(device);
cpu::run(device);
@@ -168,23 +161,24 @@ pub struct Device {
byte_swap: usize,
pub cpu: cpu::Cpu,
pif: pif::Pif,
cart: cart_rom::Cart,
cart: cart::rom::Cart,
memory: memory::Memory,
pub rsp: rsp_interface::Rsp,
pub rdp: rdp::Rdp,
pub rdram: rdram::Rdram,
mi: mi::Mi,
pi: pi::Pi,
sc64: cart::sc64::Sc64,
pub vi: vi::Vi,
ai: ai::Ai,
si: si::Si,
ri: ri::Ri,
flashram: sram::Flashram,
flashram: cart::sram::Flashram,
pub vru: controller::vru::Vru,
}
impl Device {
pub fn new(config_dir: std::path::PathBuf) -> 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
@@ -194,13 +188,15 @@ impl Device {
}
Device {
netplay: None,
ui: ui::Ui::new(config_dir),
ui: ui::Ui::new(),
byte_swap,
cpu: cpu::Cpu {
cop0: cop0::Cop0 {
regs: [0; cop0::COP0_REGS_COUNT as usize],
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
reg_latch: 0,
is_event: false,
pending_compare_interrupt: false,
instrs: [cop0::reserved; 32],
instrs2: [cop0::reserved; 32],
tlb_lut_w: vec![
@@ -293,13 +289,13 @@ impl Device {
change_pak: controller::PakType::None,
}; 5],
},
cart: cart_rom::Cart {
cart: cart::rom::Cart {
rom: Vec::new(),
is_viewer_buffer: [0; 0xFFFF],
pal: false,
latch: 0,
cic_seed: 0,
cic_type: cart_rom::CicType::CicNus6102,
cic_type: cart::rom::CicType::CicNus6102,
rdram_size_offset: 0,
rtc: cart::AfRtc { control: 0x0200 },
},
@@ -372,6 +368,8 @@ impl Device {
regs: [0; rsp_interface::SP_REGS_COUNT as usize],
regs2: [0; rsp_interface::SP_REGS2_COUNT as usize],
mem: [0; 0x2000],
last_status_value: 0,
run_after_dma: false,
fifo: [rsp_interface::RspDma {
dir: rsp_interface::DmaDir::None,
length: 0,
@@ -383,6 +381,7 @@ impl Device {
regs_dpc: [0; rdp::DPC_REGS_COUNT as usize],
regs_dps: [0; rdp::DPS_REGS_COUNT as usize],
wait_frozen: false,
last_status_value: 0,
},
mi: mi::Mi {
regs: [0; mi::MI_REGS_COUNT as usize],
@@ -390,6 +389,13 @@ impl Device {
pi: pi::Pi {
regs: [0; pi::PI_REGS_COUNT as usize],
},
sc64: cart::sc64::Sc64 {
regs: [0; cart::sc64::SC64_REGS_COUNT as usize],
regs_locked: true,
cfg: [0; cart::sc64::SC64_CFG_COUNT as usize],
sector: 0,
buffer: [0; 8192],
},
ai: ai::Ai {
regs: [0; ai::AI_REGS_COUNT as usize],
last_read: 0,
@@ -417,12 +423,12 @@ impl Device {
limiter: None,
vi_counter: 0,
},
flashram: sram::Flashram {
flashram: cart::sram::Flashram {
status: 0,
erase_page: 0,
page_buf: [0xff; 128],
silicon_id: [sram::FLASHRAM_TYPE_ID, sram::MX29L1100_ID],
mode: sram::FlashramMode::ReadArray,
silicon_id: [cart::sram::FLASHRAM_TYPE_ID, cart::sram::MX29L1100_ID],
mode: cart::sram::FlashramMode::ReadArray,
},
vru: controller::vru::Vru {
status: 0,
+1 -1
View File
@@ -68,7 +68,7 @@ fn do_dma(device: &mut device::Device) {
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration,
dma_event,
);
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_AI);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
}
fn fifo_push(device: &mut device::Device) {
+4
View File
@@ -4,6 +4,10 @@ use chrono::Timelike;
use crate::device;
use crate::ui;
pub mod rom;
pub mod sc64;
pub mod sram;
const JCMD_STATUS: u8 = 0x00;
const JCMD_EEPROM_READ: u8 = 0x04;
const JCMD_EEPROM_WRITE: u8 = 0x05;
@@ -56,7 +56,25 @@ pub fn read_mem(
}
}
pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u32) {
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if device.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
let masked_address = address as usize & CART_MASK;
let mut data = u32::from_be_bytes(
device.cart.rom[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.cart.rom[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
for i in 0..4 {
device.ui.saves.romsave.0.insert(
(masked_address + i) as u32,
device.cart.rom[masked_address + i],
);
}
device.ui.saves.romsave.1 = true;
}
device.cart.latch = value & mask;
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
@@ -76,24 +94,27 @@ pub fn dma_read(
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
if device.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.cart.rom[(cart_addr + i) as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.cart.rom[(cart_addr + i) as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
device.ui.saves.romsave.0.insert(
(cart_addr + i).to_string(),
serde_json::to_value(device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap])
.unwrap(),
);
device
.ui
.saves
.romsave
.0
.insert(cart_addr + i, device.cart.rom[(cart_addr + i) as usize]);
device.ui.saves.romsave.1 = true;
}
}
}
device.ui.saves.romsave.1 = true;
device::pi::calculate_cycles(device, 1, length)
}
@@ -139,8 +160,7 @@ pub fn load_rom_save(device: &mut device::Device) {
return;
}
for (key, value) in device.ui.saves.romsave.0.iter() {
device.cart.rom[key.parse::<usize>().unwrap()] =
serde_json::from_value(value.clone()).unwrap()
device.cart.rom[*key as usize] = *value
}
}
@@ -185,6 +205,7 @@ fn set_cic(device: &mut device::Device) {
device.cart.cic_type = CicType::CicNus5167;
device.cart.cic_seed = 0xdd;
device.cart.rdram_size_offset = 0x318;
device.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1;
}
_ => {
device.cart.cic_type = CicType::CicNus6102;
+238
View File
@@ -0,0 +1,238 @@
use crate::device;
pub const SDCARD_SIZE: usize = 0x4000000;
const SC64_SCR_REG: u32 = 0;
const SC64_DATA0_REG: u32 = 1;
const SC64_DATA1_REG: u32 = 2;
const SC64_IDENTIFIER_REG: u32 = 3;
const SC64_KEY_REG: u32 = 4;
//const SC64_IRQ_REG: u32 = 5;
//const SC64_AUX_REG: u32 = 6;
pub const SC64_REGS_COUNT: u32 = 7;
pub const SC64_ROM_WRITE_ENABLE: u32 = 1;
pub const SC64_CFG_COUNT: u32 = 15;
const SC64_BUFFER_MASK: usize = 0x1FFF;
pub struct Sc64 {
pub buffer: [u8; 8192],
pub regs: [u32; SC64_REGS_COUNT as usize],
pub regs_locked: bool,
pub cfg: [u32; SC64_CFG_COUNT as usize],
pub sector: u32,
}
fn format_sdcard(device: &mut device::Device) {
if device.ui.saves.sdcard.0.is_empty() {
device.ui.saves.sdcard.0.resize(SDCARD_SIZE, 0);
let buf = std::io::Cursor::new(&mut device.ui.saves.sdcard.0);
fatfs::format_volume(buf, fatfs::FormatVolumeOptions::new()).unwrap();
}
}
pub fn read_regs(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
if device.sc64.regs_locked {
return 0;
}
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SC64_SCR_REG | SC64_DATA0_REG | SC64_DATA1_REG => device.sc64.regs[reg as usize],
SC64_IDENTIFIER_REG => 0x53437632,
_ => panic!("unknown read reg {} address {:#x}", reg, address),
}
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SC64_KEY_REG => {
device::memory::masked_write_32(&mut device.sc64.regs[reg as usize], value, mask);
if device.sc64.regs[SC64_KEY_REG as usize] == 0x4F434B5F {
device.sc64.regs_locked = false;
} else if device.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF {
device.sc64.regs_locked = true;
}
}
SC64_DATA0_REG | SC64_DATA1_REG => {
if !device.sc64.regs_locked {
device::memory::masked_write_32(&mut device.sc64.regs[reg as usize], value, mask);
}
}
SC64_SCR_REG => {
if !device.sc64.regs_locked {
match char::from_u32(value & mask).unwrap() {
'c' => {
// get config
device.sc64.regs[SC64_DATA1_REG as usize] =
device.sc64.cfg[device.sc64.regs[SC64_DATA0_REG as usize] as usize]
}
'C' => {
// set config
std::mem::swap(
&mut device.sc64.cfg
[device.sc64.regs[SC64_DATA0_REG as usize] as usize],
&mut device.sc64.regs[SC64_DATA1_REG as usize],
);
}
'i' => {
// sd card operation
match device.sc64.regs[SC64_DATA1_REG as usize] {
0 => { //Init SD card
}
1 => { //Deinit SD card
}
_ => {
panic!(
"unknown sc64 sd card operation: {}",
device.sc64.regs[SC64_DATA1_REG as usize]
)
}
}
}
'I' => {
// set sd sector
device.sc64.sector = device.sc64.regs[SC64_DATA0_REG as usize];
}
's' => {
format_sdcard(device);
// read sd card
let address = device.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let offset = (device.sc64.sector * 512) as usize;
let length = (device.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize;
let mut i = 0;
while i < length {
if offset + i < device.ui.saves.sdcard.0.len() {
let data = u32::from_be_bytes(
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
.try_into()
.unwrap(),
);
device::memory::data_write(
device,
address + i as u64,
data,
0xFFFFFFFF,
false,
);
} else {
panic!("sd card read out of bounds")
}
i += 4;
}
}
'S' => {
format_sdcard(device);
// write sd card
let address = device.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let offset = (device.sc64.sector * 512) as usize;
let length = (device.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize;
let mut i = 0;
while i < length {
if offset + i < device.ui.saves.sdcard.0.len() {
let data = device::memory::data_read(
device,
address + i as u64,
device::memory::AccessSize::Word,
false,
)
.to_be_bytes();
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
.copy_from_slice(&data);
} else {
panic!("sd card write out of bounds")
}
i += 4;
}
device.ui.saves.sdcard.1 = true;
}
'U' => {} // USB_WRITE_STATUS, ignored
'M' => {} // USB_WRITE, ignored
_ => {
panic!(
"unknown sc64 command: {}",
char::from_u32(value & mask).unwrap()
)
}
}
}
}
_ => panic!(
"unknown write reg {} address {:#x} value {}",
reg,
address,
char::from_u32(value & mask).unwrap()
),
}
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & SC64_BUFFER_MASK;
u32::from_be_bytes(
device.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let masked_address = address as usize & SC64_BUFFER_MASK;
let mut data = u32::from_be_bytes(
device.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.sc64.buffer[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
}
pub fn dma_read(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.sc64.buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
pub fn dma_write(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.rdram.mem[i as usize ^ device.byte_swap] = device.sc64.buffer[j as usize];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
+98 -359
View File
@@ -288,10 +288,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303CF00A80003035D001200F900EA"),
String::from("pikachu"),
),
(
String::from("040801740024"),
String::from("hey!"),
),
(String::from("040801740024"), String::from("hey!")),
(
String::from("03CF00C603360405000F0234"),
String::from("come-here"),
@@ -324,10 +321,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E2006903FC000303B402E2018F"),
String::from("lets-play"),
),
(
String::from("040B00C002EB0213"),
String::from("hello"),
),
(String::from("040B00C002EB0213"), String::from("hello")),
(
String::from("043B0213000303AB00A5034803FC006903FC00A503270024"),
String::from("open-sesame"),
@@ -356,14 +350,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD033603FC012602F10024"),
String::from("im-sorry"),
),
(
String::from("03FC023A0024"),
String::from("sorry"),
),
(
String::from("03FC012602F10024"),
String::from("sorry"),
),
(String::from("03FC023A0024"), String::from("sorry")),
(String::from("03FC012602F10024"), String::from("sorry")),
(
String::from("042F01B00003036000ED03DB030C00EA"),
String::from("i-choose-you"),
@@ -392,10 +380,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303D8000303CF00C9"),
String::from("pikka"),
),
(
String::from("03A50024000303CF00A8"),
String::from("pika"),
),
(String::from("03A50024000303CF00A8"), String::from("pika")),
(
String::from("03A50024000303CF00A8000303A50024000303CF00A8"),
String::from("pikapika"),
@@ -440,10 +425,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03F600C60348000603870234"),
String::from("thunder"),
),
(
String::from("03F600C6033F0234"),
String::from("thunder"),
),
(String::from("03F600C6033F0234"), String::from("thunder")),
(
String::from("0432009F02E20066000303D8000303C602F10045000303D802F40087000303C6"),
String::from("electric-rat"),
@@ -489,7 +471,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("boing-boing"),
),
(
String::from("043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366"),
String::from(
"043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366",
),
String::from("on-to-the-next-stage!"),
),
(
@@ -516,10 +500,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("039F020A02EE0006038700A5033C0087000303AB009F02EE"),
String::from("golden-apple"),
),
(
String::from("03A5002400030360"),
String::from("peach"),
),
(String::from("03A5002400030360"), String::from("peach")),
(
String::from("03FC000303C602FD012C00060372029D0024"),
String::from("strawberry"),
@@ -556,10 +537,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032101A702EE00060390040B02340006037E"),
String::from("wild-herb"),
),
(
String::from("03D502D60348"),
String::from("corn"),
),
(String::from("03D502D60348"), String::from("corn")),
(
String::from("03B1014D000303B4000303D502D60348"),
String::from("popcorn"),
@@ -592,18 +570,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CC008102F700A8000303C6"),
String::from("carrot"),
),
(
String::from("03CC02A300A8000303C6"),
String::from("carrot"),
),
(String::from("03CC02A300A8000303C6"), String::from("carrot")),
(
String::from("043200C60339030900A50348"),
String::from("onion"),
),
(
String::from("043200A5033F00A50348"),
String::from("onion"),
),
(String::from("043200A5033F00A50348"), String::from("onion")),
(
String::from("043501080339030900A50348"),
String::from("onion"),
@@ -621,7 +593,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("sweet-potato"),
),
(
String::from("0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213"),
String::from(
"0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213",
),
String::from("hot-sweet-potato"),
),
(
@@ -732,10 +706,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CF00C9000303B4000303CC018F000303D8"),
String::from("cupcake"),
),
(
String::from("03840174002703DB0024"),
String::from("daisy"),
),
(String::from("03840174002703DB0024"), String::from("daisy")),
(
String::from("03A50045000303C000E70339030900A8"),
String::from("petunia"),
@@ -768,10 +739,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC00C6034803F002EB01DD0234"),
String::from("sunflower"),
),
(
String::from("02DF003C02DF0024"),
String::from("lily"),
),
(String::from("02DF003C02DF0024"), String::from("lily")),
(
String::from("02FD021603DE0006037500C900060390"),
String::from("rose-bud"),
@@ -852,10 +820,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D5014D000303AB0234000303D501CE0348"),
String::from("copper-coin"),
),
(
String::from("02FA00EA0006036F0024"),
String::from("ruby"),
),
(String::from("02FA00EA0006036F0024"), String::from("ruby")),
(
String::from("03FC008A03F001AA0300"),
String::from("sapphire"),
@@ -904,10 +869,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("037502340006039003F00069036C0234"),
String::from("bird-feather"),
),
(
String::from("037B020A02EE000303C6"),
String::from("bolt"),
),
(String::from("037B020A02EE000303C6"), String::from("bolt")),
(
String::from("032A0087000603A203390045000303C6"),
String::from("magnet"),
@@ -920,10 +882,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0333025500060375009F02EE"),
String::from("marble"),
),
(
String::from("03D802F4018F000303C6"),
String::from("crate"),
),
(String::from("03D802F4018F000303C6"), String::from("crate")),
(
String::from("03D50255000603900006037B02D9000603900006037B014D000303D803FC"),
String::from("cardboard-box"),
@@ -936,10 +895,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("043B021603EA022804050297"),
String::from("over-here"),
),
(
String::from("040B009F02EB0213"),
String::from("hello"),
),
(String::from("040B009F02EB0213"), String::from("hello")),
(
String::from("039C010B00060390034501950024000303C6"),
String::from("good-night"),
@@ -972,18 +928,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("036C0087000303C603FC03F001AD0348"),
String::from("thats-fine"),
),
(
String::from("043B0213000303CC01740024"),
String::from("ok"),
),
(
String::from("04020276"),
String::from("sure"),
),
(
String::from("02FD01B0000303C6"),
String::from("right"),
),
(String::from("043B0213000303CC01740024"), String::from("ok")),
(String::from("04020276"), String::from("sure")),
(String::from("02FD01B0000303C6"), String::from("right")),
(
String::from("02E2006903FC0006038A00ED036C0087000303C6"),
String::from("lets-do-that"),
@@ -1012,18 +959,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CF00C9000303BD00A8000303C301F2000303C6"),
String::from("cut-it-out"),
),
(
String::from("03450213"),
String::from("no"),
),
(String::from("03450213"), String::from("no")),
(
String::from("0318018F0006038700A80006039F0213"),
String::from("way-to-go"),
),
(
String::from("03F002EB01DD0234"),
String::from("flower"),
),
(String::from("03F002EB01DD0234"), String::from("flower")),
(
String::from("038400840348000303C603FC"),
String::from("dance"),
@@ -1104,14 +1045,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03F90009030C00E102E2018F000303BD0234"),
String::from("see-you-later"),
),
(
String::from("03F90009030C00EA"),
String::from("see-you"),
),
(
String::from("037B01B0"),
String::from("bye"),
),
(String::from("03F90009030C00EA"), String::from("see-you")),
(String::from("037B01B0"), String::from("bye")),
(
String::from("0411031B00A8000303BD0219030900A80006038A00CF0042035A"),
String::from("what-are-you-doing?"),
@@ -1164,10 +1099,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("04290045000303C603FC021603EA0237036C02B8"),
String::from("its-over-there"),
),
(
String::from("02DF003C02DF0024"),
String::from("lily"),
),
(String::from("02DF003C02DF0024"), String::from("lily")),
(
String::from("03FC00C6034803F002EB01DD0234"),
String::from("sunflower"),
@@ -1188,10 +1120,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0438012C0006038100480402"),
String::from("oddish"),
),
(
String::from("03A202E800E70336"),
String::from("gloom"),
),
(String::from("03A202E800E70336"), String::from("gloom")),
(
String::from("03EA01A702EE000303B402E800E70336"),
String::from("vileplume"),
@@ -1200,14 +1129,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD033603FC02BE0024"),
String::from("im-sorry"),
),
(
String::from("03FC02BE0024"),
String::from("sorry"),
),
(
String::from("03CC0084033F00A50348"),
String::from("cannon"),
),
(String::from("03FC02BE0024"), String::from("sorry")),
(String::from("03CC0084033F00A50348"), String::from("cannon")),
(
String::from("03810045000603930045000303C301F2000303C6"),
String::from("dig-it-out"),
@@ -1216,18 +1139,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03810045000603A2036C017A0234"),
String::from("dig-there"),
),
(
String::from("03810045000603A2"),
String::from("dig"),
),
(
String::from("03AE010202EE"),
String::from("pull"),
),
(
String::from("033F00C9000303C6"),
String::from("nut"),
),
(String::from("03810045000603A2"), String::from("dig")),
(String::from("03AE010202EE"), String::from("pull")),
(String::from("033F00C9000303C6"), String::from("nut")),
(
String::from("03780108033C0084033F00C9"),
String::from("banana"),
@@ -1244,10 +1158,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0372029D002400060390000303C602F4006903E40234"),
String::from("buried-treasure"),
),
(
String::from("0408005D02EB0213"),
String::from("hello"),
),
(String::from("0408005D02EB0213"), String::from("hello")),
(
String::from("042F01A702E500CC03E7030C00EA"),
String::from("i-love-you"),
@@ -1312,26 +1223,17 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("041101290348000303BD0234"),
String::from("haunter"),
),
(
String::from("02FD0129035A"),
String::from("wrong"),
),
(String::from("02FD0129035A"), String::from("wrong")),
(
String::from("036C0087000303C603DE02FD0129035A"),
String::from("thats-wrong"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03CF00C9000303B700450006038D01F2000303C6"),
String::from("cut-it-out"),
),
(
String::from("03CF00A503270297"),
String::from("c-mere"),
),
(String::from("03CF00A503270297"), String::from("c-mere")),
(
String::from("03CF00A5033604050297"),
String::from("come-here"),
@@ -1348,26 +1250,17 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0321012C0003036001F2000303C6"),
String::from("watch-out"),
),
(
String::from("036C02B8"),
String::from("there"),
),
(String::from("036C02B8"), String::from("there")),
(
String::from("03FC03150042035A034501F2"),
String::from("swing-now"),
),
(
String::from("03FC03150042035A"),
String::from("swing"),
),
(String::from("03FC03150042035A"), String::from("swing")),
(
String::from("039F020D02FD01B0000303C6"),
String::from("go-right"),
),
(
String::from("02FD01B0000303C6"),
String::from("right"),
),
(String::from("02FD01B0000303C6"), String::from("right")),
(
String::from("03F00258036C022E02FD01B0000303C6"),
String::from("farther-right"),
@@ -1376,10 +1269,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("039F020A02E2006903F0000303C6"),
String::from("go-left"),
),
(
String::from("02E2006903F0000303C6"),
String::from("left"),
),
(String::from("02E2006903F0000303C6"), String::from("left")),
(
String::from("03F00258036C022B02E2006903F0000303C6"),
String::from("farther-left"),
@@ -1472,10 +1362,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02DF004803F900420348000303BD00A503270024"),
String::from("listen-to-me"),
),
(
String::from("03F002FA00EA000303C6"),
String::from("fruit"),
),
(String::from("03F002FA00EA000303C6"), String::from("fruit")),
(
String::from("03450213000303C6000303A8008700060390"),
String::from("notepad"),
@@ -1500,14 +1387,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0411014D000303C603FC03150024000303B4000303BA018F0006038D0213"),
String::from("hot-sweet-potato"),
),
(
String::from("03D200E102EE"),
String::from("cool"),
),
(
String::from("03F001AD0348"),
String::from("fine"),
),
(String::from("03D200E102EE"), String::from("cool")),
(String::from("03F001AD0348"), String::from("fine")),
(
String::from("0309023703FC0213000303C9030C00EA000303C6"),
String::from("youre-so-cute"),
@@ -1572,14 +1453,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A2033F00A8000303C6"),
String::from("magnet"),
),
(
String::from("03D501CE0348"),
String::from("coin"),
),
(
String::from("036600D5009F02EE"),
String::from("jewel"),
),
(String::from("03D501CE0348"), String::from("coin")),
(String::from("036600D5009F02EE"), String::from("jewel")),
(
String::from("0333025500060375009F02EE"),
String::from("marble"),
@@ -1596,10 +1471,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D50315004803DE000303C301AD0336"),
String::from("quiz-time"),
),
(
String::from("03B7002703E70024"),
String::from("tv"),
),
(String::from("03B7002703E70024"), String::from("tv")),
(
String::from("042F01B0000303D503150045000303C6"),
String::from("i-quit"),
@@ -1616,10 +1488,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03150024000303CF00A50348000303B402E2018F"),
String::from("we-can-play"),
),
(
String::from("0372008700060390"),
String::from("bad"),
),
(String::from("0372008700060390"), String::from("bad")),
(
String::from("0345014D000303C90045000303C3012F03F0"),
String::from("knock-it-off"),
@@ -1636,10 +1505,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD03360345014D000303C6000303B402E201740042035A"),
String::from("im-not-playing"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03FC000303C3014D000303B4036C0087000303C6"),
String::from("stop-that"),
@@ -1660,10 +1526,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D802DF00210348036C0087000303BD00C9000303B4"),
String::from("clean-that-up"),
),
(
String::from("03B70048040200EA"),
String::from("tissue"),
),
(String::from("03B70048040200EA"), String::from("tissue")),
(
String::from("037500C002EE0006037500AB03FC02D9"),
String::from("bulbasaur"),
@@ -1768,10 +1631,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC000303A502880213"),
String::from("spearow"),
),
(
String::from("03ED02880213"),
String::from("fearow"),
),
(String::from("03ED02880213"), String::from("fearow")),
(
String::from("04260024000303CF00A5034803DE"),
String::from("ekans"),
@@ -1788,10 +1648,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303CF00A80003036000EA"),
String::from("pikachu"),
),
(
String::from("02FD01B00003036000EA"),
String::from("raichu"),
),
(String::from("02FD01B00003036000EA"), String::from("raichu")),
(
String::from("03FC00840348040202FA00EA"),
String::from("sandshrew"),
@@ -1884,22 +1741,13 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0438012C0006038100480402"),
String::from("oddish"),
),
(
String::from("03A202E800E70336"),
String::from("gloom"),
),
(String::from("03A202E800E70336"), String::from("gloom")),
(
String::from("03EA01A702EE000303B402E800E70336"),
String::from("vileplume"),
),
(
String::from("03B1024000AB03FC"),
String::from("paras"),
),
(
String::from("03A802A300AB03FC"),
String::from("paras"),
),
(String::from("03B1024000AB03FC"), String::from("paras")),
(String::from("03A802A300AB03FC"), String::from("paras")),
(
String::from("03B1024000AB03FC0066000303D8000303C6"),
String::from("parasect"),
@@ -1920,10 +1768,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03810045000603A202E500A8000303C6"),
String::from("diglett"),
),
(
String::from("0327001501F503F6"),
String::from("meowth"),
),
(String::from("0327001501F503F6"), String::from("meowth")),
(
String::from("03AB023703E400A50348"),
String::from("persian"),
@@ -2024,14 +1869,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A202F4008A03EA02E50234"),
String::from("graveler"),
),
(
String::from("039F020A02E500A50336"),
String::from("golem"),
),
(
String::from("039F014402EE0336"),
String::from("golem"),
),
(String::from("039F020A02E500A50336"), String::from("golem")),
(String::from("039F014402EE0336"), String::from("golem")),
(
String::from("03B1021003390024000303BD00A8"),
String::from("ponyta"),
@@ -2060,18 +1899,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A2033F00A8000303C3014A0348"),
String::from("magneton"),
),
(
String::from("0008"),
String::from("farfetch"),
),
(String::from("0008"), String::from("farfetch")),
(
String::from("038D02130006039002F100150213"),
String::from("dodrio"),
),
(
String::from("03F9001B02EE"),
String::from("seel"),
),
(String::from("03F9001B02EE"), String::from("seel")),
(
String::from("038A00EA0006039F014A035A"),
String::from("dewgong"),
@@ -2080,14 +1913,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("038A00EA0006039F014A0348"),
String::from("dewgong"),
),
(
String::from("03A202FD01AD032D0234"),
String::from("grimer"),
),
(
String::from("032D00C9000303D8"),
String::from("muk"),
),
(String::from("03A202FD01AD032D0234"), String::from("grimer")),
(String::from("032D00C9000303D8"), String::from("muk")),
(
String::from("0402005D02EE000603870234"),
String::from("shellder"),
@@ -2100,10 +1927,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0396008A03FC000303C602DF00"),
String::from("gastly"),
),
(
String::from("0396008A03FC02DF0024"),
String::from("gastly"),
),
(String::from("0396008A03FC02DF0024"), String::from("gastly")),
(
String::from("041101290348000303BD0234"),
String::from("haunter"),
@@ -2232,10 +2056,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CC018C035A0006039900AB03FC000303D5014A0348"),
String::from("kangaskhan"),
),
(
String::from("041102DC03F90024"),
String::from("horsea"),
),
(String::from("041102DC03F90024"), String::from("horsea")),
(
String::from("03F900240006039002F700A8"),
String::from("seadra"),
@@ -2260,10 +2081,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0327004803FC000303BD0231033301AD0336"),
String::from("mr.mime"),
),
(
String::from("03FC01B3036C0234"),
String::from("scyther"),
),
(String::from("03FC01B3036C0234"), String::from("scyther")),
(
String::from("03630042035A000303D803FC"),
String::from("jynx"),
@@ -2280,18 +2098,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A203330255"),
String::from("magmar"),
),
(
String::from("03A50042034803FC0234"),
String::from("pinsir"),
),
(
String::from("03C3020D02FD012F03FC"),
String::from("tauros"),
),
(
String::from("03C30246021603FC"),
String::from("tauros"),
),
(String::from("03A50042034803FC0234"), String::from("pinsir")),
(String::from("03C3020D02FD012F03FC"), String::from("tauros")),
(String::from("03C30246021603FC"), String::from("tauros")),
(
String::from("032A0087000603630045000303D50255000303B4"),
String::from("magikarp"),
@@ -2312,14 +2121,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E20087000303B402F700AB03FC"),
String::from("lapras"),
),
(
String::from("03810045000303C30213"),
String::from("ditto"),
),
(
String::from("0426002703E70024"),
String::from("eevee"),
),
(String::from("03810045000303C30213"), String::from("ditto")),
(String::from("0426002703E70024"), String::from("eevee")),
(
String::from("03EA018F000303B102BE0015014A0348"),
String::from("vaporeon"),
@@ -2416,10 +2219,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0327030C00EA000303C000EA"),
String::from("mewtwo"),
),
(
String::from("0327030C00EA"),
String::from("mew"),
),
(String::from("0327030C00EA"), String::from("mew")),
(
String::from("03C3014D000303AB00AB03EA000603630045000603A202DF0024000303AB00AB03F0"),
String::from("top-of-jigglypuff"),
@@ -2456,10 +2256,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("038700C9000603A2000303C602F100150213"),
String::from("dugtrio"),
),
(
String::from("03A50045000603630024"),
String::from("pidgey"),
),
(String::from("03A50045000603630024"), String::from("pidgey")),
(
String::from("038D02130006038A00DB0213"),
String::from("doduo"),
@@ -2528,30 +2325,18 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E2006903FC000303B402E2018F"),
String::from("lets-play"),
),
(
String::from("02FD0129035A"),
String::from("wrong"),
),
(String::from("02FD0129035A"), String::from("wrong")),
(
String::from("0372008700060390000303A50024000303CF00A80003036000EA"),
String::from("bad-pikachu"),
),
(
String::from("0327004803FC"),
String::from("miss"),
),
(String::from("0327004803FC"), String::from("miss")),
(
String::from("038D02100348000303C6000303D5012302DF0045000303C6"),
String::from("dont-call-it"),
),
(
String::from("041101F503FC"),
String::from("house"),
),
(
String::from("041102100336"),
String::from("home"),
),
(String::from("041101F503FC"), String::from("house")),
(String::from("041102100336"), String::from("home")),
(
String::from("03EA00A202F1004500060381000F00C60348"),
String::from("viridian"),
@@ -2560,10 +2345,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03EA00A202F1004500060381000F00C6034803F002C400AB03FC000303C6"),
String::from("viridian-forest"),
),
(
String::from("043B0213000303CF0234"),
String::from("ochre"),
),
(String::from("043B0213000303CF0234"), String::from("ochre")),
(
String::from("043B0213000303CF0225031E010B0006039003DE"),
String::from("ochre-woods"),
@@ -2604,10 +2386,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D502130006037E03ED004803FF0042035A0411020A02EE"),
String::from("cobalt-fishing-hole"),
),
(
String::from("03D5021603FC000303C6"),
String::from("coast"),
),
(String::from("03D5021603FC000303C6"), String::from("coast")),
(
String::from("03D502130006037B012302EE000303C6000303D5021603FC000303C6"),
String::from("cobalt-coast"),
@@ -2620,16 +2399,15 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03BD0231033F00A8000303B4"),
String::from("turnip"),
),
(
String::from("03BD02310348000303B4"),
String::from("turnip"),
),
(String::from("03BD02310348000303B4"), String::from("turnip")),
(
String::from("03FC03150024000303C6000303AB00A8000303BA018F0006038D0213"),
String::from("sweet-potato"),
),
(
String::from("0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213"),
String::from(
"0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213",
),
String::from("hot-sweet-potato"),
),
(
@@ -2640,18 +2418,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("04080084034B00420348036C02B8"),
String::from("hang-in-there"),
),
(
String::from("03AE010202EE"),
String::from("pull"),
),
(String::from("03AE010202EE"), String::from("pull")),
(
String::from("02F1001B02DF0045000303B700420348"),
String::from("reel-it-in"),
),
(
String::from("034501F2"),
String::from("now"),
),
(String::from("034501F2"), String::from("now")),
(
String::from("03CC00870003035D0045000303C6"),
String::from("catch-it"),
@@ -2676,14 +2448,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02F1003C02DF002703F90045000303C6"),
String::from("release-it"),
),
(
String::from("02E500C6034800030360"),
String::from("lunch"),
),
(
String::from("03FC033C0087000303D8"),
String::from("snack"),
),
(String::from("02E500C6034800030360"), String::from("lunch")),
(String::from("03FC033C0087000303D8"), String::from("snack")),
(
String::from("02E2006903F90024000303C6"),
String::from("lets-eat"),
@@ -2700,10 +2466,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01B0000603930045000303B70045000303C6"),
String::from("i-get-it"),
),
(
String::from("0411032101B0"),
String::from("why?"),
),
(String::from("0411032101B0"), String::from("why?")),
(
String::from("041101F2000303CF00C60336"),
String::from("how-come?"),
@@ -2728,10 +2491,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC000303C3014D000303B4036C0087000303C6"),
String::from("stop-that"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03CF00C9000303B70045000303C301F2000303C6"),
String::from("cut-it-out"),
@@ -2744,10 +2504,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E20066000303C603DE0006039F0213"),
String::from("lets-go"),
),
(
String::from("031B0225012F03F0"),
String::from("we-re-off"),
),
(String::from("031B0225012F03F0"), String::from("we-re-off")),
(
String::from("03BA008700060393030902190045000303C6"),
String::from("tag-youre-it"),
@@ -2804,18 +2561,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("039F0213000303BD00C6033302460213"),
String::from("go-tomorrow"),
),
(
String::from("02FD014D000303D8"),
String::from("rock"),
),
(String::from("02FD014D000303D8"), String::from("rock")),
(
String::from("03F9004803DE023703FC"),
String::from("scissors"),
),
(
String::from("03A8018F000303AB0234"),
String::from("paper"),
),
(String::from("03A8018F000303AB0234"), String::from("paper")),
(
String::from("0381002A000303B10255000303C6"),
String::from("depart"),
@@ -2872,10 +2623,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03450213000602E50042032A002A000303C6"),
String::from("no-limit"),
),
(
String::from("03B7005A0348"),
String::from("ten"),
),
(String::from("03B7005A0348"), String::from("ten")),
(
String::from("03E7002A03F6000303B700090348"),
String::from("fifteen"),
@@ -2888,18 +2636,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03C30318004B0339038A002403EA019E03EA"),
String::from("twenty-five"),
),
(
String::from("036C02AC03BA0027"),
String::from("thirty"),
),
(String::from("036C02AC03BA0027"), String::from("thirty")),
(
String::from("036C02AC03BA002703EA019E03EA"),
String::from("thirty-five"),
),
(
String::from("03EA02D6000603900048"),
String::from("forty"),
),
(String::from("03EA02D6000603900048"), String::from("forty")),
(
String::from("03EA02D600060390004803EA019E03EA"),
String::from("forty-five"),
@@ -2928,10 +2670,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC004B036C00630348000303B7000903EA019E03EA"),
String::from("seventy-five"),
),
(
String::from("042C018C03B70009"),
String::from("eighty"),
),
(String::from("042C018C03B70009"), String::from("eighty")),
(
String::from("042C018C03B7000903EA019E03EA"),
String::from("eighty-five"),
+6 -5
View File
@@ -55,14 +55,14 @@ pub const COP0_CAUSE_EXCCODE_CPU: u64 = 11 << 2;
pub const COP0_CAUSE_EXCCODE_TR: u64 = 13 << 2;
pub const COP0_CAUSE_EXCCODE_FPE: u64 = 15 << 2;
pub const COP0_CAUSE_IP2: u64 = 1 << 10;
const COP0_CAUSE_IP7: u64 = 1 << 15;
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
pub const COP0_CAUSE_BD: u64 = 1 << 31;
pub const COP0_CAUSE_CE1: u64 = 1 << 28;
pub const COP0_CAUSE_CE2: u64 = 1 << 29;
pub const COP0_CAUSE_IP_MASK: u64 = 0b00000000000000001111111100000000;
pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
//pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
pub const COP0_CONTEXT_BADVPN2_MASK: u64 = 0b00000000011111111111111111110000;
pub const COP0_XCONTEXT_BADVPN2_MASK: u64 = 0b01111111111111111111111111110000;
pub const COP0_XCONTEXT_REGION_MASK: u64 = 0b110000000000000000000000000000000;
@@ -97,6 +97,8 @@ pub struct Cop0 {
pub tlb_lut_r: Vec<device::tlb::TlbLut>,
pub tlb_lut_w: Vec<device::tlb::TlbLut>,
pub tlb_entries: [device::tlb::TlbEntry; 32],
pub is_event: bool,
pub pending_compare_interrupt: bool,
}
fn mfc0(device: &mut device::Device, opcode: u32) {
@@ -214,6 +216,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
compare_event,
);
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = false;
}
COP0_STATUS_REG => {
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR {
@@ -231,9 +234,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
}
fn compare_event(device: &mut device::Device) {
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] |= COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = true;
device::events::create_event(
device,
device::events::EventType::Compare,
+3 -1
View File
@@ -8,7 +8,7 @@ pub enum EventType {
PI,
DP,
SP,
InterruptCheck,
Interrupt,
SPDma,
Compare,
Vru,
@@ -53,7 +53,9 @@ pub fn trigger_event(device: &mut device::Device) {
device.cpu.events[next_event_name].enabled = false;
let handler = device.cpu.events[next_event_name].handler;
device.cpu.cop0.is_event = true;
handler(device);
device.cpu.cop0.is_event = false;
set_next_event(device);
}
+32 -9
View File
@@ -1,15 +1,6 @@
use crate::device;
pub fn check_pending_interrupts(device: &mut device::Device) {
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& (device::cop0::COP0_STATUS_IE
| device::cop0::COP0_STATUS_EXL
@@ -20,6 +11,38 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
return;
}
if device.mi.regs[device::mi::MI_INTR_REG as usize]
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
!= 0
{
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
} else if device.cpu.cop0.pending_compare_interrupt {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = false;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if device.cpu.cop0.is_event {
interrupt_exception(device);
} else {
device::events::create_event(
device,
device::events::EventType::Interrupt,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
interrupt_exception,
);
}
}
pub fn interrupt_exception(device: &mut device::Device) {
exception_general(device, 0x180);
}
+14 -6
View File
@@ -16,9 +16,11 @@ const MM_SI_REGS: usize = 0x04800000;
//const MM_DOM2_ADDR1: usize = 0x05000000;
pub const MM_DOM2_ADDR2: usize = 0x08000000;
pub const MM_CART_ROM: usize = 0x10000000;
const MM_PIF_MEM: usize = 0x1fc00000;
pub const MM_PIF_MEM: usize = 0x1fc00000;
//const MM_DOM1_ADDR3: usize = 0x1fd00000;
const MM_IS_VIEWER: usize = 0x13ff0000;
pub const MM_SC64_BUFFER: usize = 0x1ffe0000;
const MM_SC64_REGS: usize = 0x1fff0000;
#[derive(PartialEq)]
pub enum AccessType {
@@ -142,18 +144,24 @@ pub fn init(device: &mut device::Device) {
device.memory.memory_map_read[i] = device::si::read_regs;
device.memory.memory_map_write[i] = device::si::write_regs;
} else if (MM_DOM2_ADDR2 >> 16..=(MM_DOM2_ADDR2 + 0x1FFFF) >> 16).contains(&i) {
device.memory.memory_map_read[i] = device::sram::read_mem;
device.memory.memory_map_write[i] = device::sram::write_mem;
device.memory.memory_map_read[i] = device::cart::sram::read_mem;
device.memory.memory_map_write[i] = device::cart::sram::write_mem;
} else if i >= MM_CART_ROM >> 16 && i <= (MM_CART_ROM + device.cart.rom.len() - 1) >> 16 {
device.memory.fast_read[i] = device::cart_rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart_rom::read_mem;
device.memory.memory_map_write[i] = device::cart_rom::write_mem;
device.memory.fast_read[i] = device::cart::rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart::rom::read_mem;
device.memory.memory_map_write[i] = device::cart::rom::write_mem;
} else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::is_viewer::read_mem;
device.memory.memory_map_write[i] = device::is_viewer::write_mem;
} else if i >= MM_PIF_MEM >> 16 && i <= (MM_PIF_MEM + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::pif::read_mem;
device.memory.memory_map_write[i] = device::pif::write_mem;
} else if i >= MM_SC64_BUFFER >> 16 && i <= (MM_SC64_BUFFER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::cart::sc64::read_mem;
device.memory.memory_map_write[i] = device::cart::sc64::write_mem;
} else if i >= MM_SC64_REGS >> 16 && i <= (MM_SC64_REGS + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::cart::sc64::read_regs;
device.memory.memory_map_write[i] = device::cart::sc64::write_regs;
}
}
device::cache::init(device)
+3 -31
View File
@@ -2,8 +2,8 @@ use crate::device;
const MI_INIT_MODE_REG: u32 = 0;
const MI_VERSION_REG: u32 = 1;
const MI_INTR_REG: u32 = 2;
const MI_INTR_MASK_REG: u32 = 3;
pub const MI_INTR_REG: u32 = 2;
pub const MI_INTR_MASK_REG: u32 = 3;
pub const MI_REGS_COUNT: u32 = 4;
/* read */
@@ -64,11 +64,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
}
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
} else {
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
@@ -149,37 +145,13 @@ pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn schedule_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::events::create_event(
device,
device::events::EventType::InterruptCheck,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
device::exceptions::check_pending_interrupts,
)
}
pub fn init(device: &mut device::Device) {
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
}
+49 -11
View File
@@ -114,17 +114,27 @@ fn dma_write(device: &mut device::Device) {
fn get_handler(address: u32) -> PiHandler {
let mut handler = PiHandler {
read: device::cart_rom::dma_read,
write: device::cart_rom::dma_write,
read: device::cart::rom::dma_read,
write: device::cart::rom::dma_write,
};
if address >= device::memory::MM_CART_ROM as u32 {
handler.read = device::cart_rom::dma_read;
handler.write = device::cart_rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32 {
handler.read = device::sram::dma_read;
handler.write = device::sram::dma_write;
if address >= device::memory::MM_SC64_BUFFER as u32
&& address < (device::memory::MM_SC64_BUFFER + 0x2000) as u32
{
handler.read = device::cart::sc64::dma_read;
handler.write = device::cart::sc64::dma_write;
} else if address >= device::memory::MM_CART_ROM as u32
&& address < device::memory::MM_PIF_MEM as u32
{
handler.read = device::cart::rom::dma_read;
handler.write = device::cart::rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32
&& address < device::memory::MM_CART_ROM as u32
{
handler.read = device::cart::sram::dma_read;
handler.write = device::cart::sram::dma_write;
} else {
panic!("unknown pi handler")
handler.read = unknown_dma_read;
handler.write = unknown_dma_write;
}
handler
}
@@ -149,10 +159,15 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
device.pi.regs[PI_STATUS_REG as usize] = 0
}
}
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM1_PGS_REG | PI_BSD_DOM1_RLS_REG
| PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG | PI_BSD_DOM2_PGS_REG | PI_BSD_DOM2_RLS_REG => {
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG => {
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xFF, mask)
}
PI_BSD_DOM1_PGS_REG | PI_BSD_DOM2_PGS_REG => {
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xF, mask)
}
PI_BSD_DOM1_RLS_REG | PI_BSD_DOM2_RLS_REG => {
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0x3, mask)
}
_ => device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value, mask),
}
}
@@ -189,3 +204,26 @@ pub fn dma_event(device: &mut device::Device) {
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
}
fn unknown_dma_read(
device: &mut device::Device,
mut _cart_addr: u32,
mut _dram_addr: u32,
length: u32,
) -> u64 {
device::pi::calculate_cycles(device, 1, length)
}
fn unknown_dma_write(
device: &mut device::Device,
mut _cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
for i in 0..length {
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] = 0;
}
device::pi::calculate_cycles(device, 1, length)
}
+26 -1
View File
@@ -45,6 +45,7 @@ pub struct Rdp {
pub regs_dpc: [u32; DPC_REGS_COUNT as usize],
pub regs_dps: [u32; DPS_REGS_COUNT as usize],
pub wait_frozen: bool,
pub last_status_value: u32,
}
pub fn read_regs_dpc(
@@ -52,7 +53,26 @@ pub fn read_regs_dpc(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device.rdp.regs_dpc[((address & 0xFFFF) >> 2) as usize]
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
DPC_STATUS_REG => {
let value =
device.rdp.regs_dpc[reg as usize] & (DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
if value == device.rdp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
device.rdp.last_status_value = value;
device.rdp.regs_dpc[reg as usize]
}
DPC_CLOCK_REG => 0xFFFFFF, // needed for JFG
DPC_CURRENT_REG => {
if device.rdp.wait_frozen {
device.rsp.cpu.sync_point = true;
}
device.rdp.regs_dpc[reg as usize]
}
_ => device.rdp.regs_dpc[reg as usize],
}
}
pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -93,6 +113,8 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
fn run_rdp(device: &mut device::Device) {
let timer = ui::video::process_rdp_list();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
if timer != 0 {
device::events::create_event(
@@ -174,5 +196,8 @@ pub fn init(device: &mut device::Device) {
}
fn rdp_interrupt_event(device: &mut device::Device) {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
}
+3 -2
View File
@@ -73,16 +73,17 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let chip_id = address >> 13 & 3;
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
match reg as u32 {
RDRAM_MODE_REG => device.pi.regs[reg as usize] ^ 0xc0c0c0c0,
0x80 => 0x00000000, //Row, needed for libdragon
_ => device.rdram.regs[chip_id as usize][reg as usize],
}
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let chip_id = address >> 13 & 3;
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
device::memory::masked_write_32(
&mut device.rdram.regs[chip_id as usize][reg as usize],
+44 -14
View File
@@ -81,6 +81,8 @@ pub struct Rsp {
pub regs2: [u32; SP_REGS2_COUNT as usize],
pub mem: [u8; 0x2000],
pub fifo: [RspDma; 2],
pub last_status_value: u32,
pub run_after_dma: bool,
}
pub fn read_mem_fast(
@@ -249,7 +251,11 @@ fn fifo_pop(device: &mut device::Device) {
do_dma(device, device.rsp.fifo[0])
} else {
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 0;
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY;
if device.rsp.run_after_dma {
device.rsp.run_after_dma = false;
do_task(device);
}
}
}
@@ -260,8 +266,30 @@ pub fn read_regs(
) -> u32 {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SP_DMA_BUSY_REG | SP_DMA_FULL_REG => {
if device.rsp.regs[reg as usize] != 0 {
device.rsp.cpu.sync_point = true;
}
device.rsp.regs[reg as usize]
}
SP_STATUS_REG => {
let value = device.rsp.regs[reg as usize]
& !(SP_STATUS_HALT | SP_STATUS_BROKE | SP_STATUS_INTR_BREAK);
if value == device.rsp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
if value & (SP_STATUS_DMA_BUSY | SP_STATUS_DMA_FULL) != 0 {
device.rsp.cpu.sync_point = true;
}
device.rsp.last_status_value = value;
device.rsp.regs[reg as usize]
}
SP_SEMAPHORE_REG => {
let value = device.rsp.regs[reg as usize];
if value == 1 {
device.rsp.cpu.sync_point = true;
device.rsp.cpu.cycle_counter += 4; // needed for DK64
}
device.rsp.regs[reg as usize] = 1;
value
}
@@ -284,9 +312,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
SP_SEMAPHORE_REG => {
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], 0, mask)
}
_ => {
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
}
_ => device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask),
}
}
@@ -335,7 +361,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
}
/* set SP interrupt */
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
}
/* clear / set single step */
@@ -421,20 +447,25 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
device.rsp.cpu.broken = false;
device.rsp.cpu.halted = false;
device.rsp.cpu.sync_point = false;
do_task(device);
}
}
fn do_task(device: &mut device::Device) {
let timer = device::rsp_cpu::run(device);
device.rsp.cpu.sync_point = false;
device.rsp.last_status_value = 0;
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
device.rsp.run_after_dma = true
} else {
let timer = device::rsp_cpu::run(device);
device::events::create_event(
device,
device::events::EventType::SP,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
rsp_event,
)
device::events::create_event(
device,
device::events::EventType::SP,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
rsp_event,
)
}
}
fn rsp_event(device: &mut device::Device) {
@@ -450,7 +481,6 @@ fn rsp_event(device: &mut device::Device) {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
return;
}
device.rsp.cpu.sync_point = false;
do_task(device)
}
+1 -4
View File
@@ -565,8 +565,6 @@ pub fn mfc0(device: &mut device::Device, opcode: u32) {
device::memory::AccessSize::Word,
)
}
device.rsp.cpu.cycle_counter += 4; // needed for DK64
device.rsp.cpu.sync_point = true;
}
pub fn mtc0(device: &mut device::Device, opcode: u32) {
@@ -592,7 +590,6 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
!device::rsp_interface::SP_STATUS_HALT; // set halt when event happens
device.rsp.cpu.halted = true // the RSP can halt itself by setting SP_SET_HALT
}
device.rsp.cpu.sync_point = true;
}
pub fn mfc2(device: &mut device::Device, opcode: u32) {
@@ -721,7 +718,7 @@ pub fn lsv(device: &mut device::Device, opcode: u32) {
modify_vpr16(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element / 2,
(device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8
((device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8)
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u16),
);
} else {
+1 -1
View File
@@ -5,7 +5,7 @@ use governor::clock::Clock;
const VI_STATUS_REG: u32 = 0;
//const VI_ORIGIN_REG: u32 = 1;
pub const VI_WIDTH_REG: u32 = 2;
//const VI_WIDTH_REG: u32 = 2;
//const VI_V_INTR_REG: u32 = 3;
const VI_CURRENT_REG: u32 = 4;
//const VI_BURST_REG: u32 = 5;
+18 -22
View File
@@ -66,35 +66,21 @@ struct Args {
#[tokio::main]
async fn main() {
let exe_path = std::env::current_exe().unwrap();
let portable_dir = exe_path.parent();
let portable = portable_dir.unwrap().join("portable.txt").exists();
let config_dir;
let cache_dir;
let data_dir;
if portable {
config_dir = portable_dir.unwrap().join("portable_data").join("config");
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
data_dir = portable_dir.unwrap().join("portable_data").join("data");
} else {
config_dir = dirs::config_dir().unwrap().join("gopher64");
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
data_dir = dirs::data_dir().unwrap().join("gopher64");
};
let dirs = ui::get_dirs();
let mut result = std::fs::create_dir_all(config_dir.clone());
let mut result = std::fs::create_dir_all(dirs.config_dir);
if result.is_err() {
panic!("could not create config dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(cache_dir.clone());
result = std::fs::create_dir_all(dirs.cache_dir.clone());
if result.is_err() {
panic!("could not create cache dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(data_dir.clone().join("saves"));
result = std::fs::create_dir_all(dirs.data_dir.join("saves"));
if result.is_err() {
panic!("could not create data dir: {}", result.err().unwrap())
}
let running_file = cache_dir.join("game_running");
let running_file = dirs.cache_dir.join("game_running");
if running_file.exists() {
result = std::fs::remove_file(running_file);
if result.is_err() {
@@ -106,7 +92,7 @@ async fn main() {
let args_as_strings: Vec<String> = std::env::args().collect();
let args_count = args_as_strings.len();
if args_count > 1 {
let mut device = device::Device::new(config_dir);
let mut device = device::Device::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut device.ui);
@@ -166,7 +152,7 @@ async fn main() {
println!("Could not read rom file");
return;
}
device::run_game(rom_contents, data_dir, &mut device, args.fullscreen);
device::run_game(rom_contents, &mut device, args.fullscreen);
}
} else {
let options = eframe::NativeOptions {
@@ -178,12 +164,22 @@ async fn main() {
),
..Default::default()
};
let controllers_paths;
let controller_names;
{
let game_ui = ui::Ui::new();
controllers_paths = gui::get_controller_paths(&game_ui);
controller_names = gui::get_controller_names(&game_ui);
}
eframe::run_native(
"gopher64",
options,
Box::new(|cc| {
Ok(Box::new(ui::gui::GopherEguiApp::new(
cc, config_dir, cache_dir, data_dir,
cc,
controllers_paths,
controller_names,
)))
}),
)
+10 -7
View File
@@ -11,12 +11,13 @@ const UDP_SYNC_DATA: u8 = 4;
//TCP packet formats
const TCP_SEND_SAVE: u8 = 1;
const TCP_RECEIVE_SAVE: u8 = 2;
//const TCP_RECEIVE_SAVE: u8 = 2;
//const TCP_SEND_SETTINGS: u8 = 3;
//const TCP_RECEIVE_SETTINGS: u8 = 4;
const TCP_REGISTER_PLAYER: u8 = 5;
const TCP_GET_REGISTRATION: u8 = 6;
const TCP_DISCONNECT_NOTICE: u8 = 7;
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
const CS4: u32 = 32;
@@ -57,19 +58,21 @@ pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size:
request.push(0); // null terminate string
request.extend_from_slice(&(size as u32).to_be_bytes());
let mut send_data = save_data.to_owned();
send_data.resize(size, 0); // pad with zeros if needed
request.extend(send_data);
if size > 0 {
request.extend(save_data.to_owned());
}
netplay.tcp_stream.write_all(&request).unwrap();
}
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>, size: usize) {
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE].to_vec();
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>) {
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE_WITH_SIZE].to_vec();
request.extend_from_slice(save_type.as_bytes());
request.push(0); // null terminate string
netplay.tcp_stream.write_all(&request).unwrap();
let mut response: Vec<u8> = vec![0; size];
let mut size: [u8; 4] = [0; 4];
netplay.tcp_stream.read_exact(&mut size).unwrap();
let mut response: Vec<u8> = vec![0; u32::from_be_bytes(size) as usize];
netplay.tcp_stream.read_exact(&mut response).unwrap();
*save_data = response;
}
+42 -21
View File
@@ -6,10 +6,16 @@ pub mod storage;
pub mod video;
pub mod vru;
pub struct Dirs {
pub config_dir: std::path::PathBuf,
pub cache_dir: std::path::PathBuf,
pub data_dir: std::path::PathBuf,
}
pub struct Ui {
pub dirs: Dirs,
pub keyboard_state: *const bool,
pub controllers: [input::Controllers; 4],
pub config_file_path: std::path::PathBuf,
pub config: config::Config,
pub save_type: Vec<storage::SaveTypes>,
pub game_id: String,
@@ -32,26 +38,47 @@ impl Drop for Ui {
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
}
write_config(self);
}
}
fn write_config(ui: &Ui) {
let f = std::fs::File::create(ui.config_file_path.clone()).unwrap();
serde_json::to_writer_pretty(f, &ui.config).unwrap();
}
pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) {
unsafe {
let init = sdl3_sys::init::SDL_WasInit(0);
if init & flag == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
panic!("Could not initialize SDL subsystem: {}", flag);
let err = std::ffi::CStr::from_ptr(sdl3_sys::error::SDL_GetError())
.to_str()
.unwrap();
panic!("Could not initialize SDL subsystem: {}, {}", flag, err);
}
}
}
pub fn get_dirs() -> Dirs {
let exe_path = std::env::current_exe().unwrap();
let portable_dir = exe_path.parent();
let portable = portable_dir.unwrap().join("portable.txt").exists();
let config_dir;
let cache_dir;
let data_dir;
if portable {
config_dir = portable_dir.unwrap().join("portable_data").join("config");
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
data_dir = portable_dir.unwrap().join("portable_data").join("data");
} else {
config_dir = dirs::config_dir().unwrap().join("gopher64");
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
data_dir = dirs::data_dir().unwrap().join("gopher64");
};
Dirs {
config_dir,
cache_dir,
data_dir,
}
}
impl Ui {
pub fn new(config_dir: std::path::PathBuf) -> Ui {
pub fn new() -> Ui {
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
let mut num_joysticks = 0;
let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
@@ -59,15 +86,7 @@ impl Ui {
panic!("Could not get joystick list");
}
let config_file_path = config_dir.join("config.json");
let config_file = std::fs::read(config_file_path.clone());
let mut config_map = config::Config::new();
if config_file.is_ok() {
let result = serde_json::from_slice(config_file.unwrap().as_ref());
if result.is_ok() {
config_map = result.unwrap();
}
}
let dirs = get_dirs();
Ui {
controllers: [
@@ -93,8 +112,7 @@ impl Ui {
},
],
keyboard_state: std::ptr::null_mut(),
config_file_path,
config: config_map,
config: config::Config::new(),
save_type: vec![],
game_id: String::new(),
game_hash: String::new(),
@@ -103,6 +121,7 @@ impl Ui {
fla_file_path: std::path::PathBuf::new(),
sra_file_path: std::path::PathBuf::new(),
pak_file_path: std::path::PathBuf::new(),
sdcard_file_path: std::path::PathBuf::new(),
romsave_file_path: std::path::PathBuf::new(),
},
saves: storage::Saves {
@@ -110,7 +129,8 @@ impl Ui {
sram: (Vec::new(), false),
flash: (Vec::new(), false),
mempak: (Vec::new(), false),
romsave: (serde_json::Map::new(), false),
sdcard: (Vec::new(), false),
romsave: (std::collections::HashMap::new(), false),
},
pak_audio: audio::PakAudio {
mempak: include_bytes!("../data/mempak.wav").to_vec(),
@@ -123,6 +143,7 @@ impl Ui {
audio_device: 0,
num_joysticks,
joysticks,
dirs,
}
}
}
+1 -1
View File
@@ -116,7 +116,7 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
let min_latency = (device.ui.audio_freq * 0.02) * 4.0;
if audio_queued < min_latency {
let silence_buffer: Vec<u8> = vec![0; (min_latency - audio_queued) as usize & !3];
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
+22
View File
@@ -31,8 +31,30 @@ pub struct Config {
pub video: Video,
}
impl Drop for Config {
fn drop(&mut self) {
write_config(self);
}
}
fn write_config(config: &Config) {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("config.json");
let f = std::fs::File::create(file_path).unwrap();
serde_json::to_writer_pretty(f, &config).unwrap();
}
impl Config {
pub fn new() -> Config {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("config.json");
let config_file = std::fs::read(file_path);
if config_file.is_ok() {
let result = serde_json::from_slice(config_file.unwrap().as_ref());
if let Ok(result) = result {
return result;
}
}
let mut input_profiles = std::collections::HashMap::new();
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
Config {
+166 -113
View File
@@ -2,12 +2,11 @@ use crate::device;
use crate::netplay;
use crate::ui;
use eframe::egui;
pub mod gui_netplay;
pub struct GopherEguiApp {
config_dir: std::path::PathBuf,
cache_dir: std::path::PathBuf,
data_dir: std::path::PathBuf,
dirs: ui::Dirs,
configure_profile: bool,
profile_name: String,
controller_names: Vec<String>,
@@ -15,6 +14,7 @@ pub struct GopherEguiApp {
selected_profile: [String; 4],
input_profiles: Vec<String>,
controller_enabled: [bool; 4],
controller_paths: Vec<String>,
upscale: bool,
integer_scaling: bool,
fullscreen: bool,
@@ -38,10 +38,10 @@ struct SaveConfig {
emulate_vru: bool,
}
fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
let mut profiles = vec![];
for key in game_ui.config.input.input_profiles.keys() {
profiles.push((*key).clone())
for key in config.input.input_profiles.keys() {
profiles.push(key.clone())
}
profiles
}
@@ -81,22 +81,14 @@ pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<String> {
impl GopherEguiApp {
pub fn new(
cc: &eframe::CreationContext<'_>,
config_dir: std::path::PathBuf,
cache_dir: std::path::PathBuf,
data_dir: std::path::PathBuf,
controller_paths: Vec<String>,
controller_names: Vec<String>,
) -> GopherEguiApp {
add_japanese_font(&cc.egui_ctx);
let game_ui = ui::Ui::new(config_dir.clone());
let controller_paths = get_controller_paths(&game_ui);
add_fonts(&cc.egui_ctx);
let config = ui::config::Config::new();
let mut selected_controller = [-1, -1, -1, -1];
for (pos, item) in game_ui
.config
.input
.controller_assignment
.iter()
.enumerate()
{
for (pos, item) in config.input.controller_assignment.iter().enumerate() {
if item.is_some() {
for (path_pos, path) in controller_paths.iter().enumerate() {
if item.as_deref().unwrap() == *path {
@@ -107,54 +99,55 @@ impl GopherEguiApp {
}
}
GopherEguiApp {
cache_dir: cache_dir.clone(),
config_dir: config_dir.clone(),
data_dir: data_dir.clone(),
configure_profile: false,
profile_name: "".to_string(),
selected_profile: game_ui.config.input.input_profile_binding.clone(),
selected_profile: config.input.input_profile_binding.clone(),
selected_controller,
controller_names: get_controller_names(&game_ui),
input_profiles: get_input_profiles(&game_ui),
controller_enabled: game_ui.config.input.controller_enabled,
upscale: game_ui.config.video.upscale,
integer_scaling: game_ui.config.video.integer_scaling,
fullscreen: game_ui.config.video.fullscreen,
emulate_vru: game_ui.config.input.emulate_vru,
controller_names,
input_profiles: get_input_profiles(&config),
controller_enabled: config.input.controller_enabled,
upscale: config.video.upscale,
integer_scaling: config.video.integer_scaling,
fullscreen: config.video.fullscreen,
emulate_vru: config.input.emulate_vru,
show_vru_dialog: false,
dinput: false,
controller_paths,
netplay_error_receiver: None,
vru_window_receiver: None,
vru_word_notifier: None,
vru_word_list: Vec::new(),
netplay: Default::default(),
dirs: ui::get_dirs(),
}
}
}
fn save_config(game_ui: &mut ui::Ui, save_config_items: SaveConfig) {
let controller_paths = get_controller_paths(game_ui);
fn save_config(
config: &mut ui::config::Config,
controller_paths: Vec<String>,
save_config_items: SaveConfig,
) {
for (pos, item) in save_config_items.selected_controller.iter().enumerate() {
if *item != -1 {
game_ui.config.input.controller_assignment[pos] =
config.input.controller_assignment[pos] =
Some(controller_paths[*item as usize].clone());
} else {
game_ui.config.input.controller_assignment[pos] = None
config.input.controller_assignment[pos] = None
}
}
game_ui.config.input.input_profile_binding = save_config_items.selected_profile;
game_ui.config.input.controller_enabled = save_config_items.controller_enabled;
config.input.input_profile_binding = save_config_items.selected_profile;
config.input.controller_enabled = save_config_items.controller_enabled;
game_ui.config.video.upscale = save_config_items.upscale;
game_ui.config.video.integer_scaling = save_config_items.integer_scaling;
game_ui.config.video.fullscreen = save_config_items.fullscreen;
game_ui.config.input.emulate_vru = save_config_items.emulate_vru;
config.video.upscale = save_config_items.upscale;
config.video.integer_scaling = save_config_items.integer_scaling;
config.video.fullscreen = save_config_items.fullscreen;
config.input.emulate_vru = save_config_items.emulate_vru;
}
impl Drop for GopherEguiApp {
fn drop(&mut self) {
let mut game_ui = ui::Ui::new(self.config_dir.clone());
let save_config_items = SaveConfig {
selected_controller: self.selected_controller,
selected_profile: self.selected_profile.clone(),
@@ -164,7 +157,12 @@ impl Drop for GopherEguiApp {
fullscreen: self.fullscreen,
emulate_vru: self.emulate_vru,
};
save_config(&mut game_ui, save_config_items);
let mut config = ui::config::Config::new();
save_config(
&mut config,
self.controller_paths.clone(),
save_config_items,
);
}
}
@@ -181,11 +179,25 @@ fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) {
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = app.profile_name.clone();
let config_dir = app.config_dir.clone();
let dinput = app.dinput;
tokio::spawn(async move {
let mut game_ui = ui::Ui::new(config_dir);
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
std::thread::spawn(move || {
if cfg!(target_os = "macos") {
let mut command =
std::process::Command::new(std::env::current_exe().unwrap());
if dinput {
command.arg("--use-dinput");
}
command.arg("--configure-input-profile");
command.arg(profile_name);
let status = command.status().expect("failed to execute process");
if !status.success() {
panic!("process exited with: {}", status);
}
} else {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
}
});
app.configure_profile = false;
if !app.profile_name.is_empty()
@@ -245,7 +257,6 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let task;
let netplay;
let selected_controller = app.selected_controller;
@@ -255,21 +266,18 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let integer_scaling = app.integer_scaling;
let fullscreen = app.fullscreen;
let emulate_vru = app.emulate_vru;
let config_dir = app.config_dir.clone();
let cache_dir = app.cache_dir.clone();
let data_dir = app.data_dir.clone();
let peer_addr;
let session;
let player_number;
let cache_dir = app.dirs.cache_dir.clone();
let controller_paths = app.controller_paths.clone();
if app.netplay.player_name.is_empty() {
task = Some(rfd::AsyncFileDialog::new().pick_file());
netplay = false;
peer_addr = None;
session = None;
player_number = None;
} else {
task = None;
netplay = true;
peer_addr = app.netplay.peer_addr;
session = app.netplay.waiting_session.clone();
@@ -279,7 +287,7 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let (netplay_error_notifier, netplay_error_receiver): (
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(8);
) = tokio::sync::mpsc::channel(1);
let (vru_window_notifier, vru_window_receiver): (
tokio::sync::mpsc::Sender<Vec<String>>,
@@ -304,61 +312,89 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let rom_contents = app.netplay.rom_contents.clone();
let gui_ctx = ctx.clone();
let mut task = None;
if !netplay {
task = Some(rfd::AsyncFileDialog::new().pick_file());
}
tokio::spawn(async move {
let file = if !netplay { task.unwrap().await } else { None };
if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
let mut device = device::Device::new(config_dir);
std::thread::Builder::new()
.name("n64".to_string())
.spawn(move || {
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
upscale,
integer_scaling,
fullscreen,
emulate_vru,
};
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
upscale,
integer_scaling,
fullscreen,
emulate_vru,
};
save_config(&mut device.ui, save_config_items);
if cfg!(target_os = "macos") && file.is_some() {
// mac os requires the process to be started on the main thread
// this means that netplay and VRU emulation will not work on mac os
{
let mut config = ui::config::Config::new();
save_config(&mut config, controller_paths, save_config_items);
}
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
if fullscreen {
command.arg("--fullscreen");
}
command.arg(file.unwrap().path());
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
netplay_error_notifier,
gui_ctx,
));
device::run_game(rom_contents, data_dir, &mut device, fullscreen);
netplay::close(&mut device);
} else {
if emulate_vru {
device.vru.window_notifier = Some(vru_window_notifier);
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
let status = command.status().expect("failed to execute process");
if !status.success() {
panic!("process exited with: {}", status);
}
} else if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
let mut device = device::Device::new();
save_config(&mut device.ui.config, controller_paths, save_config_items);
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
netplay_error_notifier,
gui_ctx,
));
device::run_game(rom_contents, &mut device, fullscreen);
netplay::close(&mut device);
} else {
if emulate_vru {
device.vru.window_notifier = Some(vru_window_notifier);
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
}
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(rom_contents, &mut device, fullscreen);
}
}
let result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(rom_contents, data_dir, &mut device, fullscreen);
}
}
let result = std::fs::remove_file(running_file.clone());
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
})
.unwrap();
});
}
@@ -401,9 +437,9 @@ impl eframe::App for GopherEguiApp {
if ui.button("Open ROM").clicked() {
open_rom(self, ctx);
}
if ui.button("Netplay: Create Session").clicked()
&& !self.cache_dir.join("game_running").exists()
if !cfg!(target_os = "macos")
&& ui.button("Netplay: Create Session").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.netplay.create = true;
}
@@ -411,13 +447,14 @@ impl eframe::App for GopherEguiApp {
ui.end_row();
if ui.button("Configure Input Profile").clicked()
&& !self.cache_dir.join("game_running").exists()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.configure_profile = true;
}
if ui.button("Netplay: Join Session").clicked()
&& !self.cache_dir.join("game_running").exists()
if !cfg!(target_os = "macos")
&& ui.button("Netplay: Join Session").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.netplay.join = true;
}
@@ -476,10 +513,12 @@ impl eframe::App for GopherEguiApp {
ui.checkbox(&mut self.integer_scaling, "Integer Scaling");
ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)");
ui.add_space(32.0);
ui.checkbox(
&mut self.emulate_vru,
"Emulate VRU (connects VRU to controller port 4)",
);
if !cfg!(target_os = "macos") {
ui.checkbox(
&mut self.emulate_vru,
"Emulate VRU (connects VRU to controller port 4)",
);
}
ui.add_space(32.0);
ui.label(format!("Version: {}", env!("CARGO_PKG_VERSION")));
});
@@ -498,7 +537,21 @@ impl eframe::App for GopherEguiApp {
}
}
fn add_japanese_font(ctx: &egui::Context) {
fn add_fonts(ctx: &egui::Context) {
ctx.add_font(eframe::epaint::text::FontInsert::new(
"regular_font",
egui::FontData::from_static(include_bytes!("../../data/Roboto-Regular.ttf")),
vec![
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Proportional,
priority: egui::epaint::text::FontPriority::Highest,
},
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Monospace,
priority: egui::epaint::text::FontPriority::Highest,
},
],
));
ctx.add_font(eframe::epaint::text::FontInsert::new(
"japanese_font",
egui::FontData::from_static(include_bytes!("../../data/NotoSansJP-Regular.ttf")),
+56 -59
View File
@@ -1,6 +1,6 @@
use crate::device;
use crate::ui::gui;
use crate::ui::gui::GopherEguiApp;
use crate::ui::{self, gui};
use eframe::egui;
use sha2::{Digest, Sha256};
@@ -102,17 +102,12 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.server_receiver = Some(rx);
let gui_ctx = ctx.clone();
let task = reqwest::get("https://m64p.s3.amazonaws.com/servers.json");
tokio::spawn(async move {
if let Ok(response) =
reqwest::get("https://m64p.s3.amazonaws.com/servers.json").await
{
if let Ok(servers) = response
.json::<std::collections::HashMap<String, String>>()
.await
{
let _ = tx.send(servers).await;
gui_ctx.request_repaint();
}
let response = task.await;
if let Ok(response) = response {
tx.send(response.json().await.unwrap()).await.unwrap();
gui_ctx.request_repaint();
}
});
}
@@ -184,25 +179,25 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.netplay.create_rom_label = "Open ROM".to_string();
}
if ui.button(&app.netplay.create_rom_label).clicked() {
let task = rfd::AsyncFileDialog::new().pick_file();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.create_rom_label = "Inspecting ROM".to_string();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await
parse_rom_file(file, tx).await;
} else {
let _ = tx
.send((
"".to_string(),
"".to_string(),
"Open ROM".to_string(),
vec![],
))
.await;
tx.send((
"".to_string(),
"".to_string(),
"Open ROM".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
});
@@ -347,7 +342,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.have_sessions.is_some()
&& app.netplay.server != app.netplay.have_sessions.as_ref().unwrap().clone()
&& app.netplay.server != *app.netplay.have_sessions.as_ref().unwrap()
{
// User has changed the server
app.netplay.have_sessions = None;
@@ -399,24 +394,21 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.path());
if !rom_contents.is_empty() {
let hash = device::cart_rom::calculate_hash(&rom_contents);
let game_name = std::str::from_utf8(&rom_contents[0x20..0x20 + 0x14])
.unwrap()
.trim()
.replace('\0', "")
.to_string();
let _ = tx
.send((hash, game_name, file.file_name(), rom_contents))
.await;
let hash = device::cart::rom::calculate_hash(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);
tx.send((hash, game_name, file.file_name(), rom_contents))
.await
.unwrap();
} else {
let _ = tx
.send((
"".to_string(),
"".to_string(),
"Invalid ROM".to_string(),
vec![],
))
.await;
tx.send((
"".to_string(),
"".to_string(),
"Invalid ROM".to_string(),
vec![],
))
.await
.unwrap();
}
}
@@ -587,25 +579,25 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
{
app.netplay.error = "Session requires a password".to_string();
} else {
let task = rfd::AsyncFileDialog::new().pick_file();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.join_rom_label = "Inspecting ROM".to_string();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await
parse_rom_file(file, tx).await;
} else {
let _ = tx
.send((
"".to_string(),
"".to_string(),
"No ROM selected".to_string(),
vec![],
))
.await;
tx.send((
"".to_string(),
"".to_string(),
"No ROM selected".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
});
@@ -757,17 +749,22 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
}
app.netplay.player_number = player as u8;
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
gui::open_rom(app, ctx);
app.netplay = Default::default();
return;
if message.accept.unwrap() == 0 {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
gui::open_rom(app, ctx);
app.netplay = Default::default();
return;
} else {
app.netplay.error = message.message.unwrap();
}
}
} else {
app.netplay.error = message.message.unwrap();
+142 -49
View File
@@ -1,6 +1,7 @@
use crate::device;
use crate::netplay;
use crate::ui;
use std::io::Read;
use std::io::Write;
#[derive(PartialEq)]
pub enum SaveTypes {
@@ -9,6 +10,7 @@ pub enum SaveTypes {
Sram,
Flash,
Mempak,
Sdcard,
Romsave,
}
@@ -17,6 +19,7 @@ pub struct Paths {
pub sra_file_path: std::path::PathBuf,
pub fla_file_path: std::path::PathBuf,
pub pak_file_path: std::path::PathBuf,
pub sdcard_file_path: std::path::PathBuf,
pub romsave_file_path: std::path::PathBuf,
}
@@ -27,10 +30,25 @@ pub struct Saves {
pub sram: (Vec<u8>, bool),
pub flash: (Vec<u8>, bool),
pub mempak: (Vec<u8>, bool),
pub romsave: (serde_json::Map<String, serde_json::Value>, bool),
pub sdcard: (Vec<u8>, bool),
pub romsave: (std::collections::HashMap<u32, u8>, bool),
}
fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
let header_type = std::str::from_utf8(rom[0x3C..0x3E].try_into().unwrap());
if header_type.is_ok() && header_type.unwrap() == "ED" {
let save_type = rom[0x3F] >> 4;
match save_type {
0 => return vec![],
1 => return vec![SaveTypes::Eeprom4k],
2 => return vec![SaveTypes::Eeprom16k],
3 => return vec![SaveTypes::Sram],
4 => panic!("Unsupported save type: {}", save_type),
5 => return vec![SaveTypes::Flash],
6 => panic!("Unsupported save type: {}", save_type),
_ => panic!("Unknown save type: {}", save_type),
}
}
match game_id {
"NB7" | // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
"NGT" | // City Tour GrandPrix - Zen Nihon GT Senshuken
@@ -88,36 +106,61 @@ fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
}
}
pub fn init(ui: &mut ui::Ui, data_dir: std::path::PathBuf) {
let id = ui.game_id.as_str();
ui.save_type = get_save_type(id);
pub fn get_game_name(rom: &[u8]) -> String {
let mut game_name = "".to_owned();
let header_value = std::str::from_utf8(&rom[0x20..0x34]);
if header_value.is_ok() {
let re = regex::Regex::new(r"[^a-zA-Z0-9_ -]").unwrap();
game_name = re
.replace_all(header_value.unwrap(), "")
.trim()
.replace('\0', "");
}
game_name
}
let base_path = data_dir.join("saves");
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
ui.save_type = get_save_type(rom, &ui.game_id);
let base_path = ui.dirs.data_dir.join("saves");
let game_name = get_game_name(rom);
let prefix = if game_name.is_empty() {
&ui.game_id
} else {
&game_name
};
ui.paths.eep_file_path.clone_from(&base_path);
ui.paths
.eep_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".eep");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".eep");
ui.paths.sra_file_path.clone_from(&base_path);
ui.paths
.sra_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".sra");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".sra");
ui.paths.fla_file_path.clone_from(&base_path);
ui.paths
.fla_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".fla");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".fla");
ui.paths.pak_file_path.clone_from(&base_path);
ui.paths
.pak_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".mpk");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk");
ui.paths.sdcard_file_path.clone_from(&base_path);
ui.paths
.sdcard_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".img");
ui.paths.romsave_file_path.clone_from(&base_path);
ui.paths
.romsave_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".romsave");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".romsave");
}
pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
@@ -138,12 +181,13 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
if mempak.is_ok() {
ui.saves.mempak.0 = mempak.unwrap();
}
if netplay.is_none() {
// can't do romsaves with the current netplay implementation
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave.0 = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap();
}
let sdcard = std::fs::read(&mut ui.paths.sdcard_file_path);
if sdcard.is_ok() {
ui.saves.sdcard.0 = sdcard.unwrap();
}
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave.0 = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
}
}
@@ -153,58 +197,100 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
netplay.as_mut().unwrap(),
"eep",
&ui.saves.eeprom.0,
device::cart::EEPROM_MAX_SIZE,
ui.saves.eeprom.0.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"sra",
&ui.saves.sram.0,
device::sram::SRAM_SIZE,
ui.saves.sram.0.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"fla",
&ui.saves.flash.0,
device::sram::FLASHRAM_SIZE,
ui.saves.flash.0.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"mpk",
&ui.saves.mempak.0,
device::controller::mempak::MEMPAK_SIZE * 4,
ui.saves.mempak.0.len(),
);
let mut compressed_sd = Vec::new();
if !ui.saves.sdcard.0.is_empty() {
compressed_sd = compress_file(&ui.saves.sdcard.0);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"img",
&compressed_sd,
compressed_sd.len(),
);
let mut compressed_romsave = Vec::new();
if !ui.saves.romsave.0.is_empty() {
compressed_romsave =
compress_file(&postcard::to_stdvec(&ui.saves.romsave.0).unwrap());
}
netplay::send_save(
netplay.as_mut().unwrap(),
"rom",
&compressed_romsave,
compressed_romsave.len(),
);
} else {
netplay::receive_save(
netplay.as_mut().unwrap(),
"eep",
&mut ui.saves.eeprom.0,
device::cart::EEPROM_MAX_SIZE,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"sra",
&mut ui.saves.sram.0,
device::sram::SRAM_SIZE,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"fla",
&mut ui.saves.flash.0,
device::sram::FLASHRAM_SIZE,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"mpk",
&mut ui.saves.mempak.0,
device::controller::mempak::MEMPAK_SIZE * 4,
);
netplay::receive_save(netplay.as_mut().unwrap(), "eep", &mut ui.saves.eeprom.0);
netplay::receive_save(netplay.as_mut().unwrap(), "sra", &mut ui.saves.sram.0);
netplay::receive_save(netplay.as_mut().unwrap(), "fla", &mut ui.saves.flash.0);
netplay::receive_save(netplay.as_mut().unwrap(), "mpk", &mut ui.saves.mempak.0);
let mut compressed_sd = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut compressed_sd);
if !compressed_sd.is_empty() {
ui.saves.sdcard.0 = decompress_file(&compressed_sd);
}
let mut compressed_romsave = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut compressed_romsave);
if !compressed_romsave.is_empty() {
let romsave_bytes = decompress_file(&compressed_romsave);
ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap();
}
}
}
}
fn decompress_file(input: &[u8]) -> Vec<u8> {
let mut decompressed_file = Vec::new();
{
let mut reader = zip::ZipArchive::new(std::io::Cursor::new(input)).unwrap();
let mut file = reader.by_index(0).unwrap();
file.read_to_end(&mut decompressed_file).unwrap();
}
decompressed_file
}
fn compress_file(input: &[u8]) -> Vec<u8> {
let mut compressed_file = Vec::new();
{
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(&mut compressed_file));
writer
.start_file(
"save",
zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated),
)
.unwrap();
writer.write_all(input).unwrap();
}
compressed_file
}
fn write_rom_save(ui: &ui::Ui) {
let f = std::fs::File::create(ui.paths.romsave_file_path.clone()).unwrap();
serde_json::to_writer(f, &ui.saves.romsave.0).unwrap();
let data = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
}
pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
@@ -221,7 +307,10 @@ pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
if ui.saves.mempak.1 {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.romsave.1 && netplay.is_none() {
if ui.saves.sdcard.1 {
write_save(ui, SaveTypes::Sdcard)
}
if ui.saves.romsave.1 {
write_save(ui, SaveTypes::Romsave)
}
}
@@ -247,6 +336,10 @@ fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
path = ui.paths.pak_file_path.as_ref();
data = ui.saves.mempak.0.as_ref();
}
SaveTypes::Sdcard => {
path = ui.paths.sdcard_file_path.as_ref();
data = ui.saves.sdcard.0.as_ref();
}
SaveTypes::Romsave => {
write_rom_save(ui);
return;
-1
View File
@@ -37,7 +37,6 @@ pub fn init(device: &mut device::Device, fullscreen: bool) {
VI_V_START_REG: &mut device.vi.regs[device::vi::VI_V_START_REG as usize],
VI_X_SCALE_REG: &mut device.vi.regs[device::vi::VI_X_SCALE_REG as usize],
VI_Y_SCALE_REG: &mut device.vi.regs[device::vi::VI_Y_SCALE_REG as usize],
VI_WIDTH_REG: &mut device.vi.regs[device::vi::VI_WIDTH_REG as usize],
};
unsafe {