From 74861cdfd82b1e267ad135848600fd96c047c5bd Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 15:20:52 +0300 Subject: [PATCH 01/16] init crate::ext::write mod --- src/ext/mod.rs | 1 + src/ext/write.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/ext/write.rs diff --git a/src/ext/mod.rs b/src/ext/mod.rs index 76def6c..9db0cc4 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -7,3 +7,4 @@ mod mapping; pub mod read; pub mod search; +pub mod write; diff --git a/src/ext/write.rs b/src/ext/write.rs new file mode 100644 index 0000000..6d3544f --- /dev/null +++ b/src/ext/write.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2026 Eray Erdin +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +pub trait WriteExt {} + +#[cfg(target_os = "linux")] +impl WriteExt for crate::process::Process {} + +pub enum WriteExtError {} + +pub type WriteExtResult = Result; From 6167df920e5b1af5243f6f91589ff6b44d7172ab Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 15:58:38 +0300 Subject: [PATCH 02/16] impl WriteExt::write_bytes --- src/ext/write.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/src/ext/write.rs b/src/ext/write.rs index 6d3544f..42d59d6 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -4,11 +4,112 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -pub trait WriteExt {} +use std::io::{self, Seek, Write}; + +pub trait WriteExt { + fn write_bytes(&self, address: usize, bytes: &[u8]) -> WriteExtResult<()>; +} #[cfg(target_os = "linux")] -impl WriteExt for crate::process::Process {} +impl WriteExt for crate::process::Process { + fn write_bytes(&self, address: usize, bytes: &[u8]) -> WriteExtResult<()> { + use nix::{sys::ptrace, unistd::Pid}; + + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + file.seek(io::SeekFrom::Start(address as u64))?; + file.write_all(bytes)?; + + ptrace::detach(Pid::from_raw(self.pid), None)?; + Ok(()) + } +} -pub enum WriteExtError {} +#[derive(Debug, Error)] +pub enum WriteExtError { + #[error("A *nix error occured. {0:?}")] + Nix(#[from] nix::Error), + #[error("An IO error occured. {0:?}")] + IO(#[from] io::Error), +} pub type WriteExtResult = Result; + +#[cfg(target_os = "linux")] +#[cfg(test)] +mod tests { + use std::{env, io::BufRead, process}; + + use super::*; + + struct DummyProcess { + child: process::Child, + pub pid: i32, + pub base_url: String, + } + + impl Drop for DummyProcess { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } + } + + #[fixture] + fn dummy_process() -> DummyProcess { + let cwd = env::current_dir().expect("Could not get the current working directory."); + let dummy_process_dir = cwd.join("dummy_process"); + + let build_status = process::Command::new("cargo") + .arg("build") + .current_dir(&dummy_process_dir) + .status() + .expect("Failed to run dummy_process compilation."); + assert!(build_status.success(), "Failed to build dummy_process"); + + let bin_path = dummy_process_dir.join("target/debug/dummy_process"); + let mut child = process::Command::new(bin_path) + .stdout(process::Stdio::piped()) + .spawn() + .expect("Could not spawn dummy_process."); + + let port = { + let stdout = child.stdout.as_mut().expect("Failed to get stdout."); + let reader = io::BufReader::new(stdout); + let port_line = reader + .lines() + .next() + .expect("Port line does not exist.") + .expect("Could not read port line of stdout."); + port_line + .trim() + .split(":") + .last() + .expect("Could not read port part from stdout.") + .parse::() + .expect("Could not parse port from stdout.") + }; + + let base_url = format!("http://127.0.0.1:{port}"); + + let pid = { + let body = reqwest::blocking::get(format!("{base_url}/pid")) + .expect("Could not request PID of dummy process."); + body.text() + .expect("Could not decode the charset of PID of dummy process.") + .parse::() + .expect("Could not parse PID of dummy process") + }; + + DummyProcess { + child, + pid, + base_url, + } + } +} From a5c16cfb3d9c5f6a56b45bc47dc29d2f54865f11 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:02:11 +0300 Subject: [PATCH 03/16] add byte_order logic to SearchExt::search_u8 --- src/ext/search.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ext/search.rs b/src/ext/search.rs index b42641b..5de1379 100644 --- a/src/ext/search.rs +++ b/src/ext/search.rs @@ -18,7 +18,7 @@ use crate::ext::mapping::MappingExtError; pub trait SearchExt { /// Search for a byte pattern and return memory addresses. fn search_bytes(&self, pattern: &[u8]) -> SearchExtResult>; - fn search_u8(&self, value: u8) -> SearchExtResult>; + fn search_u8(&self, value: u8, byte_order: Option) -> SearchExtResult>; fn search_u16(&self, value: u16, byte_order: Option) -> SearchExtResult>; fn search_u32(&self, value: u32, byte_order: Option) -> SearchExtResult>; #[cfg(target_pointer_width = "64")] @@ -149,8 +149,14 @@ impl SearchExt for crate::prelude::Process { Ok(addresses) } - fn search_u8(&self, value: u8) -> SearchExtResult> { - self.search_bytes(&[value]) + fn search_u8(&self, value: u8, byte_order: Option) -> SearchExtResult> { + self.search_bytes( + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) } fn search_u16(&self, value: u16, byte_order: Option) -> SearchExtResult> { @@ -344,7 +350,9 @@ mod linux_tests { fn test_search_u8(dummy_process: DummyProcess) { let process = Process::try_new(dummy_process.pid).expect("Could not get dummy process with id."); - let addresses = process.search_u8(2).expect("Could not search u8 value."); + let addresses = process + .search_u8(2, None) + .expect("Could not search u8 value."); let target_address: usize = { let response = From 46bb6c283492eb511cdf4fa97345fa3f29f52ff9 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:19:08 +0300 Subject: [PATCH 04/16] add is_write param to Process::mem_file --- src/ext/read.rs | 20 ++++++++++---------- src/ext/search.rs | 2 +- src/process.rs | 8 ++++++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ext/read.rs b/src/ext/read.rs index 6898164..ecfef55 100644 --- a/src/ext/read.rs +++ b/src/ext/read.rs @@ -39,7 +39,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 1]; @@ -56,7 +56,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 2]; @@ -77,7 +77,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 4]; file.read_exact(&mut buffer)?; @@ -98,7 +98,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 8]; @@ -119,7 +119,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 1]; @@ -140,7 +140,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 2]; @@ -161,7 +161,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 4]; @@ -183,7 +183,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 8]; @@ -204,7 +204,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 4]; @@ -226,7 +226,7 @@ impl ReadExt for crate::prelude::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; file.seek(io::SeekFrom::Start(address as u64))?; let mut buffer = [0u8; 8]; diff --git a/src/ext/search.rs b/src/ext/search.rs index 5de1379..2b9a0ee 100644 --- a/src/ext/search.rs +++ b/src/ext/search.rs @@ -47,7 +47,7 @@ impl SearchExt for crate::prelude::Process { } let mappings = self.mappings()?; - let mut file = self.mem_file()?; + let mut file = self.mem_file(false)?; const BUFFER_SIZE: usize = 8192; let mut buffer = [0u8; BUFFER_SIZE]; diff --git a/src/process.rs b/src/process.rs index 2979b4a..546c90c 100644 --- a/src/process.rs +++ b/src/process.rs @@ -34,8 +34,12 @@ impl Process { } #[cfg(target_os = "linux")] - pub(crate) fn mem_file(&self) -> io::Result { - fs::File::open(self.mem_path()) + pub(crate) fn mem_file(&self, is_write: bool) -> io::Result { + if is_write { + fs::OpenOptions::new().write(true).open(self.mem_path()) + } else { + fs::OpenOptions::new().read(true).open(self.mem_path()) + } } } From e877e1f055d912c1a10a5639c7316a7580e8c1ce Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:20:50 +0300 Subject: [PATCH 05/16] impl WriteExt::write_u8 for Process --- src/ext/write.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/ext/write.rs b/src/ext/write.rs index 42d59d6..11e2a98 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -6,8 +6,16 @@ use std::io::{self, Seek, Write}; +use crate::prelude::ByteOrder; + pub trait WriteExt { fn write_bytes(&self, address: usize, bytes: &[u8]) -> WriteExtResult<()>; + fn write_u8( + &self, + address: usize, + value: u8, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -21,13 +29,29 @@ impl WriteExt for crate::process::Process { let _ = ptrace::detach(Pid::from_raw(self.pid), None); } - let mut file = self.mem_file()?; + let mut file = self.mem_file(true)?; file.seek(io::SeekFrom::Start(address as u64))?; file.write_all(bytes)?; ptrace::detach(Pid::from_raw(self.pid), None)?; Ok(()) } + + fn write_u8( + &self, + address: usize, + value: u8, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -45,6 +69,8 @@ pub type WriteExtResult = Result; mod tests { use std::{env, io::BufRead, process}; + use crate::process::Process; + use super::*; struct DummyProcess { @@ -112,4 +138,34 @@ mod tests { base_url, } } + + #[rstest] + fn test_write_u8(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/u8val/address", dummy_process.base_url)) + .expect("Could not request u8val address."); + response + .text() + .expect("Could not decode the charset of u8val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_u8(address, 20, None) + .expect("Could not write onto u8val."); + let value: u8 = { + let response = reqwest::blocking::get(format!("{}/u8val", dummy_process.base_url)) + .expect("Could not request u8val value."); + response + .text() + .expect("Could not decode the charset of u8val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, 20); + } } From a38865c84ed379b45ad5e22318ee97cc596ca6b8 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:23:24 +0300 Subject: [PATCH 06/16] impl WriteExt::write_u16 --- src/ext/write.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index 11e2a98..b1e9e3d 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -16,6 +16,12 @@ pub trait WriteExt { value: u8, byte_order: Option, ) -> WriteExtResult<()>; + fn write_u16( + &self, + address: usize, + value: u16, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -52,6 +58,22 @@ impl WriteExt for crate::process::Process { }), ) } + + fn write_u16( + &self, + address: usize, + value: u16, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -168,4 +190,34 @@ mod tests { assert_eq!(value, 20); } + + #[rstest] + fn test_write_u16(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/u16val/address", dummy_process.base_url)) + .expect("Could not request u16val address."); + response + .text() + .expect("Could not decode the charset of u16val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_u16(address, 30, None) + .expect("Could not write onto u16val."); + let value: u16 = { + let response = reqwest::blocking::get(format!("{}/u16val", dummy_process.base_url)) + .expect("Could not request u16val value."); + response + .text() + .expect("Could not decode the charset of u16val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, 30); + } } From 152ec520ea0022950b4a3f230a1ab52bccb78db4 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:25:32 +0300 Subject: [PATCH 07/16] impl WriteExt::write_u32 --- src/ext/write.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index b1e9e3d..fb863ad 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -22,6 +22,12 @@ pub trait WriteExt { value: u16, byte_order: Option, ) -> WriteExtResult<()>; + fn write_u32( + &self, + address: usize, + value: u32, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -74,6 +80,22 @@ impl WriteExt for crate::process::Process { }), ) } + + fn write_u32( + &self, + address: usize, + value: u32, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -220,4 +242,34 @@ mod tests { assert_eq!(value, 30); } + + #[rstest] + fn test_write_u32(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/u32val/address", dummy_process.base_url)) + .expect("Could not request u32val address."); + response + .text() + .expect("Could not decode the charset of u32val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_u32(address, 40, None) + .expect("Could not write onto u32val."); + let value: u32 = { + let response = reqwest::blocking::get(format!("{}/u32val", dummy_process.base_url)) + .expect("Could not request u32val value."); + response + .text() + .expect("Could not decode the charset of u32val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, 40); + } } From 7c372732bf96e22c694f0fd8870bebc130a96da6 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:27:41 +0300 Subject: [PATCH 08/16] impl WriteExt::write_u64 --- src/ext/write.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index fb863ad..841ad1a 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -28,6 +28,13 @@ pub trait WriteExt { value: u32, byte_order: Option, ) -> WriteExtResult<()>; + #[cfg(target_pointer_width = "64")] + fn write_u64( + &self, + address: usize, + value: u64, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -96,6 +103,23 @@ impl WriteExt for crate::process::Process { }), ) } + + #[cfg(target_pointer_width = "64")] + fn write_u64( + &self, + address: usize, + value: u64, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -272,4 +296,35 @@ mod tests { assert_eq!(value, 40); } + + #[cfg(target_pointer_width = "64")] + #[rstest] + fn test_write_u64(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/u64val/address", dummy_process.base_url)) + .expect("Could not request u64val address."); + response + .text() + .expect("Could not decode the charset of u64val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_u64(address, 50, None) + .expect("Could not write onto u64val."); + let value: u64 = { + let response = reqwest::blocking::get(format!("{}/u64val", dummy_process.base_url)) + .expect("Could not request u64val value."); + response + .text() + .expect("Could not decode the charset of u64val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, 50); + } } From 54a05f369243f211c882ccb6a694c13d6caca1ad Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:33:44 +0300 Subject: [PATCH 09/16] impl WriteExt::write_i8 --- src/ext/write.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index 841ad1a..2ca3f09 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -35,6 +35,12 @@ pub trait WriteExt { value: u64, byte_order: Option, ) -> WriteExtResult<()>; + fn write_i8( + &self, + address: usize, + value: i8, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -120,6 +126,22 @@ impl WriteExt for crate::process::Process { }), ) } + + fn write_i8( + &self, + address: usize, + value: i8, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -327,4 +349,34 @@ mod tests { assert_eq!(value, 50); } + + #[rstest] + fn test_write_i8(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/i8val/address", dummy_process.base_url)) + .expect("Could not request i8val address."); + response + .text() + .expect("Could not decode the charset of i8val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_i8(address, -10, None) + .expect("Could not write onto i8val."); + let value: i8 = { + let response = reqwest::blocking::get(format!("{}/i8val", dummy_process.base_url)) + .expect("Could not request i8val value."); + response + .text() + .expect("Could not decode the charset of i8val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, -10); + } } From 8d1545a67129e4b318804d98b4bf6f9975e25787 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:35:17 +0300 Subject: [PATCH 10/16] impl WriteExt::write_i16 --- src/ext/write.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index 2ca3f09..8a39649 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -41,6 +41,12 @@ pub trait WriteExt { value: i8, byte_order: Option, ) -> WriteExtResult<()>; + fn write_i16( + &self, + address: usize, + value: i16, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -142,6 +148,22 @@ impl WriteExt for crate::process::Process { }), ) } + + fn write_i16( + &self, + address: usize, + value: i16, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -379,4 +401,34 @@ mod tests { assert_eq!(value, -10); } + + #[rstest] + fn test_write_i16(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/i16val/address", dummy_process.base_url)) + .expect("Could not request i16val address."); + response + .text() + .expect("Could not decode the charset of i16val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_i16(address, -20, None) + .expect("Could not write onto i16val."); + let value: i16 = { + let response = reqwest::blocking::get(format!("{}/i16val", dummy_process.base_url)) + .expect("Could not request i16val value."); + response + .text() + .expect("Could not decode the charset of i16val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, -20); + } } From f8e4ea73a9e6bb7d8519872217af5bb1a1d51d3f Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:36:33 +0300 Subject: [PATCH 11/16] impl WriteExt::write_i32 --- src/ext/write.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index 8a39649..7940988 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -47,6 +47,12 @@ pub trait WriteExt { value: i16, byte_order: Option, ) -> WriteExtResult<()>; + fn write_i32( + &self, + address: usize, + value: i32, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -164,6 +170,22 @@ impl WriteExt for crate::process::Process { }), ) } + + fn write_i32( + &self, + address: usize, + value: i32, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -431,4 +453,34 @@ mod tests { assert_eq!(value, -20); } + + #[rstest] + fn test_write_i32(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/i32val/address", dummy_process.base_url)) + .expect("Could not request i32val address."); + response + .text() + .expect("Could not decode the charset of i32val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_i32(address, -20, None) + .expect("Could not write onto i32val."); + let value: i32 = { + let response = reqwest::blocking::get(format!("{}/i32val", dummy_process.base_url)) + .expect("Could not request i32val value."); + response + .text() + .expect("Could not decode the charset of i32val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, -20); + } } From 8b63f360d43de779831c3708df39bc039842c615 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:36:49 +0300 Subject: [PATCH 12/16] change test value in test_write_i32 --- src/ext/write.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ext/write.rs b/src/ext/write.rs index 7940988..f42c9a2 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -469,7 +469,7 @@ mod tests { .expect("Could not parse address."); process - .write_i32(address, -20, None) + .write_i32(address, -30, None) .expect("Could not write onto i32val."); let value: i32 = { let response = reqwest::blocking::get(format!("{}/i32val", dummy_process.base_url)) @@ -481,6 +481,6 @@ mod tests { } .expect("Could not parse value."); - assert_eq!(value, -20); + assert_eq!(value, -30); } } From 826435d7558c77ccbe9d6e066c44cc9ad1b74df8 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:39:09 +0300 Subject: [PATCH 13/16] impl WriteExt::write_i64 --- src/ext/write.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index f42c9a2..f9ad3e9 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -53,6 +53,13 @@ pub trait WriteExt { value: i32, byte_order: Option, ) -> WriteExtResult<()>; + #[cfg(target_pointer_width = "64")] + fn write_i64( + &self, + address: usize, + value: i64, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -186,6 +193,23 @@ impl WriteExt for crate::process::Process { }), ) } + + #[cfg(target_pointer_width = "64")] + fn write_i64( + &self, + address: usize, + value: i64, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] @@ -483,4 +507,35 @@ mod tests { assert_eq!(value, -30); } + + #[cfg(target_pointer_width = "64")] + #[rstest] + fn test_write_i64(dummy_process: DummyProcess) { + let process = Process::try_new(dummy_process.pid).expect("Could not init Process."); + let address: usize = { + let response = + reqwest::blocking::get(format!("{}/i64val/address", dummy_process.base_url)) + .expect("Could not request i64val address."); + response + .text() + .expect("Could not decode the charset of i64val address.") + .parse() + } + .expect("Could not parse address."); + + process + .write_i64(address, -40, None) + .expect("Could not write onto i64val."); + let value: i64 = { + let response = reqwest::blocking::get(format!("{}/i64val", dummy_process.base_url)) + .expect("Could not request i64val value."); + response + .text() + .expect("Could not decode the charset of i64val value.") + .parse() + } + .expect("Could not parse value."); + + assert_eq!(value, -40); + } } From 000732e94c31a0eb49be1a8a35dd3b75f8619d3c Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:40:30 +0300 Subject: [PATCH 14/16] impl WriteExt::write_f32 and write_f64 --- src/ext/write.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/ext/write.rs b/src/ext/write.rs index f9ad3e9..8722bba 100644 --- a/src/ext/write.rs +++ b/src/ext/write.rs @@ -60,6 +60,19 @@ pub trait WriteExt { value: i64, byte_order: Option, ) -> WriteExtResult<()>; + fn write_f32( + &self, + address: usize, + value: f32, + byte_order: Option, + ) -> WriteExtResult<()>; + #[cfg(target_pointer_width = "64")] + fn write_f64( + &self, + address: usize, + value: f64, + byte_order: Option, + ) -> WriteExtResult<()>; } #[cfg(target_os = "linux")] @@ -210,6 +223,39 @@ impl WriteExt for crate::process::Process { }), ) } + + fn write_f32( + &self, + address: usize, + value: f32, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } + + #[cfg(target_pointer_width = "64")] + fn write_f64( + &self, + address: usize, + value: f64, + byte_order: Option, + ) -> WriteExtResult<()> { + self.write_bytes( + address, + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) + } } #[derive(Debug, Error)] From 6c29e73911514eba77063f44b2bb3e08b47742d3 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:41:35 +0300 Subject: [PATCH 15/16] export WriteExt --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 2a544ed..b357bd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,5 +20,6 @@ pub mod prelude { pub use crate::byte_order::ByteOrder; pub use crate::ext::read::{ReadExt, ReadExtError, ReadExtResult}; pub use crate::ext::search::{SearchExt, SearchExtError, SearchExtResult}; + pub use crate::ext::write::{WriteExt, WriteExtError, WriteExtResult}; pub use crate::process::{Process, ProcessError}; } From 58b8286ff632c3af740fd09a70b62cc977d98f79 Mon Sep 17 00:00:00 2001 From: Eray Erdin Date: Tue, 27 Jan 2026 16:42:16 +0300 Subject: [PATCH 16/16] add license to lib.rs --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b357bd2..dbeab79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,9 @@ +// Copyright (c) 2026 Eray Erdin +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + #[cfg(test)] #[macro_use] extern crate rstest;