Added fps unlocker and game running support
This commit is contained in:
parent
19bf0fe01c
commit
de5339fa38
8 changed files with 309 additions and 7 deletions
|
@ -16,13 +16,14 @@ serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", optional = true }
|
||||||
wincompatlib = { version = "0.1.2", features = ["dxvk"], optional = true }
|
wincompatlib = { version = "0.1.2", features = ["dxvk"], optional = true }
|
||||||
lazy_static = { version = "1.4.0", optional = true }
|
lazy_static = { version = "1.4.0", optional = true }
|
||||||
|
md5 = { version = "0.7.0", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
states = []
|
states = []
|
||||||
config = ["dep:serde", "dep:serde_json"]
|
config = ["dep:serde", "dep:serde_json"]
|
||||||
components = ["dep:wincompatlib", "dep:lazy_static"]
|
components = ["dep:wincompatlib", "dep:lazy_static"]
|
||||||
runner = []
|
game = ["components"]
|
||||||
fps-unlocker = []
|
fps-unlocker = ["dep:md5"]
|
||||||
|
|
||||||
default = ["all"]
|
default = ["all"]
|
||||||
all = ["states", "config", "components", "runner", "fps-unlocker"]
|
all = ["states", "config", "components", "game", "fps-unlocker"]
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* Remove excess code from gtk launcher and prepare it for relm4 rewrite;
|
* Remove excess code from gtk launcher and prepare it for relm4 rewrite;
|
||||||
* Prepare codebase for tauri rewrite;
|
* Prepare codebase for tauri rewrite;
|
||||||
|
|
||||||
## Current progress (50%)
|
## Current progress (75%)
|
||||||
|
|
||||||
| Status | Feature | Description |
|
| Status | Feature | Description |
|
||||||
| :-: | - | - |
|
| :-: | - | - |
|
||||||
|
@ -16,5 +16,5 @@
|
||||||
| ✅ | | List Wine and DXVK versions |
|
| ✅ | | List Wine and DXVK versions |
|
||||||
| ❌ | | Download, delete and select wine |
|
| ❌ | | Download, delete and select wine |
|
||||||
| ❌ | | Download, delete, select and apply DXVK |
|
| ❌ | | Download, delete, select and apply DXVK |
|
||||||
| ❌ | runner | Run the game |
|
| ✅ | game | Run the game |
|
||||||
| ❌ | fps-unlocker | Support of FPS unlocker. Manage its config, download, use in game runner |
|
| ✅ | fps-unlocker | Support of FPS unlocker. Manage its config, download, use in game runner |
|
||||||
|
|
|
@ -34,14 +34,17 @@ pub struct Version {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Version {
|
impl Version {
|
||||||
|
/// Get latest recommended dxvk version
|
||||||
pub fn latest() -> Self {
|
pub fn latest() -> Self {
|
||||||
get_groups()[0].versions[0].clone()
|
get_groups()[0].versions[0].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check is current dxvk downloaded in specified folder
|
||||||
pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool {
|
pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool {
|
||||||
folder.into().join(&self.name).exists()
|
folder.into().join(&self.name).exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply current dxvk to specified wine prefix
|
||||||
pub fn apply<T: Into<PathBuf>>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result<Output> {
|
pub fn apply<T: Into<PathBuf>>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result<Output> {
|
||||||
let apply_path = dxvks_folder.into().join(&self.name).join("setup_dxvk.sh");
|
let apply_path = dxvks_folder.into().join(&self.name).join("setup_dxvk.sh");
|
||||||
let config = config::get()?;
|
let config = config::get()?;
|
||||||
|
@ -75,11 +78,12 @@ impl Version {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get dxvk groups
|
||||||
pub fn get_groups() -> Vec<Group> {
|
pub fn get_groups() -> Vec<Group> {
|
||||||
GROUPS.clone()
|
GROUPS.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List downloaded DXVK versions in some specific folder
|
/// List downloaded dxvk versions in some specific folder
|
||||||
pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> {
|
pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> {
|
||||||
let mut downloaded = Vec::new();
|
let mut downloaded = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,17 @@ pub struct Version {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Version {
|
impl Version {
|
||||||
|
/// Get latest recommended wine version
|
||||||
pub fn latest() -> Self {
|
pub fn latest() -> Self {
|
||||||
get_groups()[0].versions[0].clone()
|
get_groups()[0].versions[0].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check is current wine downloaded in specified folder
|
||||||
pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool {
|
pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool {
|
||||||
folder.into().join(&self.name).exists()
|
folder.into().join(&self.name).exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert current wine struct to one from `wincompatlib`
|
||||||
pub fn to_wine(&self) -> Wine {
|
pub fn to_wine(&self) -> Wine {
|
||||||
Wine::new(
|
Wine::new(
|
||||||
&self.files.wine64,
|
&self.files.wine64,
|
||||||
|
@ -69,10 +72,12 @@ pub struct Files {
|
||||||
pub winecfg: String
|
pub winecfg: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get wine groups
|
||||||
pub fn get_groups() -> Vec<Group> {
|
pub fn get_groups() -> Vec<Group> {
|
||||||
GROUPS.clone()
|
GROUPS.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List downloaded wine versions in some specific folder
|
||||||
pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> {
|
pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> {
|
||||||
let mut downloaded = Vec::new();
|
let mut downloaded = Vec::new();
|
||||||
|
|
||||||
|
|
66
src/fps_unlocker/config_schema.rs
Normal file
66
src/fps_unlocker/config_schema.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::FpsUnlockerConfig;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub struct ConfigSchema {
|
||||||
|
pub DllList: Vec<String>,
|
||||||
|
pub Priority: u64,
|
||||||
|
pub MonitorNum: u64,
|
||||||
|
pub CustomResY: u64,
|
||||||
|
pub CustomResX: u64,
|
||||||
|
pub FPSTarget: u64,
|
||||||
|
pub UsePowerSave: bool,
|
||||||
|
pub StartMinimized: bool,
|
||||||
|
pub IsExclusiveFullscreen: bool,
|
||||||
|
pub UseCustomRes: bool,
|
||||||
|
pub Fullscreen: bool,
|
||||||
|
pub PopupWindow: bool,
|
||||||
|
pub AutoClose: bool,
|
||||||
|
pub AutoDisableVSync: bool,
|
||||||
|
pub AutoStart: bool,
|
||||||
|
pub GamePath: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ConfigSchema {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
DllList: vec![],
|
||||||
|
Priority: 3,
|
||||||
|
MonitorNum: 1,
|
||||||
|
CustomResY: 1080,
|
||||||
|
CustomResX: 1920,
|
||||||
|
FPSTarget: 120,
|
||||||
|
UsePowerSave: false,
|
||||||
|
IsExclusiveFullscreen: false,
|
||||||
|
UseCustomRes: false,
|
||||||
|
Fullscreen: false,
|
||||||
|
PopupWindow: false,
|
||||||
|
AutoDisableVSync: true,
|
||||||
|
GamePath: None,
|
||||||
|
|
||||||
|
// Launcher-specific settings
|
||||||
|
AutoStart: true,
|
||||||
|
AutoClose: true,
|
||||||
|
StartMinimized: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigSchema {
|
||||||
|
pub fn from_config(config: FpsUnlockerConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
FPSTarget: config.fps,
|
||||||
|
UsePowerSave: config.power_saving,
|
||||||
|
Fullscreen: config.fullscreen,
|
||||||
|
Priority: config.priority,
|
||||||
|
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json(&self) -> serde_json::Result<String> {
|
||||||
|
serde_json::to_string(self)
|
||||||
|
}
|
||||||
|
}
|
82
src/fps_unlocker/mod.rs
Normal file
82
src/fps_unlocker/mod.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anime_game_core::installer::downloader::Downloader;
|
||||||
|
|
||||||
|
use crate::config::game::enhancements::fps_unlocker::config::Config as FpsUnlockerConfig;
|
||||||
|
|
||||||
|
pub mod config_schema;
|
||||||
|
|
||||||
|
const LATEST_INFO: (&str, &str) = (
|
||||||
|
"6040a6f0be5dbf4d55d6b129cad47b5b",
|
||||||
|
"https://github.com/34736384/genshin-fps-unlock/releases/download/v2.0.0/unlockfps_clr.exe"
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FpsUnlocker {
|
||||||
|
dir: PathBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FpsUnlocker {
|
||||||
|
/// Get FpsUnlocker from its containment directory
|
||||||
|
///
|
||||||
|
/// Returns
|
||||||
|
/// - `Err(..)` if failed to read `unlocker.exe` file
|
||||||
|
/// - `Ok(None)` if version is not latest
|
||||||
|
/// - `Ok(..)` if version is latest
|
||||||
|
pub fn from_dir<T: Into<PathBuf>>(dir: T) -> anyhow::Result<Option<Self>> {
|
||||||
|
let dir = dir.into();
|
||||||
|
|
||||||
|
let hash = format!("{:x}", md5::compute(std::fs::read(dir.join("unlocker.exe"))?));
|
||||||
|
|
||||||
|
Ok(if hash == LATEST_INFO.0 {
|
||||||
|
Some(Self { dir })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download FPS unlocker to specified directory
|
||||||
|
pub fn download<T: Into<PathBuf>>(dir: T) -> anyhow::Result<Self> {
|
||||||
|
let mut downloader = Downloader::new(LATEST_INFO.1)?;
|
||||||
|
|
||||||
|
let dir = dir.into();
|
||||||
|
|
||||||
|
// Create FPS unlocker folder if needed
|
||||||
|
if !dir.exists() {
|
||||||
|
std::fs::create_dir_all(&dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match downloader.download_to(dir.join("unlocker.exe"), |_, _| {}) {
|
||||||
|
Ok(_) => Ok(Self {
|
||||||
|
dir
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
let err: std::io::Error = err.into();
|
||||||
|
|
||||||
|
Err(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_binary(&self) -> PathBuf {
|
||||||
|
Self::get_binary_in(&self.dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_binary_in<T: Into<PathBuf>>(dir: T) -> PathBuf {
|
||||||
|
dir.into().join("unlocker.exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dir(&self) -> &PathBuf {
|
||||||
|
&self.dir
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate and save FPS unlocker config file to the game's directory
|
||||||
|
pub fn update_config(&self, config: FpsUnlockerConfig) -> anyhow::Result<()> {
|
||||||
|
let config = config_schema::ConfigSchema::from_config(config);
|
||||||
|
|
||||||
|
Ok(std::fs::write(
|
||||||
|
self.dir.join("fps_config.json"),
|
||||||
|
config.json()?
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
}
|
138
src/game/mod.rs
Normal file
138
src/game/mod.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use anime_game_core::genshin::telemetry;
|
||||||
|
|
||||||
|
use super::consts;
|
||||||
|
use super::config;
|
||||||
|
use super::fps_unlocker::FpsUnlocker;
|
||||||
|
|
||||||
|
/// Try to run the game
|
||||||
|
///
|
||||||
|
/// If `debug = true`, then the game will be run in the new terminal window
|
||||||
|
pub fn run() -> anyhow::Result<()> {
|
||||||
|
let config = config::get()?;
|
||||||
|
|
||||||
|
if !config.game.path.exists() {
|
||||||
|
return Err(anyhow::anyhow!("Game is not installed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let wine_executable = match config.try_get_wine_executable() {
|
||||||
|
Some(path) => path,
|
||||||
|
None => return Err(anyhow::anyhow!("Couldn't find wine executable"))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check telemetry servers
|
||||||
|
|
||||||
|
if let Some(server) = telemetry::is_disabled(consts::TELEMETRY_CHECK_TIMEOUT) {
|
||||||
|
return Err(anyhow::anyhow!("Telemetry server is not disabled: {server}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare fps unlocker
|
||||||
|
// 1) Download if needed
|
||||||
|
// 2) Generate config file
|
||||||
|
// 3) Generate fpsunlocker.bat from launcher.bat
|
||||||
|
|
||||||
|
#[cfg(feature = "fps-unlocker")]
|
||||||
|
if config.game.enhancements.fps_unlocker.enabled {
|
||||||
|
let unlocker = match FpsUnlocker::from_dir(&config.game.enhancements.fps_unlocker.path) {
|
||||||
|
Ok(Some(unlocker)) => unlocker,
|
||||||
|
|
||||||
|
other => {
|
||||||
|
// Ok(None) means unknown version, so we should delete it before downloading newer one
|
||||||
|
// because otherwise downloader will try to continue downloading "partially downloaded" file
|
||||||
|
if let Ok(None) = other {
|
||||||
|
std::fs::remove_file(FpsUnlocker::get_binary_in(&config.game.enhancements.fps_unlocker.path))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match FpsUnlocker::download(&config.game.enhancements.fps_unlocker.path) {
|
||||||
|
Ok(unlocker) => unlocker,
|
||||||
|
Err(err) => return Err(anyhow::anyhow!("Failed to download FPS unlocker: {err}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate FPS unlocker config file
|
||||||
|
if let Err(err) = unlocker.update_config(config.game.enhancements.fps_unlocker.config.clone()) {
|
||||||
|
return Err(anyhow::anyhow!("Failed to update FPS unlocker config: {err}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bat_path = config.game.path.join("fpsunlocker.bat");
|
||||||
|
let original_bat_path = config.game.path.join("launcher.bat");
|
||||||
|
|
||||||
|
// Generate fpsunlocker.bat from launcher.bat
|
||||||
|
std::fs::write(bat_path, std::fs::read_to_string(original_bat_path)?
|
||||||
|
.replace("start GenshinImpact.exe %*", &format!("start GenshinImpact.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir().to_string_lossy()))
|
||||||
|
.replace("start YuanShen.exe %*", &format!("start YuanShen.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir().to_string_lossy())))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare bash -c '<command>'
|
||||||
|
|
||||||
|
let mut bash_chain = String::new();
|
||||||
|
|
||||||
|
if config.game.enhancements.gamemode {
|
||||||
|
bash_chain += "gamemoderun ";
|
||||||
|
}
|
||||||
|
|
||||||
|
bash_chain += &format!("'{}' ", wine_executable.to_string_lossy());
|
||||||
|
|
||||||
|
if let Some(virtual_desktop) = config.game.wine.virtual_desktop.get_command() {
|
||||||
|
bash_chain += &format!("{virtual_desktop} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
bash_chain += if config.game.enhancements.fps_unlocker.enabled && cfg!(feature = "fps-unlocker") {
|
||||||
|
"fpsunlocker.bat "
|
||||||
|
} else {
|
||||||
|
"launcher.bat "
|
||||||
|
};
|
||||||
|
|
||||||
|
if config.game.wine.borderless {
|
||||||
|
bash_chain += "-screen-fullscreen 0 -popupwindow ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://notabug.org/Krock/dawn/src/master/TWEAKS.md
|
||||||
|
if config.game.enhancements.fsr.enabled {
|
||||||
|
bash_chain += "-window-mode exclusive ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// gamescope <params> -- <command to run>
|
||||||
|
if let Some(gamescope) = config.game.enhancements.gamescope.get_command() {
|
||||||
|
bash_chain = format!("{gamescope} -- {bash_chain}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let bash_chain = match &config.game.command {
|
||||||
|
Some(command) => command.replace("%command%", &bash_chain),
|
||||||
|
None => bash_chain
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut command = Command::new("bash");
|
||||||
|
|
||||||
|
command.arg("-c");
|
||||||
|
command.arg(&bash_chain);
|
||||||
|
|
||||||
|
// Setup environment
|
||||||
|
|
||||||
|
command.env("WINEARCH", "win64");
|
||||||
|
command.env("WINEPREFIX", &config.game.wine.prefix);
|
||||||
|
|
||||||
|
// Add DXVK_ASYNC=1 for dxvk-async builds automatically
|
||||||
|
if let Ok(Some(dxvk)) = &config.try_get_selected_dxvk_info() {
|
||||||
|
if dxvk.version.contains("async") {
|
||||||
|
command.env("DXVK_ASYNC", "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command.envs(config.game.wine.sync.get_env_vars());
|
||||||
|
command.envs(config.game.enhancements.hud.get_env_vars(&config));
|
||||||
|
command.envs(config.game.enhancements.fsr.get_env_vars());
|
||||||
|
command.envs(config.game.wine.language.get_env_vars());
|
||||||
|
|
||||||
|
command.envs(config.game.environment);
|
||||||
|
|
||||||
|
// Run command
|
||||||
|
|
||||||
|
println!("Running command: bash -c \"{}\"", bash_chain);
|
||||||
|
|
||||||
|
command.current_dir(config.game.path).spawn()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -11,6 +11,12 @@ pub mod states;
|
||||||
#[cfg(feature = "components")]
|
#[cfg(feature = "components")]
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
|
||||||
|
#[cfg(feature = "game")]
|
||||||
|
pub mod game;
|
||||||
|
|
||||||
|
#[cfg(feature = "fps-unlocker")]
|
||||||
|
pub mod fps_unlocker;
|
||||||
|
|
||||||
/// Check if specified binary is available
|
/// Check if specified binary is available
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
Loading…
Reference in a new issue