parent
a488f0d91a
commit
edb6960d4f
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -64,6 +64,7 @@ jobs:
|
||||
matrix.llvm }}.tar.xz
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --no-default-features -v --target=${{ matrix.target }}
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
@ -113,6 +114,7 @@ jobs:
|
||||
}}.exe
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --no-default-features -v --target=${{ matrix.target }}
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
|
||||
@ -182,6 +184,7 @@ jobs:
|
||||
run: |
|
||||
cargo install cargo-bundle
|
||||
brew install molten-vk
|
||||
cargo build --no-default-features -v --target=${{ matrix.target }}
|
||||
cargo bundle --release --target=${{ matrix.target }} --format osx
|
||||
install_name_tool -change /opt/homebrew/opt/freetype/lib/libfreetype.6.dylib \
|
||||
@executable_path/../Frameworks/libfreetype.6.dylib target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/MacOS/gopher64
|
||||
|
||||
1
.github/workflows/lint.yml
vendored
1
.github/workflows/lint.yml
vendored
@ -49,3 +49,4 @@ jobs:
|
||||
- name: Lint
|
||||
run: |
|
||||
cargo clippy -- -Dwarnings
|
||||
cargo clippy --no-default-features -- -Dwarnings
|
||||
|
||||
@ -4,7 +4,9 @@ version = "1.1.17"
|
||||
edition = "2024"
|
||||
rust-version = "1.95.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
default = ["gui"]
|
||||
gui = ["dep:slint", "dep:slint-build"]
|
||||
|
||||
[dependencies]
|
||||
dirs = "6.0"
|
||||
@ -14,7 +16,7 @@ chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-big-array = "0.5"
|
||||
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia"] }
|
||||
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia"], optional = true }
|
||||
open = "5.3"
|
||||
sha2 = "0.11"
|
||||
sdl3-sys = { version = "0.6", features = ["build-from-source-static"] }
|
||||
@ -38,7 +40,7 @@ reqwest = { version = "0.13", features = ["json", "query"] }
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
bindgen = "0.72"
|
||||
slint-build = "1.12"
|
||||
slint-build = { version = "1.12", optional = true }
|
||||
cc = { version = "1.2", features = ["parallel"] }
|
||||
|
||||
[profile.release]
|
||||
|
||||
28
build.rs
28
build.rs
@ -3,8 +3,11 @@ fn main() {
|
||||
println!("cargo::rerun-if-changed=retroachievements");
|
||||
println!("cargo::rerun-if-changed=src/compat");
|
||||
|
||||
let slint_config = slint_build::CompilerConfiguration::new();
|
||||
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
|
||||
#[cfg(feature = "gui")]
|
||||
{
|
||||
let slint_config = slint_build::CompilerConfiguration::new();
|
||||
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
|
||||
}
|
||||
|
||||
let mut simd_build = cc::Build::new();
|
||||
let mut volk_build = cc::Build::new();
|
||||
@ -143,6 +146,9 @@ fn main() {
|
||||
println!("cargo:rustc-link-search=native={}", runtime_dir);
|
||||
println!("cargo:rustc-link-lib=static=clang_rt.osx");
|
||||
}
|
||||
if cfg!(not(feature = "gui")) && os == "linux" {
|
||||
println!("cargo:rustc-link-lib=dylib=freetype");
|
||||
}
|
||||
|
||||
volk_build.flag("-flto=thin");
|
||||
rdp_build.flag("-flto=thin");
|
||||
@ -155,7 +161,7 @@ fn main() {
|
||||
|
||||
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
||||
let retroachievements_bindings = bindgen::Builder::default()
|
||||
let mut retroachievements_builder = bindgen::Builder::default()
|
||||
.header("retroachievements/retroachievements.h")
|
||||
.allowlist_function("ra_init_client")
|
||||
.allowlist_function("ra_welcome")
|
||||
@ -166,15 +172,21 @@ fn main() {
|
||||
.allowlist_function("ra_do_frame")
|
||||
.allowlist_function("ra_do_idle")
|
||||
.allowlist_function("ra_http_callback")
|
||||
.allowlist_function("ra_logout_user")
|
||||
.allowlist_function("ra_login_user")
|
||||
.allowlist_function("ra_login_token_user")
|
||||
.allowlist_function("ra_is_user_logged_in")
|
||||
.allowlist_function("ra_get_username")
|
||||
.allowlist_function("ra_get_token")
|
||||
.allowlist_function("ra_state_size")
|
||||
.allowlist_function("ra_save_state")
|
||||
.allowlist_function("ra_load_state")
|
||||
.allowlist_function("ra_load_state");
|
||||
|
||||
if cfg!(feature = "gui") {
|
||||
retroachievements_builder = retroachievements_builder
|
||||
.allowlist_function("ra_logout_user")
|
||||
.allowlist_function("ra_is_user_logged_in")
|
||||
.allowlist_function("ra_get_username")
|
||||
.allowlist_function("ra_get_token");
|
||||
}
|
||||
|
||||
let retroachievements_bindings = retroachievements_builder
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
@ -15,16 +15,24 @@ pub struct Cheats {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct CheatData {
|
||||
pub note: String,
|
||||
pub data: Vec<String>,
|
||||
pub options: Option<std::collections::BTreeMap<String, String>>,
|
||||
}
|
||||
|
||||
pub type Cheat = std::collections::BTreeMap<String, std::collections::BTreeMap<String, CheatData>>;
|
||||
|
||||
pub fn init(
|
||||
device: &mut device::Device,
|
||||
cheat_settings: std::collections::HashMap<String, Option<String>>,
|
||||
) {
|
||||
let cheats =
|
||||
serde_json::from_slice::<ui::cheats::Cheats>(include_bytes!("../data/cheats.json"))
|
||||
.unwrap()
|
||||
.get(&ui::storage::get_game_crc(&device.cart.rom))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let cheats = serde_json::from_slice::<Cheat>(include_bytes!("../data/cheats.json"))
|
||||
.unwrap()
|
||||
.get(&ui::storage::get_game_crc(&device.cart.rom))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let re = regex::Regex::new(r"\?+").unwrap();
|
||||
for cheat_setting in cheat_settings.iter() {
|
||||
|
||||
@ -44,7 +44,7 @@ pub mod tlb;
|
||||
pub mod unmapped;
|
||||
pub mod vi;
|
||||
|
||||
pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::gui::GameSettings) {
|
||||
pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::GameSettings) {
|
||||
device.cpu.overclock = game_settings.overclock;
|
||||
if game_settings.disable_expansion_pak {
|
||||
device.rdram.size = 0x400000;
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use crate::{device, ui};
|
||||
use crate::device;
|
||||
#[cfg(feature = "gui")]
|
||||
use crate::ui;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const JCMD_VRU_READ: u8 = 0x09;
|
||||
@ -179,7 +181,10 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0;
|
||||
}
|
||||
JCMD_VRU_READ => {
|
||||
#[cfg(feature = "gui")]
|
||||
let index = ui::vru::prompt_for_match(&device.vru.words, device.vi.frame_time);
|
||||
#[cfg(not(feature = "gui"))]
|
||||
let index = 0x7FFF;
|
||||
let num_results = if index == 0x7FFF { 0 } else { 1 };
|
||||
let data: HashMap<usize, u16> = HashMap::from([
|
||||
(0, 0x8000),
|
||||
|
||||
14
src/main.rs
14
src/main.rs
@ -11,11 +11,10 @@ mod savestates;
|
||||
mod ui;
|
||||
use clap::Parser;
|
||||
use std::io::Error;
|
||||
use ui::gui;
|
||||
|
||||
/// N64 emulator
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(author, version, about, long_about = None, arg_required_else_help = if cfg!(feature = "gui") { false } else { true })]
|
||||
struct Args {
|
||||
game: Option<String>,
|
||||
#[arg(short, long)]
|
||||
@ -214,7 +213,7 @@ async fn main() -> std::io::Result<()> {
|
||||
device::run_game(
|
||||
&mut device,
|
||||
&rom_contents,
|
||||
ui::gui::GameSettings {
|
||||
ui::GameSettings {
|
||||
overclock,
|
||||
disable_expansion_pak,
|
||||
cheats,
|
||||
@ -282,9 +281,12 @@ async fn main() -> std::io::Result<()> {
|
||||
ui::input::bind_input_profile(&mut ui, profile, port);
|
||||
}
|
||||
} else {
|
||||
retroachievements::init_client(false, false, false);
|
||||
gui::app_window();
|
||||
retroachievements::shutdown_client();
|
||||
#[cfg(feature = "gui")]
|
||||
{
|
||||
retroachievements::init_client(false, false, false);
|
||||
ui::gui::app_window();
|
||||
retroachievements::shutdown_client();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
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()
|
||||
@ -144,115 +143,6 @@ pub extern "C" fn rust_server_call(
|
||||
});
|
||||
}
|
||||
|
||||
fn set_current_user_message(app: &ui::gui::AppWindow, rx: tokio::sync::oneshot::Receiver<bool>) {
|
||||
app.set_ra_current_user_message("Logging in...".into());
|
||||
let weak_app = app.as_weak();
|
||||
tokio::spawn(async move {
|
||||
rx.await.unwrap();
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
if is_user_logged_in() {
|
||||
handle.set_ra_current_user_message(
|
||||
format!("Logged in as {}", handle.get_ra_username()).into(),
|
||||
);
|
||||
handle.set_ra_show_profile(true);
|
||||
} else {
|
||||
handle.set_ra_current_user_message("Login failed".into());
|
||||
handle.set_ra_show_profile(false);
|
||||
}
|
||||
handle.set_ra_logging_in(false);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn ra_window(app: &ui::gui::AppWindow) {
|
||||
let mut token = String::new();
|
||||
if let Ok(ra_config) = std::fs::read(ui::get_dirs().config_dir.join("retroachievements.json"))
|
||||
&& let Ok(result) = serde_json::from_slice::<RAConfig>(ra_config.as_ref())
|
||||
{
|
||||
app.set_ra_username(result.username.into());
|
||||
app.set_ra_enabled(result.enabled);
|
||||
app.set_ra_hardcore(result.hardcore);
|
||||
app.set_ra_challenge(result.challenge);
|
||||
app.set_ra_leaderboard(result.leaderboard);
|
||||
token = result.token;
|
||||
} else {
|
||||
app.set_ra_hardcore(true);
|
||||
}
|
||||
|
||||
if app.get_ra_enabled() && !app.get_ra_username().is_empty() {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
|
||||
login_token_user(app.get_ra_username().to_string(), token, tx);
|
||||
set_current_user_message(app, rx);
|
||||
} else {
|
||||
app.set_ra_current_user_message("Not currently logged in".into());
|
||||
}
|
||||
|
||||
let weak_app2 = app.as_weak();
|
||||
app.on_ra_button_clicked(move |password| {
|
||||
weak_app2
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
|
||||
login_user(
|
||||
handle.get_ra_username().to_string(),
|
||||
password.to_string(),
|
||||
tx,
|
||||
);
|
||||
|
||||
set_current_user_message(&handle, rx);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
app.on_ra_toggled(move |enabled, hardcore, challenge, leaderboard| {
|
||||
let file_path = ui::get_dirs().config_dir.join("retroachievements.json");
|
||||
let raconfig = if let Ok(ra_config) = std::fs::read(&file_path)
|
||||
&& let Ok(result) = serde_json::from_slice::<RAConfig>(ra_config.as_ref())
|
||||
{
|
||||
if !enabled {
|
||||
unsafe { ra_logout_user() };
|
||||
RAConfig {
|
||||
username: String::new(),
|
||||
token: String::new(),
|
||||
enabled,
|
||||
hardcore,
|
||||
challenge,
|
||||
leaderboard,
|
||||
}
|
||||
} else {
|
||||
RAConfig {
|
||||
username: result.username,
|
||||
token: result.token,
|
||||
enabled,
|
||||
hardcore,
|
||||
challenge,
|
||||
leaderboard,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
RAConfig {
|
||||
username: String::new(),
|
||||
token: String::new(),
|
||||
enabled,
|
||||
hardcore,
|
||||
challenge,
|
||||
leaderboard,
|
||||
}
|
||||
};
|
||||
let f = std::fs::File::create(&file_path).unwrap();
|
||||
serde_json::to_writer_pretty(f, &raconfig).unwrap();
|
||||
});
|
||||
|
||||
app.on_ra_games_clicked(move || {
|
||||
open::that_detached("https://retroachievements.org/system/2-nintendo-64/games").unwrap();
|
||||
});
|
||||
|
||||
app.on_ra_show_profile_clicked(move |username| {
|
||||
open::that_detached(format!("https://retroachievements.org/user/{}", username)).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn load_game(rom: &[u8], rom_size: usize) {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
|
||||
unsafe {
|
||||
@ -299,6 +189,11 @@ pub fn login_user(username: String, password: String, tx: tokio::sync::oneshot::
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn logout_user() {
|
||||
unsafe { ra_logout_user() };
|
||||
}
|
||||
|
||||
pub fn login_token_user(username: String, token: String, tx: tokio::sync::oneshot::Sender<bool>) {
|
||||
unsafe {
|
||||
let tx_ptr = Box::into_raw(Box::new(tx)) as *mut std::ffi::c_void;
|
||||
@ -308,10 +203,12 @@ pub fn login_token_user(username: String, token: String, tx: tokio::sync::onesho
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn is_user_logged_in() -> bool {
|
||||
unsafe { ra_is_user_logged_in() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn get_username() -> &'static str {
|
||||
let c_username = unsafe { ra_get_username() };
|
||||
if c_username.is_null() {
|
||||
@ -323,6 +220,7 @@ pub fn get_username() -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn get_token() -> &'static str {
|
||||
let c_token = unsafe { ra_get_token() };
|
||||
if c_token.is_null() {
|
||||
|
||||
14
src/ui.rs
14
src/ui.rs
@ -1,12 +1,18 @@
|
||||
pub mod audio;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod cheats;
|
||||
pub mod config;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod gui;
|
||||
pub mod input;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod netplay;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod retroachievements;
|
||||
pub mod storage;
|
||||
pub mod usb;
|
||||
pub mod video;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod vru;
|
||||
|
||||
pub struct Dirs {
|
||||
@ -47,6 +53,14 @@ pub struct Usb {
|
||||
pub cart_rx: Option<tokio::sync::broadcast::Receiver<usb::UsbData>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameSettings {
|
||||
pub overclock: bool,
|
||||
pub disable_expansion_pak: bool,
|
||||
pub cheats: std::collections::HashMap<String, Option<String>>,
|
||||
pub load_savestate_slot: Option<u32>,
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
pub dirs: Dirs,
|
||||
pub config: config::Config,
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
use crate::cheats;
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::ui::gui::AppWindow;
|
||||
use crate::ui::gui::ErrorDialog;
|
||||
use slint::ComponentHandle;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct CheatData {
|
||||
note: String,
|
||||
pub data: Vec<String>,
|
||||
pub options: Option<std::collections::BTreeMap<String, String>>,
|
||||
}
|
||||
|
||||
pub type Cheats = std::collections::BTreeMap<String, std::collections::BTreeMap<String, CheatData>>;
|
||||
|
||||
pub fn cheats_window(app: &AppWindow) {
|
||||
let weak = app.as_weak();
|
||||
app.on_cheats_select_rom_clicked(move |rom_dir| {
|
||||
@ -38,7 +30,7 @@ pub fn cheats_window(app: &AppWindow) {
|
||||
handle.set_cheat_game_name(game_name.into());
|
||||
})
|
||||
.unwrap();
|
||||
let cheats: Cheats =
|
||||
let cheats: cheats::Cheat =
|
||||
serde_json::from_slice(include_bytes!("../../data/cheats.json")).unwrap();
|
||||
if let Some(cheat) = cheats.get(&game_crc) {
|
||||
let cheat = cheat.clone();
|
||||
|
||||
@ -18,14 +18,6 @@ pub struct NetplayDevice {
|
||||
pub player_number: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameSettings {
|
||||
pub overclock: bool,
|
||||
pub disable_expansion_pak: bool,
|
||||
pub cheats: std::collections::HashMap<String, Option<String>>,
|
||||
pub load_savestate_slot: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RASettings {
|
||||
pub enabled: bool,
|
||||
@ -358,7 +350,7 @@ fn about_window(app: &AppWindow) {
|
||||
pub fn app_window() {
|
||||
let app = AppWindow::new().unwrap();
|
||||
about_window(&app);
|
||||
retroachievements::ra_window(&app);
|
||||
ui::retroachievements::ra_window(&app);
|
||||
let mut controller_paths;
|
||||
{
|
||||
let game_ui = ui::Ui::new();
|
||||
@ -378,7 +370,7 @@ pub fn app_window() {
|
||||
|
||||
pub fn run_rom(
|
||||
file_path: std::path::PathBuf,
|
||||
game_settings: GameSettings,
|
||||
game_settings: ui::GameSettings,
|
||||
netplay: Option<NetplayDevice>,
|
||||
ra_settings: RASettings,
|
||||
weak: slint::Weak<AppWindow>,
|
||||
@ -473,7 +465,7 @@ fn open_rom(app: &AppWindow) {
|
||||
if let Some(file) = select_rom.await {
|
||||
run_rom(
|
||||
file.path().to_path_buf(),
|
||||
GameSettings {
|
||||
ui::GameSettings {
|
||||
overclock,
|
||||
disable_expansion_pak,
|
||||
cheats: std::collections::HashMap::new(), // will be filled in later
|
||||
|
||||
@ -384,6 +384,7 @@ pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
|
||||
controllers
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<Option<String>> {
|
||||
let mut controller_paths: Vec<Option<String>> = vec![];
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use crate::ui::gui::{
|
||||
AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, GameSettings, NetplayCreate,
|
||||
NetplayDevice, NetplayJoin, NetplayWait, RASettings, run_rom, save_settings,
|
||||
AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, NetplayCreate, NetplayDevice,
|
||||
NetplayJoin, NetplayWait, RASettings, run_rom, save_settings,
|
||||
};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use sha2::{Digest, Sha256};
|
||||
@ -247,7 +247,7 @@ fn show_custom_url_dialog(weak: slint::Weak<NetplayCreate>, server_url: slint::S
|
||||
|
||||
pub fn setup_create_window(
|
||||
create_window: &NetplayCreate,
|
||||
game_settings: GameSettings,
|
||||
game_settings: ui::GameSettings,
|
||||
ra_settings: RASettings,
|
||||
rom_dir: slint::SharedString,
|
||||
weak_app: slint::Weak<AppWindow>,
|
||||
@ -613,7 +613,7 @@ fn create_session(
|
||||
game_hash: String,
|
||||
game_cheats: String,
|
||||
password: String,
|
||||
game_settings: GameSettings,
|
||||
game_settings: ui::GameSettings,
|
||||
ra_settings: RASettings,
|
||||
weak_app: slint::Weak<AppWindow>,
|
||||
weak: slint::Weak<NetplayCreate>,
|
||||
@ -703,7 +703,7 @@ fn create_session(
|
||||
handle.get_rom_path(),
|
||||
message.player_name.as_ref().unwrap().into(),
|
||||
session.port.unwrap(),
|
||||
GameSettings {
|
||||
ui::GameSettings {
|
||||
overclock: overclock.parse().unwrap(),
|
||||
disable_expansion_pak: disable_expansion_pak.parse().unwrap(),
|
||||
cheats: serde_json::from_str(cheats).unwrap(),
|
||||
@ -826,7 +826,7 @@ fn join_session(
|
||||
handle.get_rom_path(),
|
||||
message.player_name.as_ref().unwrap().into(),
|
||||
session.port.unwrap(),
|
||||
GameSettings {
|
||||
ui::GameSettings {
|
||||
overclock: overclock.parse().unwrap(),
|
||||
disable_expansion_pak: disable_expansion_pak.parse().unwrap(),
|
||||
cheats: serde_json::from_str(cheats).unwrap(),
|
||||
@ -872,7 +872,7 @@ fn setup_wait_window(
|
||||
rom_path: slint::SharedString,
|
||||
player_name: slint::SharedString,
|
||||
port: i32,
|
||||
game_settings: GameSettings,
|
||||
game_settings: ui::GameSettings,
|
||||
ra_settings: RASettings,
|
||||
peer_addr: slint::SharedString,
|
||||
weak_app: slint::Weak<AppWindow>,
|
||||
@ -1090,7 +1090,7 @@ fn setup_wait_window(
|
||||
|
||||
run_rom(
|
||||
handle.get_rom_path().as_str().into(),
|
||||
GameSettings {
|
||||
ui::GameSettings {
|
||||
overclock: game_settings.overclock,
|
||||
disable_expansion_pak: game_settings.disable_expansion_pak,
|
||||
cheats: game_settings.cheats,
|
||||
@ -1214,7 +1214,7 @@ pub fn netplay_window(app: &AppWindow, controller_paths: &[Option<String>]) {
|
||||
save_settings(&handle, &controller_paths);
|
||||
setup_create_window(
|
||||
&create_window,
|
||||
GameSettings {
|
||||
ui::GameSettings {
|
||||
overclock: handle.get_overclock_n64_cpu(),
|
||||
disable_expansion_pak: handle.get_disable_expansion_pak(),
|
||||
cheats: std::collections::HashMap::new(), // not used here
|
||||
|
||||
114
src/ui/retroachievements.rs
Normal file
114
src/ui/retroachievements.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use crate::retroachievements;
|
||||
use crate::ui;
|
||||
use slint::ComponentHandle;
|
||||
|
||||
pub fn ra_window(app: &ui::gui::AppWindow) {
|
||||
let mut token = String::new();
|
||||
if let Ok(ra_config) = std::fs::read(ui::get_dirs().config_dir.join("retroachievements.json"))
|
||||
&& let Ok(result) =
|
||||
serde_json::from_slice::<retroachievements::RAConfig>(ra_config.as_ref())
|
||||
{
|
||||
app.set_ra_username(result.username.into());
|
||||
app.set_ra_enabled(result.enabled);
|
||||
app.set_ra_hardcore(result.hardcore);
|
||||
app.set_ra_challenge(result.challenge);
|
||||
app.set_ra_leaderboard(result.leaderboard);
|
||||
token = result.token;
|
||||
} else {
|
||||
app.set_ra_hardcore(true);
|
||||
}
|
||||
|
||||
if app.get_ra_enabled() && !app.get_ra_username().is_empty() {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
|
||||
retroachievements::login_token_user(app.get_ra_username().to_string(), token, tx);
|
||||
set_current_user_message(app, rx);
|
||||
} else {
|
||||
app.set_ra_current_user_message("Not currently logged in".into());
|
||||
}
|
||||
|
||||
let weak_app2 = app.as_weak();
|
||||
app.on_ra_button_clicked(move |password| {
|
||||
weak_app2
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
|
||||
retroachievements::login_user(
|
||||
handle.get_ra_username().to_string(),
|
||||
password.to_string(),
|
||||
tx,
|
||||
);
|
||||
|
||||
set_current_user_message(&handle, rx);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
app.on_ra_toggled(move |enabled, hardcore, challenge, leaderboard| {
|
||||
let file_path = ui::get_dirs().config_dir.join("retroachievements.json");
|
||||
let raconfig = if let Ok(ra_config) = std::fs::read(&file_path)
|
||||
&& let Ok(result) =
|
||||
serde_json::from_slice::<retroachievements::RAConfig>(ra_config.as_ref())
|
||||
{
|
||||
if !enabled {
|
||||
retroachievements::logout_user();
|
||||
retroachievements::RAConfig {
|
||||
username: String::new(),
|
||||
token: String::new(),
|
||||
enabled,
|
||||
hardcore,
|
||||
challenge,
|
||||
leaderboard,
|
||||
}
|
||||
} else {
|
||||
retroachievements::RAConfig {
|
||||
username: result.username,
|
||||
token: result.token,
|
||||
enabled,
|
||||
hardcore,
|
||||
challenge,
|
||||
leaderboard,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
retroachievements::RAConfig {
|
||||
username: String::new(),
|
||||
token: String::new(),
|
||||
enabled,
|
||||
hardcore,
|
||||
challenge,
|
||||
leaderboard,
|
||||
}
|
||||
};
|
||||
let f = std::fs::File::create(&file_path).unwrap();
|
||||
serde_json::to_writer_pretty(f, &raconfig).unwrap();
|
||||
});
|
||||
|
||||
app.on_ra_games_clicked(move || {
|
||||
open::that_detached("https://retroachievements.org/system/2-nintendo-64/games").unwrap();
|
||||
});
|
||||
|
||||
app.on_ra_show_profile_clicked(move |username| {
|
||||
open::that_detached(format!("https://retroachievements.org/user/{}", username)).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn set_current_user_message(app: &ui::gui::AppWindow, rx: tokio::sync::oneshot::Receiver<bool>) {
|
||||
app.set_ra_current_user_message("Logging in...".into());
|
||||
let weak_app = app.as_weak();
|
||||
tokio::spawn(async move {
|
||||
rx.await.unwrap();
|
||||
weak_app
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
if retroachievements::is_user_logged_in() {
|
||||
handle.set_ra_current_user_message(
|
||||
format!("Logged in as {}", handle.get_ra_username()).into(),
|
||||
);
|
||||
handle.set_ra_show_profile(true);
|
||||
} else {
|
||||
handle.set_ra_current_user_message("Login failed".into());
|
||||
handle.set_ra_show_profile(false);
|
||||
}
|
||||
handle.set_ra_logging_in(false);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::gui;
|
||||
use crate::retroachievements;
|
||||
use crate::ui::gui;
|
||||
use slint::ComponentHandle;
|
||||
|
||||
pub fn prompt_for_match(words: &[String], frame_time: f64) -> u16 {
|
||||
|
||||
Reference in New Issue
Block a user