Skip to content

Commit

Permalink
Allow querying the scripts of nodes in Godot (#99)
Browse files Browse the repository at this point in the history
This allows you to get access to the underlying values stored in the
scripts, which likely contain a lot of important information for the
auto splitter.
  • Loading branch information
CryZe authored Jul 5, 2024
1 parent 340daa6 commit 83f7de0
Show file tree
Hide file tree
Showing 40 changed files with 1,282 additions and 73 deletions.
42 changes: 16 additions & 26 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,21 @@ on:

jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
label:
- WebAssembly Unknown
- WebAssembly WASI

include:
- label: WebAssembly Unknown
target: wasm32-unknown-unknown
os: ubuntu-latest
install_target: true
- label: WebAssembly WASI
target: wasm32-wasi
os: ubuntu-latest
install_target: true

target: [wasm32-unknown-unknown, wasm32-wasip1]
toolchain: [stable, nightly]
steps:
- name: Checkout Commit
uses: actions/checkout@v4

- name: Install Rust
uses: hecrj/setup-rust-action@v2
with:
rust-version: ${{ matrix.toolchain || 'stable' }}

- name: Install Target
if: matrix.install_target != ''
run: rustup target add ${{ matrix.target }}
targets: ${{ matrix.target }}
rust-version: ${{ matrix.toolchain }}

- name: Build (No Default Features)
run: |
Expand All @@ -53,14 +38,19 @@ jobs:
run: |
cargo build --all-features --target ${{ matrix.target }}
- name: Test (Target, All Features)
run: |
cargo test --all-features
test:
name: Test (Host)
runs-on: ubuntu-latest
steps:
- name: Checkout Commit
uses: actions/checkout@v4

# Test on the host to also run the doc tests
- name: Test (Host, All Features)
- name: Install Rust
uses: hecrj/setup-rust-action@v2

- name: Test (All Features)
run: |
cargo test --target x86_64-unknown-linux-gnu --all-features
cargo test --all-features
clippy:
name: Check clippy lints
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ jobs:
with:
components: rust-docs
rust-version: nightly
targets: wasm32-wasi
targets: wasm32-wasip1

- name: Build docs
run: RUSTDOCFLAGS="--cfg doc_cfg" cargo doc --all-features --target wasm32-wasi
run: RUSTDOCFLAGS="--cfg doc_cfg" cargo doc --all-features --target wasm32-wasip1

- name: Setup Pages
uses: actions/configure-pages@v3
Expand All @@ -61,12 +61,12 @@ jobs:
--exclude=.github \
.
env:
INPUT_PATH: target/wasm32-wasi/doc
INPUT_PATH: target/wasm32-wasip1/doc

- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: target/wasm32-wasi/doc
path: target/wasm32-wasip1/doc

- name: Deploy to GitHub Pages
id: deployment
Expand Down
2 changes: 2 additions & 0 deletions src/game_engine/godot/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ mod object;
mod os;
mod string;
mod templates;
mod variant;

pub use object::*;
pub use os::*;
pub use string::*;
pub use templates::*;
pub use variant::*;
4 changes: 4 additions & 0 deletions src/game_engine/godot/core/object/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mod object;
mod script_instance;
mod script_language;

pub use object::*;
pub use script_instance::*;
pub use script_language::*;
69 changes: 67 additions & 2 deletions src/game_engine/godot/core/object/object.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,62 @@
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/object/object.h>
use crate::{
game_engine::godot::{Ptr, VTable},
game_engine::godot::{Ptr, VTable, VariantType},
Error, Process,
};

use super::ScriptInstance;

#[allow(unused)]
mod offsets {
// *const VTable
pub const VTABLE_PTR: u64 = 0x0;
// *const ObjectGDExtension
pub const EXTENSION: u64 = 0x8;
// GDExtensionClassInstancePtr
pub const EXTENSION_INSTANCE: u64 = 0x10;
// HashMap<StringName, SignalData>
pub const SIGNAL_MAP: u64 = 0x18;
// List<Connection>
pub const CONNECTIONS: u64 = 0x48;
// bool
pub const BLOCK_SIGNALS: u64 = 0x50;
// i32
pub const PREDELETE_OK: u64 = 0x54;
// ObjectID
pub const INSTANCE_ID: u64 = 0x58;
// bool
pub const CAN_TRANSLATE: u64 = 0x60;
// bool
pub const EMITTING: u64 = 0x61;
// *const ScriptInstance
pub const SCRIPT_INSTANCE: u64 = 0x68;
// Variant
pub const SCRIPT: u64 = 0x70;
// HashMap<StringName, Variant>
pub const METADATA: u64 = 0x88;
// HashMap<StringName, Variant*>
pub const METADATA_PROPERTIES: u64 = 0xb8;
// *const StringName
pub const CLASS_NAME_PTR: u64 = 0xe8;
}

/// Information about a property of a script. This is not publicly exposed in
/// Godot.
///
/// Check the [`Ptr<PropertyInfo>`] documentation to see all the methods you can
/// call on it.
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct PropertyInfo;

impl Ptr<PropertyInfo> {
/// Returns the type of the property as a [`VariantType`].
pub fn get_variant_type(self, process: &Process) -> Result<VariantType, Error> {
self.read_at_byte_offset(0x0, process)
}
}

/// Base class for all other classes in the engine.
///
/// [`Object`](https://docs.godotengine.org/en/4.2/classes/class_object.html)
Expand All @@ -18,6 +70,19 @@ 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())
self.read_at_byte_offset(offsets::VTABLE_PTR, process)
}

/// Returns the object's Script instance, or [`None`] if no script is
/// attached.
///
/// [`Object.get_script`](https://docs.godotengine.org/en/4.2/classes/class_object.html#class-object-method-get-script)
pub fn get_script_instance(
self,
process: &Process,
) -> Result<Option<Ptr<ScriptInstance>>, Error> {
let ptr: Ptr<ScriptInstance> =
self.read_at_byte_offset(offsets::SCRIPT_INSTANCE, process)?;
Ok(if ptr.is_null() { None } else { Some(ptr) })
}
}
12 changes: 12 additions & 0 deletions src/game_engine/godot/core/object/script_instance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/object/script_instance.h>
/// An instance of a [`Script`](super::Script).
///
/// You need to cast this to a
/// [`GDScriptInstance`](crate::game_engine::godot::GDScriptInstance) or
/// [`CSharpScriptInstance`](crate::game_engine::godot::CSharpScriptInstance) to
/// do anything meaningful with it. Make sure to verify the script language
/// before casting.
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct ScriptInstance;
12 changes: 12 additions & 0 deletions src/game_engine/godot/core/object/script_language.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/object/script_language.h>
/// A class stored as a resource.
///
/// [`Script`](https://docs.godotengine.org/en/4.2/classes/class_script.html)
///
/// You need to cast this to a [`GDScript`](crate::game_engine::godot::GDScript)
/// or [`CSharpScript`](crate::game_engine::godot::CSharpScript) to do anything
/// meaningful with it. Make sure to verify the script language before casting.
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Script;
63 changes: 58 additions & 5 deletions src/game_engine/godot/core/string/string_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,82 @@ use arrayvec::ArrayVec;
use bytemuck::{Pod, Zeroable};

use crate::{
game_engine::godot::{KnownSize, Ptr},
game_engine::godot::{Hash, Ptr, SizeInTargetProcess},
Address64, Error, Process,
};

use super::String;

#[allow(unused)]
mod offsets {
pub mod data {
use super::super::{SizeInTargetProcess, String};

pub const REFCOUNT: u64 = 0x00;
pub const STATIC_COUNT: u64 = 0x04;
pub const CNAME: u64 = 0x08;
pub const NAME: u64 = 0x10;
pub const IDX: u64 = NAME + String::<0>::SIZE;
pub const HASH: u64 = IDX + 0x4;
}
}

/// 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>);
pub struct StringName {
data: Ptr<Data>,
}

impl<const N: usize> Hash<[u8; N]> for StringName {
fn hash_of_lookup_key(lookup_key: &[u8; N]) -> u32 {
// String::hash
let mut hashv: u32 = 5381;

for c in lossy_chars(lookup_key) {
hashv = hashv.wrapping_mul(33).wrapping_add(c as u32);
}

hashv
}

fn eq(&self, lookup_key: &[u8; N], process: &Process) -> bool {
let Ok(name) = self.read::<N>(process) else {
return false;
};
name.chars().eq(lossy_chars(lookup_key))
}
}

impl KnownSize for StringName {}
fn lossy_chars(lookup_key: &[u8]) -> impl Iterator<Item = char> + '_ {
lookup_key.utf8_chunks().flat_map(|chunk| {
chunk.valid().chars().chain(if chunk.invalid().is_empty() {
None
} else {
Some(char::REPLACEMENT_CHARACTER)
})
})
}

impl SizeInTargetProcess for StringName {
const SIZE: u64 = 0x8;
}

#[derive(Debug, Copy, Clone, Pod, Zeroable)]
#[repr(transparent)]
struct StringNameData(Address64);
struct Data(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)?;
// FIXME: This skips cname entirely atm.

// FIXME: Use CowData
let cow_data: Address64 = self
.data
.read_at_byte_offset(offsets::data::NAME, process)?;

// Only on 4.2 or before.
let len = process
Expand Down
6 changes: 6 additions & 0 deletions src/game_engine/godot/core/string/ustring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
use arrayvec::{ArrayString, ArrayVec};

use crate::game_engine::godot::SizeInTargetProcess;

/// 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> SizeInTargetProcess for String<N> {
const SIZE: u64 = 0x8;
}

impl<const N: usize> String<N> {
/// Returns an iterator over the characters in this string.
pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
Expand Down
30 changes: 30 additions & 0 deletions src/game_engine/godot/core/templates/cowdata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! <https://github.com/godotengine/godot/blob/07cf36d21c9056fb4055f020949fb90ebd795afb/core/templates/cowdata.h>
use bytemuck::{Pod, Zeroable};

use crate::game_engine::godot::Ptr;

/// A copy-on-write data type. This is not publicly exposed in Godot.
#[repr(transparent)]
pub struct CowData<T>(Ptr<T>);

impl<T> Copy for CowData<T> {}

impl<T> Clone for CowData<T> {
fn clone(&self) -> Self {
*self
}
}

// SAFETY: The type is transparent over a `Ptr`, which is `Pod`.
unsafe impl<T: 'static> Pod for CowData<T> {}

// SAFETY: The type is transparent over a `Ptr`, which is `Zeroable`.
unsafe impl<T> Zeroable for CowData<T> {}

impl<T> CowData<T> {
/// Returns the pointer to the underlying data.
pub fn ptr(self) -> Ptr<T> {
self.0
}
}
Loading

0 comments on commit 83f7de0

Please sign in to comment.