mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 09:35:21 +02:00
Compare commits
184 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 920a0323c0 | |||
| 14e45be300 | |||
| fba6a4a6d4 | |||
| 1efdeafc31 | |||
| a7c2c26d78 | |||
| 7d63a694cc | |||
| 5c98a08fe4 | |||
| 39ffad3af3 | |||
| ca2a3d423b | |||
| fe5346235e | |||
| f84444a12e | |||
| 026e4c0e74 | |||
| 363ed0953a | |||
| cb9309d7e4 | |||
| 1b1313003c | |||
| 7219a4445c | |||
| 687badc142 | |||
| 3e2189463f | |||
| ed8b4e8e02 | |||
| 611121568e | |||
| 27922fbf9c | |||
| 71b7baf613 | |||
| f4714a7813 | |||
| 4730b7f293 | |||
| 6f33934180 | |||
| cbc9045ad4 | |||
| 2148b1c198 | |||
| 3dc28cc603 | |||
| 1f746c5b36 | |||
| a2ba5baaa8 | |||
| 333865e7de | |||
| b1bb96c7d2 | |||
| 9e88343c6b | |||
| 75e59ee051 | |||
| 952f951620 | |||
| 6820da889c | |||
| 6292459f8b | |||
| 416d43dee5 | |||
| b0e9c29a05 | |||
| 02c8661b4e | |||
| fdf493bfc3 | |||
| 8cbe1ca5b2 | |||
| dae67b7e8b | |||
| 5436b8c0fe | |||
| 8366ab0cad | |||
| 7d805ac69b | |||
| 2c0710a44f | |||
| 040ca493c8 | |||
| bda2266d74 | |||
| 195412a48e | |||
| 9257e8c66d | |||
| 87ae2b940a | |||
| ba8f39f283 | |||
| 1a823399ab | |||
| 26f0936dc7 | |||
| ada4b7e3b4 | |||
| 004af133eb | |||
| 0af411d662 | |||
| 549d71bebf | |||
| 9eacf8a916 | |||
| dbd12516cf | |||
| 4ead0218d9 | |||
| 14ea0eee3f | |||
| b860be4143 | |||
| b065f807a2 | |||
| ee95816385 | |||
| d021cb5d32 | |||
| 9819fad475 | |||
| dd767047fd | |||
| f6480ea312 | |||
| e4174c7d4b | |||
| f3d8080a26 | |||
| b581bdcd40 | |||
| 842c81988f | |||
| 45d05cecf0 | |||
| eae81c4623 | |||
| b3f8688ba5 | |||
| 0a10b2c0ba | |||
| be67cb264c | |||
| c6c2187652 | |||
| b2c1d88ea4 | |||
| 80176766a7 | |||
| 7ac6f2adb1 | |||
| 643338e086 | |||
| d9a08f112d | |||
| 344f41cf47 | |||
| 9978e28cc3 | |||
| d3c26fb359 | |||
| cd106345c9 | |||
| f75636f7e5 | |||
| 97e4921b6a | |||
| 21300ab7db | |||
| 8fd7688aa7 | |||
| a013e72fda | |||
| f2812b0434 | |||
| 5f84c18b39 | |||
| 9058cd1715 | |||
| 5d00eccb19 | |||
| e821626f2b | |||
| 63b8b79dd7 | |||
| 90c9bfbf09 | |||
| 9a3a56d0c6 | |||
| e71d5a1b2d | |||
| a731b870da | |||
| c54356e6f4 | |||
| b2d2089d95 | |||
| a47801b9cb | |||
| 9a76bcd303 | |||
| ba6fe1bc05 | |||
| e02add9676 | |||
| 24b464fea4 | |||
| 89673df459 | |||
| 774bb672fd | |||
| f5d4160a5b | |||
| 913ec771bb | |||
| c3dee292a2 | |||
| 508ae5bc17 | |||
| 376fc85660 | |||
| 31bad0416d | |||
| e103118407 | |||
| 12e37b0b29 | |||
| 415c3eca40 | |||
| 2c60f6bf81 | |||
| 5409b0f5d4 | |||
| f9de4110ce | |||
| 0eadc38917 | |||
| a40e795036 | |||
| b70c943a43 | |||
| c962589478 | |||
| 6dec154351 | |||
| ca33797973 | |||
| 8d331e1aed | |||
| 5e7f562d0d | |||
| 14e3fe2a55 | |||
| b1c6dd2910 | |||
| e87c4736ee | |||
| e9e1e000c2 | |||
| bc83dd26ee | |||
| ae97990308 | |||
| e02ac09703 | |||
| 4cbdae2fce | |||
| 877eff0fd4 | |||
| b560944234 | |||
| c6bfea5018 | |||
| 74db103659 | |||
| fb756d5d94 | |||
| bdd15e340b | |||
| f9dbaeeb23 | |||
| d644617d62 | |||
| bbb0bd1447 | |||
| 711edef9cc | |||
| 64dae8970d | |||
| 4da7cd0551 | |||
| ce5ee30ef0 | |||
| 9eb4e73c4d | |||
| 3b1ef2938d | |||
| b8ce85b9fd | |||
| 49ba66f0a3 | |||
| 0bad5999c1 | |||
| 2911be7deb | |||
| 7ea1516d52 | |||
| 3bac21a908 | |||
| 182cb78649 | |||
| 42919d141a | |||
| 979c3666df | |||
| 4ff7a640d8 | |||
| 97928e1612 | |||
| 19354fe732 | |||
| febf321d58 | |||
| 1dcfe3a25d | |||
| 93aafafa75 | |||
| 44dd53b367 | |||
| 1ffa586789 | |||
| f12baef633 | |||
| 3e4efe110b | |||
| 981a8e8f05 | |||
| 13d6992ca1 | |||
| 5af1c1f7ee | |||
| 00c236ce8c | |||
| 034fa7c29c | |||
| ac30d7a6dd | |||
| e848789965 | |||
| 2f1a1b1479 | |||
| 52caa9cee3 |
+3
-6
@@ -1,8 +1,5 @@
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.'cfg(target_arch = "x86_64")']
|
||||
rustflags = ["-C", "target-cpu=x86-64-v3"]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-cpu=x86-64-v3"]
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-C", "target-cpu=x86-64-v2"]
|
||||
[target.'cfg(target_arch = "aarch64")']
|
||||
rustflags = ["-C", "target-cpu=cortex-a76"]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
github: loganmc10
|
||||
patreon: loganmc10
|
||||
+58
-30
@@ -11,61 +11,89 @@ env:
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
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:
|
||||
submodules: recursive
|
||||
- name: Install sdl2 headers
|
||||
run: |
|
||||
sudo apt -y install libsdl2-dev
|
||||
- name: Update rust
|
||||
run: |
|
||||
rustup update
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
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
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gopher64-linux
|
||||
path: target/release/gopher64
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2025
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64-pc-windows-msvc]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update rust
|
||||
run: |
|
||||
rustup update
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gopher64-windows
|
||||
path: target/release/gopher64.exe
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64.exe
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64-apple-darwin, aarch64-apple-darwin]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install sdl2 headers
|
||||
run: |
|
||||
brew install sdl2
|
||||
- name: Update rust
|
||||
run: |
|
||||
rustup update
|
||||
- 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: |
|
||||
cargo build --release -v
|
||||
brew install molten-vk
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gopher64-mac
|
||||
path: target/release/gopher64
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
|
||||
@@ -16,13 +16,23 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install sdl2 headers
|
||||
run: |
|
||||
sudo apt -y install libsdl2-dev
|
||||
- name: Update rust
|
||||
run: |
|
||||
rustup update
|
||||
rustup component add clippy
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
components: clippy, rustfmt
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
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
|
||||
run: |
|
||||
cargo clippy -- -Dwarnings
|
||||
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./Cargo.toml"
|
||||
]
|
||||
}
|
||||
+22
-13
@@ -1,34 +1,43 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "0.1.0"
|
||||
version = "1.0.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dirs = "5.0"
|
||||
zip = "0.6"
|
||||
governor = "0.6"
|
||||
sevenz-rust = "0.5"
|
||||
dirs = "6.0"
|
||||
zip = "2.1"
|
||||
governor = "0.8"
|
||||
sevenz-rust = "0.6"
|
||||
chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
eframe = "0.23"
|
||||
rfd = {version = "0.12", default-features = false, features = ["xdg-portal"] }
|
||||
eframe = "0.30"
|
||||
sha2 = "0.10"
|
||||
rusttype = "0.9"
|
||||
futures = "0.3"
|
||||
sdl2-sys = { git = "https://github.com/Rust-SDL2/rust-sdl2.git" }
|
||||
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
|
||||
rfd = "0.15"
|
||||
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.sdl2]
|
||||
git = "https://github.com/Rust-SDL2/rust-sdl2.git"
|
||||
[dependencies.reqwest]
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
features = ["static-link","bundled"]
|
||||
features = ["http2","rustls-tls","charset","json","blocking"]
|
||||
|
||||
[build-dependencies]
|
||||
winres = "0.1"
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
bindgen = "0.71"
|
||||
cc = { version = "1.2", features = ["parallel"] }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# gopher64
|
||||
(gopher64 also has a GUI that can be launched by just running the executable)
|
||||
|
||||
```
|
||||
Usage: gopher64 [OPTIONS] [GAME]
|
||||
@@ -11,6 +12,8 @@ Options:
|
||||
|
||||
-c, --configure-input-profile <PROFILE_NAME>
|
||||
Create a new input profile (keyboard/gamepad mappings).
|
||||
-u, --use-dinput
|
||||
Use DirectInput when configuring a new input profile.
|
||||
-b, --bind-input-profile <PROFILE_NAME>
|
||||
Must also specify --port. Used to bind a previously created profile to a port
|
||||
-l, --list-controllers
|
||||
@@ -19,11 +22,33 @@ Options:
|
||||
Must also specify --port. Used to assign a controller listed in --list-controllers to a port
|
||||
-p, --port <PORT>
|
||||
Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller
|
||||
-z, --clear-input-bindings
|
||||
Clear all input profile bindings and controller assignments
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
## discord
|
||||
|
||||
https://discord.gg/9RGXq8W8JQ
|
||||
|
||||
## controls
|
||||
|
||||
Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied.
|
||||
|
||||
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
|
||||
|
||||
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
|
||||
|
||||
## netplay
|
||||
|
||||
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion, or contact me on Discord). You can also run the server (https://github.com/simple64/simple64-netplay-server) yourself on a LAN.
|
||||
|
||||
## portable mode
|
||||
|
||||
If you would like to keep all the game data in the same folder as executable, you just need to create a file called "portable.txt" in the same directory as the executable.
|
||||
|
||||
## goals
|
||||
|
||||
1. Performance. I want to be able to use this emulator on my laptop.
|
||||
@@ -38,14 +63,6 @@ Options:
|
||||
4. `cargo build --release`
|
||||
5. `./target/release/gopher64 /path/to/rom.z64`
|
||||
|
||||
## controls
|
||||
|
||||
Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied.
|
||||
|
||||
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
|
||||
|
||||
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
|
||||
|
||||
## contributing
|
||||
|
||||
I am very open to contributions! Please reach out to me via a GitHub issue, or via discord (loganmc10) before doing substantial work on a PR.
|
||||
@@ -56,4 +73,4 @@ Gopher64 is licensed under the GPLv3 license. Many portions of gopher64 have bee
|
||||
|
||||
## where to download
|
||||
|
||||
Builds can be found as artifacts on [GitHub Actions](https://github.com/gopher64/gopher64/actions?query=branch%3Amain)
|
||||
https://github.com/gopher64/gopher64/releases
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-changed=parallel-rdp");
|
||||
println!("cargo::rerun-if-changed=src/compat");
|
||||
|
||||
let mut simd_build = cc::Build::new();
|
||||
let mut build = cc::Build::new();
|
||||
build
|
||||
.cpp(true)
|
||||
.warnings(false)
|
||||
.std("c++17")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/command_ring.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/rdp_device.cpp")
|
||||
@@ -37,6 +42,7 @@ fn main() {
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/aligned_alloc.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/timer.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/timeline_trace_file.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/environment.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/thread_name.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/volk/volk.c")
|
||||
.file("parallel-rdp/interface.cpp")
|
||||
@@ -46,22 +52,118 @@ fn main() {
|
||||
.include("parallel-rdp/parallel-rdp-standalone/vulkan")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/util")
|
||||
.includes(std::env::var("DEP_SDL2_INCLUDE"));
|
||||
.include(
|
||||
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
|
||||
.join("include"),
|
||||
);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
if os == "windows" {
|
||||
if arch == "x86_64" {
|
||||
build.flag("/arch:AVX2");
|
||||
} else if arch == "aarch64" {
|
||||
panic!("unsupported platform")
|
||||
} else {
|
||||
panic!("unknown arch")
|
||||
}
|
||||
build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
|
||||
|
||||
winres::WindowsResource::new()
|
||||
.set_icon("data/icon.ico")
|
||||
.compile()
|
||||
.unwrap();
|
||||
} else if os == "linux" || os == "macos" {
|
||||
if arch == "x86_64" {
|
||||
build.flag("-march=x86-64-v3");
|
||||
} else if arch == "aarch64" {
|
||||
build.flag("-march=armv8.2-a");
|
||||
simd_build.flag("-march=armv8.2-a");
|
||||
simd_build.flag("-DSSE2NEON_SUPPRESS_WARNINGS");
|
||||
simd_build.file("src/compat/aarch64.c");
|
||||
} else {
|
||||
panic!("unknown arch")
|
||||
}
|
||||
} else {
|
||||
panic!("unknown OS")
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
build
|
||||
.flag("-Wno-missing-field-initializers")
|
||||
.flag("-Wno-unused-parameter");
|
||||
}
|
||||
|
||||
build.compile("parallel-rdp");
|
||||
|
||||
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
||||
let parallel_bindings = bindgen::Builder::default()
|
||||
.header("parallel-rdp/interface.hpp")
|
||||
.allowlist_function("rdp_init")
|
||||
.allowlist_function("rdp_close")
|
||||
.allowlist_function("rdp_set_vi_register")
|
||||
.allowlist_function("rdp_update_screen")
|
||||
.allowlist_function("rdp_process_commands")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
parallel_bindings
|
||||
.write_to_file(out_path.join("parallel_bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
|
||||
if arch == "aarch64" {
|
||||
let simd_bindings = bindgen::Builder::default()
|
||||
.header("src/compat/sse2neon/v1.8.0/sse2neon.h")
|
||||
.allowlist_function("_mm_setzero_si128")
|
||||
.allowlist_function("_mm_set_epi8")
|
||||
.allowlist_function("_mm_movemask_epi8")
|
||||
.allowlist_function("_mm_shuffle_epi8")
|
||||
.allowlist_function("_mm_packs_epi16")
|
||||
.allowlist_function("_mm_set_epi16")
|
||||
.allowlist_function("_mm_cmpeq_epi8")
|
||||
.allowlist_function("_mm_and_si128")
|
||||
.allowlist_function("_mm_set1_epi8")
|
||||
.allowlist_function("_mm_mullo_epi16")
|
||||
.allowlist_function("_mm_cmpeq_epi16")
|
||||
.allowlist_function("_mm_add_epi16")
|
||||
.allowlist_function("_mm_slli_epi16")
|
||||
.allowlist_function("_mm_mulhi_epi16")
|
||||
.allowlist_function("_mm_srai_epi16")
|
||||
.allowlist_function("_mm_andnot_si128")
|
||||
.allowlist_function("_mm_or_si128")
|
||||
.allowlist_function("_mm_mulhi_epu16")
|
||||
.allowlist_function("_mm_sub_epi16")
|
||||
.allowlist_function("_mm_unpacklo_epi16")
|
||||
.allowlist_function("_mm_unpackhi_epi16")
|
||||
.allowlist_function("_mm_packs_epi32")
|
||||
.allowlist_function("_mm_adds_epu16")
|
||||
.allowlist_function("_mm_cmpgt_epi16")
|
||||
.allowlist_function("_mm_blendv_epi8")
|
||||
.allowlist_function("_mm_min_epi16")
|
||||
.allowlist_function("_mm_max_epi16")
|
||||
.allowlist_function("_mm_subs_epi16")
|
||||
.allowlist_function("_mm_adds_epi16")
|
||||
.allowlist_function("_mm_xor_si128")
|
||||
.allowlist_function("_mm_cmplt_epi16")
|
||||
.allowlist_function("_mm_subs_epu16")
|
||||
.allowlist_function("_mm_set1_epi32")
|
||||
.blocklist_type("__m128i")
|
||||
.blocklist_type("int64x2_t")
|
||||
.wrap_static_fns(true)
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
simd_bindings
|
||||
.write_to_file(out_path.join("simd_bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
|
||||
simd_build.file(std::env::temp_dir().join("bindgen").join("extern.c"));
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
Binary file not shown.
+124
-125
@@ -3,6 +3,7 @@
|
||||
#include "rdp_device.hpp"
|
||||
#include "interface.hpp"
|
||||
#include "spirv.hpp"
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
using namespace Vulkan;
|
||||
|
||||
@@ -51,19 +52,16 @@ enum vi_registers
|
||||
};
|
||||
|
||||
static bool fullscreen;
|
||||
static void *rdram;
|
||||
static bool integer_scaling;
|
||||
static SDL_Window *window;
|
||||
static RDP::CommandProcessor *processor;
|
||||
static SDL_WSIPlatform *wsi_platform;
|
||||
static WSI *wsi;
|
||||
static uint32_t cmd_data[0x00040000 >> 2];
|
||||
static int cmd_cur;
|
||||
static int cmd_ptr;
|
||||
static uint8_t emu_running;
|
||||
static uint64_t rdp_sync_signal;
|
||||
static uint32_t vi_registers[14];
|
||||
|
||||
static uint64_t last_frame_counter;
|
||||
static uint64_t frame_counter;
|
||||
static bool emu_running;
|
||||
static GFX_INFO gfx_info;
|
||||
|
||||
static const unsigned cmd_len_lut[64] = {
|
||||
1,
|
||||
@@ -132,73 +130,22 @@ static const unsigned cmd_len_lut[64] = {
|
||||
1,
|
||||
};
|
||||
|
||||
enum
|
||||
bool sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
{
|
||||
MB_RDRAM_DRAM_ALIGNMENT_REQUIREMENT = 64 * 1024
|
||||
};
|
||||
|
||||
void vk_init(void *mem_base, uint32_t rdram_size, uint8_t _fullscreen)
|
||||
{
|
||||
fullscreen = _fullscreen != 0;
|
||||
rdram = mem_base;
|
||||
bool window_vsync = 0;
|
||||
wsi = new WSI;
|
||||
SDL_WSIPlatform *wsi_platform = new SDL_WSIPlatform;
|
||||
wsi_platform->set_window(window);
|
||||
wsi->set_platform(wsi_platform);
|
||||
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 (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
|
||||
{
|
||||
vk_close();
|
||||
emu_running = false;
|
||||
}
|
||||
if (!wsi->init_simple(1, handles))
|
||||
else if (event->type == SDL_EVENT_WINDOW_RESIZED && emu_running)
|
||||
{
|
||||
vk_close();
|
||||
wsi_platform->do_resize();
|
||||
}
|
||||
RDP::CommandProcessorFlags flags = 0;
|
||||
processor = new RDP::CommandProcessor(wsi->get_device(), rdram, 0, rdram_size, rdram_size / 2, flags);
|
||||
|
||||
if (!processor->device_is_supported())
|
||||
else if (event->type == SDL_EVENT_KEY_DOWN && fullscreen)
|
||||
{
|
||||
delete processor;
|
||||
delete wsi;
|
||||
processor = nullptr;
|
||||
vk_close();
|
||||
}
|
||||
wsi->begin_frame();
|
||||
|
||||
emu_running = 1;
|
||||
last_frame_counter = 0;
|
||||
frame_counter = 0;
|
||||
}
|
||||
|
||||
void vk_close()
|
||||
{
|
||||
delete processor;
|
||||
delete wsi;
|
||||
}
|
||||
|
||||
int sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
{
|
||||
if (event->type == SDL_WINDOWEVENT)
|
||||
{
|
||||
switch (event->window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
emu_running = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (fullscreen && event->type == SDL_KEYDOWN)
|
||||
{
|
||||
switch (event->key.keysym.scancode)
|
||||
switch (event->key.scancode)
|
||||
{
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
emu_running = 0;
|
||||
emu_running = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -208,47 +155,122 @@ int sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_sdl_window(void *_window)
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen)
|
||||
{
|
||||
window = (SDL_Window *)_window;
|
||||
SDL_SetEventFilter(sdl_event_filter, nullptr);
|
||||
bool result = SDL_AddEventWatch(sdl_event_filter, nullptr);
|
||||
if (!result)
|
||||
{
|
||||
printf("Could not add event watch.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
gfx_info = _gfx_info;
|
||||
fullscreen = _fullscreen;
|
||||
integer_scaling = _integer_scaling;
|
||||
bool window_vsync = 0;
|
||||
wsi = new WSI;
|
||||
wsi_platform = new SDL_WSIPlatform;
|
||||
wsi_platform->set_window(window);
|
||||
wsi->set_platform(wsi_platform);
|
||||
wsi->set_present_mode(window_vsync ? PresentMode::SyncToVBlank : PresentMode::UnlockedMaybeTear);
|
||||
wsi->set_backbuffer_srgb(false);
|
||||
Context::SystemHandles handles = {};
|
||||
if (!::Vulkan::Context::init_loader((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()))
|
||||
{
|
||||
rdp_close();
|
||||
}
|
||||
if (!wsi->init_simple(1, handles))
|
||||
{
|
||||
rdp_close();
|
||||
}
|
||||
RDP::CommandProcessorFlags flags = 0;
|
||||
if (_upscale)
|
||||
{
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
|
||||
}
|
||||
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
|
||||
|
||||
if (!processor->device_is_supported())
|
||||
{
|
||||
delete processor;
|
||||
delete wsi;
|
||||
processor = nullptr;
|
||||
rdp_close();
|
||||
}
|
||||
wsi->begin_frame();
|
||||
|
||||
emu_running = true;
|
||||
}
|
||||
|
||||
void rdp_close()
|
||||
{
|
||||
wsi->end_frame();
|
||||
|
||||
if (processor)
|
||||
{
|
||||
delete processor;
|
||||
processor = nullptr;
|
||||
}
|
||||
if (wsi)
|
||||
{
|
||||
delete wsi;
|
||||
wsi = nullptr;
|
||||
}
|
||||
if (wsi_platform)
|
||||
{
|
||||
delete wsi_platform;
|
||||
wsi_platform = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void calculate_viewport(float *x, float *y, float *width, float *height)
|
||||
{
|
||||
bool window_widescreen = false;
|
||||
int32_t display_width = (window_widescreen ? 854 : 640);
|
||||
int32_t display_height = 480;
|
||||
const int32_t display_width = 320;
|
||||
const int32_t display_height = 240;
|
||||
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
|
||||
*width = w;
|
||||
*height = h;
|
||||
*x = 0;
|
||||
*y = 0;
|
||||
int32_t hw = display_height * *width;
|
||||
int32_t wh = display_width * *height;
|
||||
if (integer_scaling)
|
||||
{
|
||||
// Integer scaling path
|
||||
int scale_x = w / display_width;
|
||||
int scale_y = h / display_height;
|
||||
int scale = (scale_x < scale_y) ? scale_x : scale_y;
|
||||
if (scale < 1)
|
||||
scale = 1;
|
||||
|
||||
// add letterboxes or pillarboxes if the window has a different aspect ratio
|
||||
// than the current display mode
|
||||
if (hw > wh)
|
||||
{
|
||||
int32_t w_max = wh / display_height;
|
||||
*x += (*width - w_max) / 2;
|
||||
*width = w_max;
|
||||
// Calculate scaled dimensions
|
||||
int scaled_width = display_width * scale;
|
||||
int scaled_height = display_height * scale;
|
||||
|
||||
*width = scaled_width;
|
||||
*height = scaled_height;
|
||||
}
|
||||
else if (hw < wh)
|
||||
else
|
||||
{
|
||||
int32_t h_max = hw / display_width;
|
||||
*y += (*height - h_max) / 2;
|
||||
*height = h_max;
|
||||
// Regular scaling path - maintain aspect ratio
|
||||
float scale_x = w / (float)display_width;
|
||||
float scale_y = h / (float)display_height;
|
||||
float scale = (scale_x < scale_y) ? scale_x : scale_y;
|
||||
|
||||
*width = display_width * scale;
|
||||
*height = display_height * scale;
|
||||
}
|
||||
|
||||
// Center the viewport
|
||||
*x = (w - *width) / 2.0f;
|
||||
*y = (h - *height) / 2.0f;
|
||||
}
|
||||
|
||||
static void render_frame(Vulkan::Device &device)
|
||||
{
|
||||
RDP::ScanoutOptions options = {};
|
||||
options.persist_frame_on_invalid_input = true;
|
||||
options.blend_previous_frame = true;
|
||||
options.upscale_deinterlacing = false;
|
||||
Vulkan::ImageHandle image = processor->scanout(options);
|
||||
|
||||
// Normally reflection is automated.
|
||||
@@ -297,16 +319,14 @@ static void render_frame(Vulkan::Device &device)
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value)
|
||||
{
|
||||
processor->set_vi_register(RDP::VIRegister(reg), value);
|
||||
vi_registers[reg] = value;
|
||||
}
|
||||
|
||||
uint8_t rdp_update_screen()
|
||||
bool rdp_update_screen()
|
||||
{
|
||||
auto &device = wsi->get_device();
|
||||
render_frame(device);
|
||||
wsi->end_frame();
|
||||
wsi->begin_frame();
|
||||
frame_counter++;
|
||||
return emu_running;
|
||||
}
|
||||
|
||||
@@ -349,11 +369,11 @@ static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
|
||||
return (delta * scale) / 0x800;
|
||||
}
|
||||
|
||||
uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
|
||||
uint64_t rdp_process_commands()
|
||||
{
|
||||
uint64_t interrupt_timer = 0;
|
||||
const uint32_t DP_CURRENT = dpc_regs[DPC_CURRENT_REG] & 0x00FFFFF8;
|
||||
const uint32_t DP_END = dpc_regs[DPC_END_REG] & 0x00FFFFF8;
|
||||
const uint32_t DP_CURRENT = *gfx_info.DPC_CURRENT_REG & 0x00FFFFF8;
|
||||
const uint32_t DP_END = *gfx_info.DPC_END_REG & 0x00FFFFF8;
|
||||
|
||||
int length = DP_END - DP_CURRENT;
|
||||
if (length <= 0)
|
||||
@@ -363,16 +383,14 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
|
||||
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
|
||||
return interrupt_timer;
|
||||
|
||||
dpc_regs[DPC_STATUS_REG] |= DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK;
|
||||
|
||||
uint32_t offset = DP_CURRENT;
|
||||
if (dpc_regs[DPC_STATUS_REG] & DP_STATUS_XBUS_DMA)
|
||||
if (*gfx_info.DPC_STATUS_REG & DP_STATUS_XBUS_DMA)
|
||||
{
|
||||
do
|
||||
{
|
||||
offset &= 0xFF8;
|
||||
cmd_data[2 * cmd_ptr + 0] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(SP_DMEM + offset));
|
||||
cmd_data[2 * cmd_ptr + 1] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(SP_DMEM + offset + 4));
|
||||
cmd_data[2 * cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
|
||||
cmd_data[2 * cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
|
||||
offset += sizeof(uint64_t);
|
||||
cmd_ptr++;
|
||||
} while (--length > 0);
|
||||
@@ -388,8 +406,8 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
|
||||
do
|
||||
{
|
||||
offset &= 0xFFFFF8;
|
||||
cmd_data[2 * cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>((uint8_t *)rdram + offset);
|
||||
cmd_data[2 * cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>((uint8_t *)rdram + offset + 4);
|
||||
cmd_data[2 * cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset);
|
||||
cmd_data[2 * cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset + 4);
|
||||
offset += sizeof(uint64_t);
|
||||
cmd_ptr++;
|
||||
} while (--length > 0);
|
||||
@@ -404,7 +422,7 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
|
||||
|
||||
if (cmd_ptr - cmd_cur - cmd_length < 0)
|
||||
{
|
||||
dpc_regs[DPC_START_REG] = dpc_regs[DPC_CURRENT_REG] = dpc_regs[DPC_END_REG];
|
||||
*gfx_info.DPC_START_REG = *gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
|
||||
return interrupt_timer;
|
||||
}
|
||||
|
||||
@@ -413,29 +431,19 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
|
||||
|
||||
if (RDP::Op(command) == RDP::Op::SyncFull)
|
||||
{
|
||||
if (frame_counter != last_frame_counter) // Only sync once per frame
|
||||
{
|
||||
rdp_sync_signal = processor->signal_timeline();
|
||||
last_frame_counter = frame_counter;
|
||||
}
|
||||
else
|
||||
{
|
||||
rdp_sync_signal = 0;
|
||||
}
|
||||
processor->wait_for_timeline(processor->signal_timeline());
|
||||
|
||||
uint32_t width = viCalculateHorizonalWidth(vi_registers[VI_H_START_REG], vi_registers[VI_X_SCALE_REG], vi_registers[VI_WIDTH_REG]);
|
||||
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG, *gfx_info.VI_WIDTH_REG);
|
||||
if (width == 0)
|
||||
{
|
||||
width = 320;
|
||||
}
|
||||
uint32_t height = viCalculateVerticalHeight(vi_registers[VI_V_START_REG], vi_registers[VI_Y_SCALE_REG]);
|
||||
uint32_t height = viCalculateVerticalHeight(*gfx_info.VI_V_START_REG, *gfx_info.VI_Y_SCALE_REG);
|
||||
if (height == 0)
|
||||
{
|
||||
height = 240;
|
||||
}
|
||||
interrupt_timer = width * height * 4;
|
||||
|
||||
dpc_regs[DPC_STATUS_REG] &= ~(DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK);
|
||||
}
|
||||
|
||||
cmd_cur += cmd_length;
|
||||
@@ -443,16 +451,7 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
|
||||
|
||||
cmd_ptr = 0;
|
||||
cmd_cur = 0;
|
||||
dpc_regs[DPC_CURRENT_REG] = dpc_regs[DPC_END_REG];
|
||||
dpc_regs[DPC_STATUS_REG] |= DP_STATUS_CBUF_READY;
|
||||
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
|
||||
|
||||
return interrupt_timer;
|
||||
}
|
||||
|
||||
void full_sync()
|
||||
{
|
||||
if (rdp_sync_signal)
|
||||
{
|
||||
processor->wait_for_timeline(rdp_sync_signal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
void vk_init(void *mem_base, uint32_t rdram_size, uint8_t fullscreen);
|
||||
void vk_close();
|
||||
void set_sdl_window(void *_window);
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value);
|
||||
uint8_t rdp_update_screen();
|
||||
uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM);
|
||||
void full_sync();
|
||||
typedef struct
|
||||
{
|
||||
uint8_t *RDRAM;
|
||||
uint8_t *DMEM;
|
||||
uint32_t RDRAM_SIZE;
|
||||
uint32_t *DPC_CURRENT_REG;
|
||||
uint32_t *DPC_START_REG;
|
||||
uint32_t *DPC_END_REG;
|
||||
uint32_t *DPC_STATUS_REG;
|
||||
uint32_t *VI_H_START_REG;
|
||||
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);
|
||||
void rdp_close();
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value);
|
||||
bool rdp_update_screen();
|
||||
uint64_t rdp_process_commands();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Submodule parallel-rdp/parallel-rdp-standalone updated: 37bf2967e3...388d70f583
@@ -1,11 +1,11 @@
|
||||
#include "wsi_platform.hpp"
|
||||
#include <SDL_vulkan.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevice gpu)
|
||||
{
|
||||
VkSurfaceKHR surface = nullptr;
|
||||
SDL_bool result = SDL_Vulkan_CreateSurface(window, instance, &surface);
|
||||
if (result != SDL_TRUE)
|
||||
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
|
||||
if (result != true)
|
||||
{
|
||||
printf("Error creating surface\n");
|
||||
}
|
||||
@@ -14,18 +14,23 @@ VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevi
|
||||
|
||||
void SDL_WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface)
|
||||
{
|
||||
SDL_Vulkan_DestroySurface(instance, surface, NULL);
|
||||
}
|
||||
|
||||
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
|
||||
{
|
||||
|
||||
unsigned int extensionCount = 0;
|
||||
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr);
|
||||
std::vector<const char *> extensionNames(extensionCount);
|
||||
SDL_bool result = SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames.data());
|
||||
if (result != SDL_TRUE)
|
||||
char const *const *extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
|
||||
if (extensions == NULL)
|
||||
{
|
||||
printf("Error creating surface\n");
|
||||
printf("Error getting instance extensions\n");
|
||||
}
|
||||
|
||||
std::vector<const char *> extensionNames;
|
||||
for (int i = 0; i < extensionCount; ++i)
|
||||
{
|
||||
extensionNames.push_back(extensions[i]);
|
||||
}
|
||||
return extensionNames;
|
||||
}
|
||||
@@ -54,7 +59,17 @@ void SDL_WSIPlatform::poll_input()
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
void SDL_WSIPlatform::set_window(SDL_Window *_window)
|
||||
{
|
||||
window = _window;
|
||||
}
|
||||
|
||||
void SDL_WSIPlatform::do_resize()
|
||||
{
|
||||
resize = true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "wsi.hpp"
|
||||
#include <SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
class SDL_WSIPlatform : public Vulkan::WSIPlatform
|
||||
{
|
||||
@@ -13,9 +13,10 @@ public:
|
||||
uint32_t get_surface_height() override;
|
||||
bool alive(Vulkan::WSI &wsi) override;
|
||||
void poll_input() override;
|
||||
void poll_input_async(Granite::InputTrackerHandler *handler) override;
|
||||
void set_window(SDL_Window *_window);
|
||||
void do_resize();
|
||||
|
||||
private:
|
||||
VkSurfaceKHR surface;
|
||||
SDL_Window *window;
|
||||
};
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
@@ -0,0 +1,197 @@
|
||||
#include "src/compat/sse2neon/v1.8.0/sse2neon.h"
|
||||
|
||||
// Static wrappers
|
||||
|
||||
__m128i _mm_srli_epi16__extern(__m128i __a, int __imm8) { return _mm_srli_epi16(__a, __imm8); }
|
||||
|
||||
__m128i _mm_insert_epi8__extern(__m128i __a, int __i, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_insert_epi8(__a, __i, 0);
|
||||
case 1:
|
||||
return _mm_insert_epi8(__a, __i, 1);
|
||||
case 2:
|
||||
return _mm_insert_epi8(__a, __i, 2);
|
||||
case 3:
|
||||
return _mm_insert_epi8(__a, __i, 3);
|
||||
case 4:
|
||||
return _mm_insert_epi8(__a, __i, 4);
|
||||
case 5:
|
||||
return _mm_insert_epi8(__a, __i, 5);
|
||||
case 6:
|
||||
return _mm_insert_epi8(__a, __i, 6);
|
||||
case 7:
|
||||
return _mm_insert_epi8(__a, __i, 7);
|
||||
case 8:
|
||||
return _mm_insert_epi8(__a, __i, 8);
|
||||
case 9:
|
||||
return _mm_insert_epi8(__a, __i, 9);
|
||||
case 10:
|
||||
return _mm_insert_epi8(__a, __i, 10);
|
||||
case 11:
|
||||
return _mm_insert_epi8(__a, __i, 11);
|
||||
case 12:
|
||||
return _mm_insert_epi8(__a, __i, 12);
|
||||
case 13:
|
||||
return _mm_insert_epi8(__a, __i, 13);
|
||||
case 14:
|
||||
return _mm_insert_epi8(__a, __i, 14);
|
||||
case 15:
|
||||
return _mm_insert_epi8(__a, __i, 15);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int _mm_extract_epi8__extern(__m128i __a, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_extract_epi8(__a, 0);
|
||||
case 1:
|
||||
return _mm_extract_epi8(__a, 1);
|
||||
case 2:
|
||||
return _mm_extract_epi8(__a, 2);
|
||||
case 3:
|
||||
return _mm_extract_epi8(__a, 3);
|
||||
case 4:
|
||||
return _mm_extract_epi8(__a, 4);
|
||||
case 5:
|
||||
return _mm_extract_epi8(__a, 5);
|
||||
case 6:
|
||||
return _mm_extract_epi8(__a, 6);
|
||||
case 7:
|
||||
return _mm_extract_epi8(__a, 7);
|
||||
case 8:
|
||||
return _mm_extract_epi8(__a, 8);
|
||||
case 9:
|
||||
return _mm_extract_epi8(__a, 9);
|
||||
case 10:
|
||||
return _mm_extract_epi8(__a, 10);
|
||||
case 11:
|
||||
return _mm_extract_epi8(__a, 11);
|
||||
case 12:
|
||||
return _mm_extract_epi8(__a, 12);
|
||||
case 13:
|
||||
return _mm_extract_epi8(__a, 13);
|
||||
case 14:
|
||||
return _mm_extract_epi8(__a, 14);
|
||||
case 15:
|
||||
return _mm_extract_epi8(__a, 15);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
__m128i _mm_insert_epi16__extern(__m128i __a, int __i, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_insert_epi16(__a, __i, 0);
|
||||
case 1:
|
||||
return _mm_insert_epi16(__a, __i, 1);
|
||||
case 2:
|
||||
return _mm_insert_epi16(__a, __i, 2);
|
||||
case 3:
|
||||
return _mm_insert_epi16(__a, __i, 3);
|
||||
case 4:
|
||||
return _mm_insert_epi16(__a, __i, 4);
|
||||
case 5:
|
||||
return _mm_insert_epi16(__a, __i, 5);
|
||||
case 6:
|
||||
return _mm_insert_epi16(__a, __i, 6);
|
||||
case 7:
|
||||
return _mm_insert_epi16(__a, __i, 7);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int _mm_extract_epi16__extern(__m128i __a, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_extract_epi16(__a, 0);
|
||||
case 1:
|
||||
return _mm_extract_epi16(__a, 1);
|
||||
case 2:
|
||||
return _mm_extract_epi16(__a, 2);
|
||||
case 3:
|
||||
return _mm_extract_epi16(__a, 3);
|
||||
case 4:
|
||||
return _mm_extract_epi16(__a, 4);
|
||||
case 5:
|
||||
return _mm_extract_epi16(__a, 5);
|
||||
case 6:
|
||||
return _mm_extract_epi16(__a, 6);
|
||||
case 7:
|
||||
return _mm_extract_epi16(__a, 7);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
__m128i _mm_insert_epi32__extern(__m128i __a, int __i, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_insert_epi32(__a, __i, 0);
|
||||
case 1:
|
||||
return _mm_insert_epi32(__a, __i, 1);
|
||||
case 2:
|
||||
return _mm_insert_epi32(__a, __i, 2);
|
||||
case 3:
|
||||
return _mm_insert_epi32(__a, __i, 3);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int _mm_extract_epi32__extern(__m128i __a, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_extract_epi32(__a, 0);
|
||||
case 1:
|
||||
return _mm_extract_epi32(__a, 1);
|
||||
case 2:
|
||||
return _mm_extract_epi32(__a, 2);
|
||||
case 3:
|
||||
return _mm_extract_epi32(__a, 3);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
__m128i _mm_insert_epi64__extern(__m128i __a, int64_t __i, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_insert_epi64(__a, __i, 0);
|
||||
case 1:
|
||||
return _mm_insert_epi64(__a, __i, 1);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t _mm_extract_epi64__extern(__m128i __a, const int __imm8)
|
||||
{
|
||||
switch (__imm8)
|
||||
{
|
||||
case 0:
|
||||
return _mm_extract_epi64(__a, 0);
|
||||
case 1:
|
||||
return _mm_extract_epi64(__a, 1);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
unsafe extern "C" {
|
||||
#[link_name = "_mm_srli_epi16__extern"]
|
||||
fn _mm_srli_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> __m128i;
|
||||
|
||||
#[link_name = "_mm_insert_epi8__extern"]
|
||||
fn _mm_insert_epi8(
|
||||
__a: __m128i,
|
||||
__i: ::std::os::raw::c_int,
|
||||
__imm8: ::std::os::raw::c_int,
|
||||
) -> __m128i;
|
||||
|
||||
#[link_name = "_mm_extract_epi8__extern"]
|
||||
fn _mm_extract_epi8(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
|
||||
|
||||
#[link_name = "_mm_insert_epi16__extern"]
|
||||
fn _mm_insert_epi16(
|
||||
__a: __m128i,
|
||||
__i: ::std::os::raw::c_int,
|
||||
__imm8: ::std::os::raw::c_int,
|
||||
) -> __m128i;
|
||||
|
||||
#[link_name = "_mm_extract_epi16__extern"]
|
||||
fn _mm_extract_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
|
||||
|
||||
#[link_name = "_mm_insert_epi32__extern"]
|
||||
fn _mm_insert_epi32(
|
||||
__a: __m128i,
|
||||
__i: ::std::os::raw::c_int,
|
||||
__imm8: ::std::os::raw::c_int,
|
||||
) -> __m128i;
|
||||
|
||||
#[link_name = "_mm_extract_epi32__extern"]
|
||||
fn _mm_extract_epi32(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
|
||||
|
||||
#[link_name = "_mm_insert_epi64__extern"]
|
||||
fn _mm_insert_epi64(
|
||||
__a: __m128i,
|
||||
__i: ::std::os::raw::c_longlong,
|
||||
__imm8: ::std::os::raw::c_int,
|
||||
) -> __m128i;
|
||||
|
||||
#[link_name = "_mm_extract_epi64__extern"]
|
||||
fn _mm_extract_epi64(
|
||||
__a: __m128i,
|
||||
__imm8: ::std::os::raw::c_int,
|
||||
) -> ::std::os::raw::c_longlong;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+95
-41
@@ -1,11 +1,24 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(improper_ctypes)]
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct __m128i(std::arch::aarch64::int64x2_t);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
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;
|
||||
@@ -17,7 +30,6 @@ pub mod exceptions;
|
||||
pub mod fpu_instructions;
|
||||
pub mod is_viewer;
|
||||
pub mod memory;
|
||||
pub mod mempak;
|
||||
pub mod mi;
|
||||
pub mod pi;
|
||||
pub mod pif;
|
||||
@@ -29,36 +41,41 @@ 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(file_path: &std::path::Path, device: &mut Device, fullscreen: bool) {
|
||||
let rom_contents = get_rom_contents(file_path);
|
||||
|
||||
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
|
||||
let (rdram_ptr, rdram_size) = rdram::init(device);
|
||||
rdram::init(device);
|
||||
|
||||
ui::audio::init(&mut device.ui, 33600);
|
||||
ui::video::init(&mut device.ui, rdram_ptr, rdram_size, fullscreen);
|
||||
ui::video::init(device, fullscreen);
|
||||
ui::input::init(&mut device.ui);
|
||||
|
||||
mi::init(device);
|
||||
pif::init(device);
|
||||
if device.ui.config.input.emulate_vru {
|
||||
controller::vru::init(device);
|
||||
}
|
||||
memory::init(device);
|
||||
rsp_interface::init(device);
|
||||
rdp::init(device);
|
||||
vi::init(device);
|
||||
cpu::init(device);
|
||||
|
||||
ui::storage::init(&mut device.ui);
|
||||
ui::storage::load_saves(&mut device.ui);
|
||||
cart_rom::load_rom_save(device);
|
||||
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);
|
||||
|
||||
cpu::run(device);
|
||||
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::storage::write_saves(&device.ui, &device.netplay);
|
||||
}
|
||||
|
||||
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
|
||||
@@ -83,13 +100,14 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
|
||||
}
|
||||
return data;
|
||||
} else {
|
||||
panic!("unknown rom format")
|
||||
let data: Vec<u8> = vec![];
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
|
||||
pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
|
||||
let mut contents = vec![];
|
||||
if file_path.extension().unwrap().to_ascii_lowercase() == "zip" {
|
||||
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
|
||||
let zip_file = fs::File::open(file_path).unwrap();
|
||||
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
|
||||
for i in 0..archive.len() {
|
||||
@@ -106,7 +124,7 @@ fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if file_path.extension().unwrap().to_ascii_lowercase() == "7z" {
|
||||
} else if file_path.extension().unwrap().eq_ignore_ascii_case("7z") {
|
||||
let mut archive =
|
||||
sevenz_rust::SevenZReader::open(file_path, sevenz_rust::Password::empty()).unwrap();
|
||||
|
||||
@@ -138,22 +156,25 @@ fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
|
||||
}
|
||||
|
||||
pub struct Device {
|
||||
pub netplay: Option<netplay::Netplay>,
|
||||
pub ui: ui::Ui,
|
||||
byte_swap: usize,
|
||||
cpu: cpu::Cpu,
|
||||
pub cpu: cpu::Cpu,
|
||||
pif: pif::Pif,
|
||||
cart: cart_rom::Cart,
|
||||
cart: cart::rom::Cart,
|
||||
memory: memory::Memory,
|
||||
rsp: rsp_interface::Rsp,
|
||||
rdp: rdp::Rdp,
|
||||
pub rsp: rsp_interface::Rsp,
|
||||
pub rdp: rdp::Rdp,
|
||||
pub rdram: rdram::Rdram,
|
||||
mi: mi::Mi,
|
||||
pi: pi::Pi,
|
||||
vi: vi::Vi,
|
||||
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 {
|
||||
@@ -166,6 +187,7 @@ impl Device {
|
||||
byte_swap = 3;
|
||||
}
|
||||
Device {
|
||||
netplay: None,
|
||||
ui: ui::Ui::new(),
|
||||
byte_swap,
|
||||
cpu: cpu::Cpu {
|
||||
@@ -173,6 +195,8 @@ impl Device {
|
||||
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![
|
||||
@@ -214,7 +238,8 @@ impl Device {
|
||||
cop1: cop1::Cop1 {
|
||||
fcr0: 0,
|
||||
fcr31: 0,
|
||||
flush_mode: 0,
|
||||
//#[cfg(target_arch = "x86_64")]
|
||||
//flush_mode: 0,
|
||||
fgr32: [[0; 4]; 32],
|
||||
fgr64: [[0; 8]; 32],
|
||||
instrs: [cop1::reserved; 32],
|
||||
@@ -239,7 +264,7 @@ impl Device {
|
||||
llbit: false,
|
||||
lo: 0,
|
||||
hi: 0,
|
||||
running: 0,
|
||||
running: false,
|
||||
instrs: [cop0::reserved; 64],
|
||||
special_instrs: [cop0::reserved; 64],
|
||||
regimm_instrs: [cop0::reserved; 32],
|
||||
@@ -247,7 +272,7 @@ impl Device {
|
||||
enabled: false,
|
||||
count: u64::MAX,
|
||||
handler: events::dummy_event,
|
||||
}; events::EventType::EventTypeCount as usize],
|
||||
}; events::EventType::Count as usize],
|
||||
next_event_count: u64::MAX,
|
||||
next_event: 0,
|
||||
},
|
||||
@@ -261,15 +286,16 @@ impl Device {
|
||||
rx_buf: None,
|
||||
process: None,
|
||||
pak_handler: None,
|
||||
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 },
|
||||
},
|
||||
@@ -292,7 +318,11 @@ impl Device {
|
||||
words: [0; 4],
|
||||
}; 512],
|
||||
},
|
||||
rdram: rdram::Rdram { mem: vec![] },
|
||||
rdram: rdram::Rdram {
|
||||
mem: vec![],
|
||||
size: 0x800000,
|
||||
regs: [[0; rdram::RDRAM_REGS_COUNT as usize]; 4],
|
||||
},
|
||||
rsp: rsp_interface::Rsp {
|
||||
cpu: rsp_cpu::Cpu {
|
||||
instructions: [rsp_cpu::Instructions {
|
||||
@@ -311,21 +341,21 @@ impl Device {
|
||||
sync_point: false,
|
||||
cycle_counter: 0,
|
||||
gpr: [0; 32],
|
||||
vpr: [0; 32],
|
||||
vpr: unsafe { [_mm_setzero_si128(); 32] },
|
||||
reciprocals: [0; 512],
|
||||
inverse_square_roots: [0; 512],
|
||||
divdp: false,
|
||||
divin: 0,
|
||||
divout: 0,
|
||||
shuffle: unsafe { [std::arch::x86_64::_mm_setzero_si128(); 16] },
|
||||
vcol: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
vcoh: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
vccl: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
vcch: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
vce: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
accl: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
accm: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
acch: unsafe { std::arch::x86_64::_mm_setzero_si128() },
|
||||
shuffle: unsafe { [_mm_setzero_si128(); 16] },
|
||||
vcol: unsafe { _mm_setzero_si128() },
|
||||
vcoh: unsafe { _mm_setzero_si128() },
|
||||
vccl: unsafe { _mm_setzero_si128() },
|
||||
vcch: unsafe { _mm_setzero_si128() },
|
||||
vce: unsafe { _mm_setzero_si128() },
|
||||
accl: unsafe { _mm_setzero_si128() },
|
||||
accm: unsafe { _mm_setzero_si128() },
|
||||
acch: unsafe { _mm_setzero_si128() },
|
||||
special_instrs: [rsp_su_instructions::reserved; 64],
|
||||
regimm_instrs: [rsp_su_instructions::reserved; 32],
|
||||
cop0_instrs: [rsp_su_instructions::reserved; 32],
|
||||
@@ -338,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,
|
||||
@@ -356,6 +388,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,
|
||||
@@ -372,6 +411,7 @@ impl Device {
|
||||
},
|
||||
ri: ri::Ri {
|
||||
regs: [0; ri::RI_REGS_COUNT as usize],
|
||||
ram_init: false,
|
||||
},
|
||||
vi: vi::Vi {
|
||||
regs: [0; vi::VI_REGS_COUNT as usize],
|
||||
@@ -380,13 +420,27 @@ impl Device {
|
||||
delay: 0,
|
||||
count_per_scanline: 0,
|
||||
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,
|
||||
voice_state: 0,
|
||||
load_offset: 0,
|
||||
voice_init: 0,
|
||||
word_buffer: [0; 40],
|
||||
words: Vec::new(),
|
||||
talking: false,
|
||||
word_mappings: HashMap::new(),
|
||||
window_notifier: None,
|
||||
word_receiver: None,
|
||||
gui_ctx: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+16
-15
@@ -1,16 +1,16 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub const AI_DRAM_ADDR_REG: u32 = 0;
|
||||
pub const AI_LEN_REG: u32 = 1;
|
||||
//pub const AI_CONTROL_REG: u32 = 2;
|
||||
pub const AI_STATUS_REG: u32 = 3;
|
||||
pub const AI_DACRATE_REG: u32 = 4;
|
||||
//pub const AI_BITRATE_REG: u32 = 5;
|
||||
const AI_DRAM_ADDR_REG: u32 = 0;
|
||||
const AI_LEN_REG: u32 = 1;
|
||||
// const AI_CONTROL_REG: u32 = 2;
|
||||
const AI_STATUS_REG: u32 = 3;
|
||||
const AI_DACRATE_REG: u32 = 4;
|
||||
// const AI_BITRATE_REG: u32 = 5;
|
||||
pub const AI_REGS_COUNT: u32 = 6;
|
||||
|
||||
pub const AI_STATUS_BUSY: u32 = 0x40000000;
|
||||
pub const AI_STATUS_FULL: u32 = 0x80000000;
|
||||
const AI_STATUS_BUSY: u32 = 0x40000000;
|
||||
const AI_STATUS_FULL: u32 = 0x80000000;
|
||||
|
||||
pub struct Ai {
|
||||
pub regs: [u32; AI_REGS_COUNT as usize],
|
||||
@@ -26,7 +26,7 @@ pub struct AiDma {
|
||||
pub duration: u64,
|
||||
}
|
||||
|
||||
pub fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
|
||||
fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
|
||||
if device.ai.fifo[0].duration == 0 {
|
||||
return 0;
|
||||
}
|
||||
@@ -43,7 +43,7 @@ pub fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
|
||||
dma_length & !7
|
||||
}
|
||||
|
||||
pub fn get_dma_duration(device: &device::Device) -> u64 {
|
||||
fn get_dma_duration(device: &device::Device) -> u64 {
|
||||
let samples_per_sec = device.vi.clock / (1 + device.ai.regs[AI_DACRATE_REG as usize]) as u64;
|
||||
let bytes_per_sample = 4; /* XXX: assume 16bit stereo - should depends on bitrate instead */
|
||||
let length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
|
||||
@@ -51,7 +51,7 @@ pub fn get_dma_duration(device: &device::Device) -> u64 {
|
||||
length * (device.cpu.clock_rate / (bytes_per_sample * samples_per_sec))
|
||||
}
|
||||
|
||||
pub fn do_dma(device: &mut device::Device) {
|
||||
fn do_dma(device: &mut device::Device) {
|
||||
device.ai.last_read = device.ai.fifo[0].length;
|
||||
|
||||
if device.ai.delayed_carry {
|
||||
@@ -68,10 +68,10 @@ pub 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);
|
||||
}
|
||||
|
||||
pub fn fifo_push(device: &mut device::Device) {
|
||||
fn fifo_push(device: &mut device::Device) {
|
||||
let duration = get_dma_duration(device);
|
||||
|
||||
if (device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_BUSY) != 0 {
|
||||
@@ -91,7 +91,7 @@ pub fn fifo_push(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fifo_pop(device: &mut device::Device) {
|
||||
fn fifo_pop(device: &mut device::Device) {
|
||||
if device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_FULL != 0 {
|
||||
device.ai.fifo[0].address = device.ai.fifo[1].address;
|
||||
device.ai.fifo[0].length = device.ai.fifo[1].length;
|
||||
@@ -144,6 +144,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
AI_DACRATE_REG => {
|
||||
if device.ai.regs[reg as usize] != value & mask {
|
||||
let frequency = device.vi.clock / (1 + (value & mask)) as u64;
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::audio::init(&mut device.ui, frequency)
|
||||
}
|
||||
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
|
||||
@@ -152,7 +153,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dma_event(device: &mut device::Device) {
|
||||
fn dma_event(device: &mut device::Device) {
|
||||
if device.ai.last_read != 0 {
|
||||
let diff = device.ai.fifo[0].length - device.ai.last_read;
|
||||
ui::audio::play_audio(
|
||||
|
||||
+1
-1
@@ -205,7 +205,7 @@ pub fn dcache_writeback(device: &mut device::Device, line_index: usize) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
|
||||
fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
|
||||
device::cop0::add_cycles(device, 7);
|
||||
|
||||
device.memory.dcache[line_index].valid = true;
|
||||
|
||||
+23
-19
@@ -4,25 +4,29 @@ use chrono::Timelike;
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub const JCMD_STATUS: u8 = 0x00;
|
||||
pub const JCMD_EEPROM_READ: u8 = 0x04;
|
||||
pub const JCMD_EEPROM_WRITE: u8 = 0x05;
|
||||
pub const JCMD_AF_RTC_STATUS: u8 = 0x06;
|
||||
pub const JCMD_AF_RTC_READ: u8 = 0x07;
|
||||
pub const JCMD_AF_RTC_WRITE: u8 = 0x08;
|
||||
pub const JCMD_RESET: u8 = 0xff;
|
||||
pub mod rom;
|
||||
pub mod sc64;
|
||||
pub mod sram;
|
||||
|
||||
pub const JDT_AF_RTC: u16 = 0x1000; /* RTC */
|
||||
pub const JDT_EEPROM_4K: u16 = 0x8000; /* 4k EEPROM */
|
||||
pub const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
|
||||
pub const EEPROM_BLOCK_SIZE: usize = 8;
|
||||
const JCMD_STATUS: u8 = 0x00;
|
||||
const JCMD_EEPROM_READ: u8 = 0x04;
|
||||
const JCMD_EEPROM_WRITE: u8 = 0x05;
|
||||
const JCMD_AF_RTC_STATUS: u8 = 0x06;
|
||||
const JCMD_AF_RTC_READ: u8 = 0x07;
|
||||
const JCMD_AF_RTC_WRITE: u8 = 0x08;
|
||||
const JCMD_RESET: u8 = 0xff;
|
||||
|
||||
const JDT_AF_RTC: u16 = 0x1000; /* RTC */
|
||||
const JDT_EEPROM_4K: u16 = 0x8000; /* 4k EEPROM */
|
||||
const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
|
||||
const EEPROM_BLOCK_SIZE: usize = 8;
|
||||
pub const EEPROM_MAX_SIZE: usize = 0x800;
|
||||
|
||||
pub struct AfRtc {
|
||||
pub control: u16,
|
||||
}
|
||||
|
||||
pub fn byte2bcd(mut n: u32) -> u8 {
|
||||
fn byte2bcd(mut n: u32) -> u8 {
|
||||
n %= 100;
|
||||
(((n / 10) << 4) | (n % 10)) as u8
|
||||
}
|
||||
@@ -95,13 +99,13 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_eeprom(device: &mut device::Device) {
|
||||
fn format_eeprom(device: &mut device::Device) {
|
||||
if device.ui.saves.eeprom.0.len() < EEPROM_MAX_SIZE {
|
||||
device.ui.saves.eeprom.0.resize(EEPROM_MAX_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
|
||||
fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
|
||||
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
|
||||
|
||||
format_eeprom(device);
|
||||
@@ -110,7 +114,7 @@ pub fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usiz
|
||||
.copy_from_slice(&device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]);
|
||||
}
|
||||
|
||||
pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
|
||||
|
||||
format_eeprom(device);
|
||||
@@ -123,7 +127,7 @@ pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usi
|
||||
device.ui.saves.eeprom.1 = true
|
||||
}
|
||||
|
||||
pub fn time2data(device: &mut device::Device, offset: usize) {
|
||||
fn time2data(device: &mut device::Device, offset: usize) {
|
||||
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
|
||||
|
||||
device.pif.ram[offset] = byte2bcd(now.second());
|
||||
@@ -136,7 +140,7 @@ pub fn time2data(device: &mut device::Device, offset: usize) {
|
||||
device.pif.ram[offset + 7] = byte2bcd((now.year() as u32 - 1900) / 100);
|
||||
}
|
||||
|
||||
pub fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
match device.pif.ram[block] {
|
||||
0 => {
|
||||
device.pif.ram[offset] = device.cart.rtc.control as u8;
|
||||
@@ -155,11 +159,11 @@ pub fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usiz
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn af_rtc_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
fn af_rtc_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
match device.pif.ram[block] {
|
||||
0 => {
|
||||
device.cart.rtc.control =
|
||||
(device.pif.ram[offset + 1] as u16) << 8 | device.pif.ram[offset] as u16;
|
||||
((device.pif.ram[offset + 1] as u16) << 8) | device.pif.ram[offset] as u16;
|
||||
device.pif.ram[status] = 0x00;
|
||||
}
|
||||
1 => {
|
||||
|
||||
@@ -10,7 +10,7 @@ pub enum CicType {
|
||||
CicNus5167,
|
||||
}
|
||||
|
||||
pub const CART_MASK: usize = 0xFFFFFFF;
|
||||
const CART_MASK: usize = 0xFFFFFFF;
|
||||
pub struct Cart {
|
||||
pub rom: Vec<u8>,
|
||||
pub is_viewer_buffer: [u8; 0xFFFF],
|
||||
@@ -80,14 +80,15 @@ pub fn dma_read(
|
||||
cart_addr &= CART_MASK as u32;
|
||||
|
||||
for i in 0..length {
|
||||
device.cart.rom[(cart_addr + i) as usize] =
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
|
||||
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.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
device.ui.saves.romsave.1 = true;
|
||||
@@ -127,6 +128,9 @@ pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
|
||||
device.ui.game_hash = calculate_hash(&device.cart.rom);
|
||||
|
||||
device.ui.game_id = String::from_utf8(device.cart.rom[0x3B..0x3E].to_vec()).unwrap();
|
||||
if device.ui.game_id.contains('\0') {
|
||||
device.ui.game_id = String::from("UNK");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_rom_save(device: &mut device::Device) {
|
||||
@@ -134,12 +138,11 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_system_region(device: &mut device::Device, country: u8) {
|
||||
fn set_system_region(device: &mut device::Device, country: u8) {
|
||||
let pal_codes: [u8; 8] = [b'D', b'F', b'I', b'P', b'S', b'U', b'X', b'Y'];
|
||||
for i in pal_codes {
|
||||
if country == i {
|
||||
@@ -148,7 +151,7 @@ pub fn set_system_region(device: &mut device::Device, country: u8) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cic(device: &mut device::Device) {
|
||||
fn set_cic(device: &mut device::Device) {
|
||||
let hash = calculate_hash(&device.cart.rom[0x40..0x1000]);
|
||||
match hash.as_str() {
|
||||
"B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => {
|
||||
@@ -190,7 +193,7 @@ pub fn set_cic(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_hash(data: &[u8]) -> String {
|
||||
pub fn calculate_hash(data: &[u8]) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data);
|
||||
format!("{:X}", hasher.finalize())
|
||||
@@ -0,0 +1,233 @@
|
||||
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_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;
|
||||
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.memory_map_write[(address >> 16) as usize](
|
||||
device,
|
||||
address + i as u64,
|
||||
data,
|
||||
0xFFFFFFFF,
|
||||
);
|
||||
} 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;
|
||||
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.memory_map_read[(address >> 16) as usize](
|
||||
device,
|
||||
address + i as u64,
|
||||
device::memory::AccessSize::Word,
|
||||
)
|
||||
.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;
|
||||
}
|
||||
_ => {
|
||||
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)
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub const SRAM_MASK: usize = 0xFFFF;
|
||||
const SRAM_MASK: usize = 0xFFFF;
|
||||
pub const SRAM_SIZE: usize = 0x8000;
|
||||
pub const FLASHRAM_SIZE: usize = 0x20000;
|
||||
pub const FLASHRAM_TYPE_ID: u32 = 0x11118001;
|
||||
pub const MX29L1100_ID: u32 = 0x00c2001e;
|
||||
pub const MX29L0000_ID: u32 = 0x00c20000;
|
||||
pub const MX29L0001_ID: u32 = 0x00c20001;
|
||||
const MX29L0000_ID: u32 = 0x00c20000;
|
||||
const MX29L0001_ID: u32 = 0x00c20001;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum FlashramMode {
|
||||
@@ -27,19 +27,19 @@ pub struct Flashram {
|
||||
pub silicon_id: [u32; 2],
|
||||
}
|
||||
|
||||
pub fn format_sram(device: &mut device::Device) {
|
||||
fn format_sram(device: &mut device::Device) {
|
||||
if device.ui.saves.sram.0.len() < SRAM_SIZE {
|
||||
device.ui.saves.sram.0.resize(SRAM_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_flash(device: &mut device::Device) {
|
||||
fn format_flash(device: &mut device::Device) {
|
||||
if device.ui.saves.flash.0.len() < FLASHRAM_SIZE {
|
||||
device.ui.saves.flash.0.resize(FLASHRAM_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
|
||||
fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
|
||||
let masked_address = address as usize & SRAM_MASK;
|
||||
|
||||
format_sram(device);
|
||||
@@ -51,7 +51,7 @@ pub fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
|
||||
fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
|
||||
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
|
||||
/* read Status register */
|
||||
device.flashram.status
|
||||
@@ -80,7 +80,7 @@ pub fn read_mem(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let masked_address = address as usize & SRAM_MASK;
|
||||
|
||||
format_sram(device);
|
||||
@@ -96,7 +96,7 @@ pub fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mas
|
||||
device.ui.saves.sram.1 = true
|
||||
}
|
||||
|
||||
pub fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
|
||||
/* clear/set Status register */
|
||||
device.flashram.status = (value & mask) & 0xff;
|
||||
@@ -128,12 +128,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
);
|
||||
}
|
||||
|
||||
pub fn dma_read_sram(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) {
|
||||
fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr: u32, length: u32) {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= SRAM_MASK as u32;
|
||||
let mut i = dram_addr;
|
||||
@@ -141,7 +136,7 @@ pub fn dma_read_sram(
|
||||
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length {
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.ui.saves.sram.0[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
|
||||
i += 1;
|
||||
j += 1;
|
||||
@@ -150,7 +145,7 @@ pub fn dma_read_sram(
|
||||
device.ui.saves.sram.1 = true
|
||||
}
|
||||
|
||||
pub fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
|
||||
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
|
||||
format_flash(device);
|
||||
|
||||
if (cart_addr & 0x1ffff) == 0x00000
|
||||
@@ -178,7 +173,7 @@ pub fn dma_read(device: &mut device::Device, cart_addr: u32, dram_addr: u32, len
|
||||
device::pi::calculate_cycles(device, 2, length)
|
||||
}
|
||||
|
||||
pub fn dma_write_sram(
|
||||
fn dma_write_sram(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
@@ -191,14 +186,14 @@ pub fn dma_write_sram(
|
||||
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length {
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.0[j as usize];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dma_write_flash(
|
||||
fn dma_write_flash(
|
||||
device: &mut device::Device,
|
||||
mut cart_addr: u32,
|
||||
mut dram_addr: u32,
|
||||
@@ -251,7 +246,7 @@ pub fn dma_write(device: &mut device::Device, cart_addr: u32, dram_addr: u32, le
|
||||
device::pi::calculate_cycles(device, 2, length)
|
||||
}
|
||||
|
||||
pub fn flashram_command(device: &mut device::Device, command: u32) {
|
||||
fn flashram_command(device: &mut device::Device, command: u32) {
|
||||
match command & 0xff000000 {
|
||||
0x3c000000 => {
|
||||
/* set chip erase mode */
|
||||
+91
-27
@@ -1,31 +1,38 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
|
||||
pub mod mempak;
|
||||
pub mod rumble;
|
||||
pub mod vru;
|
||||
|
||||
pub const JCMD_STATUS: u8 = 0x00;
|
||||
pub const JCMD_CONTROLLER_READ: u8 = 0x01;
|
||||
pub const JCMD_PAK_READ: u8 = 0x02;
|
||||
pub const JCMD_PAK_WRITE: u8 = 0x03;
|
||||
//pub const JCMD_VRU_READ: u8 = 0x09;
|
||||
//pub const JCMD_VRU_WRITE: u8 = 0x0A;
|
||||
//pub const JCMD_VRU_READ_STATUS: u8 = 0x0B;
|
||||
//pub const JCMD_VRU_WRITE_CONFIG: u8 = 0x0C;
|
||||
//pub const JCMD_VRU_WRITE_INIT: u8 = 0x0D;
|
||||
const JCMD_PAK_READ: u8 = 0x02;
|
||||
const JCMD_PAK_WRITE: u8 = 0x03;
|
||||
pub const JCMD_RESET: u8 = 0xff;
|
||||
|
||||
//pub const JDT_NONE: u16 = 0x0000;
|
||||
pub const JDT_JOY_ABS_COUNTERS: u16 = 0x0001; /* joystick with absolute coordinates */
|
||||
//pub const JDT_JOY_REL_COUNTERS: u16 = 0x0002; /* joystick with relative coordinates (= mouse) */
|
||||
pub const JDT_JOY_PORT: u16 = 0x0004; /* has port for external paks */
|
||||
//pub const JDT_VRU: u16 = 0x0100; /* VRU */
|
||||
pub const PAK_CHUNK_SIZE: usize = 0x20;
|
||||
pub const CONT_STATUS_PAK_NOT_PRESENT: u8 = 0;
|
||||
pub const CONT_STATUS_PAK_PRESENT: u8 = 1;
|
||||
pub const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT;
|
||||
//const JDT_NONE: u16 = 0x0000;
|
||||
const JDT_JOY_ABS_COUNTERS: u16 = 0x0001; /* joystick with absolute coordinates */
|
||||
//const JDT_JOY_REL_COUNTERS: u16 = 0x0002; /* joystick with relative coordinates (= mouse) */
|
||||
const JDT_JOY_PORT: u16 = 0x0004; /* has port for external paks */
|
||||
const PAK_CHUNK_SIZE: usize = 0x20;
|
||||
const CONT_STATUS_PAK_PRESENT: u8 = 1;
|
||||
const CONT_STATUS_PAK_NOT_PRESENT: u8 = 2;
|
||||
const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum PakType {
|
||||
None = 0,
|
||||
MemPak = 1,
|
||||
RumblePak = 2,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PakHandler {
|
||||
pub read: fn(&mut device::Device, usize, u16, usize, usize),
|
||||
pub write: fn(&mut device::Device, usize, u16, usize, usize),
|
||||
pub pak_type: PakType,
|
||||
}
|
||||
|
||||
pub fn process(device: &mut device::Device, channel: usize) {
|
||||
@@ -46,8 +53,34 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
}
|
||||
JCMD_CONTROLLER_READ => {
|
||||
let offset = device.pif.channels[channel].rx_buf.unwrap();
|
||||
let input = ui::input::get(&mut device.ui, channel);
|
||||
device.pif.ram[offset..offset + 4].copy_from_slice(&input.to_ne_bytes());
|
||||
let input = if device.netplay.is_none() {
|
||||
ui::input::get(&mut device.ui, channel)
|
||||
} else {
|
||||
if device.netplay.as_ref().unwrap().player_number as usize == channel {
|
||||
let local_input = ui::input::get(&mut device.ui, 0);
|
||||
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
|
||||
}
|
||||
|
||||
netplay::get_input(device, channel)
|
||||
};
|
||||
|
||||
device.pif.ram[offset..offset + 4].copy_from_slice(&input.0.to_ne_bytes());
|
||||
if input.1 {
|
||||
// pak change button pressed
|
||||
if device::events::get_event(device, device::events::EventType::PakSwitch).is_none()
|
||||
{
|
||||
device.pif.channels[channel].change_pak =
|
||||
device.pif.channels[channel].pak_handler.unwrap().pak_type;
|
||||
device.pif.channels[channel].pak_handler = None;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::PakSwitch,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
|
||||
+ (device.cpu.clock_rate), // 1 second
|
||||
pak_switch_event,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
JCMD_PAK_READ => pak_read_block(
|
||||
device,
|
||||
@@ -67,7 +100,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pak_read_block(
|
||||
fn pak_read_block(
|
||||
device: &mut device::Device,
|
||||
addr_acrc: usize,
|
||||
data: usize,
|
||||
@@ -75,18 +108,18 @@ pub fn pak_read_block(
|
||||
channel: usize,
|
||||
) {
|
||||
let address =
|
||||
(device.pif.ram[addr_acrc] as u16) << 8 | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
|
||||
((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
|
||||
let handler = device.pif.channels[channel].pak_handler;
|
||||
|
||||
if handler.is_some() {
|
||||
(handler.unwrap().read)(device, channel, address, data, PAK_CHUNK_SIZE);
|
||||
device.pif.ram[dcrc] = pak_data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
} else {
|
||||
device.pif.ram[dcrc] = !pak_data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pak_write_block(
|
||||
fn pak_write_block(
|
||||
device: &mut device::Device,
|
||||
addr_acrc: usize,
|
||||
data: usize,
|
||||
@@ -94,18 +127,18 @@ pub fn pak_write_block(
|
||||
channel: usize,
|
||||
) {
|
||||
let address =
|
||||
(device.pif.ram[addr_acrc] as u16) << 8 | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
|
||||
((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
|
||||
let handler = device.pif.channels[channel].pak_handler;
|
||||
|
||||
if handler.is_some() {
|
||||
(handler.unwrap().write)(device, channel, address, data, PAK_CHUNK_SIZE);
|
||||
device.pif.ram[dcrc] = pak_data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
} else {
|
||||
device.pif.ram[dcrc] = !pak_data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pak_data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
|
||||
fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
|
||||
let mut i = 0;
|
||||
let mut crc = 0;
|
||||
|
||||
@@ -124,3 +157,34 @@ pub fn pak_data_crc(device: &device::Device, data_offset: usize, size: usize) ->
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
fn pak_switch_event(device: &mut device::Device) {
|
||||
for (i, channel) in device.pif.channels.iter_mut().enumerate() {
|
||||
if channel.change_pak != PakType::None {
|
||||
if channel.change_pak == PakType::RumblePak {
|
||||
if device.netplay.is_none() {
|
||||
device::ui::input::set_rumble(&mut device.ui, i, 0);
|
||||
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
|
||||
device::ui::input::set_rumble(&mut device.ui, 0, 0);
|
||||
}
|
||||
|
||||
let handler = device::controller::PakHandler {
|
||||
read: device::controller::mempak::read,
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
};
|
||||
channel.pak_handler = Some(handler);
|
||||
ui::audio::play_pak_switch(&mut device.ui, PakType::MemPak);
|
||||
} else if channel.change_pak == PakType::MemPak {
|
||||
let handler = device::controller::PakHandler {
|
||||
read: device::controller::rumble::read,
|
||||
write: device::controller::rumble::write,
|
||||
pak_type: device::controller::PakType::RumblePak,
|
||||
};
|
||||
channel.pak_handler = Some(handler);
|
||||
ui::audio::play_pak_switch(&mut device.ui, PakType::RumblePak);
|
||||
}
|
||||
channel.change_pak = PakType::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::device;
|
||||
|
||||
pub const MEMPAK_SIZE: usize = 0x8000;
|
||||
pub const MPK_PAGE_SIZE: usize = 256;
|
||||
const MPK_PAGE_SIZE: usize = 256;
|
||||
|
||||
pub fn format_mempak(device: &mut device::Device) {
|
||||
fn format_mempak(device: &mut device::Device) {
|
||||
if device.ui.saves.mempak.0.len() < MEMPAK_SIZE * 4 {
|
||||
device.ui.saves.mempak.0.resize(MEMPAK_SIZE * 4, 0);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use crate::device;
|
||||
|
||||
pub fn read(device: &mut device::Device, _channel: usize, address: u16, data: usize, size: usize) {
|
||||
let value: u8 = if (0x8000..0x9000).contains(&address) {
|
||||
0x80
|
||||
} else {
|
||||
0x00
|
||||
};
|
||||
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
if address == 0xc000 {
|
||||
let rumble = device.pif.ram[data + size - 1];
|
||||
if device.netplay.is_none() {
|
||||
device::ui::input::set_rumble(&mut device.ui, channel, rumble);
|
||||
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
|
||||
device::ui::input::set_rumble(&mut device.ui, 0, rumble);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+40
-39
@@ -1,37 +1,37 @@
|
||||
use crate::device;
|
||||
|
||||
pub const COP0_INDEX_REG: u32 = 0;
|
||||
pub const COP0_RANDOM_REG: u32 = 1;
|
||||
const COP0_RANDOM_REG: u32 = 1;
|
||||
pub const COP0_ENTRYLO0_REG: u32 = 2;
|
||||
pub const COP0_ENTRYLO1_REG: u32 = 3;
|
||||
pub const COP0_CONTEXT_REG: u32 = 4;
|
||||
pub const COP0_PAGEMASK_REG: u32 = 5;
|
||||
pub const COP0_WIRED_REG: u32 = 6;
|
||||
//pub const COP0_UNUSED_7: u32 = 7;
|
||||
const COP0_WIRED_REG: u32 = 6;
|
||||
//const COP0_UNUSED_7: u32 = 7;
|
||||
pub const COP0_BADVADDR_REG: u32 = 8;
|
||||
pub const COP0_COUNT_REG: u32 = 9;
|
||||
pub const COP0_ENTRYHI_REG: u32 = 10;
|
||||
pub const COP0_COMPARE_REG: u32 = 11;
|
||||
const COP0_COMPARE_REG: u32 = 11;
|
||||
pub const COP0_STATUS_REG: u32 = 12;
|
||||
pub const COP0_CAUSE_REG: u32 = 13;
|
||||
pub const COP0_EPC_REG: u32 = 14;
|
||||
pub const COP0_PREVID_REG: u32 = 15;
|
||||
pub const COP0_CONFIG_REG: u32 = 16;
|
||||
const COP0_PREVID_REG: u32 = 15;
|
||||
const COP0_CONFIG_REG: u32 = 16;
|
||||
pub const COP0_LLADDR_REG: u32 = 17;
|
||||
//pub const COP0_WATCHLO_REG: u32 = 18;
|
||||
//pub const COP0_WATCHHI_REG: u32 = 19;
|
||||
//const COP0_WATCHLO_REG: u32 = 18;
|
||||
//const COP0_WATCHHI_REG: u32 = 19;
|
||||
pub const COP0_XCONTEXT_REG: u32 = 20;
|
||||
//pub const COP0_UNUSED_21: u32 = 21;
|
||||
//pub const COP0_UNUSED_22: u32 = 22;
|
||||
//pub const COP0_UNUSED_23: u32 = 23;
|
||||
//pub const COP0_UNUSED_24: u32 = 24;
|
||||
//pub const COP0_UNUSED_25: u32 = 25;
|
||||
//pub const COP0_PARITYERR_REG: u32 = 26;
|
||||
//pub const COP0_CACHEERR_REG: u32 = 27;
|
||||
//const COP0_UNUSED_21: u32 = 21;
|
||||
//const COP0_UNUSED_22: u32 = 22;
|
||||
//const COP0_UNUSED_23: u32 = 23;
|
||||
//const COP0_UNUSED_24: u32 = 24;
|
||||
//const COP0_UNUSED_25: u32 = 25;
|
||||
//const COP0_PARITYERR_REG: u32 = 26;
|
||||
//const COP0_CACHEERR_REG: u32 = 27;
|
||||
pub const COP0_TAGLO_REG: u32 = 28;
|
||||
//pub const COP0_TAGHI_REG: u32 = 29;
|
||||
pub const COP0_ERROREPC_REG: u32 = 30;
|
||||
//pub const COP0_UNUSED_31: u32 = 31;
|
||||
//const COP0_TAGHI_REG: u32 = 29;
|
||||
const COP0_ERROREPC_REG: u32 = 30;
|
||||
//const COP0_UNUSED_31: u32 = 31;
|
||||
pub const COP0_REGS_COUNT: u32 = 32;
|
||||
|
||||
pub const COP0_STATUS_IE: u64 = 1 << 0;
|
||||
@@ -42,12 +42,12 @@ pub const COP0_STATUS_FR: u64 = 1 << 26;
|
||||
pub const COP0_STATUS_CU1: u64 = 1 << 29;
|
||||
pub const COP0_STATUS_CU2: u64 = 1 << 30;
|
||||
|
||||
//pub const COP0_CAUSE_EXCCODE_INTR: u64 = 0 << 2;
|
||||
//const COP0_CAUSE_EXCCODE_INTR: u64 = 0 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_MOD: u64 = 1 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_TLBL: u64 = 2 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_TLBS: u64 = 3 << 2;
|
||||
//pub const COP0_CAUSE_EXCCODE_ADEL: u64 = 4 << 2;
|
||||
//pub const COP0_CAUSE_EXCCODE_ADES: u64 = 5 << 2;
|
||||
//const COP0_CAUSE_EXCCODE_ADEL: u64 = 4 << 2;
|
||||
//const COP0_CAUSE_EXCCODE_ADES: u64 = 5 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_SYS: u64 = 8 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_BP: u64 = 9 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_RI: u64 = 10 << 2;
|
||||
@@ -62,7 +62,7 @@ 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,20 +97,22 @@ 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,
|
||||
}
|
||||
|
||||
pub fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device::cpu_instructions::se32(
|
||||
(get_control_registers(device, device::cpu_instructions::rd(opcode))) as u32 as i32,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dmfc0(device: &mut device::Device, opcode: u32) {
|
||||
fn dmfc0(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] =
|
||||
get_control_registers(device, device::cpu_instructions::rd(opcode))
|
||||
}
|
||||
|
||||
pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
device::cop0::set_control_registers(
|
||||
device,
|
||||
device::cpu_instructions::rd(opcode),
|
||||
@@ -120,7 +122,7 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dmtc0(device: &mut device::Device, opcode: u32) {
|
||||
fn dmtc0(device: &mut device::Device, opcode: u32) {
|
||||
device::cop0::set_control_registers(
|
||||
device,
|
||||
device::cpu_instructions::rd(opcode),
|
||||
@@ -128,24 +130,24 @@ pub fn dmtc0(device: &mut device::Device, opcode: u32) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn tlbr(device: &mut device::Device, _opcode: u32) {
|
||||
fn tlbr(device: &mut device::Device, _opcode: u32) {
|
||||
device::tlb::read(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
|
||||
}
|
||||
|
||||
pub fn tlbwi(device: &mut device::Device, _opcode: u32) {
|
||||
fn tlbwi(device: &mut device::Device, _opcode: u32) {
|
||||
device::tlb::write(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
|
||||
}
|
||||
|
||||
pub fn tlbwr(device: &mut device::Device, _opcode: u32) {
|
||||
fn tlbwr(device: &mut device::Device, _opcode: u32) {
|
||||
let random = set_random_register(device);
|
||||
device::tlb::write(device, random)
|
||||
}
|
||||
|
||||
pub fn tlbp(device: &mut device::Device, _opcode: u32) {
|
||||
fn tlbp(device: &mut device::Device, _opcode: u32) {
|
||||
device::tlb::probe(device)
|
||||
}
|
||||
|
||||
pub fn eret(device: &mut device::Device, _opcode: u32) {
|
||||
fn eret(device: &mut device::Device, _opcode: u32) {
|
||||
if device.cpu.cop0.regs[COP0_STATUS_REG as usize] & COP0_STATUS_ERL != 0 {
|
||||
device.cpu.pc = device.cpu.cop0.regs[COP0_ERROREPC_REG as usize];
|
||||
device.cpu.cop0.regs[COP0_STATUS_REG as usize] &= !COP0_STATUS_ERL
|
||||
@@ -158,7 +160,7 @@ pub fn eret(device: &mut device::Device, _opcode: u32) {
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn execute_cp0(device: &mut device::Device, opcode: u32) {
|
||||
fn execute_cp0(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop0.instrs2[(opcode & 0x3F) as usize](device, opcode)
|
||||
}
|
||||
|
||||
@@ -174,7 +176,7 @@ pub fn reserved(device: &mut device::Device, _opcode: u32) {
|
||||
device::exceptions::reserved_exception(device, 0);
|
||||
}
|
||||
|
||||
pub fn get_control_registers(device: &device::Device, index: u32) -> u64 {
|
||||
fn get_control_registers(device: &device::Device, index: u32) -> u64 {
|
||||
match index {
|
||||
COP0_COUNT_REG => device.cpu.cop0.regs[index as usize] >> 1,
|
||||
COP0_RANDOM_REG => set_random_register(device),
|
||||
@@ -183,7 +185,7 @@ pub fn get_control_registers(device: &device::Device, index: u32) -> u64 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
|
||||
fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
|
||||
device.cpu.cop0.reg_latch = data;
|
||||
match index {
|
||||
COP0_COUNT_REG => {
|
||||
@@ -214,6 +216,7 @@ pub fn set_control_registers(device: &mut device::Device, index: u32, mut data:
|
||||
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 {
|
||||
@@ -230,10 +233,8 @@ pub fn set_control_registers(device: &mut device::Device, index: u32, mut data:
|
||||
device::exceptions::check_pending_interrupts(device);
|
||||
}
|
||||
|
||||
pub 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;
|
||||
|
||||
fn compare_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.pending_compare_interrupt = true;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Compare,
|
||||
@@ -243,7 +244,7 @@ pub fn compare_event(device: &mut device::Device) {
|
||||
device::exceptions::check_pending_interrupts(device);
|
||||
}
|
||||
|
||||
pub fn set_random_register(device: &device::Device) -> u64 {
|
||||
fn set_random_register(device: &device::Device) -> u64 {
|
||||
if device.cpu.cop0.regs[COP0_WIRED_REG as usize] > 31 {
|
||||
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG as usize]) & 0x3F
|
||||
} else {
|
||||
|
||||
+47
-41
@@ -1,32 +1,34 @@
|
||||
use crate::device;
|
||||
|
||||
//pub const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2;
|
||||
//pub const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3;
|
||||
//pub const FCR31_FLAG_OVERFLOW_BIT: u32 = 1 << 4;
|
||||
//pub const FCR31_FLAG_DIVBYZERO_BIT: u32 = 1 << 5;
|
||||
//pub const FCR31_FLAG_INVALID_BIT: u32 = 1 << 6;
|
||||
//pub const FCR31_ENABLE_INEXACT_BIT: u32 = 1 << 7;
|
||||
//pub const FCR31_ENABLE_UNDERFLOW_BIT: u32 = 1 << 8;
|
||||
//pub const FCR31_ENABLE_OVERFLOW_BIT: u32 = 1 << 9;
|
||||
//pub const FCR31_ENABLE_DIVBYZERO_BIT: u32 = 1 << 10;
|
||||
//pub const FCR31_ENABLE_INVALID_BIT: u32 = 1 << 11;
|
||||
//pub const FCR31_CAUSE_INEXACT_BIT: u32 = 1 << 12;
|
||||
//pub const FCR31_CAUSE_UNDERFLOW_BIT: u32 = 1 << 13;
|
||||
//pub const FCR31_CAUSE_OVERFLOW_BIT: u32 = 1 << 14;
|
||||
//pub const FCR31_CAUSE_DIVBYZERO_BIT: u32 = 1 << 15;
|
||||
//pub const FCR31_CAUSE_INVALID_BIT: u32 = 1 << 16;
|
||||
pub const FCR31_CAUSE_UNIMP_BIT: u32 = 1 << 17;
|
||||
//const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2;
|
||||
//const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3;
|
||||
//const FCR31_FLAG_OVERFLOW_BIT: u32 = 1 << 4;
|
||||
//const FCR31_FLAG_DIVBYZERO_BIT: u32 = 1 << 5;
|
||||
//const FCR31_FLAG_INVALID_BIT: u32 = 1 << 6;
|
||||
//const FCR31_ENABLE_INEXACT_BIT: u32 = 1 << 7;
|
||||
//const FCR31_ENABLE_UNDERFLOW_BIT: u32 = 1 << 8;
|
||||
//const FCR31_ENABLE_OVERFLOW_BIT: u32 = 1 << 9;
|
||||
//const FCR31_ENABLE_DIVBYZERO_BIT: u32 = 1 << 10;
|
||||
//const FCR31_ENABLE_INVALID_BIT: u32 = 1 << 11;
|
||||
//const FCR31_CAUSE_INEXACT_BIT: u32 = 1 << 12;
|
||||
//const FCR31_CAUSE_UNDERFLOW_BIT: u32 = 1 << 13;
|
||||
//const FCR31_CAUSE_OVERFLOW_BIT: u32 = 1 << 14;
|
||||
//const FCR31_CAUSE_DIVBYZERO_BIT: u32 = 1 << 15;
|
||||
//const FCR31_CAUSE_INVALID_BIT: u32 = 1 << 16;
|
||||
const FCR31_CAUSE_UNIMP_BIT: u32 = 1 << 17;
|
||||
pub const FCR31_CMP_BIT: u32 = 1 << 23;
|
||||
pub const FCR31_FS_BIT: u32 = 1 << 24;
|
||||
//#[cfg(target_arch = "x86_64")]
|
||||
//const FCR31_FS_BIT: u32 = 1 << 24;
|
||||
|
||||
pub const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000;
|
||||
pub const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000;
|
||||
pub const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111;
|
||||
const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000;
|
||||
const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000;
|
||||
const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111;
|
||||
|
||||
pub struct Cop1 {
|
||||
pub fcr0: u32,
|
||||
pub fcr31: u32,
|
||||
pub flush_mode: u32,
|
||||
//#[cfg(target_arch = "x86_64")]
|
||||
//pub flush_mode: u32,
|
||||
pub fgr32: [[u8; 4]; 32],
|
||||
pub fgr64: [[u8; 8]; 32],
|
||||
pub instrs: [fn(&mut device::Device, u32); 32],
|
||||
@@ -171,7 +173,7 @@ pub fn sdc1(device: &mut device::Device, opcode: u32) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mfc1(device: &mut device::Device, opcode: u32) {
|
||||
fn mfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -183,7 +185,7 @@ pub fn mfc1(device: &mut device::Device, opcode: u32) {
|
||||
device::cpu_instructions::se32(u32::from_ne_bytes(value.to_ne_bytes()) as i32);
|
||||
}
|
||||
|
||||
pub fn dmfc1(device: &mut device::Device, opcode: u32) {
|
||||
fn dmfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -195,7 +197,7 @@ pub fn dmfc1(device: &mut device::Device, opcode: u32) {
|
||||
u64::from_ne_bytes(value.to_ne_bytes());
|
||||
}
|
||||
|
||||
pub fn cfc1(device: &mut device::Device, opcode: u32) {
|
||||
fn cfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -207,7 +209,7 @@ pub fn cfc1(device: &mut device::Device, opcode: u32) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dcfc1(device: &mut device::Device, opcode: u32) {
|
||||
fn dcfc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -219,7 +221,7 @@ pub fn dcfc1(device: &mut device::Device, opcode: u32) {
|
||||
device::exceptions::floating_point_exception(device)
|
||||
}
|
||||
|
||||
pub fn mtc1(device: &mut device::Device, opcode: u32) {
|
||||
fn mtc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -237,7 +239,7 @@ pub fn mtc1(device: &mut device::Device, opcode: u32) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dmtc1(device: &mut device::Device, opcode: u32) {
|
||||
fn dmtc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -250,7 +252,7 @@ pub fn dmtc1(device: &mut device::Device, opcode: u32) {
|
||||
set_fpr_double(device, device::fpu_instructions::fs(opcode) as usize, value)
|
||||
}
|
||||
|
||||
pub fn ctc1(device: &mut device::Device, opcode: u32) {
|
||||
fn ctc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -264,7 +266,7 @@ pub fn ctc1(device: &mut device::Device, opcode: u32) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dctc1(device: &mut device::Device, opcode: u32) {
|
||||
fn dctc1(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -276,7 +278,7 @@ pub fn dctc1(device: &mut device::Device, opcode: u32) {
|
||||
device::exceptions::floating_point_exception(device)
|
||||
}
|
||||
|
||||
pub fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
|
||||
fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -286,7 +288,7 @@ pub fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop1.b_instrs[((opcode >> 16) & 0x3) as usize](device, opcode)
|
||||
}
|
||||
|
||||
pub fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
|
||||
fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -296,7 +298,7 @@ pub fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop1.s_instrs[(opcode & 0x3F) as usize](device, opcode)
|
||||
}
|
||||
|
||||
pub fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
|
||||
fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -306,7 +308,7 @@ pub fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop1.d_instrs[(opcode & 0x3F) as usize](device, opcode)
|
||||
}
|
||||
|
||||
pub fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
|
||||
fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -316,7 +318,7 @@ pub fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop1.l_instrs[(opcode & 0x3F) as usize](device, opcode)
|
||||
}
|
||||
|
||||
pub fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
|
||||
fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
|
||||
== 0
|
||||
{
|
||||
@@ -326,7 +328,7 @@ pub fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop1.w_instrs[(opcode & 0x3F) as usize](device, opcode)
|
||||
}
|
||||
|
||||
pub fn unusable(device: &mut device::Device, _opcode: u32) {
|
||||
fn unusable(device: &mut device::Device, _opcode: u32) {
|
||||
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE1)
|
||||
}
|
||||
|
||||
@@ -340,7 +342,7 @@ pub fn reserved(device: &mut device::Device, opcode: u32) {
|
||||
device::exceptions::reserved_exception(device, device::cop0::COP0_CAUSE_CE1);
|
||||
}
|
||||
|
||||
pub fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
|
||||
fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
|
||||
match index {
|
||||
0 => device.cpu.cop1.fcr0,
|
||||
31 => device.cpu.cop1.fcr31,
|
||||
@@ -350,7 +352,7 @@ pub fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32) {
|
||||
fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32) {
|
||||
match index {
|
||||
0 => { // read only
|
||||
}
|
||||
@@ -359,15 +361,16 @@ pub fn set_control_registers_fpu(device: &mut device::Device, index: u32, data:
|
||||
device.cpu.cop1.fcr31 = data & FCR31_WRITE_MASK;
|
||||
// the Cause bits are ANDed with the Enable bits to check for exceptions
|
||||
// "Unimplemented Operation" has no Enable bit and always causes an exception
|
||||
if (device.cpu.cop1.fcr31 & FCR31_CAUSE_MASK) >> 5
|
||||
if ((device.cpu.cop1.fcr31 & FCR31_CAUSE_MASK) >> 5)
|
||||
& (device.cpu.cop1.fcr31 & FCR31_ENABLE_MASK)
|
||||
!= 0
|
||||
|| device.cpu.cop1.fcr31 & FCR31_CAUSE_UNIMP_BIT != 0
|
||||
{
|
||||
device::exceptions::floating_point_exception(device)
|
||||
}
|
||||
|
||||
unsafe {
|
||||
/*
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
let flush_mode;
|
||||
if (device.cpu.cop1.fcr31 & 2) != 0 {
|
||||
if (device.cpu.cop1.fcr31 & FCR31_FS_BIT) != 0 {
|
||||
@@ -380,10 +383,13 @@ pub fn set_control_registers_fpu(device: &mut device::Device, index: u32, data:
|
||||
}
|
||||
if flush_mode != device.cpu.cop1.flush_mode {
|
||||
#[allow(deprecated)]
|
||||
std::arch::x86_64::_MM_SET_FLUSH_ZERO_MODE(flush_mode);
|
||||
unsafe {
|
||||
std::arch::x86_64::_MM_SET_FLUSH_ZERO_MODE(flush_mode)
|
||||
};
|
||||
device.cpu.cop1.flush_mode = flush_mode;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
_ => {
|
||||
panic!("unknown FCR register")
|
||||
|
||||
+7
-7
@@ -5,7 +5,7 @@ pub struct Cop2 {
|
||||
pub reg_latch: u64,
|
||||
}
|
||||
|
||||
pub fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
@@ -15,7 +15,7 @@ pub fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
device::cpu_instructions::se32((device.cpu.cop2.reg_latch) as u32 as i32)
|
||||
}
|
||||
|
||||
pub fn dmfc2(device: &mut device::Device, opcode: u32) {
|
||||
fn dmfc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
@@ -24,7 +24,7 @@ pub fn dmfc2(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device.cpu.cop2.reg_latch
|
||||
}
|
||||
|
||||
pub fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
@@ -34,7 +34,7 @@ pub fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
device::cpu_instructions::se32((device.cpu.cop2.reg_latch) as u32 as i32)
|
||||
}
|
||||
|
||||
pub fn mtc2(device: &mut device::Device, opcode: u32) {
|
||||
fn mtc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
@@ -43,7 +43,7 @@ pub fn mtc2(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
|
||||
}
|
||||
|
||||
pub fn dmtc2(device: &mut device::Device, opcode: u32) {
|
||||
fn dmtc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
@@ -52,7 +52,7 @@ pub fn dmtc2(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
|
||||
}
|
||||
|
||||
pub fn ctc2(device: &mut device::Device, opcode: u32) {
|
||||
fn ctc2(device: &mut device::Device, opcode: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
|
||||
== 0
|
||||
{
|
||||
@@ -72,7 +72,7 @@ pub fn reserved(device: &mut device::Device, opcode: u32) {
|
||||
device::exceptions::reserved_exception(device, device::cop0::COP0_CAUSE_CE2);
|
||||
}
|
||||
|
||||
pub fn unusable(device: &mut device::Device, _opcode: u32) {
|
||||
fn unusable(device: &mut device::Device, _opcode: u32) {
|
||||
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE2)
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -25,13 +25,13 @@ pub struct Cpu {
|
||||
pub pc_phys: u64,
|
||||
pub lo: u64,
|
||||
pub hi: u64,
|
||||
pub running: u8,
|
||||
pub running: bool,
|
||||
pub llbit: bool,
|
||||
pub clock_rate: u64,
|
||||
pub instrs: [fn(&mut device::Device, u32); 64],
|
||||
pub special_instrs: [fn(&mut device::Device, u32); 64],
|
||||
pub regimm_instrs: [fn(&mut device::Device, u32); 32],
|
||||
pub events: [device::events::Event; device::events::EventType::EventTypeCount as usize],
|
||||
pub events: [device::events::Event; device::events::EventType::Count as usize],
|
||||
pub next_event_count: u64,
|
||||
pub next_event: usize,
|
||||
}
|
||||
@@ -231,8 +231,8 @@ pub fn in_delay_slot_taken(device: &device::Device) -> bool {
|
||||
}
|
||||
|
||||
pub fn run(device: &mut device::Device) {
|
||||
device.cpu.running = 1;
|
||||
while device.cpu.running == 1 {
|
||||
device.cpu.running = true;
|
||||
while device.cpu.running {
|
||||
device.cpu.gpr[0] = 0; // gpr 0 is read only
|
||||
let (cached, err);
|
||||
(device.cpu.pc_phys, cached, err) = device::memory::translate_address(
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn rt(opcode: u32) -> u32 {
|
||||
(opcode >> 16) & 0x1F
|
||||
}
|
||||
|
||||
pub fn sa(opcode: u32) -> u32 {
|
||||
fn sa(opcode: u32) -> u32 {
|
||||
(opcode >> 6) & 0x1F
|
||||
}
|
||||
|
||||
@@ -28,23 +28,23 @@ pub fn se16(value: i16) -> u64 {
|
||||
value as i64 as u64
|
||||
}
|
||||
|
||||
pub fn se8(value: i8) -> u64 {
|
||||
fn se8(value: i8) -> u64 {
|
||||
value as i64 as u64
|
||||
}
|
||||
|
||||
pub fn bshift<T: Into<u64>>(address: T) -> u64 {
|
||||
fn bshift<T: Into<u64>>(address: T) -> u64 {
|
||||
((address.into() & 3) ^ 3) << 3
|
||||
}
|
||||
|
||||
pub fn hshift<T: Into<u64>>(address: T) -> u64 {
|
||||
fn hshift<T: Into<u64>>(address: T) -> u64 {
|
||||
((address.into() & 2) ^ 2) << 3
|
||||
}
|
||||
|
||||
pub fn bits_below_mask<T: Into<u64>>(x: T) -> u64 {
|
||||
fn bits_below_mask<T: Into<u64>>(x: T) -> u64 {
|
||||
(1 << x.into()) - 1
|
||||
}
|
||||
|
||||
pub fn bits_above_mask<T: Into<u64>>(x: T) -> u64 {
|
||||
fn bits_above_mask<T: Into<u64>>(x: T) -> u64 {
|
||||
!bits_below_mask(x)
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ pub fn check_relative_idle_loop(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_absolute_idle_loop(device: &mut device::Device, opcode: u32) {
|
||||
if (opcode & 0x3FFFFFF) as u64 == (device.cpu.pc_phys & 0x0FFFFFFF) >> 2
|
||||
fn check_absolute_idle_loop(device: &mut device::Device, target: u64) {
|
||||
if device.cpu.pc == target
|
||||
&& device.memory.fast_read[(device.cpu.pc_phys >> 16) as usize](
|
||||
device,
|
||||
device.cpu.pc_phys + 4,
|
||||
@@ -76,10 +76,10 @@ pub fn j(device: &mut device::Device, opcode: u32) {
|
||||
if device::cpu::in_delay_slot_taken(device) {
|
||||
return;
|
||||
}
|
||||
check_absolute_idle_loop(device, opcode);
|
||||
let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
|
||||
check_absolute_idle_loop(device, target);
|
||||
device.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.cpu.branch_state.pc =
|
||||
(device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64
|
||||
device.cpu.branch_state.pc = target
|
||||
}
|
||||
|
||||
pub fn jal(device: &mut device::Device, opcode: u32) {
|
||||
@@ -89,10 +89,10 @@ pub fn jal(device: &mut device::Device, opcode: u32) {
|
||||
device.cpu.gpr[31] = device.cpu.pc + 8
|
||||
}
|
||||
if !device::cpu::in_delay_slot_taken(device) {
|
||||
check_absolute_idle_loop(device, opcode);
|
||||
let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
|
||||
check_absolute_idle_loop(device, target);
|
||||
device.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.cpu.branch_state.pc =
|
||||
(device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64
|
||||
device.cpu.branch_state.pc = target
|
||||
} else if !device::cpu::in_delay_slot(device) {
|
||||
device.cpu.branch_state.state = device::cpu::State::NotTaken;
|
||||
}
|
||||
@@ -346,12 +346,12 @@ pub fn lwl(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
device.cpu.gpr[rt(opcode) as usize] = se32(
|
||||
((device.cpu.gpr[rt(opcode) as usize] as u32) & mask
|
||||
| device::memory::data_read(
|
||||
| (device::memory::data_read(
|
||||
device,
|
||||
phys_address,
|
||||
device::memory::AccessSize::Word,
|
||||
cached,
|
||||
) << shift) as i32,
|
||||
) << shift)) as i32,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -431,12 +431,12 @@ pub fn lwr(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
device.cpu.gpr[rt(opcode) as usize] = se32(
|
||||
((device.cpu.gpr[rt(opcode) as usize] as u32) & (mask as u32)
|
||||
| device::memory::data_read(
|
||||
| (device::memory::data_read(
|
||||
device,
|
||||
phys_address,
|
||||
device::memory::AccessSize::Word,
|
||||
cached,
|
||||
) >> shift) as i32,
|
||||
) >> shift)) as i32,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -966,7 +966,6 @@ pub fn srav(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
pub fn jr(device: &mut device::Device, opcode: u32) {
|
||||
if !device::cpu::in_delay_slot_taken(device) {
|
||||
check_relative_idle_loop(device, opcode);
|
||||
device.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
|
||||
} else if !device::cpu::in_delay_slot(device) {
|
||||
@@ -978,7 +977,6 @@ pub fn jalr(device: &mut device::Device, opcode: u32) {
|
||||
let in_delay_slot_taken = device::cpu::in_delay_slot_taken(device);
|
||||
|
||||
if !in_delay_slot_taken {
|
||||
check_relative_idle_loop(device, opcode);
|
||||
device.cpu.branch_state.state = device::cpu::State::Take;
|
||||
device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
|
||||
} else if !device::cpu::in_delay_slot(device) {
|
||||
|
||||
@@ -8,10 +8,12 @@ pub enum EventType {
|
||||
PI,
|
||||
DP,
|
||||
SP,
|
||||
InterruptCheck,
|
||||
Interrupt,
|
||||
SPDma,
|
||||
Compare,
|
||||
EventTypeCount,
|
||||
Vru,
|
||||
PakSwitch,
|
||||
Count,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
@@ -51,11 +53,13 @@ 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);
|
||||
}
|
||||
|
||||
pub fn set_next_event(device: &mut device::Device) {
|
||||
fn set_next_event(device: &mut device::Device) {
|
||||
device.cpu.next_event_count = u64::MAX;
|
||||
for (pos, i) in device.cpu.events.iter().enumerate() {
|
||||
if i.enabled && i.count < device.cpu.next_event_count {
|
||||
|
||||
+33
-10
@@ -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);
|
||||
}
|
||||
|
||||
@@ -133,7 +156,7 @@ pub fn tlb_miss_exception(
|
||||
exception_general(device, vector_offset)
|
||||
}
|
||||
|
||||
pub fn exception_general(device: &mut device::Device, vector_offset: u32) {
|
||||
fn exception_general(device: &mut device::Device, vector_offset: u32) {
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_EXL
|
||||
== 0
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ pub fn ft(opcode: u32) -> u32 {
|
||||
(opcode >> 16) & 0x1F
|
||||
}
|
||||
|
||||
pub fn fd(opcode: u32) -> u32 {
|
||||
fn fd(opcode: u32) -> u32 {
|
||||
(opcode >> 6) & 0x1F
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::device;
|
||||
|
||||
pub const IS_VIEWER_MASK: usize = 0xFFFF;
|
||||
const IS_VIEWER_MASK: usize = 0xFFFF;
|
||||
|
||||
pub fn read_mem(
|
||||
device: &mut device::Device,
|
||||
|
||||
+30
-25
@@ -1,24 +1,26 @@
|
||||
use crate::device;
|
||||
|
||||
pub const MM_RDRAM_DRAM: usize = 0x00000000;
|
||||
pub const MM_RDRAM_REGS: usize = 0x03f00000;
|
||||
pub const MM_RSP_MEM: usize = 0x04000000;
|
||||
pub const MM_RSP_REGS: usize = 0x04040000;
|
||||
pub const MM_RSP_REGS_PC: usize = 0x04080000;
|
||||
pub const MM_DPC_REGS: usize = 0x04100000;
|
||||
pub const MM_DPS_REGS: usize = 0x04200000;
|
||||
pub const MM_MI_REGS: usize = 0x04300000;
|
||||
pub const MM_VI_REGS: usize = 0x04400000;
|
||||
pub const MM_AI_REGS: usize = 0x04500000;
|
||||
pub const MM_PI_REGS: usize = 0x04600000;
|
||||
pub const MM_RI_REGS: usize = 0x04700000;
|
||||
pub const MM_SI_REGS: usize = 0x04800000;
|
||||
const MM_RDRAM_DRAM: usize = 0x00000000;
|
||||
const MM_RDRAM_REGS: usize = 0x03f00000;
|
||||
const MM_RSP_MEM: usize = 0x04000000;
|
||||
const MM_RSP_REGS: usize = 0x04040000;
|
||||
const MM_RSP_REGS_PC: usize = 0x04080000;
|
||||
const MM_DPC_REGS: usize = 0x04100000;
|
||||
const MM_DPS_REGS: usize = 0x04200000;
|
||||
const MM_MI_REGS: usize = 0x04300000;
|
||||
const MM_VI_REGS: usize = 0x04400000;
|
||||
const MM_AI_REGS: usize = 0x04500000;
|
||||
const MM_PI_REGS: usize = 0x04600000;
|
||||
const MM_RI_REGS: usize = 0x04700000;
|
||||
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;
|
||||
pub const MM_PIF_MEM: usize = 0x1fc00000;
|
||||
//pub const MM_DOM1_ADDR3: usize = 0x1fd00000;
|
||||
pub const MM_IS_VIEWER: usize = 0x13ff0000;
|
||||
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 {
|
||||
@@ -56,13 +58,10 @@ pub fn translate_address(
|
||||
address: u64,
|
||||
access_type: AccessType,
|
||||
) -> (u64, bool, bool) {
|
||||
let mut cached = false;
|
||||
if (address & 0xc0000000) != 0x80000000 {
|
||||
return device::tlb::get_physical_address(device, address, access_type);
|
||||
} else if address & 0x20000000 == 0 {
|
||||
cached = true;
|
||||
}
|
||||
(address & 0x1FFFFFFF, cached, false)
|
||||
(address & 0x1FFFFFFF, address & 0x20000000 == 0, false)
|
||||
}
|
||||
|
||||
pub fn data_read(
|
||||
@@ -145,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)
|
||||
|
||||
+28
-56
@@ -1,7 +1,7 @@
|
||||
use crate::device;
|
||||
|
||||
pub const MI_INIT_MODE_REG: u32 = 0;
|
||||
pub const MI_VERSION_REG: u32 = 1;
|
||||
const MI_INIT_MODE_REG: u32 = 0;
|
||||
const MI_VERSION_REG: u32 = 1;
|
||||
pub const MI_INTR_REG: u32 = 2;
|
||||
pub const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_REGS_COUNT: u32 = 4;
|
||||
@@ -15,34 +15,34 @@ pub const MI_INTR_PI: u32 = 1 << 4;
|
||||
pub const MI_INTR_DP: u32 = 1 << 5;
|
||||
|
||||
/* write */
|
||||
pub const MI_CLR_SP: u32 = 1 << 0;
|
||||
pub const MI_SET_SP: u32 = 1 << 1;
|
||||
pub const MI_CLR_SI: u32 = 1 << 2;
|
||||
pub const MI_SET_SI: u32 = 1 << 3;
|
||||
pub const MI_CLR_AI: u32 = 1 << 4;
|
||||
pub const MI_SET_AI: u32 = 1 << 5;
|
||||
pub const MI_CLR_VI: u32 = 1 << 6;
|
||||
pub const MI_SET_VI: u32 = 1 << 7;
|
||||
pub const MI_CLR_PI: u32 = 1 << 8;
|
||||
pub const MI_SET_PI: u32 = 1 << 9;
|
||||
pub const MI_CLR_DP: u32 = 1 << 10;
|
||||
pub const MI_SET_DP: u32 = 1 << 11;
|
||||
const MI_CLR_SP: u32 = 1 << 0;
|
||||
const MI_SET_SP: u32 = 1 << 1;
|
||||
const MI_CLR_SI: u32 = 1 << 2;
|
||||
const MI_SET_SI: u32 = 1 << 3;
|
||||
const MI_CLR_AI: u32 = 1 << 4;
|
||||
const MI_SET_AI: u32 = 1 << 5;
|
||||
const MI_CLR_VI: u32 = 1 << 6;
|
||||
const MI_SET_VI: u32 = 1 << 7;
|
||||
const MI_CLR_PI: u32 = 1 << 8;
|
||||
const MI_SET_PI: u32 = 1 << 9;
|
||||
const MI_CLR_DP: u32 = 1 << 10;
|
||||
const MI_SET_DP: u32 = 1 << 11;
|
||||
|
||||
/* mode read */
|
||||
pub const MI_INIT_MODE: u32 = 1 << 7;
|
||||
pub const MI_EBUS_MODE: u32 = 1 << 8;
|
||||
pub const MI_RDRAM_MODE: u32 = 1 << 9;
|
||||
const MI_INIT_MODE: u32 = 1 << 7;
|
||||
const MI_EBUS_MODE: u32 = 1 << 8;
|
||||
const MI_RDRAM_MODE: u32 = 1 << 9;
|
||||
|
||||
/* mode write */
|
||||
pub const MI_CLR_INIT: u32 = 1 << 7;
|
||||
pub const MI_SET_INIT: u32 = 1 << 8;
|
||||
pub const MI_CLR_EBUS: u32 = 1 << 9;
|
||||
pub const MI_SET_EBUS: u32 = 1 << 10;
|
||||
pub const MI_CLR_DP_INTR: u32 = 1 << 11;
|
||||
pub const MI_CLR_RDRAM: u32 = 1 << 12;
|
||||
pub const MI_SET_RDRAM: u32 = 1 << 13;
|
||||
const MI_CLR_INIT: u32 = 1 << 7;
|
||||
const MI_SET_INIT: u32 = 1 << 8;
|
||||
const MI_CLR_EBUS: u32 = 1 << 9;
|
||||
const MI_SET_EBUS: u32 = 1 << 10;
|
||||
const MI_CLR_DP_INTR: u32 = 1 << 11;
|
||||
const MI_CLR_RDRAM: u32 = 1 << 12;
|
||||
const MI_SET_RDRAM: u32 = 1 << 13;
|
||||
|
||||
pub const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
|
||||
const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
|
||||
|
||||
pub struct Mi {
|
||||
pub regs: [u32; MI_REGS_COUNT as usize],
|
||||
@@ -64,18 +64,14 @@ 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;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn update_init_mode(device: &mut device::Device, w: u32) {
|
||||
fn update_init_mode(device: &mut device::Device, w: u32) {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_LENGTH_MASK;
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= w & MI_INIT_LENGTH_MASK;
|
||||
|
||||
@@ -103,7 +99,7 @@ pub fn update_init_mode(device: &mut device::Device, w: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_intr_mask(device: &mut device::Device, w: u32) {
|
||||
fn update_intr_mask(device: &mut device::Device, w: u32) {
|
||||
if w & MI_CLR_SP != 0 {
|
||||
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_SP
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+40
-31
@@ -1,35 +1,36 @@
|
||||
use crate::device;
|
||||
|
||||
pub const PI_DRAM_ADDR_REG: u32 = 0;
|
||||
pub const PI_CART_ADDR_REG: u32 = 1;
|
||||
pub const PI_RD_LEN_REG: u32 = 2;
|
||||
pub const PI_WR_LEN_REG: u32 = 3;
|
||||
const PI_DRAM_ADDR_REG: u32 = 0;
|
||||
const PI_CART_ADDR_REG: u32 = 1;
|
||||
const PI_RD_LEN_REG: u32 = 2;
|
||||
const PI_WR_LEN_REG: u32 = 3;
|
||||
pub const PI_STATUS_REG: u32 = 4;
|
||||
pub const PI_BSD_DOM1_LAT_REG: u32 = 5;
|
||||
pub const PI_BSD_DOM1_PWD_REG: u32 = 6;
|
||||
pub const PI_BSD_DOM1_PGS_REG: u32 = 7;
|
||||
pub const PI_BSD_DOM1_RLS_REG: u32 = 8;
|
||||
pub const PI_BSD_DOM2_LAT_REG: u32 = 9;
|
||||
pub const PI_BSD_DOM2_PWD_REG: u32 = 10;
|
||||
pub const PI_BSD_DOM2_PGS_REG: u32 = 11;
|
||||
pub const PI_BSD_DOM2_RLS_REG: u32 = 12;
|
||||
pub const PI_REGS_COUNT: u32 = 13;
|
||||
const PI_BSD_DOM1_LAT_REG: u32 = 5;
|
||||
const PI_BSD_DOM1_PWD_REG: u32 = 6;
|
||||
const PI_BSD_DOM1_PGS_REG: u32 = 7;
|
||||
const PI_BSD_DOM1_RLS_REG: u32 = 8;
|
||||
const PI_BSD_DOM2_LAT_REG: u32 = 9;
|
||||
const PI_BSD_DOM2_PWD_REG: u32 = 10;
|
||||
const PI_BSD_DOM2_PGS_REG: u32 = 11;
|
||||
const PI_BSD_DOM2_RLS_REG: u32 = 12;
|
||||
//const UNKNOWN_REG: u32 = 13; //LibDragon
|
||||
pub const PI_REGS_COUNT: u32 = 14;
|
||||
|
||||
/* PI_STATUS - read */
|
||||
pub const PI_STATUS_DMA_BUSY: u32 = 1 << 0;
|
||||
const PI_STATUS_DMA_BUSY: u32 = 1 << 0;
|
||||
pub const PI_STATUS_IO_BUSY: u32 = 1 << 1;
|
||||
//pub const PI_STATUS_ERROR: u32 = 1 << 2;
|
||||
pub const PI_STATUS_INTERRUPT: u32 = 1 << 3;
|
||||
//const PI_STATUS_ERROR: u32 = 1 << 2;
|
||||
const PI_STATUS_INTERRUPT: u32 = 1 << 3;
|
||||
|
||||
/* PI_STATUS - write */
|
||||
pub const PI_STATUS_RESET: u32 = 1 << 0;
|
||||
pub const PI_STATUS_CLR_INTR: u32 = 1 << 1;
|
||||
const PI_STATUS_RESET: u32 = 1 << 0;
|
||||
const PI_STATUS_CLR_INTR: u32 = 1 << 1;
|
||||
|
||||
pub struct Pi {
|
||||
pub regs: [u32; PI_REGS_COUNT as usize],
|
||||
}
|
||||
|
||||
pub struct PiHandler {
|
||||
struct PiHandler {
|
||||
read: fn(&mut device::Device, u32, u32, u32) -> u64,
|
||||
write: fn(&mut device::Device, u32, u32, u32) -> u64,
|
||||
}
|
||||
@@ -48,7 +49,7 @@ pub fn read_regs(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dma_read(device: &mut device::Device) {
|
||||
fn dma_read(device: &mut device::Device) {
|
||||
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
|
||||
|
||||
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
|
||||
@@ -78,7 +79,7 @@ pub fn dma_read(device: &mut device::Device) {
|
||||
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
|
||||
}
|
||||
|
||||
pub fn dma_write(device: &mut device::Device) {
|
||||
fn dma_write(device: &mut device::Device) {
|
||||
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
|
||||
|
||||
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
|
||||
@@ -111,17 +112,20 @@ pub fn dma_write(device: &mut device::Device) {
|
||||
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
|
||||
}
|
||||
|
||||
pub fn get_handler(address: u32) -> PiHandler {
|
||||
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;
|
||||
if address >= device::memory::MM_SC64_BUFFER 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 {
|
||||
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;
|
||||
handler.read = device::cart::sram::dma_read;
|
||||
handler.write = device::cart::sram::dma_write;
|
||||
} else {
|
||||
panic!("unknown pi handler")
|
||||
}
|
||||
@@ -148,10 +152,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),
|
||||
}
|
||||
}
|
||||
|
||||
+27
-11
@@ -15,6 +15,7 @@ pub struct PifChannel {
|
||||
pub rx_buf: Option<usize>,
|
||||
pub process: Option<fn(&mut device::Device, usize)>,
|
||||
pub pak_handler: Option<device::controller::PakHandler>,
|
||||
pub change_pak: device::controller::PakType,
|
||||
}
|
||||
|
||||
pub const PIF_RAM_SIZE: usize = 64;
|
||||
@@ -72,7 +73,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY
|
||||
}
|
||||
|
||||
pub fn process_channel(device: &mut device::Device, channel: usize) -> usize {
|
||||
fn process_channel(device: &mut device::Device, channel: usize) -> usize {
|
||||
/* don't process channel if it has been disabled */
|
||||
if device.pif.channels[channel].tx.is_none() {
|
||||
return 0;
|
||||
@@ -99,17 +100,18 @@ pub fn update_pif_ram(device: &mut device::Device) -> u64 {
|
||||
for k in 0..PIF_CHANNELS_COUNT {
|
||||
active_channels += process_channel(device, k)
|
||||
}
|
||||
(24000 + (active_channels * 28000)) as u64
|
||||
|
||||
(24000 + (active_channels * 30000)) as u64
|
||||
}
|
||||
|
||||
pub fn disable_pif_channel(channel: &mut PifChannel) {
|
||||
fn disable_pif_channel(channel: &mut PifChannel) {
|
||||
channel.tx = None;
|
||||
channel.rx = None;
|
||||
channel.tx_buf = None;
|
||||
channel.rx_buf = None;
|
||||
}
|
||||
|
||||
pub fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize) -> usize {
|
||||
fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize) -> usize {
|
||||
let tx = device.pif.ram[buf] & 0x3f;
|
||||
let rx = device.pif.ram[buf + 1] & 0x3f;
|
||||
|
||||
@@ -123,7 +125,7 @@ pub fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize
|
||||
(2 + tx + rx) as usize
|
||||
}
|
||||
|
||||
pub fn setup_channels_format(device: &mut device::Device) {
|
||||
fn setup_channels_format(device: &mut device::Device) {
|
||||
let mut i: usize = 0;
|
||||
let mut k: usize = 0;
|
||||
while i < PIF_RAM_SIZE && k < PIF_CHANNELS_COUNT {
|
||||
@@ -225,16 +227,30 @@ pub fn init(device: &mut device::Device) {
|
||||
device.pif.ram[0x27] = device.cart.cic_seed;
|
||||
|
||||
let mempak_handler = device::controller::PakHandler {
|
||||
read: device::mempak::read,
|
||||
write: device::mempak::write,
|
||||
read: device::controller::mempak::read,
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
};
|
||||
|
||||
device.pif.channels[0].pak_handler = Some(mempak_handler);
|
||||
device.pif.channels[0].process = Some(device::controller::process);
|
||||
for i in 0..4 {
|
||||
if device.netplay.is_none() {
|
||||
if device.ui.config.input.controller_enabled[i] {
|
||||
device.pif.channels[i].pak_handler = Some(mempak_handler);
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
}
|
||||
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
|
||||
device.pif.channels[i].pak_handler = Some(mempak_handler);
|
||||
device.pif.channels[i].process = Some(device::controller::process);
|
||||
}
|
||||
}
|
||||
if device.ui.config.input.emulate_vru {
|
||||
device.pif.channels[3].pak_handler = None;
|
||||
device.pif.channels[3].process = Some(device::controller::vru::process);
|
||||
}
|
||||
device.pif.channels[4].process = Some(device::cart::process)
|
||||
}
|
||||
|
||||
pub fn process_cic_challenge(device: &mut device::Device) {
|
||||
fn process_cic_challenge(device: &mut device::Device) {
|
||||
let mut challenge: [u8; 30] = [0; 30];
|
||||
let mut response: [u8; 30] = [0; 30];
|
||||
|
||||
@@ -255,7 +271,7 @@ pub fn process_cic_challenge(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn n64_cic_nus_6105(chl: [u8; 30], rsp: &mut [u8; 30], len: usize) {
|
||||
fn n64_cic_nus_6105(chl: [u8; 30], rsp: &mut [u8; 30], len: usize) {
|
||||
let lut0: [u8; 0x10] = [
|
||||
0x4, 0x7, 0xA, 0x7, 0xE, 0x5, 0xE, 0x1, 0xC, 0xF, 0x8, 0xF, 0x6, 0x3, 0x6, 0x9,
|
||||
];
|
||||
|
||||
+48
-35
@@ -5,41 +5,41 @@ pub const DPC_START_REG: u32 = 0;
|
||||
pub const DPC_END_REG: u32 = 1;
|
||||
pub const DPC_CURRENT_REG: u32 = 2;
|
||||
pub const DPC_STATUS_REG: u32 = 3;
|
||||
pub const DPC_CLOCK_REG: u32 = 4;
|
||||
pub const DPC_BUFBUSY_REG: u32 = 5;
|
||||
pub const DPC_PIPEBUSY_REG: u32 = 6;
|
||||
pub const DPC_TMEM_REG: u32 = 7;
|
||||
const DPC_CLOCK_REG: u32 = 4;
|
||||
const DPC_BUFBUSY_REG: u32 = 5;
|
||||
const DPC_PIPEBUSY_REG: u32 = 6;
|
||||
const DPC_TMEM_REG: u32 = 7;
|
||||
pub const DPC_REGS_COUNT: u32 = 8;
|
||||
|
||||
//pub const DPS_TBIST_REG: u32 = 0;
|
||||
//pub const DPS_TEST_MODE_REG: u32 = 1;
|
||||
//pub const DPS_BUFTEST_ADDR_REG: u32 = 2;
|
||||
//pub const DPS_BUFTEST_DATA_REG: u32 = 3;
|
||||
//const DPS_TBIST_REG: u32 = 0;
|
||||
//const DPS_TEST_MODE_REG: u32 = 1;
|
||||
//const DPS_BUFTEST_ADDR_REG: u32 = 2;
|
||||
//const DPS_BUFTEST_DATA_REG: u32 = 3;
|
||||
pub const DPS_REGS_COUNT: u32 = 4;
|
||||
|
||||
/* DPC status - read */
|
||||
pub const DPC_STATUS_XBUS_DMEM_DMA: u32 = 1 << 0;
|
||||
pub const DPC_STATUS_FREEZE: u32 = 1 << 1;
|
||||
pub const DPC_STATUS_FLUSH: u32 = 1 << 2;
|
||||
pub const DPC_STATUS_START_GCLK: u32 = 1 << 3;
|
||||
pub const DPC_STATUS_TMEM_BUSY: u32 = 1 << 4;
|
||||
pub const DPC_STATUS_PIPE_BUSY: u32 = 1 << 5;
|
||||
pub const DPC_STATUS_CMD_BUSY: u32 = 1 << 6;
|
||||
pub const DPC_STATUS_CBUF_READY: u32 = 1 << 7;
|
||||
//pub const DPC_STATUS_DMA_BUSY: u32 = 1 << 8;
|
||||
//pub const DPC_STATUS_END_VALID: u32 = 1 << 9;
|
||||
pub const DPC_STATUS_START_VALID: u32 = 1 << 10;
|
||||
const DPC_STATUS_XBUS_DMEM_DMA: u32 = 1 << 0;
|
||||
const DPC_STATUS_FREEZE: u32 = 1 << 1;
|
||||
const DPC_STATUS_FLUSH: u32 = 1 << 2;
|
||||
const DPC_STATUS_START_GCLK: u32 = 1 << 3;
|
||||
const DPC_STATUS_TMEM_BUSY: u32 = 1 << 4;
|
||||
const DPC_STATUS_PIPE_BUSY: u32 = 1 << 5;
|
||||
const DPC_STATUS_CMD_BUSY: u32 = 1 << 6;
|
||||
const DPC_STATUS_CBUF_READY: u32 = 1 << 7;
|
||||
//const DPC_STATUS_DMA_BUSY: u32 = 1 << 8;
|
||||
//const DPC_STATUS_END_VALID: u32 = 1 << 9;
|
||||
const DPC_STATUS_START_VALID: u32 = 1 << 10;
|
||||
/* DPC status - write */
|
||||
pub const DPC_CLR_XBUS_DMEM_DMA: u32 = 1 << 0;
|
||||
pub const DPC_SET_XBUS_DMEM_DMA: u32 = 1 << 1;
|
||||
pub const DPC_CLR_FREEZE: u32 = 1 << 2;
|
||||
pub const DPC_SET_FREEZE: u32 = 1 << 3;
|
||||
pub const DPC_CLR_FLUSH: u32 = 1 << 4;
|
||||
pub const DPC_SET_FLUSH: u32 = 1 << 5;
|
||||
pub const DPC_CLR_TMEM_CTR: u32 = 1 << 6;
|
||||
pub const DPC_CLR_PIPE_CTR: u32 = 1 << 7;
|
||||
pub const DPC_CLR_CMD_CTR: u32 = 1 << 8;
|
||||
pub const DPC_CLR_CLOCK_CTR: u32 = 1 << 9;
|
||||
const DPC_CLR_XBUS_DMEM_DMA: u32 = 1 << 0;
|
||||
const DPC_SET_XBUS_DMEM_DMA: u32 = 1 << 1;
|
||||
const DPC_CLR_FREEZE: u32 = 1 << 2;
|
||||
const DPC_SET_FREEZE: u32 = 1 << 3;
|
||||
const DPC_CLR_FLUSH: u32 = 1 << 4;
|
||||
const DPC_SET_FLUSH: u32 = 1 << 5;
|
||||
const DPC_CLR_TMEM_CTR: u32 = 1 << 6;
|
||||
const DPC_CLR_PIPE_CTR: u32 = 1 << 7;
|
||||
const DPC_CLR_CMD_CTR: u32 = 1 << 8;
|
||||
const DPC_CLR_CLOCK_CTR: u32 = 1 << 9;
|
||||
|
||||
pub struct Rdp {
|
||||
pub regs_dpc: [u32; DPC_REGS_COUNT as usize],
|
||||
@@ -52,7 +52,17 @@ 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_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) {
|
||||
@@ -91,8 +101,10 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_rdp(device: &mut device::Device) {
|
||||
let timer = ui::video::process_rdp_list(&mut device.rdp.regs_dpc, &mut device.rsp.mem);
|
||||
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;
|
||||
|
||||
if timer != 0 {
|
||||
device::events::create_event(
|
||||
device,
|
||||
@@ -119,7 +131,7 @@ pub fn write_regs_dps(device: &mut device::Device, address: u64, value: u32, mas
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_dpc_status(device: &mut device::Device, w: u32) {
|
||||
fn update_dpc_status(device: &mut device::Device, w: u32) {
|
||||
/* clear / set xbus_dmem_dma */
|
||||
if w & DPC_CLR_XBUS_DMEM_DMA != 0 {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_XBUS_DMEM_DMA
|
||||
@@ -172,7 +184,8 @@ pub fn init(device: &mut device::Device) {
|
||||
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CBUF_READY;
|
||||
}
|
||||
|
||||
pub fn rdp_interrupt_event(device: &mut device::Device) {
|
||||
ui::video::rdp_full_sync();
|
||||
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);
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
|
||||
}
|
||||
|
||||
+45
-16
@@ -1,12 +1,24 @@
|
||||
use crate::device;
|
||||
use std::alloc::{alloc_zeroed, Layout};
|
||||
|
||||
pub const RDRAM_MASK: usize = 0xFFFFFF;
|
||||
//const RDRAM_CONFIG_REG: u32 = 0;
|
||||
//const RDRAM_DEVICE_ID_REG: u32 = 1;
|
||||
//const RDRAM_DELAY_REG: u32 = 2;
|
||||
const RDRAM_MODE_REG: u32 = 3;
|
||||
//const RDRAM_REF_INTERVAL_REG: u32 = 4;
|
||||
//const RDRAM_REF_ROW_REG: u32 = 5;
|
||||
//const RDRAM_RAS_INTERVAL_REG: u32 = 6;
|
||||
//const RDRAM_MIN_INTERVAL_REG: u32 = 7;
|
||||
//const RDRAM_ADDR_SELECT_REG: u32 = 8;
|
||||
//const RDRAM_DEVICE_MANUF_REG: u32 = 9;
|
||||
pub const RDRAM_REGS_COUNT: u32 = 10;
|
||||
|
||||
pub const RDRAM_SIZE: usize = 0x800000; // 8MB
|
||||
pub const RDRAM_MASK: usize = 0xFFFFFF;
|
||||
|
||||
pub struct Rdram {
|
||||
pub mem: Vec<u8>,
|
||||
pub size: u32,
|
||||
pub regs: [[u32; RDRAM_REGS_COUNT as usize]; 4],
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
@@ -32,7 +44,7 @@ pub fn read_mem(
|
||||
rdram_calculate_cycles(access_size as u64) / (access_size as u64 / 4),
|
||||
);
|
||||
let masked_address = address as usize & RDRAM_MASK;
|
||||
if address < RDRAM_SIZE as u64 {
|
||||
if address < device.rdram.size as u64 {
|
||||
u32::from_ne_bytes(
|
||||
device.rdram.mem[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
@@ -44,7 +56,7 @@ pub fn read_mem(
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if address < RDRAM_SIZE as u64 {
|
||||
if address < device.rdram.size as u64 {
|
||||
let mut data = u32::from_ne_bytes(
|
||||
device.rdram.mem[address as usize..(address + 4) as usize]
|
||||
.try_into()
|
||||
@@ -57,30 +69,47 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
_device: &mut device::Device,
|
||||
_address: u64,
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
panic!("rdram read reg");
|
||||
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) {
|
||||
panic!("rdram write reg");
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
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],
|
||||
value,
|
||||
mask,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) -> (*mut u8, usize) {
|
||||
pub fn init(device: &mut device::Device) {
|
||||
let alignment = 64 * 1024;
|
||||
let layout = Layout::from_size_align(RDRAM_SIZE, alignment).expect("Invalid layout");
|
||||
let layout =
|
||||
Layout::from_size_align(device.rdram.size as usize, alignment).expect("Invalid layout");
|
||||
let ptr = unsafe { alloc_zeroed(layout) };
|
||||
device.rdram.mem = unsafe { Vec::from_raw_parts(ptr, RDRAM_SIZE, RDRAM_SIZE) };
|
||||
device.rdram.mem =
|
||||
unsafe { Vec::from_raw_parts(ptr, device.rdram.size as usize, device.rdram.size as usize) };
|
||||
|
||||
// hack, skip RDRAM initialization
|
||||
let data: u32 = RDRAM_SIZE as u32;
|
||||
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
|
||||
.copy_from_slice(&data.to_ne_bytes());
|
||||
(ptr, RDRAM_SIZE)
|
||||
.copy_from_slice(&device.rdram.size.to_ne_bytes());
|
||||
|
||||
device.ri.regs[device::ri::RI_MODE_REG as usize] = 0x0e;
|
||||
device.ri.regs[device::ri::RI_CONFIG_REG as usize] = 0x40;
|
||||
device.ri.regs[device::ri::RI_SELECT_REG as usize] = 0x14;
|
||||
device.ri.regs[device::ri::RI_REFRESH_REG as usize] = 0x00063634;
|
||||
}
|
||||
|
||||
pub fn rdram_calculate_cycles(length: u64) -> u64 {
|
||||
31 + (length / 3)// https://hcs64.com/dma.html, https://github.com/rasky/n64-systembench
|
||||
31 + (length / 3) // https://hcs64.com/dma.html, https://github.com/rasky/n64-systembench
|
||||
}
|
||||
|
||||
+11
-9
@@ -1,17 +1,18 @@
|
||||
use crate::device;
|
||||
|
||||
//pub const RI_MODE_REG: u32 = 0;
|
||||
//pub const RI_CONFIG_REG: u32 = 1;
|
||||
//pub const RI_CURRENT_LOAD_REG: u32 = 2;
|
||||
pub const RI_MODE_REG: u32 = 0;
|
||||
pub const RI_CONFIG_REG: u32 = 1;
|
||||
//const RI_CURRENT_LOAD_REG: u32 = 2;
|
||||
pub const RI_SELECT_REG: u32 = 3;
|
||||
//pub const RI_REFRESH_REG: u32 = 4;
|
||||
//pub const RI_LATENCY_REG: u32 = 5;
|
||||
//pub const RI_ERROR_REG: u32 = 6;
|
||||
//pub const RI_WERROR_REG: u32 = 7;
|
||||
pub const RI_REFRESH_REG: u32 = 4;
|
||||
//const RI_LATENCY_REG: u32 = 5;
|
||||
//const RI_ERROR_REG: u32 = 6;
|
||||
//const RI_WERROR_REG: u32 = 7;
|
||||
pub const RI_REGS_COUNT: u32 = 8;
|
||||
|
||||
pub struct Ri {
|
||||
pub regs: [u32; RI_REGS_COUNT as usize],
|
||||
pub ram_init: bool,
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
@@ -19,8 +20,9 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG {
|
||||
return 0x14; // hack, skip RDRAM initialization
|
||||
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG && !device.ri.ram_init {
|
||||
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
|
||||
device.ri.ram_init = true;
|
||||
}
|
||||
device.ri.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
+14
-9
@@ -1,4 +1,9 @@
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use device::__m128i;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
||||
use crate::device;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
pub struct BranchState {
|
||||
@@ -30,17 +35,17 @@ pub struct Cpu {
|
||||
pub cycle_counter: u64,
|
||||
pub shuffle: [__m128i; 16],
|
||||
pub gpr: [u32; 32],
|
||||
pub vpr: [u128; 32],
|
||||
pub vpr: [__m128i; 32],
|
||||
pub reciprocals: [u16; 512],
|
||||
pub inverse_square_roots: [u16; 512],
|
||||
pub vcol: std::arch::x86_64::__m128i,
|
||||
pub vcoh: std::arch::x86_64::__m128i,
|
||||
pub vccl: std::arch::x86_64::__m128i,
|
||||
pub vcch: std::arch::x86_64::__m128i,
|
||||
pub vce: std::arch::x86_64::__m128i,
|
||||
pub accl: std::arch::x86_64::__m128i,
|
||||
pub accm: std::arch::x86_64::__m128i,
|
||||
pub acch: std::arch::x86_64::__m128i,
|
||||
pub vcol: __m128i,
|
||||
pub vcoh: __m128i,
|
||||
pub vccl: __m128i,
|
||||
pub vcch: __m128i,
|
||||
pub vce: __m128i,
|
||||
pub accl: __m128i,
|
||||
pub accm: __m128i,
|
||||
pub acch: __m128i,
|
||||
pub divdp: bool,
|
||||
pub divin: i16,
|
||||
pub divout: i16,
|
||||
|
||||
+105
-72
@@ -1,64 +1,64 @@
|
||||
use crate::device;
|
||||
|
||||
pub const SP_MEM_ADDR_REG: u32 = 0;
|
||||
pub const SP_DRAM_ADDR_REG: u32 = 1;
|
||||
pub const SP_RD_LEN_REG: u32 = 2;
|
||||
pub const SP_WR_LEN_REG: u32 = 3;
|
||||
const SP_MEM_ADDR_REG: u32 = 0;
|
||||
const SP_DRAM_ADDR_REG: u32 = 1;
|
||||
const SP_RD_LEN_REG: u32 = 2;
|
||||
const SP_WR_LEN_REG: u32 = 3;
|
||||
pub const SP_STATUS_REG: u32 = 4;
|
||||
pub const SP_DMA_FULL_REG: u32 = 5;
|
||||
pub const SP_DMA_BUSY_REG: u32 = 6;
|
||||
pub const SP_SEMAPHORE_REG: u32 = 7;
|
||||
const SP_DMA_FULL_REG: u32 = 5;
|
||||
const SP_DMA_BUSY_REG: u32 = 6;
|
||||
const SP_SEMAPHORE_REG: u32 = 7;
|
||||
pub const SP_REGS_COUNT: u32 = 8;
|
||||
|
||||
pub const SP_PC_REG: u32 = 0;
|
||||
//pub const SP_IBIST_REG: u32 = 1;
|
||||
//const SP_IBIST_REG: u32 = 1;
|
||||
pub const SP_REGS2_COUNT: u32 = 2;
|
||||
|
||||
/* SP_STATUS - read */
|
||||
pub const SP_STATUS_HALT: u32 = 1 << 0;
|
||||
pub const SP_STATUS_BROKE: u32 = 1 << 1;
|
||||
pub const SP_STATUS_DMA_BUSY: u32 = 1 << 2;
|
||||
pub const SP_STATUS_DMA_FULL: u32 = 1 << 3;
|
||||
//pub const SP_STATUS_IO_FULL: u32 = 1 << 4;
|
||||
pub const SP_STATUS_SSTEP: u32 = 1 << 5;
|
||||
pub const SP_STATUS_INTR_BREAK: u32 = 1 << 6;
|
||||
pub const SP_STATUS_SIG0: u32 = 1 << 7;
|
||||
pub const SP_STATUS_SIG1: u32 = 1 << 8;
|
||||
pub const SP_STATUS_SIG2: u32 = 1 << 9;
|
||||
pub const SP_STATUS_SIG3: u32 = 1 << 10;
|
||||
pub const SP_STATUS_SIG4: u32 = 1 << 11;
|
||||
pub const SP_STATUS_SIG5: u32 = 1 << 12;
|
||||
pub const SP_STATUS_SIG6: u32 = 1 << 13;
|
||||
pub const SP_STATUS_SIG7: u32 = 1 << 14;
|
||||
const SP_STATUS_BROKE: u32 = 1 << 1;
|
||||
const SP_STATUS_DMA_BUSY: u32 = 1 << 2;
|
||||
const SP_STATUS_DMA_FULL: u32 = 1 << 3;
|
||||
//const SP_STATUS_IO_FULL: u32 = 1 << 4;
|
||||
const SP_STATUS_SSTEP: u32 = 1 << 5;
|
||||
const SP_STATUS_INTR_BREAK: u32 = 1 << 6;
|
||||
const SP_STATUS_SIG0: u32 = 1 << 7;
|
||||
const SP_STATUS_SIG1: u32 = 1 << 8;
|
||||
const SP_STATUS_SIG2: u32 = 1 << 9;
|
||||
const SP_STATUS_SIG3: u32 = 1 << 10;
|
||||
const SP_STATUS_SIG4: u32 = 1 << 11;
|
||||
const SP_STATUS_SIG5: u32 = 1 << 12;
|
||||
const SP_STATUS_SIG6: u32 = 1 << 13;
|
||||
const SP_STATUS_SIG7: u32 = 1 << 14;
|
||||
|
||||
/* SP_STATUS - write */
|
||||
pub const SP_CLR_HALT: u32 = 1 << 0;
|
||||
const SP_CLR_HALT: u32 = 1 << 0;
|
||||
pub const SP_SET_HALT: u32 = 1 << 1;
|
||||
pub const SP_CLR_BROKE: u32 = 1 << 2;
|
||||
pub const SP_CLR_INTR: u32 = 1 << 3;
|
||||
pub const SP_SET_INTR: u32 = 1 << 4;
|
||||
pub const SP_CLR_SSTEP: u32 = 1 << 5;
|
||||
pub const SP_SET_SSTEP: u32 = 1 << 6;
|
||||
pub const SP_CLR_INTR_BREAK: u32 = 1 << 7;
|
||||
pub const SP_SET_INTR_BREAK: u32 = 1 << 8;
|
||||
pub const SP_CLR_SIG0: u32 = 1 << 9;
|
||||
pub const SP_SET_SIG0: u32 = 1 << 10;
|
||||
pub const SP_CLR_SIG1: u32 = 1 << 11;
|
||||
pub const SP_SET_SIG1: u32 = 1 << 12;
|
||||
pub const SP_CLR_SIG2: u32 = 1 << 13;
|
||||
pub const SP_SET_SIG2: u32 = 1 << 14;
|
||||
pub const SP_CLR_SIG3: u32 = 1 << 15;
|
||||
pub const SP_SET_SIG3: u32 = 1 << 16;
|
||||
pub const SP_CLR_SIG4: u32 = 1 << 17;
|
||||
pub const SP_SET_SIG4: u32 = 1 << 18;
|
||||
pub const SP_CLR_SIG5: u32 = 1 << 19;
|
||||
pub const SP_SET_SIG5: u32 = 1 << 20;
|
||||
pub const SP_CLR_SIG6: u32 = 1 << 21;
|
||||
pub const SP_SET_SIG6: u32 = 1 << 22;
|
||||
pub const SP_CLR_SIG7: u32 = 1 << 23;
|
||||
pub const SP_SET_SIG7: u32 = 1 << 24;
|
||||
const SP_CLR_BROKE: u32 = 1 << 2;
|
||||
const SP_CLR_INTR: u32 = 1 << 3;
|
||||
const SP_SET_INTR: u32 = 1 << 4;
|
||||
const SP_CLR_SSTEP: u32 = 1 << 5;
|
||||
const SP_SET_SSTEP: u32 = 1 << 6;
|
||||
const SP_CLR_INTR_BREAK: u32 = 1 << 7;
|
||||
const SP_SET_INTR_BREAK: u32 = 1 << 8;
|
||||
const SP_CLR_SIG0: u32 = 1 << 9;
|
||||
const SP_SET_SIG0: u32 = 1 << 10;
|
||||
const SP_CLR_SIG1: u32 = 1 << 11;
|
||||
const SP_SET_SIG1: u32 = 1 << 12;
|
||||
const SP_CLR_SIG2: u32 = 1 << 13;
|
||||
const SP_SET_SIG2: u32 = 1 << 14;
|
||||
const SP_CLR_SIG3: u32 = 1 << 15;
|
||||
const SP_SET_SIG3: u32 = 1 << 16;
|
||||
const SP_CLR_SIG4: u32 = 1 << 17;
|
||||
const SP_SET_SIG4: u32 = 1 << 18;
|
||||
const SP_CLR_SIG5: u32 = 1 << 19;
|
||||
const SP_SET_SIG5: u32 = 1 << 20;
|
||||
const SP_CLR_SIG6: u32 = 1 << 21;
|
||||
const SP_SET_SIG6: u32 = 1 << 22;
|
||||
const SP_CLR_SIG7: u32 = 1 << 23;
|
||||
const SP_SET_SIG7: u32 = 1 << 24;
|
||||
|
||||
pub const RSP_MEM_MASK: usize = 0x1FFF;
|
||||
const RSP_MEM_MASK: usize = 0x1FFF;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum DmaDir {
|
||||
@@ -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(
|
||||
@@ -129,7 +131,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, _mask: u
|
||||
// SH/SB are broken: They overwrite the whole 32 bit, filling everything that isn't written with zeroes
|
||||
}
|
||||
|
||||
pub fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
let l = dma.length;
|
||||
|
||||
let length = ((l & 0xfff) | 7) + 1;
|
||||
@@ -165,11 +167,14 @@ pub fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
while j < count {
|
||||
let mut i = 0;
|
||||
while i < length {
|
||||
let data = u32::from_ne_bytes(
|
||||
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let mut data = 0;
|
||||
if dram_addr < device.rdram.size {
|
||||
data = u32::from_ne_bytes(
|
||||
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
if offset != 0 {
|
||||
// imem being updated
|
||||
device.rsp.cpu.instructions[((mem_addr & 0xFFF) / 4) as usize].func =
|
||||
@@ -202,7 +207,7 @@ pub fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn fifo_push(device: &mut device::Device, dir: DmaDir) {
|
||||
fn fifo_push(device: &mut device::Device, dir: DmaDir) {
|
||||
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
|
||||
panic!("RSP DMA already full")
|
||||
}
|
||||
@@ -234,7 +239,7 @@ pub fn fifo_push(device: &mut device::Device, dir: DmaDir) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fifo_pop(device: &mut device::Device) {
|
||||
fn fifo_pop(device: &mut device::Device) {
|
||||
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
|
||||
device.rsp.fifo[0].dir = device.rsp.fifo[1].dir;
|
||||
device.rsp.fifo[0].length = device.rsp.fifo[1].length;
|
||||
@@ -246,7 +251,11 @@ pub 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,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
|
||||
}
|
||||
@@ -281,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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +338,7 @@ pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask:
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
let was_halted = device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT != 0;
|
||||
|
||||
/* clear / set halt */
|
||||
@@ -332,7 +361,7 @@ pub 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 */
|
||||
@@ -418,23 +447,28 @@ pub 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);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_task(device: &mut device::Device) {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
fn do_task(device: &mut device::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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rsp_event(device: &mut device::Device) {
|
||||
fn rsp_event(device: &mut device::Device) {
|
||||
if device.rsp.cpu.broken {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT | SP_STATUS_BROKE;
|
||||
|
||||
@@ -447,7 +481,6 @@ pub 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)
|
||||
}
|
||||
|
||||
|
||||
+362
-171
@@ -1,66 +1,181 @@
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use device::__m128i;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
include!("../compat/aarch64.rs");
|
||||
use crate::device;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
pub fn rd(opcode: u32) -> u32 {
|
||||
fn rd(opcode: u32) -> u32 {
|
||||
(opcode >> 11) & 0x1F
|
||||
}
|
||||
|
||||
pub fn rs(opcode: u32) -> u32 {
|
||||
fn rs(opcode: u32) -> u32 {
|
||||
(opcode >> 21) & 0x1F
|
||||
}
|
||||
|
||||
pub fn rt(opcode: u32) -> u32 {
|
||||
fn rt(opcode: u32) -> u32 {
|
||||
(opcode >> 16) & 0x1F
|
||||
}
|
||||
|
||||
pub fn sa(opcode: u32) -> u32 {
|
||||
fn sa(opcode: u32) -> u32 {
|
||||
(opcode >> 6) & 0x1F
|
||||
}
|
||||
|
||||
pub fn imm(opcode: u32) -> u16 {
|
||||
fn imm(opcode: u32) -> u16 {
|
||||
opcode as u16
|
||||
}
|
||||
|
||||
pub fn se16(value: i16) -> u32 {
|
||||
fn se16(value: i16) -> u32 {
|
||||
value as i32 as u32
|
||||
}
|
||||
|
||||
pub fn voffset(opcode: u32) -> u8 {
|
||||
fn voffset(opcode: u32) -> u8 {
|
||||
(opcode & 0x7F) as u8
|
||||
}
|
||||
|
||||
pub fn velement(opcode: u32) -> u8 {
|
||||
fn velement(opcode: u32) -> u8 {
|
||||
((opcode >> 7) & 0xF) as u8
|
||||
}
|
||||
|
||||
pub fn sign_extend_7bit_offset(offset: u8, shift_amount: u32) -> u32 {
|
||||
fn sign_extend_7bit_offset(offset: u8, shift_amount: u32) -> u32 {
|
||||
let soffset = (((offset << 1) & 0x80) | offset) as i8;
|
||||
|
||||
(((soffset) as i32) as u32) << shift_amount
|
||||
}
|
||||
|
||||
pub fn modify_vpr_byte(vpr: &mut u128, value: u8, element: u8) {
|
||||
let pos = 15 - (element & 15);
|
||||
let mask = 0xFF << (pos * 8);
|
||||
*vpr &= !mask;
|
||||
*vpr |= (value as u128) << (pos * 8);
|
||||
fn modify_vpr8(vpr: &mut __m128i, element: u8, value: u8) {
|
||||
unsafe {
|
||||
*vpr = match element & 15 {
|
||||
0 => _mm_insert_epi8(*vpr, value as i32, 15),
|
||||
1 => _mm_insert_epi8(*vpr, value as i32, 14),
|
||||
2 => _mm_insert_epi8(*vpr, value as i32, 13),
|
||||
3 => _mm_insert_epi8(*vpr, value as i32, 12),
|
||||
4 => _mm_insert_epi8(*vpr, value as i32, 11),
|
||||
5 => _mm_insert_epi8(*vpr, value as i32, 10),
|
||||
6 => _mm_insert_epi8(*vpr, value as i32, 9),
|
||||
7 => _mm_insert_epi8(*vpr, value as i32, 8),
|
||||
8 => _mm_insert_epi8(*vpr, value as i32, 7),
|
||||
9 => _mm_insert_epi8(*vpr, value as i32, 6),
|
||||
10 => _mm_insert_epi8(*vpr, value as i32, 5),
|
||||
11 => _mm_insert_epi8(*vpr, value as i32, 4),
|
||||
12 => _mm_insert_epi8(*vpr, value as i32, 3),
|
||||
13 => _mm_insert_epi8(*vpr, value as i32, 2),
|
||||
14 => _mm_insert_epi8(*vpr, value as i32, 1),
|
||||
15 => _mm_insert_epi8(*vpr, value as i32, 0),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_vpr_byte(vpr: u128, element: u8) -> u8 {
|
||||
let pos = 15 - (element & 15);
|
||||
(vpr >> (pos * 8)) as u8
|
||||
fn get_vpr8(vpr: __m128i, element: u8) -> u8 {
|
||||
unsafe {
|
||||
match element & 15 {
|
||||
0 => _mm_extract_epi8(vpr, 15) as u8,
|
||||
1 => _mm_extract_epi8(vpr, 14) as u8,
|
||||
2 => _mm_extract_epi8(vpr, 13) as u8,
|
||||
3 => _mm_extract_epi8(vpr, 12) as u8,
|
||||
4 => _mm_extract_epi8(vpr, 11) as u8,
|
||||
5 => _mm_extract_epi8(vpr, 10) as u8,
|
||||
6 => _mm_extract_epi8(vpr, 9) as u8,
|
||||
7 => _mm_extract_epi8(vpr, 8) as u8,
|
||||
8 => _mm_extract_epi8(vpr, 7) as u8,
|
||||
9 => _mm_extract_epi8(vpr, 6) as u8,
|
||||
10 => _mm_extract_epi8(vpr, 5) as u8,
|
||||
11 => _mm_extract_epi8(vpr, 4) as u8,
|
||||
12 => _mm_extract_epi8(vpr, 3) as u8,
|
||||
13 => _mm_extract_epi8(vpr, 2) as u8,
|
||||
14 => _mm_extract_epi8(vpr, 1) as u8,
|
||||
15 => _mm_extract_epi8(vpr, 0) as u8,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modify_vpr_element(vpr: &mut u128, value: u16, element: u8) {
|
||||
let pos = 7 - (element & 7);
|
||||
let mask = 0xFFFF << (pos * 16);
|
||||
*vpr &= !mask;
|
||||
*vpr |= (value as u128) << (pos * 16);
|
||||
pub fn modify_vpr16(vpr: &mut __m128i, element: u8, value: u16) {
|
||||
unsafe {
|
||||
*vpr = match element & 7 {
|
||||
0 => _mm_insert_epi16(*vpr, value as i32, 7),
|
||||
1 => _mm_insert_epi16(*vpr, value as i32, 6),
|
||||
2 => _mm_insert_epi16(*vpr, value as i32, 5),
|
||||
3 => _mm_insert_epi16(*vpr, value as i32, 4),
|
||||
4 => _mm_insert_epi16(*vpr, value as i32, 3),
|
||||
5 => _mm_insert_epi16(*vpr, value as i32, 2),
|
||||
6 => _mm_insert_epi16(*vpr, value as i32, 1),
|
||||
7 => _mm_insert_epi16(*vpr, value as i32, 0),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_vpr_element(vpr: u128, element: u8) -> u16 {
|
||||
let pos = 7 - (element & 7);
|
||||
(vpr >> (pos * 16)) as u16
|
||||
pub fn get_vpr16(vpr: __m128i, element: u8) -> u16 {
|
||||
unsafe {
|
||||
match element & 7 {
|
||||
0 => _mm_extract_epi16(vpr, 7) as u16,
|
||||
1 => _mm_extract_epi16(vpr, 6) as u16,
|
||||
2 => _mm_extract_epi16(vpr, 5) as u16,
|
||||
3 => _mm_extract_epi16(vpr, 4) as u16,
|
||||
4 => _mm_extract_epi16(vpr, 3) as u16,
|
||||
5 => _mm_extract_epi16(vpr, 2) as u16,
|
||||
6 => _mm_extract_epi16(vpr, 1) as u16,
|
||||
7 => _mm_extract_epi16(vpr, 0) as u16,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_vpr32(vpr: &mut __m128i, element: u8, value: u32) {
|
||||
unsafe {
|
||||
*vpr = match element & 3 {
|
||||
0 => _mm_insert_epi32(*vpr, value as i32, 3),
|
||||
1 => _mm_insert_epi32(*vpr, value as i32, 2),
|
||||
2 => _mm_insert_epi32(*vpr, value as i32, 1),
|
||||
3 => _mm_insert_epi32(*vpr, value as i32, 0),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vpr32(vpr: __m128i, element: u8) -> u32 {
|
||||
unsafe {
|
||||
match element & 3 {
|
||||
0 => _mm_extract_epi32(vpr, 3) as u32,
|
||||
1 => _mm_extract_epi32(vpr, 2) as u32,
|
||||
2 => _mm_extract_epi32(vpr, 1) as u32,
|
||||
3 => _mm_extract_epi32(vpr, 0) as u32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_vpr64(vpr: &mut __m128i, element: u8, value: u64) {
|
||||
unsafe {
|
||||
*vpr = match element & 1 {
|
||||
0 => _mm_insert_epi64(*vpr, value as i64, 1),
|
||||
1 => _mm_insert_epi64(*vpr, value as i64, 0),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vpr64(vpr: __m128i, element: u8) -> u64 {
|
||||
unsafe {
|
||||
match element & 1 {
|
||||
0 => _mm_extract_epi64(vpr, 1) as u64,
|
||||
1 => _mm_extract_epi64(vpr, 0) as u64,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_vpr128(vpr: &mut __m128i, value: u128) {
|
||||
unsafe { *vpr = std::mem::transmute::<u128, __m128i>(value) }
|
||||
}
|
||||
|
||||
fn get_vpr128(vpr: __m128i) -> u128 {
|
||||
unsafe { std::mem::transmute::<__m128i, u128>(vpr) }
|
||||
}
|
||||
|
||||
pub fn j(device: &mut device::Device, opcode: u32) {
|
||||
@@ -208,7 +323,7 @@ pub fn lw(device: &mut device::Device, opcode: u32) {
|
||||
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
(w[0] as u32) << 24 | (w[1] as u32) << 16 | (w[2] as u32) << 8 | (w[3] as u32)
|
||||
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
|
||||
}
|
||||
|
||||
pub fn lbu(device: &mut device::Device, opcode: u32) {
|
||||
@@ -240,7 +355,7 @@ pub fn lwu(device: &mut device::Device, opcode: u32) {
|
||||
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
(w[0] as u32) << 24 | (w[1] as u32) << 16 | (w[2] as u32) << 8 | (w[3] as u32)
|
||||
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
|
||||
}
|
||||
|
||||
pub fn sb(device: &mut device::Device, opcode: u32) {
|
||||
@@ -450,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) {
|
||||
@@ -477,16 +590,16 @@ 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) {
|
||||
let hi = get_vpr_byte(device.rsp.cpu.vpr[rd(opcode) as usize], velement(opcode));
|
||||
let lo = get_vpr_byte(
|
||||
let hi = get_vpr8(device.rsp.cpu.vpr[rd(opcode) as usize], velement(opcode));
|
||||
let lo = get_vpr8(
|
||||
device.rsp.cpu.vpr[rd(opcode) as usize],
|
||||
velement(opcode) + 1,
|
||||
);
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = ((hi as u16) << 8 | (lo as u16)) as i16 as i32 as u32
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
(((hi as u16) << 8) | (lo as u16)) as i16 as i32 as u32
|
||||
}
|
||||
|
||||
pub fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
@@ -507,7 +620,7 @@ pub fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
lo = &mut device.rsp.cpu.vce;
|
||||
}
|
||||
_ => {
|
||||
panic!("unknown ctc2")
|
||||
panic!("unknown cfc2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,16 +632,16 @@ pub fn cfc2(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
|
||||
pub fn mtc2(device: &mut device::Device, opcode: u32) {
|
||||
modify_vpr_byte(
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rd(opcode) as usize],
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8,
|
||||
velement(opcode),
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8,
|
||||
);
|
||||
if velement(opcode) != 15 {
|
||||
modify_vpr_byte(
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rd(opcode) as usize],
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] as u8,
|
||||
velement(opcode) + 1,
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] as u8,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -573,7 +686,7 @@ pub fn ctc2(device: &mut device::Device, opcode: u32) {
|
||||
),
|
||||
_mm_setzero_si128(),
|
||||
);
|
||||
*hi = std::arch::x86_64::_mm_cmpeq_epi8(
|
||||
*hi = _mm_cmpeq_epi8(
|
||||
_mm_and_si128(
|
||||
_mm_set1_epi8(!(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as i8),
|
||||
mask,
|
||||
@@ -588,10 +701,10 @@ pub fn lbv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 0));
|
||||
|
||||
let element = velement(opcode);
|
||||
modify_vpr_byte(
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
element,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -600,15 +713,25 @@ pub fn lsv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 1));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = std::cmp::min(element + 2, 16);
|
||||
while element < end {
|
||||
modify_vpr_byte(
|
||||
|
||||
if element % 2 == 0 {
|
||||
modify_vpr16(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
element,
|
||||
element / 2,
|
||||
((device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8)
|
||||
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u16),
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
} else {
|
||||
let end = std::cmp::min(element + 2, 16);
|
||||
while element < end {
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,15 +740,28 @@ pub fn llv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 2));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = std::cmp::min(element + 4, 16);
|
||||
while element < end {
|
||||
modify_vpr_byte(
|
||||
|
||||
if element % 4 == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u32) << (8 * (3 - i));
|
||||
}
|
||||
modify_vpr32(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
element,
|
||||
element / 4,
|
||||
value,
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
} else {
|
||||
let end = std::cmp::min(element + 4, 16);
|
||||
while element < end {
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,15 +770,28 @@ pub fn ldv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 3));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = std::cmp::min(element + 8, 16);
|
||||
while element < end {
|
||||
modify_vpr_byte(
|
||||
|
||||
if element % 8 == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u64) << (8 * (7 - i));
|
||||
}
|
||||
modify_vpr64(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
element,
|
||||
element / 8,
|
||||
value,
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
} else {
|
||||
let end = std::cmp::min(element + 8, 16);
|
||||
while element < end {
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,15 +800,24 @@ pub fn lqv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 4));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
|
||||
while element < end {
|
||||
modify_vpr_byte(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
element,
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
|
||||
if element == 0 && address % 16 == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
|
||||
}
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
} else {
|
||||
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
|
||||
while element < end {
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,14 +827,22 @@ pub fn lrv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let mut element = 16u8.wrapping_sub(((address & 15) as u8).wrapping_sub(velement(opcode)));
|
||||
address &= !15;
|
||||
while element < 16 {
|
||||
modify_vpr_byte(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
element,
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
if element == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
|
||||
}
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
} else {
|
||||
while element < 16 {
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,17 +852,15 @@ pub fn lpv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
|
||||
address &= !7;
|
||||
let mut offset: u8 = 0;
|
||||
while offset < 8 {
|
||||
modify_vpr_element(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset)) & 15) as u32))
|
||||
& 0xFFF) as usize] as u16)
|
||||
<< 8,
|
||||
offset,
|
||||
);
|
||||
offset += 1;
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (((device.rsp.mem
|
||||
[((address.wrapping_add(((index.wrapping_add(i)) & 15) as u32)) & 0xFFF) as usize]
|
||||
as u16)
|
||||
<< 8) as u128)
|
||||
<< (16 * (7 - i));
|
||||
}
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
}
|
||||
|
||||
pub fn luv(device: &mut device::Device, opcode: u32) {
|
||||
@@ -705,17 +869,15 @@ pub fn luv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
|
||||
address &= !7;
|
||||
let mut offset: u8 = 0;
|
||||
while offset < 8 {
|
||||
modify_vpr_element(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset)) & 15) as u32))
|
||||
& 0xFFF) as usize] as u16)
|
||||
<< 7,
|
||||
offset,
|
||||
);
|
||||
offset += 1;
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (((device.rsp.mem
|
||||
[((address.wrapping_add(((index.wrapping_add(i)) & 15) as u32)) & 0xFFF) as usize]
|
||||
as u16)
|
||||
<< 7) as u128)
|
||||
<< (16 * (7 - i));
|
||||
}
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
}
|
||||
|
||||
pub fn lhv(device: &mut device::Device, opcode: u32) {
|
||||
@@ -724,17 +886,15 @@ pub fn lhv(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
|
||||
address &= !7;
|
||||
let mut offset: u8 = 0;
|
||||
while offset < 8 {
|
||||
modify_vpr_element(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset * 2)) & 15) as u32))
|
||||
& 0xFFF) as usize] as u16)
|
||||
<< 7,
|
||||
offset,
|
||||
);
|
||||
offset += 1;
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (((device.rsp.mem
|
||||
[((address.wrapping_add(((index.wrapping_add(i * 2)) & 15) as u32)) & 0xFFF) as usize]
|
||||
as u16)
|
||||
<< 7) as u128)
|
||||
<< (16 * (7 - i));
|
||||
}
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
}
|
||||
|
||||
pub fn lfv(device: &mut device::Device, opcode: u32) {
|
||||
@@ -745,30 +905,30 @@ pub fn lfv(device: &mut device::Device, opcode: u32) {
|
||||
address &= !7;
|
||||
let start = velement(opcode);
|
||||
let end = std::cmp::min(start + 8, 16);
|
||||
let mut tmp: u128 = 0;
|
||||
let mut tmp: __m128i = unsafe { _mm_setzero_si128() };
|
||||
let mut offset: u8 = 0;
|
||||
while offset < 4 {
|
||||
modify_vpr_element(
|
||||
modify_vpr16(
|
||||
&mut tmp,
|
||||
offset,
|
||||
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset * 4)) & 15) as u32))
|
||||
& 0xFFF) as usize] as u16)
|
||||
<< 7,
|
||||
offset,
|
||||
);
|
||||
modify_vpr_element(
|
||||
modify_vpr16(
|
||||
&mut tmp,
|
||||
offset + 4,
|
||||
(device.rsp.mem[((address
|
||||
.wrapping_add(((index.wrapping_add(offset * 4).wrapping_add(8)) & 15) as u32))
|
||||
& 0xFFF) as usize] as u16)
|
||||
<< 7,
|
||||
offset + 4,
|
||||
);
|
||||
offset += 1;
|
||||
}
|
||||
offset = start;
|
||||
while offset < end {
|
||||
let value = get_vpr_byte(tmp, offset);
|
||||
modify_vpr_byte(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value, offset);
|
||||
let value = get_vpr8(tmp, offset);
|
||||
modify_vpr8(&mut device.rsp.cpu.vpr[rt(opcode) as usize], offset, value);
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
@@ -783,28 +943,26 @@ pub fn ltv(device: &mut device::Device, opcode: u32) {
|
||||
address = begin + (((velement(opcode)) as u32 + (address & 8)) & 15);
|
||||
let vtbase = rt(opcode) & !7;
|
||||
let mut vtoff = (velement(opcode)) as u32 >> 1;
|
||||
let mut i = 0;
|
||||
while i < 8 {
|
||||
modify_vpr_byte(
|
||||
for i in 0..8 {
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[(vtbase + vtoff) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
i * 2,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
address += 1;
|
||||
if address == begin + 16 {
|
||||
address = begin
|
||||
}
|
||||
modify_vpr_byte(
|
||||
modify_vpr8(
|
||||
&mut device.rsp.cpu.vpr[(vtbase + vtoff) as usize],
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
i * 2 + 1,
|
||||
device.rsp.mem[(address & 0xFFF) as usize],
|
||||
);
|
||||
address += 1;
|
||||
if address == begin + 16 {
|
||||
address = begin
|
||||
}
|
||||
vtoff = (vtoff + 1) & 7;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,7 +971,7 @@ pub fn sbv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 0));
|
||||
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], velement(opcode))
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], velement(opcode))
|
||||
}
|
||||
|
||||
pub fn ssv(device: &mut device::Device, opcode: u32) {
|
||||
@@ -821,12 +979,20 @@ pub fn ssv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 1));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = element + 2;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
|
||||
if element % 2 == 0 {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 2].copy_from_slice(
|
||||
&get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element / 2).to_be_bytes(),
|
||||
);
|
||||
} else {
|
||||
let end = element + 2;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,12 +1001,20 @@ pub fn slv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 2));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = element + 4;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
|
||||
if element % 4 == 0 {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 4].copy_from_slice(
|
||||
&get_vpr32(device.rsp.cpu.vpr[rt(opcode) as usize], element / 4).to_be_bytes(),
|
||||
);
|
||||
} else {
|
||||
let end = element + 4;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -849,12 +1023,20 @@ pub fn sdv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 3));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = element + 8;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
|
||||
if element % 8 == 0 {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 8].copy_from_slice(
|
||||
&get_vpr64(device.rsp.cpu.vpr[rt(opcode) as usize], element / 8).to_be_bytes(),
|
||||
);
|
||||
} else {
|
||||
let end = element + 8;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,12 +1045,19 @@ pub fn sqv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 4));
|
||||
|
||||
let mut element = velement(opcode);
|
||||
let end = element + (16 - (address & 15)) as u8;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
|
||||
if element == 0 && address % 16 == 0 {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 16]
|
||||
.copy_from_slice(&get_vpr128(device.rsp.cpu.vpr[rt(opcode) as usize]).to_be_bytes());
|
||||
} else {
|
||||
let end = element + (16 - (address & 15)) as u8;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -880,11 +1069,18 @@ pub fn srv(device: &mut device::Device, opcode: u32) {
|
||||
let end = element + (address & 15) as u8;
|
||||
let base = (16 - (address & 15)) as u8;
|
||||
address &= !15;
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element + base);
|
||||
address += 1;
|
||||
element += 1;
|
||||
|
||||
if element == 0 && base == 0 {
|
||||
let start = (address & 0xFFF) as usize;
|
||||
device.rsp.mem[start..start + 16]
|
||||
.copy_from_slice(&get_vpr128(device.rsp.cpu.vpr[rt(opcode) as usize]).to_be_bytes());
|
||||
} else {
|
||||
while element < end {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element + base);
|
||||
address += 1;
|
||||
element += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,10 +1093,10 @@ pub fn spv(device: &mut device::Device, opcode: u32) {
|
||||
while element < end {
|
||||
if (element & 15) < 8 {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
|
||||
} else {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
(get_vpr_element(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
|
||||
(get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
|
||||
}
|
||||
address += 1;
|
||||
element += 1;
|
||||
@@ -916,10 +1112,10 @@ pub fn suv(device: &mut device::Device, opcode: u32) {
|
||||
while element < end {
|
||||
if (element & 15) < 8 {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
(get_vpr_element(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
|
||||
(get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
|
||||
} else {
|
||||
device.rsp.mem[(address & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
|
||||
}
|
||||
address += 1;
|
||||
element += 1;
|
||||
@@ -933,13 +1129,11 @@ pub fn shv(device: &mut device::Device, opcode: u32) {
|
||||
let element = velement(opcode);
|
||||
let index = (address & 7) as u8;
|
||||
address &= !7;
|
||||
let mut offset = 0;
|
||||
while offset < 8 {
|
||||
for offset in 0..8 {
|
||||
let byte_val = element + offset * 2;
|
||||
let value = get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val) << 1
|
||||
| get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val + 1) >> 7;
|
||||
let value = (get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val) << 1)
|
||||
| (get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val + 1) >> 7);
|
||||
device.rsp.mem[((address + ((index + offset * 2) & 15) as u32) & 0xFFF) as usize] = value;
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,8 +1142,7 @@ pub fn sfv(device: &mut device::Device, opcode: u32) {
|
||||
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 4));
|
||||
let base = address & 7;
|
||||
address &= !7;
|
||||
let element = velement(opcode);
|
||||
let elements = match element {
|
||||
let elements = match velement(opcode) {
|
||||
0 | 15 => [0, 1, 2, 3],
|
||||
1 => [6, 7, 4, 5],
|
||||
4 => [1, 2, 3, 0],
|
||||
@@ -966,12 +1159,10 @@ pub fn sfv(device: &mut device::Device, opcode: u32) {
|
||||
}
|
||||
};
|
||||
let mut offset = 0;
|
||||
let mut i = 0;
|
||||
while i < 4 {
|
||||
for element in elements {
|
||||
device.rsp.mem[((address + ((base + offset) & 15)) & 0xFFF) as usize] =
|
||||
(get_vpr_element(device.rsp.cpu.vpr[rt(opcode) as usize], elements[i]) >> 7) as u8;
|
||||
(get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
|
||||
offset += 4;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -985,7 +1176,7 @@ pub fn swv(device: &mut device::Device, opcode: u32) {
|
||||
address &= !7;
|
||||
while element < end {
|
||||
device.rsp.mem[((address + (base & 15)) & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
|
||||
base += 1;
|
||||
element += 1;
|
||||
}
|
||||
@@ -1002,11 +1193,11 @@ pub fn stv(device: &mut device::Device, opcode: u32) {
|
||||
let mut offset = start;
|
||||
while offset < end {
|
||||
device.rsp.mem[((address + (base & 15)) & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[offset as usize], element);
|
||||
get_vpr8(device.rsp.cpu.vpr[offset as usize], element);
|
||||
base = base.wrapping_add(1);
|
||||
element += 1;
|
||||
device.rsp.mem[((address + (base & 15)) & 0xFFF) as usize] =
|
||||
get_vpr_byte(device.rsp.cpu.vpr[offset as usize], element);
|
||||
get_vpr8(device.rsp.cpu.vpr[offset as usize], element);
|
||||
base = base.wrapping_add(1);
|
||||
element += 1;
|
||||
offset += 1;
|
||||
|
||||
+211
-464
File diff suppressed because it is too large
Load Diff
+12
-12
@@ -1,18 +1,18 @@
|
||||
use crate::device;
|
||||
|
||||
pub const SI_DRAM_ADDR_REG: u32 = 0;
|
||||
pub const SI_PIF_ADDR_RD64B_REG: u32 = 1;
|
||||
//pub const SI_R2_REG: u32 = 2;
|
||||
//pub const SI_R3_REG: u32 = 3;
|
||||
pub const SI_PIF_ADDR_WR64B_REG: u32 = 4;
|
||||
//pub const SI_R5_REG: u32 = 5;
|
||||
const SI_DRAM_ADDR_REG: u32 = 0;
|
||||
const SI_PIF_ADDR_RD64B_REG: u32 = 1;
|
||||
//const SI_R2_REG: u32 = 2;
|
||||
//const SI_R3_REG: u32 = 3;
|
||||
const SI_PIF_ADDR_WR64B_REG: u32 = 4;
|
||||
//const SI_R5_REG: u32 = 5;
|
||||
pub const SI_STATUS_REG: u32 = 6;
|
||||
pub const SI_REGS_COUNT: u32 = 7;
|
||||
|
||||
pub const SI_STATUS_DMA_BUSY: u32 = 1 << 0;
|
||||
pub const SI_STATUS_IO_BUSY: u32 = 1 << 1;
|
||||
//pub const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
|
||||
pub const SI_STATUS_INTERRUPT: u32 = 1 << 12;
|
||||
//const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
|
||||
const SI_STATUS_INTERRUPT: u32 = 1 << 12;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum DmaDir {
|
||||
@@ -34,7 +34,7 @@ pub fn read_regs(
|
||||
device.si.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
pub fn dma_read(device: &mut device::Device) {
|
||||
fn dma_read(device: &mut device::Device) {
|
||||
device.si.dma_dir = DmaDir::Read;
|
||||
|
||||
let duration = device::pif::update_pif_ram(device);
|
||||
@@ -49,7 +49,7 @@ pub fn dma_read(device: &mut device::Device) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dma_write(device: &mut device::Device) {
|
||||
fn dma_write(device: &mut device::Device) {
|
||||
device.si.dma_dir = DmaDir::Write;
|
||||
|
||||
copy_pif_rdram(device);
|
||||
@@ -59,7 +59,7 @@ pub fn dma_write(device: &mut device::Device) {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 5200, //based on https://github.com/rasky/n64-systembench
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 6000, //based on https://github.com/rasky/n64-systembench
|
||||
dma_event,
|
||||
)
|
||||
}
|
||||
@@ -78,7 +78,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
|
||||
//rdram is in native endian format, and pif memory is in big endian format
|
||||
pub fn copy_pif_rdram(device: &mut device::Device) {
|
||||
fn copy_pif_rdram(device: &mut device::Device) {
|
||||
let dram_addr = device.si.regs[SI_DRAM_ADDR_REG as usize] as usize & device::rdram::RDRAM_MASK;
|
||||
if device.si.dma_dir == DmaDir::Write {
|
||||
let mut i = 0;
|
||||
|
||||
+16
-9
@@ -38,8 +38,8 @@ pub fn read(device: &mut device::Device, index: u64) {
|
||||
device.cpu.cop0.tlb_entries[index as usize].mask << 13;
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] =
|
||||
(device.cpu.cop0.tlb_entries[index as usize].region as u64) << 62
|
||||
| device.cpu.cop0.tlb_entries[index as usize].vpn2 << 13
|
||||
((device.cpu.cop0.tlb_entries[index as usize].region as u64) << 62)
|
||||
| (device.cpu.cop0.tlb_entries[index as usize].vpn2 << 13)
|
||||
| (device.cpu.cop0.tlb_entries[index as usize].asid) as u64;
|
||||
|
||||
device.cpu.cop0.regs[device::cop0::COP0_ENTRYLO0_REG as usize] =
|
||||
@@ -136,7 +136,8 @@ pub fn probe(device: &mut device::Device) {
|
||||
if e.region != (device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] >> 62) as u8 {
|
||||
continue;
|
||||
}
|
||||
if e.g == 0 && e.asid != device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] as u8 {
|
||||
if e.g == 0 && e.asid != device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] as u8
|
||||
{
|
||||
continue;
|
||||
}
|
||||
device.cpu.cop0.regs[device::cop0::COP0_INDEX_REG as usize] = pos as u64;
|
||||
@@ -144,7 +145,7 @@ pub fn probe(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tlb_unmap(device: &mut device::Device, index: u64) {
|
||||
fn tlb_unmap(device: &mut device::Device, index: u64) {
|
||||
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
|
||||
|
||||
if e.v_even != 0 {
|
||||
@@ -182,11 +183,14 @@ pub fn tlb_unmap(device: &mut device::Device, index: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tlb_map(device: &mut device::Device, index: u64) {
|
||||
fn tlb_map(device: &mut device::Device, index: u64) {
|
||||
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
|
||||
|
||||
if e.v_even != 0 && e.start_even < e.end_even
|
||||
&& !(e.start_even >= 0x80000000 && e.end_even < 0xC0000000) && e.phys_even < 0x20000000 {
|
||||
if e.v_even != 0
|
||||
&& e.start_even < e.end_even
|
||||
&& !(e.start_even >= 0x80000000 && e.end_even < 0xC0000000)
|
||||
&& e.phys_even < 0x20000000
|
||||
{
|
||||
let mut i = e.start_even;
|
||||
while i < e.end_even {
|
||||
device.cpu.cop0.tlb_lut_r[(i >> 12) as usize].address =
|
||||
@@ -205,8 +209,11 @@ pub fn tlb_map(device: &mut device::Device, index: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
if e.v_odd != 0 && e.start_odd < e.end_odd
|
||||
&& !(e.start_odd >= 0x80000000 && e.end_odd < 0xC0000000) && e.phys_odd < 0x20000000 {
|
||||
if e.v_odd != 0
|
||||
&& e.start_odd < e.end_odd
|
||||
&& !(e.start_odd >= 0x80000000 && e.end_odd < 0xC0000000)
|
||||
&& e.phys_odd < 0x20000000
|
||||
{
|
||||
let mut i = e.start_odd;
|
||||
while i < e.end_odd {
|
||||
device.cpu.cop0.tlb_lut_r[(i >> 12) as usize].address =
|
||||
|
||||
+37
-26
@@ -1,21 +1,22 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use governor::clock::Clock;
|
||||
|
||||
pub const VI_STATUS_REG: u32 = 0;
|
||||
//pub const VI_ORIGIN_REG: u32 = 1;
|
||||
//pub const VI_WIDTH_REG: u32 = 2;
|
||||
//pub const VI_V_INTR_REG: u32 = 3;
|
||||
pub const VI_CURRENT_REG: u32 = 4;
|
||||
//pub const VI_BURST_REG: u32 = 5;
|
||||
pub const VI_V_SYNC_REG: u32 = 6;
|
||||
pub const VI_H_SYNC_REG: u32 = 7;
|
||||
//pub const VI_LEAP_REG: u32 = 8;
|
||||
//pub const VI_H_START_REG: u32 = 9;
|
||||
//pub const VI_V_START_REG: u32 = 10;
|
||||
//pub const VI_V_BURST_REG: u32 = 11;
|
||||
//pub const VI_X_SCALE_REG: u32 = 12;
|
||||
//pub const VI_Y_SCALE_REG: u32 = 13;
|
||||
const VI_STATUS_REG: u32 = 0;
|
||||
//const VI_ORIGIN_REG: u32 = 1;
|
||||
pub 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;
|
||||
const VI_V_SYNC_REG: u32 = 6;
|
||||
const VI_H_SYNC_REG: u32 = 7;
|
||||
//const VI_LEAP_REG: u32 = 8;
|
||||
pub const VI_H_START_REG: u32 = 9;
|
||||
pub const VI_V_START_REG: u32 = 10;
|
||||
//const VI_V_BURST_REG: u32 = 11;
|
||||
pub const VI_X_SCALE_REG: u32 = 12;
|
||||
pub const VI_Y_SCALE_REG: u32 = 13;
|
||||
pub const VI_REGS_COUNT: u32 = 14;
|
||||
|
||||
pub struct Vi {
|
||||
@@ -25,11 +26,14 @@ pub struct Vi {
|
||||
pub field: u32,
|
||||
pub limiter: Option<governor::DefaultDirectRateLimiter>,
|
||||
pub count_per_scanline: u64,
|
||||
pub vi_counter: u64,
|
||||
}
|
||||
|
||||
//static mut FRAME_COUNTER: u64 = 0;
|
||||
|
||||
pub fn set_expected_refresh_rate(device: &mut device::Device) {
|
||||
const LIMIT_BUFFER: u64 = 3;
|
||||
|
||||
fn set_expected_refresh_rate(device: &mut device::Device) {
|
||||
let expected_refresh_rate = device.vi.clock as f64
|
||||
/ (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as f64
|
||||
/ ((device.vi.regs[VI_H_SYNC_REG as usize] & 0xFFF) + 1) as f64
|
||||
@@ -39,13 +43,13 @@ pub fn set_expected_refresh_rate(device: &mut device::Device) {
|
||||
device.vi.delay / (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as u64;
|
||||
|
||||
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
|
||||
1.0 / expected_refresh_rate,
|
||||
(1.0 / expected_refresh_rate) * LIMIT_BUFFER as f64,
|
||||
))
|
||||
.unwrap();
|
||||
device.vi.limiter = Some(governor::RateLimiter::direct(quota))
|
||||
}
|
||||
|
||||
pub fn set_vertical_interrupt(device: &mut device::Device) {
|
||||
fn set_vertical_interrupt(device: &mut device::Device) {
|
||||
if device::events::get_event(device, device::events::EventType::VI).is_none() {
|
||||
device::events::create_event(
|
||||
device,
|
||||
@@ -56,7 +60,7 @@ pub fn set_vertical_interrupt(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_current_line(device: &mut device::Device) {
|
||||
fn set_current_line(device: &mut device::Device) {
|
||||
let delay = device.vi.delay;
|
||||
let next_vi = device::events::get_event(device, device::events::EventType::VI);
|
||||
if next_vi.is_some() {
|
||||
@@ -97,24 +101,22 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
|
||||
set_vertical_interrupt(device);
|
||||
set_expected_refresh_rate(device);
|
||||
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
|
||||
}
|
||||
}
|
||||
VI_H_SYNC_REG => {
|
||||
if device.vi.regs[reg as usize] != value & mask {
|
||||
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
|
||||
set_expected_refresh_rate(device);
|
||||
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
|
||||
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
|
||||
}
|
||||
}
|
||||
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
|
||||
}
|
||||
|
||||
pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
device.cpu.running = ui::video::update_screen();
|
||||
|
||||
/*
|
||||
@@ -123,7 +125,16 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
}
|
||||
*/
|
||||
|
||||
speed_limiter(device);
|
||||
let mut enable_speed_limiter = true;
|
||||
if device.netplay.is_some() {
|
||||
netplay::send_sync_check(device);
|
||||
enable_speed_limiter = !device.netplay.as_ref().unwrap().fast_forward;
|
||||
}
|
||||
|
||||
device.vi.vi_counter += 1;
|
||||
if device.vi.vi_counter % LIMIT_BUFFER == 0 && enable_speed_limiter {
|
||||
speed_limiter(device);
|
||||
}
|
||||
|
||||
/* toggle vi field if in interlaced mode */
|
||||
device.vi.field ^= (device.vi.regs[VI_STATUS_REG as usize] >> 6) & 0x1;
|
||||
@@ -158,13 +169,13 @@ pub fn init(device: &mut device::Device) {
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn speed_limiter(device: &device::Device) {
|
||||
fn speed_limiter(device: &device::Device) {
|
||||
let result = device.vi.limiter.as_ref().unwrap().check();
|
||||
if result.is_err() {
|
||||
let outcome = result.unwrap_err();
|
||||
let dur = outcome.wait_time_from(governor::clock::DefaultClock::default().now());
|
||||
std::thread::sleep(dur);
|
||||
spin_sleep::sleep(dur);
|
||||
|
||||
device.vi.limiter.as_ref().unwrap().check().unwrap();
|
||||
let _ = device.vi.limiter.as_ref().unwrap().check();
|
||||
}
|
||||
}
|
||||
|
||||
+64
-12
@@ -1,10 +1,13 @@
|
||||
#![feature(round_ties_even)]
|
||||
#![feature(string_remove_matches)]
|
||||
#![deny(warnings)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
mod device;
|
||||
mod netplay;
|
||||
mod ui;
|
||||
use clap::Parser;
|
||||
use ui::gui;
|
||||
|
||||
/// N64 emulator
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -20,6 +23,12 @@ struct Args {
|
||||
help = "Create a new input profile (keyboard/gamepad mappings)."
|
||||
)]
|
||||
configure_input_profile: Option<String>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Use DirectInput when configuring a new input profile."
|
||||
)]
|
||||
use_dinput: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
@@ -39,7 +48,7 @@ struct Args {
|
||||
value_name = "CONTROLLER_NUMBER",
|
||||
help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port"
|
||||
)]
|
||||
assign_controller: Option<u32>,
|
||||
assign_controller: Option<i32>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
@@ -56,12 +65,27 @@ struct Args {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config_dir = dirs::config_dir().unwrap().join("gopher64");
|
||||
let cache_dir = dirs::cache_dir().unwrap().join("gopher64");
|
||||
let dirs = ui::get_dirs();
|
||||
|
||||
let _ = std::fs::create_dir_all(config_dir.clone());
|
||||
let _ = std::fs::create_dir_all(cache_dir.clone());
|
||||
let _ = std::fs::remove_file(cache_dir.clone().join("game_running"));
|
||||
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(dirs.cache_dir.clone());
|
||||
if result.is_err() {
|
||||
panic!("could not create cache dir: {}", result.err().unwrap())
|
||||
}
|
||||
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 = dirs.cache_dir.join("game_running");
|
||||
if running_file.exists() {
|
||||
result = std::fs::remove_file(running_file);
|
||||
if result.is_err() {
|
||||
panic!("could not remove running file: {}", result.err().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
let args = Args::parse();
|
||||
let args_as_strings: Vec<String> = std::env::args().collect();
|
||||
@@ -81,7 +105,10 @@ fn main() {
|
||||
}
|
||||
}
|
||||
if args.list_controllers {
|
||||
ui::input::list_controllers(&device.ui);
|
||||
let controllers = gui::get_controller_names(&device.ui);
|
||||
for (i, controller) in controllers.iter().enumerate() {
|
||||
println!("Controller {}: {}", i, controller);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if args.assign_controller.is_some() {
|
||||
@@ -112,23 +139,48 @@ fn main() {
|
||||
ui::input::configure_input_profile(
|
||||
&mut device.ui,
|
||||
args.configure_input_profile.unwrap(),
|
||||
args.use_dinput,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if args.game.is_some() {
|
||||
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
|
||||
device::run_game(file_path, &mut device, args.fullscreen);
|
||||
let rom_contents = device::get_rom_contents(file_path);
|
||||
if rom_contents.is_empty() {
|
||||
println!("Could not read rom file");
|
||||
return;
|
||||
}
|
||||
device::run_game(rom_contents, &mut device, args.fullscreen);
|
||||
}
|
||||
} else {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(eframe::egui::vec2(640.0, 480.0)),
|
||||
viewport: eframe::egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_icon(
|
||||
eframe::icon_data::from_png_bytes(include_bytes!("../data/gopher64.png"))
|
||||
.unwrap(),
|
||||
),
|
||||
..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| Box::new(ui::gui::GopherEguiApp::new())),
|
||||
Box::new(|cc| {
|
||||
Ok(Box::new(ui::gui::GopherEguiApp::new(
|
||||
cc,
|
||||
controllers_paths,
|
||||
controller_names,
|
||||
)))
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use eframe::egui;
|
||||
use std::io::{Read, Write};
|
||||
//UDP packet formats
|
||||
const UDP_SEND_KEY_INFO: u8 = 0;
|
||||
const UDP_RECEIVE_KEY_INFO: u8 = 1;
|
||||
const UDP_REQUEST_KEY_INFO: u8 = 2;
|
||||
const UDP_RECEIVE_KEY_INFO_GRATUITOUS: u8 = 3;
|
||||
const UDP_SYNC_DATA: u8 = 4;
|
||||
|
||||
//TCP packet formats
|
||||
const TCP_SEND_SAVE: u8 = 1;
|
||||
//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;
|
||||
|
||||
pub struct Netplay {
|
||||
udp_socket: std::net::UdpSocket,
|
||||
tcp_stream: std::net::TcpStream,
|
||||
pub player_number: u8,
|
||||
pub player_data: [PlayerData; 4],
|
||||
vi_counter: u32,
|
||||
status: u8,
|
||||
buffer_target: u8,
|
||||
pub fast_forward: bool,
|
||||
pub error_notifier: std::sync::mpsc::Sender<String>,
|
||||
pub gui_ctx: egui::Context,
|
||||
}
|
||||
|
||||
pub struct PlayerData {
|
||||
lag: u8,
|
||||
count: u32,
|
||||
pub reg_id: u32,
|
||||
input_events: std::collections::HashMap<u32, InputEvent>,
|
||||
}
|
||||
|
||||
struct InputEvent {
|
||||
input: u32,
|
||||
plugin: u8,
|
||||
}
|
||||
|
||||
fn send_error(netplay: &mut Netplay, error: String) {
|
||||
netplay.error_notifier.send(error).unwrap();
|
||||
|
||||
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
|
||||
}
|
||||
|
||||
pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size: usize) {
|
||||
let mut request: Vec<u8> = [TCP_SEND_SAVE].to_vec();
|
||||
request.extend_from_slice(save_type.as_bytes());
|
||||
request.push(0); // null terminate string
|
||||
request.extend_from_slice(&(size as u32).to_be_bytes());
|
||||
|
||||
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>) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
pub fn send_sync_check(device: &mut device::Device) {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
if netplay.vi_counter % 600 == 0 {
|
||||
let mut request: Vec<u8> = [UDP_SYNC_DATA].to_vec();
|
||||
request.extend_from_slice(&(netplay.vi_counter).to_be_bytes());
|
||||
|
||||
for i in 0..device::cop0::COP0_REGS_COUNT as usize {
|
||||
request.extend_from_slice(&(device.cpu.cop0.regs[i] as u32).to_be_bytes());
|
||||
}
|
||||
|
||||
netplay.udp_socket.send(&request).unwrap();
|
||||
}
|
||||
netplay.vi_counter = netplay.vi_counter.wrapping_add(1);
|
||||
}
|
||||
|
||||
pub fn send_input(netplay: &Netplay, input: (u32, bool)) {
|
||||
let mut request: Vec<u8> = [UDP_SEND_KEY_INFO].to_vec();
|
||||
request.push(netplay.player_number);
|
||||
request.extend_from_slice(
|
||||
&(netplay.player_data[netplay.player_number as usize].count).to_be_bytes(),
|
||||
);
|
||||
request.extend_from_slice(&(input.0).to_be_bytes());
|
||||
request.push(input.1 as u8);
|
||||
netplay.udp_socket.send(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
let mut input = None;
|
||||
|
||||
let timeout = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
let mut request_timer = std::time::Instant::now() - std::time::Duration::from_millis(5);
|
||||
while input.is_none() {
|
||||
process_incoming(netplay); // we execute process_incoming before request_input so that we send an accurate buffer count
|
||||
if std::time::Instant::now() > request_timer {
|
||||
// sends a request packet every 5ms
|
||||
request_input(netplay, channel);
|
||||
request_timer = std::time::Instant::now() + std::time::Duration::from_millis(5);
|
||||
}
|
||||
input = netplay.player_data[channel]
|
||||
.input_events
|
||||
.remove(&netplay.player_data[channel].count);
|
||||
|
||||
if std::time::Instant::now() > timeout {
|
||||
send_error(
|
||||
netplay,
|
||||
"Timed out waiting for input. Lost connection to server".to_string(),
|
||||
);
|
||||
input = Some(InputEvent {
|
||||
input: 0,
|
||||
plugin: 0,
|
||||
});
|
||||
device.cpu.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
netplay.fast_forward = netplay.player_data[channel].lag > 0
|
||||
&& netplay.player_data[channel].input_events.len() as u8 > netplay.buffer_target;
|
||||
|
||||
netplay.player_data[channel].count = netplay.player_data[channel].count.wrapping_add(1);
|
||||
(
|
||||
input.as_ref().unwrap().input,
|
||||
input.as_ref().unwrap().plugin != 0,
|
||||
)
|
||||
}
|
||||
|
||||
fn request_input(netplay: &Netplay, channel: usize) {
|
||||
let mut request: Vec<u8> = [UDP_REQUEST_KEY_INFO].to_vec();
|
||||
request.push(channel as u8); //The player we need input for
|
||||
request.extend_from_slice(
|
||||
&(netplay.player_data[netplay.player_number as usize].reg_id).to_be_bytes(),
|
||||
);
|
||||
request.extend_from_slice(&(netplay.player_data[channel].count).to_be_bytes());
|
||||
request.push(0); //spectator mode
|
||||
request.push(netplay.player_data[channel].input_events.len() as u8);
|
||||
netplay.udp_socket.send(&request).unwrap();
|
||||
}
|
||||
|
||||
fn process_incoming(netplay: &mut Netplay) {
|
||||
let mut buf: [u8; 1024] = [0; 1024];
|
||||
while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) {
|
||||
match buf[0] {
|
||||
UDP_RECEIVE_KEY_INFO | UDP_RECEIVE_KEY_INFO_GRATUITOUS => {
|
||||
let player = buf[1] as usize;
|
||||
//current_status is a status update from the server
|
||||
//it will let us know if another player has disconnected, or the games have desynced
|
||||
let current_status = buf[2];
|
||||
if buf[0] == UDP_RECEIVE_KEY_INFO {
|
||||
netplay.player_data[player].lag = buf[3];
|
||||
}
|
||||
if current_status != netplay.status {
|
||||
if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 {
|
||||
send_error(
|
||||
netplay,
|
||||
format!("Players have desynced at VI {}", netplay.vi_counter),
|
||||
);
|
||||
}
|
||||
for dis in 1..5 {
|
||||
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
|
||||
{
|
||||
send_error(netplay, format!("Player {} has disconnected", dis));
|
||||
}
|
||||
}
|
||||
netplay.status = current_status;
|
||||
}
|
||||
|
||||
let mut buffer_offset = 5;
|
||||
for _i in 0..buf[4] {
|
||||
let count = u32::from_be_bytes(
|
||||
buf[buffer_offset..buffer_offset + 4].try_into().unwrap(),
|
||||
);
|
||||
buffer_offset += 4;
|
||||
|
||||
if (count.wrapping_sub(netplay.player_data[player].count)) > (u32::MAX / 2) {
|
||||
//event doesn't need to be recorded
|
||||
buffer_offset += 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
let input = u32::from_be_bytes(
|
||||
buf[buffer_offset..buffer_offset + 4].try_into().unwrap(),
|
||||
);
|
||||
buffer_offset += 4;
|
||||
let plugin = buf[buffer_offset];
|
||||
buffer_offset += 1;
|
||||
let input_event = InputEvent { input, plugin };
|
||||
netplay.player_data[player]
|
||||
.input_events
|
||||
.insert(count, input_event);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic! {"unknown UDP packet"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
mut peer_addr: std::net::SocketAddr,
|
||||
session: ui::gui::gui_netplay::NetplayRoom,
|
||||
player_number: u8,
|
||||
error_notifier: std::sync::mpsc::Sender<String>,
|
||||
gui_ctx: egui::Context,
|
||||
) -> Netplay {
|
||||
peer_addr.set_port(session.port.unwrap() as u16);
|
||||
let udp_socket;
|
||||
if peer_addr.is_ipv4() {
|
||||
udp_socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address");
|
||||
socket2::SockRef::from(&udp_socket)
|
||||
.set_tos(CS4 << 2)
|
||||
.unwrap();
|
||||
} else {
|
||||
udp_socket = std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address");
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
socket2::SockRef::from(&udp_socket)
|
||||
.set_tclass_v6(CS4 << 2)
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
udp_socket.connect(peer_addr).unwrap();
|
||||
udp_socket.set_nonblocking(true).unwrap();
|
||||
|
||||
let mut stream = std::net::TcpStream::connect(peer_addr).unwrap();
|
||||
|
||||
let regid = (player_number + 1) as u32;
|
||||
let mut request: [u8; 8] = [
|
||||
TCP_REGISTER_PLAYER,
|
||||
player_number,
|
||||
0, //plugin/pak
|
||||
0, //rawdata
|
||||
0, //regid (u32)
|
||||
0, //regid (u32)
|
||||
0, //regid (u32)
|
||||
0, //regid (u32)
|
||||
];
|
||||
|
||||
request[4..8].copy_from_slice(®id.to_be_bytes());
|
||||
stream.write_all(&request).unwrap();
|
||||
|
||||
let mut response: [u8; 2] = [0, 0];
|
||||
stream.read_exact(&mut response).unwrap();
|
||||
if response[0] != 1 {
|
||||
panic!("Failed to register player");
|
||||
}
|
||||
let buffer_target = response[1];
|
||||
|
||||
let request: [u8; 1] = [TCP_GET_REGISTRATION];
|
||||
stream.write_all(&request).unwrap();
|
||||
let mut response: [u8; 24] = [0; 24];
|
||||
stream.read_exact(&mut response).unwrap();
|
||||
|
||||
let mut reg_id: [u32; 4] = [0; 4];
|
||||
for i in 0..4 {
|
||||
// reg_id of 0 means no player connected
|
||||
reg_id[i] = u32::from_be_bytes(response[(i * 6)..(i * 6) + 4].try_into().unwrap());
|
||||
}
|
||||
Netplay {
|
||||
udp_socket,
|
||||
tcp_stream: stream,
|
||||
player_number,
|
||||
vi_counter: 0,
|
||||
status: 0,
|
||||
buffer_target,
|
||||
fast_forward: false,
|
||||
error_notifier,
|
||||
gui_ctx,
|
||||
player_data: [
|
||||
PlayerData {
|
||||
lag: 0,
|
||||
count: 0,
|
||||
reg_id: reg_id[0],
|
||||
input_events: std::collections::HashMap::new(),
|
||||
},
|
||||
PlayerData {
|
||||
lag: 0,
|
||||
count: 0,
|
||||
reg_id: reg_id[1],
|
||||
input_events: std::collections::HashMap::new(),
|
||||
},
|
||||
PlayerData {
|
||||
lag: 0,
|
||||
count: 0,
|
||||
reg_id: reg_id[2],
|
||||
input_events: std::collections::HashMap::new(),
|
||||
},
|
||||
PlayerData {
|
||||
lag: 0,
|
||||
count: 0,
|
||||
reg_id: reg_id[3],
|
||||
input_events: std::collections::HashMap::new(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(device: &mut device::Device) {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
let regid = (netplay.player_number + 1) as u32;
|
||||
let mut request: [u8; 5] = [TCP_DISCONNECT_NOTICE, 0, 0, 0, 0];
|
||||
request[1..5].copy_from_slice(®id.to_be_bytes());
|
||||
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
netplay.tcp_stream.flush().unwrap();
|
||||
netplay
|
||||
.tcp_stream
|
||||
.shutdown(std::net::Shutdown::Both)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -4,79 +4,115 @@ pub mod gui;
|
||||
pub mod input;
|
||||
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,
|
||||
pub game_hash: String,
|
||||
pub paths: storage::Paths,
|
||||
pub saves: storage::Saves,
|
||||
pub sdl_context: Option<sdl2::Sdl>,
|
||||
pub video_subsystem: Option<sdl2::VideoSubsystem>,
|
||||
pub audio_subsystem: Option<sdl2::AudioSubsystem>,
|
||||
pub joystick_subsystem: Option<sdl2::JoystickSubsystem>,
|
||||
pub controller_subsystem: Option<sdl2::GameControllerSubsystem>,
|
||||
pub window: Option<sdl2::video::Window>,
|
||||
pub audio_device: Option<sdl2::audio::AudioQueue<i16>>,
|
||||
pub pak_audio: audio::PakAudio,
|
||||
pub window: *mut sdl3_sys::video::SDL_Window,
|
||||
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub audio_freq: f64,
|
||||
pub audio_device: u32,
|
||||
pub num_joysticks: i32,
|
||||
pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
|
||||
}
|
||||
|
||||
impl Drop for Ui {
|
||||
fn drop(&mut self) {
|
||||
storage::write_saves(self);
|
||||
write_config(self);
|
||||
unsafe {
|
||||
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
|
||||
sdl3_sys::init::SDL_Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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() -> Ui {
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
let audio_subsystem = sdl_context.audio().unwrap();
|
||||
let joystick_subsystem = sdl_context.joystick().unwrap();
|
||||
let controller_subsystem = sdl_context.game_controller().unwrap();
|
||||
|
||||
let config_file_path = dirs::config_dir()
|
||||
.unwrap()
|
||||
.join("gopher64")
|
||||
.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();
|
||||
}
|
||||
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
|
||||
let mut num_joysticks = 0;
|
||||
let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
|
||||
if joysticks.is_null() {
|
||||
panic!("Could not get joystick list");
|
||||
}
|
||||
|
||||
let dirs = get_dirs();
|
||||
|
||||
Ui {
|
||||
controllers: [
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
],
|
||||
config_file_path,
|
||||
config: config_map,
|
||||
keyboard_state: std::ptr::null_mut(),
|
||||
config: config::Config::new(),
|
||||
save_type: vec![],
|
||||
game_id: String::new(),
|
||||
game_hash: String::new(),
|
||||
@@ -85,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 {
|
||||
@@ -92,15 +129,21 @@ 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),
|
||||
},
|
||||
sdl_context: Some(sdl_context),
|
||||
video_subsystem: Some(video_subsystem),
|
||||
audio_subsystem: Some(audio_subsystem),
|
||||
joystick_subsystem: Some(joystick_subsystem),
|
||||
controller_subsystem: Some(controller_subsystem),
|
||||
window: None,
|
||||
audio_device: None,
|
||||
pak_audio: audio::PakAudio {
|
||||
mempak: include_bytes!("../data/mempak.wav").to_vec(),
|
||||
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
|
||||
},
|
||||
window: std::ptr::null_mut(),
|
||||
audio_stream: std::ptr::null_mut(),
|
||||
pak_audio_stream: std::ptr::null_mut(),
|
||||
audio_freq: 0.0,
|
||||
audio_device: 0,
|
||||
num_joysticks,
|
||||
joysticks,
|
||||
dirs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+117
-22
@@ -1,47 +1,142 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub struct PakAudio {
|
||||
pub mempak: Vec<u8>,
|
||||
pub rumblepak: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui, frequency: u64) {
|
||||
let desired_spec = sdl2::audio::AudioSpecDesired {
|
||||
freq: Some(frequency as i32),
|
||||
channels: Some(2),
|
||||
samples: None,
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
|
||||
|
||||
let audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: frequency as i32,
|
||||
channels: 2,
|
||||
};
|
||||
ui.audio_device = Some(
|
||||
ui.audio_subsystem
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.open_queue::<i16, _>(None, &desired_spec)
|
||||
.unwrap(),
|
||||
);
|
||||
ui.audio_device.as_mut().unwrap().resume();
|
||||
ui.audio_device = unsafe {
|
||||
sdl3_sys::audio::SDL_OpenAudioDevice(
|
||||
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&audio_spec,
|
||||
)
|
||||
};
|
||||
if ui.audio_device == 0 {
|
||||
panic!("Could not open audio device");
|
||||
}
|
||||
|
||||
let mut dst = Default::default();
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_GetAudioDeviceFormat(ui.audio_device, &mut dst, std::ptr::null_mut())
|
||||
} {
|
||||
panic!("Could not get audio device format");
|
||||
}
|
||||
|
||||
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
|
||||
if ui.audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.audio_stream) } {
|
||||
panic!("Could not bind audio stream");
|
||||
}
|
||||
|
||||
let wav_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: 24000,
|
||||
channels: 1,
|
||||
};
|
||||
|
||||
ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
|
||||
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } {
|
||||
panic!("Could not bind audio stream");
|
||||
}
|
||||
|
||||
ui.audio_freq = audio_spec.freq as f64;
|
||||
}
|
||||
|
||||
pub fn close(ui: &mut ui::Ui) {
|
||||
unsafe {
|
||||
if !ui.audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream);
|
||||
ui.audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
if !ui.pak_audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.pak_audio_stream);
|
||||
ui.pak_audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device);
|
||||
ui.audio_device = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
|
||||
if ui.pak_audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sound;
|
||||
if pak == device::controller::PakType::RumblePak {
|
||||
sound = &ui.pak_audio.rumblepak;
|
||||
} else if pak == device::controller::PakType::MemPak {
|
||||
sound = &ui.pak_audio.mempak;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.pak_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
|
||||
let audio_device = device.ui.audio_device.as_mut().unwrap();
|
||||
if device.ui.audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut primary_buffer: Vec<i16> = vec![0; length as usize / 2];
|
||||
let mut i = 0;
|
||||
while i < length as usize / 2 {
|
||||
// Left channel
|
||||
primary_buffer[i] = device.rdram.mem[dram_addr + (i * 2) + 2] as i16
|
||||
| (device.rdram.mem[dram_addr + (i * 2) + 3] as i16) << 8;
|
||||
| ((device.rdram.mem[dram_addr + (i * 2) + 3] as i16) << 8);
|
||||
|
||||
// Right channel
|
||||
primary_buffer[i + 1] = device.rdram.mem[dram_addr + (i * 2)] as i16
|
||||
| (device.rdram.mem[dram_addr + (i * 2) + 1] as i16) << 8;
|
||||
| ((device.rdram.mem[dram_addr + (i * 2) + 1] as i16) << 8);
|
||||
i += 2;
|
||||
}
|
||||
|
||||
let audio_queued = audio_device.size() as f64;
|
||||
let acceptable_latency = (audio_device.spec().freq as f64 * 0.2) * 4.0;
|
||||
let min_latency = (audio_device.spec().freq as f64 * 0.02) * 4.0;
|
||||
let audio_queued =
|
||||
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio_stream) } as f64;
|
||||
let acceptable_latency = (device.ui.audio_freq * 0.2) * 4.0;
|
||||
let min_latency = (device.ui.audio_freq * 0.02) * 4.0;
|
||||
|
||||
if audio_queued < min_latency {
|
||||
let silence_buffer: Vec<i16> = vec![0; ((min_latency - audio_queued) * 2.0) as usize & !1];
|
||||
audio_device.queue_audio(&silence_buffer).unwrap();
|
||||
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
device.ui.audio_stream,
|
||||
silence_buffer.as_ptr() as *const std::ffi::c_void,
|
||||
silence_buffer.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
if audio_queued < acceptable_latency {
|
||||
audio_device.queue_audio(&primary_buffer).unwrap();
|
||||
if audio_queued < acceptable_latency
|
||||
&& !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
device.ui.audio_stream,
|
||||
primary_buffer.as_ptr() as *const std::ffi::c_void,
|
||||
primary_buffer.len() as i32 * 2,
|
||||
)
|
||||
}
|
||||
{
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
+45
-6
@@ -2,12 +2,13 @@ use crate::ui;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputProfile {
|
||||
pub keys: [(bool, i32); 18],
|
||||
pub controller_buttons: [(bool, i32); 14],
|
||||
pub controller_axis: [(bool, i32, i16); 18],
|
||||
pub joystick_buttons: [(bool, u32); 14],
|
||||
pub joystick_hat: [(bool, u32, i8); 14],
|
||||
pub joystick_axis: [(bool, u32, i16); 18],
|
||||
pub keys: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub joystick_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub joystick_hat: [(bool, i32, u8); ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub dinput: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -15,14 +16,45 @@ pub struct Input {
|
||||
pub input_profiles: std::collections::HashMap<String, InputProfile>,
|
||||
pub input_profile_binding: [String; 4],
|
||||
pub controller_assignment: [Option<String>; 4],
|
||||
pub controller_enabled: [bool; 4],
|
||||
pub emulate_vru: bool,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Video {
|
||||
pub upscale: bool,
|
||||
pub integer_scaling: bool,
|
||||
pub fullscreen: bool,
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
pub input: Input,
|
||||
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 {
|
||||
@@ -35,6 +67,13 @@ impl Config {
|
||||
],
|
||||
controller_assignment: [None, None, None, None],
|
||||
input_profiles,
|
||||
controller_enabled: [true, false, false, false],
|
||||
emulate_vru: false,
|
||||
},
|
||||
video: Video {
|
||||
upscale: false,
|
||||
integer_scaling: false,
|
||||
fullscreen: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+440
-111
@@ -1,56 +1,98 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use eframe::egui;
|
||||
|
||||
pub mod gui_netplay;
|
||||
|
||||
pub struct GopherEguiApp {
|
||||
dirs: ui::Dirs,
|
||||
configure_profile: bool,
|
||||
profile_name: String,
|
||||
controllers: Vec<String>,
|
||||
controller_names: Vec<String>,
|
||||
selected_controller: [i32; 4],
|
||||
selected_profile: [String; 4],
|
||||
input_profiles: Vec<String>,
|
||||
controller_enabled: [bool; 4],
|
||||
controller_paths: Vec<String>,
|
||||
upscale: bool,
|
||||
integer_scaling: bool,
|
||||
fullscreen: bool,
|
||||
emulate_vru: bool,
|
||||
dinput: bool,
|
||||
show_vru_dialog: bool,
|
||||
vru_window_receiver: Option<std::sync::mpsc::Receiver<Vec<String>>>,
|
||||
netplay_error_receiver: Option<std::sync::mpsc::Receiver<String>>,
|
||||
vru_word_notifier: Option<std::sync::mpsc::Sender<String>>,
|
||||
vru_word_list: Vec<String>,
|
||||
pub netplay: gui_netplay::GuiNetplay,
|
||||
}
|
||||
|
||||
fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
|
||||
struct SaveConfig {
|
||||
selected_controller: [i32; 4],
|
||||
selected_profile: [String; 4],
|
||||
controller_enabled: [bool; 4],
|
||||
upscale: bool,
|
||||
integer_scaling: bool,
|
||||
fullscreen: bool,
|
||||
emulate_vru: bool,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn get_controllers(game_ui: &ui::Ui) -> Vec<String> {
|
||||
pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
|
||||
let mut controllers: Vec<String> = vec![];
|
||||
|
||||
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
|
||||
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
|
||||
for i in 0..num_joysticks {
|
||||
controllers.push(joystick_subsystem.name_for_index(i).unwrap());
|
||||
for offset in 0..game_ui.num_joysticks as isize {
|
||||
let name = unsafe {
|
||||
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID(
|
||||
*(game_ui.joysticks.offset(offset)),
|
||||
))
|
||||
};
|
||||
controllers.push(name.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
controllers
|
||||
}
|
||||
|
||||
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<String> {
|
||||
let mut controller_paths: Vec<String> = vec![];
|
||||
|
||||
for offset in 0..game_ui.num_joysticks as isize {
|
||||
let path = unsafe {
|
||||
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
|
||||
*(game_ui.joysticks.offset(offset)),
|
||||
))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
};
|
||||
controller_paths.push(path);
|
||||
}
|
||||
|
||||
controller_paths
|
||||
}
|
||||
|
||||
impl GopherEguiApp {
|
||||
pub fn new() -> GopherEguiApp {
|
||||
let game_ui = ui::Ui::new();
|
||||
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
|
||||
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
|
||||
let mut guids: Vec<String> = vec![];
|
||||
for i in 0..num_joysticks {
|
||||
guids.push(joystick_subsystem.device_guid(i).unwrap().to_string());
|
||||
}
|
||||
pub fn new(
|
||||
cc: &eframe::CreationContext<'_>,
|
||||
controller_paths: Vec<String>,
|
||||
controller_names: Vec<String>,
|
||||
) -> GopherEguiApp {
|
||||
add_japanese_font(&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 (guid_pos, guid) in guids.iter().enumerate() {
|
||||
if item.as_deref().unwrap() == *guid {
|
||||
selected_controller[pos] = guid_pos as i32;
|
||||
for (path_pos, path) in controller_paths.iter().enumerate() {
|
||||
if item.as_deref().unwrap() == *path {
|
||||
selected_controller[pos] = path_pos as i32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -59,123 +101,372 @@ impl GopherEguiApp {
|
||||
GopherEguiApp {
|
||||
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,
|
||||
controllers: get_controllers(&game_ui),
|
||||
input_profiles: get_input_profiles(&game_ui),
|
||||
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, selected_controller: [i32; 4], selected_profile: [String; 4]) {
|
||||
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
|
||||
for (pos, item) in selected_controller.iter().enumerate() {
|
||||
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] = Some(
|
||||
joystick_subsystem
|
||||
.device_guid(*item as u32)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
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 = selected_profile;
|
||||
config.input.input_profile_binding = save_config_items.selected_profile;
|
||||
config.input.controller_enabled = save_config_items.controller_enabled;
|
||||
|
||||
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();
|
||||
let save_config_items = SaveConfig {
|
||||
selected_controller: self.selected_controller,
|
||||
selected_profile: self.selected_profile.clone(),
|
||||
controller_enabled: self.controller_enabled,
|
||||
upscale: self.upscale,
|
||||
integer_scaling: self.integer_scaling,
|
||||
fullscreen: self.fullscreen,
|
||||
emulate_vru: self.emulate_vru,
|
||||
};
|
||||
let mut config = ui::config::Config::new();
|
||||
save_config(
|
||||
&mut game_ui,
|
||||
self.selected_controller,
|
||||
self.selected_profile.clone(),
|
||||
&mut config,
|
||||
self.controller_paths.clone(),
|
||||
save_config_items,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
egui::Window::new("Configure Input Profile")
|
||||
// .open(&mut self.configure_profile)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let name_label = ui.label("Profile Name:");
|
||||
ui.text_edit_singleline(&mut app.profile_name)
|
||||
.labelled_by(name_label.id);
|
||||
});
|
||||
ui.checkbox(&mut app.dinput, "Use DirectInput");
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Configure Profile").clicked() {
|
||||
let profile_name = app.profile_name.clone();
|
||||
let dinput = app.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()
|
||||
&& !app.input_profiles.contains(&app.profile_name)
|
||||
{
|
||||
app.input_profiles.push(app.profile_name.clone())
|
||||
}
|
||||
};
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
app.configure_profile = false
|
||||
};
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
ctx.show_viewport_immediate(
|
||||
egui::ViewportId::from_hash_of("vru_dialog"),
|
||||
egui::ViewportBuilder::default()
|
||||
.with_title("What would you like to say?")
|
||||
.with_always_on_top(),
|
||||
|ctx, class| {
|
||||
assert!(
|
||||
class == egui::ViewportClass::Immediate,
|
||||
"This egui backend doesn't support multiple viewports"
|
||||
);
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::Grid::new("vru_words").show(ui, |ui| {
|
||||
for (i, v) in app.vru_word_list.iter().enumerate() {
|
||||
if i % 5 == 0 {
|
||||
ui.end_row();
|
||||
}
|
||||
if ui.button((*v).to_string()).clicked() {
|
||||
app.vru_word_notifier
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(v.clone())
|
||||
.unwrap();
|
||||
app.show_vru_dialog = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if ctx.input(|i| i.viewport().close_requested()) {
|
||||
app.vru_word_notifier
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(String::from(""))
|
||||
.unwrap();
|
||||
app.show_vru_dialog = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let netplay;
|
||||
|
||||
let selected_controller = app.selected_controller;
|
||||
let selected_profile = app.selected_profile.clone();
|
||||
let controller_enabled = app.controller_enabled;
|
||||
let upscale = app.upscale;
|
||||
let integer_scaling = app.integer_scaling;
|
||||
let fullscreen = app.fullscreen;
|
||||
let emulate_vru = app.emulate_vru;
|
||||
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() {
|
||||
netplay = false;
|
||||
peer_addr = None;
|
||||
session = None;
|
||||
player_number = None;
|
||||
} else {
|
||||
netplay = true;
|
||||
peer_addr = app.netplay.peer_addr;
|
||||
session = app.netplay.waiting_session.clone();
|
||||
player_number = Some(app.netplay.player_number);
|
||||
}
|
||||
|
||||
let (netplay_error_notifier, netplay_error_receiver): (
|
||||
std::sync::mpsc::Sender<String>,
|
||||
std::sync::mpsc::Receiver<String>,
|
||||
) = std::sync::mpsc::channel();
|
||||
|
||||
let (vru_window_notifier, vru_window_receiver): (
|
||||
std::sync::mpsc::Sender<Vec<String>>,
|
||||
std::sync::mpsc::Receiver<Vec<String>>,
|
||||
) = std::sync::mpsc::channel();
|
||||
|
||||
let (vru_word_notifier, vru_word_receiver): (
|
||||
std::sync::mpsc::Sender<String>,
|
||||
std::sync::mpsc::Receiver<String>,
|
||||
) = std::sync::mpsc::channel();
|
||||
|
||||
if netplay {
|
||||
app.netplay_error_receiver = Some(netplay_error_receiver);
|
||||
}
|
||||
if emulate_vru && !netplay {
|
||||
app.vru_window_receiver = Some(vru_window_receiver);
|
||||
app.vru_word_notifier = Some(vru_word_notifier);
|
||||
} else {
|
||||
app.vru_window_receiver = None;
|
||||
app.vru_word_notifier = None;
|
||||
}
|
||||
|
||||
let rom_contents = app.netplay.rom_contents.clone();
|
||||
let gui_ctx = ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let file = if !netplay {
|
||||
rfd::FileDialog::new().pick_file()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let save_config_items = SaveConfig {
|
||||
selected_controller,
|
||||
selected_profile,
|
||||
controller_enabled,
|
||||
upscale,
|
||||
integer_scaling,
|
||||
fullscreen,
|
||||
emulate_vru,
|
||||
};
|
||||
|
||||
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().as_path());
|
||||
|
||||
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().as_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())
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl eframe::App for GopherEguiApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
if self.netplay.create {
|
||||
gui_netplay::netplay_create(self, ctx);
|
||||
}
|
||||
|
||||
if self.netplay.join {
|
||||
gui_netplay::netplay_join(self, ctx);
|
||||
}
|
||||
|
||||
if self.netplay.wait {
|
||||
gui_netplay::netplay_wait(self, ctx);
|
||||
}
|
||||
|
||||
if self.netplay_error_receiver.is_some() {
|
||||
let result = self.netplay_error_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
self.netplay.error = result.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if !self.netplay.error.is_empty() {
|
||||
gui_netplay::netplay_error(self, ctx, self.netplay.error.clone());
|
||||
}
|
||||
|
||||
if self.configure_profile {
|
||||
egui::Window::new("Configure Input Profile")
|
||||
// .open(&mut self.configure_profile)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let name_label = ui.label("Profile Name: ");
|
||||
ui.text_edit_singleline(&mut self.profile_name)
|
||||
.labelled_by(name_label.id);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Configure Profile").clicked() {
|
||||
let profile_name = self.profile_name.clone();
|
||||
execute(async {
|
||||
let mut game_ui = ui::Ui::new();
|
||||
ui::input::configure_input_profile(&mut game_ui, profile_name);
|
||||
});
|
||||
self.configure_profile = false;
|
||||
if !self.profile_name.is_empty() && self.profile_name != "default" {
|
||||
self.input_profiles.push(self.profile_name.clone())
|
||||
}
|
||||
};
|
||||
if ui.button("Close").clicked() {
|
||||
self.configure_profile = false
|
||||
};
|
||||
});
|
||||
});
|
||||
configure_profile(self, ctx);
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.set_enabled(!self.configure_profile);
|
||||
if self.configure_profile {
|
||||
ui.disable()
|
||||
}
|
||||
egui::Grid::new("button_grid")
|
||||
.min_col_width(200.0)
|
||||
.show(ui, |ui| {
|
||||
if ui.button("Open ROM").clicked() {
|
||||
open_rom(self, ctx);
|
||||
}
|
||||
if !cfg!(target_os = "macos")
|
||||
&& ui.button("Netplay: Create Session").clicked()
|
||||
&& !self.dirs.cache_dir.join("game_running").exists()
|
||||
{
|
||||
self.netplay.create = true;
|
||||
}
|
||||
|
||||
if ui.button("Open ROM").clicked() {
|
||||
// Spawn dialog on main thread
|
||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||
let selected_controller = self.selected_controller;
|
||||
let selected_profile = self.selected_profile.clone();
|
||||
execute(async move {
|
||||
let file = task.await;
|
||||
ui.end_row();
|
||||
|
||||
if let Some(file) = file {
|
||||
let running_file = dirs::cache_dir()
|
||||
.unwrap()
|
||||
.join("gopher64")
|
||||
.join("game_running");
|
||||
if running_file.exists() {
|
||||
return;
|
||||
}
|
||||
let _ = std::fs::File::create(running_file.clone());
|
||||
let mut device = device::Device::new();
|
||||
save_config(&mut device.ui, selected_controller, selected_profile);
|
||||
device::run_game(std::path::Path::new(file.path()), &mut device, false);
|
||||
let _ = std::fs::remove_file(running_file.clone());
|
||||
if ui.button("Configure Input Profile").clicked()
|
||||
&& !self.dirs.cache_dir.join("game_running").exists()
|
||||
{
|
||||
self.configure_profile = true;
|
||||
}
|
||||
|
||||
if !cfg!(target_os = "macos")
|
||||
&& ui.button("Netplay: Join Session").clicked()
|
||||
&& !self.dirs.cache_dir.join("game_running").exists()
|
||||
{
|
||||
self.netplay.join = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ui.button("Configure Input Profile").clicked() {
|
||||
let running_file = dirs::cache_dir()
|
||||
.unwrap()
|
||||
.join("gopher64")
|
||||
.join("game_running");
|
||||
if running_file.exists() {
|
||||
return;
|
||||
}
|
||||
self.configure_profile = true;
|
||||
}
|
||||
|
||||
ui.add_space(32.0);
|
||||
ui.label("Controller Config:");
|
||||
egui::Grid::new("some_unique_id").show(ui, |ui| {
|
||||
egui::Grid::new("controller_config").show(ui, |ui| {
|
||||
ui.label("Port");
|
||||
ui.label("Enabled");
|
||||
ui.label("Profile");
|
||||
ui.label("Controller");
|
||||
ui.end_row();
|
||||
for i in 0..4 {
|
||||
ui.label(format!("{}", i + 1));
|
||||
ui.checkbox(&mut self.controller_enabled[i], "");
|
||||
|
||||
egui::ComboBox::from_id_source(format!("profile-combo-{}", i))
|
||||
egui::ComboBox::from_id_salt(format!("profile-combo-{}", i))
|
||||
.selected_text(self.selected_profile[i].clone())
|
||||
.show_ui(ui, |ui| {
|
||||
for j in 0..self.input_profiles.len() {
|
||||
@@ -190,9 +481,9 @@ impl eframe::App for GopherEguiApp {
|
||||
let controller_text = if self.selected_controller[i] == -1 {
|
||||
"None".to_string()
|
||||
} else {
|
||||
self.controllers[self.selected_controller[i] as usize].clone()
|
||||
self.controller_names[self.selected_controller[i] as usize].clone()
|
||||
};
|
||||
egui::ComboBox::from_id_source(format!("controller-combo-{}", i))
|
||||
egui::ComboBox::from_id_salt(format!("controller-combo-{}", i))
|
||||
.selected_text(controller_text)
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(
|
||||
@@ -200,21 +491,59 @@ impl eframe::App for GopherEguiApp {
|
||||
-1,
|
||||
"None".to_string(),
|
||||
);
|
||||
for j in 0..self.controllers.len() {
|
||||
for j in 0..self.controller_names.len() {
|
||||
ui.selectable_value(
|
||||
&mut self.selected_controller[i],
|
||||
j as i32,
|
||||
self.controllers[j].clone(),
|
||||
self.controller_names[j].clone(),
|
||||
);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
ui.add_space(32.0);
|
||||
ui.checkbox(&mut self.upscale, "High-Res Graphics");
|
||||
ui.checkbox(&mut self.integer_scaling, "Integer Scaling");
|
||||
ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)");
|
||||
ui.add_space(32.0);
|
||||
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")));
|
||||
});
|
||||
|
||||
if self.emulate_vru && self.vru_window_receiver.is_some() {
|
||||
let result = self.vru_window_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
self.show_vru_dialog = true;
|
||||
self.vru_word_list = result.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_vru_dialog {
|
||||
show_vru_dialog(self, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<F: std::future::Future<Output = ()> + Send + 'static>(f: F) {
|
||||
std::thread::spawn(move || futures::executor::block_on(f));
|
||||
fn add_japanese_font(ctx: &egui::Context) {
|
||||
ctx.add_font(eframe::epaint::text::FontInsert::new(
|
||||
"japanese_font",
|
||||
egui::FontData::from_static(include_bytes!("../../data/NotoSansJP-Regular.ttf")),
|
||||
vec![
|
||||
eframe::epaint::text::InsertFontFamily {
|
||||
family: egui::FontFamily::Proportional,
|
||||
priority: egui::epaint::text::FontPriority::Lowest,
|
||||
},
|
||||
eframe::epaint::text::InsertFontFamily {
|
||||
family: egui::FontFamily::Monospace,
|
||||
priority: egui::epaint::text::FontPriority::Lowest,
|
||||
},
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,878 @@
|
||||
use crate::device;
|
||||
use crate::ui::gui;
|
||||
use crate::ui::gui::GopherEguiApp;
|
||||
use eframe::egui;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
const NETPLAY_VERSION: i32 = 17;
|
||||
const EMU_NAME: &str = "gopher64";
|
||||
|
||||
type GameInfo = (String, String, String, Vec<u8>);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GuiNetplay {
|
||||
pub create: bool,
|
||||
pub join: bool,
|
||||
pub wait: bool,
|
||||
pub session_name: String,
|
||||
pub password: String,
|
||||
pub player_name: String,
|
||||
pub error: String,
|
||||
pub create_rom_label: String,
|
||||
pub join_rom_label: String,
|
||||
pub send_chat: bool,
|
||||
pub have_sessions: Option<(String, String)>,
|
||||
pub begin_game: bool,
|
||||
pub chat_log: String,
|
||||
pub chat_message: String,
|
||||
pub selected_session: Option<NetplayRoom>,
|
||||
pub pending_begin: bool,
|
||||
pub peer_addr: Option<std::net::SocketAddr>,
|
||||
pub motd: String,
|
||||
pub sessions: Vec<NetplayRoom>,
|
||||
pub rom_contents: Vec<u8>,
|
||||
pub player_number: u8,
|
||||
pub player_names: [String; 4],
|
||||
pub server: (String, String),
|
||||
pub socket_waiting: bool,
|
||||
pub game_info: GameInfo,
|
||||
pub servers: std::collections::HashMap<String, String>,
|
||||
pub waiting_session: Option<NetplayRoom>,
|
||||
pub socket:
|
||||
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
|
||||
pub server_receiver:
|
||||
Option<std::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
|
||||
pub game_info_receiver: Option<std::sync::mpsc::Receiver<GameInfo>>,
|
||||
pub broadcast_socket: Option<std::net::UdpSocket>,
|
||||
pub broadcast_timer: Option<std::time::Instant>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Default, Clone)]
|
||||
pub struct NetplayRoom {
|
||||
room_name: Option<String>,
|
||||
password: Option<String>,
|
||||
protected: Option<bool>,
|
||||
#[serde(rename = "MD5")]
|
||||
md5: Option<String>,
|
||||
game_name: Option<String>,
|
||||
pub port: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct NetplayMessage {
|
||||
#[serde(rename = "type")]
|
||||
message_type: String,
|
||||
player_name: Option<String>,
|
||||
client_sha: Option<String>,
|
||||
netplay_version: Option<i32>,
|
||||
emulator: Option<String>,
|
||||
room: Option<NetplayRoom>,
|
||||
accept: Option<i32>,
|
||||
message: Option<String>,
|
||||
auth: Option<String>,
|
||||
player_names: Option<[String; 4]>,
|
||||
#[serde(rename = "authTime")]
|
||||
auth_time: Option<String>,
|
||||
rooms: Option<Vec<NetplayRoom>>,
|
||||
}
|
||||
|
||||
fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.servers.is_empty() {
|
||||
if app.netplay.broadcast_socket.is_none() {
|
||||
app.netplay.broadcast_socket = Some(
|
||||
std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address"),
|
||||
);
|
||||
let socket = app.netplay.broadcast_socket.as_ref().unwrap();
|
||||
socket
|
||||
.set_broadcast(true)
|
||||
.expect("set_broadcast call failed");
|
||||
socket
|
||||
.set_nonblocking(true)
|
||||
.expect("could not set up socket");
|
||||
let data: [u8; 1] = [1];
|
||||
socket
|
||||
.send_to(&data, (std::net::Ipv4Addr::BROADCAST, 45000))
|
||||
.expect("couldn't send data");
|
||||
app.netplay.broadcast_timer =
|
||||
Some(std::time::Instant::now() + std::time::Duration::from_secs(5));
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.server_receiver.is_none() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.server_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(response) =
|
||||
reqwest::blocking::get("https://m64p.s3.amazonaws.com/servers.json")
|
||||
.unwrap()
|
||||
.json::<std::collections::HashMap<String, String>>()
|
||||
{
|
||||
tx.send(response).unwrap();
|
||||
gui_ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if app.netplay.broadcast_timer.is_some()
|
||||
&& std::time::Instant::now() > app.netplay.broadcast_timer.unwrap()
|
||||
{
|
||||
app.netplay.broadcast_timer = None;
|
||||
}
|
||||
if app.netplay.broadcast_socket.is_some() && app.netplay.broadcast_timer.is_some() {
|
||||
let mut buffer = [0; 1024];
|
||||
let result = app
|
||||
.netplay
|
||||
.broadcast_socket
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.recv_from(&mut buffer);
|
||||
if result.is_ok() {
|
||||
let (amt, _src) = result.unwrap();
|
||||
let data: std::collections::HashMap<String, String> =
|
||||
serde_json::from_slice(&buffer[..amt]).unwrap();
|
||||
for server in data.iter() {
|
||||
let (server_name, server_ip) = server;
|
||||
app.netplay
|
||||
.servers
|
||||
.insert(server_name.to_string(), server_ip.to_string());
|
||||
app.netplay.server = (server.0.clone(), server.1.clone());
|
||||
}
|
||||
app.netplay.broadcast_socket = None;
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.server_receiver.is_some() {
|
||||
let result = app.netplay.server_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.servers.extend(result.unwrap());
|
||||
app.netplay.server_receiver = None;
|
||||
if app.netplay.server.0.is_empty() {
|
||||
let first_server = app.netplay.servers.iter().next().unwrap();
|
||||
app.netplay.server = (first_server.0.clone(), first_server.1.clone());
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
egui::Window::new("Create Netplay Session").show(ctx, |ui| {
|
||||
egui::Grid::new("netplay_create_grid").show(ui, |ui| {
|
||||
let profile_name_label = ui.label("Session Name:");
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 200.0;
|
||||
ui.add_sized(size, |ui: &mut egui::Ui| {
|
||||
ui.text_edit_singleline(&mut app.netplay.session_name)
|
||||
.labelled_by(profile_name_label.id)
|
||||
});
|
||||
|
||||
ui.end_row();
|
||||
|
||||
let password_label = ui.label("Password (Optional):");
|
||||
|
||||
ui.text_edit_singleline(&mut app.netplay.password)
|
||||
.labelled_by(password_label.id);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
ui.label("ROM");
|
||||
if app.netplay.create_rom_label.is_empty() {
|
||||
app.netplay.create_rom_label = "Open ROM".to_string();
|
||||
}
|
||||
if ui.button(&app.netplay.create_rom_label).clicked() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.create_rom_label = "Inspecting ROM".to_string();
|
||||
std::thread::spawn(move || {
|
||||
let file = rfd::FileDialog::new().pick_file();
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx);
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Open ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
gui_ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
|
||||
ui.end_row();
|
||||
|
||||
let player_name_label = ui.label("Player Name:");
|
||||
|
||||
ui.text_edit_singleline(&mut app.netplay.player_name)
|
||||
.labelled_by(player_name_label.id);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Server:");
|
||||
|
||||
get_servers(app, ctx);
|
||||
|
||||
if app.netplay.game_info_receiver.is_some() {
|
||||
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
if !data.0.is_empty() {
|
||||
app.netplay.game_info = data;
|
||||
app.netplay.rom_contents = app.netplay.game_info.3.clone();
|
||||
app.netplay.create_rom_label = app.netplay.game_info.2.clone();
|
||||
} else {
|
||||
app.netplay.create_rom_label = data.2;
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
egui::ComboBox::from_id_salt("server-combobox")
|
||||
.selected_text(app.netplay.server.0.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
for server in app.netplay.servers.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.server,
|
||||
(server.0.clone(), server.1.clone()),
|
||||
server.0,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ui.end_row();
|
||||
|
||||
if app.netplay.socket_waiting {
|
||||
let data = app.netplay.socket.as_mut().unwrap().read();
|
||||
if data.is_ok() {
|
||||
let message: NetplayMessage =
|
||||
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_create_room" {
|
||||
app.netplay.create = false;
|
||||
app.netplay.wait = true;
|
||||
app.netplay.waiting_session = Some(message.room.unwrap());
|
||||
}
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
}
|
||||
app.netplay.socket_waiting = false;
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Create Session").clicked() {
|
||||
if app.netplay.player_name.is_empty() {
|
||||
app.netplay.error = "Player Name cannot be empty".to_string();
|
||||
} else if app.netplay.session_name.is_empty() {
|
||||
app.netplay.error = "Session Name cannot be empty".to_string();
|
||||
} else if app.netplay.game_info.0.is_empty() {
|
||||
app.netplay.error = "ROM not loaded".to_string();
|
||||
} else {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let mut game_name = app.netplay.game_info.1.to_string();
|
||||
if game_name.is_empty() {
|
||||
// If the ROM doesn't report a name, use the filename
|
||||
game_name = app.netplay.create_rom_label.clone();
|
||||
}
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_create_room".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
message: None,
|
||||
rooms: None,
|
||||
auth_time: Some(now_utc),
|
||||
player_names: None,
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: Some(NetplayRoom {
|
||||
room_name: Some(app.netplay.session_name.clone()),
|
||||
password: Some(app.netplay.password.clone()),
|
||||
game_name: Some(game_name),
|
||||
md5: Some(app.netplay.game_info.0.clone()),
|
||||
protected: None,
|
||||
port: None,
|
||||
}),
|
||||
};
|
||||
let (mut socket, _response) =
|
||||
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&netplay_message).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
match socket.get_mut() {
|
||||
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
|
||||
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
|
||||
stream.set_nonblocking(true)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
.expect("could not set socket to non-blocking");
|
||||
app.netplay.socket_waiting = true;
|
||||
app.netplay.socket = Some(socket);
|
||||
}
|
||||
}
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
// User has changed the server
|
||||
app.netplay.have_sessions = None;
|
||||
app.netplay.socket = None;
|
||||
}
|
||||
if app.netplay.socket.is_none() {
|
||||
let (mut sock, _response) =
|
||||
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
|
||||
match sock.get_mut() {
|
||||
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
|
||||
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
|
||||
stream.set_nonblocking(true)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
.expect("could not set socket to non-blocking");
|
||||
app.netplay.socket = Some(sock);
|
||||
}
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
if app.netplay.have_sessions.is_none() {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let request_rooms = NetplayMessage {
|
||||
message_type: "request_get_rooms".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
player_names: None,
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
rooms: None,
|
||||
message: None,
|
||||
auth_time: Some(now_utc),
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: None,
|
||||
};
|
||||
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&request_rooms).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.have_sessions = Some(app.netplay.server.clone());
|
||||
app.netplay.socket_waiting = true;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender<GameInfo>) {
|
||||
let rom_contents = device::get_rom_contents(file.as_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', "");
|
||||
tx.send((
|
||||
hash,
|
||||
game_name,
|
||||
file.file_name().unwrap().to_string_lossy().to_string(),
|
||||
rom_contents,
|
||||
))
|
||||
.unwrap();
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.socket_waiting {
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
let data = socket.read();
|
||||
if data.is_ok() {
|
||||
let message: NetplayMessage =
|
||||
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_get_rooms" {
|
||||
if message.rooms.is_some() {
|
||||
app.netplay.sessions = message.rooms.unwrap();
|
||||
}
|
||||
} else if message.message_type == "reply_join_room" {
|
||||
app.netplay.join = false;
|
||||
app.netplay.wait = true;
|
||||
app.netplay.waiting_session = Some(message.room.unwrap());
|
||||
}
|
||||
app.netplay.socket_waiting = false;
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.game_info_receiver.is_some() {
|
||||
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
if !data.0.is_empty() {
|
||||
app.netplay.game_info = data;
|
||||
app.netplay.rom_contents = app.netplay.game_info.3.clone();
|
||||
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_join_room".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
netplay_version: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
message: None,
|
||||
rooms: None,
|
||||
auth_time: None,
|
||||
player_names: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: Some(app.netplay.password.clone()),
|
||||
game_name: None,
|
||||
md5: Some(app.netplay.game_info.0.clone()),
|
||||
protected: None,
|
||||
port: app.netplay.selected_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&netplay_message).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
app.netplay.socket_waiting = true;
|
||||
} else {
|
||||
app.netplay.error = data.2;
|
||||
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
egui::Window::new("Join Netplay Session").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 100.0;
|
||||
ui.add_sized(
|
||||
size,
|
||||
egui::TextEdit::singleline(&mut app.netplay.player_name).hint_text("Player name"),
|
||||
);
|
||||
|
||||
get_servers(app, ctx);
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Refresh").clicked() {
|
||||
app.netplay.socket = None;
|
||||
app.netplay.have_sessions = None;
|
||||
app.netplay.selected_session = None;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
egui::ComboBox::from_id_salt("server-combobox")
|
||||
.selected_text(app.netplay.server.0.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
for server in app.netplay.servers.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.server,
|
||||
(server.0.clone(), server.1.clone()),
|
||||
server.0,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if !app.netplay.server.0.is_empty() {
|
||||
get_sessions(app, ctx);
|
||||
}
|
||||
ui.add_space(16.0);
|
||||
if app.netplay.sessions.is_empty() {
|
||||
ui.label("No sessions available");
|
||||
} else {
|
||||
egui::Grid::new("netplay_join_grid").show(ui, |ui| {
|
||||
ui.label(egui::RichText::new("Session Name (click to select)").underline());
|
||||
ui.label(egui::RichText::new("Game Name").underline());
|
||||
ui.label(egui::RichText::new("Password Protected").underline());
|
||||
ui.end_row();
|
||||
|
||||
for room in app.netplay.sessions.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.selected_session,
|
||||
Some(room.clone()),
|
||||
room.room_name.as_ref().unwrap(),
|
||||
);
|
||||
ui.label(room.game_name.as_ref().unwrap());
|
||||
ui.label(room.protected.unwrap_or(false).to_string());
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.add_space(16.0);
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 130.0;
|
||||
ui.add_sized(
|
||||
size,
|
||||
egui::TextEdit::singleline(&mut app.netplay.password)
|
||||
.hint_text("Password (if required)"),
|
||||
);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
if app.netplay.join_rom_label.is_empty() {
|
||||
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
|
||||
}
|
||||
if ui.button(&app.netplay.join_rom_label).clicked() {
|
||||
if app.netplay.player_name.is_empty() {
|
||||
app.netplay.error = "Player Name cannot be empty".to_string();
|
||||
} else if app.netplay.selected_session.is_none() {
|
||||
app.netplay.error = "No session selected".to_string();
|
||||
} else if app
|
||||
.netplay
|
||||
.selected_session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.protected
|
||||
.unwrap()
|
||||
&& app.netplay.password.is_empty()
|
||||
{
|
||||
app.netplay.error = "Session requires a password".to_string();
|
||||
} else {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.join_rom_label = "Inspecting ROM".to_string();
|
||||
std::thread::spawn(move || {
|
||||
let file = rfd::FileDialog::new().pick_file();
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx);
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"No ROM selected".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
gui_ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let motd_message = NetplayMessage {
|
||||
message_type: "request_motd".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
rooms: None,
|
||||
player_names: None,
|
||||
message: None,
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: None,
|
||||
};
|
||||
let request_players = NetplayMessage {
|
||||
message_type: "request_players".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
player_names: None,
|
||||
emulator: None,
|
||||
rooms: None,
|
||||
accept: None,
|
||||
message: None,
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: None,
|
||||
game_name: None,
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
|
||||
if !app.netplay.socket_waiting {
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&motd_message).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&request_players).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.socket_waiting = true;
|
||||
}
|
||||
|
||||
if app.netplay.begin_game {
|
||||
let begin_game = NetplayMessage {
|
||||
message_type: "request_begin_game".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
player_names: None,
|
||||
rooms: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
message: None,
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: None,
|
||||
game_name: None,
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&begin_game).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.begin_game = false;
|
||||
}
|
||||
|
||||
if app.netplay.send_chat {
|
||||
let send_chat = NetplayMessage {
|
||||
message_type: "request_chat_message".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: None,
|
||||
netplay_version: None,
|
||||
player_names: None,
|
||||
rooms: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
message: Some(app.netplay.chat_message.clone()),
|
||||
auth_time: None,
|
||||
auth: None,
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: None,
|
||||
game_name: None,
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
}),
|
||||
};
|
||||
app.netplay.chat_message.clear();
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&send_chat).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.send_chat = false;
|
||||
}
|
||||
|
||||
if app.netplay.socket_waiting {
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
let data = socket.read();
|
||||
if data.is_ok() {
|
||||
let message: NetplayMessage =
|
||||
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
|
||||
if message.accept.unwrap() == 0 {
|
||||
if message.message_type == "reply_motd" {
|
||||
let re = regex::Regex::new(r"<[^>]*>").unwrap();
|
||||
app.netplay.motd = re
|
||||
.replace_all(message.message.unwrap().as_str(), "")
|
||||
.into_owned();
|
||||
} else if message.message_type == "reply_players" {
|
||||
app.netplay.player_names = message.player_names.unwrap();
|
||||
} else if message.message_type == "reply_chat_message" {
|
||||
app.netplay.chat_log.push_str(&message.message.unwrap());
|
||||
app.netplay.chat_log.push('\n');
|
||||
} else if message.message_type == "reply_begin_game" {
|
||||
let mut player = 0;
|
||||
for (i, name) in app.netplay.player_names.iter().enumerate() {
|
||||
if *name == app.netplay.player_name {
|
||||
player = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
app.netplay.player_number = player as u8;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
egui::Window::new("Pending Netplay Session").show(ctx, |ui| {
|
||||
egui::Grid::new("netplay_wait_grid_1").show(ui, |ui| {
|
||||
ui.label("Session Name:");
|
||||
let room_name = app
|
||||
.netplay
|
||||
.waiting_session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.room_name
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let game_name = app
|
||||
.netplay
|
||||
.waiting_session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.game_name
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
ui.label(room_name);
|
||||
ui.end_row();
|
||||
ui.label("Game Name:");
|
||||
ui.label(game_name);
|
||||
ui.end_row();
|
||||
for i in 0..4 {
|
||||
ui.label(format!("Player {}:", i + 1));
|
||||
ui.label(app.netplay.player_names[i].clone());
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
egui::ScrollArea::vertical()
|
||||
.max_height(100.0)
|
||||
.stick_to_bottom(true)
|
||||
.show(ui, |ui| {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut app.netplay.chat_log)
|
||||
.interactive(false)
|
||||
.desired_width(ui.available_width()),
|
||||
);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = ui.spacing().interact_size;
|
||||
size.x = 200.0;
|
||||
|
||||
ui.add_sized(
|
||||
size,
|
||||
egui::TextEdit::singleline(&mut app.netplay.chat_message)
|
||||
.hint_text("Enter chat message here"),
|
||||
);
|
||||
|
||||
if ui
|
||||
.add_enabled(!app.netplay.send_chat, egui::Button::new("Send Message"))
|
||||
.clicked()
|
||||
&& !app.netplay.chat_message.is_empty()
|
||||
{
|
||||
app.netplay.send_chat = true;
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
ui.label(app.netplay.motd.clone());
|
||||
ui.add_space(16.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let button_enabled = app.netplay.player_name == app.netplay.player_names[0]
|
||||
&& !app.netplay.pending_begin;
|
||||
if ui
|
||||
.add_enabled(button_enabled, egui::Button::new("Start Session"))
|
||||
.clicked()
|
||||
{
|
||||
app.netplay.begin_game = true;
|
||||
app.netplay.pending_begin = true;
|
||||
}
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn netplay_error(app: &mut GopherEguiApp, ctx: &egui::Context, error: String) {
|
||||
egui::Window::new("Netplay Error").show(ctx, |ui| {
|
||||
ui.label(error);
|
||||
if ui.button("Close").clicked() {
|
||||
app.netplay.error = "".to_string();
|
||||
};
|
||||
});
|
||||
}
|
||||
+553
-285
File diff suppressed because it is too large
Load Diff
+157
-58
@@ -1,3 +1,4 @@
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -7,6 +8,7 @@ pub enum SaveTypes {
|
||||
Sram,
|
||||
Flash,
|
||||
Mempak,
|
||||
Sdcard,
|
||||
Romsave,
|
||||
}
|
||||
|
||||
@@ -15,6 +17,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,
|
||||
}
|
||||
|
||||
@@ -25,10 +28,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),
|
||||
}
|
||||
|
||||
pub 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: {:X}", save_type),
|
||||
5 => return vec![SaveTypes::Flash],
|
||||
6 => panic!("Unsupported save type: {:X}", save_type),
|
||||
_ => panic!("Unknown save type: {:X}", save_type),
|
||||
}
|
||||
}
|
||||
match game_id {
|
||||
"NB7" | // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
|
||||
"NGT" | // City Tour GrandPrix - Zen Nihon GT Senshuken
|
||||
@@ -86,90 +104,167 @@ pub fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui) {
|
||||
let id = ui.game_id.as_str();
|
||||
ui.save_type = get_save_type(id);
|
||||
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
|
||||
ui.save_type = get_save_type(rom, &ui.game_id);
|
||||
|
||||
let base_path = dirs::data_dir().unwrap().join("gopher64").join("saves");
|
||||
let base_path = ui.dirs.data_dir.join("saves");
|
||||
|
||||
let result = std::fs::create_dir_all(base_path.clone());
|
||||
if result.is_err() {
|
||||
panic!("could not create save dir")
|
||||
}
|
||||
let game_name = std::str::from_utf8(&rom[0x20..0x20 + 0x14])
|
||||
.unwrap()
|
||||
.trim()
|
||||
.replace('\0', "");
|
||||
|
||||
ui.paths.eep_file_path = base_path.clone();
|
||||
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 = base_path.clone();
|
||||
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 = base_path.clone();
|
||||
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 = base_path.clone();
|
||||
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.romsave_file_path = base_path.clone();
|
||||
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) {
|
||||
let eep = std::fs::read(&mut ui.paths.eep_file_path);
|
||||
if eep.is_ok() {
|
||||
ui.saves.eeprom.0 = eep.unwrap();
|
||||
pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
|
||||
let eep = std::fs::read(&mut ui.paths.eep_file_path);
|
||||
if eep.is_ok() {
|
||||
ui.saves.eeprom.0 = eep.unwrap();
|
||||
}
|
||||
let sra = std::fs::read(&mut ui.paths.sra_file_path);
|
||||
if sra.is_ok() {
|
||||
ui.saves.sram.0 = sra.unwrap();
|
||||
}
|
||||
let fla = std::fs::read(&mut ui.paths.fla_file_path);
|
||||
if fla.is_ok() {
|
||||
ui.saves.flash.0 = fla.unwrap();
|
||||
}
|
||||
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
|
||||
if mempak.is_ok() {
|
||||
ui.saves.mempak.0 = mempak.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();
|
||||
}
|
||||
}
|
||||
let sra = std::fs::read(&mut ui.paths.sra_file_path);
|
||||
if sra.is_ok() {
|
||||
ui.saves.sram.0 = sra.unwrap();
|
||||
}
|
||||
let fla = std::fs::read(&mut ui.paths.fla_file_path);
|
||||
if fla.is_ok() {
|
||||
ui.saves.flash.0 = fla.unwrap();
|
||||
}
|
||||
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
|
||||
if mempak.is_ok() {
|
||||
ui.saves.mempak.0 = mempak.unwrap();
|
||||
}
|
||||
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();
|
||||
|
||||
if netplay.is_some() {
|
||||
if netplay.as_ref().unwrap().player_number == 0 {
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"eep",
|
||||
&ui.saves.eeprom.0,
|
||||
ui.saves.eeprom.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"sra",
|
||||
&ui.saves.sram.0,
|
||||
ui.saves.sram.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"fla",
|
||||
&ui.saves.flash.0,
|
||||
ui.saves.flash.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"mpk",
|
||||
&ui.saves.mempak.0,
|
||||
ui.saves.mempak.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"img",
|
||||
&ui.saves.sdcard.0,
|
||||
ui.saves.sdcard.0.len(),
|
||||
);
|
||||
let mut romsave_bytes: Vec<u8> = vec![];
|
||||
if !ui.saves.romsave.0.is_empty() {
|
||||
romsave_bytes = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
|
||||
}
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"rom",
|
||||
&romsave_bytes,
|
||||
romsave_bytes.len(),
|
||||
);
|
||||
} else {
|
||||
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);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut ui.saves.sdcard.0);
|
||||
let mut romsave_bytes: Vec<u8> = vec![];
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut romsave_bytes);
|
||||
if !romsave_bytes.is_empty() {
|
||||
ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub 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).unwrap();
|
||||
fn write_rom_save(ui: &ui::Ui) {
|
||||
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) {
|
||||
if ui.saves.eeprom.1 {
|
||||
write_save(ui, SaveTypes::Eeprom16k)
|
||||
}
|
||||
if ui.saves.sram.1 {
|
||||
write_save(ui, SaveTypes::Sram)
|
||||
}
|
||||
if ui.saves.flash.1 {
|
||||
write_save(ui, SaveTypes::Flash)
|
||||
}
|
||||
if ui.saves.mempak.1 {
|
||||
write_save(ui, SaveTypes::Mempak)
|
||||
}
|
||||
if ui.saves.romsave.1 {
|
||||
write_save(ui, SaveTypes::Romsave)
|
||||
pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
|
||||
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
|
||||
if ui.saves.eeprom.1 {
|
||||
write_save(ui, SaveTypes::Eeprom16k)
|
||||
}
|
||||
if ui.saves.sram.1 {
|
||||
write_save(ui, SaveTypes::Sram)
|
||||
}
|
||||
if ui.saves.flash.1 {
|
||||
write_save(ui, SaveTypes::Flash)
|
||||
}
|
||||
if ui.saves.mempak.1 {
|
||||
write_save(ui, SaveTypes::Mempak)
|
||||
}
|
||||
if ui.saves.sdcard.1 {
|
||||
write_save(ui, SaveTypes::Sdcard)
|
||||
}
|
||||
if ui.saves.romsave.1 {
|
||||
write_save(ui, SaveTypes::Romsave)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
|
||||
fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
|
||||
let path: &std::path::Path;
|
||||
let data: &Vec<u8>;
|
||||
match save_type {
|
||||
@@ -189,6 +284,10 @@ pub 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;
|
||||
|
||||
+78
-44
@@ -1,36 +1,65 @@
|
||||
#![allow(non_snake_case)]
|
||||
include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs"));
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
extern "C" {
|
||||
pub fn vk_init(rdram_ptr: usize, rdram_size: u32, fullscreen: u8);
|
||||
pub fn set_sdl_window(window: usize);
|
||||
pub fn rdp_update_screen() -> u8;
|
||||
pub fn rdp_set_vi_register(reg: u32, value: u32);
|
||||
pub fn rdp_process_commands(dpc_regs: &mut [u32; 8], SP_DMEM: &mut [u8; 8192]) -> u64;
|
||||
pub fn full_sync();
|
||||
}
|
||||
pub fn init(device: &mut device::Device, fullscreen: bool) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
|
||||
|
||||
let title = std::ffi::CString::new("gopher64").unwrap();
|
||||
|
||||
let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN;
|
||||
|
||||
pub fn init(ui: &mut ui::Ui, rdram_ptr: *mut u8, rdram_size: usize, fullscreen: bool) {
|
||||
let mut builder = ui
|
||||
.video_subsystem
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.window("gopher64", 640, 480);
|
||||
builder.position_centered().vulkan();
|
||||
if fullscreen {
|
||||
builder.fullscreen_desktop();
|
||||
flags |= sdl3_sys::video::SDL_WINDOW_FULLSCREEN;
|
||||
} else {
|
||||
builder.resizable();
|
||||
flags |= sdl3_sys::video::SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
ui.window = Some(builder.build().unwrap());
|
||||
|
||||
device.ui.window =
|
||||
unsafe { sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), 640, 480, flags) };
|
||||
if device.ui.window.is_null() {
|
||||
panic!("Could not create window");
|
||||
}
|
||||
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.window) } {
|
||||
panic!("Could not show window");
|
||||
}
|
||||
|
||||
let gfx_info = GFX_INFO {
|
||||
RDRAM: device.rdram.mem.as_mut_ptr(),
|
||||
DMEM: device.rsp.mem.as_mut_ptr(),
|
||||
RDRAM_SIZE: device.rdram.size,
|
||||
DPC_CURRENT_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_CURRENT_REG as usize],
|
||||
DPC_START_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_START_REG as usize],
|
||||
DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize],
|
||||
DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize],
|
||||
VI_H_START_REG: &mut device.vi.regs[device::vi::VI_H_START_REG as usize],
|
||||
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 {
|
||||
set_sdl_window(ui.window.as_mut().unwrap().raw() as usize);
|
||||
vk_init(rdram_ptr as usize, rdram_size as u32, fullscreen as u8)
|
||||
rdp_init(
|
||||
device.ui.window as *mut std::ffi::c_void,
|
||||
gfx_info,
|
||||
device.ui.config.video.upscale,
|
||||
device.ui.config.video.integer_scaling,
|
||||
fullscreen,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_screen() -> u8 {
|
||||
// when the window is closed, running is set to 0
|
||||
|
||||
pub fn close(ui: &ui::Ui) {
|
||||
unsafe {
|
||||
rdp_close();
|
||||
sdl3_sys::video::SDL_DestroyWindow(ui.window);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_screen() -> bool {
|
||||
// when the window is closed, running is set to false
|
||||
unsafe { rdp_update_screen() }
|
||||
}
|
||||
|
||||
@@ -40,30 +69,27 @@ pub fn set_register(reg: u32, value: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_rdp_list(dpc_regs: &mut [u32; 8], sp_dmem: &mut [u8; 8192]) -> u64 {
|
||||
|
||||
unsafe { rdp_process_commands(dpc_regs, sp_dmem) }
|
||||
pub fn process_rdp_list() -> u64 {
|
||||
unsafe { rdp_process_commands() }
|
||||
}
|
||||
|
||||
pub fn rdp_full_sync() {
|
||||
unsafe {
|
||||
full_sync();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
text: &str,
|
||||
canvas: &mut sdl2::render::Canvas<sdl2::video::Window>,
|
||||
font: &rusttype::Font,
|
||||
) {
|
||||
pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font: &rusttype::Font) {
|
||||
let text_size = 32;
|
||||
let scale = rusttype::Scale::uniform(text_size as f32);
|
||||
let v_metrics = font.v_metrics(scale);
|
||||
let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent);
|
||||
|
||||
// Clear the canvas
|
||||
canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0));
|
||||
canvas.clear();
|
||||
unsafe {
|
||||
sdl3_sys::render::SDL_SetRenderDrawColor(
|
||||
renderer,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
|
||||
);
|
||||
sdl3_sys::render::SDL_RenderClear(renderer);
|
||||
};
|
||||
|
||||
for glyph in font.layout(text, scale, offset) {
|
||||
if let Some(bb) = glyph.pixel_bounding_box() {
|
||||
@@ -71,15 +97,23 @@ pub fn draw_text(
|
||||
let x = x as i32 + bb.min.x;
|
||||
let y = y as i32 + bb.min.y + (240 - text_size);
|
||||
if v > 0.5 {
|
||||
canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255));
|
||||
canvas
|
||||
.draw_point(sdl2::rect::Point::new(x, y))
|
||||
.expect("Error drawing pixel");
|
||||
unsafe {
|
||||
sdl3_sys::render::SDL_SetRenderDrawColor(
|
||||
renderer,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
|
||||
);
|
||||
sdl3_sys::render::SDL_RenderPoint(renderer, x as f32, y as f32);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Present the canvas
|
||||
canvas.present();
|
||||
if !unsafe { sdl3_sys::render::SDL_RenderPresent(renderer) } {
|
||||
panic!("Could not present renderer");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use eframe::egui;
|
||||
|
||||
use crate::ui;
|
||||
|
||||
pub fn prompt_for_match(
|
||||
words: &[String],
|
||||
window_notifier: &std::sync::mpsc::Sender<Vec<String>>,
|
||||
word_index_receiver: &std::sync::mpsc::Receiver<String>,
|
||||
gui_ctx: &egui::Context,
|
||||
) -> u16 {
|
||||
let mut dedup_words = words.to_owned();
|
||||
dedup_words.sort();
|
||||
dedup_words.dedup();
|
||||
window_notifier.send(dedup_words).unwrap();
|
||||
gui_ctx.request_repaint(); // this is so the window pops up right away
|
||||
let mut result = word_index_receiver.try_recv();
|
||||
while result.is_err() {
|
||||
result = word_index_receiver.try_recv();
|
||||
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
|
||||
ui::video::update_screen(); // so the OS doesn't complain about the game window being frozen
|
||||
}
|
||||
for (i, v) in words.iter().enumerate() {
|
||||
if *v == *result.as_ref().unwrap() {
|
||||
return i as u16;
|
||||
}
|
||||
}
|
||||
0x7FFF
|
||||
}
|
||||
Reference in New Issue
Block a user