Compare commits

...

57 Commits

Author SHA1 Message Date
Logan McNaughton 5b6f9b76a6 fix interlaced image (#383) 2025-04-15 23:23:47 +02:00
Logan McNaughton 5a838464a4 update cargo packages 2025-04-15 22:44:24 +02:00
Logan McNaughton 383b568012 remove framebuffer tracking (#381)
* remove framebuffer tracking

* more
2025-04-15 11:08:40 +02:00
Logan McNaughton 27bb66df4c Cleanup some controller code (#378)
* controller hotplugging

* more

* more
2025-04-13 21:07:54 +02:00
Logan McNaughton 33f74e5062 update cargo packages 2025-04-08 07:52:02 +02:00
Logan McNaughton 68f90e1cd7 update 7z package (#370) 2025-04-05 09:30:12 +02:00
dependabot[bot] cc5f636912 Bump zip from 2.5.0 to 2.6.0 (#369)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.5.0...v2.6.0)

---
updated-dependencies:
- dependency-name: zip
  dependency-version: 2.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-04 06:51:11 +02:00
Logan McNaughton e3a9849ad7 bump to 1.0.16 (#368) 2025-04-03 12:13:20 +02:00
Logan McNaughton 7ed6867691 Update rust to 1.86.0 (#367) 2025-04-03 12:01:15 +02:00
Logan McNaughton 11a8e96564 better default for address tracking 2025-04-02 14:06:43 +02:00
Logan McNaughton 7111cc9761 update cargo packages 2025-04-02 12:26:48 +02:00
Logan McNaughton b4832b941f sync parallel rdp for texture images (#361)
* sync parallel rdp for texture images

* more

* more

* more

* more

* more
2025-04-02 12:24:30 +02:00
dependabot[bot] 82b1888f82 Bump governor from 0.9.0 to 0.10.0 (#362)
Bumps [governor](https://github.com/boinkor-net/governor) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/boinkor-net/governor/releases)
- [Changelog](https://github.com/boinkor-net/governor/blob/master/release.toml)
- [Commits](https://github.com/boinkor-net/governor/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: governor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-28 07:47:22 +01:00
Logan McNaughton f35869190b gather controller input even without focus (#360) 2025-03-27 08:55:52 +01:00
Logan McNaughton 64eda1a1e3 bump to 1.0.15 (#358) 2025-03-26 11:27:02 +01:00
Logan McNaughton 8026bf13d6 update cargo packages 2025-03-26 11:18:05 +01:00
Logan McNaughton 83103130e7 cleanup 2025-03-25 12:59:32 +01:00
dependabot[bot] 87717c4c7e Bump governor from 0.8.1 to 0.9.0 (#357)
Bumps [governor](https://github.com/boinkor-net/governor) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/boinkor-net/governor/releases)
- [Changelog](https://github.com/boinkor-net/governor/blob/master/release.toml)
- [Commits](https://github.com/boinkor-net/governor/compare/v0.8.1...v0.9.0)

---
updated-dependencies:
- dependency-name: governor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 06:00:08 +01:00
Logan McNaughton 6355885a56 let server determine netplay buffer target (#356) 2025-03-24 15:03:50 +01:00
dependabot[bot] 5d180dfb65 Bump zip from 2.4.2 to 2.5.0 (#354)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.4.2 to 2.5.0.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.4.2...v2.5.0)

---
updated-dependencies:
- dependency-name: zip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 11:28:28 +01:00
Logan McNaughton fd6965ff3b remove rusttype (#355)
* remove rusttype

* more
2025-03-24 11:06:17 +01:00
Logan McNaughton 2fcf202a5c move variable into function 2025-03-23 22:07:32 +01:00
Logan McNaughton 93630c4156 fix audit workflow (#350) 2025-03-23 21:31:24 +01:00
Logan McNaughton a66e8184e4 add workflow dispatch option 2025-03-23 19:25:15 +01:00
Logan McNaughton e4f05af38d add audit action (#349) 2025-03-23 19:24:17 +01:00
Logan McNaughton 8a09cb711f Add RDP to savestate (#348)
* organize RDP device

* more

* more

* more

* more

* more

* more

* more

* more
2025-03-22 21:24:39 +01:00
Logan McNaughton a58eed7dc9 add a few more loops (#347)
* add a few more loops

* more
2025-03-22 08:23:48 +01:00
Logan McNaughton 09ba7e4d13 use loop for cache (#346) 2025-03-21 22:37:04 +01:00
Logan McNaughton 7316e41c7e pin rust version 2025-03-21 15:37:11 +01:00
Logan McNaughton 1aa00de1b2 fix crash when attempting to read zip without rom (#345)
* fix crash when attempting to read zip without rom

* more
2025-03-19 18:37:35 +01:00
Logan McNaughton f71ec9d54a add cic entry for libdragon ipl3 (#344) 2025-03-19 14:15:00 +01:00
Logan McNaughton bf2734e59c update cargo packages 2025-03-19 09:56:00 +01:00
dependabot[bot] 7ee26961f1 Bump zip from 2.2.3 to 2.4.1 (#341)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.2.3 to 2.4.1.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.2.3...v2.4.1)

---
updated-dependencies:
- dependency-name: zip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-18 09:21:31 +01:00
Logan McNaughton 5d692318fe better region check 2025-03-17 10:06:26 +01:00
Logan McNaughton f2966230f9 Fix freeze in Bass Hunter 64 (#339) 2025-03-17 09:46:09 +01:00
Logan McNaughton 48575f9c52 fix windows cross compile (#336) 2025-03-16 20:31:57 +01:00
Logan McNaughton dbab58f4a7 catch possible error in loading save state (#335) 2025-03-16 17:10:16 +01:00
Logan McNaughton f6b0b59bbc fix savestate default memory 2025-03-16 10:32:32 +01:00
Logan McNaughton ca61695dd1 update cargo packages 2025-03-16 10:13:23 +01:00
Logan McNaughton a182fbb1f9 cleanup function 2025-03-16 10:11:50 +01:00
Logan McNaughton da9b2247f8 remove more mut (#334) 2025-03-16 10:10:16 +01:00
Logan McNaughton 1bcadb3a8b remove un-needed mut (#333)
* remove un-needed mut

* more

* more
2025-03-16 09:48:32 +01:00
Logan McNaughton 433f08c7e0 Update README.md 2025-03-16 07:55:10 +01:00
Logan McNaughton b07dde8768 disable mempak for Chameleon Twist (#332) 2025-03-16 07:35:37 +01:00
Logan McNaughton cba7bc7477 ensure gain setting persists 2025-03-16 07:18:05 +01:00
Logan McNaughton ea6f203e33 allow up to 200% audio volume (#331) 2025-03-15 20:21:46 +01:00
Logan McNaughton e48ad9bdff add way to change audio volume (#330)
* add way to change audio volume

* more
2025-03-15 11:11:18 +01:00
Logan McNaughton 11b1137412 update netplay server name 2025-03-13 21:03:50 +01:00
Logan McNaughton 5528f233c0 fix compile warning 2025-03-13 16:51:41 +01:00
Logan McNaughton 9d18673257 bump to 1.0.14 (#328) 2025-03-13 07:20:29 +01:00
Logan McNaughton 2a3fa03b35 update cargo packages 2025-03-13 07:09:52 +01:00
Logan McNaughton ec2f47e5d3 cleanup vector code 2025-03-11 15:08:35 +01:00
Logan McNaughton 139d66eb82 use vector for rdram_dirty (#322) 2025-03-11 14:52:57 +01:00
Logan McNaughton 6e10bee216 use glow for egui (#321)
* Revert "use GL backend for GUI to avoid conflicts with game window (#287)"

This reverts commit 00964f06f0.

* use glow
2025-03-11 09:43:09 +01:00
Logan McNaughton dff9fc5381 bump to 1.0.13 (#318) 2025-03-10 23:01:21 +01:00
Logan McNaughton 84dcc6c154 fix image scale when using upscaling (#317) 2025-03-10 22:53:16 +01:00
Logan McNaughton 8aa23e1665 bump to 1.0.12 (#316) 2025-03-10 17:22:51 +01:00
37 changed files with 1021 additions and 959 deletions
+18
View File
@@ -0,0 +1,18 @@
name: Security audit
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
audit:
permissions:
issues: write
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
Generated
+514 -353
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "1.0.11"
version = "1.0.16"
edition = "2024"
rust-version = "1.85"
@@ -8,16 +8,16 @@ rust-version = "1.85"
[dependencies]
dirs = "6.0"
zip = "2.2"
governor = "0.8"
sevenz-rust = "0.6"
zip = "2.6"
governor = "0.10"
sevenz-rust2 = { version = "0.13.0", default-features = false }
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-big-array = "0.5"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "wgpu"] }
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "glow"] }
sha2 = "0.10"
rusttype = "0.9"
ab_glyph = "0.2"
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.43", features = ["rt-multi-thread", "macros"] }
@@ -35,7 +35,7 @@ clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
[build-dependencies]
winres = "0.1"
winresource = "0.1"
bindgen = "0.71"
cc = { version = "1.2", features = ["parallel"] }
+6 -6
View File
@@ -15,17 +15,17 @@ https://discord.gg/9RGXq8W8JQ
Keys are mapped according to [these defaults](https://github.com/gopher64/gopher64/wiki/Default-Keyboard-Setup). Xbox-style controllers also have a [default mapping applied](https://github.com/gopher64/gopher64/wiki/Default-Gamepad-Setup).
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`
You can create your 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`
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.
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/gopher64/gopher64-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.
If you would like to keep all the game data in the same folder as the executable, you just need to create a file called "portable.txt" in the same directory as the executable.
## flatpak
@@ -39,7 +39,7 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
1. Performance. I want to be able to use this emulator on my laptop.
2. Easy to use.
3. Easy to work on. Dynamic recompilers perform well, but they are very hard to read and understand. This emulator will only have interpreters for the CPU and RSP. Additionally, it is completely written in Rust (besides Parallel-RDP), a modern programming language with a growing user base. I've tried to avoid the use of macros, which can reduce some repetitiveness in the code, but also reduce readability.
3. Easy to work on. Dynamic recompilers perform well, but they are very hard to read and understand. This emulator will only have interpreters for the CPU and RSP. Additionally, it is completely written in Rust (besides Parallel-RDP), a modern programming language with a growing user base. I've tried to avoid the use of macros, which can reduce some repetitiveness in the code but also reduce readability.
## building and usage
@@ -52,7 +52,7 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
## 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.
I am very open to contributions! Please contact me via a GitHub issue or Discord (loganmc10) before doing substantial work on a PR.
## license
+12 -4
View File
@@ -59,9 +59,16 @@ fn main() {
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
if os == "windows" {
if arch == "x86_64" {
build.flag("/arch:AVX2");
if env == "msvc" {
build.flag("/arch:AVX2");
} else if env == "gnu" {
build.flag("-march=x86-64-v3");
} else {
panic!("unknown env")
}
} else if arch == "aarch64" {
panic!("unsupported platform")
} else {
@@ -69,7 +76,7 @@ fn main() {
}
build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
winres::WindowsResource::new()
winresource::WindowsResource::new()
.set_icon("data/icon.ico")
.compile()
.unwrap();
@@ -101,8 +108,9 @@ fn main() {
.allowlist_function("rdp_process_commands")
.allowlist_function("rdp_check_callback")
.allowlist_function("rdp_new_processor")
.allowlist_function("rdp_check_framebuffers")
.allowlist_function("rdp_full_sync")
.allowlist_function("rdp_state_size")
.allowlist_function("rdp_save_state")
.allowlist_function("rdp_load_state")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
+57 -117
View File
@@ -52,42 +52,32 @@ enum vi_registers
VI_REGS_COUNT
};
typedef struct
{
uint32_t cmd_data[0x00040000 >> 2];
int cmd_cur;
int cmd_ptr;
uint32_t region;
} RDP_DEVICE;
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 RDP_DEVICE rdp_device;
static bool crop_letterbox;
static CALL_BACK callback;
static GFX_INFO gfx_info;
static uint32_t region;
static bool crop_letterbox;
static const uint32_t *fragment_spirv;
static size_t fragment_size;
typedef struct
{
uint32_t depthbuffer_address;
uint32_t framebuffer_address;
uint32_t framebuffer_pixel_size;
uint32_t framebuffer_width;
uint32_t framebuffer_height;
uint32_t framebuffer_size;
uint32_t depthbuffer_size;
uint8_t depthbuffer_enabled;
} FrameBufferInfo;
typedef struct
{
float SourceSize[4];
float OutputSize[4];
} Push;
static uint8_t *rdram_dirty;
static uint64_t sync_signal;
static FrameBufferInfo frame_buffer_info;
static const unsigned cmd_len_lut[64] = {
1,
1,
@@ -195,6 +185,12 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
case SDL_SCANCODE_F7:
callback.load_state = true;
break;
case SDL_SCANCODE_LEFTBRACKET:
callback.lower_volume = true;
break;
case SDL_SCANCODE_RIGHTBRACKET:
callback.raise_volume = true;
break;
default:
break;
}
@@ -205,11 +201,8 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
void rdp_new_processor(GFX_INFO _gfx_info)
{
memset(&frame_buffer_info, 0, sizeof(FrameBufferInfo));
sync_signal = 0;
memset(rdram_dirty, 0, gfx_info.RDRAM_SIZE / 8);
gfx_info = _gfx_info;
if (processor)
{
delete processor;
@@ -270,7 +263,6 @@ void rdp_init(void *_window, GFX_INFO _gfx_info)
rdp_close();
}
rdram_dirty = (uint8_t *)malloc(gfx_info.RDRAM_SIZE / 8);
rdp_new_processor(gfx_info);
if (!processor->device_is_supported())
@@ -289,12 +281,6 @@ void rdp_init(void *_window, GFX_INFO _gfx_info)
void rdp_close()
{
if (rdram_dirty)
{
free(rdram_dirty);
rdram_dirty = nullptr;
}
wsi->end_frame();
if (processor)
@@ -414,7 +400,7 @@ static void render_frame(Vulkan::Device &device)
if (image)
{
VkViewport vp = cmd->get_viewport();
calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height, image->get_height());
calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height, image->get_height() / gfx_info.upscale);
if (gfx_info.crt)
{
@@ -456,42 +442,24 @@ CALL_BACK rdp_check_callback()
CALL_BACK return_value = callback;
callback.save_state = false;
callback.load_state = false;
callback.lower_volume = false;
callback.raise_volume = false;
return return_value;
}
void rdp_check_framebuffers(uint32_t address)
size_t rdp_state_size()
{
if (sync_signal && rdram_dirty[address >> 3])
{
processor->wait_for_timeline(sync_signal);
memset(rdram_dirty, 0, gfx_info.RDRAM_SIZE / 8);
sync_signal = 0;
}
return sizeof(RDP_DEVICE);
}
void rdp_full_sync()
void rdp_save_state(uint8_t *state)
{
processor->wait_for_timeline(processor->signal_timeline());
memcpy(state, &rdp_device, sizeof(RDP_DEVICE));
}
void calculate_buffer_size()
void rdp_load_state(const uint8_t *state)
{
switch (frame_buffer_info.framebuffer_pixel_size)
{
case 0:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height / 2) >> 3;
break;
case 1:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height) >> 3;
break;
case 2:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
break;
case 3:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 4) >> 3;
break;
}
frame_buffer_info.depthbuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
memcpy(&rdp_device, state, sizeof(RDP_DEVICE));
}
uint64_t rdp_process_commands()
@@ -505,7 +473,7 @@ uint64_t rdp_process_commands()
return interrupt_timer;
length = unsigned(length) >> 3;
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
if ((rdp_device.cmd_ptr + length) & ~(0x0003FFFF >> 3))
return interrupt_timer;
uint32_t offset = DP_CURRENT;
@@ -514,10 +482,10 @@ uint64_t rdp_process_commands()
do
{
offset &= 0xFF8;
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));
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
offset += sizeof(uint64_t);
cmd_ptr++;
rdp_device.cmd_ptr++;
} while (--length > 0);
}
else
@@ -531,89 +499,61 @@ uint64_t rdp_process_commands()
do
{
offset &= 0xFFFFF8;
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);
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset);
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset + 4);
offset += sizeof(uint64_t);
cmd_ptr++;
rdp_device.cmd_ptr++;
} while (--length > 0);
}
}
while (cmd_cur - cmd_ptr < 0)
while (rdp_device.cmd_cur - rdp_device.cmd_ptr < 0)
{
uint32_t w1 = cmd_data[2 * cmd_cur];
uint32_t w2 = cmd_data[2 * cmd_cur + 1];
uint32_t w1 = rdp_device.cmd_data[2 * rdp_device.cmd_cur];
uint32_t w2 = rdp_device.cmd_data[2 * rdp_device.cmd_cur + 1];
uint32_t command = (w1 >> 24) & 63;
int cmd_length = cmd_len_lut[command];
if (cmd_ptr - cmd_cur - cmd_length < 0)
if (rdp_device.cmd_ptr - rdp_device.cmd_cur - cmd_length < 0)
{
*gfx_info.DPC_START_REG = *gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
return interrupt_timer;
}
if (command >= 8)
processor->enqueue_command(cmd_length * 2, &cmd_data[2 * cmd_cur]);
processor->enqueue_command(cmd_length * 2, &rdp_device.cmd_data[2 * rdp_device.cmd_cur]);
if ((RDP::Op(command) >= RDP::Op::FillTriangle && RDP::Op(command) <= RDP::Op::ShadeTextureZBufferTriangle) ||
RDP::Op(command) == RDP::Op::TextureRectangle ||
RDP::Op(command) == RDP::Op::TextureRectangleFlip ||
RDP::Op(command) == RDP::Op::FillRectangle)
switch (RDP::Op(command))
{
if (!rdram_dirty[frame_buffer_info.framebuffer_address])
{
for (uint32_t i = frame_buffer_info.framebuffer_address; i < frame_buffer_info.framebuffer_address + frame_buffer_info.framebuffer_size; ++i)
{
rdram_dirty[i] = 1;
}
}
if (frame_buffer_info.depthbuffer_enabled && !rdram_dirty[frame_buffer_info.depthbuffer_address])
{
for (uint32_t i = frame_buffer_info.depthbuffer_address; i < frame_buffer_info.depthbuffer_address + frame_buffer_info.depthbuffer_size; ++i)
{
rdram_dirty[i] = 1;
}
}
}
else if (RDP::Op(command) == RDP::Op::SetOtherModes)
{
frame_buffer_info.depthbuffer_enabled = (w2 >> 5) & 1;
}
else if (RDP::Op(command) == RDP::Op::SetColorImage)
{
frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF) >> 3;
frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3;
frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1;
calculate_buffer_size();
}
else if (RDP::Op(command) == RDP::Op::SetMaskImage)
{
frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF) >> 3;
}
else if (RDP::Op(command) == RDP::Op::SetScissor)
case RDP::Op::SetScissor:
{
uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2;
uint32_t upper_left_y = (w1 & 0xFFF) >> 2;
uint32_t lower_right_x = ((w2 >> 12) & 0xFFF) >> 2;
uint32_t lower_right_y = (w2 & 0xFFF) >> 2;
region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
frame_buffer_info.framebuffer_height = lower_right_y;
calculate_buffer_size();
if (lower_right_x > upper_left_x && lower_right_y > upper_left_y)
{
rdp_device.region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
}
else
{
rdp_device.region = 0;
}
break;
}
else if (RDP::Op(command) == RDP::Op::SyncFull)
{
sync_signal = processor->signal_timeline();
interrupt_timer = region;
case RDP::Op::SyncFull:
processor->wait_for_timeline(processor->signal_timeline());
interrupt_timer = rdp_device.region;
if (interrupt_timer == 0)
interrupt_timer = 5000;
break;
}
cmd_cur += cmd_length;
rdp_device.cmd_cur += cmd_length;
}
cmd_ptr = 0;
cmd_cur = 0;
rdp_device.cmd_ptr = 0;
rdp_device.cmd_cur = 0;
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
return interrupt_timer;
+6 -2
View File
@@ -2,6 +2,7 @@
#ifdef __cplusplus
#include <cstdint>
#include <stddef.h>
extern "C"
{
@@ -30,6 +31,8 @@ extern "C"
bool save_state;
bool load_state;
bool enable_speedlimiter;
bool lower_volume;
bool raise_volume;
} CALL_BACK;
void rdp_init(void *_window, GFX_INFO _gfx_info);
@@ -39,8 +42,9 @@ extern "C"
CALL_BACK rdp_check_callback();
uint64_t rdp_process_commands();
void rdp_new_processor(GFX_INFO _gfx_info);
void rdp_check_framebuffers(uint32_t address);
void rdp_full_sync();
size_t rdp_state_size();
void rdp_save_state(uint8_t *state);
void rdp_load_state(const uint8_t *state);
#ifdef __cplusplus
}
+1 -1
View File
@@ -28,7 +28,7 @@ std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
}
std::vector<const char *> extensionNames;
for (int i = 0; i < extensionCount; ++i)
for (unsigned int i = 0; i < extensionCount; ++i)
{
extensionNames.push_back(extensions[i]);
}
+2
View File
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.86.0"
+14 -11
View File
@@ -99,11 +99,11 @@ fn init_rng(device: &mut Device) {
device.rng = rand_chacha::ChaCha8Rng::seed_from_u64(rng_seed);
}
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
let test = u32::from_be_bytes(contents[0..4].try_into().unwrap());
if test == 0x80371240 {
// z64
contents
Some(contents)
} else if test == 0x37804012 {
// v64
let mut data: Vec<u8> = vec![0; contents.len()];
@@ -111,7 +111,7 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
let temp = u16::from_ne_bytes(contents[i..i + 2].try_into().unwrap());
data[i..i + 2].copy_from_slice(&temp.to_be_bytes());
}
return data;
Some(data)
} else if test == 0x40123780 {
// n64
let mut data: Vec<u8> = vec![0; contents.len()];
@@ -119,14 +119,13 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
let temp = u32::from_ne_bytes(contents[i..i + 4].try_into().unwrap());
data[i..i + 4].copy_from_slice(&temp.to_be_bytes());
}
return data;
Some(data)
} else {
let data: Vec<u8> = vec![];
data
None
}
}
pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
let mut contents = vec![];
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
let zip_file = fs::File::open(file_path).unwrap();
@@ -147,12 +146,12 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
}
} 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();
sevenz_rust2::SevenZReader::open(file_path, sevenz_rust2::Password::empty()).unwrap();
let mut found = false;
archive
.for_each_entries(
&mut |entry: &sevenz_rust::SevenZArchiveEntry, reader: &mut dyn std::io::Read| {
&mut |entry: &sevenz_rust2::SevenZArchiveEntry, reader: &mut dyn std::io::Read| {
let name = entry.name().to_ascii_lowercase();
if !found
&& (name.ends_with("z64") || name.ends_with("n64") || name.ends_with("v64"))
@@ -173,7 +172,11 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
contents = fs::read(file_path).expect("Should have been able to read the file");
}
swap_rom(contents)
if contents.is_empty() {
None
} else {
swap_rom(contents)
}
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -348,7 +351,7 @@ impl Device {
},
},
memory: memory::Memory {
fast_read: [unmapped::read_mem; 0x2000],
fast_read: [unmapped::read_mem_fast; 0x2000],
memory_map_read: [unmapped::read_mem; 0x2000],
memory_map_write: [unmapped::write_mem; 0x2000],
icache: [cache::ICache {
+1 -1
View File
@@ -27,7 +27,7 @@ pub struct AiDma {
pub duration: u64,
}
fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
fn get_remaining_dma_length(device: &device::Device) -> u64 {
if device.ai.fifo[0].duration == 0 {
return 0;
}
+36 -160
View File
@@ -30,54 +30,14 @@ pub fn icache_writeback(device: &mut device::Device, line_index: usize) {
let cache_address = ((device.memory.icache[line_index].tag
| (device.memory.icache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address,
device.memory.icache[line_index].words[0],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device.memory.icache[line_index].words[1],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device.memory.icache[line_index].words[2],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device.memory.icache[line_index].words[3],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x10,
device.memory.icache[line_index].words[4],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x14,
device.memory.icache[line_index].words[5],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x18,
device.memory.icache[line_index].words[6],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x1C,
device.memory.icache[line_index].words[7],
0xFFFFFFFF,
);
for i in 0..8 {
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device.memory.icache[line_index].words[i as usize],
0xFFFFFFFF,
);
}
}
pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
@@ -88,71 +48,17 @@ pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address:
let cache_address = ((device.memory.icache[line_index].tag
| (device.memory.icache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.icache[line_index].words[0] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[1] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[2] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[3] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[4] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x10,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[5] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x14,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[6] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x18,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[7] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x1C,
device::memory::AccessSize::Icache,
);
for i in 0..8 {
device.memory.icache[line_index].words[i as usize] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].instruction[0] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
device.memory.icache[line_index].instruction[1] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
device.memory.icache[line_index].instruction[2] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
device.memory.icache[line_index].instruction[3] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
device.memory.icache[line_index].instruction[4] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
device.memory.icache[line_index].instruction[5] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
device.memory.icache[line_index].instruction[6] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
device.memory.icache[line_index].instruction[7] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
device.memory.icache[line_index].instruction[i as usize] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[i as usize]);
}
}
pub fn icache_fetch(device: &mut device::Device, phys_address: u64) {
@@ -180,30 +86,15 @@ pub fn dcache_writeback(device: &mut device::Device, line_index: usize) {
let cache_address = ((device.memory.dcache[line_index].tag
| (device.memory.dcache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address,
device.memory.dcache[line_index].words[0],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device.memory.dcache[line_index].words[1],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device.memory.dcache[line_index].words[2],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device.memory.dcache[line_index].words[3],
0xFFFFFFFF,
);
for i in 0..4 {
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device.memory.dcache[line_index].words[i as usize],
0xFFFFFFFF,
);
}
}
fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
@@ -216,30 +107,15 @@ fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64
let cache_address = ((device.memory.dcache[line_index].tag
| (device.memory.dcache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.dcache[line_index].words[0] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[1] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[2] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[3] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device::memory::AccessSize::Dcache,
);
for i in 0..4 {
device.memory.dcache[line_index].words[i as usize] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device::memory::AccessSize::Dcache,
);
}
}
pub fn dcache_read(device: &mut device::Device, phys_address: u64) -> u32 {
+3 -3
View File
@@ -4,7 +4,7 @@ use sha2::{Digest, Sha256};
const CART_MASK: usize = 0xFFFFFFF;
fn read_cart_word(device: &mut device::Device, address: usize) -> u32 {
fn read_cart_word(device: &device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = [0; 4];
for i in 0..4 {
if let Some(value) = device
@@ -24,7 +24,7 @@ fn read_cart_word(device: &mut device::Device, address: usize) -> u32 {
}
pub fn read_mem_fast(
device: &mut device::Device,
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@@ -186,7 +186,7 @@ fn set_cic(device: &mut device::Device) {
}
_ => {
device.cart.cic_seed = 0x3F; // CicNus6102
println!("unknown IPL3 {}", hash)
//println!("unknown IPL3 {}", hash)
}
}
}
+5 -5
View File
@@ -57,10 +57,10 @@ pub fn process(device: &mut device::Device, channel: usize) {
JCMD_CONTROLLER_READ => {
let offset = device.pif.channels[channel].rx_buf.unwrap();
let input = if device.netplay.is_none() {
ui::input::get(&mut device.ui, channel)
ui::input::get(&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);
let local_input = ui::input::get(&device.ui, 0);
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
}
@@ -163,9 +163,9 @@ pub fn pak_switch_event(device: &mut device::Device) {
if channel.change_pak != PakType::None {
//stop rumble if it is on
if device.netplay.is_none() {
device::ui::input::set_rumble(&mut device.ui, i, 0);
device::ui::input::set_rumble(&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);
device::ui::input::set_rumble(&device.ui, 0, 0);
}
let new_pak_type = match channel.change_pak {
@@ -202,7 +202,7 @@ pub fn pak_switch_event(device: &mut device::Device) {
pak_type: new_pak_type,
});
}
ui::audio::play_pak_switch(&mut device.ui, new_pak_type);
ui::audio::play_pak_switch(&device.ui, new_pak_type);
channel.change_pak = PakType::None;
}
}
+8 -8
View File
@@ -29,7 +29,7 @@ pub enum CartType {
}
fn write_mbc1(
pif_ram: &mut [u8],
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
@@ -66,7 +66,7 @@ fn write_mbc1(
fn read_mbc1(
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@@ -100,7 +100,7 @@ fn read_mbc1(
}
fn write_mbc3(
pif_ram: &mut [u8],
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
@@ -161,7 +161,7 @@ fn write_mbc3(
fn read_mbc3(
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@@ -213,7 +213,7 @@ fn read_mbc3(
}
fn write_mbc5(
pif_ram: &mut [u8],
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
@@ -247,7 +247,7 @@ fn write_mbc5(
fn read_mbc5(
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@@ -278,7 +278,7 @@ fn read_mbc5(
pub fn read(
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@@ -299,7 +299,7 @@ pub fn read(
}
pub fn write(
pif_ram: &mut [u8],
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
+2 -2
View File
@@ -16,9 +16,9 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
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);
device::ui::input::set_rumble(&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);
device::ui::input::set_rumble(&device.ui, 0, rumble);
}
}
}
+2 -2
View File
@@ -54,7 +54,7 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
0xC..=0xF => {
device::controller::gbcart::read(
&mut device.pif.ram,
&mut pak.cart,
&pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
@@ -119,7 +119,7 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
}
0xC..=0xF => {
device::controller::gbcart::write(
&mut device.pif.ram,
&device.pif.ram,
&mut pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
+2 -2
View File
@@ -36,9 +36,9 @@ pub fn create_event_at(device: &mut device::Device, name: usize, when: u64) {
set_next_event(device);
}
pub fn get_event(device: &mut device::Device, name: usize) -> Option<&mut Event> {
pub fn get_event(device: &device::Device, name: usize) -> Option<&Event> {
if device.cpu.events[name].enabled {
return Some(&mut device.cpu.events[name]);
return Some(&device.cpu.events[name]);
}
None
}
+2 -2
View File
@@ -40,8 +40,8 @@ pub enum AccessSize {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Memory {
#[serde(skip, default = "savestates::default_memory_read")]
pub fast_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
#[serde(skip, default = "savestates::default_memory_read_fast")]
pub fast_read: [fn(&device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
#[serde(skip, default = "savestates::default_memory_read")]
pub memory_map_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000],
#[serde(skip, default = "savestates::default_memory_write")]
+1 -2
View File
@@ -1,4 +1,4 @@
use crate::{device, ui};
use crate::device;
use rand_chacha::rand_core::RngCore;
const PI_DRAM_ADDR_REG: u32 = 0;
@@ -83,7 +83,6 @@ fn dma_write(device: &mut device::Device) {
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG as usize] & 0xFFFFFE;
let mut length = (device.pi.regs[PI_WR_LEN_REG as usize] & 0xFFFFFF) + 1;
ui::video::check_framebuffers(dram_addr);
/* PI seems to treat the first 128 bytes differently, see https://n64brew.dev/wiki/Peripheral_Interface#Unaligned_DMA_transfer */
if length >= 0x7f && (length & 1) != 0 {
+20 -7
View File
@@ -232,6 +232,23 @@ pub fn connect_pif_channels(device: &mut device::Device) {
device.pif.channels[4].process = Some(device::cart::process)
}
fn get_default_handler(device: &device::Device) -> device::controller::PakHandler {
if device.ui.game_id == "NCT" {
// Chameleon Twist does not support the mempak
device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
}
} else {
device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
}
}
}
pub fn init(device: &mut device::Device) {
if device.cart.pal {
device.pif.rom = rom::PAL_PIF_ROM;
@@ -241,11 +258,7 @@ pub fn init(device: &mut device::Device) {
device.pif.ram[0x26] = device.cart.cic_seed;
device.pif.ram[0x27] = device.cart.cic_seed;
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
let default_handler = get_default_handler(device);
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
@@ -258,13 +271,13 @@ pub fn init(device: &mut device::Device) {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
if device.transferpaks[i].cart.rom.is_empty() {
device.pif.channels[i].pak_handler = Some(mempak_handler);
device.pif.channels[i].pak_handler = Some(default_handler);
} else {
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
}
} 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].pak_handler = Some(default_handler);
}
}
if device.ui.config.input.emulate_vru {
+1 -4
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use std::alloc::{Layout, alloc_zeroed};
//const RDRAM_CONFIG_REG: u32 = 0;
@@ -24,7 +23,7 @@ pub struct Rdram {
}
pub fn read_mem_fast(
device: &mut device::Device,
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@@ -47,7 +46,6 @@ pub fn read_mem(
);
let masked_address = address as usize & RDRAM_MASK;
if address < device.rdram.size as u64 {
ui::video::check_framebuffers(masked_address as u32);
u32::from_ne_bytes(
device.rdram.mem[masked_address..masked_address + 4]
.try_into()
@@ -60,7 +58,6 @@ pub fn read_mem(
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if address < device.rdram.size as u64 {
ui::video::check_framebuffers(address as u32);
let mut data = u32::from_ne_bytes(
device.rdram.mem[address as usize..(address + 4) as usize]
.try_into()
+1 -3
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
const SP_MEM_ADDR_REG: u32 = 0;
const SP_DRAM_ADDR_REG: u32 = 1;
@@ -89,7 +88,7 @@ pub struct Rsp {
}
pub fn read_mem_fast(
device: &mut device::Device,
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@@ -147,7 +146,6 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
let mut dram_addr = dma.dramaddr & 0xfffff8;
let offset = dma.memaddr & 0x1000;
ui::video::check_framebuffers(dram_addr);
if dma.dir == DmaDir::Read {
let mut j = 0;
while j < count {
+17 -23
View File
@@ -317,10 +317,9 @@ pub fn lw(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 4];
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
for (i, item) in w.iter_mut().enumerate() {
*item = device.rsp.mem[(address as usize + i) & 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)
@@ -349,10 +348,9 @@ pub fn lwu(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 4];
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
for (i, item) in w.iter_mut().enumerate() {
*item = device.rsp.mem[(address as usize + i) & 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)
@@ -378,14 +376,10 @@ pub fn sw(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
device.rsp.mem[address as usize & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 24) as u8;
device.rsp.mem[(address as usize + 1) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 16) as u8;
device.rsp.mem[(address as usize + 2) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8;
device.rsp.mem[(address as usize + 3) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize]) as u8;
for i in 0..4 {
device.rsp.mem[(address as usize + i) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> ((3 - i) * 8)) as u8;
}
}
pub fn sll(device: &mut device::Device, opcode: u32) {
@@ -606,19 +600,19 @@ pub fn mfc2(device: &mut device::Device, opcode: u32) {
pub fn cfc2(device: &mut device::Device, opcode: u32) {
let hi;
let lo;
let mut zero = unsafe { _mm_setzero_si128() };
let zero = unsafe { _mm_setzero_si128() };
match rd(opcode) & 3 {
0x00 => {
hi = &mut device.rsp.cpu.vcoh;
lo = &mut device.rsp.cpu.vcol;
hi = &device.rsp.cpu.vcoh;
lo = &device.rsp.cpu.vcol;
}
0x01 => {
hi = &mut device.rsp.cpu.vcch;
lo = &mut device.rsp.cpu.vccl;
hi = &device.rsp.cpu.vcch;
lo = &device.rsp.cpu.vccl;
}
0x02 | 0x03 => {
hi = &mut zero;
lo = &mut device.rsp.cpu.vce;
hi = &zero;
lo = &device.rsp.cpu.vce;
}
_ => {
panic!("unknown cfc2")
+2 -2
View File
@@ -146,7 +146,7 @@ pub fn probe(device: &mut device::Device) {
}
fn tlb_unmap(device: &mut device::Device, index: u64) {
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
let e = &device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0 {
let mut i = e.start_even;
@@ -184,7 +184,7 @@ fn tlb_unmap(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];
let e = &device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0
&& e.start_even < e.end_even
+10 -2
View File
@@ -1,7 +1,7 @@
use crate::device;
pub fn read_mem(
_device: &mut device::Device,
pub fn read_mem_fast(
_device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@@ -9,4 +9,12 @@ pub fn read_mem(
value | (value << 16)
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
access_size: device::memory::AccessSize,
) -> u32 {
read_mem_fast(device, address, access_size)
}
pub fn write_mem(_device: &mut device::Device, _address: u64, _value: u32, _mask: u32) {}
+2 -1
View File
@@ -86,7 +86,8 @@ fn set_current_line(device: &mut device::Device) {
}
/* update current field */
device.vi.regs[VI_CURRENT_REG as usize] =
(device.vi.regs[VI_CURRENT_REG as usize] & !1) | device.vi.field
(device.vi.regs[VI_CURRENT_REG as usize] & !1) | device.vi.field;
ui::video::set_register(VI_CURRENT_REG, device.vi.regs[VI_CURRENT_REG as usize])
}
pub fn read_regs(
+13 -25
View File
@@ -151,36 +151,24 @@ async fn main() {
}
} else if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
let rom_contents = device::get_rom_contents(file_path);
if rom_contents.is_empty() {
if let Some(rom_contents) = device::get_rom_contents(file_path) {
let handle = std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let mut device = device::Device::new();
let overclock = device.ui.config.emulation.overclock;
device::run_game(&mut device, rom_contents, args.fullscreen, overclock);
})
.unwrap();
handle.join().unwrap();
} else {
println!("Could not read rom file");
return;
}
let handle = std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let mut device = device::Device::new();
let overclock = device.ui.config.emulation.overclock;
device::run_game(&mut device, rom_contents, args.fullscreen, overclock);
})
.unwrap();
handle.join().unwrap();
} else {
let options = eframe::NativeOptions {
wgpu_options: eframe::egui_wgpu::WgpuConfiguration {
wgpu_setup: eframe::egui_wgpu::WgpuSetup::CreateNew(
eframe::egui_wgpu::WgpuSetupCreateNew {
instance_descriptor: eframe::egui_wgpu::wgpu::InstanceDescriptor {
backends: eframe::wgpu::Backends::GL,
..Default::default()
},
..Default::default()
},
),
..Default::default()
},
viewport: eframe::egui::ViewportBuilder::default()
.with_inner_size([854.0, 480.0])
.with_icon(
+3 -3
View File
@@ -144,7 +144,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::Inpu
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, &mut device.ui); // we execute process_incoming before request_input so that we send an accurate buffer count
process_incoming(netplay, &device.ui); // 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);
@@ -155,7 +155,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::Inpu
.remove(&netplay.player_data[channel].count);
if std::time::Instant::now() > timeout {
ui::audio::play_netplay_audio(&mut device.ui, NETPLAY_ERROR_LOST_CONNECTION);
ui::audio::play_netplay_audio(&device.ui, NETPLAY_ERROR_LOST_CONNECTION);
input = Some(InputEvent {
input: 0,
plugin: 0,
@@ -186,7 +186,7 @@ fn request_input(netplay: &Netplay, channel: usize) {
netplay.udp_socket.send(&request).unwrap();
}
fn process_incoming(netplay: &mut Netplay, ui: &mut ui::Ui) {
fn process_incoming(netplay: &mut Netplay, ui: &ui::Ui) {
let mut buf: [u8; 1024] = [0; 1024];
while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) {
match buf[0] {
+100 -100
View File
@@ -70,7 +70,8 @@ where
}
pub fn create_savestate(device: &device::Device) {
ui::video::full_sync();
let mut rdp_state: Vec<u8> = vec![0; ui::video::state_size()];
ui::video::save_state(rdp_state.as_mut_ptr());
let data: &[(&[u8], &str)] = &[
(&postcard::to_stdvec(device).unwrap(), "device"),
@@ -78,6 +79,7 @@ pub fn create_savestate(device: &device::Device) {
&postcard::to_stdvec(&device.ui.storage.saves).unwrap(),
"saves",
),
(&rdp_state, "rdp_state"),
];
let compressed_file = ui::storage::compress_file(data);
std::fs::write(
@@ -88,116 +90,109 @@ pub fn create_savestate(device: &device::Device) {
}
pub fn load_savestate(device: &mut device::Device) {
let savestate = std::fs::read(&mut device.ui.storage.paths.savestate_file_path);
let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path);
if savestate.is_ok() {
let device_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "device");
let save_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "saves");
let state: device::Device = postcard::from_bytes(&device_bytes).unwrap();
let rdp_state = ui::storage::decompress_file(savestate.as_ref().unwrap(), "rdp_state");
if let Ok(state) = postcard::from_bytes::<device::Device>(&device_bytes) {
device.ui.storage.saves = postcard::from_bytes(&save_bytes).unwrap();
device.ui.storage.saves = postcard::from_bytes(&save_bytes).unwrap();
device.cpu = state.cpu;
device.pif = state.pif;
device.cpu = state.cpu;
device.pif = state.pif;
let rom = device.cart.rom.clone();
device.cart = state.cart;
device.cart.rom = rom;
let rom = device.cart.rom.clone();
device.cart = state.cart;
device.cart.rom = rom;
device.memory = state.memory;
device.rsp = state.rsp;
device.rdp = state.rdp;
device.rdram = state.rdram;
device.mi = state.mi;
device.pi = state.pi;
device.vi = state.vi;
device.ai = state.ai;
device.si = state.si;
device.ri = state.ri;
device.vru = state.vru;
device.memory = state.memory;
device.rsp = state.rsp;
device.rdp = state.rdp;
device.rdram = state.rdram;
device.mi = state.mi;
device.pi = state.pi;
device.vi = state.vi;
device.ai = state.ai;
device.si = state.si;
device.ri = state.ri;
device.vru = state.vru;
let mut tpak_rom = [vec![], vec![], vec![], vec![]];
for (i, item) in tpak_rom.iter_mut().enumerate() {
*item = device.transferpaks[i].cart.rom.clone();
}
device.transferpaks = state.transferpaks;
for (i, item) in tpak_rom.iter().enumerate() {
device.transferpaks[i].cart.rom = item.clone();
}
let mut tpak_rom = [vec![], vec![], vec![], vec![]];
for (i, item) in tpak_rom.iter_mut().enumerate() {
*item = device.transferpaks[i].cart.rom.clone();
}
device.transferpaks = state.transferpaks;
for (i, item) in tpak_rom.iter().enumerate() {
device.transferpaks[i].cart.rom = item.clone();
}
device::memory::init(device);
device::vi::set_expected_refresh_rate(device);
device::cpu::map_instructions(device);
device::cop0::map_instructions(device);
device::cop1::map_instructions(device);
device::cop2::map_instructions(device);
device::rsp_cpu::map_instructions(device);
device::memory::init(device);
device::vi::set_expected_refresh_rate(device);
device::cpu::map_instructions(device);
device::cop0::map_instructions(device);
device::cop1::map_instructions(device);
device::cop2::map_instructions(device);
device::rsp_cpu::map_instructions(device);
let mut mem_addr = 0x1000;
while mem_addr < 0x2000 {
let data =
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
device::rsp_cpu::decode_opcode(device, data);
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
mem_addr += 4;
}
let mut mem_addr = 0x1000;
while mem_addr < 0x2000 {
let data =
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
device::rsp_cpu::decode_opcode(device, data);
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
mem_addr += 4;
}
for line_index in 0..512 {
device.memory.icache[line_index].instruction[0] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
device.memory.icache[line_index].instruction[1] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
device.memory.icache[line_index].instruction[2] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
device.memory.icache[line_index].instruction[3] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
device.memory.icache[line_index].instruction[4] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
device.memory.icache[line_index].instruction[5] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
device.memory.icache[line_index].instruction[6] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
device.memory.icache[line_index].instruction[7] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
}
device::pif::connect_pif_channels(device);
for i in 0..4 {
if device.pif.channels[i].pak_handler.is_some() {
if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::RumblePak
{
let rumblepak_handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::MemPak
{
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
device.pif.channels[i].pak_handler = Some(mempak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::TransferPak
{
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: device::controller::PakType::TransferPak,
};
device.pif.channels[i].pak_handler = Some(tpak_handler);
for line_index in 0..512 {
for i in 0..8 {
device.memory.icache[line_index].instruction[i] = device::cpu::decode_opcode(
device,
device.memory.icache[line_index].words[i],
);
}
}
}
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::load_state(device);
device::pif::connect_pif_channels(device);
for i in 0..4 {
if device.pif.channels[i].pak_handler.is_some() {
if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::RumblePak
{
let rumblepak_handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::MemPak
{
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
device.pif.channels[i].pak_handler = Some(mempak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::TransferPak
{
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: device::controller::PakType::TransferPak,
};
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
}
}
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::load_state(device, rdp_state.as_ptr());
} else {
println!("Failed to load savestate");
}
}
}
@@ -216,11 +211,16 @@ where
[device::cop0::reserved; N]
}
pub fn default_memory_read_fast()
-> [fn(&device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::unmapped::read_mem_fast; 0x2000]
}
pub fn default_memory_read()
-> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::rdram::read_mem; 0x2000]
[device::unmapped::read_mem; 0x2000]
}
pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] {
[device::rdram::write_mem; 0x2000]
[device::unmapped::write_mem; 0x2000]
}
+11 -7
View File
@@ -17,11 +17,11 @@ pub struct Audio {
pub event_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub audio_device: u32,
pub event_audio: audio::EventAudio,
pub gain: f32,
}
pub struct Input {
pub num_joysticks: i32,
pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
pub joysticks: Vec<sdl3_sys::joystick::SDL_JoystickID>,
pub keyboard_state: *const bool,
pub controllers: [input::Controllers; 4],
}
@@ -53,7 +53,6 @@ impl Drop for Ui {
fn drop(&mut self) {
if self.with_sdl {
unsafe {
sdl3_sys::stdinc::SDL_free(self.input.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
}
}
@@ -97,7 +96,7 @@ pub fn get_dirs() -> Dirs {
}
impl Ui {
fn construct_ui(num_joysticks: i32, joysticks: *mut u32, with_sdl: bool) -> Ui {
fn construct_ui(joysticks: Vec<u32>, with_sdl: bool) -> Ui {
let dirs = get_dirs();
Ui {
@@ -125,7 +124,6 @@ impl Ui {
},
],
keyboard_state: std::ptr::null_mut(),
num_joysticks,
joysticks,
},
storage: Storage {
@@ -188,6 +186,7 @@ impl Ui {
audio_stream: std::ptr::null_mut(),
event_audio_stream: std::ptr::null_mut(),
audio_device: 0,
gain: 1.0,
},
video: Video {
window: std::ptr::null_mut(),
@@ -199,7 +198,7 @@ impl Ui {
}
pub fn default() -> Ui {
Self::construct_ui(0, std::ptr::null_mut(), false)
Self::construct_ui(vec![], false)
}
pub fn new() -> Ui {
@@ -209,6 +208,11 @@ impl Ui {
if joysticks.is_null() {
panic!("Could not get joystick list");
}
Self::construct_ui(num_joysticks, joysticks, true)
let mut joystick_vec = vec![];
for i in 0..num_joysticks {
joystick_vec.push(unsafe { *joysticks.add(i as usize) });
}
unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }
Self::construct_ui(joystick_vec, true)
}
}
+32 -5
View File
@@ -51,7 +51,8 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
return;
}
if !unsafe {
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.audio_stream)
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain)
&& sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.audio_stream)
} {
panic!("Could not bind audio stream");
}
@@ -65,7 +66,11 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
ui.audio.event_audio_stream =
unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
if !unsafe {
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.event_audio_stream)
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain)
&& sdl3_sys::audio::SDL_BindAudioStream(
ui.audio.audio_device,
ui.audio.event_audio_stream,
)
} {
panic!("Could not bind audio stream");
}
@@ -86,7 +91,29 @@ pub fn close(ui: &mut ui::Ui) {
}
}
pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) {
pub fn lower_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) - 0.05;
if ui.audio.gain < 0.0 {
ui.audio.gain = 0.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain);
}
}
pub fn raise_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) + 0.05;
if ui.audio.gain > 2.0 {
ui.audio.gain = 2.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain);
}
}
pub fn play_netplay_audio(ui: &ui::Ui, error: u32) {
if ui.audio.event_audio_stream.is_null() {
return;
}
@@ -118,7 +145,7 @@ pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) {
}
}
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
pub fn play_pak_switch(ui: &ui::Ui, pak: device::controller::PakType) {
if ui.audio.event_audio_stream.is_null() {
return;
}
@@ -140,7 +167,7 @@ pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
}
}
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
if device.ui.audio.audio_stream.is_null() {
return;
}
+35 -44
View File
@@ -61,11 +61,9 @@ fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
let mut controllers: Vec<String> = vec![];
for offset in 0..game_ui.input.num_joysticks as isize {
for joystick in game_ui.input.joysticks.iter() {
let name = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID(
*(game_ui.input.joysticks.offset(offset)),
))
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID(*joystick))
};
controllers.push(name.to_string_lossy().to_string());
}
@@ -76,13 +74,11 @@ pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<String> {
let mut controller_paths: Vec<String> = vec![];
for offset in 0..game_ui.input.num_joysticks as isize {
for joystick in game_ui.input.joysticks.iter() {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(game_ui.input.joysticks.offset(offset)),
))
.to_string_lossy()
.to_string()
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(*joystick))
.to_string_lossy()
.to_string()
};
controller_paths.push(path);
}
@@ -193,37 +189,33 @@ impl Drop for GopherEguiApp {
}
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 || {
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
};
})
});
egui::Window::new("Configure Input 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 || {
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) {
@@ -449,16 +441,15 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context, enable_overclock:
device.vru_window.gui_ctx = Some(gui_ctx);
}
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
if let Some(rom_contents) = device::get_rom_contents(file.unwrap().path()) {
device::run_game(
&mut device,
rom_contents,
fullscreen,
enable_overclock,
);
} else {
println!("Could not read rom file");
}
}
let result = std::fs::remove_file(running_file);
+2 -3
View File
@@ -334,7 +334,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
protected: None,
port: None,
features: Some(features),
buffer_target: Some(2),
buffer_target: None,
}),
};
let (mut socket, _response) =
@@ -430,8 +430,7 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.path());
if !rom_contents.is_empty() {
if let Some(rom_contents) = device::get_rom_contents(file.path()) {
let hash = device::cart::rom::calculate_hash(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);
+18 -17
View File
@@ -276,7 +276,7 @@ fn set_buttons_from_controller(
}
}
pub fn set_rumble(ui: &mut ui::Ui, channel: usize, rumble: u8) {
pub fn set_rumble(ui: &ui::Ui, channel: usize, rumble: u8) {
if !ui.input.controllers[channel].rumble {
return;
}
@@ -334,7 +334,7 @@ fn change_paks(
pressed
}
pub fn get(ui: &mut ui::Ui, channel: usize) -> InputData {
pub fn get(ui: &ui::Ui, channel: usize) -> InputData {
let profile_name = ui.config.input.input_profile_binding[channel].clone();
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
let mut keys = 0;
@@ -393,10 +393,10 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> InputData {
}
pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) {
if controller < ui.input.num_joysticks {
if controller < ui.input.joysticks.len() as i32 {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(ui.input.joysticks.offset(controller as isize)),
ui.input.joysticks[controller as usize],
))
.to_string_lossy()
.to_string()
@@ -448,16 +448,14 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
let mut open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick> = Vec::new();
let mut open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad> = Vec::new();
for offset in 0..ui.input.num_joysticks as isize {
for joystick in ui.input.joysticks.iter() {
if !dinput {
let controller =
unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*ui.input.joysticks.offset(offset)) };
let controller = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*joystick) };
if !controller.is_null() {
open_controllers.push(controller);
}
} else {
let joystick =
unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*ui.input.joysticks.offset(offset)) };
let joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*joystick) };
if !joystick.is_null() {
open_joysticks.push(joystick);
}
@@ -482,8 +480,6 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
if !unsafe { sdl3_sys::video::SDL_ShowWindow(window) } {
panic!("Could not show window")
}
let font =
rusttype::Font::try_from_bytes(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
let key_labels: [(&str, usize); PROFILE_SIZE] = [
("A", A_BUTTON),
@@ -540,6 +536,10 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
id: 0,
axis: 0,
};
let font =
ab_glyph::FontRef::try_from_slice(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
for (key, value) in key_labels.iter() {
let mut event: sdl3_sys::events::SDL_Event = Default::default();
while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } {} // clear events
@@ -859,24 +859,25 @@ pub fn init(ui: &mut ui::Ui) {
panic!("Could not get keyboard state");
}
let mut taken = [false; 4];
for i in 0..4 {
let controller_assignment = &ui.config.input.controller_assignment[i];
if controller_assignment.is_some() && ui.config.input.controller_enabled[i] {
let mut joystick_id = 0;
let assigned_path = controller_assignment.as_ref().unwrap();
for offset in 0..ui.input.num_joysticks as isize {
for joystick in ui.input.joysticks.iter() {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(ui.input.joysticks.offset(offset)),
*joystick,
))
.to_string_lossy()
.to_string()
};
if path == *assigned_path && !taken[offset as usize] {
joystick_id = unsafe { *(ui.input.joysticks.offset(offset)) };
taken[offset as usize] = true;
if path == *assigned_path
&& unsafe { sdl3_sys::joystick::SDL_GetJoystickFromID(*joystick) }.is_null()
&& unsafe { sdl3_sys::gamepad::SDL_GetGamepadFromID(*joystick) }.is_null()
{
joystick_id = *joystick;
break;
}
}
+6 -6
View File
@@ -196,27 +196,27 @@ pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
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.storage.paths.eep_file_path);
let eep = std::fs::read(&ui.storage.paths.eep_file_path);
if eep.is_ok() {
ui.storage.saves.eeprom.data = eep.unwrap();
}
let sra = std::fs::read(&mut ui.storage.paths.sra_file_path);
let sra = std::fs::read(&ui.storage.paths.sra_file_path);
if sra.is_ok() {
ui.storage.saves.sram.data = sra.unwrap();
}
let fla = std::fs::read(&mut ui.storage.paths.fla_file_path);
let fla = std::fs::read(&ui.storage.paths.fla_file_path);
if fla.is_ok() {
ui.storage.saves.flash.data = fla.unwrap();
}
let mempak = std::fs::read(&mut ui.storage.paths.pak_file_path);
let mempak = std::fs::read(&ui.storage.paths.pak_file_path);
if mempak.is_ok() {
ui.storage.saves.mempak.data = mempak.unwrap();
}
let sdcard = std::fs::read(&mut ui.storage.paths.sdcard_file_path);
let sdcard = std::fs::read(&ui.storage.paths.sdcard_file_path);
if sdcard.is_ok() {
ui.storage.saves.sdcard.data = sdcard.unwrap();
}
let romsave = std::fs::read(&mut ui.storage.paths.romsave_file_path);
let romsave = std::fs::read(&ui.storage.paths.romsave_file_path);
if romsave.is_ok() {
ui.storage.saves.romsave.data =
postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
+49 -19
View File
@@ -1,6 +1,7 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs"));
use crate::{device, ui};
use ab_glyph::{Font, ScaleFont};
pub fn init(device: &mut device::Device) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
@@ -41,7 +42,13 @@ pub fn init(device: &mut device::Device) {
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.video.window) } {
panic!("Could not show window");
}
unsafe { sdl3_sys::everything::SDL_HideCursor() };
unsafe {
sdl3_sys::everything::SDL_HideCursor();
sdl3_sys::everything::SDL_SetHint(
sdl3_sys::everything::SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
std::ffi::CString::new("1").unwrap().as_ptr(),
);
}
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
@@ -74,15 +81,15 @@ pub fn update_screen() {
unsafe { rdp_update_screen() }
}
pub fn check_framebuffers(address: u32) {
unsafe { rdp_check_framebuffers(address) }
pub fn state_size() -> usize {
unsafe { rdp_state_size() }
}
pub fn full_sync() {
unsafe { rdp_full_sync() }
pub fn save_state(rdp_state: *mut u8) {
unsafe { rdp_save_state(rdp_state) }
}
pub fn load_state(device: &mut device::Device) {
pub fn load_state(device: &mut device::Device, rdp_state: *const u8) {
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
DMEM: device.rsp.mem.as_mut_ptr(),
@@ -100,6 +107,10 @@ pub fn load_state(device: &mut device::Device) {
};
unsafe {
rdp_new_processor(gfx_info);
rdp_load_state(rdp_state);
for reg in 0..device::vi::VI_REGS_COUNT {
rdp_set_vi_register(reg, device.vi.regs[reg as usize])
}
}
}
@@ -114,6 +125,12 @@ pub fn check_callback(device: &mut device::Device) {
}
device.vi.enable_speed_limiter = callback.enable_speedlimiter;
}
if callback.lower_volume {
ui::audio::lower_audio_volume(&mut device.ui);
} else if callback.raise_volume {
ui::audio::raise_audio_volume(&mut device.ui);
}
}
pub fn set_register(reg: u32, value: u32) {
@@ -126,12 +143,11 @@ pub fn process_rdp_list() -> u64 {
unsafe { rdp_process_commands() }
}
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);
pub fn draw_text(
text: &str,
renderer: *mut sdl3_sys::render::SDL_Renderer,
font: &ab_glyph::FontRef,
) {
// Clear the canvas
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
@@ -144,12 +160,20 @@ pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font
sdl3_sys::render::SDL_RenderClear(renderer);
};
for glyph in font.layout(text, scale, offset) {
if let Some(bb) = glyph.pixel_bounding_box() {
glyph.draw(|x, y, v| {
let x = x as i32 + bb.min.x;
let y = y as i32 + bb.min.y + (240 - text_size);
if v > 0.5 {
let text_size = 40.0;
let (mut w, mut h) = (0, 0);
unsafe { sdl3_sys::render::SDL_GetRenderOutputSize(renderer, &mut w, &mut h) };
let x_start = 20.0;
let y_start = (h / 2) as f32;
let mut x_offset = 0.0;
for c in text.chars() {
let q_glyph_id = font.glyph_id(c);
let q_glyph = q_glyph_id.with_scale(text_size);
if let Some(q) = font.outline_glyph(q_glyph) {
q.draw(|x, y, c| {
if c > 0.5 {
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
renderer,
@@ -158,11 +182,17 @@ pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font
255,
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
);
sdl3_sys::render::SDL_RenderPoint(renderer, x as f32, y as f32);
sdl3_sys::render::SDL_RenderPoint(
renderer,
x_start + x_offset + x as f32 - q.px_bounds().width()
+ q.px_bounds().max.x,
y_start + y as f32 - q.px_bounds().height() + q.px_bounds().max.y,
);
};
}
});
}
x_offset += font.as_scaled(text_size).h_advance(q_glyph_id);
}
// Present the canvas