acquire key
This commit is contained in:
70
src/app_logic.rs
Normal file
70
src/app_logic.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use std::{sync::Arc, thread, time::Duration};
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use egui::{mutex::Mutex};
|
||||
|
||||
use crate::omori_locator;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum UiState {
|
||||
Loading,
|
||||
KeyRequired(String)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UiStateHolder {
|
||||
pub state: Arc<Mutex<UiState>>
|
||||
}
|
||||
|
||||
pub enum UiEvent {
|
||||
SetKey(Vec<u8>)
|
||||
}
|
||||
|
||||
pub struct AppThread {
|
||||
ui_state: UiStateHolder,
|
||||
context: egui::Context,
|
||||
ui_event_channel: Receiver<UiEvent>,
|
||||
decryption_key: Vec<u8>
|
||||
}
|
||||
|
||||
impl AppThread {
|
||||
fn commit(&self, s: UiState) {
|
||||
let mut state = self.ui_state.state.lock();
|
||||
*state = s;
|
||||
}
|
||||
|
||||
fn get_key(&mut self, reason: String) {
|
||||
self.commit(UiState::KeyRequired(reason));
|
||||
loop {
|
||||
match self.ui_event_channel.recv().expect("The UI event channel was closed.") {
|
||||
UiEvent::SetKey(key) => {
|
||||
self.decryption_key = key;
|
||||
break
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.commit(UiState::Loading);
|
||||
}
|
||||
|
||||
pub fn create(state_holder: &UiStateHolder, context: &egui::Context, ui_event_channel: &Receiver<UiEvent>) -> AppThread {
|
||||
AppThread {
|
||||
ui_state: state_holder.clone(),
|
||||
context: context.clone(),
|
||||
ui_event_channel: ui_event_channel.clone(),
|
||||
decryption_key: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main(&mut self) {
|
||||
self.commit(UiState::Loading);
|
||||
|
||||
match omori_locator::get_omori_key() {
|
||||
Ok(key) => self.decryption_key = key,
|
||||
Err(reason) => self.get_key(format!("{:?}", reason)),
|
||||
}
|
||||
|
||||
println!("{:?}", self.decryption_key);
|
||||
}
|
||||
}
|
||||
9
src/config.rs
Normal file
9
src/config.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
struct Config {
|
||||
key: Option<Vec<u8>>
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self { key: Default::default() }
|
||||
}
|
||||
}
|
||||
1
src/keyhash
Normal file
1
src/keyhash
Normal file
@@ -0,0 +1 @@
|
||||
<EFBFBD><EFBFBD>
|
||||
109
src/main.rs
109
src/main.rs
@@ -1,10 +1,16 @@
|
||||
mod omori_locator;
|
||||
mod app_logic;
|
||||
mod config;
|
||||
|
||||
use std::{thread, time::Duration};
|
||||
use std::{sync::Arc, thread, time::Duration};
|
||||
|
||||
use app_logic::{AppThread, UiEvent, UiState, UiStateHolder};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use eframe::egui;
|
||||
use egui::{RichText, ThemePreference};
|
||||
use omori_locator::get_omori_path;
|
||||
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 sha2::Digest;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
env_logger::init();
|
||||
@@ -15,13 +21,22 @@ fn main() -> anyhow::Result<()> {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let app_state = UiStateHolder {
|
||||
state: Arc::new(Mutex::new(UiState::Loading))
|
||||
};
|
||||
|
||||
let (sender, receiver): (Sender<UiEvent>, Receiver<UiEvent>) = crossbeam_channel::unbounded();
|
||||
|
||||
println!("{:?}", get_omori_path().unwrap());
|
||||
|
||||
eframe::run_native("TundleBool", options, Box::new(|cc| {
|
||||
cc.egui_ctx.set_theme(ThemePreference::System);
|
||||
|
||||
let mut app_thread = AppThread::create(&app_state, &cc.egui_ctx, &receiver);
|
||||
|
||||
Ok(Box::<Application>::default())
|
||||
thread::spawn(move || {
|
||||
app_thread.main();
|
||||
});
|
||||
|
||||
Ok(Box::<Application>::new(Application::create(sender, app_state)))
|
||||
})).unwrap();
|
||||
|
||||
Ok(())
|
||||
@@ -29,32 +44,78 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
|
||||
struct Application {
|
||||
updates: u32
|
||||
state: UiStateHolder,
|
||||
sender: Sender<UiEvent>,
|
||||
key_input: String
|
||||
}
|
||||
|
||||
impl Default for Application {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
updates: 0
|
||||
impl Application {
|
||||
fn create(sender: Sender<UiEvent>, state: UiStateHolder) -> Application {
|
||||
Application {
|
||||
sender,
|
||||
state,
|
||||
key_input: "".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GAME_KEY_HASH: &[u8; 32] = include_bytes!("keyhash");
|
||||
|
||||
impl eframe::App for Application {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
ctx.request_repaint();
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new("Welcome to BundleTool").size(32.0));
|
||||
ui.label(RichText::new(format!("{}", self.updates)).size(32.0));
|
||||
});
|
||||
self.updates += 1;
|
||||
ui.separator();
|
||||
egui::ScrollArea::vertical().max_width(300.0).auto_shrink(false).show(ui, |ui| {
|
||||
for i in 1..3000 {
|
||||
ui.add(egui::Label::new("Meow"));
|
||||
let state = self.state.state.lock();
|
||||
let state = state.clone();
|
||||
|
||||
match state {
|
||||
UiState::Loading => {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
center_vertical(ui,|ui| {
|
||||
ui.label(RichText::new("Loading").size(32.0));
|
||||
});
|
||||
});
|
||||
},
|
||||
UiState::KeyRequired(reason) => {
|
||||
let mut is_valid = true;
|
||||
let hash = sha2::Sha256::digest(&self.key_input.as_bytes());
|
||||
for i in 0..32 {
|
||||
if hash[i] != GAME_KEY_HASH[i] {
|
||||
is_valid = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::top("key_prompt_title_bar").show(ctx, |ui| {
|
||||
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||
ui.label(RichText::new("Decryption key required").size(32.0));
|
||||
ui.label(RichText::new(format!("Reason: {reason}")).size(24.0));
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
top_horizontal(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Decryption key: ");
|
||||
ui.text_edit_singleline(&mut self.key_input);
|
||||
if is_valid {
|
||||
ui.label("Valid");
|
||||
} else {
|
||||
ui.label(RichText::new("Invalid").color(ui.visuals().error_fg_color));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::bottom("key_prompt_button_bar").show(ctx, |ui| {
|
||||
if !is_valid {
|
||||
ui.disable();
|
||||
}
|
||||
|
||||
ui.with_layout(Layout::top_down(Align::Max), |ui| {
|
||||
if ui.button("Continue").clicked() {
|
||||
self.sender.send(UiEvent::SetKey(self.key_input.as_bytes().to_vec())).expect("Failed to send");
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use regex::bytes::Regex;
|
||||
use sha2::Digest;
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
@@ -76,4 +78,38 @@ pub fn get_omori_path() -> anyhow::Result<PathBuf> {
|
||||
|
||||
|
||||
omori_path.with_context(|| "Failed to find omori")
|
||||
}
|
||||
|
||||
const GAME_KEY_HASH: &[u8; 32] = include_bytes!("keyhash");
|
||||
|
||||
pub fn get_omori_key() -> anyhow::Result<Vec<u8>> {
|
||||
let mut base: PathBuf = get_steam_root()?;
|
||||
|
||||
base.push("appcache/appinfo.vdf");
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let mut fd = File::open(base)?;
|
||||
fd.read_to_end(&mut buffer)?;
|
||||
drop(fd);
|
||||
|
||||
let re = Regex::new(r"--([0-9a-f]{32})")?;
|
||||
|
||||
for (_, [key]) in re.captures_iter(&buffer).map(|c| c.extract()) {
|
||||
let hash = sha2::Sha256::digest(key);
|
||||
|
||||
let mut equal = true;
|
||||
for i in 0..32 {
|
||||
if hash[i] != GAME_KEY_HASH[i] {
|
||||
equal = false;
|
||||
}
|
||||
}
|
||||
|
||||
if equal {
|
||||
return Ok(key.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Err(anyhow::anyhow!("Couldn't find any valid decryption key for OMORI in your Steam installation."))
|
||||
}
|
||||
Reference in New Issue
Block a user