init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
3985
Cargo.lock
generated
Normal file
3985
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal 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
13
build.rs
Normal 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
7
run.ps
Normal 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
387
src/main.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user