mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 01:25:20 +02:00
finish up RA support (#745)
* finish up RA support * more * capture some corner cases * fix post data * more * more * fix range * more * more * more * more * show in-progress achievements * more * more * more * more
This commit is contained in:
@@ -150,7 +150,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
arch: mac-aarch64
|
||||
arch: macos-aarch64
|
||||
llvm: ARM64
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
Windows: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe
|
||||
|
||||
MacOS: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-macos-aarch64.zip
|
||||
|
||||
Linux: https://flathub.org/apps/io.github.gopher64.gopher64
|
||||
|
||||
## wiki
|
||||
@@ -11,7 +13,7 @@ https://github.com/gopher64/gopher64/wiki
|
||||
|
||||
## discord
|
||||
|
||||
https://discord.gg/9RGXq8W8JQ
|
||||
[Discord invite link](https://discord.gg/9RGXq8W8JQ)
|
||||
|
||||
## controls
|
||||
|
||||
@@ -19,7 +21,7 @@ Keys are mapped according to [these defaults](https://github.com/gopher64/gopher
|
||||
|
||||
## netplay
|
||||
|
||||
Gopher64 supports netplay (online play with others) via cloud hosted servers. You can also run the server (https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
|
||||
Gopher64 supports netplay (online play with others) via cloud hosted servers. You can also run the [server](https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
|
||||
|
||||
## portable mode
|
||||
|
||||
@@ -35,8 +37,8 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
|
||||
|
||||
## building and usage
|
||||
|
||||
1. Linux only: install the SDL3 dependencies: https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
|
||||
2. Install rust: https://www.rust-lang.org/tools/install
|
||||
1. Linux only: [install the SDL3 dependencies](https://wiki.libsdl.org/SDL3/README-linux#build-dependencies)
|
||||
2. [Install rust](https://www.rust-lang.org/tools/install)
|
||||
3. `git clone --recursive https://github.com/gopher64/gopher64.git`
|
||||
4. `cd gopher64`
|
||||
5. `cargo build --release`
|
||||
@@ -48,10 +50,12 @@ I am very open to contributions! Please contact me via a GitHub issue or Discord
|
||||
|
||||
## 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.
|
||||
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).
|
||||
|
||||
## privacy and code signing policy
|
||||
|
||||
Free code signing for the Windows release is provided by [SignPath.io](https://about.signpath.io), certificate by [SignPath Foundation](https://signpath.org).
|
||||
|
||||
During online netplay sessions, the server logs your IP address and basic session information (game title and session name) for operational purposes. No additional personal data is collected or stored.
|
||||
|
||||
If you enable the RetroAchievements feature, some data is sent to their systems. Please see their terms [here](https://retroachievements.org/terms).
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "interface.hpp"
|
||||
#include "../retroachievements/retroachievements.h"
|
||||
#include "rdp_device.hpp"
|
||||
#include "spirv.hpp"
|
||||
#include "spirv_crt.hpp"
|
||||
@@ -155,8 +156,17 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) {
|
||||
case SDL_SCANCODE_F7:
|
||||
callback.load_state = true;
|
||||
break;
|
||||
case SDL_SCANCODE_F8:
|
||||
if (messages.empty())
|
||||
SDL_RunOnMainThread(ra_display_inprogress_achievements, nullptr, true);
|
||||
break;
|
||||
case SDL_SCANCODE_F9:
|
||||
display_challenge_indicator = !display_challenge_indicator;
|
||||
rdp_onscreen_message(
|
||||
std::format("Challenge indicators: {}",
|
||||
display_challenge_indicator ? "ON" : "OFF")
|
||||
.c_str(),
|
||||
false);
|
||||
break;
|
||||
case SDL_SCANCODE_F12:
|
||||
callback.reset_game = true;
|
||||
@@ -566,10 +576,16 @@ void rdp_load_state(const uint8_t *state) {
|
||||
memcpy(&rdp_device, state, sizeof(RDP_DEVICE));
|
||||
}
|
||||
|
||||
void rdp_onscreen_message(const char *message) {
|
||||
static void push_onscreen_message(void *message) {
|
||||
if (messages.empty())
|
||||
message_timer = SDL_GetTicks() + MESSAGE_TIME;
|
||||
messages.push({std::string(message), Vulkan::ImageHandle()});
|
||||
messages.push({std::string((const char *)message), Vulkan::ImageHandle()});
|
||||
}
|
||||
|
||||
void rdp_onscreen_message(const char *message, bool long_message) {
|
||||
SDL_RunOnMainThread(push_onscreen_message, (void *)message, true);
|
||||
if (long_message)
|
||||
SDL_RunOnMainThread(push_onscreen_message, (void *)message, true);
|
||||
}
|
||||
|
||||
uint32_t pixel_size(uint32_t pixel_type, uint32_t area) {
|
||||
|
||||
@@ -44,7 +44,7 @@ void rdp_update_screen();
|
||||
void rdp_render_frame();
|
||||
CALL_BACK rdp_check_callback();
|
||||
uint64_t rdp_process_commands();
|
||||
void rdp_onscreen_message(const char *_message);
|
||||
void rdp_onscreen_message(const char *message, bool long_message);
|
||||
void rdp_new_processor(GFX_INFO _gfx_info);
|
||||
void rdp_check_framebuffers(uint32_t address, uint32_t length);
|
||||
size_t rdp_state_size();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string.h>
|
||||
|
||||
void rust_server_call(const char *url, const char *post_data,
|
||||
const char *content_type,
|
||||
rc_client_server_callback_t callback,
|
||||
void *callback_data);
|
||||
void store_retroachievements_credentials(const char *username,
|
||||
@@ -28,7 +29,7 @@ static rc_client_leaderboard_list_t *g_leaderboard_list = NULL;
|
||||
|
||||
static uint32_t read_memory(uint32_t address, uint8_t *buffer,
|
||||
uint32_t num_bytes, rc_client_t *client) {
|
||||
if (address + num_bytes >= g_dmem_size)
|
||||
if (address + num_bytes > g_dmem_size)
|
||||
memset(buffer, 0, num_bytes);
|
||||
else
|
||||
memcpy(buffer, &g_dmem[address], num_bytes);
|
||||
@@ -38,7 +39,8 @@ static uint32_t read_memory(uint32_t address, uint8_t *buffer,
|
||||
static void server_call(const rc_api_request_t *request,
|
||||
rc_client_server_callback_t callback,
|
||||
void *callback_data, rc_client_t *client) {
|
||||
rust_server_call(request->url, request->post_data, callback, callback_data);
|
||||
rust_server_call(request->url, request->post_data, request->content_type,
|
||||
callback, callback_data);
|
||||
}
|
||||
|
||||
void ra_http_callback(const char *content, size_t content_size, int status_code,
|
||||
@@ -75,6 +77,8 @@ static void login_callback(int result, const char *error_message,
|
||||
"RA login failed: %s", error_message);
|
||||
store_retroachievements_credentials(NULL, NULL, userdata);
|
||||
return;
|
||||
} else {
|
||||
memset(load_game_error_message, 0, sizeof(load_game_error_message));
|
||||
}
|
||||
|
||||
// Login was successful. Capture the token for future logins so we don't have
|
||||
@@ -94,6 +98,8 @@ const char *ra_get_token() { return g_token; }
|
||||
|
||||
void ra_logout_user() {
|
||||
g_user_logged_in = false;
|
||||
g_username = NULL;
|
||||
g_token = NULL;
|
||||
rc_client_logout(g_client);
|
||||
}
|
||||
|
||||
@@ -124,6 +130,8 @@ static void load_game_callback(int result, const char *error_message,
|
||||
"RA load failed: %s", error_message);
|
||||
notify_load_game(userdata);
|
||||
return;
|
||||
} else {
|
||||
memset(load_game_error_message, 0, sizeof(load_game_error_message));
|
||||
}
|
||||
|
||||
if (!rc_client_is_processing_required(client)) {
|
||||
@@ -139,8 +147,7 @@ static void load_game_callback(int result, const char *error_message,
|
||||
|
||||
void ra_welcome() {
|
||||
if (strlen(load_game_error_message) > 0) {
|
||||
rdp_onscreen_message(load_game_error_message);
|
||||
rdp_onscreen_message(load_game_error_message); // show it a bit longer
|
||||
rdp_onscreen_message(load_game_error_message, true);
|
||||
}
|
||||
if (!g_game_loaded)
|
||||
return;
|
||||
@@ -163,8 +170,7 @@ void ra_welcome() {
|
||||
snprintf(buffer + message_length, sizeof(buffer) - message_length,
|
||||
"Game has no achievements");
|
||||
}
|
||||
rdp_onscreen_message(buffer);
|
||||
rdp_onscreen_message(buffer); // show it a bit longer
|
||||
rdp_onscreen_message(buffer, true);
|
||||
}
|
||||
|
||||
void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata) {
|
||||
@@ -188,14 +194,14 @@ static void leaderboard_submitted(const rc_client_leaderboard_t *leaderboard) {
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "Leaderboard submitted: %s - %s",
|
||||
leaderboard->title, leaderboard->tracker_value);
|
||||
rdp_onscreen_message(buffer);
|
||||
rdp_onscreen_message(buffer, false);
|
||||
}
|
||||
|
||||
static void achievement_triggered(const rc_client_achievement_t *achievement) {
|
||||
char buffer[512];
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "Unlocked: %s", achievement->title);
|
||||
rdp_onscreen_message(buffer);
|
||||
rdp_onscreen_message(buffer, false);
|
||||
}
|
||||
|
||||
static void game_completed(rc_client_t *client) {
|
||||
@@ -205,7 +211,7 @@ static void game_completed(rc_client_t *client) {
|
||||
snprintf(buffer, sizeof(buffer), "%s: %s",
|
||||
rc_client_get_hardcore_enabled(client) ? "Mastered" : "Completed",
|
||||
game->title);
|
||||
rdp_onscreen_message(buffer);
|
||||
rdp_onscreen_message(buffer, false);
|
||||
}
|
||||
|
||||
static void subset_completed(const rc_client_subset_t *subset,
|
||||
@@ -215,7 +221,7 @@ static void subset_completed(const rc_client_subset_t *subset,
|
||||
snprintf(buffer, sizeof(buffer), "Subset %s: %s",
|
||||
rc_client_get_hardcore_enabled(client) ? "mastered" : "completed",
|
||||
subset->title);
|
||||
rdp_onscreen_message(buffer);
|
||||
rdp_onscreen_message(buffer, false);
|
||||
}
|
||||
|
||||
static void server_error(const rc_client_server_error_t *server_error) {
|
||||
@@ -223,10 +229,13 @@ static void server_error(const rc_client_server_error_t *server_error) {
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "RA server error: %s",
|
||||
server_error->error_message);
|
||||
rdp_onscreen_message(buffer);
|
||||
rdp_onscreen_message(buffer, false);
|
||||
}
|
||||
|
||||
static const char *get_leaderboard_title(const char *display) {
|
||||
if (g_leaderboard_list == NULL)
|
||||
return NULL;
|
||||
|
||||
for (uint32_t i = 0; i < g_leaderboard_list->num_buckets; i++) {
|
||||
for (uint32_t j = 0; j < g_leaderboard_list->buckets[i].num_leaderboards;
|
||||
j++) {
|
||||
@@ -314,7 +323,6 @@ void ra_init_client(bool hardcore, bool challenge, bool leaderboard) {
|
||||
g_user_logged_in = false;
|
||||
g_dmem = NULL;
|
||||
g_dmem_size = 0;
|
||||
memset(load_game_error_message, 0, sizeof(load_game_error_message));
|
||||
|
||||
// Provide a logging function to simplify debugging
|
||||
rc_client_enable_logging(g_client, RC_CLIENT_LOG_LEVEL_WARN, log_message);
|
||||
@@ -383,3 +391,35 @@ void ra_load_state(const uint8_t *state, size_t state_size) {
|
||||
printf("RetroAchievements: Failed to deserialize progress\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ra_display_inprogress_achievements(void *userdata) {
|
||||
if (!g_game_loaded)
|
||||
return;
|
||||
|
||||
rc_client_achievement_list_t *list = rc_client_create_achievement_list(
|
||||
g_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE,
|
||||
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE);
|
||||
|
||||
char buffer[1024] = {0};
|
||||
size_t buffer_length = 0;
|
||||
|
||||
for (uint32_t i = 0; i < list->num_buckets; i++) {
|
||||
if (list->buckets[i].bucket_type == RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED) {
|
||||
for (uint32_t j = 0; j < list->buckets[i].num_achievements; j++) {
|
||||
const rc_client_achievement_t *achievement =
|
||||
list->buckets[i].achievements[j];
|
||||
if (achievement->measured_percent > 0.0f) {
|
||||
if (buffer_length < sizeof(buffer) - 1) {
|
||||
buffer_length += snprintf(
|
||||
buffer + buffer_length, sizeof(buffer) - buffer_length,
|
||||
"%s: %s\n", achievement->title, achievement->measured_progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buffer_length > 0) {
|
||||
rdp_onscreen_message(buffer, false);
|
||||
}
|
||||
rc_client_destroy_achievement_list(list);
|
||||
}
|
||||
|
||||
@@ -22,3 +22,11 @@ const char *ra_get_token();
|
||||
size_t ra_state_size();
|
||||
void ra_save_state(uint8_t *state, size_t state_size);
|
||||
void ra_load_state(const uint8_t *state, size_t state_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void ra_display_inprogress_achievements(void *userdata);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ pub fn init(
|
||||
|
||||
if !device.cheats.cheats.is_empty() {
|
||||
device.cheats.enabled = true;
|
||||
ui::video::onscreen_message(&device.ui, "Cheats enabled");
|
||||
ui::video::onscreen_message(&device.ui, "Cheats enabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,21 +190,21 @@ pub fn pak_switch_event(device: &mut device::Device) {
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "MemPak selected");
|
||||
ui::video::onscreen_message(&device.ui, "MemPak selected", false);
|
||||
} else if new_pak_type == PakType::RumblePak {
|
||||
channel.pak_handler = Some(device::controller::PakHandler {
|
||||
read: device::controller::rumble::read,
|
||||
write: device::controller::rumble::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "RumblePak selected");
|
||||
ui::video::onscreen_message(&device.ui, "RumblePak selected", false);
|
||||
} else if new_pak_type == PakType::TransferPak {
|
||||
channel.pak_handler = Some(device::controller::PakHandler {
|
||||
read: device::controller::transferpak::read,
|
||||
write: device::controller::transferpak::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "TransferPak selected");
|
||||
ui::video::onscreen_message(&device.ui, "TransferPak selected", false);
|
||||
}
|
||||
channel.change_pak = PakType::None;
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@ pub fn init(device: &mut device::Device) {
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
"Overclocking enabled, setting clock rate to 125 MHz",
|
||||
false,
|
||||
);
|
||||
125000000
|
||||
};
|
||||
|
||||
@@ -166,7 +166,7 @@ pub fn reset_event(device: &mut device::Device) {
|
||||
|
||||
device::pif::reset_pif(device, true);
|
||||
|
||||
ui::video::onscreen_message(&device.ui, "Game reset");
|
||||
ui::video::onscreen_message(&device.ui, "Game reset", false);
|
||||
}
|
||||
|
||||
fn exception_general(device: &mut device::Device, vector_offset: u32) {
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
|
||||
if device.netplay.is_none() && paused {
|
||||
if retroachievements::get_hardcore() {
|
||||
ui::video::onscreen_message(&device.ui, "Cannot pause in RA hardcore mode");
|
||||
ui::video::onscreen_message(&device.ui, "Cannot pause in RA hardcore mode", false);
|
||||
} else {
|
||||
ui::video::pause_loop(device.vi.frame_time);
|
||||
}
|
||||
|
||||
@@ -207,6 +207,8 @@ async fn main() -> std::io::Result<()> {
|
||||
retroachievements::login_user(username, password, tx);
|
||||
} else if let Some(token) = args.ra_token {
|
||||
retroachievements::login_token_user(username, token, tx);
|
||||
} else {
|
||||
tx.send(false).unwrap();
|
||||
}
|
||||
|
||||
rx.await.unwrap();
|
||||
|
||||
+7
-4
@@ -146,7 +146,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::Inpu
|
||||
.remove(&netplay.player_data[channel].count);
|
||||
|
||||
if std::time::Instant::now() > timeout {
|
||||
ui::video::onscreen_message(&device.ui, "Lost connection to netplay server");
|
||||
ui::video::onscreen_message(&device.ui, "Lost connection to netplay server", false);
|
||||
input = Some(InputEvent {
|
||||
input: 0,
|
||||
plugin: 0,
|
||||
@@ -191,13 +191,16 @@ fn process_incoming(netplay: &mut Netplay, ui: &ui::Ui) {
|
||||
}
|
||||
if current_status != netplay.status {
|
||||
if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 {
|
||||
ui::video::onscreen_message(ui, "Netplay desync detected");
|
||||
ui::video::onscreen_message(ui, "Netplay desync detected"); // queue this message twice to ensure they see it
|
||||
ui::video::onscreen_message(ui, "Netplay desync detected", true);
|
||||
}
|
||||
for dis in 1..5 {
|
||||
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
|
||||
{
|
||||
ui::video::onscreen_message(ui, &format!("Player {dis} disconnected"));
|
||||
ui::video::onscreen_message(
|
||||
ui,
|
||||
&format!("Player {dis} disconnected"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
netplay.status = current_status;
|
||||
|
||||
+22
-20
@@ -3,6 +3,17 @@ include!(concat!(env!("OUT_DIR"), "/retroachievements_bindings.rs"));
|
||||
use crate::ui;
|
||||
use slint::ComponentHandle;
|
||||
|
||||
static WEB_CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
|
||||
reqwest::Client::builder()
|
||||
.user_agent(format!(
|
||||
"{}/{}",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("GIT_DESCRIBE")
|
||||
))
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct RAConfig {
|
||||
pub username: String,
|
||||
@@ -64,33 +75,24 @@ pub extern "C" fn store_retroachievements_credentials(
|
||||
pub extern "C" fn rust_server_call(
|
||||
c_url: *const std::ffi::c_char,
|
||||
c_post_data: *const std::ffi::c_char,
|
||||
c_content_type: *const std::ffi::c_char,
|
||||
c_callback: *mut std::ffi::c_void,
|
||||
c_callback_data: *mut std::ffi::c_void,
|
||||
) {
|
||||
let url = unsafe { std::ffi::CStr::from_ptr(c_url).to_str().unwrap() };
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent(format!(
|
||||
"{}/{}",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("GIT_DESCRIBE")
|
||||
))
|
||||
.build()
|
||||
.unwrap();
|
||||
let url = unsafe { std::ffi::CStr::from_ptr(c_url).to_str().unwrap() }.to_string();
|
||||
|
||||
let task = if !c_post_data.is_null() {
|
||||
let post_data = unsafe { std::ffi::CStr::from_ptr(c_post_data).to_str().unwrap() };
|
||||
let query_string =
|
||||
reqwest::Url::parse(&format!("http://nothing.com?{}", post_data)).unwrap();
|
||||
client
|
||||
let post_data =
|
||||
unsafe { std::ffi::CStr::from_ptr(c_post_data).to_str().unwrap() }.to_string();
|
||||
let content_type =
|
||||
unsafe { std::ffi::CStr::from_ptr(c_content_type).to_str().unwrap() }.to_string();
|
||||
WEB_CLIENT
|
||||
.post(url)
|
||||
.query(
|
||||
&query_string
|
||||
.query_pairs()
|
||||
.into_owned()
|
||||
.collect::<Vec<(String, String)>>(),
|
||||
)
|
||||
.body(post_data)
|
||||
.header(reqwest::header::CONTENT_TYPE, content_type)
|
||||
.send()
|
||||
} else {
|
||||
client.get(url).send()
|
||||
WEB_CLIENT.get(url).send()
|
||||
};
|
||||
let callback = c_callback.addr();
|
||||
let callback_data = c_callback_data.addr();
|
||||
|
||||
+9
-1
@@ -96,12 +96,17 @@ pub fn create_savestate(device: &device::Device) {
|
||||
"Savestate created in slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn load_savestate(device: &mut device::Device) {
|
||||
if retroachievements::get_hardcore() {
|
||||
ui::video::onscreen_message(&device.ui, "Cannot load savestate in RA hardcore mode");
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
"Cannot load savestate in RA hardcore mode",
|
||||
false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path);
|
||||
@@ -217,6 +222,7 @@ pub fn load_savestate(device: &mut device::Device) {
|
||||
"Savestate loaded from slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
ui::video::onscreen_message(
|
||||
@@ -225,6 +231,7 @@ pub fn load_savestate(device: &mut device::Device) {
|
||||
"Failed to load savestate from slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -234,6 +241,7 @@ pub fn load_savestate(device: &mut device::Device) {
|
||||
"Could not find savestate in slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -53,7 +53,11 @@ pub fn lower_audio_volume(ui: &mut ui::Ui) {
|
||||
}
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
|
||||
}
|
||||
ui::video::onscreen_message(ui, &format!("Audio volume: {:.0}%", ui.audio.gain * 100.0));
|
||||
ui::video::onscreen_message(
|
||||
ui,
|
||||
&format!("Audio volume: {:.0}%", ui.audio.gain * 100.0),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn raise_audio_volume(ui: &mut ui::Ui) {
|
||||
@@ -64,7 +68,11 @@ pub fn raise_audio_volume(ui: &mut ui::Ui) {
|
||||
}
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
|
||||
}
|
||||
ui::video::onscreen_message(ui, &format!("Audio volume: {:.0}%", ui.audio.gain * 100.0));
|
||||
ui::video::onscreen_message(
|
||||
ui,
|
||||
&format!("Audio volume: {:.0}%", ui.audio.gain * 100.0),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
fn adjust_audio_frequency(device: &device::Device, frequency: f32) {
|
||||
|
||||
+9
-3
@@ -183,7 +183,8 @@ pub fn check_callback(device: &mut device::Device) -> (bool, bool) {
|
||||
if device.ui.storage.save_state_slot != callback.save_state_slot {
|
||||
onscreen_message(
|
||||
&device.ui,
|
||||
&format!("Switching savestate slot to {}", callback.save_state_slot,),
|
||||
&format!("Switching savestate slot to {}", callback.save_state_slot),
|
||||
false,
|
||||
);
|
||||
device.ui.storage.save_state_slot = callback.save_state_slot;
|
||||
device
|
||||
@@ -215,8 +216,13 @@ pub fn check_framebuffers(address: u32, length: u32) {
|
||||
unsafe { rdp_check_framebuffers(address, length) }
|
||||
}
|
||||
|
||||
pub fn onscreen_message(_ui: &ui::Ui, message: &str) {
|
||||
unsafe { rdp_onscreen_message(std::ffi::CString::new(message).unwrap().as_ptr()) };
|
||||
pub fn onscreen_message(_ui: &ui::Ui, message: &str, long_message: bool) {
|
||||
unsafe {
|
||||
rdp_onscreen_message(
|
||||
std::ffi::CString::new(message).unwrap().as_ptr(),
|
||||
long_message,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
|
||||
Reference in New Issue
Block a user