Skip to content

Refactor SceneManager / Transform; fix a number of bugs in scene/game object traversal#133

Draft
mitchell-merry wants to merge 7 commits into
LiveSplit:masterfrom
mitchell-merry:transform-refactor
Draft

Refactor SceneManager / Transform; fix a number of bugs in scene/game object traversal#133
mitchell-merry wants to merge 7 commits into
LiveSplit:masterfrom
mitchell-merry:transform-refactor

Conversation

@mitchell-merry
Copy link
Copy Markdown
Contributor

@mitchell-merry mitchell-merry commented May 11, 2026

Another one of the changes I made to asr that I need to upstream...

Why?

I'm now more and more often traversing the gameobject tree in my autosplitters. This has culminated in being able to get a field on a component of a gameobject in a scene: https://github.com/mitchell-merry/autosplitters-wasm/blob/94fbcef38134317b3a5bd7e549a483f18bed17c4/cuphead/src/memory.rs#L170 which does wonders for what's possible.

I have been maintaining a fork with a bunch of tweaks to asr to make this possible, and it's about time I upstream some of it.

  • Concepts of Scene/SceneManager and Transform/GameObject are a little too interwoven
    • I wanted to access GameObject#activeInHierarchy, but it doesn't really make sense to expose that on the Transform directly to me. It's a feature of GameObjects - what if you don't have a transform?
    • get_game_object_from_dont_destroy_on_load doesn't really make sense as a specific function, since we can already get the DDoL scene + game objects on arbitrary scenes
  • Existing logic has some bugs
    • We have klass_name in the SceneManager offsets, but this doesn't really make sense there, since that's strictly to do with the version of Mono/IL2CPP
    • When traversing root game objects, it actually traverses in reverse

What?

  • Split operations on a Scene into the Scene struct rather than being on SceneManager
    • root_game_objects, get_root_game_object
    • Removed get_game_object_from_dont_destroy_on_load (replacement is to get the DDoL scene then call get_root_game_object, like any other scene)
  • Scene#root_game_objects now reads next instead of prev, so the objects are read in the correct order
  • Add Transform#find_transform which traverses the game object tree for you via a path of game object names
  • Split Transform into GameObject & Transform
    • Move Transform#get_name logic to GameObject#get_name, add Transform#get_game_object to Transform and make Transform#get_name use the above two
    • Move Transform#classes and Transform#get_class to GameObject
  • Transform#get_class split into GameObject#get_class_mono and GameObject#get_class_il2cpp since the offset of klass.name depends on the mono version / il2cpp version, same way as it does in the regular path dereferencing code
  • Add GameObject#is_active_in_hierarchy and GameObject#is_active_self, which are self-explanatory

Testing

TODO:

  • Test on Mono versions
  • Test on IL2CPP versions
    • since the offsets change here for class name especially.


impl Class {
pub(super) fn get_name<const N: usize>(
pub fn get_from_component(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"component" isn't accurate here, but there's something in between the component and the class. not exactly sure what it is.

}))
}

// TODO it's really dumb i have to split this by mono/il2cpp
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we really need to implement some sort of strategy pattern here. The amount of code duplication is getting ridiculous

game_object_activeself: 0x5E,
game_object_activeinhierarchy: 0x5F,
klass: 0x28,
klass_name: 0x48,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this changes depending on the mono version, which, of course it does.

Code updated to use the mono/il2cpp class name offsets

/// (and so on), as well as a list of `Component`s, which are classes (eg.
/// `MonoBehaviour`) containing data we might want to retrieve for the auto
/// splitter logic.
fn root_game_objects<'a>(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes more conceptual sense to live on the Scene rather than the SceneManager


/// Tries to find the specified root [`Transform`] from the
/// `DontDestroyOnLoad` Unity scene.
pub fn get_game_object_from_dont_destroy_on_load(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no replacement for this - get the DDoL scene from SceneManager, then call get_root_game_object as with any other Scene


iter::from_fn(move || {
// TODO check if this is correct on other games
let [_prev, next, current]: [Address; 3] = match scene_manager.pointer_size {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: there's a change here, previously we were using [first, _, third] where first was our next element.

That's wrong: https://gist.github.com/just-ero/92457b51baf85bd1e5b8c87de8c9835e#file-list-hpp it actually causes us to go in the reverse order in the list. That caused me some issues in Cuphead

@mitchell-merry mitchell-merry changed the title Transform/GameObject refactors Refactor SceneManager / Transform; fix a number of bugs in scene/game object traversal May 11, 2026
}

/// Returns the full path to the [scene](Scene), as a [String](alloc::string::String).
pub fn path_as_string(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just realised this is already in #127, I'll take it out of this one when I get the chance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant