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