Skip to content

Commit

Permalink
Allow querying the scripts of nodes in Godot
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 committed Jul 5, 2024
1 parent 340daa6 commit db99a4d
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 db99a4d

Please sign in to comment.