game picker
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
use std::{sync::Arc, thread, time::Duration};
|
||||
use std::{f32::consts::E, path::PathBuf, sync::Arc, thread, time::Duration};
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use egui::{mutex::Mutex};
|
||||
use log::debug;
|
||||
|
||||
use crate::{config, omori_locator};
|
||||
use crate::{config::{self, ConfigProvider}, omori_locator};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum UiState {
|
||||
Loading,
|
||||
KeyRequired(String),
|
||||
PickGame(Result<PathBuf, String>, Vec<PathBuf>),
|
||||
Error(String)
|
||||
}
|
||||
|
||||
@@ -47,6 +49,19 @@ impl AppThread {
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_game(&mut self, provider: &ConfigProvider) -> anyhow::Result<()> {
|
||||
let steam_location = match omori_locator::get_omori_path() {
|
||||
Ok(l) => Ok(l),
|
||||
Err(e) => Err(String::from(format!("{:#}", e)))
|
||||
};
|
||||
|
||||
let location_history = provider.get_game_paths()?;
|
||||
|
||||
self.commit(UiState::PickGame(steam_location.clone(), location_history.clone()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create(state_holder: &UiStateHolder, context: &egui::Context, ui_event_channel: &Receiver<UiEvent>) -> AppThread {
|
||||
AppThread {
|
||||
ui_state: state_holder.clone(),
|
||||
@@ -57,6 +72,7 @@ impl AppThread {
|
||||
}
|
||||
|
||||
fn real_main(&mut self) -> anyhow::Result<()> {
|
||||
debug!("I am ALIVE! HAHAHAHA!");
|
||||
self.commit(UiState::Loading);
|
||||
|
||||
let mut config_provider = config::ConfigProvider::new()?;
|
||||
@@ -71,6 +87,8 @@ impl AppThread {
|
||||
|
||||
config_provider.set_key(&self.decryption_key)?;
|
||||
|
||||
self.pick_game(&config_provider);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
use std::{fs::{self, File}, path::PathBuf};
|
||||
use std::{fs::{self, File}, path::PathBuf, time::{Instant, SystemTime}};
|
||||
|
||||
use anyhow::Context;
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
use egui::ahash::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct Config {
|
||||
key: Option<Vec<u8>>
|
||||
key: Option<Vec<u8>>,
|
||||
|
||||
#[serde(default)]
|
||||
recently_used_game_paths: HashMap<PathBuf, u64>
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self { key: Default::default() }
|
||||
Self { key: Default::default(), recently_used_game_paths: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +65,19 @@ impl ConfigProvider {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_game_paths(&self) -> anyhow::Result<Vec<PathBuf>> {
|
||||
let mut paths: Vec<(&PathBuf, &u64)> = self.config.recently_used_game_paths.iter().collect();
|
||||
paths.sort_by(|a, b| a.1.cmp(b.1));
|
||||
|
||||
Ok(paths.iter().map(|v| v.0.clone()).collect())
|
||||
}
|
||||
|
||||
pub fn set_game_path_used(&mut self, which: &PathBuf) -> anyhow::Result<()> {
|
||||
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_secs();
|
||||
self.config.recently_used_game_paths.insert(which.clone(), current_time);
|
||||
self.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
61
src/main.rs
61
src/main.rs
@@ -9,12 +9,22 @@ use crossbeam_channel::{Receiver, Sender};
|
||||
use eframe::egui;
|
||||
use egui::{mutex::{Mutex, RwLock}, Align, Layout, RichText, ThemePreference};
|
||||
use egui_alignments::{center_horizontal, center_vertical, top_horizontal, Aligner};
|
||||
use omori_locator::{get_omori_key, get_omori_path};
|
||||
use egui_modal::Modal;
|
||||
use sha2::Digest;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
log::trace!("How do you even do serious work on ValorantOS??");
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
log::trace!("Mac user, rich fuck, send me $500");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
log::trace!("Linux user... Please touch grass for once");
|
||||
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_min_inner_size([640.0, 480.0]),
|
||||
@@ -128,6 +138,55 @@ impl eframe::App for Application {
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
UiState::PickGame(steam, others) => {
|
||||
let invalid_path_modal = Modal::new(ctx, "invalid_path_modal");
|
||||
invalid_path_modal.show(|ui| {
|
||||
invalid_path_modal.title(ui, "Invalid path");
|
||||
invalid_path_modal.frame(ui, |ui| {
|
||||
invalid_path_modal.body(ui, "Please pick a correct installation of OMORI.");
|
||||
});
|
||||
invalid_path_modal.buttons(ui, |ui| {
|
||||
invalid_path_modal.button(ui, "OK");
|
||||
});
|
||||
});
|
||||
egui::TopBottomPanel::top("key_prompt_title_bar").show(ctx, |ui| {
|
||||
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||
ui.label(RichText::new("Select your base game").size(32.0));
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical(|ui| {
|
||||
ui.label(RichText::new("Steam Installation").size(24.0));
|
||||
match steam {
|
||||
Ok(path) => {
|
||||
ui.label(RichText::new(format!("Found game at:\n{}", path.display())).italics());
|
||||
ui.separator();
|
||||
ui.button(RichText::new("Use Steam version").size(16.0));
|
||||
},
|
||||
Err(e) => {
|
||||
ui.label(RichText::new(format!("Failed to find game:\n{}", e)).color(ui.visuals().error_fg_color));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
columns[1].vertical(|ui| {
|
||||
ui.label(RichText::new("Custom").size(24.0));
|
||||
if ui.button(RichText::new("Pick game location").size(16.0)).clicked() {
|
||||
rfd::FileDialog::new().pick_folder();
|
||||
invalid_path_modal.open();
|
||||
}
|
||||
ui.separator();
|
||||
if others.len() > 0 {
|
||||
|
||||
} else {
|
||||
ui.label("Once you use a custom location, it will be remembered here.");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::Context;
|
||||
use log::{debug, info, trace};
|
||||
use regex::bytes::Regex;
|
||||
use sha2::Digest;
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
use std::{fs::{self, File}, io::Read, path::PathBuf};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn get_steam_root() -> anyhow::Result<PathBuf> {
|
||||
@@ -13,6 +14,8 @@ fn get_steam_root() -> anyhow::Result<PathBuf> {
|
||||
#[cfg(target_os="macos")]
|
||||
base.push("Library/Application Support/Steam");
|
||||
|
||||
info!("Using {:?} as steam base", base);
|
||||
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
@@ -29,14 +32,20 @@ fn get_steam_root() -> anyhow::Result<PathBuf> {
|
||||
|
||||
let mut base = PathBuf::from(value);
|
||||
|
||||
debug!("Using {:?} as steam base", base);
|
||||
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
const OMORI_STEAM_APPID: &str = "1150690";
|
||||
|
||||
pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
||||
let mut base = get_steam_root()?;
|
||||
|
||||
base.push("steamapps");
|
||||
base.push("libraryfolders.vdf");
|
||||
|
||||
info!("Searching {:?} for library folders with omori", base);
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
@@ -50,7 +59,6 @@ pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
||||
|
||||
let mut omori_path: Option<PathBuf> = None;
|
||||
for key in keys {
|
||||
println!("{:?}", key);
|
||||
let entry = data.get(key).with_context(|| "Couldn't read specific steam library libraryfolders object.")?;
|
||||
let entry = entry.get(0).with_context(|| "Couldn't read specific steam library libraryfolders object.")?;
|
||||
let entry = entry.get_obj().with_context(|| "libraryfolders parsing failed: Not an object")?;
|
||||
@@ -64,7 +72,7 @@ pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
||||
let path = path.get(0).with_context(|| "libraryfolders parsing failed: No path")?;
|
||||
let path = path.get_str().with_context(|| "libraryfolders parsing failed: No path")?;
|
||||
|
||||
if apps.iter().find(|x| (**x).eq("1150690")).is_some() {
|
||||
if apps.iter().find(|x| (**x).eq(OMORI_STEAM_APPID)).is_some() {
|
||||
let mut base = PathBuf::from(path);
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -91,7 +99,13 @@ pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
||||
}
|
||||
|
||||
|
||||
omori_path.with_context(|| "Failed to find omori")
|
||||
let path = omori_path.with_context(|| "Failed to find omori")?;
|
||||
debug!("{:?} seems to be it.", path);
|
||||
if validate_omori_installation(&path) {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("The steam installation of OMORI is not valid"))
|
||||
}
|
||||
}
|
||||
|
||||
const GAME_KEY_HASH: &[u8; 32] = include_bytes!("keyhash");
|
||||
@@ -127,4 +141,56 @@ pub fn get_omori_key() -> anyhow::Result<Vec<u8>> {
|
||||
|
||||
|
||||
Err(anyhow::anyhow!("Couldn't find any valid decryption key for OMORI in your Steam installation."))
|
||||
}
|
||||
|
||||
|
||||
const EXPECTED_PATHS: [&str; 32] = [
|
||||
"index.html",
|
||||
"editor.json",
|
||||
"audio/bgm/AMB_forest.rpgmvo",
|
||||
"audio/me/cutscene_omori_saves_player.rpgmvo",
|
||||
"data/Actors.KEL",
|
||||
"data/System.KEL",
|
||||
"data/Atlas.PLUTO",
|
||||
"data/Troops.KEL",
|
||||
"fonts/OMORI_GAME.ttf",
|
||||
"icon/icon.png",
|
||||
"img/animations/e_calm_down.rpgmvp",
|
||||
"img/atlases/battleATLAS.rpgmvp",
|
||||
"img/battlebacks1/battleback_dw_humphrey.rpgmvp", // best area
|
||||
"img/characters/!FA_OBJECTS_2.rpgmvp",
|
||||
"img/characters/FA_AUBREY.rpgmvp",
|
||||
"img/enemies/!battle_snow_bunny.rpgmvp",
|
||||
"img/faces/01_OMORI_BATTLE.rpgmvp",
|
||||
"img/overlays/fog.rpgmvp",
|
||||
"img/parallaxes/!BS_faces.rpgmvp",
|
||||
"img/pictures/basil_something.rpgmvp",
|
||||
"img/slotmachine/bet_line_2.rpgmvp",
|
||||
"img/sv_actors/!battle_aubrey.rpgmvp",
|
||||
"img/system/bar_gradients.rpgmvp",
|
||||
"img/system/Window.png",
|
||||
"img/tilesets/BS_Beach.rpgmvp",
|
||||
"img/transition/starburst_2.rpgmvp",
|
||||
"js/rpg_core.js",
|
||||
"js/plugins/Omori BASE.OMORI",
|
||||
"languages/en/10_cutscenes_fakeknifefight.HERO",
|
||||
"maps/FA_SUNSET_INTERIOR2.AUBREY",
|
||||
"movies/secret_ending.webm",
|
||||
"movies/Bad End Credits Draft 4.webm"
|
||||
];
|
||||
|
||||
pub fn validate_omori_installation(base: &PathBuf) -> bool {
|
||||
info!("Validating {:?}", base);
|
||||
for path in EXPECTED_PATHS {
|
||||
trace!("Checking {path}");
|
||||
let mut real = base.clone();
|
||||
real.push(path);
|
||||
|
||||
if let Ok(r) = fs::exists(&real) {
|
||||
if !r { return false; }
|
||||
} else { return false; }
|
||||
}
|
||||
info!("Validation passed");
|
||||
|
||||
true
|
||||
}
|
||||
Reference in New Issue
Block a user