This commit is contained in:
2024-11-21 20:32:34 +01:00
commit 72ec4d7188
6 changed files with 4414 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

3985
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

21
Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "rocknstone"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = "0.29.1"
egui = "0.29.1"
egui_plot = "0.29.0"
env_logger = "0.11.5"
windows = { version = "0.58.0", features = [
"Win32_System",
"Win32_System_Diagnostics",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Threading",
"Win32_System_Diagnostics_Debug",
"Win32_UI_Input_KeyboardAndMouse",
] }
[build-dependencies]
embed-manifest = "1.3.1"

13
build.rs Normal file
View File

@@ -0,0 +1,13 @@
// use embed_manifest::{embed_manifest, manifest::ExecutionLevel, new_manifest};
fn main() {
// if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
// embed_manifest(
// new_manifest("RocknStone")
// .requested_execution_level(ExecutionLevel::HighestAvailable)
// .ui_access(true),
// )
// .expect("unable to embed manifest file");
// }
// println!("cargo:rerun-if-changed=build.rs");
}

7
run.ps Normal file
View File

@@ -0,0 +1,7 @@
New-SelfSignedCertificate -DnsName jheuel@bla.de -Type CodeSigning -CertStoreLocation cert:\CurrentUser\My
Export-Certificate -Cert (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)[0] -FilePath code_signing.crt
Import-Certificate -FilePath .\code_signing.crt -Cert Cert:\CurrentUser\TrustedPublisher
Import-Certificate -FilePath .\code_signing.crt -Cert Cert:\CurrentUser\Root
Set-AuthenticodeSignature .\target\debug\rocknstone.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)
Set-AuthenticodeSignature .\target\release\rocknstone.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)

387
src/main.rs Normal file
View File

@@ -0,0 +1,387 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![feature(duration_millis_float)]
use eframe::egui;
use egui::Color32;
use egui_plot::{Legend, Line, Plot, PlotPoints};
use windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY;
use windows::Win32::UI::Input::KeyboardAndMouse::{
SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP,
};
use windows::Win32::{
Foundation::{CloseHandle, HANDLE},
System::{
Diagnostics::{
Debug::ReadProcessMemory,
ToolHelp::{
CreateToolhelp32Snapshot, Module32First, Process32First, Process32Next,
MODULEENTRY32, PROCESSENTRY32, TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32,
},
},
Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
},
};
use std::{
error::Error,
sync::{Arc, Mutex},
time::{Duration, SystemTime, UNIX_EPOCH},
};
const NUM_POINTS: usize = 500;
fn kick() {
simulate_keypress(0x4C);
}
fn simulate_keypress(keycode: u16) {
unsafe {
// Define key press (down)
let key_down = INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: VIRTUAL_KEY(keycode),
wScan: 0,
time: 0,
dwExtraInfo: 0,
..Default::default()
},
},
};
// Define key release (up)
let key_up = INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: VIRTUAL_KEY(keycode),
wScan: 0,
dwFlags: KEYEVENTF_KEYUP,
time: 0,
dwExtraInfo: 0,
},
},
};
let inputs = [key_down];
let input_count = inputs.len() as u32;
if SendInput(&inputs, std::mem::size_of::<INPUT>() as i32) != input_count {
eprintln!("Failed to send input events.");
}
let inputs = [key_up];
let input_count = inputs.len() as u32;
if SendInput(&inputs, std::mem::size_of::<INPUT>() as i32) != input_count {
eprintln!("Failed to send input events.");
}
}
}
fn find_process_id(target_name: &str) -> Option<u32> {
unsafe {
let snapshot = CreateToolhelp32Snapshot(
windows::Win32::System::Diagnostics::ToolHelp::TH32CS_SNAPPROCESS,
0,
)
.ok()?;
let mut entry = PROCESSENTRY32::default();
entry.dwSize = std::mem::size_of::<PROCESSENTRY32>() as u32;
if let Ok(_) = Process32First(snapshot, &mut entry) {
loop {
let name =
String::from_utf8(entry.szExeFile.iter().map(|&c| c as u8).collect()).unwrap();
if name.trim_end_matches(char::from(0)) == target_name {
return Some(entry.th32ProcessID);
}
entry.szExeFile = [0; 260]; // reset name buffer
if let Err(e) = Process32Next(snapshot, &mut entry) {
eprintln!("Error: {}", e);
break;
}
}
}
None
}
}
fn get_base_address(pid: u32) -> Option<usize> {
unsafe {
// Take a snapshot of all modules in the target process
let snapshot =
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid).ok()?;
let mut module_entry = MODULEENTRY32::default();
module_entry.dwSize = std::mem::size_of::<MODULEENTRY32>() as u32;
// Enumerate modules
if let Ok(_) = Module32First(snapshot, &mut module_entry) {
// The first module is usually the main module, so return its base address
let base_address = module_entry.modBaseAddr as usize;
CloseHandle(snapshot).unwrap();
return Some(base_address);
}
CloseHandle(snapshot).unwrap();
None
}
}
fn resolve_pointer(
process: HANDLE,
base_address: usize,
offsets: &[usize],
) -> Result<usize, Box<dyn Error>> {
let mut address = base_address;
let mut buffer = [0u8; std::mem::size_of::<usize>()];
for &offset in offsets {
unsafe {
let mut bytes_read = 0;
let result = ReadProcessMemory(
process,
address as *const _,
buffer.as_mut_ptr() as *mut _,
buffer.len(),
Some(&mut bytes_read),
);
if bytes_read != buffer.len() {
return Err(Box::from(format!(
"Only {} out of {} bytes were read.",
bytes_read,
buffer.len()
)));
}
result.unwrap();
address = usize::from_ne_bytes(buffer) + offset;
}
}
Ok(address)
}
fn read_memory(pid: u32, base_address: usize, offsets: &[usize]) -> Result<f64, String> {
unsafe {
let Ok(process) = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid)
else {
return Err("Failed to open process".into());
};
let final_address = match resolve_pointer(process, base_address, offsets) {
Ok(addr) => addr,
_ => {
CloseHandle(process).unwrap();
return Err("Failed to resolve pointer".into());
}
};
let mut buffer = [0u8; 4];
let mut bytes_read = 0;
if let Err(_) = ReadProcessMemory(
process,
final_address as *const _,
buffer.as_mut_ptr() as *mut _,
buffer.len(),
Some(&mut bytes_read),
) {
CloseHandle(process).unwrap();
return Err("Failed to read memory".into());
}
CloseHandle(process).unwrap();
Ok(f32::from_ne_bytes(buffer) as f64)
}
}
fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();
let state = Arc::new(Mutex::new(State {
x: 0.0,
x_min: -445.37,
x_max: 261.8,
history: Vec::new(),
slider: 42,
}));
{
let state = Arc::clone(&state);
let _handle = std::thread::spawn(move || {
let process_name = "FSD-Win64-Shipping.exe";
// let Some(pid) = find_process_id(process_name) else {
// return Err(Box::from("Failed to find process ID"));
// };
let pid = find_process_id(process_name).unwrap();
let base_address = get_base_address(pid).unwrap() + 0x06044FA0;
let mut offsets = [0x54, 0x270, 0x430, 0x320, 0x4E0];
offsets.reverse();
loop {
{
let mut state = state.lock().unwrap();
match read_memory(pid, base_address, &offsets) {
Ok(f) => {
state.x = f;
if f < state.x_min {
state.x_min = f;
println!("min: {}", state.x_min);
}
if f > state.x_max {
state.x_max = f;
println!("max: {}", state.x_max);
}
let start = SystemTime::now();
let since_the_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let t = since_the_epoch.as_millis_f64();
state.history.push((t, f as f64));
state.slider = (f - state.x_min) as u32;
if state.history.len() > NUM_POINTS {
state.history.remove(0);
}
kick();
}
Err(e) => eprintln!("Error: {}", e),
};
}
std::thread::sleep(Duration::from_millis(50));
}
});
}
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
..Default::default()
};
eframe::run_native(
"Rock n Stone!",
options,
Box::new(|_cc| {
Ok(Box::new(MyApp {
state: Arc::clone(&state),
}))
}),
)
.unwrap();
Ok(())
}
struct State {
x: f64,
x_min: f64,
x_max: f64,
history: Vec<(f64, f64)>,
slider: u32,
}
struct MyApp {
state: Arc<Mutex<State>>,
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let data_points = NUM_POINTS;
let head_height = egui::TopBottomPanel::top("my_panel").show(ctx, |ui| {
let rect = ui.vertical_centered(|ui| ui.heading("Rock n Stone!").rect);
let title_height = rect.inner.height();
let rect = ui.vertical_centered(|ui| {
let width = ctx.input(|i| i.viewport().outer_rect).unwrap().width() - 32.;
ui.style_mut().spacing.slider_width = width;
let state = self.state.lock().unwrap();
let slider = state.slider;
let mut slider = slider;
let x_max = state.x_max;
let x_min = state.x_min;
ui.add(egui::Slider::new(&mut slider, 0..=(x_max - x_min) as u32).show_value(false))
.rect
});
let slider_height = rect.inner.height();
title_height + slider_height
});
let plot_height =
((ctx.input(|i| i.viewport().outer_rect).unwrap().height()) - head_height.inner) / 2.
- 24.;
egui::TopBottomPanel::top("my_panel")
.exact_height(plot_height)
.show(ctx, |ui| {
let positions: Vec<_> = {
let state = self.state.lock().unwrap();
let x_max = state.x_max;
let x_min = state.x_min;
state
.history
.iter()
.map(|&(t, v)| [t, v as f64 - (x_min + x_max) / 2.])
.rev()
.take(data_points)
.collect()
};
let my_plot = Plot::new("Plot").legend(Legend::default());
my_plot.show(ui, |plot_ui| {
// plot_ui.points(Points::new(PlotPoints::from(positions.clone())).name("Position"));
plot_ui.line(Line::new(PlotPoints::from(positions.clone())).name("Position"));
});
});
egui::TopBottomPanel::top("my_panel2")
.exact_height(plot_height)
.show(ctx, |ui| {
// egui::CentralPanel::default().show(ctx, |ui| {
let positions: Vec<_> = {
let state = self.state.lock().unwrap();
state
.history
.iter()
.rev()
.take(data_points)
.cloned()
.collect()
};
// // save to file
// let mut file = std::fs::File::create("data.txt").unwrap();
// let s: String = positions.iter().map(|(t, v)| format!("{},{}", t, v)).collect();
// file.write_all(s.as_bytes()).unwrap();
let positions: Vec<_> = positions.iter().rev().take(data_points).collect();
let speed: Vec<[f64; 2]> = positions
.windows(2)
.map(|w| {
[
(w[0].0 + w[1].0) / 2.,
(w[1].1 - w[0].1) / (w[1].0 - w[0].0),
]
})
.filter(|v| v[1].abs() > 0.3)
.collect();
let speed: Vec<_> = speed
.iter()
.zip(speed.iter().skip(1))
.filter_map(|(a, b)| if (b[1] * a[1]) > 0.0 { Some(*b) } else { None })
.collect();
let speed: Vec<_> = speed
.iter()
.skip(1)
.zip(speed.iter())
.filter_map(|(a, b)| if (b[1] * a[1]) > 0.0 { Some(*b) } else { None })
.collect();
let my_plot = Plot::new("Plot").legend(Legend::default());
my_plot.show(ui, |plot_ui| {
// plot_ui.points(Points::new(PlotPoints::from(speed.clone())).color(Color32::ORANGE).name("Speed"));
plot_ui.line(
Line::new(PlotPoints::from(speed))
.color(Color32::ORANGE)
.name("Speed"),
);
});
});
egui::CentralPanel::default().show(ctx, |_ui| {});
ctx.request_repaint();
}
}