Proc API
The Proc API lets AngelScript access and edit memory of external processes using a lightweight handle type proc_t.
🔑 Process Handles
Referencing Processes
proc_t ref_process(uint pid)
proc_t ref_process(const string &in name)pid– Windows process ID.name– Executable name ("notepad.exe","cs2.exe", etc).
Returns a proc_t handle, or 0 if the process could not be opened.
Releasing Handles
void proc_t::deref()Decrements the internal reference and clears the handle.
After calling deref(), the handle becomes invalid (further calls are no-ops).
You must also call deref() if proc_t::alive() returns false.
🧬 Process Info
base_address()– Image base (module base) of the process.peb()– Address of the process’ PEB.pid()– Process ID.alive()- Determines whether the process is currently alive and running.
📖 Scalar Reads
All addresses are virtual addresses in the target process.
Unsigned Integers
Reads unsigned integer values at addr.
Signed Integers
Reads signed integer values at addr.
Floating-Point
Reads float / double from addr.
✏️ Scalar Writes
Unsigned Integers
Writes unsigned integer values.
Returns true on success.
Signed Integers
Writes signed integer values.
Floating-Point
Writes float / double.
🔤 Strings
All string helpers assume null-terminated memory on the remote side (for reads) and write a terminating null when writing.
Reading Strings
rs– Reads an ANSI/UTF-8 style string fromaddr.rws– Reads a UTF-16 wide string, converts to UTF-8string.
max_chars is a hard cap to avoid walking off invalid memory.
Writing Strings
ws– Writestextas ANSI/UTF-8 bytes plus trailing\0.wws– Convertstextto UTF-16 and writes wide string plus trailingL'\0'.
Returns true on success.
📦 Raw Memory (byte arrays)
Uses array<uint8> for bulk reads/writes.
rvm– Readssizebytes starting ataddrintoout_buf.out_bufis resized tosize.wvm– Writes bytes fromin_buftoaddr. Returnstrueif all bytes were written.
🧮 SIMD Helpers
Convenience wrappers around 16/32/64-byte vectors (__m128/__m256/__m512).
All data is exposed as raw bytes.
r128/r256/r512– Read 16/32/64 bytes into the output array (array is resized).w128/w256/w512– Write 16/32/64 bytes from the input array. Returnsfalseif the array is too small or the write fails.
Thread Environment Blocks (TEB)
array<uint64>@ proc_t::get_all_tebs() const
array<uint64>@ proc_t::get_all_tebs() constReturns a list of TEB (Thread Environment Block) addresses for all threads currently discovered in the target process.
Signature
Returns
array<uint64>—{ teb1, teb2, ... }Each entry is a virtual address of a TEB in the target process.
Returns null if no threads are found or enumeration fails.
📦 Modules & Pattern Scans
Module Lookup
Looks up a module by name (e.g. "notepad.exe", "client.dll"):
On success:
module_base– Base address of the module.module_size– Size in bytes.Returns
true.
Returns
falseif the module is not found.
Code Pattern Search
Scans [search_start, search_start + search_size) for a textual pattern:
signature– Pattern string, e.g."48 8B ?? ?? ?? 89"(exact format is the same as used internally by your pattern scanner).Returns the address of the first match, or
0if not found.
🔍 Example: Scanning Notepad
Process Struct Helpers
These helpers let you describe a C-style structure in a dictionary and read it.
📦 Struct Descriptor (common concept)
A struct descriptor is a dictionary where each entry:
Key = field name (string)
Value = another
dictionarywith:"offset"– byte offset from struct base (int64)"type"– field type string ("u16","i32","f32", etc.)(optional)
"max_chars"for string fields
Example:
Supported field types
"u8"
1
unsigned 8-bit
"u16"
2
unsigned 16-bit
"u32"
4
unsigned 32-bit
"u64"
8
unsigned 64-bit
"i8"
1
signed 8-bit
"i16"
2
signed 16-bit
"i32"
4
signed 32-bit
"i64"
8
signed 64-bit
"f32"
4
32-bit float
"f64"
8
64-bit double
"string"
var
UTF-8 C-string in target
"wstring"
var
UTF-16 string → UTF-8
For "string" / "wstring", you can add "max_chars" to limit how many characters are read (default ~256, clamped to 1..8192).
📘 bool proc_t::read_struct(uint64 addr, dictionary &out result, const dictionary &in desc)
bool proc_t::read_struct(uint64 addr, dictionary &out result, const dictionary &in desc)Reads one structure instance from the target process into an existing dictionary.
Signature
Parameters
addrAbsolute address of the struct in the remote process.resultDictionary that will be filled with the decoded fields. (Existing contents are cleared before filling.)descStruct descriptordictionarydescribed above.
Return
trueon successfalseon failure (invalid process handle, invalid descriptor, etc.)
Stored value types
To keep things simple (and consistent with your JSON API):
All integer fields are stored as
int64All float fields are stored as
doubleString fields are stored as script
string
So you can safely read with:
You can also read into uint64 etc. – the dictionary will convert as needed.
Example – Read one player struct
📘 bool proc_t::read_struct_array(uint64 base, uint count, uint size, array<dictionary>@ &out result, const dictionary &in desc)
bool proc_t::read_struct_array(uint64 base, uint count, uint size, array<dictionary>@ &out result, const dictionary &in desc)Reads an array of structures into an array<dictionary>.
Signature
Parameters
baseAddress of the first element.countNumber of elements to read.sizeSize in bytes of each element in the array (sizeof(struct)).resultarray<dictionary>that will be resized tocount. Each element will be filled with that struct’s data. If an element isnullinternally, it is created.descSame struct descriptor as forread_struct.
Return
trueon successfalseif the process is invalid,count/sizeis 0, etc.
Example – Player list
🧪 Full Example Script – Read IMAGE_DOS_HEADER from notepad.exe
IMAGE_DOS_HEADER from notepad.exeThis is a complete AngelScript file you can drop into your environment. It:
Builds a DOS header descriptor
Uses
proc_t.read_structto read it fromnotepad.exePrints all key fields and the computed NT header address
Virtual Memory Allocation
These functions let you allocate and manage RWX memory inside the target process. They are intended for advanced use; you are fully responsible for tracking and freeing any allocations you create.
🔴 Important Memory Management Notes (MUST READ!)
These points are critical for using alloc_vm safely:
Control Flow Guard If the target process has Control Flow Guard (CFG) enabled and you intend to execute code from this region, CFG may block all jumps and calls into the allocated memory. To avoid this, CFG must be fully disabled for the target process. Be aware that disabling CFG reduces the security of the device.
Allocations are not automatically freed. Every allocation you create must be manually released using
free_vm(). If you lose track of an allocation, the memory will remain reserved until the target process exits.Allocations persist for the lifetime of the target process. Memory created with
alloc_vmis automatically released only when the target process itself closes.Closing the Perception overlay will clear all allocations. When the overlay is closed, Perception will clean up every RWX allocation for all processes currently referenced. If you need an allocation to stay alive, do not close the overlay while that memory is being used.
Repeated unfreed allocations can exhaust physical memory. If you repeatedly allocate memory without freeing it, these leaks will accumulate and can eventually consume all available physical RAM, potentially degrading system performance or causing instability. If you are writing an injector make sure your inject frees the memory
If you are writing an injector, you must free allocated memory. Any injector, loader, or external tool that allocates memory through
alloc_vmis responsible for freeing it once the memory is no longer needed. Failing to do so will leave permanent physical memory leaks until the target process exits.
Functions
uint64 proc_t::alloc_vm(uint size)
Allocates a block of read–write–execute (RWX) memory in the target process and returns its base address.
Parameters
size— Size of the allocation in bytes.
Returns
Base address of the allocated region on success.
0on failure.
Notes
The allocated region is not associated with any module and is not discoverable through higher-level helpers (e.g. module lookup, pattern scan helpers, etc.). You must store and manage the returned address yourself.
The memory is RWX. If the target process uses Control Flow Guard (CFG) and you intend to execute from this region, CFG may block jumps/calls into it unless the target process allows it.
bool proc_t::free_vm(uint64 address)
Frees a region previously allocated with alloc_vm.
Parameters
address— Base address previously returned byalloc_vm.
Returns
trueif the region was successfully freed.falseif the address is invalid or the free fails.
🔗 Export Lookup – get_proc_address
get_proc_addressResolves the address of an exported symbol inside a module in the target process.
module_baseBase address of the module inside the target process. Typically obtained from:proc_t::base_address(), orproc_t::get_module("module_name.dll", base, size).
export_nameName of the exported function or symbol, e.g."Sleep","CreateFileW".
Returns
Absolute virtual address of the exported symbol in the target process.
0if the export is not found or the arguments are invalid.
Notes
This walks the module’s export table only – it does not search imports or runtime-resolved addresses.
The address is directly usable for:
Call stubs / shellcode in the remote process.
Manual thunking (e.g. “jmp [resolved_addr]” from your injected code).
You are responsible for ensuring the returned address is valid / executable before calling into it.
Example
🧷 Import Table (IAT) Slot Lookup – get_import_rdata_address
get_import_rdata_addressResolves the address of the import table entry (IAT slot) for a given imported function inside a module.
This does not return the function’s address directly – it returns the address of the pointer stored in .rdata / IAT. That pointer can then be read or patched (for IAT hooks, trampolines, etc.).
module_baseBase address of the module whose imports you want to inspect/patch (usually the main EXE or a specific DLL), obtained frombase_address()orget_module(...).import_nameName of the imported function as it appears in the import table, e.g."Sleep","MessageBoxW".
Returns
Address of the IAT entry (pointer-sized slot) for the requested import.
0if the import cannot be found or the arguments are invalid.
How to use
Read current target of the import:
Install a basic IAT hook (patch the pointer):
Notes
This only sees imports that are present in the module’s static import table.
Imports resolved manually at runtime (e.g. via
GetProcAddressinto custom memory) will not show up here.Ideal for:
Classic IAT hooks (redirecting calls to your own stub).
Finding the call site used by a module to invoke a given API, so your shellcode can jump through the same IAT entry.
Pointer Array Helpers
array<uint64>@ proc_t::read_pointer_array(uint64 base, uint count, int offset_delta) const
Reads an array of 64-bit pointers from the target process.
This is a convenience helper for common layouts like:
pointer lists (
T**, vtable arrays, entity pointer arrays)“pointer + fixed offset” patterns
Parameters
base— Address of the first pointer (each element isuint64, so step = 8 bytes).count— Number of pointers to read.offset_delta— Value added to each pointer after reading (can be negative).
Return
Returns
array<uint64>@containingcountelements.Returns
nullifbaseis invalid,countis 0, or the process handle is invalid.
Example
Virtual Memory Analysis
These APIs allow querying and iterating virtual memory regions.
bool proc_t::virtual_query(uint64 address, uint64 &out region_start, uint64 &out region_size, uint32 &out protection, bool &out heap_likely) const
Queries the memory region that contains address.
Parameters
address— Any virtual address inside the target region.region_start— Region start address (output).region_size— Region size in bytes (output).protection— Region protection flags (output).heap_likely— True if the region is likely heap/alloc-like (output).
Returns
trueon successfalseif the address is not mapped or cannot be queried
void proc_t::get_vad_snapshot(bool heap_likely_only, array<dictionary>@ &out regions) const
Builds a snapshot of virtual memory regions.
When
heap_likely_only == true, only heap-like regions are returned.When
heap_likely_only == false, all regions are returned.
regions output format:
regions[i]is adictionarywith:"start"(uint64)"size"(uint64)"end"(uint64)"protection"(uint32)"heap_likely"(bool)
Memory Scan Helpers
These helpers scan memory by iterating the VAD snapshot.
All scan functions output a list of matching addresses to result.
void proc_t::scan_bytes(const array<uint8> &in pattern, array<uint64> &out result, bool heap_likely_only) const
Scans for an exact byte sequence (pattern).
void proc_t::scan_all_bytes(array<uint64> &out result, bool heap_likely_only) const
Scans for all readable regions and returns their base addresses (useful for building your own multi-pass scanners / range filters).
void proc_t::scan_u32(uint32 value, array<uint64> &out result, bool heap_likely_only) const
Scans for a 32-bit unsigned value.
void proc_t::scan_u64(uint64 value, array<uint64> &out result, bool heap_likely_only) const
Scans for a 64-bit unsigned value.
void proc_t::scan_all_u32(array<uint64> &out result, bool heap_likely_only) const
Collects candidate addresses for 32-bit scanning across regions (fast pre-pass for multi-scan workflows).
void proc_t::scan_all_u64(array<uint64> &out result, bool heap_likely_only) const
Collects candidate addresses for 64-bit scanning across regions (fast pre-pass for multi-scan workflows).
Last updated