-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Support for the Godot Engine (#98)
This adds support for querying information and navigating the node tree in games built with the Godot Engine. This is initial version only supports games using Godot 4.2 without debug symbols and is missing many features such as accessing variables inside scripts. It is intentionally structured and documented in a way to match both the Godot documentation and the source code folder structure.
- Loading branch information
Showing
26 changed files
with
723 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
mod object; | ||
mod os; | ||
mod string; | ||
mod templates; | ||
|
||
pub use object::*; | ||
pub use os::*; | ||
pub use string::*; | ||
pub use templates::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod object; | ||
|
||
pub use object::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/object/object.h> | ||
use crate::{ | ||
game_engine::godot::{Ptr, VTable}, | ||
Error, Process, | ||
}; | ||
|
||
/// Base class for all other classes in the engine. | ||
/// | ||
/// [`Object`](https://docs.godotengine.org/en/4.2/classes/class_object.html) | ||
/// | ||
/// Check the [`Ptr<Object>`] documentation to see all the methods you can call | ||
/// on it. | ||
#[derive(Debug, Copy, Clone)] | ||
#[repr(transparent)] | ||
pub struct Object; | ||
|
||
impl Ptr<Object> { | ||
/// Returns a pointer to the object's virtual method table. | ||
pub fn get_vtable(self, process: &Process) -> Result<Ptr<VTable>, Error> { | ||
process.read(self.addr()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/os/main_loop.h> | ||
use crate::game_engine::godot::Object; | ||
|
||
/// Abstract base class for the game's main loop. | ||
/// | ||
/// [`MainLoop`](https://docs.godotengine.org/en/4.2/classes/class_mainloop.html) | ||
#[derive(Debug, Copy, Clone)] | ||
#[repr(transparent)] | ||
pub struct MainLoop; | ||
extends!(MainLoop: Object); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod main_loop; | ||
|
||
pub use main_loop::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
mod string_name; | ||
mod ustring; | ||
|
||
pub use string_name::*; | ||
pub use ustring::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/string/string_name.h> | ||
use core::mem::MaybeUninit; | ||
|
||
use arrayvec::ArrayVec; | ||
use bytemuck::{Pod, Zeroable}; | ||
|
||
use crate::{ | ||
game_engine::godot::{KnownSize, Ptr}, | ||
Address64, Error, Process, | ||
}; | ||
|
||
use super::String; | ||
|
||
/// A built-in type for unique strings. | ||
/// | ||
/// [`StringName`](https://docs.godotengine.org/en/4.2/classes/class_stringname.html) | ||
#[derive(Debug, Copy, Clone, Pod, Zeroable)] | ||
#[repr(transparent)] | ||
pub struct StringName(Ptr<StringNameData>); | ||
|
||
impl KnownSize for StringName {} | ||
|
||
#[derive(Debug, Copy, Clone, Pod, Zeroable)] | ||
#[repr(transparent)] | ||
struct StringNameData(Address64); | ||
|
||
impl StringName { | ||
/// Reads the string from the target process. | ||
pub fn read<const N: usize>(self, process: &Process) -> Result<String<N>, Error> { | ||
let cow_data: Address64 = self.0.read_at_offset(0x10, process)?; | ||
|
||
// Only on 4.2 or before. | ||
let len = process | ||
.read::<u32>(cow_data + -0x4)? | ||
.checked_sub(1) | ||
.ok_or(Error {})?; | ||
let mut buf = [MaybeUninit::uninit(); N]; | ||
let buf = buf.get_mut(..len as usize).ok_or(Error {})?; | ||
let buf = process.read_into_uninit_slice(cow_data, buf)?; | ||
|
||
let mut out = ArrayVec::new(); | ||
out.extend(buf.iter().copied()); | ||
|
||
Ok(String(out)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/string/ustring.h> | ||
use arrayvec::{ArrayString, ArrayVec}; | ||
|
||
/// A built-in type for strings. | ||
/// | ||
/// [`String`](https://docs.godotengine.org/en/4.2/classes/class_string.html) | ||
#[derive(Clone)] | ||
pub struct String<const N: usize>(pub(super) ArrayVec<u32, N>); | ||
|
||
impl<const N: usize> String<N> { | ||
/// Returns an iterator over the characters in this string. | ||
pub fn chars(&self) -> impl Iterator<Item = char> + '_ { | ||
self.0 | ||
.iter() | ||
.copied() | ||
.map(|c| char::from_u32(c).unwrap_or(char::REPLACEMENT_CHARACTER)) | ||
} | ||
|
||
/// Converts this string to an [`ArrayString`]. If the string is too long to | ||
/// fit in the array, the excess characters are truncated. | ||
pub fn to_array_string<const UTF8_SIZE: usize>(&self) -> ArrayString<UTF8_SIZE> { | ||
let mut buf = ArrayString::<UTF8_SIZE>::new(); | ||
for c in self.chars() { | ||
let _ = buf.try_push(c); | ||
} | ||
buf | ||
} | ||
|
||
/// Checks if this string matches the given string. | ||
pub fn matches_str(&self, text: &str) -> bool { | ||
self.chars().eq(text.chars()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/templates/hash_map.h> | ||
use core::{iter, mem::size_of}; | ||
|
||
use crate::{game_engine::godot::Ptr, Address64, Error, Process}; | ||
|
||
/// A type that we know the size of in the target process. | ||
pub trait KnownSize {} | ||
|
||
/// A hash map that maps keys to values. This is not publicly exposed as such in | ||
/// Godot, because it's a template class. The closest equivalent is the general | ||
/// [`Dictionary`](https://docs.godotengine.org/en/4.2/classes/class_dictionary.html). | ||
/// | ||
/// Check the [`Ptr`] documentation to see all the methods you can call on it. | ||
#[derive(Debug, Copy, Clone)] | ||
#[repr(transparent)] | ||
pub struct HashMap<K, V>(core::marker::PhantomData<(K, V)>); | ||
|
||
impl<K, V> Ptr<HashMap<K, V>> { | ||
/// Returns an iterator over the key-value pairs in this hash map. | ||
pub fn iter<'a>(&'a self, process: &'a Process) -> impl Iterator<Item = (Ptr<K>, Ptr<V>)> + 'a | ||
where | ||
K: KnownSize, | ||
{ | ||
let mut current: Address64 = self.read_at_offset(0x18, process).unwrap_or_default(); | ||
iter::from_fn(move || { | ||
if current.is_null() { | ||
return None; | ||
} | ||
let ret = ( | ||
Ptr::new(current + 0x10), | ||
Ptr::new(current + 0x10 + size_of::<K>() as u64), | ||
); | ||
current = process.read(current).ok()?; | ||
Some(ret) | ||
}) | ||
} | ||
|
||
/// Returns the number of elements in this hash map. | ||
pub fn size(self, process: &Process) -> Result<u32, Error> { | ||
self.read_at_offset(0x2C, process) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod hash_map; | ||
|
||
pub use hash_map::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//! This module is not Godot specific and instead provides generic utilities for | ||
//! working with processes written in C++. It could be moved outside at some | ||
//! point in the future. | ||
mod ptr; | ||
mod type_info; | ||
mod vtable; | ||
|
||
pub use ptr::*; | ||
pub use type_info::*; | ||
pub use vtable::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
use core::{any::type_name, fmt, marker::PhantomData, ops::Add}; | ||
|
||
use bytemuck::{CheckedBitPattern, Pod, Zeroable}; | ||
|
||
use crate::{Address64, Error, Process}; | ||
|
||
/// A pointer is an address in the target process that knows the type that it's | ||
/// targeting. | ||
#[repr(transparent)] | ||
pub struct Ptr<T>(Address64, PhantomData<fn() -> T>); | ||
|
||
impl<T> fmt::Debug for Ptr<T> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
write!(f, "{}*: {}", type_name::<T>(), self.0) | ||
} | ||
} | ||
|
||
impl<T> Copy for Ptr<T> {} | ||
|
||
impl<T> Clone for Ptr<T> { | ||
fn clone(&self) -> Self { | ||
*self | ||
} | ||
} | ||
|
||
// SAFETY: The type is transparent over an `Address64`, which is `Pod`. | ||
unsafe impl<T: 'static> Pod for Ptr<T> {} | ||
|
||
// SAFETY: The type is transparent over an `Address64`, which is `Zeroable`. | ||
unsafe impl<T> Zeroable for Ptr<T> {} | ||
|
||
impl<T> Ptr<T> { | ||
/// Creates a new pointer from the given address. | ||
pub fn new(addr: Address64) -> Self { | ||
Self(addr, PhantomData) | ||
} | ||
|
||
/// Checks whether the pointer is null. | ||
pub fn is_null(self) -> bool { | ||
self.0.is_null() | ||
} | ||
|
||
/// Reads the value that this pointer points to from the target process. | ||
pub fn deref(self, process: &Process) -> Result<T, Error> | ||
where | ||
T: CheckedBitPattern, | ||
{ | ||
process.read(self.0) | ||
} | ||
|
||
/// Reads the value that this pointer points to from the target process at | ||
/// the given offset. | ||
pub fn read_at_offset<U, O>(self, offset: O, process: &Process) -> Result<U, Error> | ||
where | ||
U: CheckedBitPattern, | ||
Address64: Add<O, Output = Address64>, | ||
{ | ||
process.read(self.0 + offset) | ||
} | ||
|
||
/// Casts this pointer to a pointer of a different type without any checks. | ||
pub fn unchecked_cast<U>(self) -> Ptr<U> { | ||
Ptr::new(self.0) | ||
} | ||
|
||
/// Returns the address that this pointer points to. | ||
pub fn addr(self) -> Address64 { | ||
self.0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
use crate::{string::ArrayCString, Address64, Error, Process}; | ||
|
||
use super::Ptr; | ||
|
||
/// The class `TypeInfo` holds implementation-specific information about a | ||
/// type, including the name of the type and means to compare two types for | ||
/// equality or collating order. This is the class returned by | ||
/// [`Ptr<VTable>::get_type_info`]. | ||
/// | ||
/// [`std::type_info`](https://en.cppreference.com/w/cpp/types/type_info) | ||
#[derive(Debug, Copy, Clone)] | ||
#[repr(transparent)] | ||
pub struct TypeInfo; | ||
|
||
impl Ptr<TypeInfo> { | ||
/// Returns a GCC/Clang mangled null-terminated character string containing | ||
/// the name of the type. No guarantees are given; in particular, the | ||
/// returned string can be identical for several types. | ||
/// | ||
/// [`std::type_info::name`](https://en.cppreference.com/w/cpp/types/type_info/name) | ||
pub fn get_mangled_name<const N: usize>( | ||
self, | ||
process: &Process, | ||
) -> Result<ArrayCString<N>, Error> { | ||
let name_ptr: Address64 = self.read_at_offset(0x8, process)?; | ||
process.read(name_ptr) | ||
} | ||
|
||
/// Checks if the mangled name of the type matches the given string. | ||
pub fn matches_mangled_name<const N: usize>( | ||
self, | ||
mangled_name: &[u8; N], | ||
process: &Process, | ||
) -> Result<bool, Error> { | ||
Ok(self.get_mangled_name::<N>(process)?.matches(mangled_name)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use crate::{Error, Process}; | ||
|
||
use super::{Ptr, TypeInfo}; | ||
|
||
/// A C++ virtual method table. | ||
/// | ||
/// This can be used to look up virtual functions and type information for the | ||
/// object. A pointer to a vtable is unique for each type, so comparing pointers | ||
/// is enough to check for type equality. | ||
/// | ||
/// [Wikipedia](https://en.wikipedia.org/wiki/Virtual_method_table) | ||
#[derive(Debug, Copy, Clone)] | ||
#[repr(transparent)] | ||
pub struct VTable; | ||
|
||
impl Ptr<VTable> { | ||
/// Queries information of a type. Used where the dynamic type of a | ||
/// polymorphic object must be known and for static type identification. | ||
/// | ||
/// [`typeid`](https://en.cppreference.com/w/cpp/language/typeid) | ||
pub fn get_type_info(self, process: &Process) -> Result<Ptr<TypeInfo>, Error> { | ||
self.read_at_offset(-8, process) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
//! Support for games using the Godot engine. | ||
//! | ||
//! The support is still very experimental. Currently only games using Godot 4.2 | ||
//! without any debug symbols are supported. | ||
//! | ||
//! The main entry point is [`SceneTree::locate`], which locates the | ||
//! [`SceneTree`] instance in the game's memory. From there you can find the | ||
//! root node and all its child nodes. | ||
//! | ||
//! # Example | ||
//! | ||
//! ```no_run | ||
//! # async fn example(process: asr::Process, main_module_address: asr::Address) { | ||
//! use asr::game_engine::godot::SceneTree; | ||
//! | ||
//! // We first locate the SceneTree instance. | ||
//! let scene_tree = SceneTree::wait_locate(&process, main_module_address).await; | ||
//! | ||
//! // We access the root node of the SceneTree. | ||
//! let root = scene_tree.wait_get_root(&process).await; | ||
//! | ||
//! // We print the tree of nodes starting from the root. | ||
//! asr::print_limited::<4096>(&root.print_tree::<64>(&process)); | ||
//! # } | ||
macro_rules! extends { | ||
($Sub:ident: $Base:ident) => { | ||
impl core::ops::Deref for crate::game_engine::godot::Ptr<$Sub> { | ||
type Target = crate::game_engine::godot::Ptr<$Base>; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
bytemuck::cast_ref(self) | ||
} | ||
} | ||
}; | ||
} | ||
|
||
mod core; | ||
mod scene; | ||
|
||
pub use core::*; | ||
pub use scene::*; | ||
|
||
mod cpp; | ||
pub use cpp::*; |
Oops, something went wrong.