leonitousconforti/tinyburg

View on GitHub
packages/insight/src/agents/get-costume-data.ts

Summary

Maintainability
A
1 hr
Test Coverage
import "frida-il2cpp-bridge";

import type { ICostumeAgentExports } from "../shared/costume-agent-exports.js";

import { readField } from "../helpers/read.js";
import { TinyTowerFridaAgent } from "./base-frida-agent.js";
import { copyDictionaryToJs } from "../helpers/copy-dictionary-to-js.js";

export class GetCostumeData extends TinyTowerFridaAgent<GetCostumeData> {
    public loadDependencies() {
        const csharpAssembly = Il2Cpp.domain.assembly("Assembly-CSharp");
        const AppUtilClass = csharpAssembly.image.class("AppUtil");
        const CostumeClass = csharpAssembly.image.class("VCostume");
        const VCostumeTableClass = csharpAssembly.image.class("VCostumeTable");
        const VCostumeTableInstance = VCostumeTableClass.field<Il2Cpp.Object>("_instance").value;
        const costumesDictionary = VCostumeTableInstance.field<Il2Cpp.Object>("costumes").value;
        return { AppUtilClass, CostumeClass, VCostumeTableClass, VCostumeTableInstance, costumesDictionary };
    }

    public retrieveData() {
        // Extract the version of the game
        const version = this.dependencies.AppUtilClass.method<Il2Cpp.String>("VersionString").invoke().content;

        // Extract the costumes
        const costumeEntries =
            // Copy the costumes dictionary over to JS and map the entries (key is the name of the
            // costume and value is the costume data) to a list of entries with the costume properties
            Object.entries(copyDictionaryToJs<Il2Cpp.String, Il2Cpp.Object>(this.dependencies.costumesDictionary))
                .map(
                    ([name, costume]) =>
                        [
                            name,
                            costume.class.fields.map((property) => [
                                property.name,
                                readField(costume.field(property.name)),
                            ]),
                        ] as const
                )
                // Reassemble the list of costume entries into an object
                .map(([name, data]) => [name, Object.fromEntries(data)] as const);

        return { TTVersion: version || "unknown", costumes: Object.fromEntries(costumeEntries) };
    }

    public transformToSourceCode() {
        // Source code for the costumes array
        const costumesSourceTS = `export const costumes = ${JSON.stringify(this.data.costumes)} as const;\n`;
        const costumeSourceTS = "export type Costume = typeof costumes;\n";
        return `// TinyTower version: ${this.data.TTVersion}\n` + costumesSourceTS + costumeSourceTS;
    }
}

// Main entry point exported for when this file is compiled as a frida agent
const rpcExports: ICostumeAgentExports = {
    main: async () => {
        const instance = await new GetCostumeData().start();
        return instance.transformToSourceCode();
    },
};
rpc.exports = rpcExports as unknown as RpcExports;