Compare commits

...

41 commits

Author SHA1 Message Date
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
35 changed files with 819 additions and 719 deletions

18
.github/workflows/sec.yml vendored Normal file
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 }}

445
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "1.0.13"
version = "1.0.15"
edition = "2024"
rust-version = "1.85"
@ -8,8 +8,8 @@ rust-version = "1.85"
[dependencies]
dirs = "6.0"
zip = "2.2"
governor = "0.8"
zip = "2.5"
governor = "0.10"
sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
@ -17,7 +17,7 @@ serde_json = "1.0"
serde-big-array = "0.5"
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"] }

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

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();
@ -102,7 +109,9 @@ fn main() {
.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");

View file

@ -52,42 +52,48 @@ enum vi_registers
VI_REGS_COUNT
};
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 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 texture_address;
uint32_t framebuffer_pixel_size;
uint32_t framebuffer_width;
uint32_t texture_pixel_size;
uint32_t texture_width;
uint32_t framebuffer_height;
uint32_t framebuffer_size;
uint32_t depthbuffer_size;
uint8_t depthbuffer_enabled;
} FrameBufferInfo;
typedef struct
{
uint32_t cmd_data[0x00040000 >> 2];
int cmd_cur;
int cmd_ptr;
uint32_t region;
FrameBufferInfo frame_buffer_info;
} RDP_DEVICE;
static SDL_Window *window;
static RDP::CommandProcessor *processor;
static SDL_WSIPlatform *wsi_platform;
static WSI *wsi;
static RDP_DEVICE rdp_device;
static bool crop_letterbox;
static CALL_BACK callback;
static GFX_INFO gfx_info;
static const uint32_t *fragment_spirv;
static size_t fragment_size;
std::vector<bool> rdram_dirty;
uint64_t sync_signal;
typedef struct
{
float SourceSize[4];
float OutputSize[4];
} Push;
static std::vector<bool> rdram_dirty;
static uint64_t sync_signal;
static FrameBufferInfo frame_buffer_info;
static const unsigned cmd_len_lut[64] = {
1,
1,
@ -195,6 +201,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 +217,14 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
void rdp_new_processor(GFX_INFO _gfx_info)
{
memset(&frame_buffer_info, 0, sizeof(FrameBufferInfo));
gfx_info = _gfx_info;
sync_signal = 0;
rdram_dirty.assign(gfx_info.RDRAM_SIZE / 8, false);
rdp_device.frame_buffer_info.framebuffer_address = rdram_dirty.size();
rdp_device.frame_buffer_info.depthbuffer_address = rdram_dirty.size();
rdp_device.frame_buffer_info.texture_address = rdram_dirty.size();
gfx_info = _gfx_info;
if (processor)
{
delete processor;
@ -449,6 +464,8 @@ 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;
}
@ -462,29 +479,61 @@ void rdp_check_framebuffers(uint32_t address)
}
}
void rdp_full_sync()
size_t rdp_state_size()
{
processor->wait_for_timeline(processor->signal_timeline());
return sizeof(RDP_DEVICE);
}
void calculate_buffer_size()
void rdp_save_state(uint8_t *state)
{
switch (frame_buffer_info.framebuffer_pixel_size)
processor->wait_for_timeline(processor->signal_timeline());
memcpy(state, &rdp_device, sizeof(RDP_DEVICE));
}
void rdp_load_state(const uint8_t *state)
{
memcpy(&rdp_device, state, sizeof(RDP_DEVICE));
}
uint32_t texture_size(uint32_t area)
{
switch (rdp_device.frame_buffer_info.texture_pixel_size)
{
case 0:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height / 2) >> 3;
break;
return (area / 2) >> 3;
case 1:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height) >> 3;
break;
return (area) >> 3;
case 2:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
break;
return (area * 2) >> 3;
case 3:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 4) >> 3;
break;
return (area * 4) >> 3;
default:
printf("Invalid texture pixel size: %u\n", rdp_device.frame_buffer_info.texture_pixel_size);
return 0;
}
frame_buffer_info.depthbuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
}
uint32_t framebuffer_size()
{
switch (rdp_device.frame_buffer_info.framebuffer_pixel_size)
{
case 0:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height / 2) >> 3;
case 1:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) >> 3;
case 2:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height * 2) >> 3;
case 3:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height * 4) >> 3;
default:
printf("Invalid framebuffer pixel size: %u\n", rdp_device.frame_buffer_info.framebuffer_pixel_size);
return 0;
}
}
uint32_t depthbuffer_size()
{
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height * 2) >> 3;
}
uint64_t rdp_process_commands()
@ -498,7 +547,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;
@ -507,10 +556,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
@ -524,83 +573,118 @@ 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])
case RDP::Op::FillTriangle:
case RDP::Op::FillZBufferTriangle:
case RDP::Op::TextureTriangle:
case RDP::Op::TextureZBufferTriangle:
case RDP::Op::ShadeTriangle:
case RDP::Op::ShadeZBufferTriangle:
case RDP::Op::ShadeTextureTriangle:
case RDP::Op::ShadeTextureZBufferTriangle:
case RDP::Op::TextureRectangle:
case RDP::Op::TextureRectangleFlip:
case RDP::Op::FillRectangle:
if (!rdram_dirty[rdp_device.frame_buffer_info.framebuffer_address])
{
std::fill_n(rdram_dirty.begin() + frame_buffer_info.framebuffer_address, frame_buffer_info.framebuffer_size, true);
std::fill_n(rdram_dirty.begin() + rdp_device.frame_buffer_info.framebuffer_address, framebuffer_size(), true);
}
if (frame_buffer_info.depthbuffer_enabled && !rdram_dirty[frame_buffer_info.depthbuffer_address])
if (rdp_device.frame_buffer_info.depthbuffer_address < rdram_dirty.size() && !rdram_dirty[rdp_device.frame_buffer_info.depthbuffer_address])
{
std::fill_n(rdram_dirty.begin() + frame_buffer_info.depthbuffer_address, frame_buffer_info.depthbuffer_size, true);
std::fill_n(rdram_dirty.begin() + rdp_device.frame_buffer_info.depthbuffer_address, depthbuffer_size(), true);
}
}
else if (RDP::Op(command) == RDP::Op::SetOtherModes)
break;
case RDP::Op::LoadTLut:
case RDP::Op::LoadTile:
if (!rdram_dirty[rdp_device.frame_buffer_info.texture_address])
{
uint32_t lower_right_t = (w2 & 0xFFF) >> 2;
std::fill_n(rdram_dirty.begin() +
rdp_device.frame_buffer_info.texture_address,
texture_size(rdp_device.frame_buffer_info.texture_width * lower_right_t), true);
}
break;
case RDP::Op::LoadBlock:
{
frame_buffer_info.depthbuffer_enabled = (w2 >> 5) & 1;
uint32_t upper_left_s = ((w1 >> 12) & 0xFFF) >> 2;
uint32_t offset_address = rdp_device.frame_buffer_info.texture_address + texture_size(upper_left_s);
if (!rdram_dirty[offset_address])
{
uint32_t lower_right_s = ((w2 >> 12) & 0xFFF) >> 2;
std::fill_n(rdram_dirty.begin() + offset_address, texture_size(lower_right_s - upper_left_s), true);
}
break;
}
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::SetColorImage:
rdp_device.frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF) >> 3;
rdp_device.frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3;
rdp_device.frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1;
break;
case RDP::Op::SetMaskImage:
rdp_device.frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF) >> 3;
break;
case RDP::Op::SetTextureImage:
rdp_device.frame_buffer_info.texture_address = (w2 & 0x00FFFFFF) >> 3;
rdp_device.frame_buffer_info.texture_pixel_size = (w1 >> 19) & 0x3;
rdp_device.frame_buffer_info.texture_width = (w1 & 0x3FF) + 1;
break;
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;
}
rdp_device.frame_buffer_info.framebuffer_height = lower_right_y;
break;
}
else if (RDP::Op(command) == RDP::Op::SyncFull)
{
case RDP::Op::SyncFull:
sync_signal = processor->signal_timeline();
interrupt_timer = region;
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;

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);
@ -40,7 +43,9 @@ extern "C"
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
}

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
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "1.85.1"

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();
@ -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 {

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

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 {

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

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

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,

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);
}
}
}

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,

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
}

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")]

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 {

View file

@ -24,7 +24,7 @@ pub struct Rdram {
}
pub fn read_mem_fast(
device: &mut device::Device,
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {

View file

@ -89,7 +89,7 @@ pub struct Rsp {
}
pub fn read_mem_fast(
device: &mut device::Device,
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {

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")

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

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) {}

View file

@ -151,22 +151,22 @@ 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 {
viewport: eframe::egui::ViewportBuilder::default()

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] {

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

View file

@ -17,6 +17,7 @@ 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 {
@ -188,6 +189,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(),

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

View file

@ -193,37 +193,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 +445,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);

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);

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;
@ -482,8 +482,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 +538,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

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();

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(),
@ -78,11 +85,15 @@ pub fn check_framebuffers(address: u32) {
unsafe { rdp_check_framebuffers(address) }
}
pub fn full_sync() {
unsafe { rdp_full_sync() }
pub fn state_size() -> usize {
unsafe { rdp_state_size() }
}
pub fn load_state(device: &mut device::Device) {
pub fn save_state(rdp_state: *mut u8) {
unsafe { rdp_save_state(rdp_state) }
}
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 +111,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 +129,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 +147,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 +164,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 +186,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