UnityPy icon indicating copy to clipboard operation
UnityPy copied to clipboard

Make PlayerSettings editable

Open UserUnknownFactor opened this issue 3 years ago • 10 comments

Just as the title says. I didn't need the previously non-unserialized fields of PlayerSettings of new versions so they're restored by binary-reading and writing them back as is. If anyone completes the class later it would be even better.

UserUnknownFactor avatar Feb 11 '22 17:02 UserUnknownFactor

Sorry for replying only that late. I was quite busy around the time you made the PR and then forgot about it -.-'

There will soon be complete support for the whole PlayerSettings as well as all other currently unimplemented or not completely implemented types. The maker of AssetRipper dumped all typetrees of all Unity versions, so I've been working every now and then on making a code generator that automatically creates reader and writer for all types based on those typetrees.

(Edit: correct creator of Assetstudio to AssetRipper as noted below)

K0lb3 avatar Mar 06 '22 00:03 K0lb3

The maker of AssetStudio dumped all typetrees of all Unity versions

Perfare did not do this

ds5678 avatar Mar 06 '22 04:03 ds5678

The maker of AssetStudio dumped all typetrees of all Unity versions

Perfare did not do this

Sorry, I meant you (@ds5678 ). I shouldn't do stuff way past midnight anymore, somehow I always make at least one dumb mistake during that time frame.

K0lb3 avatar Mar 06 '22 07:03 K0lb3

I don't think AssetRipper TypeTreeDumps is a good solution, in practice I've encountered too many CustomClass to parse and it's a good idea to continue using il2cppdumper + TypeTreeGenerator.

In my project , I used the function to dump all dll file into a large JSON file. So maybe continue to improve the compatibility of TypeTreeGenerator?

@regist_udp_rpc
def dumpAndMaketree(self, app_dir: str, dllexport_dir: str, callback=None):
    if False is os.path.exists(os.path.join(dllexport_dir,"DummyDll/Assembly-CSharp.dll")):
        self.log.emit("准备加载动态库....")
        self.process = QProcess()
        self.process.finished.connect(self.onProcessFinished)
        self.process.errorOccurred.connect(self.onReadyReadStandardError)
        self.process.readyReadStandardOutput.connect(self.onReadyReadStandardOutput)
        if os.path.exists(dllexport_dir) == False:
            os.makedirs(dllexport_dir)
        if os.path.exists(dllexport_dir + "DummyDll") == False:
            self.process.setProgram(shell.il2cppdumper)
            self.process.setArguments([f"{app_dir}/lib/arm64-v8a/libil2cpp.so",
                                       f"{app_dir}/assets/bin/Data/Managed/Metadata/global-metadata.dat",
                                       dllexport_dir])
            self.process.start()
            self.process.waitForFinished()
            pyfiles = glob(shell.il2cppdumper_path + "/*.py")
            for pyf in pyfiles:
                pyname = os.path.basename(pyf)
                shutil.copyfile(pyf, f"{dllexport_dir}/{pyname}")

        self.log.emit("动态库导出完成!")
    treedata = os.path.join(dllexport_dir , "trees.bin")
    if os.path.exists(treedata):
        with open(treedata,"rb") as fb:
            self.dlltrees = json.loads(fb.read())
        return
    self.log.emit("开始解析动态库...")
    dlls = glob(dllexport_dir + "DummyDll/*.dll")
    def s2i(s):
        try:
            return int(s)
        except Exception as e:
            return 0
    treesConfig = {}
    for dll in dlls:
        dllname = os.path.basename(dll)
        cmd = subprocess.Popen([shell.TypeTreeGeneratorCLI,"-p",
                                   dllexport_dir + "DummyDll", "-a", dllname,
                                   "-d", "json_min",
                                   "-v",",".join([str(i) for i in self.UnityVersion])],
                               stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           env=shell.ENV)
        out = cmd.stdout.readlines()
        if len(out)>0:
            out = out[0]
            try:
                treesConfig[dllname] = json.loads(base64.decodebytes(out))
                if dllname == "Unity.TextMeshPro.dll" and "TMPro.TextMeshProUGUI" in treesConfig[dllname]:
                    hasChange = treesConfig[dllname]["TMPro.TextMeshProUGUI"]
                    treesConfig[dllname]["TMPro.TextMeshProUGUI"]= self.append_type("m_fontAsset", 3,
                                                                                    hasChange,
                                                                                    {"type":"int","name":"m_Type","meta_flag":0,"level":2})
                if dllname == "Unity.TextMeshPro.dll" and "TMPro.TextMeshPro" in treesConfig[dllname]:
                    hasChange = treesConfig[dllname]["TMPro.TextMeshPro"]
                    treesConfig[dllname]["TMPro.TextMeshPro"] = self.append_type("m_fontAsset", 3,
                                                                                     hasChange,
                                                                                     {"type": "int",
                                                                                      "name": "m_Type",
                                                                                      "meta_flag": 0, "level": 2})
                self.log.emit("loading dll:"+dllname)
            except Exception as e:
                print(f"error:{e}")
        # self.process.setProgram(shell.TypeTreeGeneratorCLI)
        # self.process.setArguments()
        # self.process.start()
        # self.process.waitForFinished()
    self.dlltrees = treesConfig
    try:
        with open(treedata, "wb") as fb:
            fb.write(json.dumps(treesConfig,ensure_ascii=False).encode())
        self.log.emit("动态库解析完成!")
    except Exception as e:
        os.remove(treedata)
        self.log.emit("动态库解析失败.."+e)

ablegao avatar Mar 06 '22 16:03 ablegao

At the very least, you should be using Cpp2IL instead of Il2CppDumper since dumper doesn't recover attributes, specifically the SerializeField attribute.

ds5678 avatar Mar 06 '22 17:03 ds5678

@ablegao The PlayerSettings as well as the GameObjects aren't custom classes and are instead default classes of Unity. Therefore using their typetrees to generate parsers and writers will offer a clean and complete solution to implementing good readers and writers for all default classes.

This won't affect the topic of custom classes which you mentioned.

K0lb3 avatar Mar 10 '22 10:03 K0lb3

@ds5678 Not related to this question, but I want to help clarify that Il2CppDumper has recover attributes, at least SerializeField does, and I've been working with it for a long time without any problems. Of course, if you just need the dummydll, it is indeed better to use Cpp2IL.

bananaheaven avatar Mar 16 '22 00:03 bananaheaven

Small update. The code generation for the typetrees is nearly done, so there will be complete PlaySettings support soon.

K0lb3 avatar Mar 22 '22 14:03 K0lb3

@UserUnknownFactor Cough, it took a bit longer than I thought it would, "distraction" through normal life is annoying for sure.

Anyway, thanks to @ds5678 all default Unity objects can now be edited via their typetrees, which are shipped by UnityPy itself inside the by @ds5678 developed tpk file.

K0lb3 avatar Jun 10 '22 20:06 K0lb3

My plan moving forward is focussing on this, instead of using custom implementations for each type.

My current plan is to make hollow classes for type hinting, which may also contain post-processing and export functions. The actual object parsing will happen via the typetrees. These classes will be generated based on the trees and also use slots, which will reduce the memory footprint by a fair bit, while also decreasing the access time to the fields.

As parsing them is currently still quite slow, I plan to work on a C-implementation of the typetree reader next, before reworking the whole object class system.

K0lb3 avatar Jun 10 '22 20:06 K0lb3