mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 09:35:21 +02:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2cda03c8d | |||
| a07237aa64 | |||
| fdc17f4ae5 | |||
| 4802f3b71a | |||
| a4c505c72d | |||
| 30cb3f8339 | |||
| a5f5867ebb | |||
| ee8e34e17c | |||
| da1e686c1a | |||
| 5a3609d501 | |||
| 7b51ca82eb | |||
| ee77e75855 | |||
| ff093a9f7f | |||
| f7cb0db348 | |||
| eb9f37f1f5 | |||
| 37b902eb31 | |||
| 00964f06f0 | |||
| 319d7c8553 | |||
| 82b4adb4ad | |||
| 8fbc63a677 | |||
| 48ee50dea3 | |||
| 34a09925b4 | |||
| 168347b46e | |||
| 95a3d3d5ec | |||
| f6349f13da | |||
| fb2cf7fcb9 | |||
| 9980302d37 | |||
| 4692e2bcb9 | |||
| 04390301c9 | |||
| bc415821d6 | |||
| a068363787 | |||
| 97839fae40 | |||
| b6c805a27d | |||
| b7509a2425 | |||
| 6ecedde329 | |||
| 09dfd69283 | |||
| d262567084 | |||
| c363e78fbb | |||
| 792a3a6476 | |||
| 5bdb54a911 | |||
| b01c2d1885 | |||
| bb2da243b3 | |||
| 938a99caa9 | |||
| f7bfcfccba | |||
| 656e390171 | |||
| 17990dfebc | |||
| 671a961789 | |||
| aac68a4a1b | |||
| dc5a78a75e | |||
| 91a5483452 |
@@ -18,8 +18,10 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
arch: linux-x86_64
|
||||
- os: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
arch: linux-aarch64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -43,18 +45,22 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-2025
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64-pc-windows-msvc]
|
||||
include:
|
||||
- target: x86_64-pc-windows-msvc
|
||||
arch: windows-x86_64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -66,18 +72,24 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64.exe
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}.exe
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64-apple-darwin, aarch64-apple-darwin]
|
||||
include:
|
||||
- target: x86_64-apple-darwin
|
||||
arch: mac-x86_64
|
||||
- target: aarch64-apple-darwin
|
||||
arch: mac-aarch64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -89,8 +101,10 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gopher64-${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}
|
||||
|
||||
Generated
+268
-209
File diff suppressed because it is too large
Load Diff
+7
-3
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "1.0.7"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
version = "1.0.10"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -27,7 +27,9 @@ mimalloc = "0.1"
|
||||
regex = "1.11"
|
||||
tungstenite = "0.26"
|
||||
socket2 = "0.5"
|
||||
rand_chacha = "0.9"
|
||||
fatfs = "0.3"
|
||||
semver = "1.0"
|
||||
postcard = { version = "1.1", features = ["use-std"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
|
||||
@@ -39,3 +41,5 @@ cc = { version = "1.2", features = ["parallel"] }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
panic = "abort"
|
||||
|
||||
@@ -1,41 +1,19 @@
|
||||
# gopher64
|
||||
(gopher64 also has a GUI that can be launched by just running the executable)
|
||||
## download
|
||||
|
||||
```
|
||||
Usage: gopher64 [OPTIONS] [GAME]
|
||||
https://github.com/gopher64/gopher64/releases
|
||||
|
||||
Arguments:
|
||||
[GAME]
|
||||
## wiki
|
||||
|
||||
https://github.com/gopher64/gopher64/wiki
|
||||
|
||||
Options:
|
||||
-f, --fullscreen
|
||||
|
||||
-c, --configure-input-profile <PROFILE_NAME>
|
||||
Create a new input profile (keyboard/gamepad mappings).
|
||||
-u, --use-dinput
|
||||
Use DirectInput when configuring a new input profile.
|
||||
-b, --bind-input-profile <PROFILE_NAME>
|
||||
Must also specify --port. Used to bind a previously created profile to a port
|
||||
-l, --list-controllers
|
||||
Lists connected controllers which can be used in --assign-controller
|
||||
-a, --assign-controller <CONTROLLER_NUMBER>
|
||||
Must also specify --port. Used to assign a controller listed in --list-controllers to a port
|
||||
-p, --port <PORT>
|
||||
Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller
|
||||
-z, --clear-input-bindings
|
||||
Clear all input profile bindings and controller assignments
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
## discord
|
||||
|
||||
https://discord.gg/9RGXq8W8JQ
|
||||
|
||||
## controls
|
||||
|
||||
Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied.
|
||||
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`
|
||||
|
||||
@@ -49,6 +27,14 @@ Gopher64 supports netplay (online play with others). It has a few public netplay
|
||||
|
||||
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.
|
||||
|
||||
## flatpak
|
||||
|
||||
If you want to run the flatpak from the command line, you need to add the `--filesystem=host:ro` option, for example:
|
||||
|
||||
```
|
||||
flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
|
||||
```
|
||||
|
||||
## goals
|
||||
|
||||
1. Performance. I want to be able to use this emulator on my laptop.
|
||||
@@ -57,11 +43,12 @@ If you would like to keep all the game data in the same folder as executable, yo
|
||||
|
||||
## building and usage
|
||||
|
||||
1. Install rust: https://www.rust-lang.org/tools/install
|
||||
2. `git clone --recursive https://github.com/gopher64/gopher64.git`
|
||||
3. `cd gopher64`
|
||||
4. `cargo build --release`
|
||||
5. `./target/release/gopher64 /path/to/rom.z64`
|
||||
1. Linux only: install the SDL3 dependencies: https://wiki.libsdl.org/SDL3/README/linux
|
||||
2. Install rust: https://www.rust-lang.org/tools/install
|
||||
3. `git clone --recursive https://github.com/gopher64/gopher64.git`
|
||||
4. `cd gopher64`
|
||||
5. `cargo build --release`
|
||||
6. `./target/release/gopher64 /path/to/rom.z64`
|
||||
|
||||
## contributing
|
||||
|
||||
@@ -70,7 +57,3 @@ I am very open to contributions! Please reach out to me via a GitHub issue, or v
|
||||
## license
|
||||
|
||||
Gopher64 is licensed under the GPLv3 license. Many portions of gopher64 have been adapted from mupen64plus and/or ares. The license for mupen64plus can be found here: https://github.com/mupen64plus/mupen64plus-core/blob/master/LICENSES. The license for ares can be found here: https://github.com/ares-emulator/ares/blob/master/LICENSE.
|
||||
|
||||
## where to download
|
||||
|
||||
https://github.com/gopher64/gopher64/releases
|
||||
|
||||
@@ -101,6 +101,8 @@ 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")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+145
-10
@@ -63,6 +63,23 @@ static int cmd_ptr;
|
||||
static CALL_BACK callback;
|
||||
static GFX_INFO gfx_info;
|
||||
static uint32_t region;
|
||||
static bool crop_letterbox;
|
||||
|
||||
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;
|
||||
|
||||
static uint8_t *rdram_dirty;
|
||||
static uint64_t sync_signal;
|
||||
static FrameBufferInfo frame_buffer_info;
|
||||
|
||||
static const unsigned cmd_len_lut[64] = {
|
||||
1,
|
||||
@@ -141,7 +158,7 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
{
|
||||
wsi_platform->do_resize();
|
||||
}
|
||||
else if (event->type == SDL_EVENT_KEY_UP)
|
||||
else if (event->type == SDL_EVENT_KEY_DOWN && !event->key.repeat)
|
||||
{
|
||||
switch (event->key.scancode)
|
||||
{
|
||||
@@ -152,10 +169,19 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
SDL_SetWindowFullscreen(window, fullscreen);
|
||||
}
|
||||
break;
|
||||
case SDL_SCANCODE_F:
|
||||
if (event->key.mod & SDL_KMOD_ALT)
|
||||
{
|
||||
callback.enable_speedlimiter = !callback.enable_speedlimiter;
|
||||
}
|
||||
break;
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
if (fullscreen)
|
||||
callback.emu_running = false;
|
||||
break;
|
||||
case SDL_SCANCODE_F4:
|
||||
crop_letterbox = !crop_letterbox;
|
||||
break;
|
||||
case SDL_SCANCODE_F5:
|
||||
callback.save_state = true;
|
||||
break;
|
||||
@@ -170,23 +196,34 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale)
|
||||
void rdp_new_processor(GFX_INFO _gfx_info, uint32_t upscale)
|
||||
{
|
||||
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;
|
||||
}
|
||||
RDP::CommandProcessorFlags flags = 0;
|
||||
if (_upscale)
|
||||
|
||||
if (upscale == 2)
|
||||
{
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
|
||||
}
|
||||
else if (upscale == 4)
|
||||
{
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_4X_BIT;
|
||||
}
|
||||
|
||||
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
|
||||
}
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen)
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, uint32_t _upscale, bool _integer_scaling, bool _fullscreen)
|
||||
{
|
||||
window = (SDL_Window *)_window;
|
||||
bool result = SDL_AddEventWatch(sdl_event_filter, nullptr);
|
||||
@@ -215,6 +252,8 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
|
||||
{
|
||||
rdp_close();
|
||||
}
|
||||
|
||||
rdram_dirty = (uint8_t *)malloc(gfx_info.RDRAM_SIZE / 8);
|
||||
rdp_new_processor(gfx_info, _upscale);
|
||||
|
||||
if (!processor->device_is_supported())
|
||||
@@ -227,10 +266,18 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
|
||||
wsi->begin_frame();
|
||||
|
||||
callback.emu_running = true;
|
||||
callback.enable_speedlimiter = true;
|
||||
crop_letterbox = false;
|
||||
}
|
||||
|
||||
void rdp_close()
|
||||
{
|
||||
if (rdram_dirty)
|
||||
{
|
||||
free(rdram_dirty);
|
||||
rdram_dirty = nullptr;
|
||||
}
|
||||
|
||||
wsi->end_frame();
|
||||
|
||||
if (processor)
|
||||
@@ -252,7 +299,7 @@ void rdp_close()
|
||||
|
||||
static void calculate_viewport(float *x, float *y, float *width, float *height)
|
||||
{
|
||||
const int32_t display_width = gfx_info.PAL ? 384 : 320;
|
||||
const int32_t display_width = gfx_info.widescreen ? (gfx_info.PAL ? 512 : 426) : (gfx_info.PAL ? 384 : 320);
|
||||
const int32_t display_height = gfx_info.PAL ? 288 : 240;
|
||||
|
||||
int w, h;
|
||||
@@ -296,6 +343,22 @@ static void render_frame(Vulkan::Device &device)
|
||||
options.persist_frame_on_invalid_input = true;
|
||||
options.blend_previous_frame = true;
|
||||
options.upscale_deinterlacing = false;
|
||||
|
||||
if (crop_letterbox && gfx_info.widescreen)
|
||||
{
|
||||
options.crop_rect.enable = true;
|
||||
if (gfx_info.PAL)
|
||||
{
|
||||
options.crop_rect.top = 36;
|
||||
options.crop_rect.bottom = 36;
|
||||
}
|
||||
else
|
||||
{
|
||||
options.crop_rect.top = 30;
|
||||
options.crop_rect.bottom = 30;
|
||||
}
|
||||
}
|
||||
|
||||
Vulkan::ImageHandle image = processor->scanout(options);
|
||||
|
||||
// Normally reflection is automated.
|
||||
@@ -362,6 +425,41 @@ CALL_BACK rdp_check_callback()
|
||||
return return_value;
|
||||
}
|
||||
|
||||
void rdp_check_framebuffers(uint32_t address)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void rdp_full_sync()
|
||||
{
|
||||
processor->wait_for_timeline(processor->signal_timeline());
|
||||
}
|
||||
|
||||
void calculate_buffer_size()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
uint64_t rdp_process_commands()
|
||||
{
|
||||
uint64_t interrupt_timer = 0;
|
||||
@@ -423,18 +521,55 @@ uint64_t rdp_process_commands()
|
||||
if (command >= 8)
|
||||
processor->enqueue_command(cmd_length * 2, &cmd_data[2 * cmd_cur]);
|
||||
|
||||
if (RDP::Op(command) == RDP::Op::SetScissor)
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 (RDP::Op(command) == RDP::Op::SyncFull)
|
||||
else if (RDP::Op(command) == RDP::Op::SyncFull)
|
||||
{
|
||||
processor->wait_for_timeline(processor->signal_timeline());
|
||||
|
||||
sync_signal = processor->signal_timeline();
|
||||
interrupt_timer = region;
|
||||
if (interrupt_timer == 0)
|
||||
interrupt_timer = 5000;
|
||||
|
||||
@@ -17,6 +17,7 @@ extern "C"
|
||||
uint32_t *DPC_END_REG;
|
||||
uint32_t *DPC_STATUS_REG;
|
||||
bool PAL;
|
||||
bool widescreen;
|
||||
} GFX_INFO;
|
||||
|
||||
typedef struct
|
||||
@@ -24,15 +25,18 @@ extern "C"
|
||||
bool emu_running;
|
||||
bool save_state;
|
||||
bool load_state;
|
||||
bool enable_speedlimiter;
|
||||
} CALL_BACK;
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen);
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, uint32_t _upscale, bool _integer_scaling, bool _fullscreen);
|
||||
void rdp_close();
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value);
|
||||
void rdp_update_screen();
|
||||
CALL_BACK rdp_check_callback();
|
||||
uint64_t rdp_process_commands();
|
||||
void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale);
|
||||
void rdp_new_processor(GFX_INFO _gfx_info, uint32_t _upscale);
|
||||
void rdp_check_framebuffers(uint32_t address);
|
||||
void rdp_full_sync();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+42
-6
@@ -10,6 +10,8 @@ include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::arch::x86_64::*;
|
||||
|
||||
use rand_chacha::rand_core::{RngCore, SeedableRng};
|
||||
|
||||
use crate::{netplay, ui};
|
||||
use std::{collections::HashMap, fs, io::Read};
|
||||
|
||||
@@ -42,7 +44,12 @@ pub mod tlb;
|
||||
pub mod unmapped;
|
||||
pub mod vi;
|
||||
|
||||
pub fn run_game(rom_contents: Vec<u8>, device: &mut Device) {
|
||||
pub fn run_game(device: &mut Device, rom_contents: Vec<u8>, fullscreen: bool, overclock: bool) {
|
||||
device.ui.fullscreen = fullscreen;
|
||||
device.cpu.overclock = overclock;
|
||||
|
||||
init_rng(device);
|
||||
|
||||
cart::rom::init(device, rom_contents); // cart needs to come before rdram
|
||||
|
||||
// rdram pointer is shared with parallel-rdp
|
||||
@@ -72,7 +79,24 @@ pub fn run_game(rom_contents: Vec<u8>, device: &mut Device) {
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::storage::write_saves(&device.ui, &device.netplay);
|
||||
ui::storage::write_saves(device);
|
||||
}
|
||||
|
||||
fn set_rng() -> rand_chacha::ChaCha8Rng {
|
||||
rand_chacha::ChaCha8Rng::from_os_rng()
|
||||
}
|
||||
|
||||
fn init_rng(device: &mut Device) {
|
||||
let mut rng_seed = set_rng().next_u64();
|
||||
if device.netplay.is_some() {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
if netplay.player_number == 0 {
|
||||
netplay::send_rng(netplay, rng_seed);
|
||||
} else {
|
||||
rng_seed = netplay::receive_rng(netplay);
|
||||
}
|
||||
}
|
||||
device.rng = rand_chacha::ChaCha8Rng::seed_from_u64(rng_seed);
|
||||
}
|
||||
|
||||
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
|
||||
@@ -174,8 +198,11 @@ pub struct Device {
|
||||
pub ai: ai::Ai,
|
||||
pub si: si::Si,
|
||||
pub ri: ri::Ri,
|
||||
#[serde(skip, default = "set_rng")]
|
||||
pub rng: rand_chacha::ChaCha8Rng,
|
||||
pub vru: controller::vru::Vru,
|
||||
pub vru_window: controller::vru::VruWindow,
|
||||
pub transferpaks: [controller::transferpak::TransferPak; 4],
|
||||
}
|
||||
|
||||
pub fn zero_m128i() -> __m128i {
|
||||
@@ -269,6 +296,7 @@ impl Device {
|
||||
pc: 0xBFC00000,
|
||||
pc_phys: 0,
|
||||
llbit: false,
|
||||
overclock: false,
|
||||
lo: 0,
|
||||
hi: 0,
|
||||
running: false,
|
||||
@@ -297,19 +325,19 @@ impl Device {
|
||||
},
|
||||
cart: cart::Cart {
|
||||
rom: Vec::new(),
|
||||
is_viewer_buffer: [0; 0xFFFF],
|
||||
is_viewer_buffer: vec![0; 0xFFFF],
|
||||
pal: false,
|
||||
latch: 0,
|
||||
cic_seed: 0,
|
||||
cic_type: cart::CicType::CicNus6102,
|
||||
rdram_size_offset: 0,
|
||||
rtc_timestamp: 0,
|
||||
rtc: cart::AfRtc { control: 0x0200 },
|
||||
sc64: cart::sc64::Sc64 {
|
||||
regs: [0; cart::sc64::SC64_REGS_COUNT as usize],
|
||||
regs_locked: true,
|
||||
cfg: [0; cart::sc64::SC64_CFG_COUNT as usize],
|
||||
sector: 0,
|
||||
buffer: [0; 8192],
|
||||
buffer: vec![0; 8192],
|
||||
writeback_sector: vec![0; 256],
|
||||
},
|
||||
flashram: cart::sram::Flashram {
|
||||
status: 0,
|
||||
@@ -435,6 +463,7 @@ impl Device {
|
||||
field: 0,
|
||||
delay: 0,
|
||||
count_per_scanline: 0,
|
||||
enable_speed_limiter: true,
|
||||
limiter: None,
|
||||
vi_counter: 0,
|
||||
last_origin: 0,
|
||||
@@ -459,6 +488,13 @@ impl Device {
|
||||
talking: false,
|
||||
word_mappings: HashMap::new(),
|
||||
},
|
||||
rng: set_rng(),
|
||||
transferpaks: [
|
||||
controller::transferpak::TransferPak::default(),
|
||||
controller::transferpak::TransferPak::default(),
|
||||
controller::transferpak::TransferPak::default(),
|
||||
controller::transferpak::TransferPak::default(),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ fn do_dma(device: &mut device::Device) {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_AI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration,
|
||||
device.ai.fifo[0].duration,
|
||||
);
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
|
||||
}
|
||||
|
||||
+11
-11
@@ -45,16 +45,14 @@ fn byte2bcd(mut n: u32) -> u8 {
|
||||
pub struct Cart {
|
||||
#[serde(skip)]
|
||||
pub rom: Vec<u8>,
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub is_viewer_buffer: [u8; 0xFFFF],
|
||||
pub is_viewer_buffer: Vec<u8>,
|
||||
pub pal: bool,
|
||||
pub latch: u32,
|
||||
pub cic_type: CicType,
|
||||
pub cic_seed: u8,
|
||||
pub rdram_size_offset: usize,
|
||||
pub rtc: AfRtc,
|
||||
pub sc64: sc64::Sc64,
|
||||
pub flashram: sram::Flashram,
|
||||
pub rtc_timestamp: i64,
|
||||
}
|
||||
|
||||
pub fn process(device: &mut device::Device, channel: usize) {
|
||||
@@ -125,9 +123,9 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_eeprom(device: &mut device::Device) {
|
||||
if device.ui.saves.eeprom.0.len() < EEPROM_MAX_SIZE {
|
||||
device.ui.saves.eeprom.0.resize(EEPROM_MAX_SIZE, 0xFF)
|
||||
pub fn format_eeprom(device: &mut device::Device) {
|
||||
if device.ui.saves.eeprom.data.len() < EEPROM_MAX_SIZE {
|
||||
device.ui.saves.eeprom.data.resize(EEPROM_MAX_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +135,7 @@ fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
|
||||
format_eeprom(device);
|
||||
|
||||
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]
|
||||
.copy_from_slice(&device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]);
|
||||
.copy_from_slice(&device.ui.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE]);
|
||||
}
|
||||
|
||||
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
|
||||
@@ -145,16 +143,18 @@ fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize,
|
||||
|
||||
format_eeprom(device);
|
||||
|
||||
device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]
|
||||
device.ui.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE]
|
||||
.copy_from_slice(&device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]);
|
||||
|
||||
device.pif.ram[status] = 0x00;
|
||||
|
||||
device.ui.saves.eeprom.1 = true
|
||||
device.ui.saves.eeprom.written = true
|
||||
}
|
||||
|
||||
fn time2data(device: &mut device::Device, offset: usize) {
|
||||
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
|
||||
let timestamp =
|
||||
device.cart.rtc_timestamp + (device.vi.frame_time * device.vi.vi_counter as f64) as i64;
|
||||
let now = chrono::DateTime::from_timestamp(timestamp, 0).unwrap();
|
||||
|
||||
device.pif.ram[offset] = byte2bcd(now.second());
|
||||
device.pif.ram[offset + 1] = byte2bcd(now.minute());
|
||||
|
||||
+51
-59
@@ -1,19 +1,16 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
const CART_MASK: usize = 0xFFFFFFF;
|
||||
|
||||
fn read_cart_word(device: &mut device::Device, address: usize) -> u32 {
|
||||
let mut data: [u8; 4] = device.cart.rom[address..address + 4].try_into().unwrap();
|
||||
let mut data: [u8; 4] = [0; 4];
|
||||
for i in 0..4 {
|
||||
if device
|
||||
.ui
|
||||
.saves
|
||||
.romsave
|
||||
.0
|
||||
.contains_key(&(address as u32 + i))
|
||||
{
|
||||
data[i as usize] = device.ui.saves.romsave.0[&(address as u32 + i)];
|
||||
if let Some(value) = device.ui.saves.romsave.data.get(&(address as u32 + i)) {
|
||||
data[i as usize] = *value;
|
||||
} else {
|
||||
data[i as usize] = *device.cart.rom.get(address + i as usize).unwrap_or(&0);
|
||||
}
|
||||
}
|
||||
u32::from_be_bytes(data)
|
||||
@@ -46,7 +43,9 @@ pub fn read_mem(
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
|
||||
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0
|
||||
&& device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] == 0
|
||||
{
|
||||
let masked_address = address as usize & CART_MASK;
|
||||
let mut data = read_cart_word(device, masked_address);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
@@ -55,10 +54,10 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
.ui
|
||||
.saves
|
||||
.romsave
|
||||
.0
|
||||
.data
|
||||
.insert((masked_address + i) as u32, *item);
|
||||
}
|
||||
device.ui.saves.romsave.1 = true;
|
||||
device.ui.saves.romsave.written = true;
|
||||
}
|
||||
|
||||
device.cart.latch = value & mask;
|
||||
@@ -66,11 +65,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
|
||||
|
||||
let cycles = device::pi::calculate_cycles(device, 1, 4);
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
}
|
||||
|
||||
pub fn dma_read(
|
||||
@@ -79,20 +74,19 @@ pub fn dma_read(
|
||||
mut dram_addr: u32,
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
|
||||
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0
|
||||
&& device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] == 0
|
||||
{
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= CART_MASK as u32;
|
||||
|
||||
for i in 0..length {
|
||||
if cart_addr + i < device.cart.rom.len() as u32 {
|
||||
device.ui.saves.romsave.0.insert(
|
||||
cart_addr + i,
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
|
||||
);
|
||||
|
||||
device.ui.saves.romsave.1 = true;
|
||||
}
|
||||
device.ui.saves.romsave.data.insert(
|
||||
cart_addr + i,
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
|
||||
);
|
||||
}
|
||||
device.ui.saves.romsave.written = true;
|
||||
}
|
||||
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
@@ -109,26 +103,36 @@ pub fn dma_write(
|
||||
cart_addr &= CART_MASK as u32;
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
while i < dram_addr + length && j < device.cart.rom.len() as u32 {
|
||||
if device.ui.saves.romsave.0.contains_key(&j) {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.romsave.0[&j];
|
||||
while i < dram_addr + length {
|
||||
if device.ui.saves.romsave.data.contains_key(&j) {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.romsave.data[&j];
|
||||
} else {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.rom[j as usize];
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] =
|
||||
*device.cart.rom.get(j as usize).unwrap_or(&0);
|
||||
}
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
while i < dram_addr + length {
|
||||
// DMAs that extend past the end of the ROM return 0's for the portion that extends past the ROM length
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = 0;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
device::pi::calculate_cycles(device, 1, length)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
|
||||
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
|
||||
device.cart.rtc_timestamp = now.naive_local().and_utc().timestamp();
|
||||
if device.netplay.is_some() {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
if netplay.player_number == 0 {
|
||||
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
|
||||
} else {
|
||||
device.cart.rtc_timestamp = netplay::receive_rtc(netplay);
|
||||
}
|
||||
}
|
||||
|
||||
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 1;
|
||||
|
||||
device.cart.rom = rom_file;
|
||||
set_system_region(device, device.cart.rom[0x3E]);
|
||||
device.cart.pal = is_system_pal(device.cart.rom[0x3E]);
|
||||
set_cic(device);
|
||||
|
||||
device.ui.game_hash = calculate_hash(&device.cart.rom);
|
||||
@@ -139,53 +143,41 @@ pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_system_region(device: &mut device::Device, country: u8) {
|
||||
fn is_system_pal(country: u8) -> bool {
|
||||
let pal_codes: [u8; 8] = [b'D', b'F', b'I', b'P', b'S', b'U', b'X', b'Y'];
|
||||
for i in pal_codes {
|
||||
if country == i {
|
||||
device.cart.pal = true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn set_cic(device: &mut device::Device) {
|
||||
let hash = calculate_hash(&device.cart.rom[0x40..0x1000]);
|
||||
match hash.as_str() {
|
||||
"B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => {
|
||||
device.cart.cic_type = device::cart::CicType::CicNus6101;
|
||||
device.cart.cic_seed = 0x3F;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
device.cart.cic_seed = 0x3F; // CicNus6101
|
||||
}
|
||||
"61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => {
|
||||
device.cart.cic_type = device::cart::CicType::CicNus6102;
|
||||
device.cart.cic_seed = 0x3F;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
device.cart.cic_seed = 0x3F; // CicNus6102
|
||||
}
|
||||
"BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => {
|
||||
device.cart.cic_type = device::cart::CicType::CicNus6103;
|
||||
device.cart.cic_seed = 0x78;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
device.cart.cic_seed = 0x78; // CicNus6103
|
||||
}
|
||||
"04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => {
|
||||
device.cart.cic_type = device::cart::CicType::CicNus6105;
|
||||
device.cart.cic_seed = 0x91;
|
||||
device.cart.rdram_size_offset = 0x3F0;
|
||||
device.cart.cic_seed = 0x91; // CicNus6105
|
||||
}
|
||||
"36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => {
|
||||
device.cart.cic_type = device::cart::CicType::CicNus6106;
|
||||
device.cart.cic_seed = 0x85;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
device.cart.cic_seed = 0x85; // CicNus6106
|
||||
}
|
||||
"53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => {
|
||||
device.cart.cic_type = device::cart::CicType::CicNus5167;
|
||||
device.cart.cic_seed = 0xdd;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
device.cart.cic_seed = 0xdd; // CicNus5167
|
||||
device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1;
|
||||
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 0;
|
||||
}
|
||||
_ => {
|
||||
device.cart.cic_type = device::cart::CicType::CicNus6102;
|
||||
device.cart.cic_seed = 0x3F;
|
||||
device.cart.rdram_size_offset = 0x318;
|
||||
device.cart.cic_seed = 0x3F; // CicNus6102
|
||||
println!("unknown IPL3 {}", hash)
|
||||
}
|
||||
}
|
||||
|
||||
+123
-30
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub const SDCARD_SIZE: usize = 0x4000000;
|
||||
|
||||
@@ -11,25 +12,28 @@ const SC64_KEY_REG: u32 = 4;
|
||||
//const SC64_AUX_REG: u32 = 6;
|
||||
pub const SC64_REGS_COUNT: u32 = 7;
|
||||
|
||||
pub const SC64_BOOTLOADER_SWITCH: u32 = 0;
|
||||
pub const SC64_ROM_WRITE_ENABLE: u32 = 1;
|
||||
pub const SC64_SAVE_TYPE: u32 = 6;
|
||||
pub const SC64_CFG_COUNT: u32 = 15;
|
||||
|
||||
const SC64_BUFFER_MASK: usize = 0x1FFF;
|
||||
const SC64_EEPROM_MASK: usize = 0xFFF;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Sc64 {
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub buffer: [u8; 8192],
|
||||
pub buffer: Vec<u8>,
|
||||
pub regs: [u32; SC64_REGS_COUNT as usize],
|
||||
pub regs_locked: bool,
|
||||
pub cfg: [u32; SC64_CFG_COUNT as usize],
|
||||
pub sector: u32,
|
||||
pub writeback_sector: Vec<u32>,
|
||||
}
|
||||
|
||||
fn format_sdcard(device: &mut device::Device) {
|
||||
if device.ui.saves.sdcard.0.is_empty() {
|
||||
device.ui.saves.sdcard.0.resize(SDCARD_SIZE, 0);
|
||||
let buf = std::io::Cursor::new(&mut device.ui.saves.sdcard.0);
|
||||
if device.ui.saves.sdcard.data.is_empty() {
|
||||
device.ui.saves.sdcard.data.resize(SDCARD_SIZE, 0);
|
||||
let buf = std::io::Cursor::new(&mut device.ui.saves.sdcard.data);
|
||||
fatfs::format_volume(buf, fatfs::FormatVolumeOptions::new()).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -74,6 +78,11 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
SC64_SCR_REG => {
|
||||
if !device.cart.sc64.regs_locked {
|
||||
match char::from_u32(value & mask).unwrap() {
|
||||
'V' => {
|
||||
// get version
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = (2 << 16) | 20;
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 2;
|
||||
}
|
||||
'c' => {
|
||||
// get config
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = device.cart.sc64.cfg
|
||||
@@ -81,6 +90,35 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
'C' => {
|
||||
// set config
|
||||
if device.cart.sc64.regs[SC64_DATA0_REG as usize] == SC64_SAVE_TYPE {
|
||||
// if save type is being written, we are probably booting a game using the flash cart menu
|
||||
// we shouldn't write saves to disk in this case (they are written to the SD card)
|
||||
device.ui.saves.write_to_disk = false;
|
||||
device.ui.save_type =
|
||||
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
|
||||
0 => {
|
||||
vec![]
|
||||
}
|
||||
1 => {
|
||||
vec![ui::storage::SaveTypes::Eeprom4k]
|
||||
}
|
||||
2 => {
|
||||
vec![ui::storage::SaveTypes::Eeprom16k]
|
||||
}
|
||||
3 => {
|
||||
vec![ui::storage::SaveTypes::Sram]
|
||||
}
|
||||
4 => {
|
||||
vec![ui::storage::SaveTypes::Flash]
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"unknown sc64 save type: {}",
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
std::mem::swap(
|
||||
&mut device.cart.sc64.cfg
|
||||
[device.cart.sc64.regs[SC64_DATA0_REG as usize] as usize],
|
||||
@@ -117,9 +155,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
let mut i = 0;
|
||||
|
||||
while i < length {
|
||||
if offset + i < device.ui.saves.sdcard.0.len() {
|
||||
if offset + i < device.ui.saves.sdcard.data.len() {
|
||||
let data = u32::from_be_bytes(
|
||||
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
|
||||
device.ui.saves.sdcard.data[(offset + i)..(offset + i + 4)]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -148,7 +186,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
let mut i = 0;
|
||||
|
||||
while i < length {
|
||||
if offset + i < device.ui.saves.sdcard.0.len() {
|
||||
if offset + i < device.ui.saves.sdcard.data.len() {
|
||||
let data = device::memory::data_read(
|
||||
device,
|
||||
address + i as u64,
|
||||
@@ -156,17 +194,33 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
false,
|
||||
)
|
||||
.to_be_bytes();
|
||||
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
|
||||
device.ui.saves.sdcard.data[(offset + i)..(offset + i + 4)]
|
||||
.copy_from_slice(&data);
|
||||
} else {
|
||||
panic!("sd card write out of bounds")
|
||||
}
|
||||
i += 4;
|
||||
}
|
||||
device.ui.saves.sdcard.1 = true;
|
||||
device.ui.saves.sdcard.written = true;
|
||||
}
|
||||
'U' | 'u' => {} // USB status, ignored
|
||||
'M' | 'm' => {} // USB read/write, ignored
|
||||
'w' => {
|
||||
// SD card writeback pending
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
|
||||
}
|
||||
'W' => {
|
||||
let writeback_sectors_address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64;
|
||||
for i in 0..256 {
|
||||
device.cart.sc64.writeback_sector[i] = device::memory::data_read(
|
||||
device,
|
||||
writeback_sectors_address + (i * 4) as u64,
|
||||
device::memory::AccessSize::Word,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
'U' => {} // USB_WRITE_STATUS, ignored
|
||||
'M' => {} // USB_WRITE, ignored
|
||||
_ => {
|
||||
panic!(
|
||||
"unknown sc64 command: {}",
|
||||
@@ -190,24 +244,48 @@ pub fn read_mem(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let masked_address = address as usize & SC64_BUFFER_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
if address & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
let masked_address = address as usize & SC64_EEPROM_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.ui.saves.eeprom.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let masked_address = address as usize & SC64_BUFFER_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let masked_address = address as usize & SC64_BUFFER_MASK;
|
||||
let mut data = u32::from_be_bytes(
|
||||
if address & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
let masked_address = address as usize & SC64_EEPROM_MASK;
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.ui.saves.eeprom.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.ui.saves.eeprom.data[masked_address..masked_address + 4]
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
device.ui.saves.eeprom.written = true
|
||||
} else {
|
||||
let masked_address = address as usize & SC64_BUFFER_MASK;
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.cart.sc64.buffer[masked_address..masked_address + 4]
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dma_read(
|
||||
@@ -217,12 +295,20 @@ pub fn dma_read(
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= SC64_BUFFER_MASK as u32;
|
||||
let buffer = if cart_addr & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
cart_addr &= SC64_EEPROM_MASK as u32;
|
||||
device.ui.saves.eeprom.written = true;
|
||||
&mut device.ui.saves.eeprom.data
|
||||
} else {
|
||||
cart_addr &= SC64_BUFFER_MASK as u32;
|
||||
&mut device.cart.sc64.buffer
|
||||
};
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.cart.sc64.buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
|
||||
buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
@@ -237,12 +323,19 @@ pub fn dma_write(
|
||||
length: u32,
|
||||
) -> u64 {
|
||||
dram_addr &= device::rdram::RDRAM_MASK as u32;
|
||||
cart_addr &= SC64_BUFFER_MASK as u32;
|
||||
let buffer = if cart_addr & 0x2000 != 0 {
|
||||
device::cart::format_eeprom(device);
|
||||
cart_addr &= SC64_EEPROM_MASK as u32;
|
||||
&device.ui.saves.eeprom.data
|
||||
} else {
|
||||
cart_addr &= SC64_BUFFER_MASK as u32;
|
||||
&device.cart.sc64.buffer
|
||||
};
|
||||
let mut i = dram_addr;
|
||||
let mut j = cart_addr;
|
||||
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.sc64.buffer[j as usize];
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = buffer[j as usize];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
+20
-23
@@ -29,14 +29,14 @@ pub struct Flashram {
|
||||
}
|
||||
|
||||
fn format_sram(device: &mut device::Device) {
|
||||
if device.ui.saves.sram.0.len() < SRAM_SIZE {
|
||||
device.ui.saves.sram.0.resize(SRAM_SIZE, 0xFF)
|
||||
if device.ui.saves.sram.data.len() < SRAM_SIZE {
|
||||
device.ui.saves.sram.data.resize(SRAM_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_flash(device: &mut device::Device) {
|
||||
if device.ui.saves.flash.0.len() < FLASHRAM_SIZE {
|
||||
device.ui.saves.flash.0.resize(FLASHRAM_SIZE, 0xFF)
|
||||
if device.ui.saves.flash.data.len() < FLASHRAM_SIZE {
|
||||
device.ui.saves.flash.data.resize(FLASHRAM_SIZE, 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
|
||||
format_sram(device);
|
||||
|
||||
u32::from_be_bytes(
|
||||
device.ui.saves.sram.0[masked_address..masked_address + 4]
|
||||
device.ui.saves.sram.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
@@ -88,14 +88,15 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
format_sram(device);
|
||||
|
||||
let mut data = u32::from_be_bytes(
|
||||
device.ui.saves.sram.0[masked_address..masked_address + 4]
|
||||
device.ui.saves.sram.data[masked_address..masked_address + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
device::memory::masked_write_32(&mut data, value, mask);
|
||||
device.ui.saves.sram.0[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
|
||||
device.ui.saves.sram.data[masked_address..masked_address + 4]
|
||||
.copy_from_slice(&data.to_be_bytes());
|
||||
|
||||
device.ui.saves.sram.1 = true
|
||||
device.ui.saves.sram.written = true
|
||||
}
|
||||
|
||||
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -122,11 +123,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
|
||||
|
||||
let cycles = device::pi::calculate_cycles(device, 2, 4);
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
}
|
||||
|
||||
fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr: u32, length: u32) {
|
||||
@@ -138,12 +135,12 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.ui.saves.sram.0[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
|
||||
device.ui.saves.sram.data[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
device.ui.saves.sram.1 = true
|
||||
device.ui.saves.sram.written = true
|
||||
}
|
||||
|
||||
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
|
||||
@@ -188,7 +185,7 @@ fn dma_write_sram(
|
||||
format_sram(device);
|
||||
|
||||
while i < dram_addr + length && i < device.rdram.size {
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.0[j as usize];
|
||||
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.data[j as usize];
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
@@ -231,7 +228,7 @@ fn dma_write_flash(
|
||||
/* do actual DMA */
|
||||
for i in 0..length {
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] =
|
||||
device.ui.saves.flash.0[(cart_addr + i) as usize];
|
||||
device.ui.saves.flash.data[(cart_addr + i) as usize];
|
||||
}
|
||||
} else {
|
||||
/* other accesses are not implemented */
|
||||
@@ -270,14 +267,14 @@ fn flashram_command(device: &mut device::Device, command: u32) {
|
||||
if device.cart.flashram.mode == FlashramMode::SectorErase {
|
||||
let offset: usize = (device.cart.flashram.erase_page & 0xff80) as usize * 128;
|
||||
for i in 0..128 * 128 {
|
||||
device.ui.saves.flash.0[offset + i] = 0xFF;
|
||||
device.ui.saves.flash.data[offset + i] = 0xFF;
|
||||
}
|
||||
device.ui.saves.flash.1 = true
|
||||
device.ui.saves.flash.written = true
|
||||
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
|
||||
for i in 0..FLASHRAM_SIZE {
|
||||
device.ui.saves.flash.0[i] = 0xFF;
|
||||
device.ui.saves.flash.data[i] = 0xFF;
|
||||
}
|
||||
device.ui.saves.flash.1 = true
|
||||
device.ui.saves.flash.written = true
|
||||
} else {
|
||||
panic!("Unexpected flash erase command")
|
||||
}
|
||||
@@ -295,9 +292,9 @@ fn flashram_command(device: &mut device::Device, command: u32) {
|
||||
/* program selected page */
|
||||
let offset: usize = (command & 0xffff) as usize * 128;
|
||||
for i in 0..128 {
|
||||
device.ui.saves.flash.0[offset + i] = device.cart.flashram.page_buf[i];
|
||||
device.ui.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i];
|
||||
}
|
||||
device.ui.saves.flash.1 = true;
|
||||
device.ui.saves.flash.written = true;
|
||||
|
||||
/* clear program busy flag, set program success flag, transition to status mode */
|
||||
device.cart.flashram.status &= !0x01;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{device, netplay, savestates, ui};
|
||||
|
||||
pub mod gbcart;
|
||||
pub mod mempak;
|
||||
pub mod rumble;
|
||||
pub mod transferpak;
|
||||
@@ -66,8 +67,8 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
netplay::get_input(device, channel)
|
||||
};
|
||||
|
||||
device.pif.ram[offset..offset + 4].copy_from_slice(&input.0.to_ne_bytes());
|
||||
if input.1 {
|
||||
device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes());
|
||||
if input.pak_change_pressed {
|
||||
// pak change button pressed
|
||||
if device::events::get_event(device, device::events::EVENT_TYPE_PAK).is_none() {
|
||||
device.pif.channels[channel].change_pak =
|
||||
@@ -76,8 +77,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_PAK,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
|
||||
+ (device.cpu.clock_rate), // 1 second
|
||||
device.cpu.clock_rate, // 1 second
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,14 @@ pub fn pak_switch_event(device: &mut device::Device) {
|
||||
|
||||
let new_pak_type = match channel.change_pak {
|
||||
PakType::MemPak => PakType::RumblePak,
|
||||
PakType::RumblePak => PakType::MemPak,
|
||||
PakType::RumblePak => {
|
||||
if device.transferpaks[i].cart.rom.is_empty() {
|
||||
PakType::MemPak
|
||||
} else {
|
||||
PakType::TransferPak
|
||||
}
|
||||
}
|
||||
PakType::TransferPak => PakType::MemPak,
|
||||
_ => {
|
||||
panic!("Invalid pak type");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
use crate::device;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GbCart {
|
||||
pub enabled: bool,
|
||||
#[serde(skip)]
|
||||
pub rom: Vec<u8>,
|
||||
pub ram: Vec<u8>,
|
||||
pub cart_type: device::controller::gbcart::CartType,
|
||||
pub ram_enabled: bool,
|
||||
pub ram_bank: u16,
|
||||
pub rom_bank: u32,
|
||||
pub set_latch: bool,
|
||||
pub latch_second: u8,
|
||||
pub latch_minute: u8,
|
||||
pub latch_hour: u8,
|
||||
pub latch_day: u16,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CartType {
|
||||
#[default]
|
||||
None,
|
||||
MBC3RamBatt,
|
||||
MBC3RamBattRtc,
|
||||
MBC5RamBatt,
|
||||
}
|
||||
|
||||
fn write_mbc3(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let value = pif_ram[data + size - 1];
|
||||
if address < 0x2000 {
|
||||
cart.ram_enabled = value & 0xf == 0xa;
|
||||
} else if address < 0x4000 {
|
||||
let bank = value & 0x7f;
|
||||
cart.rom_bank = bank as u32;
|
||||
if cart.rom_bank == 0 {
|
||||
cart.rom_bank = 1;
|
||||
}
|
||||
} else if address < 0x6000 {
|
||||
cart.ram_bank = (value & 0xf) as u16;
|
||||
} else if address < 0x8000 {
|
||||
if !cart.set_latch && value != 0 {
|
||||
// not implemented
|
||||
}
|
||||
cart.set_latch = value != 0;
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
return;
|
||||
}
|
||||
if cart.ram_bank < 0x8 {
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
|
||||
cart.ram[banked_address as usize..banked_address as usize + size]
|
||||
.copy_from_slice(&pif_ram[data..data + size]);
|
||||
} else {
|
||||
match cart.ram_bank {
|
||||
0x8 => {
|
||||
cart.latch_second = value;
|
||||
}
|
||||
0x9 => {
|
||||
cart.latch_minute = value;
|
||||
}
|
||||
0xA => {
|
||||
cart.latch_hour = value;
|
||||
}
|
||||
0xB => {
|
||||
cart.latch_day &= 0xFF00;
|
||||
cart.latch_day |= value as u16;
|
||||
}
|
||||
0xC => {
|
||||
cart.latch_day &= 0x00FF;
|
||||
cart.latch_day |= (value as u16) << 8;
|
||||
}
|
||||
_ => {
|
||||
panic!("Unsupported ram bank {:x}", cart.ram_bank);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Unsupported write address {:x}", address);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_mbc3(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if address < 0x4000 {
|
||||
let banked_address = address & 0x3FFF;
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if address < 0x8000 {
|
||||
let banked_address = address as u32 - 0x4000 + (cart.rom_bank * 0x4000);
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0xff;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if cart.ram_bank < 0x8 {
|
||||
if cart.ram_bank > 3 {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0;
|
||||
}
|
||||
} else {
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
|
||||
pif_ram[data..data + size].copy_from_slice(
|
||||
&cart.ram[banked_address as usize..banked_address as usize + size],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let latch = match cart.ram_bank {
|
||||
0x8 => cart.latch_second,
|
||||
0x9 => cart.latch_minute,
|
||||
0xA => cart.latch_hour,
|
||||
0xB => cart.latch_day as u8,
|
||||
0xC => (cart.latch_day >> 8) as u8,
|
||||
_ => {
|
||||
panic!("Unsupported ram bank {:x}", cart.ram_bank);
|
||||
}
|
||||
};
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = latch;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Unsupported read address {:x}", address);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mbc5(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
let value = pif_ram[data + size - 1];
|
||||
if address < 0x2000 {
|
||||
cart.ram_enabled = value & 0xf == 0xa;
|
||||
} else if address < 0x3000 {
|
||||
cart.rom_bank &= 0xff00;
|
||||
cart.rom_bank |= value as u32;
|
||||
} else if address < 0x4000 {
|
||||
cart.rom_bank &= 0x00ff;
|
||||
cart.rom_bank |= ((value & 0x1) as u32) << 8;
|
||||
} else if address < 0x6000 {
|
||||
cart.ram_bank = (value & 0xf) as u16;
|
||||
} else if address < 0xa000 {
|
||||
println!("Unknown MBC5 write address {:x}", address);
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank << 13);
|
||||
cart.ram[banked_address as usize..banked_address as usize + size]
|
||||
.copy_from_slice(&pif_ram[data..data + size]);
|
||||
} else {
|
||||
panic!("Unsupported write address {:x}", address);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_mbc5(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if address < 0x4000 {
|
||||
let banked_address = address & 0x3FFF;
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if address < 0x8000 {
|
||||
let banked_address = address as u32 - 0x4000 + (cart.rom_bank << 14);
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
|
||||
} else if (0xa000..0xc000).contains(&address) {
|
||||
if !cart.ram_enabled {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0xff;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let banked_address = address - 0xA000 + (cart.ram_bank << 13);
|
||||
pif_ram[data..data + size]
|
||||
.copy_from_slice(&cart.ram[banked_address as usize..banked_address as usize + size]);
|
||||
} else {
|
||||
panic!("Unsupported read address {:x}", address);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if !cart.enabled {
|
||||
for i in 0..size {
|
||||
pif_ram[data + i] = 0x00;
|
||||
}
|
||||
return;
|
||||
}
|
||||
match cart.cart_type {
|
||||
CartType::MBC3RamBatt => read_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC3RamBattRtc => read_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC5RamBatt => read_mbc5(pif_ram, cart, address, data, size),
|
||||
_ => panic!("Unsupported cart type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
pif_ram: &mut [u8],
|
||||
cart: &mut device::controller::gbcart::GbCart,
|
||||
address: u16,
|
||||
data: usize,
|
||||
size: usize,
|
||||
) {
|
||||
if !cart.enabled {
|
||||
return;
|
||||
}
|
||||
match cart.cart_type {
|
||||
CartType::MBC3RamBatt => write_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC3RamBattRtc => write_mbc3(pif_ram, cart, address, data, size),
|
||||
CartType::MBC5RamBatt => write_mbc5(pif_ram, cart, address, data, size),
|
||||
_ => panic!("Unsupported cart type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cart_type(data: u8) -> CartType {
|
||||
match data {
|
||||
0x10 => CartType::MBC3RamBattRtc,
|
||||
0x13 => CartType::MBC3RamBatt,
|
||||
0x1b => CartType::MBC5RamBatt,
|
||||
_ => panic!("Unsupported cart type {:x}", data),
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ pub const MEMPAK_SIZE: usize = 0x8000;
|
||||
const MPK_PAGE_SIZE: usize = 256;
|
||||
|
||||
fn format_mempak(device: &mut device::Device) {
|
||||
if device.ui.saves.mempak.0.len() < MEMPAK_SIZE * 4 {
|
||||
device.ui.saves.mempak.0.resize(MEMPAK_SIZE * 4, 0);
|
||||
if device.ui.saves.mempak.data.len() < MEMPAK_SIZE * 4 {
|
||||
device.ui.saves.mempak.data.resize(MEMPAK_SIZE * 4, 0);
|
||||
|
||||
let page_0: [u8; MPK_PAGE_SIZE] = [
|
||||
/* Label area */
|
||||
@@ -38,28 +38,28 @@ fn format_mempak(device: &mut device::Device) {
|
||||
let offset = i * MEMPAK_SIZE;
|
||||
|
||||
/* Fill Page 0 with pre-initialized content */
|
||||
device.ui.saves.mempak.0[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0);
|
||||
device.ui.saves.mempak.data[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0);
|
||||
|
||||
/* Fill INODE page 1 and update it's checkum */
|
||||
let start_page = 5;
|
||||
for i in MPK_PAGE_SIZE..2 * start_page {
|
||||
device.ui.saves.mempak.0[offset + i] = 0;
|
||||
device.ui.saves.mempak.data[offset + i] = 0;
|
||||
}
|
||||
for i in (MPK_PAGE_SIZE + 2 * start_page..2 * MPK_PAGE_SIZE).step_by(2) {
|
||||
device.ui.saves.mempak.0[offset + i] = 0x00;
|
||||
device.ui.saves.mempak.0[offset + i + 1] = 0x03;
|
||||
device.ui.saves.mempak.data[offset + i] = 0x00;
|
||||
device.ui.saves.mempak.data[offset + i + 1] = 0x03;
|
||||
}
|
||||
device.ui.saves.mempak.0[offset + (MPK_PAGE_SIZE + 1)] = 0x71;
|
||||
device.ui.saves.mempak.data[offset + (MPK_PAGE_SIZE + 1)] = 0x71;
|
||||
|
||||
/* Page 2 is identical to page 1 */
|
||||
let page1 = offset + MPK_PAGE_SIZE;
|
||||
let page2 = offset + 2 * MPK_PAGE_SIZE;
|
||||
let page1data = device.ui.saves.mempak.0[page1..page1 + MPK_PAGE_SIZE].to_vec();
|
||||
device.ui.saves.mempak.0[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data);
|
||||
let page1data = device.ui.saves.mempak.data[page1..page1 + MPK_PAGE_SIZE].to_vec();
|
||||
device.ui.saves.mempak.data[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data);
|
||||
|
||||
/* Remaining pages DIR+DATA (3...) are initialized with 0x00 */
|
||||
for i in 3 * MPK_PAGE_SIZE..MEMPAK_SIZE - 3 * MPK_PAGE_SIZE {
|
||||
device.ui.saves.mempak.0[offset + i] = 0;
|
||||
device.ui.saves.mempak.data[offset + i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
|
||||
|
||||
let offset = (channel * MEMPAK_SIZE) + address as usize;
|
||||
device.pif.ram[data..data + size]
|
||||
.copy_from_slice(&device.ui.saves.mempak.0[offset..offset + size])
|
||||
.copy_from_slice(&device.ui.saves.mempak.data[offset..offset + size])
|
||||
} else {
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = 0;
|
||||
@@ -84,9 +84,9 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
|
||||
format_mempak(device);
|
||||
|
||||
let offset = (channel * MEMPAK_SIZE) + address as usize;
|
||||
device.ui.saves.mempak.0[offset..offset + size]
|
||||
device.ui.saves.mempak.data[offset..offset + size]
|
||||
.copy_from_slice(&device.pif.ram[data..data + size]);
|
||||
|
||||
device.ui.saves.mempak.1 = true
|
||||
device.ui.saves.mempak.written = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,133 @@
|
||||
use crate::device;
|
||||
|
||||
pub fn read(
|
||||
_device: &mut device::Device,
|
||||
_channel: usize,
|
||||
_address: u16,
|
||||
_data: usize,
|
||||
_size: usize,
|
||||
) {
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TransferPak {
|
||||
pub enabled: bool,
|
||||
pub reset_state: u8,
|
||||
pub bank: u16,
|
||||
pub cart: device::controller::gbcart::GbCart,
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
_device: &mut device::Device,
|
||||
_channel: usize,
|
||||
_address: u16,
|
||||
_data: usize,
|
||||
_size: usize,
|
||||
) {
|
||||
pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
let pak = &mut device.transferpaks[channel];
|
||||
|
||||
match address >> 12 {
|
||||
0x8 => {
|
||||
let value = if pak.enabled { 0x84 } else { 0x00 };
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
if !pak.enabled {
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = 0x00;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match address >> 12 {
|
||||
0xB => {
|
||||
let mut value = 0;
|
||||
if pak.cart.enabled {
|
||||
value |= 1 << 0;
|
||||
}
|
||||
value |= (pak.reset_state & 3) << 2;
|
||||
if pak.enabled {
|
||||
value |= 1 << 7;
|
||||
}
|
||||
|
||||
if pak.cart.enabled && pak.reset_state == 3 {
|
||||
pak.reset_state = 2
|
||||
} else if !pak.cart.enabled && pak.reset_state == 2 {
|
||||
pak.reset_state = 1
|
||||
} else if !pak.cart.enabled && pak.reset_state == 1 {
|
||||
pak.reset_state = 0
|
||||
}
|
||||
for i in 0..size {
|
||||
device.pif.ram[data + i] = value;
|
||||
}
|
||||
}
|
||||
0xC..=0xF => {
|
||||
device::controller::gbcart::read(
|
||||
&mut device.pif.ram,
|
||||
&mut pak.cart,
|
||||
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
|
||||
data,
|
||||
size,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("unknown transfer pak read {:x}", address >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
|
||||
let pak = &mut device.transferpaks[channel];
|
||||
|
||||
let value = device.pif.ram[data + size - 1];
|
||||
match address >> 12 {
|
||||
0x8 => {
|
||||
match value {
|
||||
0xFE => {
|
||||
pak.enabled = false;
|
||||
}
|
||||
0x84 => {
|
||||
if !pak.enabled {
|
||||
pak.bank = 3;
|
||||
pak.cart.enabled = false;
|
||||
pak.reset_state = 0;
|
||||
}
|
||||
pak.enabled = true;
|
||||
}
|
||||
_ => panic!("unknown transfer pak value"),
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
if !pak.enabled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match address >> 12 {
|
||||
0xA => {
|
||||
pak.bank = value as u16;
|
||||
if pak.bank > 3 {
|
||||
pak.bank = 0;
|
||||
}
|
||||
}
|
||||
0xB => {
|
||||
if value & 1 != 0 {
|
||||
if !pak.cart.enabled {
|
||||
pak.reset_state = 3;
|
||||
pak.cart.rom_bank = 1;
|
||||
pak.cart.ram_bank = 0;
|
||||
pak.cart.ram_enabled = false;
|
||||
pak.cart.cart_type =
|
||||
device::controller::gbcart::get_cart_type(pak.cart.rom[0x147]);
|
||||
}
|
||||
pak.cart.enabled = true;
|
||||
} else {
|
||||
pak.cart.enabled = false;
|
||||
}
|
||||
}
|
||||
0xC..=0xF => {
|
||||
device::controller::gbcart::write(
|
||||
&mut device.pif.ram,
|
||||
&mut pak.cart,
|
||||
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
|
||||
data,
|
||||
size,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("unknown transfer pak write {:x}", address >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +170,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_VRU,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
|
||||
+ (device.cpu.clock_rate * 2), // 2 seconds
|
||||
device.cpu.clock_rate * 2, // 2 seconds
|
||||
)
|
||||
} else if device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] == 0xEF {
|
||||
device.vru.talking = false;
|
||||
|
||||
+2
-2
@@ -215,7 +215,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_COMPARE,
|
||||
device.cpu.cop0.regs[COP0_COUNT_REG as usize] + ((compare_event_diff as u64) << 1),
|
||||
(compare_event_diff as u64) << 1,
|
||||
);
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
@@ -237,7 +237,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
|
||||
pub fn compare_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.pending_compare_interrupt = true;
|
||||
device::events::create_event(
|
||||
device::events::create_event_at(
|
||||
device,
|
||||
device::events::EVENT_TYPE_COMPARE,
|
||||
device.cpu.next_event_count + (u32::MAX as u64),
|
||||
|
||||
+6
-1
@@ -30,6 +30,7 @@ pub struct Cpu {
|
||||
pub hi: u64,
|
||||
pub running: bool,
|
||||
pub llbit: bool,
|
||||
pub overclock: bool,
|
||||
pub clock_rate: u64,
|
||||
#[serde(skip, default = "savestates::default_instructions")]
|
||||
pub instrs: [fn(&mut device::Device, u32); 64],
|
||||
@@ -223,7 +224,11 @@ pub fn map_instructions(device: &mut device::Device) {
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.cpu.clock_rate = 93750000;
|
||||
device.cpu.clock_rate = if !device.cpu.overclock {
|
||||
93750000
|
||||
} else {
|
||||
125000000
|
||||
};
|
||||
|
||||
map_instructions(device);
|
||||
|
||||
|
||||
@@ -21,6 +21,14 @@ pub struct Event {
|
||||
}
|
||||
|
||||
pub fn create_event(device: &mut device::Device, name: usize, when: u64) {
|
||||
device.cpu.events[name] = Event {
|
||||
enabled: true,
|
||||
count: device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + when,
|
||||
};
|
||||
set_next_event(device);
|
||||
}
|
||||
|
||||
pub fn create_event_at(device: &mut device::Device, name: usize, when: u64) {
|
||||
device.cpu.events[name] = Event {
|
||||
enabled: true,
|
||||
count: when,
|
||||
|
||||
@@ -33,11 +33,7 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
if device.cpu.cop0.is_event {
|
||||
interrupt_exception(device);
|
||||
} else {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_INT,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_INT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,13 +153,16 @@ pub fn init(device: &mut device::Device) {
|
||||
} else if (MM_DOM2_ADDR2 >> 16..=(MM_DOM2_ADDR2 + 0x1FFFF) >> 16).contains(&i) {
|
||||
device.memory.memory_map_read[i] = device::cart::sram::read_mem;
|
||||
device.memory.memory_map_write[i] = device::cart::sram::write_mem;
|
||||
} else if i >= MM_CART_ROM >> 16 && i <= (MM_CART_ROM + device.cart.rom.len() - 1) >> 16 {
|
||||
} else if device.cart.rom.len() <= MM_IS_VIEWER - MM_CART_ROM
|
||||
&& i >= MM_IS_VIEWER >> 16
|
||||
&& i <= (MM_IS_VIEWER + 0xFFFF) >> 16
|
||||
{
|
||||
device.memory.memory_map_read[i] = device::is_viewer::read_mem;
|
||||
device.memory.memory_map_write[i] = device::is_viewer::write_mem;
|
||||
} else if (MM_CART_ROM >> 16..=(MM_CART_ROM + 0x0FBFFFFF) >> 16).contains(&i) {
|
||||
device.memory.fast_read[i] = device::cart::rom::read_mem_fast;
|
||||
device.memory.memory_map_read[i] = device::cart::rom::read_mem;
|
||||
device.memory.memory_map_write[i] = device::cart::rom::write_mem;
|
||||
} else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 {
|
||||
device.memory.memory_map_read[i] = device::is_viewer::read_mem;
|
||||
device.memory.memory_map_write[i] = device::is_viewer::write_mem;
|
||||
} else if i >= MM_PIF_MEM >> 16 && i <= (MM_PIF_MEM + 0xFFFF) >> 16 {
|
||||
device.memory.memory_map_read[i] = device::pif::read_mem;
|
||||
device.memory.memory_map_write[i] = device::pif::write_mem;
|
||||
|
||||
+11
-12
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::{device, ui};
|
||||
use rand_chacha::rand_core::RngCore;
|
||||
|
||||
const PI_DRAM_ADDR_REG: u32 = 0;
|
||||
const PI_CART_ADDR_REG: u32 = 1;
|
||||
@@ -65,11 +66,7 @@ fn dma_read(device: &mut device::Device) {
|
||||
|
||||
let cycles = (handler.read)(device, cart_addr, dram_addr, length);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
|
||||
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
|
||||
device.pi.regs[PI_DRAM_ADDR_REG as usize] =
|
||||
@@ -86,6 +83,7 @@ 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 {
|
||||
@@ -97,11 +95,7 @@ fn dma_write(device: &mut device::Device) {
|
||||
|
||||
let cycles = (handler.write)(device, cart_addr, dram_addr, length);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_PI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
|
||||
);
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
|
||||
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
|
||||
device.pi.regs[PI_DRAM_ADDR_REG as usize] =
|
||||
@@ -172,7 +166,11 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_cycles(device: &device::Device, domain: i32, length: u32) -> u64 {
|
||||
fn randomize_interrupt_time(rng: &mut rand_chacha::ChaCha8Rng) -> u64 {
|
||||
rng.next_u64() % 0x10
|
||||
}
|
||||
|
||||
pub fn calculate_cycles(device: &mut device::Device, domain: i32, length: u32) -> u64 {
|
||||
let mut cycles: f64 = 0.0;
|
||||
let (page_size, latency, pulse_width, release, pages);
|
||||
let page_size_base: f64 = 2.0;
|
||||
@@ -195,6 +193,7 @@ pub fn calculate_cycles(device: &device::Device, domain: i32, length: u32) -> u6
|
||||
cycles += (14.0 + latency) * pages;
|
||||
cycles += (pulse_width + release) * (length as f64 / 2.0);
|
||||
cycles += 5.0 * pages;
|
||||
cycles += randomize_interrupt_time(&mut device.rng) as f64;
|
||||
(cycles * 1.5) as u64 // Converting RCP clock speed to CPU clock speed
|
||||
}
|
||||
|
||||
|
||||
+12
-7
@@ -55,7 +55,7 @@ pub fn read_mem(
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let mut masked_address = address as usize & PIF_MASK;
|
||||
if masked_address < PIF_RAM_OFFSET {
|
||||
panic! {"write to pif rom"}
|
||||
panic!("write to pif rom")
|
||||
}
|
||||
masked_address -= PIF_RAM_OFFSET;
|
||||
let mut data = u32::from_be_bytes(
|
||||
@@ -67,11 +67,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
device.pif.ram[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
|
||||
|
||||
device.si.dma_dir = device::si::DmaDir::Write;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_SI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 3200,
|
||||
); //based on https://github.com/rasky/n64-systembench
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SI, 3200); //based on https://github.com/rasky/n64-systembench
|
||||
device.si.regs[device::si::SI_STATUS_REG as usize] |=
|
||||
device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY
|
||||
}
|
||||
@@ -250,13 +246,22 @@ pub fn init(device: &mut device::Device) {
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: device::controller::PakType::MemPak,
|
||||
};
|
||||
let tpak_handler = device::controller::PakHandler {
|
||||
read: device::controller::transferpak::read,
|
||||
write: device::controller::transferpak::write,
|
||||
pak_type: device::controller::PakType::TransferPak,
|
||||
};
|
||||
|
||||
connect_pif_channels(device);
|
||||
|
||||
for i in 0..4 {
|
||||
if device.netplay.is_none() {
|
||||
if device.ui.config.input.controller_enabled[i] {
|
||||
device.pif.channels[i].pak_handler = Some(mempak_handler);
|
||||
if device.transferpaks[i].cart.rom.is_empty() {
|
||||
device.pif.channels[i].pak_handler = Some(mempak_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);
|
||||
|
||||
+1
-5
@@ -130,11 +130,7 @@ fn run_rdp(device: &mut device::Device) {
|
||||
device.load_state = false;
|
||||
savestates::load_savestate(device);
|
||||
}
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_DP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
)
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_DP, timer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -1,5 +1,6 @@
|
||||
use crate::device;
|
||||
use std::alloc::{alloc_zeroed, Layout};
|
||||
use crate::ui;
|
||||
use std::alloc::{Layout, alloc_zeroed};
|
||||
|
||||
//const RDRAM_CONFIG_REG: u32 = 0;
|
||||
//const RDRAM_DEVICE_ID_REG: u32 = 1;
|
||||
@@ -46,6 +47,7 @@ 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()
|
||||
@@ -58,6 +60,7 @@ 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()
|
||||
@@ -103,13 +106,12 @@ pub fn init(device: &mut device::Device) {
|
||||
unsafe { Vec::from_raw_parts(ptr, device.rdram.size as usize, device.rdram.size as usize) };
|
||||
|
||||
// hack, skip RDRAM initialization
|
||||
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
|
||||
.copy_from_slice(&device.rdram.size.to_ne_bytes());
|
||||
device.rdram.mem[0x318..0x318 + 4].copy_from_slice(&device.rdram.size.to_ne_bytes());
|
||||
// hack, skip RDRAM initialization
|
||||
device.rdram.mem[0x3f0..0x3f0 + 4].copy_from_slice(&device.rdram.size.to_ne_bytes());
|
||||
|
||||
device.ri.regs[device::ri::RI_MODE_REG as usize] = 0x0e;
|
||||
device.ri.regs[device::ri::RI_CONFIG_REG as usize] = 0x40;
|
||||
device.ri.regs[device::ri::RI_SELECT_REG as usize] = 0x14;
|
||||
device.ri.regs[device::ri::RI_REFRESH_REG as usize] = 0x00063634;
|
||||
}
|
||||
|
||||
pub fn rdram_calculate_cycles(length: u64) -> u64 {
|
||||
|
||||
+16
-9
@@ -22,17 +22,24 @@ pub fn read_regs(
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG && !device.ri.ram_init {
|
||||
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
|
||||
device.ri.ram_init = true;
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
RI_SELECT_REG => {
|
||||
if !device.ri.ram_init {
|
||||
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
|
||||
device.ri.ram_init = true;
|
||||
}
|
||||
0x14 // hack, skip RDRAM initialization
|
||||
}
|
||||
RI_REFRESH_REG => 0x00063634, // hack, skip RDRAM initialization
|
||||
_ => device.ri.regs[reg as usize],
|
||||
}
|
||||
device.ri.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
device::memory::masked_write_32(
|
||||
&mut device.ri.regs[((address & 0xFFFF) >> 2) as usize],
|
||||
value,
|
||||
mask,
|
||||
);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
if reg as u32 == RI_SELECT_REG {
|
||||
device.ri.ram_init = false;
|
||||
}
|
||||
device::memory::masked_write_32(&mut device.ri.regs[reg as usize], value, mask);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
const SP_MEM_ADDR_REG: u32 = 0;
|
||||
const SP_DRAM_ADDR_REG: u32 = 1;
|
||||
@@ -146,6 +147,7 @@ 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 {
|
||||
@@ -204,9 +206,7 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_SPDMA,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
|
||||
+ device::rdram::rdram_calculate_cycles((count * length) as u64)
|
||||
+ 9,
|
||||
device::rdram::rdram_calculate_cycles((count * length) as u64) + 9,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -465,11 +465,7 @@ fn do_task(device: &mut device::Device) {
|
||||
} else {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_SP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
)
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SP, timer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-10
@@ -1,3 +1,5 @@
|
||||
use rand_chacha::rand_core::RngCore;
|
||||
|
||||
use crate::device;
|
||||
|
||||
const SI_DRAM_ADDR_REG: u32 = 0;
|
||||
@@ -36,6 +38,10 @@ pub fn read_regs(
|
||||
device.si.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
fn randomize_interrupt_time(rng: &mut rand_chacha::ChaCha8Rng) -> u64 {
|
||||
rng.next_u64() % 0x100
|
||||
}
|
||||
|
||||
fn dma_read(device: &mut device::Device) {
|
||||
device.si.dma_dir = DmaDir::Read;
|
||||
|
||||
@@ -43,11 +49,9 @@ fn dma_read(device: &mut device::Device) {
|
||||
|
||||
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_SI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + duration,
|
||||
)
|
||||
let length = duration + randomize_interrupt_time(&mut device.rng);
|
||||
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
|
||||
}
|
||||
|
||||
fn dma_write(device: &mut device::Device) {
|
||||
@@ -57,11 +61,9 @@ fn dma_write(device: &mut device::Device) {
|
||||
|
||||
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_SI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 6000, //based on https://github.com/rasky/n64-systembench
|
||||
)
|
||||
let length = 6000 + randomize_interrupt_time(&mut device.rng); //based on https://github.com/rasky/n64-systembench
|
||||
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
|
||||
+5
-9
@@ -26,6 +26,7 @@ pub struct Vi {
|
||||
#[serde(skip)]
|
||||
pub limiter: Option<governor::DefaultDirectRateLimiter>,
|
||||
pub count_per_scanline: u64,
|
||||
pub enable_speed_limiter: bool,
|
||||
pub vi_counter: u64,
|
||||
pub last_origin: u32,
|
||||
pub internal_frame_counter: u64,
|
||||
@@ -64,11 +65,7 @@ fn create_limiter(device: &mut device::Device) {
|
||||
|
||||
fn set_vertical_interrupt(device: &mut device::Device) {
|
||||
if device::events::get_event(device, device::events::EVENT_TYPE_VI).is_none() {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EVENT_TYPE_VI,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.vi.delay,
|
||||
)
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_VI, device.vi.delay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,14 +136,13 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
ui::video::check_callback(device);
|
||||
|
||||
let mut enable_speed_limiter = true;
|
||||
if device.netplay.is_some() {
|
||||
netplay::send_sync_check(device);
|
||||
enable_speed_limiter = !device.netplay.as_ref().unwrap().fast_forward;
|
||||
device.vi.enable_speed_limiter = !device.netplay.as_ref().unwrap().fast_forward;
|
||||
}
|
||||
|
||||
device.vi.vi_counter += 1;
|
||||
if device.vi.vi_counter % device.vi.limit_freq == 0 && enable_speed_limiter {
|
||||
if device.vi.vi_counter % device.vi.limit_freq == 0 && device.vi.enable_speed_limiter {
|
||||
speed_limiter(device);
|
||||
}
|
||||
ui::video::update_screen();
|
||||
@@ -164,7 +160,7 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_VI);
|
||||
|
||||
device::events::create_event(
|
||||
device::events::create_event_at(
|
||||
device,
|
||||
device::events::EVENT_TYPE_VI,
|
||||
device.cpu.next_event_count + device.vi.delay,
|
||||
|
||||
+15
-3
@@ -161,16 +161,28 @@ async fn main() {
|
||||
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
|
||||
.spawn(move || {
|
||||
let mut device = device::Device::new();
|
||||
device.ui.fullscreen = args.fullscreen;
|
||||
device::run_game(rom_contents, &mut device);
|
||||
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([640.0, 480.0])
|
||||
.with_inner_size([854.0, 480.0])
|
||||
.with_icon(
|
||||
eframe::icon_data::from_png_bytes(include_bytes!("../data/gopher64.png"))
|
||||
.unwrap(),
|
||||
|
||||
+58
-30
@@ -1,5 +1,4 @@
|
||||
use crate::{device, ui};
|
||||
use eframe::egui;
|
||||
use std::io::{Read, Write};
|
||||
//UDP packet formats
|
||||
const UDP_SEND_KEY_INFO: u8 = 0;
|
||||
@@ -17,6 +16,10 @@ const TCP_REGISTER_PLAYER: u8 = 5;
|
||||
const TCP_GET_REGISTRATION: u8 = 6;
|
||||
const TCP_DISCONNECT_NOTICE: u8 = 7;
|
||||
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
|
||||
const TCP_SEND_RTC: u8 = 64;
|
||||
const TCP_RECEIVE_RTC: u8 = 128;
|
||||
const TCP_SEND_RNG: u8 = 65;
|
||||
const TCP_RECEIVE_RNG: u8 = 129;
|
||||
|
||||
const CS4: u32 = 32;
|
||||
|
||||
@@ -29,8 +32,6 @@ pub struct Netplay {
|
||||
status: u8,
|
||||
buffer_target: u8,
|
||||
pub fast_forward: bool,
|
||||
pub error_notifier: tokio::sync::mpsc::Sender<String>,
|
||||
pub gui_ctx: egui::Context,
|
||||
}
|
||||
|
||||
pub struct PlayerData {
|
||||
@@ -45,10 +46,44 @@ struct InputEvent {
|
||||
plugin: u8,
|
||||
}
|
||||
|
||||
fn send_error(netplay: &mut Netplay, error: String) {
|
||||
netplay.error_notifier.try_send(error).unwrap();
|
||||
pub const NETPLAY_ERROR_DESYNC: u32 = 0;
|
||||
pub const NETPLAY_ERROR_LOST_CONNECTION: u32 = 1;
|
||||
pub const NETPLAY_ERROR_PLAYER_DISCONNECTED: u32 = 2;
|
||||
pub const NETPLAY_ERROR_PLAYER_1_DISCONNECTED: u32 = 3;
|
||||
pub const NETPLAY_ERROR_PLAYER_2_DISCONNECTED: u32 = 4;
|
||||
pub const NETPLAY_ERROR_PLAYER_3_DISCONNECTED: u32 = 5;
|
||||
pub const NETPLAY_ERROR_PLAYER_4_DISCONNECTED: u32 = 6;
|
||||
|
||||
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
|
||||
pub fn send_rtc(netplay: &mut Netplay, rtc: i64) {
|
||||
let mut request: Vec<u8> = [TCP_SEND_RTC].to_vec();
|
||||
let size: u32 = 8;
|
||||
request.extend_from_slice(&size.to_be_bytes());
|
||||
request.extend_from_slice(&rtc.to_be_bytes());
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn receive_rtc(netplay: &mut Netplay) -> i64 {
|
||||
let request: Vec<u8> = [TCP_RECEIVE_RTC].to_vec();
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
let mut rtc: [u8; 8] = [0; 8];
|
||||
netplay.tcp_stream.read_exact(&mut rtc).unwrap();
|
||||
i64::from_be_bytes(rtc)
|
||||
}
|
||||
|
||||
pub fn send_rng(netplay: &mut Netplay, seed: u64) {
|
||||
let mut request: Vec<u8> = [TCP_SEND_RNG].to_vec();
|
||||
let size: u32 = 8;
|
||||
request.extend_from_slice(&size.to_be_bytes());
|
||||
request.extend_from_slice(&seed.to_be_bytes());
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn receive_rng(netplay: &mut Netplay) -> u64 {
|
||||
let request: Vec<u8> = [TCP_RECEIVE_RNG].to_vec();
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
let mut seed: [u8; 8] = [0; 8];
|
||||
netplay.tcp_stream.read_exact(&mut seed).unwrap();
|
||||
u64::from_be_bytes(seed)
|
||||
}
|
||||
|
||||
pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size: usize) {
|
||||
@@ -91,25 +126,25 @@ pub fn send_sync_check(device: &mut device::Device) {
|
||||
netplay.vi_counter = netplay.vi_counter.wrapping_add(1);
|
||||
}
|
||||
|
||||
pub fn send_input(netplay: &Netplay, input: (u32, bool)) {
|
||||
pub fn send_input(netplay: &Netplay, input: ui::input::InputData) {
|
||||
let mut request: Vec<u8> = [UDP_SEND_KEY_INFO].to_vec();
|
||||
request.push(netplay.player_number);
|
||||
request.extend_from_slice(
|
||||
&(netplay.player_data[netplay.player_number as usize].count).to_be_bytes(),
|
||||
);
|
||||
request.extend_from_slice(&(input.0).to_be_bytes());
|
||||
request.push(input.1 as u8);
|
||||
request.extend_from_slice(&(input.data).to_be_bytes());
|
||||
request.push(input.pak_change_pressed as u8);
|
||||
netplay.udp_socket.send(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::InputData {
|
||||
let netplay = device.netplay.as_mut().unwrap();
|
||||
let mut input = None;
|
||||
|
||||
let timeout = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
let mut request_timer = std::time::Instant::now() - std::time::Duration::from_millis(5);
|
||||
while input.is_none() {
|
||||
process_incoming(netplay); // we execute process_incoming before request_input so that we send an accurate buffer count
|
||||
process_incoming(netplay, &mut 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);
|
||||
@@ -120,10 +155,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
.remove(&netplay.player_data[channel].count);
|
||||
|
||||
if std::time::Instant::now() > timeout {
|
||||
send_error(
|
||||
netplay,
|
||||
"Timed out waiting for input. Lost connection to server".to_string(),
|
||||
);
|
||||
ui::audio::play_netplay_audio(&mut device.ui, NETPLAY_ERROR_LOST_CONNECTION);
|
||||
input = Some(InputEvent {
|
||||
input: 0,
|
||||
plugin: 0,
|
||||
@@ -136,10 +168,10 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
&& netplay.player_data[channel].input_events.len() as u8 > netplay.buffer_target;
|
||||
|
||||
netplay.player_data[channel].count = netplay.player_data[channel].count.wrapping_add(1);
|
||||
(
|
||||
input.as_ref().unwrap().input,
|
||||
input.as_ref().unwrap().plugin != 0,
|
||||
)
|
||||
ui::input::InputData {
|
||||
data: input.as_ref().unwrap().input,
|
||||
pak_change_pressed: input.as_ref().unwrap().plugin != 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn request_input(netplay: &Netplay, channel: usize) {
|
||||
@@ -154,7 +186,7 @@ fn request_input(netplay: &Netplay, channel: usize) {
|
||||
netplay.udp_socket.send(&request).unwrap();
|
||||
}
|
||||
|
||||
fn process_incoming(netplay: &mut Netplay) {
|
||||
fn process_incoming(netplay: &mut Netplay, ui: &mut ui::Ui) {
|
||||
let mut buf: [u8; 1024] = [0; 1024];
|
||||
while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) {
|
||||
match buf[0] {
|
||||
@@ -168,15 +200,15 @@ fn process_incoming(netplay: &mut Netplay) {
|
||||
}
|
||||
if current_status != netplay.status {
|
||||
if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 {
|
||||
send_error(
|
||||
netplay,
|
||||
format!("Players have desynced at VI {}", netplay.vi_counter),
|
||||
);
|
||||
ui::audio::play_netplay_audio(ui, NETPLAY_ERROR_DESYNC);
|
||||
}
|
||||
for dis in 1..5 {
|
||||
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
|
||||
{
|
||||
send_error(netplay, format!("Player {} has disconnected", dis));
|
||||
ui::audio::play_netplay_audio(
|
||||
ui,
|
||||
NETPLAY_ERROR_PLAYER_DISCONNECTED + dis,
|
||||
);
|
||||
}
|
||||
}
|
||||
netplay.status = current_status;
|
||||
@@ -208,7 +240,7 @@ fn process_incoming(netplay: &mut Netplay) {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic! {"unknown UDP packet"}
|
||||
panic!("unknown UDP packet")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,8 +250,6 @@ pub fn init(
|
||||
mut peer_addr: std::net::SocketAddr,
|
||||
session: ui::gui::gui_netplay::NetplayRoom,
|
||||
player_number: u8,
|
||||
error_notifier: tokio::sync::mpsc::Sender<String>,
|
||||
gui_ctx: egui::Context,
|
||||
) -> Netplay {
|
||||
peer_addr.set_port(session.port.unwrap() as u16);
|
||||
let udp_socket;
|
||||
@@ -283,8 +313,6 @@ pub fn init(
|
||||
status: 0,
|
||||
buffer_target,
|
||||
fast_forward: false,
|
||||
error_notifier,
|
||||
gui_ctx,
|
||||
player_data: [
|
||||
PlayerData {
|
||||
lag: 0,
|
||||
|
||||
+22
-2
@@ -70,6 +70,8 @@ where
|
||||
}
|
||||
|
||||
pub fn create_savestate(device: &device::Device) {
|
||||
ui::video::full_sync();
|
||||
|
||||
let data: &[(&[u8], &str)] = &[
|
||||
(&postcard::to_stdvec(device).unwrap(), "device"),
|
||||
(&postcard::to_stdvec(&device.ui.saves).unwrap(), "saves"),
|
||||
@@ -106,6 +108,15 @@ pub fn load_savestate(device: &mut device::Device) {
|
||||
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();
|
||||
}
|
||||
|
||||
device::memory::init(device);
|
||||
device::vi::set_expected_refresh_rate(device);
|
||||
device::cpu::map_instructions(device);
|
||||
@@ -164,6 +175,15 @@ pub fn load_savestate(device: &mut device::Device) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,8 +209,8 @@ where
|
||||
[device::cop0::reserved; N]
|
||||
}
|
||||
|
||||
pub fn default_memory_read(
|
||||
) -> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
|
||||
pub fn default_memory_read()
|
||||
-> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
|
||||
[device::rdram::read_mem; 0x2000]
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ pub struct Ui {
|
||||
pub game_hash: String,
|
||||
pub paths: storage::Paths,
|
||||
pub saves: storage::Saves,
|
||||
pub pak_audio: audio::PakAudio,
|
||||
pub event_audio: audio::EventAudio,
|
||||
pub window: *mut sdl3_sys::video::SDL_Window,
|
||||
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub event_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub audio_device: u32,
|
||||
pub num_joysticks: i32,
|
||||
pub fullscreen: bool,
|
||||
@@ -122,21 +122,49 @@ impl Ui {
|
||||
savestate_file_path: std::path::PathBuf::new(),
|
||||
},
|
||||
saves: storage::Saves {
|
||||
eeprom: (Vec::new(), false),
|
||||
sram: (Vec::new(), false),
|
||||
flash: (Vec::new(), false),
|
||||
mempak: (Vec::new(), false),
|
||||
sdcard: (Vec::new(), false),
|
||||
romsave: (std::collections::HashMap::new(), false),
|
||||
write_to_disk: true,
|
||||
eeprom: storage::Save {
|
||||
data: Vec::new(),
|
||||
written: false,
|
||||
},
|
||||
sram: storage::Save {
|
||||
data: Vec::new(),
|
||||
written: false,
|
||||
},
|
||||
flash: storage::Save {
|
||||
data: Vec::new(),
|
||||
written: false,
|
||||
},
|
||||
mempak: storage::Save {
|
||||
data: Vec::new(),
|
||||
written: false,
|
||||
},
|
||||
sdcard: storage::Save {
|
||||
data: Vec::new(),
|
||||
written: false,
|
||||
},
|
||||
romsave: storage::RomSave {
|
||||
data: std::collections::HashMap::new(),
|
||||
written: false,
|
||||
},
|
||||
},
|
||||
pak_audio: audio::PakAudio {
|
||||
event_audio: audio::EventAudio {
|
||||
mempak: include_bytes!("../data/mempak.wav").to_vec(),
|
||||
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
|
||||
transferpak: include_bytes!("../data/transferpak.wav").to_vec(),
|
||||
netplay_desync: include_bytes!("../data/netplay_desync.wav").to_vec(),
|
||||
netplay_lost_connection: include_bytes!("../data/netplay_lost_connection.wav")
|
||||
.to_vec(),
|
||||
netplay_disconnected: [
|
||||
include_bytes!("../data/netplay_p1_disconnected.wav").to_vec(),
|
||||
include_bytes!("../data/netplay_p2_disconnected.wav").to_vec(),
|
||||
include_bytes!("../data/netplay_p3_disconnected.wav").to_vec(),
|
||||
include_bytes!("../data/netplay_p4_disconnected.wav").to_vec(),
|
||||
],
|
||||
},
|
||||
window: std::ptr::null_mut(),
|
||||
audio_stream: std::ptr::null_mut(),
|
||||
pak_audio_stream: std::ptr::null_mut(),
|
||||
event_audio_stream: std::ptr::null_mut(),
|
||||
audio_device: 0,
|
||||
fullscreen: false,
|
||||
num_joysticks,
|
||||
|
||||
+51
-21
@@ -1,24 +1,33 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
|
||||
pub struct PakAudio {
|
||||
pub struct EventAudio {
|
||||
pub mempak: Vec<u8>,
|
||||
pub rumblepak: Vec<u8>,
|
||||
pub transferpak: Vec<u8>,
|
||||
pub netplay_desync: Vec<u8>,
|
||||
pub netplay_lost_connection: Vec<u8>,
|
||||
pub netplay_disconnected: [Vec<u8>; 4],
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui, frequency: u64) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
|
||||
|
||||
let audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
let game_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: frequency as i32,
|
||||
channels: 2,
|
||||
};
|
||||
let device_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: 48000,
|
||||
channels: 2,
|
||||
};
|
||||
ui.audio_device = unsafe {
|
||||
sdl3_sys::audio::SDL_OpenAudioDevice(
|
||||
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&audio_spec,
|
||||
&device_audio_spec,
|
||||
)
|
||||
};
|
||||
if ui.audio_device == 0 {
|
||||
@@ -32,7 +41,7 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
|
||||
panic!("Could not get audio device format");
|
||||
}
|
||||
|
||||
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
|
||||
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&game_audio_spec, &dst) };
|
||||
if ui.audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
@@ -46,8 +55,9 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
|
||||
channels: 1,
|
||||
};
|
||||
|
||||
ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
|
||||
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } {
|
||||
ui.event_audio_stream =
|
||||
unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
|
||||
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.event_audio_stream) } {
|
||||
panic!("Could not bind audio stream");
|
||||
}
|
||||
}
|
||||
@@ -58,33 +68,53 @@ pub fn close(ui: &mut ui::Ui) {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream);
|
||||
ui.audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
if !ui.pak_audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.pak_audio_stream);
|
||||
ui.pak_audio_stream = std::ptr::null_mut();
|
||||
if !ui.event_audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.event_audio_stream);
|
||||
ui.event_audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device);
|
||||
ui.audio_device = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) {
|
||||
if ui.event_audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
let sound = match error {
|
||||
netplay::NETPLAY_ERROR_DESYNC => &ui.event_audio.netplay_desync,
|
||||
netplay::NETPLAY_ERROR_LOST_CONNECTION => &ui.event_audio.netplay_lost_connection,
|
||||
netplay::NETPLAY_ERROR_PLAYER_1_DISCONNECTED => &ui.event_audio.netplay_disconnected[0],
|
||||
netplay::NETPLAY_ERROR_PLAYER_2_DISCONNECTED => &ui.event_audio.netplay_disconnected[1],
|
||||
netplay::NETPLAY_ERROR_PLAYER_3_DISCONNECTED => &ui.event_audio.netplay_disconnected[2],
|
||||
netplay::NETPLAY_ERROR_PLAYER_4_DISCONNECTED => &ui.event_audio.netplay_disconnected[3],
|
||||
_ => panic!("Invalid netplay error"),
|
||||
};
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.event_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
|
||||
if ui.pak_audio_stream.is_null() {
|
||||
if ui.event_audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sound;
|
||||
if pak == device::controller::PakType::RumblePak {
|
||||
sound = &ui.pak_audio.rumblepak;
|
||||
} else if pak == device::controller::PakType::MemPak {
|
||||
sound = &ui.pak_audio.mempak;
|
||||
} else if pak == device::controller::PakType::TransferPak {
|
||||
sound = &ui.pak_audio.transferpak;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let sound = match pak {
|
||||
device::controller::PakType::RumblePak => &ui.event_audio.rumblepak,
|
||||
device::controller::PakType::MemPak => &ui.event_audio.mempak,
|
||||
device::controller::PakType::TransferPak => &ui.event_audio.transferpak,
|
||||
_ => panic!("Invalid pak type"),
|
||||
};
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.pak_audio_stream,
|
||||
ui.event_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
|
||||
+41
-8
@@ -1,13 +1,33 @@
|
||||
use crate::ui;
|
||||
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputKeyButton {
|
||||
pub enabled: bool,
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputControllerAxis {
|
||||
pub enabled: bool,
|
||||
pub id: i32,
|
||||
pub axis: i16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputJoystickHat {
|
||||
pub enabled: bool,
|
||||
pub id: i32,
|
||||
pub direction: u8,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputProfile {
|
||||
pub keys: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub joystick_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub joystick_hat: [(bool, i32, u8); ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub keys: [InputKeyButton; ui::input::PROFILE_SIZE],
|
||||
pub controller_buttons: [InputKeyButton; ui::input::PROFILE_SIZE],
|
||||
pub controller_axis: [InputControllerAxis; ui::input::PROFILE_SIZE],
|
||||
pub joystick_buttons: [InputKeyButton; ui::input::PROFILE_SIZE],
|
||||
pub joystick_hat: [InputJoystickHat; ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [InputControllerAxis; ui::input::PROFILE_SIZE],
|
||||
pub dinput: bool,
|
||||
}
|
||||
|
||||
@@ -17,18 +37,28 @@ pub struct Input {
|
||||
pub input_profile_binding: [String; 4],
|
||||
pub controller_assignment: [Option<String>; 4],
|
||||
pub controller_enabled: [bool; 4],
|
||||
pub transfer_pak: [bool; 4],
|
||||
pub emulate_vru: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Video {
|
||||
pub upscale: bool,
|
||||
pub upscale: u32,
|
||||
pub integer_scaling: bool,
|
||||
pub fullscreen: bool,
|
||||
pub widescreen: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Emulation {
|
||||
pub overclock: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
pub input: Input,
|
||||
pub video: Video,
|
||||
pub emulation: Emulation,
|
||||
}
|
||||
|
||||
impl Drop for Config {
|
||||
@@ -68,13 +98,16 @@ impl Config {
|
||||
controller_assignment: [None, None, None, None],
|
||||
input_profiles,
|
||||
controller_enabled: [true, false, false, false],
|
||||
transfer_pak: [false, false, false, false],
|
||||
emulate_vru: false,
|
||||
},
|
||||
video: Video {
|
||||
upscale: false,
|
||||
upscale: 1,
|
||||
integer_scaling: false,
|
||||
fullscreen: false,
|
||||
widescreen: false,
|
||||
},
|
||||
emulation: Emulation { overclock: false },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+197
-39
@@ -12,28 +12,40 @@ pub struct GopherEguiApp {
|
||||
selected_profile: [String; 4],
|
||||
input_profiles: Vec<String>,
|
||||
controller_enabled: [bool; 4],
|
||||
transfer_pak: [bool; 4],
|
||||
controller_paths: Vec<String>,
|
||||
upscale: bool,
|
||||
upscale: u32,
|
||||
integer_scaling: bool,
|
||||
fullscreen: bool,
|
||||
widescreen: bool,
|
||||
overclock: bool,
|
||||
emulate_vru: bool,
|
||||
dinput: bool,
|
||||
show_vru_dialog: bool,
|
||||
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<String>>>,
|
||||
netplay_error_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
|
||||
vru_word_notifier: Option<tokio::sync::mpsc::Sender<String>>,
|
||||
vru_word_list: Vec<String>,
|
||||
pub netplay: gui_netplay::GuiNetplay,
|
||||
latest_version: Option<semver::Version>,
|
||||
update_receiver: Option<tokio::sync::mpsc::Receiver<GithubData>>,
|
||||
netplay: gui_netplay::GuiNetplay,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GithubData {
|
||||
tag_name: String,
|
||||
}
|
||||
|
||||
struct SaveConfig {
|
||||
selected_controller: [i32; 4],
|
||||
selected_profile: [String; 4],
|
||||
controller_enabled: [bool; 4],
|
||||
upscale: bool,
|
||||
transfer_pak: [bool; 4],
|
||||
upscale: u32,
|
||||
integer_scaling: bool,
|
||||
fullscreen: bool,
|
||||
widescreen: bool,
|
||||
emulate_vru: bool,
|
||||
overclock: bool,
|
||||
}
|
||||
|
||||
fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
|
||||
@@ -104,16 +116,20 @@ impl GopherEguiApp {
|
||||
controller_names,
|
||||
input_profiles: get_input_profiles(&config),
|
||||
controller_enabled: config.input.controller_enabled,
|
||||
transfer_pak: config.input.transfer_pak,
|
||||
upscale: config.video.upscale,
|
||||
integer_scaling: config.video.integer_scaling,
|
||||
fullscreen: config.video.fullscreen,
|
||||
widescreen: config.video.widescreen,
|
||||
emulate_vru: config.input.emulate_vru,
|
||||
overclock: config.emulation.overclock,
|
||||
show_vru_dialog: false,
|
||||
dinput: false,
|
||||
controller_paths,
|
||||
netplay_error_receiver: None,
|
||||
vru_window_receiver: None,
|
||||
vru_word_notifier: None,
|
||||
latest_version: None,
|
||||
update_receiver: None,
|
||||
vru_word_list: Vec::new(),
|
||||
netplay: Default::default(),
|
||||
dirs: ui::get_dirs(),
|
||||
@@ -137,11 +153,15 @@ fn save_config(
|
||||
|
||||
config.input.input_profile_binding = save_config_items.selected_profile;
|
||||
config.input.controller_enabled = save_config_items.controller_enabled;
|
||||
config.input.transfer_pak = save_config_items.transfer_pak;
|
||||
|
||||
config.video.upscale = save_config_items.upscale;
|
||||
config.video.integer_scaling = save_config_items.integer_scaling;
|
||||
config.video.fullscreen = save_config_items.fullscreen;
|
||||
config.video.widescreen = save_config_items.widescreen;
|
||||
config.input.emulate_vru = save_config_items.emulate_vru;
|
||||
|
||||
config.emulation.overclock = save_config_items.overclock;
|
||||
}
|
||||
|
||||
impl Drop for GopherEguiApp {
|
||||
@@ -150,10 +170,13 @@ impl Drop for GopherEguiApp {
|
||||
selected_controller: self.selected_controller,
|
||||
selected_profile: self.selected_profile.clone(),
|
||||
controller_enabled: self.controller_enabled,
|
||||
transfer_pak: self.transfer_pak,
|
||||
upscale: self.upscale,
|
||||
integer_scaling: self.integer_scaling,
|
||||
fullscreen: self.fullscreen,
|
||||
widescreen: self.widescreen,
|
||||
emulate_vru: self.emulate_vru,
|
||||
overclock: self.overclock,
|
||||
};
|
||||
let mut config = ui::config::Config::new();
|
||||
save_config(
|
||||
@@ -239,16 +262,58 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
fn get_latest_version(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.update_receiver.is_none() {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||
app.update_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent(env!("CARGO_PKG_NAME"))
|
||||
.build()
|
||||
.unwrap();
|
||||
let task = client
|
||||
.get("https://api.github.com/repos/gopher64/gopher64/releases/latest")
|
||||
.send();
|
||||
tokio::spawn(async move {
|
||||
let response = task.await;
|
||||
if let Ok(response) = response {
|
||||
let data: Result<GithubData, reqwest::Error> = response.json().await;
|
||||
if data.is_ok() {
|
||||
tx.send(data.unwrap()).await.unwrap();
|
||||
} else {
|
||||
tx.send(GithubData {
|
||||
tag_name: format!("v{}", env!("CARGO_PKG_VERSION")),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
gui_ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
} else if app.latest_version.is_none() {
|
||||
let result = app.update_receiver.as_mut().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
let tag = &result.unwrap().tag_name[1..];
|
||||
app.latest_version = Some(semver::Version::parse(tag).unwrap());
|
||||
} else {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context, enable_overclock: bool) {
|
||||
let netplay;
|
||||
|
||||
let selected_controller = app.selected_controller;
|
||||
let selected_profile = app.selected_profile.clone();
|
||||
let controller_enabled = app.controller_enabled;
|
||||
let transfer_pak = app.transfer_pak;
|
||||
let upscale = app.upscale;
|
||||
let integer_scaling = app.integer_scaling;
|
||||
let fullscreen = app.fullscreen;
|
||||
let widescreen = app.widescreen;
|
||||
let emulate_vru = app.emulate_vru;
|
||||
let overclock = app.overclock;
|
||||
let peer_addr;
|
||||
let session;
|
||||
let player_number;
|
||||
@@ -267,11 +332,6 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
player_number = Some(app.netplay.player_number);
|
||||
}
|
||||
|
||||
let (netplay_error_notifier, netplay_error_receiver): (
|
||||
tokio::sync::mpsc::Sender<String>,
|
||||
tokio::sync::mpsc::Receiver<String>,
|
||||
) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
let (vru_window_notifier, vru_window_receiver): (
|
||||
tokio::sync::mpsc::Sender<Vec<String>>,
|
||||
tokio::sync::mpsc::Receiver<Vec<String>>,
|
||||
@@ -282,9 +342,6 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
tokio::sync::mpsc::Receiver<String>,
|
||||
) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
if netplay {
|
||||
app.netplay_error_receiver = Some(netplay_error_receiver);
|
||||
}
|
||||
if emulate_vru && !netplay {
|
||||
app.vru_window_receiver = Some(vru_window_receiver);
|
||||
app.vru_word_notifier = Some(vru_word_notifier);
|
||||
@@ -296,12 +353,47 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let rom_contents = app.netplay.rom_contents.clone();
|
||||
let gui_ctx = ctx.clone();
|
||||
|
||||
let mut task = None;
|
||||
let mut select_rom = None;
|
||||
let mut select_gb_rom = [None, None, None, None];
|
||||
let mut select_gb_ram = [None, None, None, None];
|
||||
|
||||
if !netplay {
|
||||
task = Some(rfd::AsyncFileDialog::new().pick_file());
|
||||
select_rom = Some(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_title("Select ROM")
|
||||
.pick_file(),
|
||||
);
|
||||
for i in 0..4 {
|
||||
if transfer_pak[i] {
|
||||
select_gb_rom[i] = Some(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_title(format!("GB ROM P{}", i + 1))
|
||||
.pick_file(),
|
||||
);
|
||||
select_gb_ram[i] = Some(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_title(format!("GB RAM P{}", i + 1))
|
||||
.pick_file(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::spawn(async move {
|
||||
let file = if !netplay { task.unwrap().await } else { None };
|
||||
let file = if !netplay {
|
||||
select_rom.unwrap().await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut gb_rom_path = [None, None, None, None];
|
||||
let mut gb_ram_path = [None, None, None, None];
|
||||
if !netplay {
|
||||
for i in 0..4 {
|
||||
if transfer_pak[i] {
|
||||
gb_rom_path[i] = select_gb_rom[i].as_mut().unwrap().await;
|
||||
gb_ram_path[i] = select_gb_ram[i].as_mut().unwrap().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::Builder::new()
|
||||
.name("n64".to_string())
|
||||
@@ -311,10 +403,13 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
selected_controller,
|
||||
selected_profile,
|
||||
controller_enabled,
|
||||
transfer_pak,
|
||||
upscale,
|
||||
integer_scaling,
|
||||
fullscreen,
|
||||
widescreen,
|
||||
emulate_vru,
|
||||
overclock,
|
||||
};
|
||||
|
||||
if file.is_some() || netplay {
|
||||
@@ -336,13 +431,20 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
peer_addr.unwrap(),
|
||||
session.unwrap(),
|
||||
player_number.unwrap(),
|
||||
netplay_error_notifier,
|
||||
gui_ctx,
|
||||
));
|
||||
device.ui.fullscreen = fullscreen;
|
||||
device::run_game(rom_contents, &mut device);
|
||||
device::run_game(&mut device, rom_contents, fullscreen, enable_overclock);
|
||||
netplay::close(&mut device);
|
||||
} else {
|
||||
for i in 0..4 {
|
||||
if gb_rom_path[i].is_some() && gb_ram_path[i].is_some() {
|
||||
device.transferpaks[i].cart.rom =
|
||||
std::fs::read(gb_rom_path[i].as_ref().unwrap().path()).unwrap();
|
||||
|
||||
device.transferpaks[i].cart.ram =
|
||||
std::fs::read(gb_ram_path[i].as_ref().unwrap().path()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if emulate_vru {
|
||||
device.vru_window.window_notifier = Some(vru_window_notifier);
|
||||
device.vru_window.word_receiver = Some(vru_word_receiver);
|
||||
@@ -353,8 +455,12 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if rom_contents.is_empty() {
|
||||
println!("Could not read rom file");
|
||||
} else {
|
||||
device.ui.fullscreen = fullscreen;
|
||||
device::run_game(rom_contents, &mut device);
|
||||
device::run_game(
|
||||
&mut device,
|
||||
rom_contents,
|
||||
fullscreen,
|
||||
enable_overclock,
|
||||
);
|
||||
}
|
||||
}
|
||||
let result = std::fs::remove_file(running_file);
|
||||
@@ -381,13 +487,6 @@ impl eframe::App for GopherEguiApp {
|
||||
gui_netplay::netplay_wait(self, ctx);
|
||||
}
|
||||
|
||||
if self.netplay_error_receiver.is_some() {
|
||||
let result = self.netplay_error_receiver.as_mut().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
self.netplay.error = result.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if !self.netplay.error.is_empty() {
|
||||
gui_netplay::netplay_error(self, ctx, self.netplay.error.clone());
|
||||
}
|
||||
@@ -404,7 +503,7 @@ impl eframe::App for GopherEguiApp {
|
||||
.min_col_width(200.0)
|
||||
.show(ui, |ui| {
|
||||
if ui.button("Open ROM").clicked() {
|
||||
open_rom(self, ctx);
|
||||
open_rom(self, ctx, self.overclock);
|
||||
}
|
||||
if ui.button("Netplay: Create Session").clicked()
|
||||
&& !self.dirs.cache_dir.join("game_running").exists()
|
||||
@@ -412,6 +511,19 @@ impl eframe::App for GopherEguiApp {
|
||||
self.netplay.create = true;
|
||||
}
|
||||
|
||||
if ui.button("Open Saves Folder").clicked() {
|
||||
let command = if cfg!(target_os = "windows") {
|
||||
"explorer"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"xdg-open"
|
||||
} else {
|
||||
panic!("Unsupported platform");
|
||||
};
|
||||
let _ = std::process::Command::new(command)
|
||||
.arg(self.dirs.data_dir.join("saves"))
|
||||
.spawn();
|
||||
}
|
||||
|
||||
ui.end_row();
|
||||
|
||||
if ui.button("Configure Input Profile").clicked()
|
||||
@@ -432,12 +544,28 @@ impl eframe::App for GopherEguiApp {
|
||||
egui::Grid::new("controller_config").show(ui, |ui| {
|
||||
ui.label("Port");
|
||||
ui.label("Enabled");
|
||||
ui.label("Emulate VRU");
|
||||
ui.label("Transfer Pak");
|
||||
ui.label("Profile");
|
||||
ui.label("Controller");
|
||||
ui.end_row();
|
||||
for i in 0..4 {
|
||||
ui.label(format!("{}", i + 1));
|
||||
ui.checkbox(&mut self.controller_enabled[i], "");
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.checkbox(&mut self.controller_enabled[i], "");
|
||||
});
|
||||
let mut vru = false;
|
||||
ui.centered_and_justified(|ui| {
|
||||
if i < 3 {
|
||||
ui.add_enabled(false, egui::Checkbox::new(&mut vru, ""));
|
||||
} else {
|
||||
ui.add_enabled(true, egui::Checkbox::new(&mut self.emulate_vru, ""));
|
||||
}
|
||||
});
|
||||
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.checkbox(&mut self.transfer_pak[i], "");
|
||||
});
|
||||
|
||||
egui::ComboBox::from_id_salt(format!("profile-combo-{}", i))
|
||||
.selected_text(self.selected_profile[i].clone())
|
||||
@@ -476,18 +604,46 @@ impl eframe::App for GopherEguiApp {
|
||||
}
|
||||
});
|
||||
ui.add_space(32.0);
|
||||
ui.checkbox(&mut self.upscale, "High-Res Graphics");
|
||||
let upscale_values = [1, 2, 4];
|
||||
let mut slider_value = match self.upscale {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
4 => 2,
|
||||
_ => 0,
|
||||
};
|
||||
let display_text = format!("{}x Resolution", upscale_values[slider_value]);
|
||||
if ui
|
||||
.add(
|
||||
egui::Slider::new(&mut slider_value, 0..=2)
|
||||
.show_value(false)
|
||||
.text(display_text),
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
self.upscale = upscale_values[slider_value];
|
||||
};
|
||||
ui.checkbox(&mut self.integer_scaling, "Integer Scaling");
|
||||
ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)");
|
||||
ui.add_space(32.0);
|
||||
ui.checkbox(&mut self.widescreen, "Widescreen (stretch)");
|
||||
|
||||
ui.checkbox(
|
||||
&mut self.emulate_vru,
|
||||
"Emulate VRU (connects VRU to controller port 4)",
|
||||
);
|
||||
ui.add_space(16.0);
|
||||
ui.checkbox(&mut self.overclock, "Overclock N64 CPU (may cause bugs)");
|
||||
ui.add_space(16.0);
|
||||
|
||||
ui.hyperlink_to("Wiki", "https://github.com/gopher64/gopher64/wiki");
|
||||
ui.hyperlink_to("Discord Server", "https://discord.gg/9RGXq8W8JQ");
|
||||
ui.add_space(16.0);
|
||||
|
||||
ui.add_space(32.0);
|
||||
ui.label(format!("Version: {}", env!("CARGO_PKG_VERSION")));
|
||||
if self.latest_version.is_some() {
|
||||
let current_version = semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
||||
if current_version < *self.latest_version.as_ref().unwrap() {
|
||||
ui.hyperlink_to(
|
||||
"New version available!",
|
||||
"https://github.com/gopher64/gopher64/releases/latest",
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if self.emulate_vru && self.vru_window_receiver.is_some() {
|
||||
@@ -501,6 +657,8 @@ impl eframe::App for GopherEguiApp {
|
||||
if self.show_vru_dialog {
|
||||
show_vru_dialog(self, ctx);
|
||||
}
|
||||
|
||||
get_latest_version(self, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+124
-57
@@ -7,7 +7,19 @@ use sha2::{Digest, Sha256};
|
||||
const NETPLAY_VERSION: i32 = 17;
|
||||
const EMU_NAME: &str = "gopher64";
|
||||
|
||||
type GameInfo = (String, String, String, Vec<u8>);
|
||||
#[derive(Default)]
|
||||
pub struct GameInfo {
|
||||
pub md5: String,
|
||||
pub game_name: String,
|
||||
pub rom_label: String,
|
||||
pub rom_contents: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Default)]
|
||||
pub struct NetplayServer {
|
||||
pub name: String,
|
||||
pub ip: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GuiNetplay {
|
||||
@@ -21,7 +33,7 @@ pub struct GuiNetplay {
|
||||
pub create_rom_label: String,
|
||||
pub join_rom_label: String,
|
||||
pub send_chat: bool,
|
||||
pub have_sessions: Option<(String, String)>,
|
||||
pub have_sessions: Option<NetplayServer>,
|
||||
pub begin_game: bool,
|
||||
pub chat_log: String,
|
||||
pub chat_message: String,
|
||||
@@ -33,10 +45,10 @@ pub struct GuiNetplay {
|
||||
pub rom_contents: Vec<u8>,
|
||||
pub player_number: u8,
|
||||
pub player_names: [String; 4],
|
||||
pub server: (String, String),
|
||||
pub server: NetplayServer,
|
||||
pub socket_waiting: bool,
|
||||
pub game_info: GameInfo,
|
||||
pub servers: std::collections::HashMap<String, String>,
|
||||
pub servers: Vec<NetplayServer>,
|
||||
pub waiting_session: Option<NetplayRoom>,
|
||||
pub socket:
|
||||
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
|
||||
@@ -56,6 +68,8 @@ pub struct NetplayRoom {
|
||||
md5: Option<String>,
|
||||
game_name: Option<String>,
|
||||
pub port: Option<i32>,
|
||||
features: Option<std::collections::HashMap<String, String>>,
|
||||
buffer_target: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -131,10 +145,14 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
serde_json::from_slice(&buffer[..amt]).unwrap();
|
||||
for server in data.iter() {
|
||||
let (server_name, server_ip) = server;
|
||||
app.netplay
|
||||
.servers
|
||||
.insert(server_name.to_string(), server_ip.to_string());
|
||||
app.netplay.server = (server.0.clone(), server.1.clone());
|
||||
app.netplay.servers.push(NetplayServer {
|
||||
name: server_name.to_string(),
|
||||
ip: server_ip.to_string(),
|
||||
});
|
||||
app.netplay.server = NetplayServer {
|
||||
name: server.0.clone(),
|
||||
ip: server.1.clone(),
|
||||
};
|
||||
}
|
||||
app.netplay.broadcast_socket = None;
|
||||
}
|
||||
@@ -143,11 +161,19 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.server_receiver.is_some() {
|
||||
let result = app.netplay.server_receiver.as_mut().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.servers.extend(result.unwrap());
|
||||
for server in result.unwrap().iter() {
|
||||
app.netplay.servers.push(NetplayServer {
|
||||
name: server.0.clone(),
|
||||
ip: server.1.clone(),
|
||||
});
|
||||
}
|
||||
app.netplay.server_receiver = None;
|
||||
if app.netplay.server.0.is_empty() {
|
||||
let first_server = app.netplay.servers.iter().next().unwrap();
|
||||
app.netplay.server = (first_server.0.clone(), first_server.1.clone());
|
||||
if app.netplay.server.name.is_empty() {
|
||||
let first_server = app.netplay.servers.first().unwrap();
|
||||
app.netplay.server = NetplayServer {
|
||||
name: first_server.name.clone(),
|
||||
ip: first_server.ip.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
@@ -183,19 +209,21 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.create_rom_label = "Inspecting ROM".to_string();
|
||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||
let task = rfd::AsyncFileDialog::new()
|
||||
.set_title("Select ROM")
|
||||
.pick_file();
|
||||
tokio::spawn(async move {
|
||||
let file = task.await;
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx).await;
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Open ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
tx.send(GameInfo {
|
||||
md5: "".to_string(),
|
||||
game_name: "".to_string(),
|
||||
rom_label: "Open ROM".to_string(),
|
||||
rom_contents: vec![],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -221,25 +249,28 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
if !data.0.is_empty() {
|
||||
if !data.md5.is_empty() {
|
||||
app.netplay.game_info = data;
|
||||
app.netplay.rom_contents = app.netplay.game_info.3.clone();
|
||||
app.netplay.create_rom_label = app.netplay.game_info.2.clone();
|
||||
app.netplay.rom_contents = app.netplay.game_info.rom_contents.clone();
|
||||
app.netplay.create_rom_label = app.netplay.game_info.rom_label.clone();
|
||||
} else {
|
||||
app.netplay.create_rom_label = data.2;
|
||||
app.netplay.create_rom_label = data.rom_label;
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
egui::ComboBox::from_id_salt("server-combobox")
|
||||
.selected_text(app.netplay.server.0.to_string())
|
||||
.selected_text(app.netplay.server.name.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
for server in app.netplay.servers.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.server,
|
||||
(server.0.clone(), server.1.clone()),
|
||||
server.0,
|
||||
NetplayServer {
|
||||
name: server.name.clone(),
|
||||
ip: server.ip.clone(),
|
||||
},
|
||||
server.name.clone(),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -271,16 +302,18 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
app.netplay.error = "Player Name cannot be empty".to_string();
|
||||
} else if app.netplay.session_name.is_empty() {
|
||||
app.netplay.error = "Session Name cannot be empty".to_string();
|
||||
} else if app.netplay.game_info.0.is_empty() {
|
||||
} else if app.netplay.game_info.md5.is_empty() {
|
||||
app.netplay.error = "ROM not loaded".to_string();
|
||||
} else {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let mut game_name = app.netplay.game_info.1.to_string();
|
||||
let mut game_name = app.netplay.game_info.game_name.to_string();
|
||||
if game_name.is_empty() {
|
||||
// If the ROM doesn't report a name, use the filename
|
||||
game_name = app.netplay.create_rom_label.clone();
|
||||
}
|
||||
let mut features = std::collections::HashMap::new();
|
||||
features.insert("overclock".to_string(), app.overclock.to_string());
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_create_room".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
@@ -297,13 +330,15 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
room_name: Some(app.netplay.session_name.clone()),
|
||||
password: Some(app.netplay.password.clone()),
|
||||
game_name: Some(game_name),
|
||||
md5: Some(app.netplay.game_info.0.clone()),
|
||||
md5: Some(app.netplay.game_info.md5.clone()),
|
||||
protected: None,
|
||||
port: None,
|
||||
features: Some(features),
|
||||
buffer_target: Some(2),
|
||||
}),
|
||||
};
|
||||
let (mut socket, _response) =
|
||||
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
|
||||
tungstenite::connect(&app.netplay.server.ip).expect("Can't connect");
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
|
||||
serde_json::to_vec(&netplay_message).unwrap(),
|
||||
@@ -350,7 +385,7 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
}
|
||||
if app.netplay.socket.is_none() {
|
||||
let (mut sock, _response) =
|
||||
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
|
||||
tungstenite::connect(&app.netplay.server.ip).expect("Can't connect");
|
||||
match sock.get_mut() {
|
||||
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
|
||||
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
|
||||
@@ -385,7 +420,10 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
serde_json::to_vec(&request_rooms).unwrap(),
|
||||
)))
|
||||
.unwrap();
|
||||
app.netplay.have_sessions = Some(app.netplay.server.clone());
|
||||
app.netplay.have_sessions = Some(NetplayServer {
|
||||
name: app.netplay.server.name.clone(),
|
||||
ip: app.netplay.server.ip.clone(),
|
||||
});
|
||||
app.netplay.socket_waiting = true;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
@@ -397,16 +435,21 @@ async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<Gam
|
||||
let hash = device::cart::rom::calculate_hash(&rom_contents);
|
||||
let game_name = ui::storage::get_game_name(&rom_contents);
|
||||
|
||||
tx.send((hash, game_name, file.file_name(), rom_contents))
|
||||
.await
|
||||
.unwrap();
|
||||
tx.send(GameInfo {
|
||||
md5: hash,
|
||||
game_name,
|
||||
rom_label: file.file_name(),
|
||||
rom_contents,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
tx.send(GameInfo {
|
||||
md5: "".to_string(),
|
||||
game_name: "".to_string(),
|
||||
rom_label: "Invalid ROM".to_string(),
|
||||
rom_contents: vec![],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -442,9 +485,9 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
if !data.0.is_empty() {
|
||||
if !data.md5.is_empty() {
|
||||
app.netplay.game_info = data;
|
||||
app.netplay.rom_contents = app.netplay.game_info.3.clone();
|
||||
app.netplay.rom_contents = app.netplay.game_info.rom_contents.clone();
|
||||
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_join_room".to_string(),
|
||||
@@ -462,9 +505,11 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
room_name: None,
|
||||
password: Some(app.netplay.password.clone()),
|
||||
game_name: None,
|
||||
md5: Some(app.netplay.game_info.0.clone()),
|
||||
md5: Some(app.netplay.game_info.md5.clone()),
|
||||
protected: None,
|
||||
port: app.netplay.selected_session.as_ref().unwrap().port,
|
||||
features: None,
|
||||
buffer_target: None,
|
||||
}),
|
||||
};
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
@@ -476,7 +521,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
|
||||
app.netplay.socket_waiting = true;
|
||||
} else {
|
||||
app.netplay.error = data.2;
|
||||
app.netplay.error = data.rom_label;
|
||||
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
|
||||
}
|
||||
}
|
||||
@@ -501,19 +546,22 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
egui::ComboBox::from_id_salt("server-combobox")
|
||||
.selected_text(app.netplay.server.0.to_string())
|
||||
.selected_text(app.netplay.server.name.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
for server in app.netplay.servers.iter() {
|
||||
ui.selectable_value(
|
||||
&mut app.netplay.server,
|
||||
(server.0.clone(), server.1.clone()),
|
||||
server.0,
|
||||
NetplayServer {
|
||||
name: server.name.clone(),
|
||||
ip: server.ip.clone(),
|
||||
},
|
||||
server.name.clone(),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if !app.netplay.server.0.is_empty() {
|
||||
if !app.netplay.server.name.is_empty() {
|
||||
get_sessions(app, ctx);
|
||||
}
|
||||
ui.add_space(16.0);
|
||||
@@ -583,19 +631,21 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.join_rom_label = "Inspecting ROM".to_string();
|
||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||
let task = rfd::AsyncFileDialog::new()
|
||||
.set_title("Select ROM")
|
||||
.pick_file();
|
||||
tokio::spawn(async move {
|
||||
let file = task.await;
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx).await;
|
||||
} else {
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"No ROM selected".to_string(),
|
||||
vec![],
|
||||
))
|
||||
tx.send(GameInfo {
|
||||
md5: "".to_string(),
|
||||
game_name: "".to_string(),
|
||||
rom_label: "No ROM selected".to_string(),
|
||||
rom_contents: vec![],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -609,6 +659,17 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
}
|
||||
|
||||
pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let overclock = app
|
||||
.netplay
|
||||
.waiting_session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.features
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get("overclock")
|
||||
.unwrap();
|
||||
|
||||
let motd_message = NetplayMessage {
|
||||
message_type: "request_motd".to_string(),
|
||||
player_name: None,
|
||||
@@ -642,6 +703,8 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
features: None,
|
||||
buffer_target: None,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -680,6 +743,8 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
features: None,
|
||||
buffer_target: None,
|
||||
}),
|
||||
};
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
@@ -711,6 +776,8 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
md5: None,
|
||||
protected: None,
|
||||
port: app.netplay.waiting_session.as_ref().unwrap().port,
|
||||
features: None,
|
||||
buffer_target: None,
|
||||
}),
|
||||
};
|
||||
app.netplay.chat_message.clear();
|
||||
@@ -759,7 +826,7 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
};
|
||||
}
|
||||
|
||||
gui::open_rom(app, ctx);
|
||||
gui::open_rom(app, ctx, *overclock == "true");
|
||||
app.netplay = Default::default();
|
||||
return;
|
||||
} else {
|
||||
|
||||
+317
-180
@@ -34,6 +34,11 @@ pub struct Controllers {
|
||||
pub joystick: *mut sdl3_sys::joystick::SDL_Joystick,
|
||||
}
|
||||
|
||||
pub struct InputData {
|
||||
pub data: u32,
|
||||
pub pak_change_pressed: bool,
|
||||
}
|
||||
|
||||
fn bound_axis(x: &mut f64, y: &mut f64) {
|
||||
let radius = 95.0; // this is roughly the maxium diagonal distance of the controller
|
||||
|
||||
@@ -54,35 +59,35 @@ fn set_axis_from_joystick(
|
||||
) -> (f64, f64) {
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
if profile.joystick_axis[AXIS_LEFT].0 {
|
||||
if profile.joystick_axis[AXIS_LEFT].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_LEFT].1)
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_LEFT].id)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_LEFT].2 as isize > 0 {
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_LEFT].axis as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.joystick_axis[AXIS_RIGHT].0 {
|
||||
if profile.joystick_axis[AXIS_RIGHT].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_RIGHT].1)
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_RIGHT].id)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].2 as isize > 0 {
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].axis as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.joystick_axis[AXIS_DOWN].0 {
|
||||
if profile.joystick_axis[AXIS_DOWN].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_DOWN].1)
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_DOWN].id)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_DOWN].2 as isize > 0 {
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_DOWN].axis as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
}
|
||||
if profile.joystick_axis[AXIS_UP].0 {
|
||||
if profile.joystick_axis[AXIS_UP].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_UP].1)
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_UP].id)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_UP].2 as isize > 0 {
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_UP].axis as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
}
|
||||
@@ -128,47 +133,47 @@ fn set_axis_from_controller(
|
||||
) -> (f64, f64) {
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
if profile.controller_axis[AXIS_LEFT].0 {
|
||||
if profile.controller_axis[AXIS_LEFT].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_LEFT].1),
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_LEFT].id),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_LEFT].2 as isize > 0 {
|
||||
if axis_position as isize * profile.controller_axis[AXIS_LEFT].axis as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.controller_axis[AXIS_RIGHT].0 {
|
||||
if profile.controller_axis[AXIS_RIGHT].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].1),
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].id),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_RIGHT].2 as isize > 0 {
|
||||
if axis_position as isize * profile.controller_axis[AXIS_RIGHT].axis as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.controller_axis[AXIS_DOWN].0 {
|
||||
if profile.controller_axis[AXIS_DOWN].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_DOWN].1),
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_DOWN].id),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_DOWN].2 as isize > 0 {
|
||||
if axis_position as isize * profile.controller_axis[AXIS_DOWN].axis as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
}
|
||||
if profile.controller_axis[AXIS_UP].0 {
|
||||
if profile.controller_axis[AXIS_UP].enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_UP].1),
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_UP].id),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_UP].2 as isize > 0 {
|
||||
if axis_position as isize * profile.controller_axis[AXIS_UP].axis as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
}
|
||||
@@ -181,23 +186,23 @@ fn set_axis_from_keys(
|
||||
) -> (f64, f64) {
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
if profile.keys[AXIS_LEFT].0
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_LEFT].1 as isize) }
|
||||
if profile.keys[AXIS_LEFT].enabled
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_LEFT].id as isize) }
|
||||
{
|
||||
x = -MAX_AXIS_VALUE
|
||||
}
|
||||
if profile.keys[AXIS_RIGHT].0
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_RIGHT].1 as isize) }
|
||||
if profile.keys[AXIS_RIGHT].enabled
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_RIGHT].id as isize) }
|
||||
{
|
||||
x = MAX_AXIS_VALUE
|
||||
}
|
||||
if profile.keys[AXIS_DOWN].0
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_DOWN].1 as isize) }
|
||||
if profile.keys[AXIS_DOWN].enabled
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_DOWN].id as isize) }
|
||||
{
|
||||
y = -MAX_AXIS_VALUE
|
||||
}
|
||||
if profile.keys[AXIS_UP].0
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_UP].1 as isize) }
|
||||
if profile.keys[AXIS_UP].enabled
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_UP].id as isize) }
|
||||
{
|
||||
y = MAX_AXIS_VALUE
|
||||
}
|
||||
@@ -211,26 +216,26 @@ fn set_buttons_from_joystick(
|
||||
keys: &mut u32,
|
||||
) {
|
||||
let profile_joystick_button = profile.joystick_buttons[i];
|
||||
if profile_joystick_button.0 {
|
||||
if profile_joystick_button.enabled {
|
||||
*keys |= (unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickButton(joystick, profile_joystick_button.1)
|
||||
sdl3_sys::joystick::SDL_GetJoystickButton(joystick, profile_joystick_button.id)
|
||||
} as u32)
|
||||
<< i;
|
||||
}
|
||||
|
||||
let profile_joystick_hat = profile.joystick_hat[i];
|
||||
if profile_joystick_hat.0
|
||||
&& unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.1) }
|
||||
== profile_joystick_hat.2
|
||||
if profile_joystick_hat.enabled
|
||||
&& unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.id) }
|
||||
== profile_joystick_hat.direction
|
||||
{
|
||||
*keys |= 1 << i;
|
||||
}
|
||||
|
||||
let profile_joystick_axis = profile.joystick_axis[i];
|
||||
if profile_joystick_axis.0 {
|
||||
if profile_joystick_axis.enabled {
|
||||
let axis_position =
|
||||
unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile_joystick_axis.1) };
|
||||
if axis_position as isize * profile_joystick_axis.2 as isize > 0
|
||||
unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile_joystick_axis.id) };
|
||||
if axis_position as isize * profile_joystick_axis.axis as isize > 0
|
||||
&& axis_position.saturating_abs() > i16::MAX / 2
|
||||
{
|
||||
*keys |= 1 << i;
|
||||
@@ -245,25 +250,25 @@ fn set_buttons_from_controller(
|
||||
keys: &mut u32,
|
||||
) {
|
||||
let profile_controller_button = profile.controller_buttons[i];
|
||||
if profile_controller_button.0 {
|
||||
if profile_controller_button.enabled {
|
||||
*keys |= (unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadButton(
|
||||
controller,
|
||||
get_button_from_i32(profile_controller_button.1),
|
||||
get_button_from_i32(profile_controller_button.id),
|
||||
)
|
||||
} as u32)
|
||||
<< i;
|
||||
}
|
||||
|
||||
let profile_controller_axis = profile.controller_axis[i];
|
||||
if profile_controller_axis.0 {
|
||||
if profile_controller_axis.enabled {
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile_controller_axis.1),
|
||||
get_axis_from_i32(profile_controller_axis.id),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile_controller_axis.2 as isize > 0
|
||||
if axis_position as isize * profile_controller_axis.axis as isize > 0
|
||||
&& axis_position.saturating_abs() > i16::MAX / 2
|
||||
{
|
||||
*keys |= 1 << i;
|
||||
@@ -310,25 +315,26 @@ fn change_paks(
|
||||
let key = profile.keys[CHANGE_PAK];
|
||||
|
||||
let mut pressed = false;
|
||||
if controller_button.0 && !controller.is_null() {
|
||||
if controller_button.enabled && !controller.is_null() {
|
||||
pressed = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadButton(
|
||||
controller,
|
||||
get_button_from_i32(controller_button.1),
|
||||
get_button_from_i32(controller_button.id),
|
||||
)
|
||||
};
|
||||
} else if joystick_button.0 && !joystick.is_null() {
|
||||
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.1) };
|
||||
} else if joystick_hat.0 && !joystick.is_null() {
|
||||
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.1) }
|
||||
== joystick_hat.2;
|
||||
} else if key.0 {
|
||||
pressed = unsafe { *keyboard_state.offset(key.1 as isize) };
|
||||
} else if joystick_button.enabled && !joystick.is_null() {
|
||||
pressed =
|
||||
unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.id) };
|
||||
} else if joystick_hat.enabled && !joystick.is_null() {
|
||||
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.id) }
|
||||
== joystick_hat.direction;
|
||||
} else if key.enabled {
|
||||
pressed = unsafe { *keyboard_state.offset(key.id as isize) };
|
||||
}
|
||||
pressed
|
||||
}
|
||||
|
||||
pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) {
|
||||
pub fn get(ui: &mut 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;
|
||||
@@ -347,8 +353,8 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) {
|
||||
for i in 0..14 {
|
||||
if profile_name != "default" || channel == 0 {
|
||||
let profile_key = profile.keys[i];
|
||||
if profile_key.0 && !alt_pressed {
|
||||
keys |= (unsafe { *ui.keyboard_state.offset(profile_key.1 as isize) } as u32) << i;
|
||||
if profile_key.enabled && !alt_pressed {
|
||||
keys |= (unsafe { *ui.keyboard_state.offset(profile_key.id as isize) } as u32) << i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,10 +382,10 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) {
|
||||
keys |= (x.round() as i8 as u8 as u32) << X_AXIS_SHIFT;
|
||||
keys |= (y.round() as i8 as u8 as u32) << Y_AXIS_SHIFT;
|
||||
|
||||
(
|
||||
keys,
|
||||
change_paks(profile, joystick, controller, ui.keyboard_state),
|
||||
)
|
||||
InputData {
|
||||
data: keys,
|
||||
pak_change_pressed: change_paks(profile, joystick, controller, ui.keyboard_state),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) {
|
||||
@@ -497,14 +503,39 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
("Change Pak", CHANGE_PAK),
|
||||
];
|
||||
|
||||
let mut new_keys = [(false, 0); PROFILE_SIZE];
|
||||
let mut new_joystick_buttons = [(false, 0i32); PROFILE_SIZE];
|
||||
let mut new_joystick_hat = [(false, 0i32, 0); PROFILE_SIZE];
|
||||
let mut new_joystick_axis = [(false, 0i32, 0); PROFILE_SIZE];
|
||||
let mut new_controller_buttons = [(false, 0i32); PROFILE_SIZE];
|
||||
let mut new_controller_axis = [(false, 0i32, 0); PROFILE_SIZE];
|
||||
let mut new_keys = [ui::config::InputKeyButton {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
}; PROFILE_SIZE];
|
||||
let mut new_joystick_buttons = [ui::config::InputKeyButton {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
}; PROFILE_SIZE];
|
||||
let mut new_joystick_hat = [ui::config::InputJoystickHat {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
direction: 0,
|
||||
}; PROFILE_SIZE];
|
||||
let mut new_joystick_axis = [ui::config::InputControllerAxis {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
axis: 0,
|
||||
}; PROFILE_SIZE];
|
||||
let mut new_controller_buttons = [ui::config::InputKeyButton {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
}; PROFILE_SIZE];
|
||||
let mut new_controller_axis = [ui::config::InputControllerAxis {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
axis: 0,
|
||||
}; PROFILE_SIZE];
|
||||
|
||||
let mut last_joystick_axis_result = (false, 0, 0);
|
||||
let mut last_joystick_axis_result = ui::config::InputControllerAxis {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
axis: 0,
|
||||
};
|
||||
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
|
||||
@@ -523,25 +554,35 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
if event_type == u32::from(sdl3_sys::events::SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
|
||||
close_controllers(open_joysticks, open_controllers);
|
||||
return;
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_KEY_UP) {
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_KEY_DOWN) {
|
||||
if unsafe {
|
||||
event.key.scancode != sdl3_sys::scancode::SDL_SCANCODE_LALT
|
||||
!event.key.repeat
|
||||
&& event.key.scancode != sdl3_sys::scancode::SDL_SCANCODE_LALT
|
||||
&& event.key.scancode != sdl3_sys::scancode::SDL_SCANCODE_RALT
|
||||
} {
|
||||
new_keys[*value] = (true, i32::from(unsafe { event.key.scancode }));
|
||||
new_keys[*value] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(unsafe { event.key.scancode }),
|
||||
};
|
||||
key_set = true
|
||||
}
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
|
||||
if !open_controllers.is_empty() {
|
||||
new_controller_buttons[*value] =
|
||||
(true, i32::from(unsafe { event.gbutton.button }));
|
||||
new_controller_buttons[*value] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(unsafe { event.gbutton.button }),
|
||||
};
|
||||
key_set = true
|
||||
}
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_AXIS_MOTION) {
|
||||
let axis_value = unsafe { event.gaxis.value };
|
||||
let axis = unsafe { event.gaxis.axis };
|
||||
if !open_controllers.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
|
||||
let result = (true, axis as i32, axis_value / axis_value.saturating_abs());
|
||||
let result = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: axis as i32,
|
||||
axis: axis_value / axis_value.saturating_abs(),
|
||||
};
|
||||
if result != last_joystick_axis_result {
|
||||
new_controller_axis[*value] = result;
|
||||
last_joystick_axis_result = result;
|
||||
@@ -551,15 +592,21 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_BUTTON_DOWN)
|
||||
{
|
||||
if !open_joysticks.is_empty() {
|
||||
new_joystick_buttons[*value] =
|
||||
(true, i32::from(unsafe { event.jbutton.button }));
|
||||
new_joystick_buttons[*value] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(unsafe { event.jbutton.button }),
|
||||
};
|
||||
key_set = true
|
||||
}
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_HAT_MOTION) {
|
||||
let state = unsafe { event.jhat.value };
|
||||
let hat = unsafe { event.jhat.hat };
|
||||
if !open_joysticks.is_empty() && state != sdl3_sys::joystick::SDL_HAT_CENTERED {
|
||||
new_joystick_hat[*value] = (true, hat as i32, state);
|
||||
new_joystick_hat[*value] = ui::config::InputJoystickHat {
|
||||
enabled: true,
|
||||
id: hat as i32,
|
||||
direction: state,
|
||||
};
|
||||
key_set = true
|
||||
}
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_AXIS_MOTION)
|
||||
@@ -567,7 +614,11 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
let axis_value = unsafe { event.jaxis.value };
|
||||
let axis = unsafe { event.jaxis.axis };
|
||||
if !open_joysticks.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
|
||||
let result = (true, axis as i32, axis_value / axis_value.saturating_abs());
|
||||
let result = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: axis as i32,
|
||||
axis: axis_value / axis_value.saturating_abs(),
|
||||
};
|
||||
if result != last_joystick_axis_result {
|
||||
new_joystick_axis[*value] = result;
|
||||
last_joystick_axis_result = result;
|
||||
@@ -599,114 +650,200 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
}
|
||||
|
||||
pub fn get_default_profile() -> ui::config::InputProfile {
|
||||
let mut default_controller_buttons = [(false, 0); PROFILE_SIZE];
|
||||
let mut default_controller_axis = [(false, 0, 0); PROFILE_SIZE];
|
||||
let mut default_keys = [(false, 0); PROFILE_SIZE];
|
||||
default_keys[R_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_D));
|
||||
default_keys[L_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_A));
|
||||
default_keys[D_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_S));
|
||||
default_keys[U_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_W));
|
||||
default_keys[START_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RETURN));
|
||||
default_keys[Z_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_Z));
|
||||
default_keys[B_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LCTRL));
|
||||
default_keys[A_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LSHIFT));
|
||||
default_keys[R_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_L));
|
||||
default_keys[L_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_J));
|
||||
default_keys[D_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_K));
|
||||
default_keys[U_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_I));
|
||||
default_keys[R_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_C));
|
||||
default_keys[L_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_X));
|
||||
default_keys[AXIS_LEFT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LEFT));
|
||||
default_keys[AXIS_RIGHT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RIGHT));
|
||||
default_keys[AXIS_UP] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_UP));
|
||||
default_keys[AXIS_DOWN] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_DOWN));
|
||||
default_keys[CHANGE_PAK] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_COMMA));
|
||||
let mut default_controller_buttons = [ui::config::InputKeyButton {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
}; PROFILE_SIZE];
|
||||
let mut default_controller_axis = [ui::config::InputControllerAxis {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
axis: 0,
|
||||
}; PROFILE_SIZE];
|
||||
let mut default_keys = [ui::config::InputKeyButton {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
}; PROFILE_SIZE];
|
||||
default_keys[R_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_D),
|
||||
};
|
||||
default_keys[L_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_A),
|
||||
};
|
||||
default_keys[D_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_S),
|
||||
};
|
||||
default_keys[U_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_W),
|
||||
};
|
||||
default_keys[START_BUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_RETURN),
|
||||
};
|
||||
default_keys[Z_TRIG] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_Z),
|
||||
};
|
||||
default_keys[B_BUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_LCTRL),
|
||||
};
|
||||
default_keys[A_BUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_LSHIFT),
|
||||
};
|
||||
default_keys[R_CBUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_L),
|
||||
};
|
||||
default_keys[L_CBUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_J),
|
||||
};
|
||||
default_keys[D_CBUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_K),
|
||||
};
|
||||
default_keys[U_CBUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_I),
|
||||
};
|
||||
default_keys[R_TRIG] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_C),
|
||||
};
|
||||
default_keys[L_TRIG] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_X),
|
||||
};
|
||||
default_keys[AXIS_LEFT] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_LEFT),
|
||||
};
|
||||
default_keys[AXIS_RIGHT] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_RIGHT),
|
||||
};
|
||||
default_keys[AXIS_UP] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_UP),
|
||||
};
|
||||
default_keys[AXIS_DOWN] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_DOWN),
|
||||
};
|
||||
default_keys[CHANGE_PAK] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::scancode::SDL_SCANCODE_COMMA),
|
||||
};
|
||||
|
||||
default_controller_buttons[R_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
|
||||
);
|
||||
default_controller_buttons[L_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_LEFT),
|
||||
);
|
||||
default_controller_buttons[D_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_DOWN),
|
||||
);
|
||||
default_controller_buttons[U_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_UP),
|
||||
);
|
||||
default_controller_buttons[START_BUTTON] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_START));
|
||||
default_controller_axis[Z_TRIG] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
1,
|
||||
);
|
||||
default_controller_buttons[B_BUTTON] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_WEST));
|
||||
default_controller_buttons[A_BUTTON] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_SOUTH));
|
||||
default_controller_axis[R_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
1,
|
||||
);
|
||||
default_controller_axis[L_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
-1,
|
||||
);
|
||||
default_controller_axis[D_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
1,
|
||||
);
|
||||
default_controller_axis[U_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
-1,
|
||||
);
|
||||
default_controller_buttons[R_TRIG] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER),
|
||||
);
|
||||
default_controller_buttons[L_TRIG] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER),
|
||||
);
|
||||
default_controller_axis[AXIS_LEFT] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
|
||||
-1,
|
||||
);
|
||||
default_controller_axis[AXIS_RIGHT] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
|
||||
1,
|
||||
);
|
||||
default_controller_axis[AXIS_UP] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
|
||||
-1,
|
||||
);
|
||||
default_controller_axis[AXIS_DOWN] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
|
||||
1,
|
||||
);
|
||||
default_controller_buttons[CHANGE_PAK] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_BACK));
|
||||
default_controller_buttons[R_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
|
||||
};
|
||||
default_controller_buttons[L_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_LEFT),
|
||||
};
|
||||
default_controller_buttons[D_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_DOWN),
|
||||
};
|
||||
default_controller_buttons[U_DPAD] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_UP),
|
||||
};
|
||||
default_controller_buttons[START_BUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_START),
|
||||
};
|
||||
default_controller_axis[Z_TRIG] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
axis: 1,
|
||||
};
|
||||
default_controller_buttons[B_BUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_WEST),
|
||||
};
|
||||
default_controller_buttons[A_BUTTON] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_SOUTH),
|
||||
};
|
||||
default_controller_axis[R_CBUTTON] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
axis: 1,
|
||||
};
|
||||
default_controller_axis[L_CBUTTON] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
axis: -1,
|
||||
};
|
||||
default_controller_axis[D_CBUTTON] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
axis: 1,
|
||||
};
|
||||
default_controller_axis[U_CBUTTON] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
axis: -1,
|
||||
};
|
||||
default_controller_buttons[R_TRIG] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER),
|
||||
};
|
||||
default_controller_buttons[L_TRIG] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER),
|
||||
};
|
||||
default_controller_axis[AXIS_LEFT] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
|
||||
axis: -1,
|
||||
};
|
||||
default_controller_axis[AXIS_RIGHT] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
|
||||
axis: 1,
|
||||
};
|
||||
default_controller_axis[AXIS_UP] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
|
||||
axis: -1,
|
||||
};
|
||||
default_controller_axis[AXIS_DOWN] = ui::config::InputControllerAxis {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
|
||||
axis: 1,
|
||||
};
|
||||
default_controller_buttons[CHANGE_PAK] = ui::config::InputKeyButton {
|
||||
enabled: true,
|
||||
id: i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_BACK),
|
||||
};
|
||||
|
||||
ui::config::InputProfile {
|
||||
keys: default_keys,
|
||||
controller_buttons: default_controller_buttons,
|
||||
controller_axis: default_controller_axis,
|
||||
joystick_buttons: Default::default(),
|
||||
joystick_hat: Default::default(),
|
||||
joystick_axis: Default::default(),
|
||||
joystick_buttons: [ui::config::InputKeyButton {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
}; PROFILE_SIZE],
|
||||
joystick_hat: [ui::config::InputJoystickHat {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
direction: 0,
|
||||
}; PROFILE_SIZE],
|
||||
joystick_axis: [ui::config::InputControllerAxis {
|
||||
enabled: false,
|
||||
id: 0,
|
||||
axis: 0,
|
||||
}; PROFILE_SIZE],
|
||||
dinput: false,
|
||||
}
|
||||
}
|
||||
|
||||
+105
-54
@@ -1,4 +1,4 @@
|
||||
use crate::{netplay, ui};
|
||||
use crate::{device, netplay, ui};
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
|
||||
@@ -25,14 +25,27 @@ pub struct Paths {
|
||||
|
||||
// the bool indicates whether the save has been written to
|
||||
// if that is the case, it will be flushed to the disk when the program closes
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Save {
|
||||
pub data: Vec<u8>,
|
||||
pub written: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct RomSave {
|
||||
pub data: std::collections::HashMap<u32, u8>,
|
||||
pub written: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Saves {
|
||||
pub eeprom: (Vec<u8>, bool),
|
||||
pub sram: (Vec<u8>, bool),
|
||||
pub flash: (Vec<u8>, bool),
|
||||
pub mempak: (Vec<u8>, bool),
|
||||
pub sdcard: (Vec<u8>, bool),
|
||||
pub romsave: (std::collections::HashMap<u32, u8>, bool),
|
||||
pub eeprom: Save,
|
||||
pub sram: Save,
|
||||
pub flash: Save,
|
||||
pub mempak: Save,
|
||||
pub sdcard: Save,
|
||||
pub romsave: RomSave,
|
||||
pub write_to_disk: bool,
|
||||
}
|
||||
|
||||
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
|
||||
@@ -175,27 +188,27 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
|
||||
let eep = std::fs::read(&mut ui.paths.eep_file_path);
|
||||
if eep.is_ok() {
|
||||
ui.saves.eeprom.0 = eep.unwrap();
|
||||
ui.saves.eeprom.data = eep.unwrap();
|
||||
}
|
||||
let sra = std::fs::read(&mut ui.paths.sra_file_path);
|
||||
if sra.is_ok() {
|
||||
ui.saves.sram.0 = sra.unwrap();
|
||||
ui.saves.sram.data = sra.unwrap();
|
||||
}
|
||||
let fla = std::fs::read(&mut ui.paths.fla_file_path);
|
||||
if fla.is_ok() {
|
||||
ui.saves.flash.0 = fla.unwrap();
|
||||
ui.saves.flash.data = fla.unwrap();
|
||||
}
|
||||
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
|
||||
if mempak.is_ok() {
|
||||
ui.saves.mempak.0 = mempak.unwrap();
|
||||
ui.saves.mempak.data = mempak.unwrap();
|
||||
}
|
||||
let sdcard = std::fs::read(&mut ui.paths.sdcard_file_path);
|
||||
if sdcard.is_ok() {
|
||||
ui.saves.sdcard.0 = sdcard.unwrap();
|
||||
ui.saves.sdcard.data = sdcard.unwrap();
|
||||
}
|
||||
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
|
||||
if romsave.is_ok() {
|
||||
ui.saves.romsave.0 = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
|
||||
ui.saves.romsave.data = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,31 +217,31 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"eep",
|
||||
&ui.saves.eeprom.0,
|
||||
ui.saves.eeprom.0.len(),
|
||||
&ui.saves.eeprom.data,
|
||||
ui.saves.eeprom.data.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"sra",
|
||||
&ui.saves.sram.0,
|
||||
ui.saves.sram.0.len(),
|
||||
&ui.saves.sram.data,
|
||||
ui.saves.sram.data.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"fla",
|
||||
&ui.saves.flash.0,
|
||||
ui.saves.flash.0.len(),
|
||||
&ui.saves.flash.data,
|
||||
ui.saves.flash.data.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"mpk",
|
||||
&ui.saves.mempak.0,
|
||||
ui.saves.mempak.0.len(),
|
||||
&ui.saves.mempak.data,
|
||||
ui.saves.mempak.data.len(),
|
||||
);
|
||||
|
||||
let mut compressed_sd = Vec::new();
|
||||
if !ui.saves.sdcard.0.is_empty() {
|
||||
compressed_sd = compress_file(&[(&ui.saves.sdcard.0, "save")]);
|
||||
if !ui.saves.sdcard.data.is_empty() {
|
||||
compressed_sd = compress_file(&[(&ui.saves.sdcard.data, "save")]);
|
||||
}
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
@@ -238,9 +251,11 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
);
|
||||
|
||||
let mut compressed_romsave = Vec::new();
|
||||
if !ui.saves.romsave.0.is_empty() {
|
||||
compressed_romsave =
|
||||
compress_file(&[(&postcard::to_stdvec(&ui.saves.romsave.0).unwrap(), "save")]);
|
||||
if !ui.saves.romsave.data.is_empty() {
|
||||
compressed_romsave = compress_file(&[(
|
||||
&postcard::to_stdvec(&ui.saves.romsave.data).unwrap(),
|
||||
"save",
|
||||
)]);
|
||||
}
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
@@ -249,22 +264,22 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
compressed_romsave.len(),
|
||||
);
|
||||
} else {
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "eep", &mut ui.saves.eeprom.0);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "sra", &mut ui.saves.sram.0);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "fla", &mut ui.saves.flash.0);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "mpk", &mut ui.saves.mempak.0);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "eep", &mut ui.saves.eeprom.data);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "sra", &mut ui.saves.sram.data);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "fla", &mut ui.saves.flash.data);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "mpk", &mut ui.saves.mempak.data);
|
||||
|
||||
let mut compressed_sd = Vec::new();
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut compressed_sd);
|
||||
if !compressed_sd.is_empty() {
|
||||
ui.saves.sdcard.0 = decompress_file(&compressed_sd, "save");
|
||||
ui.saves.sdcard.data = decompress_file(&compressed_sd, "save");
|
||||
}
|
||||
|
||||
let mut compressed_romsave = Vec::new();
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut compressed_romsave);
|
||||
if !compressed_romsave.is_empty() {
|
||||
let romsave_bytes = decompress_file(&compressed_romsave, "save");
|
||||
ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap();
|
||||
ui.saves.romsave.data = postcard::from_bytes(&romsave_bytes).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,29 +314,65 @@ pub fn compress_file(data: &[(&[u8], &str)]) -> Vec<u8> {
|
||||
}
|
||||
|
||||
fn write_rom_save(ui: &ui::Ui) {
|
||||
let data = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
|
||||
let data = postcard::to_stdvec(&ui.saves.romsave.data).unwrap();
|
||||
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
|
||||
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
|
||||
if ui.saves.eeprom.1 {
|
||||
write_save(ui, SaveTypes::Eeprom16k)
|
||||
fn writeback_sdcard(device: &mut device::Device) {
|
||||
let length;
|
||||
let save_data: &[u8];
|
||||
if device.ui.saves.eeprom.written {
|
||||
if device
|
||||
.ui
|
||||
.save_type
|
||||
.contains(&ui::storage::SaveTypes::Eeprom4k)
|
||||
{
|
||||
length = 1;
|
||||
} else {
|
||||
length = 4;
|
||||
}
|
||||
if ui.saves.sram.1 {
|
||||
write_save(ui, SaveTypes::Sram)
|
||||
save_data = device.ui.saves.eeprom.data.as_ref();
|
||||
} else if device.ui.saves.sram.written {
|
||||
length = device.ui.saves.sram.data.len() / 512;
|
||||
save_data = device.ui.saves.sram.data.as_ref();
|
||||
} else if device.ui.saves.flash.written {
|
||||
length = device.ui.saves.flash.data.len() / 512;
|
||||
save_data = device.ui.saves.flash.data.as_ref();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
for i in 0..length {
|
||||
let offset = device.cart.sc64.writeback_sector[i] as usize * 512;
|
||||
device.ui.saves.sdcard.data[offset..offset + 512]
|
||||
.copy_from_slice(&save_data[i * 512..(i + 1) * 512]);
|
||||
}
|
||||
device.ui.saves.sdcard.written = true;
|
||||
}
|
||||
|
||||
pub fn write_saves(device: &mut device::Device) {
|
||||
if device.netplay.is_none() || device.netplay.as_ref().unwrap().player_number == 0 {
|
||||
if device.ui.saves.write_to_disk {
|
||||
if device.ui.saves.eeprom.written {
|
||||
write_save(&device.ui, SaveTypes::Eeprom16k)
|
||||
}
|
||||
if device.ui.saves.sram.written {
|
||||
write_save(&device.ui, SaveTypes::Sram)
|
||||
}
|
||||
if device.ui.saves.flash.written {
|
||||
write_save(&device.ui, SaveTypes::Flash)
|
||||
}
|
||||
if device.ui.saves.romsave.written {
|
||||
write_save(&device.ui, SaveTypes::Romsave)
|
||||
}
|
||||
} else {
|
||||
writeback_sdcard(device)
|
||||
}
|
||||
if ui.saves.flash.1 {
|
||||
write_save(ui, SaveTypes::Flash)
|
||||
if device.ui.saves.mempak.written {
|
||||
write_save(&device.ui, SaveTypes::Mempak)
|
||||
}
|
||||
if ui.saves.mempak.1 {
|
||||
write_save(ui, SaveTypes::Mempak)
|
||||
}
|
||||
if ui.saves.sdcard.1 {
|
||||
write_save(ui, SaveTypes::Sdcard)
|
||||
}
|
||||
if ui.saves.romsave.1 {
|
||||
write_save(ui, SaveTypes::Romsave)
|
||||
if device.ui.saves.sdcard.written {
|
||||
write_save(&device.ui, SaveTypes::Sdcard)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,23 +383,23 @@ fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
|
||||
match save_type {
|
||||
SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => {
|
||||
path = ui.paths.eep_file_path.as_ref();
|
||||
data = ui.saves.eeprom.0.as_ref();
|
||||
data = ui.saves.eeprom.data.as_ref();
|
||||
}
|
||||
SaveTypes::Sram => {
|
||||
path = ui.paths.sra_file_path.as_ref();
|
||||
data = ui.saves.sram.0.as_ref();
|
||||
data = ui.saves.sram.data.as_ref();
|
||||
}
|
||||
SaveTypes::Flash => {
|
||||
path = ui.paths.fla_file_path.as_ref();
|
||||
data = ui.saves.flash.0.as_ref();
|
||||
data = ui.saves.flash.data.as_ref();
|
||||
}
|
||||
SaveTypes::Mempak => {
|
||||
path = ui.paths.pak_file_path.as_ref();
|
||||
data = ui.saves.mempak.0.as_ref();
|
||||
data = ui.saves.mempak.data.as_ref();
|
||||
}
|
||||
SaveTypes::Sdcard => {
|
||||
path = ui.paths.sdcard_file_path.as_ref();
|
||||
data = ui.saves.sdcard.0.as_ref();
|
||||
data = ui.saves.sdcard.data.as_ref();
|
||||
}
|
||||
SaveTypes::Romsave => {
|
||||
write_rom_save(ui);
|
||||
|
||||
+25
-3
@@ -16,11 +16,22 @@ pub fn init(device: &mut device::Device) {
|
||||
}
|
||||
flags |= sdl3_sys::video::SDL_WINDOW_INPUT_FOCUS;
|
||||
|
||||
let mut window_width = 640;
|
||||
let mut window_height = 480;
|
||||
let window_width;
|
||||
let window_height;
|
||||
if device.cart.pal {
|
||||
window_width = 768;
|
||||
window_width = if device.ui.config.video.widescreen {
|
||||
1024
|
||||
} else {
|
||||
768
|
||||
};
|
||||
window_height = 576;
|
||||
} else {
|
||||
window_width = if device.ui.config.video.widescreen {
|
||||
852
|
||||
} else {
|
||||
640
|
||||
};
|
||||
window_height = 480;
|
||||
}
|
||||
device.ui.window = unsafe {
|
||||
sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), window_width, window_height, flags)
|
||||
@@ -42,6 +53,7 @@ pub fn init(device: &mut device::Device) {
|
||||
DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize],
|
||||
DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize],
|
||||
PAL: device.cart.pal,
|
||||
widescreen: device.ui.config.video.widescreen,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
@@ -67,6 +79,14 @@ pub fn update_screen() {
|
||||
unsafe { rdp_update_screen() }
|
||||
}
|
||||
|
||||
pub fn check_framebuffers(address: u32) {
|
||||
unsafe { rdp_check_framebuffers(address) }
|
||||
}
|
||||
|
||||
pub fn full_sync() {
|
||||
unsafe { rdp_full_sync() }
|
||||
}
|
||||
|
||||
pub fn load_state(device: &mut device::Device) {
|
||||
let gfx_info = GFX_INFO {
|
||||
RDRAM: device.rdram.mem.as_mut_ptr(),
|
||||
@@ -77,6 +97,7 @@ pub fn load_state(device: &mut device::Device) {
|
||||
DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize],
|
||||
DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize],
|
||||
PAL: device.cart.pal,
|
||||
widescreen: device.ui.config.video.widescreen,
|
||||
};
|
||||
unsafe {
|
||||
rdp_new_processor(gfx_info, device.ui.config.video.upscale);
|
||||
@@ -92,6 +113,7 @@ pub fn check_callback(device: &mut device::Device) {
|
||||
} else if callback.load_state {
|
||||
device.load_state = true;
|
||||
}
|
||||
device.vi.enable_speed_limiter = callback.enable_speedlimiter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user