game picker

This commit is contained in:
Rph :3
2025-04-16 23:23:17 +02:00
parent 7796045b13
commit 89f08ff580
11 changed files with 872 additions and 11 deletions

View File

@@ -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(())
}

View File

@@ -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(())
}
}

View File

@@ -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.");
}
});
});
});
}
}
}

View File

@@ -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
}