Compare commits

...

50 Commits

Author SHA1 Message Date
Logan McNaughton 2a3fa03b35 update cargo packages 2025-03-13 07:09:52 +01:00
Logan McNaughton ec2f47e5d3 cleanup vector code 2025-03-11 15:08:35 +01:00
Logan McNaughton 139d66eb82 use vector for rdram_dirty (#322) 2025-03-11 14:52:57 +01:00
Logan McNaughton 6e10bee216 use glow for egui (#321)
* Revert "use GL backend for GUI to avoid conflicts with game window (#287)"

This reverts commit 00964f06f0.

* use glow
2025-03-11 09:43:09 +01:00
Logan McNaughton dff9fc5381 bump to 1.0.13 (#318) 2025-03-10 23:01:21 +01:00
Logan McNaughton 84dcc6c154 fix image scale when using upscaling (#317) 2025-03-10 22:53:16 +01:00
Logan McNaughton 8aa23e1665 bump to 1.0.12 (#316) 2025-03-10 17:22:51 +01:00
Logan McNaughton f2b75a2dcc update cargo packages 2025-03-10 17:14:04 +01:00
Logan McNaughton 5bac846bda update shaders (#315) 2025-03-10 17:13:45 +01:00
Logan McNaughton e93c275795 Add option for CRT shader (#314)
* add CRT shader

* more

* more

* update type

* more

* more

* more

* more
2025-03-10 16:52:25 +01:00
Logan McNaughton 67a8bb5e4c show VRU dialog in main window (#311) 2025-03-09 22:20:16 +01:00
Logan McNaughton 2828accee1 re-arrange UI struct (#310)
* re-arrange UI struct

* format
2025-03-09 10:30:43 +01:00
Logan McNaughton 16a9754bda GB MBC1 support (#309) 2025-03-08 21:48:51 +01:00
Logan McNaughton fbe95767f5 update zip 2025-03-07 18:40:11 +01:00
Logan McNaughton 0989be1e0f update cargo packages 2025-03-07 18:36:08 +01:00
Logan McNaughton e73816541e condense code (#308) 2025-03-07 18:35:48 +01:00
Logan McNaughton f6b2342e0e always make the window resizeable (#305) 2025-03-07 10:43:35 +01:00
Logan McNaughton 7ab57d592b bump to 1.0.11 (#301) 2025-03-05 15:20:42 +01:00
Logan McNaughton a2cda03c8d update cargo packages 2025-03-05 15:12:05 +01:00
Logan McNaughton a07237aa64 set netplay buffer target (#300) 2025-03-04 21:23:33 +01:00
Logan McNaughton fdc17f4ae5 make is viewer optional (#299)
* make is viewer optional

* more
2025-03-04 18:52:47 +01:00
Logan McNaughton 4802f3b71a remove pub 2025-03-04 12:03:30 +01:00
Logan McNaughton a4c505c72d remove some code duplication (#298) 2025-03-04 10:12:56 +01:00
Logan McNaughton 30cb3f8339 add some randomness (#297) 2025-03-04 09:45:25 +01:00
Logan McNaughton a5f5867ebb update cargo packages 2025-03-03 10:03:44 +01:00
Logan McNaughton ee8e34e17c Add audio file 2025-03-01 21:59:35 +01:00
Logan McNaughton da1e686c1a Add option for overclocking (#293)
* add option to overclock CPU

* netplay

* more

* more

* more

* more

* more
2025-02-28 21:34:33 +01:00
Logan McNaughton 5a3609d501 add option to crop out letterbox (#292) 2025-02-28 09:47:34 +01:00
Logan McNaughton 7b51ca82eb add widescreen stretch option (#291) 2025-02-27 22:05:19 +01:00
Logan McNaughton ee77e75855 bump to 1.0.10 (#290) 2025-02-27 17:22:19 +01:00
Logan McNaughton ff093a9f7f Update README.md 2025-02-27 15:55:07 +01:00
Logan McNaughton f7cb0db348 update cargo packages 2025-02-27 11:31:18 +01:00
Logan McNaughton eb9f37f1f5 add button to open save folder (#289) 2025-02-27 11:29:30 +01:00
Logan McNaughton 37b902eb31 use deterministic rtc time (#288)
* use deterministic rtc time

* netplay
2025-02-27 10:59:44 +01:00
Logan McNaughton 00964f06f0 use GL backend for GUI to avoid conflicts with game window (#287) 2025-02-27 10:04:53 +01:00
Logan McNaughton 319d7c8553 some mbc3 rtc code (#284) 2025-02-26 21:00:15 +01:00
Logan McNaughton 82b4adb4ad flash cart menu support (#283)
* flash cart menu support

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-02-26 10:04:44 +01:00
Logan McNaughton 8fbc63a677 add links to wiki and discord (#282) 2025-02-24 18:50:29 +01:00
Logan McNaughton 48ee50dea3 fix Stadium 2 GB games (#281) 2025-02-24 15:31:19 +01:00
Logan McNaughton 34a09925b4 fix GB tower (#280) 2025-02-24 14:43:06 +01:00
Logan McNaughton 168347b46e Transfer Pak UI (#279)
* Transfer Pak UI

* more

* more

* more

* more
2025-02-24 12:49:33 +01:00
Logan McNaughton 95a3d3d5ec fix type 2025-02-23 22:28:07 +01:00
Logan McNaughton f6349f13da tpak cleanup (#278)
* tpak cleanup

* more

* more
2025-02-23 16:10:53 +01:00
Logan McNaughton fb2cf7fcb9 move UI elements around (#277) 2025-02-23 13:25:23 +01:00
Logan McNaughton 9980302d37 more tpak code (#276)
* more tpak code

* more
2025-02-23 13:10:50 +01:00
Logan McNaughton 4692e2bcb9 more tpak code (#275)
* more tpak code

* more
2025-02-23 11:59:15 +01:00
Logan McNaughton 04390301c9 more tpak code (#274) 2025-02-22 21:41:21 +01:00
Logan McNaughton bc415821d6 check for new version (#273)
* check for new version

* more
2025-02-21 23:02:16 +01:00
Logan McNaughton a068363787 Add note about command line flatpak usage 2025-02-20 20:12:25 +01:00
Logan McNaughton 97839fae40 Bump to 1.0.9 (#272) 2025-02-20 18:44:15 +01:00
50 changed files with 3357 additions and 932 deletions
Generated
+484 -319
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "1.0.8"
version = "1.0.13"
edition = "2024"
rust-version = "1.85"
@@ -8,14 +8,14 @@ rust-version = "1.85"
[dependencies]
dirs = "6.0"
zip = "2.1"
zip = "2.2"
governor = "0.8"
sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-big-array = "0.5"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "wgpu"] }
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "glow"] }
sha2 = "0.10"
rusttype = "0.9"
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
@@ -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"] }
+11 -5
View File
@@ -1,5 +1,7 @@
# gopher64
Gopher64 can be used via its GUI or the command line.
## download
https://github.com/gopher64/gopher64/releases
## wiki
@@ -25,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.
@@ -47,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
Binary file not shown.
+135
View File
@@ -0,0 +1,135 @@
#version 450
/*
CRT Shader by EasyMode
License: GPL
*/
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OutputSize;
} params;
#define SHARPNESS_IMAGE 1.0
#define SHARPNESS_EDGES 3.0
#define GLOW_WIDTH 0.5
#define GLOW_HEIGHT 0.5
#define GLOW_HALATION 0.1
#define GLOW_DIFFUSION 0.05
#define MASK_COLORS 2.0
#define MASK_STRENGTH 0.3
#define MASK_SIZE 1.0
#define SCANLINE_SIZE_MIN 0.5
#define SCANLINE_SIZE_MAX 1.5
#define SCANLINE_SHAPE 2.5
#define SCANLINE_OFFSET 1.0
#define GAMMA_INPUT 2.4
#define GAMMA_OUTPUT 2.4
#define BRIGHTNESS 1.5
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
} global;
#define FIX(c) max(abs(c), 1e-5)
#define PI 3.141592653589
#define TEX2D(c) pow(texture(tex, c).rgb, vec3(GAMMA_INPUT))
#define saturate(c) clamp(c, 0.0, 1.0)
mat3x3 get_color_matrix(sampler2D tex, vec2 co, vec2 dx)
{
return mat3x3(TEX2D(co - dx), TEX2D(co), TEX2D(co + dx));
}
vec3 blur(mat3 m, float dist, float rad)
{
vec3 x = vec3(dist - 1.0, dist, dist + 1.0) / rad;
vec3 w = exp2(x * x * -1.0);
return (m[0] * w.x + m[1] * w.y + m[2] * w.z) / (w.x + w.y + w.z);
}
vec3 filter_gaussian(sampler2D tex, vec2 co, vec2 tex_size)
{
vec2 dx = vec2(1.0 / tex_size.x, 0.0);
vec2 dy = vec2(0.0, 1.0 / tex_size.y);
vec2 pix_co = co * tex_size;
vec2 tex_co = (floor(pix_co) + 0.5) / tex_size;
vec2 dist = (fract(pix_co) - 0.5) * -1.0;
mat3x3 line0 = get_color_matrix(tex, tex_co - dy, dx);
mat3x3 line1 = get_color_matrix(tex, tex_co, dx);
mat3x3 line2 = get_color_matrix(tex, tex_co + dy, dx);
mat3x3 column = mat3x3(blur(line0, dist.x, GLOW_WIDTH),
blur(line1, dist.x, GLOW_WIDTH),
blur(line2, dist.x, GLOW_WIDTH));
return blur(column, dist.y, GLOW_HEIGHT);
}
vec3 filter_lanczos(sampler2D tex, vec2 co, vec2 tex_size, float sharp)
{
tex_size.x *= sharp;
vec2 dx = vec2(1.0 / tex_size.x, 0.0);
vec2 pix_co = co * tex_size - vec2(0.5, 0.0);
vec2 tex_co = (floor(pix_co) + vec2(0.5, 0.001)) / tex_size;
vec2 dist = fract(pix_co);
vec4 coef = PI * vec4(dist.x + 1.0, dist.x, dist.x - 1.0, dist.x - 2.0);
coef = FIX(coef);
coef = 2.0 * sin(coef) * sin(coef / 2.0) / (coef * coef);
coef /= dot(coef, vec4(1.0));
vec4 col1 = vec4(TEX2D(tex_co), 1.0);
vec4 col2 = vec4(TEX2D(tex_co + dx), 1.0);
return (mat4x4(col1, col1, col2, col2) * coef).rgb;
}
vec3 get_scanline_weight(float x, vec3 col)
{
vec3 beam = mix(vec3(SCANLINE_SIZE_MIN), vec3(SCANLINE_SIZE_MAX), pow(col, vec3(1.0 / SCANLINE_SHAPE)));
vec3 x_mul = 2.0 / beam;
vec3 x_offset = x_mul * 0.5;
return smoothstep(0.0, 1.0, 1.0 - abs(x * x_mul - x_offset)) * x_offset;
}
vec3 get_mask_weight(float x)
{
float i = mod(floor(x * params.OutputSize.x * params.SourceSize.x / (params.SourceSize.x * MASK_SIZE)), MASK_COLORS);
if (i == 0.0) return mix(vec3(1.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), MASK_COLORS - 2.0);
else if (i == 1.0) return vec3(0.0, 1.0, 0.0);
else return vec3(0.0, 0.0, 1.0);
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 0) uniform sampler2D Source;
void main()
{
float scale = floor(params.OutputSize.y * params.SourceSize.w);
float offset = 1.0 / scale * 0.5;
if (bool(mod(scale, 2.0))) offset = 0.0;
vec2 co = (vTexCoord * params.SourceSize.xy - vec2(0.0, offset * SCANLINE_OFFSET)) * params.SourceSize.zw;
vec3 col_glow = filter_gaussian(Source, co, params.SourceSize.xy);
vec3 col_soft = filter_lanczos(Source, co, params.SourceSize.xy, SHARPNESS_IMAGE);
vec3 col_sharp = filter_lanczos(Source, co, params.SourceSize.xy, SHARPNESS_EDGES);
vec3 col = sqrt(col_sharp * col_soft);
col *= get_scanline_weight(fract(co.y * params.SourceSize.y), col_soft);
col_glow = saturate(col_glow - col);
col += col_glow * col_glow * GLOW_HALATION;
col = mix(col, col * get_mask_weight(vTexCoord.x) * MASK_COLORS, MASK_STRENGTH);
col += col_glow * GLOW_DIFFUSION;
col = pow(col * BRIGHTNESS, vec3(1.0 / GAMMA_OUTPUT));
FragColor = vec4(col, 1.0);
}
+453
View File
@@ -0,0 +1,453 @@
{0x07230203,0x00010300,0x000d000b,0x000004fd,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
0x0007000f,0x00000004,0x00000004,0x6e69616d,
0x00000000,0x000001ba,0x0000021a,0x00030010,
0x00000004,0x00000007,0x00030047,0x00000185,
0x00000002,0x00050048,0x00000185,0x00000000,
0x00000023,0x00000000,0x00050048,0x00000185,
0x00000001,0x00000023,0x00000010,0x00040047,
0x000001ba,0x0000001e,0x00000000,0x00040047,
0x000001ca,0x00000021,0x00000000,0x00040047,
0x000001ca,0x00000022,0x00000000,0x00040047,
0x0000021a,0x0000001e,0x00000000,0x00020013,
0x00000002,0x00030021,0x00000003,0x00000002,
0x00030016,0x00000006,0x00000020,0x00090019,
0x00000007,0x00000006,0x00000001,0x00000000,
0x00000000,0x00000000,0x00000001,0x00000000,
0x0003001b,0x00000008,0x00000007,0x00040020,
0x00000009,0x00000000,0x00000008,0x00040017,
0x0000000a,0x00000006,0x00000002,0x00040017,
0x0000000c,0x00000006,0x00000003,0x00040017,
0x00000037,0x00000006,0x00000004,0x0004002b,
0x00000006,0x0000003a,0x4019999a,0x0006002c,
0x0000000c,0x0000003b,0x0000003a,0x0000003a,
0x0000003a,0x0004002b,0x00000006,0x00000049,
0x3f800000,0x0004002b,0x00000006,0x0000004a,
0x00000000,0x0004002b,0x00000006,0x00000068,
0xbf800000,0x00040015,0x0000006b,0x00000020,
0x00000001,0x0004002b,0x0000006b,0x0000006c,
0x00000000,0x00040015,0x0000006f,0x00000020,
0x00000000,0x0004002b,0x0000006f,0x00000070,
0x00000000,0x0004002b,0x0000006b,0x00000074,
0x00000001,0x0004002b,0x0000006f,0x00000077,
0x00000001,0x0004002b,0x00000006,0x000000a1,
0x3f000000,0x0005002c,0x0000000a,0x000000fc,
0x000000a1,0x0000004a,0x0004002b,0x00000006,
0x00000101,0x3a83126f,0x0005002c,0x0000000a,
0x00000102,0x000000a1,0x00000101,0x0004002b,
0x00000006,0x0000010b,0x40490fdb,0x0004002b,
0x00000006,0x00000116,0x40000000,0x0004002b,
0x00000006,0x0000011c,0x3727c5ac,0x0007002c,
0x00000037,0x0000012c,0x00000049,0x00000049,
0x00000049,0x00000049,0x00040018,0x00000149,
0x00000037,0x00000004,0x0006002c,0x0000000c,
0x00000165,0x000000a1,0x000000a1,0x000000a1,
0x0004002b,0x00000006,0x00000166,0x3fc00000,
0x0006002c,0x0000000c,0x00000167,0x00000166,
0x00000166,0x00000166,0x0004002b,0x00000006,
0x00000169,0x3ecccccd,0x0006002c,0x0000000c,
0x0000016a,0x00000169,0x00000169,0x00000169,
0x0004001e,0x00000185,0x00000037,0x00000037,
0x00040020,0x00000186,0x00000009,0x00000185,
0x0004003b,0x00000186,0x00000187,0x00000009,
0x00040020,0x00000188,0x00000009,0x00000006,
0x00020014,0x00000196,0x0006002c,0x0000000c,
0x0000019a,0x00000049,0x0000004a,0x00000049,
0x0006002c,0x0000000c,0x000001a1,0x0000004a,
0x00000049,0x0000004a,0x0006002c,0x0000000c,
0x000001a4,0x0000004a,0x0000004a,0x00000049,
0x0004002b,0x0000006f,0x000001aa,0x00000003,
0x00040020,0x000001b9,0x00000001,0x0000000a,
0x0004003b,0x000001b9,0x000001ba,0x00000001,
0x00040020,0x000001bc,0x00000009,0x00000037,
0x0004003b,0x00000009,0x000001ca,0x00000000,
0x0004002b,0x00000006,0x000001dc,0x40400000,
0x0004002b,0x00000006,0x000001ff,0x3dcccccd,
0x00040020,0x00000206,0x00000001,0x00000006,
0x0004002b,0x00000006,0x0000020c,0x3e99999a,
0x0004002b,0x00000006,0x00000210,0x3d4ccccd,
0x0004002b,0x00000006,0x00000216,0x3ed55555,
0x0006002c,0x0000000c,0x00000217,0x00000216,
0x00000216,0x00000216,0x00040020,0x00000219,
0x00000003,0x00000037,0x0004003b,0x00000219,
0x0000021a,0x00000003,0x0005002c,0x0000000a,
0x000004f3,0x000000a1,0x000000a1,0x0007002c,
0x00000037,0x000004f4,0x0000011c,0x0000011c,
0x0000011c,0x0000011c,0x0006002c,0x0000000c,
0x000004f6,0x00000116,0x00000116,0x00000116,
0x0006002c,0x0000000c,0x000004f7,0x00000049,
0x00000049,0x00000049,0x0006002c,0x0000000c,
0x000004f8,0x0000004a,0x0000004a,0x0000004a,
0x0006002c,0x0000000c,0x000004f9,0x0000020c,
0x0000020c,0x0000020c,0x0007002c,0x00000037,
0x000004fa,0x000000a1,0x000000a1,0x000000a1,
0x000000a1,0x0004002b,0x00000006,0x000004fb,
0x3eaaaaab,0x00050036,0x00000002,0x00000004,
0x00000000,0x00000003,0x000200f8,0x00000005,
0x00060041,0x00000188,0x000001a8,0x00000187,
0x00000074,0x00000077,0x0004003d,0x00000006,
0x000001a9,0x000001a8,0x00060041,0x00000188,
0x000001ab,0x00000187,0x0000006c,0x000001aa,
0x0004003d,0x00000006,0x000001ac,0x000001ab,
0x00050085,0x00000006,0x000001ad,0x000001a9,
0x000001ac,0x0006000c,0x00000006,0x000001ae,
0x00000001,0x00000008,0x000001ad,0x00050088,
0x00000006,0x000001b2,0x000000a1,0x000001ae,
0x0005008d,0x00000006,0x000001b4,0x000001ae,
0x00000116,0x000500b7,0x00000196,0x000001b5,
0x000001b4,0x0000004a,0x000600a9,0x00000006,
0x000004fc,0x000001b5,0x0000004a,0x000001b2,
0x0004003d,0x0000000a,0x000001bb,0x000001ba,
0x00050041,0x000001bc,0x000001bd,0x00000187,
0x0000006c,0x0004003d,0x00000037,0x000001be,
0x000001bd,0x0007004f,0x0000000a,0x000001bf,
0x000001be,0x000001be,0x00000000,0x00000001,
0x00050085,0x0000000a,0x000001c0,0x000001bb,
0x000001bf,0x00050050,0x0000000a,0x000001c3,
0x0000004a,0x000004fc,0x00050083,0x0000000a,
0x000001c4,0x000001c0,0x000001c3,0x0007004f,
0x0000000a,0x000001c7,0x000001be,0x000001be,
0x00000002,0x00000003,0x00050085,0x0000000a,
0x000001c8,0x000001c4,0x000001c7,0x00050051,
0x00000006,0x00000249,0x000001be,0x00000000,
0x00050088,0x00000006,0x0000024a,0x00000049,
0x00000249,0x00050050,0x0000000a,0x0000024b,
0x0000024a,0x0000004a,0x00050051,0x00000006,
0x0000024d,0x000001be,0x00000001,0x00050088,
0x00000006,0x0000024e,0x00000049,0x0000024d,
0x00050050,0x0000000a,0x0000024f,0x0000004a,
0x0000024e,0x00050085,0x0000000a,0x00000252,
0x000001c8,0x000001bf,0x0006000c,0x0000000a,
0x00000254,0x00000001,0x00000008,0x00000252,
0x00050081,0x0000000a,0x00000256,0x00000254,
0x000004f3,0x00050088,0x0000000a,0x00000258,
0x00000256,0x000001bf,0x0006000c,0x0000000a,
0x0000025a,0x00000001,0x0000000a,0x00000252,
0x00050083,0x0000000a,0x0000025c,0x0000025a,
0x000004f3,0x0005008e,0x0000000a,0x0000025d,
0x0000025c,0x00000068,0x00050083,0x0000000a,
0x00000260,0x00000258,0x0000024f,0x0004003d,
0x00000008,0x0000028a,0x000001ca,0x00050083,
0x0000000a,0x0000028d,0x00000260,0x0000024b,
0x00050057,0x00000037,0x0000028e,0x0000028a,
0x0000028d,0x0008004f,0x0000000c,0x0000028f,
0x0000028e,0x0000028e,0x00000000,0x00000001,
0x00000002,0x0007000c,0x0000000c,0x00000290,
0x00000001,0x0000001a,0x0000028f,0x0000003b,
0x00050057,0x00000037,0x00000293,0x0000028a,
0x00000260,0x0008004f,0x0000000c,0x00000294,
0x00000293,0x00000293,0x00000000,0x00000001,
0x00000002,0x0007000c,0x0000000c,0x00000295,
0x00000001,0x0000001a,0x00000294,0x0000003b,
0x00050081,0x0000000a,0x00000299,0x00000260,
0x0000024b,0x00050057,0x00000037,0x0000029a,
0x0000028a,0x00000299,0x0008004f,0x0000000c,
0x0000029b,0x0000029a,0x0000029a,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x0000029c,0x00000001,0x0000001a,0x0000029b,
0x0000003b,0x00050083,0x0000000a,0x000002af,
0x00000258,0x0000024b,0x00050057,0x00000037,
0x000002b0,0x0000028a,0x000002af,0x0008004f,
0x0000000c,0x000002b1,0x000002b0,0x000002b0,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x000002b2,0x00000001,0x0000001a,
0x000002b1,0x0000003b,0x00050057,0x00000037,
0x000002b5,0x0000028a,0x00000258,0x0008004f,
0x0000000c,0x000002b6,0x000002b5,0x000002b5,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x000002b7,0x00000001,0x0000001a,
0x000002b6,0x0000003b,0x00050081,0x0000000a,
0x000002bb,0x00000258,0x0000024b,0x00050057,
0x00000037,0x000002bc,0x0000028a,0x000002bb,
0x0008004f,0x0000000c,0x000002bd,0x000002bc,
0x000002bc,0x00000000,0x00000001,0x00000002,
0x0007000c,0x0000000c,0x000002be,0x00000001,
0x0000001a,0x000002bd,0x0000003b,0x00050081,
0x0000000a,0x00000268,0x00000258,0x0000024f,
0x00050083,0x0000000a,0x000002d1,0x00000268,
0x0000024b,0x00050057,0x00000037,0x000002d2,
0x0000028a,0x000002d1,0x0008004f,0x0000000c,
0x000002d3,0x000002d2,0x000002d2,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000002d4,0x00000001,0x0000001a,0x000002d3,
0x0000003b,0x00050057,0x00000037,0x000002d7,
0x0000028a,0x00000268,0x0008004f,0x0000000c,
0x000002d8,0x000002d7,0x000002d7,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000002d9,0x00000001,0x0000001a,0x000002d8,
0x0000003b,0x00050081,0x0000000a,0x000002dd,
0x00000268,0x0000024b,0x00050057,0x00000037,
0x000002de,0x0000028a,0x000002dd,0x0008004f,
0x0000000c,0x000002df,0x000002de,0x000002de,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x000002e0,0x00000001,0x0000001a,
0x000002df,0x0000003b,0x00050051,0x00000006,
0x0000026d,0x0000025d,0x00000000,0x00050083,
0x00000006,0x000002f3,0x0000026d,0x00000049,
0x00050081,0x00000006,0x000002f6,0x0000026d,
0x00000049,0x00060050,0x0000000c,0x000002f7,
0x000002f3,0x0000026d,0x000002f6,0x00050085,
0x0000000c,0x000002fa,0x000002f7,0x000004f6,
0x00050085,0x0000000c,0x000002fd,0x000002fa,
0x000002fa,0x0005008e,0x0000000c,0x000002fe,
0x000002fd,0x00000068,0x0006000c,0x0000000c,
0x000002ff,0x00000001,0x0000001d,0x000002fe,
0x00050051,0x00000006,0x00000303,0x000002ff,
0x00000000,0x0005008e,0x0000000c,0x00000304,
0x00000290,0x00000303,0x00050051,0x00000006,
0x00000308,0x000002ff,0x00000001,0x0005008e,
0x0000000c,0x00000309,0x00000295,0x00000308,
0x00050081,0x0000000c,0x0000030a,0x00000304,
0x00000309,0x00050051,0x00000006,0x0000030e,
0x000002ff,0x00000002,0x0005008e,0x0000000c,
0x0000030f,0x0000029c,0x0000030e,0x00050081,
0x0000000c,0x00000310,0x0000030a,0x0000030f,
0x00050081,0x00000006,0x00000315,0x00000303,
0x00000308,0x00050081,0x00000006,0x00000318,
0x00000315,0x0000030e,0x00060050,0x0000000c,
0x00000319,0x00000318,0x00000318,0x00000318,
0x00050088,0x0000000c,0x0000031a,0x00000310,
0x00000319,0x0005008e,0x0000000c,0x00000331,
0x000002b2,0x00000303,0x0005008e,0x0000000c,
0x00000336,0x000002b7,0x00000308,0x00050081,
0x0000000c,0x00000337,0x00000331,0x00000336,
0x0005008e,0x0000000c,0x0000033c,0x000002be,
0x0000030e,0x00050081,0x0000000c,0x0000033d,
0x00000337,0x0000033c,0x00050088,0x0000000c,
0x00000347,0x0000033d,0x00000319,0x0005008e,
0x0000000c,0x0000035e,0x000002d4,0x00000303,
0x0005008e,0x0000000c,0x00000363,0x000002d9,
0x00000308,0x00050081,0x0000000c,0x00000364,
0x0000035e,0x00000363,0x0005008e,0x0000000c,
0x00000369,0x000002e0,0x0000030e,0x00050081,
0x0000000c,0x0000036a,0x00000364,0x00000369,
0x00050088,0x0000000c,0x00000374,0x0000036a,
0x00000319,0x00050051,0x00000006,0x00000286,
0x0000025d,0x00000001,0x00050083,0x00000006,
0x0000037a,0x00000286,0x00000049,0x00050081,
0x00000006,0x0000037d,0x00000286,0x00000049,
0x00060050,0x0000000c,0x0000037e,0x0000037a,
0x00000286,0x0000037d,0x00050085,0x0000000c,
0x00000381,0x0000037e,0x000004f6,0x00050085,
0x0000000c,0x00000384,0x00000381,0x00000381,
0x0005008e,0x0000000c,0x00000385,0x00000384,
0x00000068,0x0006000c,0x0000000c,0x00000386,
0x00000001,0x0000001d,0x00000385,0x00050051,
0x00000006,0x0000038a,0x00000386,0x00000000,
0x0005008e,0x0000000c,0x0000038b,0x0000031a,
0x0000038a,0x00050051,0x00000006,0x0000038f,
0x00000386,0x00000001,0x0005008e,0x0000000c,
0x00000390,0x00000347,0x0000038f,0x00050081,
0x0000000c,0x00000391,0x0000038b,0x00000390,
0x00050051,0x00000006,0x00000395,0x00000386,
0x00000002,0x0005008e,0x0000000c,0x00000396,
0x00000374,0x00000395,0x00050081,0x0000000c,
0x00000397,0x00000391,0x00000396,0x00050081,
0x00000006,0x0000039c,0x0000038a,0x0000038f,
0x00050081,0x00000006,0x0000039f,0x0000039c,
0x00000395,0x00060050,0x0000000c,0x000003a0,
0x0000039f,0x0000039f,0x0000039f,0x00050088,
0x0000000c,0x000003a1,0x00000397,0x000003a0,
0x00060052,0x0000000a,0x000004e1,0x00000249,
0x000001bf,0x00000000,0x00050085,0x0000000a,
0x000003b6,0x000001c8,0x000004e1,0x00050083,
0x0000000a,0x000003b7,0x000003b6,0x000000fc,
0x0006000c,0x0000000a,0x000003b9,0x00000001,
0x00000008,0x000003b7,0x00050081,0x0000000a,
0x000003ba,0x000003b9,0x00000102,0x00050088,
0x0000000a,0x000003bc,0x000003ba,0x000004e1,
0x0006000c,0x0000000a,0x000003be,0x00000001,
0x0000000a,0x000003b7,0x00050051,0x00000006,
0x000003c0,0x000003be,0x00000000,0x00050081,
0x00000006,0x000003c1,0x000003c0,0x00000049,
0x00050083,0x00000006,0x000003c6,0x000003c0,
0x00000049,0x00050083,0x00000006,0x000003c9,
0x000003c0,0x00000116,0x00070050,0x00000037,
0x000003ca,0x000003c1,0x000003c0,0x000003c6,
0x000003c9,0x0005008e,0x00000037,0x000003cb,
0x000003ca,0x0000010b,0x0006000c,0x00000037,
0x000003cd,0x00000001,0x00000004,0x000003cb,
0x0007000c,0x00000037,0x000003cf,0x00000001,
0x00000028,0x000003cd,0x000004f4,0x0006000c,
0x00000037,0x000003d1,0x00000001,0x0000000d,
0x000003cf,0x0005008e,0x00000037,0x000003d2,
0x000003d1,0x00000116,0x00050085,0x00000037,
0x000003d5,0x000003cf,0x000004fa,0x0006000c,
0x00000037,0x000003d6,0x00000001,0x0000000d,
0x000003d5,0x00050085,0x00000037,0x000003d7,
0x000003d2,0x000003d6,0x00050085,0x00000037,
0x000003da,0x000003cf,0x000003cf,0x00050088,
0x00000037,0x000003db,0x000003d7,0x000003da,
0x00050094,0x00000006,0x000003dd,0x000003db,
0x0000012c,0x00070050,0x00000037,0x000003df,
0x000003dd,0x000003dd,0x000003dd,0x000003dd,
0x00050088,0x00000037,0x000003e0,0x000003db,
0x000003df,0x00050057,0x00000037,0x000003e3,
0x0000028a,0x000003bc,0x0008004f,0x0000000c,
0x000003e4,0x000003e3,0x000003e3,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000003e5,0x00000001,0x0000001a,0x000003e4,
0x0000003b,0x00050051,0x00000006,0x000003e6,
0x000003e5,0x00000000,0x00050051,0x00000006,
0x000003e7,0x000003e5,0x00000001,0x00050051,
0x00000006,0x000003e8,0x000003e5,0x00000002,
0x00050081,0x0000000a,0x000003ed,0x000003bc,
0x0000024b,0x00050057,0x00000037,0x000003ee,
0x0000028a,0x000003ed,0x0008004f,0x0000000c,
0x000003ef,0x000003ee,0x000003ee,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000003f0,0x00000001,0x0000001a,0x000003ef,
0x0000003b,0x00050051,0x00000006,0x000003f1,
0x000003f0,0x00000000,0x00050051,0x00000006,
0x000003f2,0x000003f0,0x00000001,0x00050051,
0x00000006,0x000003f3,0x000003f0,0x00000002,
0x00070050,0x00000037,0x00000407,0x000003e6,
0x000003e7,0x000003e8,0x00000049,0x00070050,
0x00000037,0x00000409,0x000003f1,0x000003f2,
0x000003f3,0x00000049,0x00070050,0x00000149,
0x0000040b,0x00000407,0x00000407,0x00000409,
0x00000409,0x00050091,0x00000037,0x0000040d,
0x0000040b,0x000003e0,0x0008004f,0x0000000c,
0x0000040e,0x0000040d,0x0000040d,0x00000000,
0x00000001,0x00000002,0x00050085,0x00000006,
0x0000041b,0x00000249,0x000001dc,0x00060052,
0x0000000a,0x000004e9,0x0000041b,0x000001bf,
0x00000000,0x00050088,0x00000006,0x0000041f,
0x000004fb,0x00000249,0x00050050,0x0000000a,
0x00000420,0x0000041f,0x0000004a,0x00050085,
0x0000000a,0x00000423,0x000001c8,0x000004e9,
0x00050083,0x0000000a,0x00000424,0x00000423,
0x000000fc,0x0006000c,0x0000000a,0x00000426,
0x00000001,0x00000008,0x00000424,0x00050081,
0x0000000a,0x00000427,0x00000426,0x00000102,
0x00050088,0x0000000a,0x00000429,0x00000427,
0x000004e9,0x0006000c,0x0000000a,0x0000042b,
0x00000001,0x0000000a,0x00000424,0x00050051,
0x00000006,0x0000042d,0x0000042b,0x00000000,
0x00050081,0x00000006,0x0000042e,0x0000042d,
0x00000049,0x00050083,0x00000006,0x00000433,
0x0000042d,0x00000049,0x00050083,0x00000006,
0x00000436,0x0000042d,0x00000116,0x00070050,
0x00000037,0x00000437,0x0000042e,0x0000042d,
0x00000433,0x00000436,0x0005008e,0x00000037,
0x00000438,0x00000437,0x0000010b,0x0006000c,
0x00000037,0x0000043a,0x00000001,0x00000004,
0x00000438,0x0007000c,0x00000037,0x0000043c,
0x00000001,0x00000028,0x0000043a,0x000004f4,
0x0006000c,0x00000037,0x0000043e,0x00000001,
0x0000000d,0x0000043c,0x0005008e,0x00000037,
0x0000043f,0x0000043e,0x00000116,0x00050085,
0x00000037,0x00000442,0x0000043c,0x000004fa,
0x0006000c,0x00000037,0x00000443,0x00000001,
0x0000000d,0x00000442,0x00050085,0x00000037,
0x00000444,0x0000043f,0x00000443,0x00050085,
0x00000037,0x00000447,0x0000043c,0x0000043c,
0x00050088,0x00000037,0x00000448,0x00000444,
0x00000447,0x00050094,0x00000006,0x0000044a,
0x00000448,0x0000012c,0x00070050,0x00000037,
0x0000044c,0x0000044a,0x0000044a,0x0000044a,
0x0000044a,0x00050088,0x00000037,0x0000044d,
0x00000448,0x0000044c,0x00050057,0x00000037,
0x00000450,0x0000028a,0x00000429,0x0008004f,
0x0000000c,0x00000451,0x00000450,0x00000450,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x00000452,0x00000001,0x0000001a,
0x00000451,0x0000003b,0x00050051,0x00000006,
0x00000453,0x00000452,0x00000000,0x00050051,
0x00000006,0x00000454,0x00000452,0x00000001,
0x00050051,0x00000006,0x00000455,0x00000452,
0x00000002,0x00050081,0x0000000a,0x0000045a,
0x00000429,0x00000420,0x00050057,0x00000037,
0x0000045b,0x0000028a,0x0000045a,0x0008004f,
0x0000000c,0x0000045c,0x0000045b,0x0000045b,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x0000045d,0x00000001,0x0000001a,
0x0000045c,0x0000003b,0x00050051,0x00000006,
0x0000045e,0x0000045d,0x00000000,0x00050051,
0x00000006,0x0000045f,0x0000045d,0x00000001,
0x00050051,0x00000006,0x00000460,0x0000045d,
0x00000002,0x00070050,0x00000037,0x00000474,
0x00000453,0x00000454,0x00000455,0x00000049,
0x00070050,0x00000037,0x00000476,0x0000045e,
0x0000045f,0x00000460,0x00000049,0x00070050,
0x00000149,0x00000478,0x00000474,0x00000474,
0x00000476,0x00000476,0x00050091,0x00000037,
0x0000047a,0x00000478,0x0000044d,0x0008004f,
0x0000000c,0x0000047b,0x0000047a,0x0000047a,
0x00000000,0x00000001,0x00000002,0x00050085,
0x0000000c,0x000001e8,0x0000047b,0x0000040e,
0x0006000c,0x0000000c,0x000001e9,0x00000001,
0x0000001f,0x000001e8,0x00050051,0x00000006,
0x000001eb,0x000001c8,0x00000001,0x00060041,
0x00000188,0x000001ec,0x00000187,0x0000006c,
0x00000077,0x0004003d,0x00000006,0x000001ed,
0x000001ec,0x00050085,0x00000006,0x000001ee,
0x000001eb,0x000001ed,0x0006000c,0x00000006,
0x000001ef,0x00000001,0x0000000a,0x000001ee,
0x0007000c,0x0000000c,0x00000482,0x00000001,
0x0000001a,0x0000040e,0x0000016a,0x0008000c,
0x0000000c,0x00000483,0x00000001,0x0000002e,
0x00000165,0x00000167,0x00000482,0x00050088,
0x0000000c,0x00000486,0x000004f6,0x00000483,
0x0005008e,0x0000000c,0x00000488,0x00000486,
0x000000a1,0x0005008e,0x0000000c,0x0000048b,
0x00000486,0x000001ef,0x00050083,0x0000000c,
0x0000048d,0x0000048b,0x00000488,0x0006000c,
0x0000000c,0x0000048e,0x00000001,0x00000004,
0x0000048d,0x00050083,0x0000000c,0x00000490,
0x000004f7,0x0000048e,0x0008000c,0x0000000c,
0x00000493,0x00000001,0x00000031,0x000004f8,
0x000004f7,0x00000490,0x00050085,0x0000000c,
0x00000495,0x00000493,0x00000488,0x00050085,
0x0000000c,0x000001f5,0x000001e9,0x00000495,
0x00050083,0x0000000c,0x000001f8,0x000003a1,
0x000001f5,0x0008000c,0x0000000c,0x000001fb,
0x00000001,0x0000002b,0x000001f8,0x000004f8,
0x000004f7,0x00050085,0x0000000c,0x000001fe,
0x000001fb,0x000001fb,0x0005008e,0x0000000c,
0x00000200,0x000001fe,0x000001ff,0x00050081,
0x0000000c,0x00000202,0x000001f5,0x00000200,
0x00050041,0x00000206,0x00000207,0x000001ba,
0x00000070,0x0004003d,0x00000006,0x00000208,
0x00000207,0x000300f7,0x000004b3,0x00000000,
0x000300fb,0x00000070,0x0000049b,0x000200f8,
0x0000049b,0x00060041,0x00000188,0x0000049d,
0x00000187,0x00000074,0x00000070,0x0004003d,
0x00000006,0x0000049e,0x0000049d,0x00050085,
0x00000006,0x0000049f,0x00000208,0x0000049e,
0x0006000c,0x00000006,0x000004a7,0x00000001,
0x00000008,0x0000049f,0x0005008d,0x00000006,
0x000004a8,0x000004a7,0x00000116,0x000500b4,
0x00000196,0x000004aa,0x000004a8,0x0000004a,
0x000300f7,0x000004b2,0x00000000,0x000400fa,
0x000004aa,0x000004ab,0x000004ac,0x000200f8,
0x000004ac,0x000500b4,0x00000196,0x000004ae,
0x000004a8,0x00000049,0x000300f7,0x000004b1,
0x00000000,0x000400fa,0x000004ae,0x000004af,
0x000004b0,0x000200f8,0x000004b0,0x000200f9,
0x000004b3,0x000200f8,0x000004af,0x000200f9,
0x000004b3,0x000200f8,0x000004b1,0x000100ff,
0x000200f8,0x000004ab,0x000200f9,0x000004b3,
0x000200f8,0x000004b2,0x000100ff,0x000200f8,
0x000004b3,0x000900f5,0x0000000c,0x000004f1,
0x0000019a,0x000004ab,0x000001a1,0x000004af,
0x000001a4,0x000004b0,0x00050085,0x0000000c,
0x0000020a,0x00000202,0x000004f1,0x0005008e,
0x0000000c,0x0000020b,0x0000020a,0x00000116,
0x0008000c,0x0000000c,0x0000020e,0x00000001,
0x0000002e,0x00000202,0x0000020b,0x000004f9,
0x0005008e,0x0000000c,0x00000211,0x000001fb,
0x00000210,0x00050081,0x0000000c,0x00000213,
0x0000020e,0x00000211,0x0005008e,0x0000000c,
0x00000215,0x00000213,0x00000166,0x0007000c,
0x0000000c,0x00000218,0x00000001,0x0000001a,
0x00000215,0x00000217,0x00050051,0x00000006,
0x0000021c,0x00000218,0x00000000,0x00050051,
0x00000006,0x0000021d,0x00000218,0x00000001,
0x00050051,0x00000006,0x0000021e,0x00000218,
0x00000002,0x00070050,0x00000037,0x0000021f,
0x0000021c,0x0000021d,0x0000021e,0x00000049,
0x0003003e,0x0000021a,0x0000021f,0x000100fd,
0x00010038}
+10
View File
@@ -0,0 +1,10 @@
#version 450
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 0) uniform sampler2D uTexture;
layout(location = 0) in vec2 vUV;
void main()
{
FragColor = textureLod(uTexture, vUV, 0.0);
}
+21
View File
@@ -0,0 +1,21 @@
#version 450
layout(location = 0) out vec2 vUV;
void main()
{
if (gl_VertexIndex == 0)
{
gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);
}
else if (gl_VertexIndex == 1)
{
gl_Position = vec4(-1.0, +3.0, 0.0, 1.0);
}
else
{
gl_Position = vec4(+3.0, -1.0, 0.0, 1.0);
}
vUV = gl_Position.xy * 0.5 + 0.5;
}
+32
View File
@@ -0,0 +1,32 @@
{0x07230203,0x00010300,0x000d000b,0x00000015,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
0x0007000f,0x00000004,0x00000004,0x6e69616d,
0x00000000,0x00000009,0x00000011,0x00030010,
0x00000004,0x00000007,0x00040047,0x00000009,
0x0000001e,0x00000000,0x00040047,0x0000000d,
0x00000021,0x00000000,0x00040047,0x0000000d,
0x00000022,0x00000000,0x00040047,0x00000011,
0x0000001e,0x00000000,0x00020013,0x00000002,
0x00030021,0x00000003,0x00000002,0x00030016,
0x00000006,0x00000020,0x00040017,0x00000007,
0x00000006,0x00000004,0x00040020,0x00000008,
0x00000003,0x00000007,0x0004003b,0x00000008,
0x00000009,0x00000003,0x00090019,0x0000000a,
0x00000006,0x00000001,0x00000000,0x00000000,
0x00000000,0x00000001,0x00000000,0x0003001b,
0x0000000b,0x0000000a,0x00040020,0x0000000c,
0x00000000,0x0000000b,0x0004003b,0x0000000c,
0x0000000d,0x00000000,0x00040017,0x0000000f,
0x00000006,0x00000002,0x00040020,0x00000010,
0x00000001,0x0000000f,0x0004003b,0x00000010,
0x00000011,0x00000001,0x0004002b,0x00000006,
0x00000013,0x00000000,0x00050036,0x00000002,
0x00000004,0x00000000,0x00000003,0x000200f8,
0x00000005,0x0004003d,0x0000000b,0x0000000e,
0x0000000d,0x0004003d,0x0000000f,0x00000012,
0x00000011,0x00070058,0x00000007,0x00000014,
0x0000000e,0x00000012,0x00000002,0x00000013,
0x0003003e,0x00000009,0x00000014,0x000100fd,
0x00010038}
+72
View File
@@ -0,0 +1,72 @@
{0x07230203,0x00010300,0x000d000b,0x00000034,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
0x0008000f,0x00000000,0x00000004,0x6e69616d,
0x00000000,0x00000008,0x00000016,0x0000002b,
0x00040047,0x00000008,0x0000000b,0x0000002a,
0x00030047,0x00000014,0x00000002,0x00050048,
0x00000014,0x00000000,0x0000000b,0x00000000,
0x00050048,0x00000014,0x00000001,0x0000000b,
0x00000001,0x00050048,0x00000014,0x00000002,
0x0000000b,0x00000003,0x00050048,0x00000014,
0x00000003,0x0000000b,0x00000004,0x00040047,
0x0000002b,0x0000001e,0x00000000,0x00020013,
0x00000002,0x00030021,0x00000003,0x00000002,
0x00040015,0x00000006,0x00000020,0x00000001,
0x00040020,0x00000007,0x00000001,0x00000006,
0x0004003b,0x00000007,0x00000008,0x00000001,
0x0004002b,0x00000006,0x0000000a,0x00000000,
0x00020014,0x0000000b,0x00030016,0x0000000f,
0x00000020,0x00040017,0x00000010,0x0000000f,
0x00000004,0x00040015,0x00000011,0x00000020,
0x00000000,0x0004002b,0x00000011,0x00000012,
0x00000001,0x0004001c,0x00000013,0x0000000f,
0x00000012,0x0006001e,0x00000014,0x00000010,
0x0000000f,0x00000013,0x00000013,0x00040020,
0x00000015,0x00000003,0x00000014,0x0004003b,
0x00000015,0x00000016,0x00000003,0x0004002b,
0x0000000f,0x00000017,0xbf800000,0x0004002b,
0x0000000f,0x00000018,0x00000000,0x0004002b,
0x0000000f,0x00000019,0x3f800000,0x0007002c,
0x00000010,0x0000001a,0x00000017,0x00000017,
0x00000018,0x00000019,0x00040020,0x0000001b,
0x00000003,0x00000010,0x0004002b,0x00000006,
0x0000001f,0x00000001,0x0004002b,0x0000000f,
0x00000023,0x40400000,0x0007002c,0x00000010,
0x00000024,0x00000017,0x00000023,0x00000018,
0x00000019,0x0007002c,0x00000010,0x00000027,
0x00000023,0x00000017,0x00000018,0x00000019,
0x00040017,0x00000029,0x0000000f,0x00000002,
0x00040020,0x0000002a,0x00000003,0x00000029,
0x0004003b,0x0000002a,0x0000002b,0x00000003,
0x0004002b,0x0000000f,0x0000002f,0x3f000000,
0x0005002c,0x00000029,0x00000033,0x0000002f,
0x0000002f,0x00050036,0x00000002,0x00000004,
0x00000000,0x00000003,0x000200f8,0x00000005,
0x0004003d,0x00000006,0x00000009,0x00000008,
0x000500aa,0x0000000b,0x0000000c,0x00000009,
0x0000000a,0x000300f7,0x0000000e,0x00000000,
0x000400fa,0x0000000c,0x0000000d,0x0000001d,
0x000200f8,0x0000000d,0x00050041,0x0000001b,
0x0000001c,0x00000016,0x0000000a,0x0003003e,
0x0000001c,0x0000001a,0x000200f9,0x0000000e,
0x000200f8,0x0000001d,0x000500aa,0x0000000b,
0x00000020,0x00000009,0x0000001f,0x000300f7,
0x00000022,0x00000000,0x000400fa,0x00000020,
0x00000021,0x00000026,0x000200f8,0x00000021,
0x00050041,0x0000001b,0x00000025,0x00000016,
0x0000000a,0x0003003e,0x00000025,0x00000024,
0x000200f9,0x00000022,0x000200f8,0x00000026,
0x00050041,0x0000001b,0x00000028,0x00000016,
0x0000000a,0x0003003e,0x00000028,0x00000027,
0x000200f9,0x00000022,0x000200f8,0x00000022,
0x000200f9,0x0000000e,0x000200f8,0x0000000e,
0x00050041,0x0000001b,0x0000002c,0x00000016,
0x0000000a,0x0004003d,0x00000010,0x0000002d,
0x0000002c,0x0007004f,0x00000029,0x0000002e,
0x0000002d,0x0000002d,0x00000000,0x00000001,
0x0005008e,0x00000029,0x00000030,0x0000002e,
0x0000002f,0x00050081,0x00000029,0x00000032,
0x00000030,0x00000033,0x0003003e,0x0000002b,
0x00000032,0x000100fd,0x00010038}
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
glslc -O crt-aperture.frag -o crt_frag.spv --target-env=vulkan1.1 -mfmt=c
glslc -O main.frag -o main_frag.spv --target-env=vulkan1.1 -mfmt=c
glslc -O main.vert -o main_vert.spv --target-env=vulkan1.1 -mfmt=c
+85 -43
View File
@@ -3,6 +3,7 @@
#include "rdp_device.hpp"
#include "interface.hpp"
#include "spirv.hpp"
#include "spirv_crt.hpp"
#include <SDL3/SDL_vulkan.h>
using namespace Vulkan;
@@ -51,8 +52,6 @@ enum vi_registers
VI_REGS_COUNT
};
static bool fullscreen;
static bool integer_scaling;
static SDL_Window *window;
static RDP::CommandProcessor *processor;
static SDL_WSIPlatform *wsi_platform;
@@ -63,6 +62,9 @@ static int cmd_ptr;
static CALL_BACK callback;
static GFX_INFO gfx_info;
static uint32_t region;
static bool crop_letterbox;
static const uint32_t *fragment_spirv;
static size_t fragment_size;
typedef struct
{
@@ -76,7 +78,13 @@ typedef struct
uint8_t depthbuffer_enabled;
} FrameBufferInfo;
static uint8_t *rdram_dirty;
typedef struct
{
float SourceSize[4];
float OutputSize[4];
} Push;
static std::vector<bool> rdram_dirty;
static uint64_t sync_signal;
static FrameBufferInfo frame_buffer_info;
@@ -164,8 +172,8 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
case SDL_SCANCODE_RETURN:
if (event->key.mod & SDL_KMOD_ALT)
{
fullscreen = !fullscreen;
SDL_SetWindowFullscreen(window, fullscreen);
gfx_info.fullscreen = !gfx_info.fullscreen;
SDL_SetWindowFullscreen(window, gfx_info.fullscreen);
}
break;
case SDL_SCANCODE_F:
@@ -175,9 +183,12 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
}
break;
case SDL_SCANCODE_ESCAPE:
if (fullscreen)
if (gfx_info.fullscreen)
callback.emu_running = false;
break;
case SDL_SCANCODE_F4:
crop_letterbox = !crop_letterbox;
break;
case SDL_SCANCODE_F5:
callback.save_state = true;
break;
@@ -192,11 +203,11 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
return 0;
}
void rdp_new_processor(GFX_INFO _gfx_info, uint32_t upscale)
void rdp_new_processor(GFX_INFO _gfx_info)
{
memset(&frame_buffer_info, 0, sizeof(FrameBufferInfo));
sync_signal = 0;
memset(rdram_dirty, 0, gfx_info.RDRAM_SIZE / 8);
rdram_dirty.assign(gfx_info.RDRAM_SIZE / 8, false);
gfx_info = _gfx_info;
if (processor)
@@ -205,12 +216,12 @@ void rdp_new_processor(GFX_INFO _gfx_info, uint32_t upscale)
}
RDP::CommandProcessorFlags flags = 0;
if (upscale == 2)
if (gfx_info.upscale == 2)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
}
else if (upscale == 4)
else if (gfx_info.upscale == 4)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_4X_BIT;
@@ -219,7 +230,7 @@ void rdp_new_processor(GFX_INFO _gfx_info, uint32_t upscale)
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, uint32_t _upscale, bool _integer_scaling, bool _fullscreen)
void rdp_init(void *_window, GFX_INFO _gfx_info)
{
window = (SDL_Window *)_window;
bool result = SDL_AddEventWatch(sdl_event_filter, nullptr);
@@ -230,8 +241,18 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, uint32_t _upscale, bool _intege
}
gfx_info = _gfx_info;
fullscreen = _fullscreen;
integer_scaling = _integer_scaling;
if (gfx_info.crt)
{
fragment_spirv = crt_fragment_spirv;
fragment_size = sizeof(crt_fragment_spirv);
}
else
{
fragment_spirv = plain_fragment_spirv;
fragment_size = sizeof(plain_fragment_spirv);
}
bool window_vsync = 0;
wsi = new WSI;
wsi_platform = new SDL_WSIPlatform;
@@ -249,8 +270,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, uint32_t _upscale, bool _intege
rdp_close();
}
rdram_dirty = (uint8_t *)malloc(gfx_info.RDRAM_SIZE / 8);
rdp_new_processor(gfx_info, _upscale);
rdp_new_processor(gfx_info);
if (!processor->device_is_supported())
{
@@ -263,16 +283,11 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, uint32_t _upscale, bool _intege
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)
@@ -292,15 +307,14 @@ void rdp_close()
}
}
static void calculate_viewport(float *x, float *y, float *width, float *height)
static void calculate_viewport(float *x, float *y, float *width, float *height, uint32_t display_height)
{
const int32_t display_width = gfx_info.PAL ? 384 : 320;
const int32_t display_height = gfx_info.PAL ? 288 : 240;
uint32_t display_width = gfx_info.widescreen ? display_height * 16 / 9 : display_height * 4 / 3;
int w, h;
SDL_GetWindowSize(window, &w, &h);
if (integer_scaling)
if (gfx_info.integer_scaling)
{
// Integer scaling path
int scale_x = w / display_width;
@@ -315,6 +329,13 @@ static void calculate_viewport(float *x, float *y, float *width, float *height)
*width = scaled_width;
*height = scaled_height;
// Center the viewport
int integer_x = (w - *width) / 2.0f;
int integer_y = (h - *height) / 2.0f;
*x = integer_x;
*y = integer_y;
}
else
{
@@ -325,11 +346,11 @@ static void calculate_viewport(float *x, float *y, float *width, float *height)
*width = display_width * scale;
*height = display_height * scale;
}
// Center the viewport
*x = (w - *width) / 2.0f;
*y = (h - *height) / 2.0f;
// Center the viewport
*x = (w - *width) / 2.0f;
*y = (h - *height) / 2.0f;
}
}
static void render_frame(Vulkan::Device &device)
@@ -338,17 +359,34 @@ 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.
Vulkan::ResourceLayout vertex_layout = {};
Vulkan::ResourceLayout fragment_layout = {};
fragment_layout.output_mask = 1 << 0;
fragment_layout.sets[0].sampled_image_mask = 1 << 0;
if (gfx_info.crt)
fragment_layout.push_constant_size = sizeof(Push);
// This request is cached.
auto *program = device.request_program(vertex_spirv, sizeof(vertex_spirv),
fragment_spirv, sizeof(fragment_spirv),
fragment_spirv, fragment_size,
&vertex_layout,
&fragment_layout);
@@ -358,9 +396,6 @@ static void render_frame(Vulkan::Device &device)
auto rp = device.get_swapchain_render_pass(Vulkan::SwapchainRenderPass::ColorOnly);
cmd->begin_render_pass(rp);
VkViewport vp = cmd->get_viewport();
calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height);
cmd->set_program(program);
// Basic default render state.
@@ -371,6 +406,19 @@ static void render_frame(Vulkan::Device &device)
// If we don't have an image, we just get a cleared screen in the render pass.
if (image)
{
VkViewport vp = cmd->get_viewport();
calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height, image->get_height() / gfx_info.upscale);
if (gfx_info.crt)
{
// Set shader parameters
Push push = {
{float(image->get_width()), float(image->get_height()), 1.0f / float(image->get_width()), 1.0f / float(image->get_height())},
{vp.width, vp.height, 1.0f / vp.width, 1.0f / vp.height},
};
cmd->push_constants(&push, 0, sizeof(push));
}
cmd->set_texture(0, 0, image->get_view(), Vulkan::StockSampler::LinearClamp);
cmd->set_viewport(vp);
// The vertices are constants in the shader.
@@ -409,7 +457,7 @@ 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);
rdram_dirty.assign(gfx_info.RDRAM_SIZE / 8, false);
sync_signal = 0;
}
}
@@ -507,18 +555,12 @@ uint64_t rdp_process_commands()
{
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;
}
std::fill_n(rdram_dirty.begin() + frame_buffer_info.framebuffer_address, frame_buffer_info.framebuffer_size, true);
}
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;
}
std::fill_n(rdram_dirty.begin() + frame_buffer_info.depthbuffer_address, frame_buffer_info.depthbuffer_size, true);
}
}
else if (RDP::Op(command) == RDP::Op::SetOtherModes)
+7 -2
View File
@@ -17,6 +17,11 @@ extern "C"
uint32_t *DPC_END_REG;
uint32_t *DPC_STATUS_REG;
bool PAL;
bool widescreen;
bool fullscreen;
bool integer_scaling;
uint32_t upscale;
bool crt;
} GFX_INFO;
typedef struct
@@ -27,13 +32,13 @@ extern "C"
bool enable_speedlimiter;
} CALL_BACK;
void rdp_init(void *_window, GFX_INFO _gfx_info, uint32_t _upscale, bool _integer_scaling, bool _fullscreen);
void rdp_init(void *_window, GFX_INFO _gfx_info);
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, uint32_t _upscale);
void rdp_new_processor(GFX_INFO _gfx_info);
void rdp_check_framebuffers(uint32_t address);
void rdp_full_sync();
+11 -11
View File
@@ -1,19 +1,19 @@
#pragma once
static const uint32_t vertex_spirv[] =
{0x07230203, 0x00010000, 0x000d000a, 0x00000034,
{0x07230203, 0x00010300, 0x000d000b, 0x00000034,
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x0008000f, 0x00000000, 0x00000004, 0x6e69616d,
0x00000000, 0x00000008, 0x00000016, 0x0000002b,
0x00040047, 0x00000008, 0x0000000b, 0x0000002a,
0x00050048, 0x00000014, 0x00000000, 0x0000000b,
0x00000000, 0x00050048, 0x00000014, 0x00000001,
0x0000000b, 0x00000001, 0x00050048, 0x00000014,
0x00000002, 0x0000000b, 0x00000003, 0x00050048,
0x00000014, 0x00000003, 0x0000000b, 0x00000004,
0x00030047, 0x00000014, 0x00000002, 0x00040047,
0x00030047, 0x00000014, 0x00000002, 0x00050048,
0x00000014, 0x00000000, 0x0000000b, 0x00000000,
0x00050048, 0x00000014, 0x00000001, 0x0000000b,
0x00000001, 0x00050048, 0x00000014, 0x00000002,
0x0000000b, 0x00000003, 0x00050048, 0x00000014,
0x00000003, 0x0000000b, 0x00000004, 0x00040047,
0x0000002b, 0x0000001e, 0x00000000, 0x00020013,
0x00000002, 0x00030021, 0x00000003, 0x00000002,
0x00040015, 0x00000006, 0x00000020, 0x00000001,
@@ -74,8 +74,8 @@ static const uint32_t vertex_spirv[] =
0x00000030, 0x00000033, 0x0003003e, 0x0000002b,
0x00000032, 0x000100fd, 0x00010038};
static const uint32_t fragment_spirv[] =
{0x07230203, 0x00010000, 0x000d000a, 0x00000015,
static const uint32_t plain_fragment_spirv[] =
{0x07230203, 0x00010300, 0x000d000b, 0x00000015,
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
@@ -83,8 +83,8 @@ static const uint32_t fragment_spirv[] =
0x00000000, 0x00000009, 0x00000011, 0x00030010,
0x00000004, 0x00000007, 0x00040047, 0x00000009,
0x0000001e, 0x00000000, 0x00040047, 0x0000000d,
0x00000022, 0x00000000, 0x00040047, 0x0000000d,
0x00000021, 0x00000000, 0x00040047, 0x00000011,
0x00000021, 0x00000000, 0x00040047, 0x0000000d,
0x00000022, 0x00000000, 0x00040047, 0x00000011,
0x0000001e, 0x00000000, 0x00020013, 0x00000002,
0x00030021, 0x00000003, 0x00000002, 0x00030016,
0x00000006, 0x00000020, 0x00040017, 0x00000007,
+456
View File
@@ -0,0 +1,456 @@
#pragma once
static const uint32_t crt_fragment_spirv[] =
{0x07230203, 0x00010300, 0x000d000b, 0x000004fd,
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x0007000f, 0x00000004, 0x00000004, 0x6e69616d,
0x00000000, 0x000001ba, 0x0000021a, 0x00030010,
0x00000004, 0x00000007, 0x00030047, 0x00000185,
0x00000002, 0x00050048, 0x00000185, 0x00000000,
0x00000023, 0x00000000, 0x00050048, 0x00000185,
0x00000001, 0x00000023, 0x00000010, 0x00040047,
0x000001ba, 0x0000001e, 0x00000000, 0x00040047,
0x000001ca, 0x00000021, 0x00000000, 0x00040047,
0x000001ca, 0x00000022, 0x00000000, 0x00040047,
0x0000021a, 0x0000001e, 0x00000000, 0x00020013,
0x00000002, 0x00030021, 0x00000003, 0x00000002,
0x00030016, 0x00000006, 0x00000020, 0x00090019,
0x00000007, 0x00000006, 0x00000001, 0x00000000,
0x00000000, 0x00000000, 0x00000001, 0x00000000,
0x0003001b, 0x00000008, 0x00000007, 0x00040020,
0x00000009, 0x00000000, 0x00000008, 0x00040017,
0x0000000a, 0x00000006, 0x00000002, 0x00040017,
0x0000000c, 0x00000006, 0x00000003, 0x00040017,
0x00000037, 0x00000006, 0x00000004, 0x0004002b,
0x00000006, 0x0000003a, 0x4019999a, 0x0006002c,
0x0000000c, 0x0000003b, 0x0000003a, 0x0000003a,
0x0000003a, 0x0004002b, 0x00000006, 0x00000049,
0x3f800000, 0x0004002b, 0x00000006, 0x0000004a,
0x00000000, 0x0004002b, 0x00000006, 0x00000068,
0xbf800000, 0x00040015, 0x0000006b, 0x00000020,
0x00000001, 0x0004002b, 0x0000006b, 0x0000006c,
0x00000000, 0x00040015, 0x0000006f, 0x00000020,
0x00000000, 0x0004002b, 0x0000006f, 0x00000070,
0x00000000, 0x0004002b, 0x0000006b, 0x00000074,
0x00000001, 0x0004002b, 0x0000006f, 0x00000077,
0x00000001, 0x0004002b, 0x00000006, 0x000000a1,
0x3f000000, 0x0005002c, 0x0000000a, 0x000000fc,
0x000000a1, 0x0000004a, 0x0004002b, 0x00000006,
0x00000101, 0x3a83126f, 0x0005002c, 0x0000000a,
0x00000102, 0x000000a1, 0x00000101, 0x0004002b,
0x00000006, 0x0000010b, 0x40490fdb, 0x0004002b,
0x00000006, 0x00000116, 0x40000000, 0x0004002b,
0x00000006, 0x0000011c, 0x3727c5ac, 0x0007002c,
0x00000037, 0x0000012c, 0x00000049, 0x00000049,
0x00000049, 0x00000049, 0x00040018, 0x00000149,
0x00000037, 0x00000004, 0x0006002c, 0x0000000c,
0x00000165, 0x000000a1, 0x000000a1, 0x000000a1,
0x0004002b, 0x00000006, 0x00000166, 0x3fc00000,
0x0006002c, 0x0000000c, 0x00000167, 0x00000166,
0x00000166, 0x00000166, 0x0004002b, 0x00000006,
0x00000169, 0x3ecccccd, 0x0006002c, 0x0000000c,
0x0000016a, 0x00000169, 0x00000169, 0x00000169,
0x0004001e, 0x00000185, 0x00000037, 0x00000037,
0x00040020, 0x00000186, 0x00000009, 0x00000185,
0x0004003b, 0x00000186, 0x00000187, 0x00000009,
0x00040020, 0x00000188, 0x00000009, 0x00000006,
0x00020014, 0x00000196, 0x0006002c, 0x0000000c,
0x0000019a, 0x00000049, 0x0000004a, 0x00000049,
0x0006002c, 0x0000000c, 0x000001a1, 0x0000004a,
0x00000049, 0x0000004a, 0x0006002c, 0x0000000c,
0x000001a4, 0x0000004a, 0x0000004a, 0x00000049,
0x0004002b, 0x0000006f, 0x000001aa, 0x00000003,
0x00040020, 0x000001b9, 0x00000001, 0x0000000a,
0x0004003b, 0x000001b9, 0x000001ba, 0x00000001,
0x00040020, 0x000001bc, 0x00000009, 0x00000037,
0x0004003b, 0x00000009, 0x000001ca, 0x00000000,
0x0004002b, 0x00000006, 0x000001dc, 0x40400000,
0x0004002b, 0x00000006, 0x000001ff, 0x3dcccccd,
0x00040020, 0x00000206, 0x00000001, 0x00000006,
0x0004002b, 0x00000006, 0x0000020c, 0x3e99999a,
0x0004002b, 0x00000006, 0x00000210, 0x3d4ccccd,
0x0004002b, 0x00000006, 0x00000216, 0x3ed55555,
0x0006002c, 0x0000000c, 0x00000217, 0x00000216,
0x00000216, 0x00000216, 0x00040020, 0x00000219,
0x00000003, 0x00000037, 0x0004003b, 0x00000219,
0x0000021a, 0x00000003, 0x0005002c, 0x0000000a,
0x000004f3, 0x000000a1, 0x000000a1, 0x0007002c,
0x00000037, 0x000004f4, 0x0000011c, 0x0000011c,
0x0000011c, 0x0000011c, 0x0006002c, 0x0000000c,
0x000004f6, 0x00000116, 0x00000116, 0x00000116,
0x0006002c, 0x0000000c, 0x000004f7, 0x00000049,
0x00000049, 0x00000049, 0x0006002c, 0x0000000c,
0x000004f8, 0x0000004a, 0x0000004a, 0x0000004a,
0x0006002c, 0x0000000c, 0x000004f9, 0x0000020c,
0x0000020c, 0x0000020c, 0x0007002c, 0x00000037,
0x000004fa, 0x000000a1, 0x000000a1, 0x000000a1,
0x000000a1, 0x0004002b, 0x00000006, 0x000004fb,
0x3eaaaaab, 0x00050036, 0x00000002, 0x00000004,
0x00000000, 0x00000003, 0x000200f8, 0x00000005,
0x00060041, 0x00000188, 0x000001a8, 0x00000187,
0x00000074, 0x00000077, 0x0004003d, 0x00000006,
0x000001a9, 0x000001a8, 0x00060041, 0x00000188,
0x000001ab, 0x00000187, 0x0000006c, 0x000001aa,
0x0004003d, 0x00000006, 0x000001ac, 0x000001ab,
0x00050085, 0x00000006, 0x000001ad, 0x000001a9,
0x000001ac, 0x0006000c, 0x00000006, 0x000001ae,
0x00000001, 0x00000008, 0x000001ad, 0x00050088,
0x00000006, 0x000001b2, 0x000000a1, 0x000001ae,
0x0005008d, 0x00000006, 0x000001b4, 0x000001ae,
0x00000116, 0x000500b7, 0x00000196, 0x000001b5,
0x000001b4, 0x0000004a, 0x000600a9, 0x00000006,
0x000004fc, 0x000001b5, 0x0000004a, 0x000001b2,
0x0004003d, 0x0000000a, 0x000001bb, 0x000001ba,
0x00050041, 0x000001bc, 0x000001bd, 0x00000187,
0x0000006c, 0x0004003d, 0x00000037, 0x000001be,
0x000001bd, 0x0007004f, 0x0000000a, 0x000001bf,
0x000001be, 0x000001be, 0x00000000, 0x00000001,
0x00050085, 0x0000000a, 0x000001c0, 0x000001bb,
0x000001bf, 0x00050050, 0x0000000a, 0x000001c3,
0x0000004a, 0x000004fc, 0x00050083, 0x0000000a,
0x000001c4, 0x000001c0, 0x000001c3, 0x0007004f,
0x0000000a, 0x000001c7, 0x000001be, 0x000001be,
0x00000002, 0x00000003, 0x00050085, 0x0000000a,
0x000001c8, 0x000001c4, 0x000001c7, 0x00050051,
0x00000006, 0x00000249, 0x000001be, 0x00000000,
0x00050088, 0x00000006, 0x0000024a, 0x00000049,
0x00000249, 0x00050050, 0x0000000a, 0x0000024b,
0x0000024a, 0x0000004a, 0x00050051, 0x00000006,
0x0000024d, 0x000001be, 0x00000001, 0x00050088,
0x00000006, 0x0000024e, 0x00000049, 0x0000024d,
0x00050050, 0x0000000a, 0x0000024f, 0x0000004a,
0x0000024e, 0x00050085, 0x0000000a, 0x00000252,
0x000001c8, 0x000001bf, 0x0006000c, 0x0000000a,
0x00000254, 0x00000001, 0x00000008, 0x00000252,
0x00050081, 0x0000000a, 0x00000256, 0x00000254,
0x000004f3, 0x00050088, 0x0000000a, 0x00000258,
0x00000256, 0x000001bf, 0x0006000c, 0x0000000a,
0x0000025a, 0x00000001, 0x0000000a, 0x00000252,
0x00050083, 0x0000000a, 0x0000025c, 0x0000025a,
0x000004f3, 0x0005008e, 0x0000000a, 0x0000025d,
0x0000025c, 0x00000068, 0x00050083, 0x0000000a,
0x00000260, 0x00000258, 0x0000024f, 0x0004003d,
0x00000008, 0x0000028a, 0x000001ca, 0x00050083,
0x0000000a, 0x0000028d, 0x00000260, 0x0000024b,
0x00050057, 0x00000037, 0x0000028e, 0x0000028a,
0x0000028d, 0x0008004f, 0x0000000c, 0x0000028f,
0x0000028e, 0x0000028e, 0x00000000, 0x00000001,
0x00000002, 0x0007000c, 0x0000000c, 0x00000290,
0x00000001, 0x0000001a, 0x0000028f, 0x0000003b,
0x00050057, 0x00000037, 0x00000293, 0x0000028a,
0x00000260, 0x0008004f, 0x0000000c, 0x00000294,
0x00000293, 0x00000293, 0x00000000, 0x00000001,
0x00000002, 0x0007000c, 0x0000000c, 0x00000295,
0x00000001, 0x0000001a, 0x00000294, 0x0000003b,
0x00050081, 0x0000000a, 0x00000299, 0x00000260,
0x0000024b, 0x00050057, 0x00000037, 0x0000029a,
0x0000028a, 0x00000299, 0x0008004f, 0x0000000c,
0x0000029b, 0x0000029a, 0x0000029a, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x0000029c, 0x00000001, 0x0000001a, 0x0000029b,
0x0000003b, 0x00050083, 0x0000000a, 0x000002af,
0x00000258, 0x0000024b, 0x00050057, 0x00000037,
0x000002b0, 0x0000028a, 0x000002af, 0x0008004f,
0x0000000c, 0x000002b1, 0x000002b0, 0x000002b0,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x000002b2, 0x00000001, 0x0000001a,
0x000002b1, 0x0000003b, 0x00050057, 0x00000037,
0x000002b5, 0x0000028a, 0x00000258, 0x0008004f,
0x0000000c, 0x000002b6, 0x000002b5, 0x000002b5,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x000002b7, 0x00000001, 0x0000001a,
0x000002b6, 0x0000003b, 0x00050081, 0x0000000a,
0x000002bb, 0x00000258, 0x0000024b, 0x00050057,
0x00000037, 0x000002bc, 0x0000028a, 0x000002bb,
0x0008004f, 0x0000000c, 0x000002bd, 0x000002bc,
0x000002bc, 0x00000000, 0x00000001, 0x00000002,
0x0007000c, 0x0000000c, 0x000002be, 0x00000001,
0x0000001a, 0x000002bd, 0x0000003b, 0x00050081,
0x0000000a, 0x00000268, 0x00000258, 0x0000024f,
0x00050083, 0x0000000a, 0x000002d1, 0x00000268,
0x0000024b, 0x00050057, 0x00000037, 0x000002d2,
0x0000028a, 0x000002d1, 0x0008004f, 0x0000000c,
0x000002d3, 0x000002d2, 0x000002d2, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000002d4, 0x00000001, 0x0000001a, 0x000002d3,
0x0000003b, 0x00050057, 0x00000037, 0x000002d7,
0x0000028a, 0x00000268, 0x0008004f, 0x0000000c,
0x000002d8, 0x000002d7, 0x000002d7, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000002d9, 0x00000001, 0x0000001a, 0x000002d8,
0x0000003b, 0x00050081, 0x0000000a, 0x000002dd,
0x00000268, 0x0000024b, 0x00050057, 0x00000037,
0x000002de, 0x0000028a, 0x000002dd, 0x0008004f,
0x0000000c, 0x000002df, 0x000002de, 0x000002de,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x000002e0, 0x00000001, 0x0000001a,
0x000002df, 0x0000003b, 0x00050051, 0x00000006,
0x0000026d, 0x0000025d, 0x00000000, 0x00050083,
0x00000006, 0x000002f3, 0x0000026d, 0x00000049,
0x00050081, 0x00000006, 0x000002f6, 0x0000026d,
0x00000049, 0x00060050, 0x0000000c, 0x000002f7,
0x000002f3, 0x0000026d, 0x000002f6, 0x00050085,
0x0000000c, 0x000002fa, 0x000002f7, 0x000004f6,
0x00050085, 0x0000000c, 0x000002fd, 0x000002fa,
0x000002fa, 0x0005008e, 0x0000000c, 0x000002fe,
0x000002fd, 0x00000068, 0x0006000c, 0x0000000c,
0x000002ff, 0x00000001, 0x0000001d, 0x000002fe,
0x00050051, 0x00000006, 0x00000303, 0x000002ff,
0x00000000, 0x0005008e, 0x0000000c, 0x00000304,
0x00000290, 0x00000303, 0x00050051, 0x00000006,
0x00000308, 0x000002ff, 0x00000001, 0x0005008e,
0x0000000c, 0x00000309, 0x00000295, 0x00000308,
0x00050081, 0x0000000c, 0x0000030a, 0x00000304,
0x00000309, 0x00050051, 0x00000006, 0x0000030e,
0x000002ff, 0x00000002, 0x0005008e, 0x0000000c,
0x0000030f, 0x0000029c, 0x0000030e, 0x00050081,
0x0000000c, 0x00000310, 0x0000030a, 0x0000030f,
0x00050081, 0x00000006, 0x00000315, 0x00000303,
0x00000308, 0x00050081, 0x00000006, 0x00000318,
0x00000315, 0x0000030e, 0x00060050, 0x0000000c,
0x00000319, 0x00000318, 0x00000318, 0x00000318,
0x00050088, 0x0000000c, 0x0000031a, 0x00000310,
0x00000319, 0x0005008e, 0x0000000c, 0x00000331,
0x000002b2, 0x00000303, 0x0005008e, 0x0000000c,
0x00000336, 0x000002b7, 0x00000308, 0x00050081,
0x0000000c, 0x00000337, 0x00000331, 0x00000336,
0x0005008e, 0x0000000c, 0x0000033c, 0x000002be,
0x0000030e, 0x00050081, 0x0000000c, 0x0000033d,
0x00000337, 0x0000033c, 0x00050088, 0x0000000c,
0x00000347, 0x0000033d, 0x00000319, 0x0005008e,
0x0000000c, 0x0000035e, 0x000002d4, 0x00000303,
0x0005008e, 0x0000000c, 0x00000363, 0x000002d9,
0x00000308, 0x00050081, 0x0000000c, 0x00000364,
0x0000035e, 0x00000363, 0x0005008e, 0x0000000c,
0x00000369, 0x000002e0, 0x0000030e, 0x00050081,
0x0000000c, 0x0000036a, 0x00000364, 0x00000369,
0x00050088, 0x0000000c, 0x00000374, 0x0000036a,
0x00000319, 0x00050051, 0x00000006, 0x00000286,
0x0000025d, 0x00000001, 0x00050083, 0x00000006,
0x0000037a, 0x00000286, 0x00000049, 0x00050081,
0x00000006, 0x0000037d, 0x00000286, 0x00000049,
0x00060050, 0x0000000c, 0x0000037e, 0x0000037a,
0x00000286, 0x0000037d, 0x00050085, 0x0000000c,
0x00000381, 0x0000037e, 0x000004f6, 0x00050085,
0x0000000c, 0x00000384, 0x00000381, 0x00000381,
0x0005008e, 0x0000000c, 0x00000385, 0x00000384,
0x00000068, 0x0006000c, 0x0000000c, 0x00000386,
0x00000001, 0x0000001d, 0x00000385, 0x00050051,
0x00000006, 0x0000038a, 0x00000386, 0x00000000,
0x0005008e, 0x0000000c, 0x0000038b, 0x0000031a,
0x0000038a, 0x00050051, 0x00000006, 0x0000038f,
0x00000386, 0x00000001, 0x0005008e, 0x0000000c,
0x00000390, 0x00000347, 0x0000038f, 0x00050081,
0x0000000c, 0x00000391, 0x0000038b, 0x00000390,
0x00050051, 0x00000006, 0x00000395, 0x00000386,
0x00000002, 0x0005008e, 0x0000000c, 0x00000396,
0x00000374, 0x00000395, 0x00050081, 0x0000000c,
0x00000397, 0x00000391, 0x00000396, 0x00050081,
0x00000006, 0x0000039c, 0x0000038a, 0x0000038f,
0x00050081, 0x00000006, 0x0000039f, 0x0000039c,
0x00000395, 0x00060050, 0x0000000c, 0x000003a0,
0x0000039f, 0x0000039f, 0x0000039f, 0x00050088,
0x0000000c, 0x000003a1, 0x00000397, 0x000003a0,
0x00060052, 0x0000000a, 0x000004e1, 0x00000249,
0x000001bf, 0x00000000, 0x00050085, 0x0000000a,
0x000003b6, 0x000001c8, 0x000004e1, 0x00050083,
0x0000000a, 0x000003b7, 0x000003b6, 0x000000fc,
0x0006000c, 0x0000000a, 0x000003b9, 0x00000001,
0x00000008, 0x000003b7, 0x00050081, 0x0000000a,
0x000003ba, 0x000003b9, 0x00000102, 0x00050088,
0x0000000a, 0x000003bc, 0x000003ba, 0x000004e1,
0x0006000c, 0x0000000a, 0x000003be, 0x00000001,
0x0000000a, 0x000003b7, 0x00050051, 0x00000006,
0x000003c0, 0x000003be, 0x00000000, 0x00050081,
0x00000006, 0x000003c1, 0x000003c0, 0x00000049,
0x00050083, 0x00000006, 0x000003c6, 0x000003c0,
0x00000049, 0x00050083, 0x00000006, 0x000003c9,
0x000003c0, 0x00000116, 0x00070050, 0x00000037,
0x000003ca, 0x000003c1, 0x000003c0, 0x000003c6,
0x000003c9, 0x0005008e, 0x00000037, 0x000003cb,
0x000003ca, 0x0000010b, 0x0006000c, 0x00000037,
0x000003cd, 0x00000001, 0x00000004, 0x000003cb,
0x0007000c, 0x00000037, 0x000003cf, 0x00000001,
0x00000028, 0x000003cd, 0x000004f4, 0x0006000c,
0x00000037, 0x000003d1, 0x00000001, 0x0000000d,
0x000003cf, 0x0005008e, 0x00000037, 0x000003d2,
0x000003d1, 0x00000116, 0x00050085, 0x00000037,
0x000003d5, 0x000003cf, 0x000004fa, 0x0006000c,
0x00000037, 0x000003d6, 0x00000001, 0x0000000d,
0x000003d5, 0x00050085, 0x00000037, 0x000003d7,
0x000003d2, 0x000003d6, 0x00050085, 0x00000037,
0x000003da, 0x000003cf, 0x000003cf, 0x00050088,
0x00000037, 0x000003db, 0x000003d7, 0x000003da,
0x00050094, 0x00000006, 0x000003dd, 0x000003db,
0x0000012c, 0x00070050, 0x00000037, 0x000003df,
0x000003dd, 0x000003dd, 0x000003dd, 0x000003dd,
0x00050088, 0x00000037, 0x000003e0, 0x000003db,
0x000003df, 0x00050057, 0x00000037, 0x000003e3,
0x0000028a, 0x000003bc, 0x0008004f, 0x0000000c,
0x000003e4, 0x000003e3, 0x000003e3, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000003e5, 0x00000001, 0x0000001a, 0x000003e4,
0x0000003b, 0x00050051, 0x00000006, 0x000003e6,
0x000003e5, 0x00000000, 0x00050051, 0x00000006,
0x000003e7, 0x000003e5, 0x00000001, 0x00050051,
0x00000006, 0x000003e8, 0x000003e5, 0x00000002,
0x00050081, 0x0000000a, 0x000003ed, 0x000003bc,
0x0000024b, 0x00050057, 0x00000037, 0x000003ee,
0x0000028a, 0x000003ed, 0x0008004f, 0x0000000c,
0x000003ef, 0x000003ee, 0x000003ee, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000003f0, 0x00000001, 0x0000001a, 0x000003ef,
0x0000003b, 0x00050051, 0x00000006, 0x000003f1,
0x000003f0, 0x00000000, 0x00050051, 0x00000006,
0x000003f2, 0x000003f0, 0x00000001, 0x00050051,
0x00000006, 0x000003f3, 0x000003f0, 0x00000002,
0x00070050, 0x00000037, 0x00000407, 0x000003e6,
0x000003e7, 0x000003e8, 0x00000049, 0x00070050,
0x00000037, 0x00000409, 0x000003f1, 0x000003f2,
0x000003f3, 0x00000049, 0x00070050, 0x00000149,
0x0000040b, 0x00000407, 0x00000407, 0x00000409,
0x00000409, 0x00050091, 0x00000037, 0x0000040d,
0x0000040b, 0x000003e0, 0x0008004f, 0x0000000c,
0x0000040e, 0x0000040d, 0x0000040d, 0x00000000,
0x00000001, 0x00000002, 0x00050085, 0x00000006,
0x0000041b, 0x00000249, 0x000001dc, 0x00060052,
0x0000000a, 0x000004e9, 0x0000041b, 0x000001bf,
0x00000000, 0x00050088, 0x00000006, 0x0000041f,
0x000004fb, 0x00000249, 0x00050050, 0x0000000a,
0x00000420, 0x0000041f, 0x0000004a, 0x00050085,
0x0000000a, 0x00000423, 0x000001c8, 0x000004e9,
0x00050083, 0x0000000a, 0x00000424, 0x00000423,
0x000000fc, 0x0006000c, 0x0000000a, 0x00000426,
0x00000001, 0x00000008, 0x00000424, 0x00050081,
0x0000000a, 0x00000427, 0x00000426, 0x00000102,
0x00050088, 0x0000000a, 0x00000429, 0x00000427,
0x000004e9, 0x0006000c, 0x0000000a, 0x0000042b,
0x00000001, 0x0000000a, 0x00000424, 0x00050051,
0x00000006, 0x0000042d, 0x0000042b, 0x00000000,
0x00050081, 0x00000006, 0x0000042e, 0x0000042d,
0x00000049, 0x00050083, 0x00000006, 0x00000433,
0x0000042d, 0x00000049, 0x00050083, 0x00000006,
0x00000436, 0x0000042d, 0x00000116, 0x00070050,
0x00000037, 0x00000437, 0x0000042e, 0x0000042d,
0x00000433, 0x00000436, 0x0005008e, 0x00000037,
0x00000438, 0x00000437, 0x0000010b, 0x0006000c,
0x00000037, 0x0000043a, 0x00000001, 0x00000004,
0x00000438, 0x0007000c, 0x00000037, 0x0000043c,
0x00000001, 0x00000028, 0x0000043a, 0x000004f4,
0x0006000c, 0x00000037, 0x0000043e, 0x00000001,
0x0000000d, 0x0000043c, 0x0005008e, 0x00000037,
0x0000043f, 0x0000043e, 0x00000116, 0x00050085,
0x00000037, 0x00000442, 0x0000043c, 0x000004fa,
0x0006000c, 0x00000037, 0x00000443, 0x00000001,
0x0000000d, 0x00000442, 0x00050085, 0x00000037,
0x00000444, 0x0000043f, 0x00000443, 0x00050085,
0x00000037, 0x00000447, 0x0000043c, 0x0000043c,
0x00050088, 0x00000037, 0x00000448, 0x00000444,
0x00000447, 0x00050094, 0x00000006, 0x0000044a,
0x00000448, 0x0000012c, 0x00070050, 0x00000037,
0x0000044c, 0x0000044a, 0x0000044a, 0x0000044a,
0x0000044a, 0x00050088, 0x00000037, 0x0000044d,
0x00000448, 0x0000044c, 0x00050057, 0x00000037,
0x00000450, 0x0000028a, 0x00000429, 0x0008004f,
0x0000000c, 0x00000451, 0x00000450, 0x00000450,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x00000452, 0x00000001, 0x0000001a,
0x00000451, 0x0000003b, 0x00050051, 0x00000006,
0x00000453, 0x00000452, 0x00000000, 0x00050051,
0x00000006, 0x00000454, 0x00000452, 0x00000001,
0x00050051, 0x00000006, 0x00000455, 0x00000452,
0x00000002, 0x00050081, 0x0000000a, 0x0000045a,
0x00000429, 0x00000420, 0x00050057, 0x00000037,
0x0000045b, 0x0000028a, 0x0000045a, 0x0008004f,
0x0000000c, 0x0000045c, 0x0000045b, 0x0000045b,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x0000045d, 0x00000001, 0x0000001a,
0x0000045c, 0x0000003b, 0x00050051, 0x00000006,
0x0000045e, 0x0000045d, 0x00000000, 0x00050051,
0x00000006, 0x0000045f, 0x0000045d, 0x00000001,
0x00050051, 0x00000006, 0x00000460, 0x0000045d,
0x00000002, 0x00070050, 0x00000037, 0x00000474,
0x00000453, 0x00000454, 0x00000455, 0x00000049,
0x00070050, 0x00000037, 0x00000476, 0x0000045e,
0x0000045f, 0x00000460, 0x00000049, 0x00070050,
0x00000149, 0x00000478, 0x00000474, 0x00000474,
0x00000476, 0x00000476, 0x00050091, 0x00000037,
0x0000047a, 0x00000478, 0x0000044d, 0x0008004f,
0x0000000c, 0x0000047b, 0x0000047a, 0x0000047a,
0x00000000, 0x00000001, 0x00000002, 0x00050085,
0x0000000c, 0x000001e8, 0x0000047b, 0x0000040e,
0x0006000c, 0x0000000c, 0x000001e9, 0x00000001,
0x0000001f, 0x000001e8, 0x00050051, 0x00000006,
0x000001eb, 0x000001c8, 0x00000001, 0x00060041,
0x00000188, 0x000001ec, 0x00000187, 0x0000006c,
0x00000077, 0x0004003d, 0x00000006, 0x000001ed,
0x000001ec, 0x00050085, 0x00000006, 0x000001ee,
0x000001eb, 0x000001ed, 0x0006000c, 0x00000006,
0x000001ef, 0x00000001, 0x0000000a, 0x000001ee,
0x0007000c, 0x0000000c, 0x00000482, 0x00000001,
0x0000001a, 0x0000040e, 0x0000016a, 0x0008000c,
0x0000000c, 0x00000483, 0x00000001, 0x0000002e,
0x00000165, 0x00000167, 0x00000482, 0x00050088,
0x0000000c, 0x00000486, 0x000004f6, 0x00000483,
0x0005008e, 0x0000000c, 0x00000488, 0x00000486,
0x000000a1, 0x0005008e, 0x0000000c, 0x0000048b,
0x00000486, 0x000001ef, 0x00050083, 0x0000000c,
0x0000048d, 0x0000048b, 0x00000488, 0x0006000c,
0x0000000c, 0x0000048e, 0x00000001, 0x00000004,
0x0000048d, 0x00050083, 0x0000000c, 0x00000490,
0x000004f7, 0x0000048e, 0x0008000c, 0x0000000c,
0x00000493, 0x00000001, 0x00000031, 0x000004f8,
0x000004f7, 0x00000490, 0x00050085, 0x0000000c,
0x00000495, 0x00000493, 0x00000488, 0x00050085,
0x0000000c, 0x000001f5, 0x000001e9, 0x00000495,
0x00050083, 0x0000000c, 0x000001f8, 0x000003a1,
0x000001f5, 0x0008000c, 0x0000000c, 0x000001fb,
0x00000001, 0x0000002b, 0x000001f8, 0x000004f8,
0x000004f7, 0x00050085, 0x0000000c, 0x000001fe,
0x000001fb, 0x000001fb, 0x0005008e, 0x0000000c,
0x00000200, 0x000001fe, 0x000001ff, 0x00050081,
0x0000000c, 0x00000202, 0x000001f5, 0x00000200,
0x00050041, 0x00000206, 0x00000207, 0x000001ba,
0x00000070, 0x0004003d, 0x00000006, 0x00000208,
0x00000207, 0x000300f7, 0x000004b3, 0x00000000,
0x000300fb, 0x00000070, 0x0000049b, 0x000200f8,
0x0000049b, 0x00060041, 0x00000188, 0x0000049d,
0x00000187, 0x00000074, 0x00000070, 0x0004003d,
0x00000006, 0x0000049e, 0x0000049d, 0x00050085,
0x00000006, 0x0000049f, 0x00000208, 0x0000049e,
0x0006000c, 0x00000006, 0x000004a7, 0x00000001,
0x00000008, 0x0000049f, 0x0005008d, 0x00000006,
0x000004a8, 0x000004a7, 0x00000116, 0x000500b4,
0x00000196, 0x000004aa, 0x000004a8, 0x0000004a,
0x000300f7, 0x000004b2, 0x00000000, 0x000400fa,
0x000004aa, 0x000004ab, 0x000004ac, 0x000200f8,
0x000004ac, 0x000500b4, 0x00000196, 0x000004ae,
0x000004a8, 0x00000049, 0x000300f7, 0x000004b1,
0x00000000, 0x000400fa, 0x000004ae, 0x000004af,
0x000004b0, 0x000200f8, 0x000004b0, 0x000200f9,
0x000004b3, 0x000200f8, 0x000004af, 0x000200f9,
0x000004b3, 0x000200f8, 0x000004b1, 0x000100ff,
0x000200f8, 0x000004ab, 0x000200f9, 0x000004b3,
0x000200f8, 0x000004b2, 0x000100ff, 0x000200f8,
0x000004b3, 0x000900f5, 0x0000000c, 0x000004f1,
0x0000019a, 0x000004ab, 0x000001a1, 0x000004af,
0x000001a4, 0x000004b0, 0x00050085, 0x0000000c,
0x0000020a, 0x00000202, 0x000004f1, 0x0005008e,
0x0000000c, 0x0000020b, 0x0000020a, 0x00000116,
0x0008000c, 0x0000000c, 0x0000020e, 0x00000001,
0x0000002e, 0x00000202, 0x0000020b, 0x000004f9,
0x0005008e, 0x0000000c, 0x00000211, 0x000001fb,
0x00000210, 0x00050081, 0x0000000c, 0x00000213,
0x0000020e, 0x00000211, 0x0005008e, 0x0000000c,
0x00000215, 0x00000213, 0x00000166, 0x0007000c,
0x0000000c, 0x00000218, 0x00000001, 0x0000001a,
0x00000215, 0x00000217, 0x00050051, 0x00000006,
0x0000021c, 0x00000218, 0x00000000, 0x00050051,
0x00000006, 0x0000021d, 0x00000218, 0x00000001,
0x00050051, 0x00000006, 0x0000021e, 0x00000218,
0x00000002, 0x00070050, 0x00000037, 0x0000021f,
0x0000021c, 0x0000021d, 0x0000021e, 0x00000049,
0x0003003e, 0x0000021a, 0x0000021f, 0x000100fd,
0x00010038};
+41 -6
View File
@@ -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.video.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,
@@ -460,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
View File
@@ -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);
}
+21 -12
View File
@@ -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) {
@@ -65,12 +63,14 @@ pub fn process(device: &mut device::Device, channel: usize) {
let eeprom_type;
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom16k)
{
eeprom_type = JDT_EEPROM_16K;
} else if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom4k)
{
@@ -125,9 +125,15 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
}
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)
pub fn format_eeprom(device: &mut device::Device) {
if device.ui.storage.saves.eeprom.data.len() < EEPROM_MAX_SIZE {
device
.ui
.storage
.saves
.eeprom
.data
.resize(EEPROM_MAX_SIZE, 0xFF)
}
}
@@ -136,8 +142,9 @@ 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.data[address..address + EEPROM_BLOCK_SIZE]);
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE].copy_from_slice(
&device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE],
);
}
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
@@ -145,16 +152,18 @@ fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize,
format_eeprom(device);
device.ui.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE]
device.ui.storage.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.written = true
device.ui.storage.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());
+49 -50
View File
@@ -1,19 +1,23 @@
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
if let Some(value) = device
.ui
.storage
.saves
.romsave
.data
.contains_key(&(address as u32 + i))
.get(&(address as u32 + i))
{
data[i as usize] = device.ui.saves.romsave.data[&(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,19 +50,22 @@ 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);
for (i, item) in data.to_be_bytes().iter().enumerate() {
device
.ui
.storage
.saves
.romsave
.data
.insert((masked_address + i) as u32, *item);
}
device.ui.saves.romsave.written = true;
device.ui.storage.saves.romsave.written = true;
}
device.cart.latch = value & mask;
@@ -66,11 +73,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 +82,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.data.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
device.ui.saves.romsave.written = true;
}
device.ui.storage.saves.romsave.data.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
}
device.ui.storage.saves.romsave.written = true;
}
device::pi::calculate_cycles(device, 1, length)
@@ -109,24 +111,34 @@ 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.data.contains_key(&j) {
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.romsave.data[&j];
while i < dram_addr + length {
if let Some(value) = device.ui.storage.saves.romsave.data.get(&j) {
device.rdram.mem[i as usize ^ device.byte_swap] = *value;
} 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;
device.cart.pal = is_system_pal(device.cart.rom[0x3E]);
set_cic(device);
@@ -153,40 +165,27 @@ 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)
}
}
+124 -30
View File
@@ -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.data.is_empty() {
device.ui.saves.sdcard.data.resize(SDCARD_SIZE, 0);
let buf = std::io::Cursor::new(&mut device.ui.saves.sdcard.data);
if device.ui.storage.saves.sdcard.data.is_empty() {
device.ui.storage.saves.sdcard.data.resize(SDCARD_SIZE, 0);
let buf = std::io::Cursor::new(&mut device.ui.storage.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.storage.saves.write_to_disk = false;
device.ui.storage.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,10 @@ 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.data.len() {
if offset + i < device.ui.storage.saves.sdcard.data.len() {
let data = u32::from_be_bytes(
device.ui.saves.sdcard.data[(offset + i)..(offset + i + 4)]
device.ui.storage.saves.sdcard.data
[(offset + i)..(offset + i + 4)]
.try_into()
.unwrap(),
);
@@ -148,7 +187,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.data.len() {
if offset + i < device.ui.storage.saves.sdcard.data.len() {
let data = device::memory::data_read(
device,
address + i as u64,
@@ -156,17 +195,33 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
false,
)
.to_be_bytes();
device.ui.saves.sdcard.data[(offset + i)..(offset + i + 4)]
device.ui.storage.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.written = true;
device.ui.storage.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 +245,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.storage.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.storage.saves.eeprom.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.storage.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 +296,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.storage.saves.eeprom.written = true;
&mut device.ui.storage.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 +324,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.storage.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;
}
+51 -27
View File
@@ -29,14 +29,20 @@ pub struct Flashram {
}
fn format_sram(device: &mut device::Device) {
if device.ui.saves.sram.data.len() < SRAM_SIZE {
device.ui.saves.sram.data.resize(SRAM_SIZE, 0xFF)
if device.ui.storage.saves.sram.data.len() < SRAM_SIZE {
device.ui.storage.saves.sram.data.resize(SRAM_SIZE, 0xFF)
}
}
fn format_flash(device: &mut device::Device) {
if device.ui.saves.flash.data.len() < FLASHRAM_SIZE {
device.ui.saves.flash.data.resize(FLASHRAM_SIZE, 0xFF)
if device.ui.storage.saves.flash.data.len() < FLASHRAM_SIZE {
device
.ui
.storage
.saves
.flash
.data
.resize(FLASHRAM_SIZE, 0xFF)
}
}
@@ -46,7 +52,7 @@ fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
format_sram(device);
u32::from_be_bytes(
device.ui.saves.sram.data[masked_address..masked_address + 4]
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
@@ -75,7 +81,12 @@ pub fn read_mem(
let cycles = device::pi::calculate_cycles(device, 2, 4);
device::cop0::add_cycles(device, cycles);
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
read_mem_sram(device, address)
} else {
read_mem_flash(device, address)
@@ -88,15 +99,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.data[masked_address..masked_address + 4]
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.ui.saves.sram.data[masked_address..masked_address + 4]
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.saves.sram.written = true
device.ui.storage.saves.sram.written = true
}
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -114,7 +125,12 @@ fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask:
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
write_mem_sram(device, address, value, mask)
} else {
write_mem_flash(device, address, value, mask)
@@ -123,11 +139,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) {
@@ -139,12 +151,13 @@ 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.data[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
device.ui.storage.saves.sram.data[j as usize] =
device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
device.ui.saves.sram.written = true
device.ui.storage.saves.sram.written = true
}
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
@@ -167,7 +180,12 @@ fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, l
// cart is big endian, rdram is native endian
pub fn dma_read(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) -> u64 {
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
dma_read_sram(device, cart_addr, dram_addr, length)
} else {
dma_read_flash(device, cart_addr, dram_addr, length)
@@ -189,7 +207,8 @@ 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.data[j as usize];
device.rdram.mem[i as usize ^ device.byte_swap] =
device.ui.storage.saves.sram.data[j as usize];
i += 1;
j += 1;
}
@@ -232,7 +251,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.data[(cart_addr + i) as usize];
device.ui.storage.saves.flash.data[(cart_addr + i) as usize];
}
} else {
/* other accesses are not implemented */
@@ -242,7 +261,12 @@ fn dma_write_flash(
// cart is big endian, rdram is native endian
pub fn dma_write(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) -> u64 {
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
dma_write_sram(device, cart_addr, dram_addr, length)
} else {
dma_write_flash(device, cart_addr, dram_addr, length)
@@ -271,14 +295,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.data[offset + i] = 0xFF;
device.ui.storage.saves.flash.data[offset + i] = 0xFF;
}
device.ui.saves.flash.written = true
device.ui.storage.saves.flash.written = true
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
for i in 0..FLASHRAM_SIZE {
device.ui.saves.flash.data[i] = 0xFF;
device.ui.storage.saves.flash.data[i] = 0xFF;
}
device.ui.saves.flash.written = true
device.ui.storage.saves.flash.written = true
} else {
panic!("Unexpected flash erase command")
}
@@ -296,9 +320,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.data[offset + i] = device.cart.flashram.page_buf[i];
device.ui.storage.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i];
}
device.ui.saves.flash.written = true;
device.ui.storage.saves.flash.written = true;
/* clear program busy flag, set program success flag, transition to status mode */
device.cart.flashram.status &= !0x01;
+10 -3
View File
@@ -1,5 +1,6 @@
use crate::{device, netplay, savestates, ui};
pub mod gbcart;
pub mod mempak;
pub mod rumble;
pub mod transferpak;
@@ -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");
}
+328
View File
@@ -0,0 +1,328 @@
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 mbc1_mode: 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,
MBC1RamBatt,
MBC3RamBatt,
MBC3RamBattRtc,
MBC5RamBatt,
}
fn write_mbc1(
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 & 0x1f;
cart.rom_bank = bank as u32;
if cart.rom_bank == 0 {
cart.rom_bank = 1;
}
} else if address < 0x6000 {
cart.ram_bank = (value & 0x3) as u16;
} else if address < 0x8000 {
cart.mbc1_mode = (value & 0x1) != 0;
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
return;
}
if cart.mbc1_mode {
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 {
let banked_address = address - 0xA000;
cart.ram[banked_address as usize..banked_address as usize + size]
.copy_from_slice(&pif_ram[data..data + size]);
}
}
}
fn read_mbc1(
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 > 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],
);
}
}
}
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::MBC1RamBatt => read_mbc1(pif_ram, cart, address, data, size),
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::MBC1RamBatt => write_mbc1(pif_ram, cart, address, data, size),
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 {
0x03 => CartType::MBC1RamBatt,
0x10 => CartType::MBC3RamBattRtc,
0x13 => CartType::MBC3RamBatt,
0x1b => CartType::MBC5RamBatt,
_ => panic!("Unsupported cart type {:x}", data),
}
}
+22 -13
View File
@@ -4,8 +4,14 @@ pub const MEMPAK_SIZE: usize = 0x8000;
const MPK_PAGE_SIZE: usize = 256;
fn format_mempak(device: &mut device::Device) {
if device.ui.saves.mempak.data.len() < MEMPAK_SIZE * 4 {
device.ui.saves.mempak.data.resize(MEMPAK_SIZE * 4, 0);
if device.ui.storage.saves.mempak.data.len() < MEMPAK_SIZE * 4 {
device
.ui
.storage
.saves
.mempak
.data
.resize(MEMPAK_SIZE * 4, 0);
let page_0: [u8; MPK_PAGE_SIZE] = [
/* Label area */
@@ -38,28 +44,31 @@ fn format_mempak(device: &mut device::Device) {
let offset = i * MEMPAK_SIZE;
/* Fill Page 0 with pre-initialized content */
device.ui.saves.mempak.data[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0);
device.ui.storage.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.data[offset + i] = 0;
device.ui.storage.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.data[offset + i] = 0x00;
device.ui.saves.mempak.data[offset + i + 1] = 0x03;
device.ui.storage.saves.mempak.data[offset + i] = 0x00;
device.ui.storage.saves.mempak.data[offset + i + 1] = 0x03;
}
device.ui.saves.mempak.data[offset + (MPK_PAGE_SIZE + 1)] = 0x71;
device.ui.storage.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.data[page1..page1 + MPK_PAGE_SIZE].to_vec();
device.ui.saves.mempak.data[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data);
let page1data =
device.ui.storage.saves.mempak.data[page1..page1 + MPK_PAGE_SIZE].to_vec();
device.ui.storage.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.data[offset + i] = 0;
device.ui.storage.saves.mempak.data[offset + i] = 0;
}
}
}
@@ -71,7 +80,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.data[offset..offset + size])
.copy_from_slice(&device.ui.storage.saves.mempak.data[offset..offset + size])
} else {
for i in 0..size {
device.pif.ram[data + i] = 0;
@@ -84,9 +93,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.data[offset..offset + size]
device.ui.storage.saves.mempak.data[offset..offset + size]
.copy_from_slice(&device.pif.ram[data..data + size]);
device.ui.saves.mempak.written = true
device.ui.storage.saves.mempak.written = true
}
}
+128 -14
View File
@@ -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);
}
}
}
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+8
View File
@@ -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,
+1 -5
View File
@@ -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);
}
}
+7 -4
View File
@@ -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;
+9 -11
View File
@@ -1,4 +1,5 @@
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] =
@@ -98,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] =
@@ -173,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;
@@ -196,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
View File
@@ -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
View File
@@ -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)
}
}
+3 -4
View File
@@ -106,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
View File
@@ -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);
}
+2 -8
View File
@@ -206,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,
);
}
@@ -467,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
View File
@@ -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) {
+2 -6
View File
@@ -65,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)
}
}
@@ -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,
+3 -3
View File
@@ -161,8 +161,8 @@ 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();
@@ -170,7 +170,7 @@ async fn main() {
} else {
let options = eframe::NativeOptions {
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(),
+37 -1
View File
@@ -16,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;
@@ -50,6 +54,38 @@ 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;
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) {
let mut request: Vec<u8> = [TCP_SEND_SAVE].to_vec();
request.extend_from_slice(save_type.as_bytes());
@@ -204,7 +240,7 @@ fn process_incoming(netplay: &mut Netplay, ui: &mut ui::Ui) {
}
}
_ => {
panic! {"unknown UDP packet"}
panic!("unknown UDP packet")
}
}
}
+29 -4
View File
@@ -74,20 +74,27 @@ pub fn create_savestate(device: &device::Device) {
let data: &[(&[u8], &str)] = &[
(&postcard::to_stdvec(device).unwrap(), "device"),
(&postcard::to_stdvec(&device.ui.saves).unwrap(), "saves"),
(
&postcard::to_stdvec(&device.ui.storage.saves).unwrap(),
"saves",
),
];
let compressed_file = ui::storage::compress_file(data);
std::fs::write(device.ui.paths.savestate_file_path.clone(), compressed_file).unwrap();
std::fs::write(
device.ui.storage.paths.savestate_file_path.clone(),
compressed_file,
)
.unwrap();
}
pub fn load_savestate(device: &mut device::Device) {
let savestate = std::fs::read(&mut device.ui.paths.savestate_file_path);
let savestate = std::fs::read(&mut device.ui.storage.paths.savestate_file_path);
if savestate.is_ok() {
let device_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "device");
let save_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "saves");
let state: device::Device = postcard::from_bytes(&device_bytes).unwrap();
device.ui.saves = postcard::from_bytes(&save_bytes).unwrap();
device.ui.storage.saves = postcard::from_bytes(&save_bytes).unwrap();
device.cpu = state.cpu;
device.pif = state.pif;
@@ -108,6 +115,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);
@@ -166,6 +182,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);
}
}
}
+115 -90
View File
@@ -12,32 +12,48 @@ pub struct Dirs {
pub data_dir: std::path::PathBuf,
}
pub struct Ui {
pub dirs: Dirs,
pub keyboard_state: *const bool,
pub controllers: [input::Controllers; 4],
pub config: config::Config,
pub save_type: Vec<storage::SaveTypes>,
pub game_id: String,
pub game_hash: String,
pub paths: storage::Paths,
pub saves: storage::Saves,
pub event_audio: audio::EventAudio,
pub window: *mut sdl3_sys::video::SDL_Window,
pub struct Audio {
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub event_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub audio_device: u32,
pub event_audio: audio::EventAudio,
}
pub struct Input {
pub num_joysticks: i32,
pub fullscreen: bool,
pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
pub keyboard_state: *const bool,
pub controllers: [input::Controllers; 4],
}
pub struct Storage {
pub save_type: Vec<storage::SaveTypes>,
pub paths: storage::Paths,
pub saves: storage::Saves,
}
pub struct Video {
pub window: *mut sdl3_sys::video::SDL_Window,
pub fullscreen: bool,
}
pub struct Ui {
pub dirs: Dirs,
pub config: config::Config,
pub game_id: String,
pub game_hash: String,
pub with_sdl: bool,
pub audio: Audio,
pub input: Input,
pub storage: Storage,
pub video: Video,
}
impl Drop for Ui {
fn drop(&mut self) {
if self.with_sdl {
unsafe {
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
sdl3_sys::stdinc::SDL_free(self.input.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
}
}
@@ -85,89 +101,98 @@ impl Ui {
let dirs = get_dirs();
Ui {
controllers: [
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
input: Input {
controllers: [
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
],
keyboard_state: std::ptr::null_mut(),
num_joysticks,
joysticks,
},
storage: Storage {
save_type: vec![],
paths: storage::Paths {
eep_file_path: std::path::PathBuf::new(),
fla_file_path: std::path::PathBuf::new(),
sra_file_path: std::path::PathBuf::new(),
pak_file_path: std::path::PathBuf::new(),
sdcard_file_path: std::path::PathBuf::new(),
romsave_file_path: std::path::PathBuf::new(),
savestate_file_path: std::path::PathBuf::new(),
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
saves: storage::Saves {
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,
},
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
],
keyboard_state: std::ptr::null_mut(),
},
config: config::Config::new(),
save_type: vec![],
game_id: String::new(),
game_hash: String::new(),
paths: storage::Paths {
eep_file_path: std::path::PathBuf::new(),
fla_file_path: std::path::PathBuf::new(),
sra_file_path: std::path::PathBuf::new(),
pak_file_path: std::path::PathBuf::new(),
sdcard_file_path: std::path::PathBuf::new(),
romsave_file_path: std::path::PathBuf::new(),
savestate_file_path: std::path::PathBuf::new(),
audio: Audio {
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(),
],
},
audio_stream: std::ptr::null_mut(),
event_audio_stream: std::ptr::null_mut(),
audio_device: 0,
},
saves: storage::Saves {
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,
},
video: Video {
window: std::ptr::null_mut(),
fullscreen: false,
},
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(),
event_audio_stream: std::ptr::null_mut(),
audio_device: 0,
fullscreen: false,
num_joysticks,
joysticks,
dirs,
with_sdl,
}
+57 -35
View File
@@ -14,33 +14,45 @@ pub struct EventAudio {
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,
};
ui.audio_device = unsafe {
let device_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: 48000,
channels: 2,
};
ui.audio.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 {
if ui.audio.audio_device == 0 {
panic!("Could not open audio device");
}
let mut dst = Default::default();
if !unsafe {
sdl3_sys::audio::SDL_GetAudioDeviceFormat(ui.audio_device, &mut dst, std::ptr::null_mut())
sdl3_sys::audio::SDL_GetAudioDeviceFormat(
ui.audio.audio_device,
&mut dst,
std::ptr::null_mut(),
)
} {
panic!("Could not get audio device format");
}
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
if ui.audio_stream.is_null() {
ui.audio.audio_stream =
unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&game_audio_spec, &dst) };
if ui.audio.audio_stream.is_null() {
return;
}
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.audio_stream) } {
if !unsafe {
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.audio_stream)
} {
panic!("Could not bind audio stream");
}
@@ -50,44 +62,54 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
channels: 1,
};
ui.event_audio_stream =
ui.audio.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) } {
if !unsafe {
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.event_audio_stream)
} {
panic!("Could not bind audio stream");
}
}
pub fn close(ui: &mut ui::Ui) {
unsafe {
if !ui.audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream);
ui.audio_stream = std::ptr::null_mut();
if !ui.audio.audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio.audio_stream);
ui.audio.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();
if !ui.audio.event_audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio.event_audio_stream);
ui.audio.event_audio_stream = std::ptr::null_mut();
}
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device);
ui.audio_device = 0;
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio.audio_device);
ui.audio.audio_device = 0;
}
}
pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) {
if ui.event_audio_stream.is_null() {
if ui.audio.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],
netplay::NETPLAY_ERROR_DESYNC => &ui.audio.event_audio.netplay_desync,
netplay::NETPLAY_ERROR_LOST_CONNECTION => &ui.audio.event_audio.netplay_lost_connection,
netplay::NETPLAY_ERROR_PLAYER_1_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[0]
}
netplay::NETPLAY_ERROR_PLAYER_2_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[1]
}
netplay::NETPLAY_ERROR_PLAYER_3_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[2]
}
netplay::NETPLAY_ERROR_PLAYER_4_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[3]
}
_ => panic!("Invalid netplay error"),
};
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
ui.event_audio_stream,
ui.audio.event_audio_stream,
sound.as_ptr() as *const std::ffi::c_void,
sound.len() as i32,
)
@@ -97,19 +119,19 @@ pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) {
}
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
if ui.event_audio_stream.is_null() {
if ui.audio.event_audio_stream.is_null() {
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,
device::controller::PakType::RumblePak => &ui.audio.event_audio.rumblepak,
device::controller::PakType::MemPak => &ui.audio.event_audio.mempak,
device::controller::PakType::TransferPak => &ui.audio.event_audio.transferpak,
_ => panic!("Invalid pak type"),
};
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
ui.event_audio_stream,
ui.audio.event_audio_stream,
sound.as_ptr() as *const std::ffi::c_void,
sound.len() as i32,
)
@@ -119,7 +141,7 @@ pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
}
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
if device.ui.audio_stream.is_null() {
if device.ui.audio.audio_stream.is_null() {
return;
}
@@ -137,7 +159,7 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
}
let audio_queued =
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio_stream) } as f64;
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio.audio_stream) } as f64;
let acceptable_latency = (device.ai.freq as f64 * 0.2) * 4.0;
let min_latency = (device.ai.freq as f64 * 0.02) * 4.0;
@@ -145,7 +167,7 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
device.ui.audio.audio_stream,
silence_buffer.as_ptr() as *const std::ffi::c_void,
silence_buffer.len() as i32,
)
@@ -157,7 +179,7 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
if audio_queued < acceptable_latency
&& !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
device.ui.audio.audio_stream,
primary_buffer.as_ptr() as *const std::ffi::c_void,
primary_buffer.len() as i32 * 2,
)
+15
View File
@@ -37,18 +37,29 @@ 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: u32,
pub integer_scaling: bool,
pub fullscreen: bool,
pub widescreen: bool,
pub crt: 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 {
@@ -88,13 +99,17 @@ 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: 1,
integer_scaling: false,
fullscreen: false,
widescreen: false,
crt: false,
},
emulation: Emulation { overclock: false },
}
}
}
+223 -63
View File
@@ -12,27 +12,42 @@ pub struct GopherEguiApp {
selected_profile: [String; 4],
input_profiles: Vec<String>,
controller_enabled: [bool; 4],
transfer_pak: [bool; 4],
controller_paths: Vec<String>,
upscale: u32,
integer_scaling: bool,
fullscreen: bool,
widescreen: bool,
crt: bool,
overclock: bool,
emulate_vru: bool,
dinput: bool,
show_vru_dialog: bool,
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<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],
transfer_pak: [bool; 4],
upscale: u32,
integer_scaling: bool,
fullscreen: bool,
widescreen: bool,
crt: bool,
emulate_vru: bool,
overclock: bool,
}
fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
@@ -46,10 +61,10 @@ fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
let mut controllers: Vec<String> = vec![];
for offset in 0..game_ui.num_joysticks as isize {
for offset in 0..game_ui.input.num_joysticks as isize {
let name = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID(
*(game_ui.joysticks.offset(offset)),
*(game_ui.input.joysticks.offset(offset)),
))
};
controllers.push(name.to_string_lossy().to_string());
@@ -61,10 +76,10 @@ pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<String> {
let mut controller_paths: Vec<String> = vec![];
for offset in 0..game_ui.num_joysticks as isize {
for offset in 0..game_ui.input.num_joysticks as isize {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(game_ui.joysticks.offset(offset)),
*(game_ui.input.joysticks.offset(offset)),
))
.to_string_lossy()
.to_string()
@@ -103,15 +118,21 @@ 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,
crt: config.video.crt,
emulate_vru: config.input.emulate_vru,
overclock: config.emulation.overclock,
show_vru_dialog: false,
dinput: false,
controller_paths,
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(),
@@ -135,11 +156,16 @@ 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.video.crt = save_config_items.crt;
config.input.emulate_vru = save_config_items.emulate_vru;
config.emulation.overclock = save_config_items.overclock;
}
impl Drop for GopherEguiApp {
@@ -148,10 +174,14 @@ 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,
crt: self.crt,
emulate_vru: self.emulate_vru,
overclock: self.overclock,
};
let mut config = ui::config::Config::new();
save_config(
@@ -197,56 +227,90 @@ fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.show_viewport_immediate(
egui::ViewportId::from_hash_of("vru_dialog"),
egui::ViewportBuilder::default()
.with_title("What would you like to say?")
.with_always_on_top(),
|ctx, class| {
assert!(
class == egui::ViewportClass::Immediate,
"This egui backend doesn't support multiple viewports"
);
egui::CentralPanel::default().show(ctx, |ui| {
egui::Grid::new("vru_words").show(ui, |ui| {
for (i, v) in app.vru_word_list.iter().enumerate() {
if i % 5 == 0 {
ui.end_row();
}
if ui.button((*v).to_string()).clicked() {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(v.clone())
.unwrap();
app.show_vru_dialog = false;
}
}
});
});
if ctx.input(|i| i.viewport().close_requested()) {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(String::from(""))
.unwrap();
app.show_vru_dialog = false;
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("What would you like to say?");
egui::Grid::new("vru_words").show(ui, |ui| {
for (i, v) in app.vru_word_list.iter().enumerate() {
if i % 5 == 0 {
ui.end_row();
}
if ui.button((*v).to_string()).clicked() {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(v.clone())
.unwrap();
app.show_vru_dialog = false;
}
}
},
);
});
ui.add_space(16.0);
if ui.button("Close without saying anything").clicked() {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(String::from(""))
.unwrap();
app.show_vru_dialog = false;
};
});
}
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 crt = app.crt;
let emulate_vru = app.emulate_vru;
let overclock = app.overclock;
let peer_addr;
let session;
let player_number;
@@ -286,12 +350,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())
@@ -301,10 +400,14 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
selected_controller,
selected_profile,
controller_enabled,
transfer_pak,
upscale,
integer_scaling,
fullscreen,
widescreen,
crt,
emulate_vru,
overclock,
};
if file.is_some() || netplay {
@@ -327,10 +430,19 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
session.unwrap(),
player_number.unwrap(),
));
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);
@@ -341,8 +453,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);
@@ -357,6 +473,11 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
impl eframe::App for GopherEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if self.show_vru_dialog {
show_vru_dialog(self, ctx);
return;
}
if self.netplay.create {
gui_netplay::netplay_create(self, ctx);
}
@@ -385,7 +506,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()
@@ -393,6 +514,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()
@@ -408,17 +542,33 @@ impl eframe::App for GopherEguiApp {
}
});
ui.add_space(32.0);
ui.add_space(16.0);
ui.label("Controller Config:");
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())
@@ -456,7 +606,7 @@ impl eframe::App for GopherEguiApp {
ui.end_row();
}
});
ui.add_space(32.0);
ui.add_space(16.0);
let upscale_values = [1, 2, 4];
let mut slider_value = match self.upscale {
1 => 0,
@@ -477,15 +627,27 @@ impl eframe::App for GopherEguiApp {
};
ui.checkbox(&mut self.integer_scaling, "Integer Scaling");
ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)");
ui.add_space(32.0);
ui.checkbox(&mut self.widescreen, "Widescreen (stretch)");
ui.checkbox(&mut self.crt, "Apply CRT shader");
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() {
@@ -496,9 +658,7 @@ impl eframe::App for GopherEguiApp {
}
}
if self.show_vru_dialog {
show_vru_dialog(self, ctx);
}
get_latest_version(self, ctx);
}
}
+32 -3
View File
@@ -68,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)]
@@ -207,7 +209,9 @@ 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;
@@ -308,6 +312,8 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
// 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()),
@@ -327,6 +333,8 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
md5: Some(app.netplay.game_info.md5.clone()),
protected: None,
port: None,
features: Some(features),
buffer_target: Some(2),
}),
};
let (mut socket, _response) =
@@ -500,6 +508,8 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
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();
@@ -621,7 +631,9 @@ 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;
@@ -647,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,
@@ -680,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,
}),
};
@@ -718,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();
@@ -749,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();
@@ -797,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 {
+29 -24
View File
@@ -277,11 +277,11 @@ fn set_buttons_from_controller(
}
pub fn set_rumble(ui: &mut ui::Ui, channel: usize, rumble: u8) {
if !ui.controllers[channel].rumble {
if !ui.input.controllers[channel].rumble {
return;
}
let controller = ui.controllers[channel].game_controller;
let joystick = ui.controllers[channel].joystick;
let controller = ui.input.controllers[channel].game_controller;
let joystick = ui.input.controllers[channel].joystick;
if !controller.is_null() {
unsafe {
sdl3_sys::gamepad::SDL_RumbleGamepad(
@@ -338,14 +338,16 @@ 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;
let controller = ui.controllers[channel].game_controller;
let joystick = ui.controllers[channel].joystick;
let controller = ui.input.controllers[channel].game_controller;
let joystick = ui.input.controllers[channel].joystick;
let alt_pressed = unsafe {
// ignore key presses if ALT is pressed
*ui.keyboard_state
*ui.input
.keyboard_state
.offset(i32::from(sdl3_sys::scancode::SDL_SCANCODE_LALT) as isize)
|| *ui
.input
.keyboard_state
.offset(i32::from(sdl3_sys::scancode::SDL_SCANCODE_RALT) as isize)
};
@@ -354,7 +356,9 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> InputData {
if profile_name != "default" || channel == 0 {
let profile_key = profile.keys[i];
if profile_key.enabled && !alt_pressed {
keys |= (unsafe { *ui.keyboard_state.offset(profile_key.id as isize) } as u32) << i;
keys |= (unsafe { *ui.input.keyboard_state.offset(profile_key.id as isize) }
as u32)
<< i;
}
}
@@ -369,7 +373,7 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> InputData {
let mut y: f64 = 0.0;
if profile_name != "default" || channel == 0 {
(x, y) = set_axis_from_keys(profile, ui.keyboard_state);
(x, y) = set_axis_from_keys(profile, ui.input.keyboard_state);
}
if !controller.is_null() {
@@ -384,15 +388,15 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> InputData {
InputData {
data: keys,
pak_change_pressed: change_paks(profile, joystick, controller, ui.keyboard_state),
pak_change_pressed: change_paks(profile, joystick, controller, ui.input.keyboard_state),
}
}
pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) {
if controller < ui.num_joysticks {
if controller < ui.input.num_joysticks {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(ui.joysticks.offset(controller as isize)),
*(ui.input.joysticks.offset(controller as isize)),
))
.to_string_lossy()
.to_string()
@@ -444,16 +448,16 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
let mut open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick> = Vec::new();
let mut open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad> = Vec::new();
for offset in 0..ui.num_joysticks as isize {
for offset in 0..ui.input.num_joysticks as isize {
if !dinput {
let controller =
unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*ui.joysticks.offset(offset)) };
unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*ui.input.joysticks.offset(offset)) };
if !controller.is_null() {
open_controllers.push(controller);
}
} else {
let joystick =
unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*ui.joysticks.offset(offset)) };
unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*ui.input.joysticks.offset(offset)) };
if !joystick.is_null() {
open_joysticks.push(joystick);
}
@@ -849,8 +853,9 @@ pub fn get_default_profile() -> ui::config::InputProfile {
}
pub fn init(ui: &mut ui::Ui) {
ui.keyboard_state = unsafe { sdl3_sys::keyboard::SDL_GetKeyboardState(std::ptr::null_mut()) };
if ui.keyboard_state.is_null() {
ui.input.keyboard_state =
unsafe { sdl3_sys::keyboard::SDL_GetKeyboardState(std::ptr::null_mut()) };
if ui.input.keyboard_state.is_null() {
panic!("Could not get keyboard state");
}
@@ -861,16 +866,16 @@ pub fn init(ui: &mut ui::Ui) {
let mut joystick_id = 0;
let assigned_path = controller_assignment.as_ref().unwrap();
for offset in 0..ui.num_joysticks as isize {
for offset in 0..ui.input.num_joysticks as isize {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(ui.joysticks.offset(offset)),
*(ui.input.joysticks.offset(offset)),
))
.to_string_lossy()
.to_string()
};
if path == *assigned_path && !taken[offset as usize] {
joystick_id = unsafe { *(ui.joysticks.offset(offset)) };
joystick_id = unsafe { *(ui.input.joysticks.offset(offset)) };
taken[offset as usize] = true;
break;
}
@@ -885,13 +890,13 @@ pub fn init(ui: &mut ui::Ui) {
if gamepad.is_null() {
println!("could not connect gamepad: {}", joystick_id)
} else {
ui.controllers[i].game_controller = gamepad;
ui.input.controllers[i].game_controller = gamepad;
let properties =
unsafe { sdl3_sys::gamepad::SDL_GetGamepadProperties(gamepad) };
if properties == 0 {
println!("could not get gamepad properties");
}
ui.controllers[i].rumble = unsafe {
ui.input.controllers[i].rumble = unsafe {
sdl3_sys::properties::SDL_GetBooleanProperty(
properties,
sdl3_sys::gamepad::SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN,
@@ -904,13 +909,13 @@ pub fn init(ui: &mut ui::Ui) {
if joystick.is_null() {
println!("could not connect joystick: {}", joystick_id)
} else {
ui.controllers[i].joystick = joystick;
ui.input.controllers[i].joystick = joystick;
let properties =
unsafe { sdl3_sys::joystick::SDL_GetJoystickProperties(joystick) };
if properties == 0 {
println!("could not get joystick properties");
}
ui.controllers[i].rumble = unsafe {
ui.input.controllers[i].rumble = unsafe {
sdl3_sys::properties::SDL_GetBooleanProperty(
properties,
sdl3_sys::joystick::SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN,
@@ -927,7 +932,7 @@ pub fn init(ui: &mut ui::Ui) {
}
pub fn close(ui: &mut ui::Ui) {
for controller in ui.controllers.iter_mut() {
for controller in ui.input.controllers.iter_mut() {
if !controller.joystick.is_null() {
unsafe { sdl3_sys::joystick::SDL_CloseJoystick(controller.joystick) }
controller.joystick = std::ptr::null_mut();
+139 -74
View File
@@ -1,4 +1,4 @@
use crate::{netplay, ui};
use crate::{device, netplay, ui};
use std::io::Read;
use std::io::Write;
@@ -45,6 +45,7 @@ pub struct Saves {
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> {
@@ -133,7 +134,7 @@ pub fn get_game_name(rom: &[u8]) -> String {
}
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
ui.save_type = get_save_type(rom, &ui.game_id);
ui.storage.save_type = get_save_type(rom, &ui.game_id);
let saves_path = ui.dirs.data_dir.join("saves");
@@ -147,67 +148,78 @@ pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
&game_name
};
ui.paths.eep_file_path.clone_from(&saves_path);
ui.paths
ui.storage.paths.eep_file_path.clone_from(&saves_path);
ui.storage
.paths
.eep_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".eep");
ui.paths.sra_file_path.clone_from(&saves_path);
ui.paths
ui.storage.paths.sra_file_path.clone_from(&saves_path);
ui.storage
.paths
.sra_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".sra");
ui.paths.fla_file_path.clone_from(&saves_path);
ui.paths
ui.storage.paths.fla_file_path.clone_from(&saves_path);
ui.storage
.paths
.fla_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".fla");
ui.paths.pak_file_path.clone_from(&saves_path);
ui.paths
ui.storage.paths.pak_file_path.clone_from(&saves_path);
ui.storage
.paths
.pak_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk");
ui.paths.sdcard_file_path.clone_from(&saves_path);
ui.paths
ui.storage.paths.sdcard_file_path.clone_from(&saves_path);
ui.storage
.paths
.sdcard_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".img");
ui.paths.romsave_file_path.clone_from(&saves_path);
ui.paths
ui.storage.paths.romsave_file_path.clone_from(&saves_path);
ui.storage
.paths
.romsave_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".romsave");
ui.paths.savestate_file_path.clone_from(&states_path);
ui.paths
ui.storage
.paths
.savestate_file_path
.clone_from(&states_path);
ui.storage
.paths
.savestate_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".state");
}
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);
let eep = std::fs::read(&mut ui.storage.paths.eep_file_path);
if eep.is_ok() {
ui.saves.eeprom.data = eep.unwrap();
ui.storage.saves.eeprom.data = eep.unwrap();
}
let sra = std::fs::read(&mut ui.paths.sra_file_path);
let sra = std::fs::read(&mut ui.storage.paths.sra_file_path);
if sra.is_ok() {
ui.saves.sram.data = sra.unwrap();
ui.storage.saves.sram.data = sra.unwrap();
}
let fla = std::fs::read(&mut ui.paths.fla_file_path);
let fla = std::fs::read(&mut ui.storage.paths.fla_file_path);
if fla.is_ok() {
ui.saves.flash.data = fla.unwrap();
ui.storage.saves.flash.data = fla.unwrap();
}
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
let mempak = std::fs::read(&mut ui.storage.paths.pak_file_path);
if mempak.is_ok() {
ui.saves.mempak.data = mempak.unwrap();
ui.storage.saves.mempak.data = mempak.unwrap();
}
let sdcard = std::fs::read(&mut ui.paths.sdcard_file_path);
let sdcard = std::fs::read(&mut ui.storage.paths.sdcard_file_path);
if sdcard.is_ok() {
ui.saves.sdcard.data = sdcard.unwrap();
ui.storage.saves.sdcard.data = sdcard.unwrap();
}
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
let romsave = std::fs::read(&mut ui.storage.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave.data = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
ui.storage.saves.romsave.data =
postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
}
}
@@ -216,31 +228,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.data,
ui.saves.eeprom.data.len(),
&ui.storage.saves.eeprom.data,
ui.storage.saves.eeprom.data.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"sra",
&ui.saves.sram.data,
ui.saves.sram.data.len(),
&ui.storage.saves.sram.data,
ui.storage.saves.sram.data.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"fla",
&ui.saves.flash.data,
ui.saves.flash.data.len(),
&ui.storage.saves.flash.data,
ui.storage.saves.flash.data.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"mpk",
&ui.saves.mempak.data,
ui.saves.mempak.data.len(),
&ui.storage.saves.mempak.data,
ui.storage.saves.mempak.data.len(),
);
let mut compressed_sd = Vec::new();
if !ui.saves.sdcard.data.is_empty() {
compressed_sd = compress_file(&[(&ui.saves.sdcard.data, "save")]);
if !ui.storage.saves.sdcard.data.is_empty() {
compressed_sd = compress_file(&[(&ui.storage.saves.sdcard.data, "save")]);
}
netplay::send_save(
netplay.as_mut().unwrap(),
@@ -250,9 +262,9 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
);
let mut compressed_romsave = Vec::new();
if !ui.saves.romsave.data.is_empty() {
if !ui.storage.saves.romsave.data.is_empty() {
compressed_romsave = compress_file(&[(
&postcard::to_stdvec(&ui.saves.romsave.data).unwrap(),
&postcard::to_stdvec(&ui.storage.saves.romsave.data).unwrap(),
"save",
)]);
}
@@ -263,22 +275,38 @@ 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.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);
netplay::receive_save(
netplay.as_mut().unwrap(),
"eep",
&mut ui.storage.saves.eeprom.data,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"sra",
&mut ui.storage.saves.sram.data,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"fla",
&mut ui.storage.saves.flash.data,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"mpk",
&mut ui.storage.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.data = decompress_file(&compressed_sd, "save");
ui.storage.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.data = postcard::from_bytes(&romsave_bytes).unwrap();
ui.storage.saves.romsave.data = postcard::from_bytes(&romsave_bytes).unwrap();
}
}
}
@@ -313,29 +341,66 @@ pub fn compress_file(data: &[(&[u8], &str)]) -> Vec<u8> {
}
fn write_rom_save(ui: &ui::Ui) {
let data = postcard::to_stdvec(&ui.saves.romsave.data).unwrap();
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
let data = postcard::to_stdvec(&ui.storage.saves.romsave.data).unwrap();
std::fs::write(ui.storage.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.written {
write_save(ui, SaveTypes::Eeprom16k)
fn writeback_sdcard(device: &mut device::Device) {
let length;
let save_data: &[u8];
if device.ui.storage.saves.eeprom.written {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom4k)
{
length = 1;
} else {
length = 4;
}
if ui.saves.sram.written {
write_save(ui, SaveTypes::Sram)
save_data = device.ui.storage.saves.eeprom.data.as_ref();
} else if device.ui.storage.saves.sram.written {
length = device.ui.storage.saves.sram.data.len() / 512;
save_data = device.ui.storage.saves.sram.data.as_ref();
} else if device.ui.storage.saves.flash.written {
length = device.ui.storage.saves.flash.data.len() / 512;
save_data = device.ui.storage.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.storage.saves.sdcard.data[offset..offset + 512]
.copy_from_slice(&save_data[i * 512..(i + 1) * 512]);
}
device.ui.storage.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.storage.saves.write_to_disk {
if device.ui.storage.saves.eeprom.written {
write_save(&device.ui, SaveTypes::Eeprom16k)
}
if device.ui.storage.saves.sram.written {
write_save(&device.ui, SaveTypes::Sram)
}
if device.ui.storage.saves.flash.written {
write_save(&device.ui, SaveTypes::Flash)
}
if device.ui.storage.saves.romsave.written {
write_save(&device.ui, SaveTypes::Romsave)
}
} else {
writeback_sdcard(device)
}
if ui.saves.flash.written {
write_save(ui, SaveTypes::Flash)
if device.ui.storage.saves.mempak.written {
write_save(&device.ui, SaveTypes::Mempak)
}
if ui.saves.mempak.written {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.sdcard.written {
write_save(ui, SaveTypes::Sdcard)
}
if ui.saves.romsave.written {
write_save(ui, SaveTypes::Romsave)
if device.ui.storage.saves.sdcard.written {
write_save(&device.ui, SaveTypes::Sdcard)
}
}
}
@@ -345,24 +410,24 @@ fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
let data: &Vec<u8>;
match save_type {
SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => {
path = ui.paths.eep_file_path.as_ref();
data = ui.saves.eeprom.data.as_ref();
path = ui.storage.paths.eep_file_path.as_ref();
data = ui.storage.saves.eeprom.data.as_ref();
}
SaveTypes::Sram => {
path = ui.paths.sra_file_path.as_ref();
data = ui.saves.sram.data.as_ref();
path = ui.storage.paths.sra_file_path.as_ref();
data = ui.storage.saves.sram.data.as_ref();
}
SaveTypes::Flash => {
path = ui.paths.fla_file_path.as_ref();
data = ui.saves.flash.data.as_ref();
path = ui.storage.paths.fla_file_path.as_ref();
data = ui.storage.saves.flash.data.as_ref();
}
SaveTypes::Mempak => {
path = ui.paths.pak_file_path.as_ref();
data = ui.saves.mempak.data.as_ref();
path = ui.storage.paths.pak_file_path.as_ref();
data = ui.storage.saves.mempak.data.as_ref();
}
SaveTypes::Sdcard => {
path = ui.paths.sdcard_file_path.as_ref();
data = ui.saves.sdcard.data.as_ref();
path = ui.storage.paths.sdcard_file_path.as_ref();
data = ui.storage.saves.sdcard.data.as_ref();
}
SaveTypes::Romsave => {
write_rom_save(ui);
+34 -22
View File
@@ -7,28 +7,38 @@ pub fn init(device: &mut device::Device) {
let title = std::ffi::CString::new("gopher64").unwrap();
let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN;
let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN
| sdl3_sys::video::SDL_WINDOW_RESIZABLE
| sdl3_sys::video::SDL_WINDOW_INPUT_FOCUS;
if device.ui.fullscreen {
if device.ui.video.fullscreen {
flags |= sdl3_sys::video::SDL_WINDOW_FULLSCREEN;
} else {
flags |= sdl3_sys::video::SDL_WINDOW_RESIZABLE;
}
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 {
device.ui.video.window = unsafe {
sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), window_width, window_height, flags)
};
if device.ui.window.is_null() {
if device.ui.video.window.is_null() {
panic!("Could not create window");
}
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.window) } {
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.video.window) } {
panic!("Could not show window");
}
unsafe { sdl3_sys::everything::SDL_HideCursor() };
@@ -42,23 +52,20 @@ 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,
fullscreen: device.ui.video.fullscreen,
integer_scaling: device.ui.config.video.integer_scaling,
upscale: device.ui.config.video.upscale,
crt: device.ui.config.video.crt,
};
unsafe {
rdp_init(
device.ui.window as *mut std::ffi::c_void,
gfx_info,
device.ui.config.video.upscale,
device.ui.config.video.integer_scaling,
device.ui.fullscreen,
)
}
unsafe { rdp_init(device.ui.video.window as *mut std::ffi::c_void, gfx_info) }
}
pub fn close(ui: &ui::Ui) {
unsafe {
rdp_close();
sdl3_sys::video::SDL_DestroyWindow(ui.window);
sdl3_sys::video::SDL_DestroyWindow(ui.video.window);
}
}
@@ -85,9 +92,14 @@ 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,
fullscreen: device.ui.video.fullscreen,
integer_scaling: device.ui.config.video.integer_scaling,
upscale: device.ui.config.video.upscale,
crt: device.ui.config.video.crt,
};
unsafe {
rdp_new_processor(gfx_info, device.ui.config.video.upscale);
rdp_new_processor(gfx_info);
}
}