Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for games using the Unity engine #36

Merged
merged 10 commits into from
Jul 17, 2023

Conversation

Jujstme
Copy link
Contributor

@Jujstme Jujstme commented May 22, 2023

This adds support for memory scanning in Unity games.

Largely based on the original code taken from https://github.com/CryZe/lunistice-auto-splitter/blob/master/asr-dotnet/src/lib.rs, with some minor bug fix and support for both Mono and IL2CPP.

More specifically, this PR adds 2 new crate features, named mono and il2cpp (as the name implies, mono is to be used for non-il2cpp).

Each of those features internally calls different offsets based on the structs used in the particular version the target game is built on. There is a try_attach() function that is capable of automatically identify the version of the unity engine used in the target process, but it's actually recommended to specify the version manually. It's up to the authors of the autosplitters, in any case, to know which version is used in their game and to be sure the automatic version reporting is accurate. If the autosplitter loads the wrong structs/offsets, it will fail to identify the subsequent Mono classes.

Also, it is of note that, since there are few 32-bit IL2CPP games, and the struct of the Il2CppClass is not known to me, only 64-bit games are supported. However, for non-il2cpp, 32-bit and 64-bit games supported.

The main class is instantiated by calling asr::dotnet::Il2cpp::attach(&process) or asr::dotnet::mono::attach(&process) (or their awaitable variants (wait_attach(&process)).
From there we can find our mono assembly of interest via get_image(&str). A get_default_image() function is also provided in case you're just looking for Assembly-CSharp.

It is then possible to lookup for a specific MonoClass by giving its name.
The code also allows, inside MonoClass:

  • to get parent classes
  • to get the offsets of specific fields in each class
  • to recover the memory address of the static table

Custom functions can be coded by the authors of the autosplitters, if necessary.

The example below refers to Pacman World Re-Pac:

async fn main() {
    loop {
        let process = Process::wait_attach("PAC-MAN WORLD Re-PAC.exe").await;

        process
            .until_closes(async {
                let unity = retry(|| Il2Cpp::try_attach(&process)).await;
                let mono = unity.wait_get_default_image().await;

                let class_scenemg = mono.wait_get_class("SceneManager").await;
                let offset_m_bProcessing = class_scenemg.wait_get_field("m_bProcessing").await;
                let class_scenemg_parent = class_scenemg.wait_get_parent().await;
                let class_scenemg_parent_instance = class_scenemg_parent.wait_get_field("s_sInstance").await;
                let class_scenemg_parent_static = class_scenemg_parent.wait_get_static_table().await;

                loop {
                    let is_loading = process
                        .read_pointer_path64::<u8>(
                            class_scenemg_parent_static,
                            &[class_scenemg_parent_instance, offset_m_bProcessing],
                        )
                        .unwrap_or_default()
                        != 0;

                    if is_loading {
                        timer::pause_game_time()
                    } else {
                        timer::resume_game_time()
                    }

                    next_tick().await;
                }
            })
            .await;
    }
}

Additionally, the SceneManager class can be instantiated to get data relative to the current Unity scene.

Example:

let scene_manager = retry(|| asr::dotnet::SceneManager::new(&process)).await;

...

let current_scene_name: Result<ArrayString<255>, Error> = scene_manager.get_current_scene_name::<255>();

SceneManager is supported in both 32-bit and 64-bit games.

@CryZe CryZe force-pushed the master branch 3 times, most recently from c914018 to f5ca177 Compare June 29, 2023 20:36
@CryZe CryZe merged commit 2b0f32c into LiveSplit:master Jul 17, 2023
4 checks passed
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.

None yet

2 participants