Skip to content

SerializedFile.load_dependencies() causes crash when dependencies do not exist #326

@eilene-ftf

Description

@eilene-ftf

Code

In this example, we have

>>> sf.externals
[<FileIdentifier(globalgamemanagers.assets)>, <FileIdentifier(Resources/unity_builtin_extra)>, <FileIdentifier(Library/unity default resources)>]

however, the directory Library does not exist, and unity default resources is located in Resources. i.e., there is some error in the game assets.

import UnityPy

am = UnityPy.AssetsManager()
am.load_file("/path/to/sharedassets0.assets")
sf, = am.assets

clips = [v for v in sf.objects.values() if v.type == 82]
clip = clips[0].read()
clip.samples

Alternately:

conv = UnityPy.export.AudioClipConverter
conv.extract_audioclip_samples(clip) 

Error

>>> conv.extract_audioclip_samples(obj)
Traceback (most recent call last):
  File "<python-input-33>", line 1, in <module>
    conv.extract_audioclip_samples(obj)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/path/to/python3.13/site-packages/UnityPy/export/AudioClipConverter.py", line 97, in extract_audioclip_samples
    audio_data = get_resource_data(
        resource.m_Source,
    ...<2 lines>...
        resource.m_Size,
    )
  File "/path/to/python3.13/site-packages/UnityPy/helpers/ResourceReader.py", line 28, in get_resource_data
    assets_file.load_dependencies(possible_names)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/path/to/python3.13/site-packages/UnityPy/files/SerializedFile.py", line 364, in load_dependencies
    self.environment.load_file(file_id.path, True)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/path/to/python3.13/site-packages/UnityPy/environment.py", line 125, in load_file
    file = os.path.join(self.path, file)
  File "<frozen posixpath>", line 77, in join
TypeError: expected str, bytes or os.PathLike object, not NoneType

Bug

The key code region that triggers the error is in SerializedFile:

    def load_dependencies(self, possible_dependencies: list = []):
        ...
        for file_id in self.externals:
            self.environment.load_file(file_id.path, True)
        for dependency in possible_dependencies:
            try:
                self.environment.load_file(dependency, True)
            except FileNotFoundError:
                pass

Specifically, there are two issues. The first issue is that there is no error handling when a file_id.path in self.externals does not exist, as there is in the case that a dependency does not exist just below. If this were the only issue, I would just make a PR as it's a simple fix for an edge case of errant game asset data. However, there is another issue that I'm not sure how to fix in a way that aligns with your design intentions for this module, as, even if the error is handled, in both the case of non-existent dependencies and non-existent externals, the UnityPy still crashes, because Environment.loadfile() raises a TypeError, and not a FileNotFoundError when the file does not exist.

The reason is simple. On line 125 of Environment.py:

file = os.path.join(self.path, file)

self.path may be None, as we see in the __init__ method above:

    def __init__(self, *args: FileSourceType, fs: Optional[AbstractFileSystem] = None):
        self.files = {}
        self.cabs = {}
        self.path = None
        self.fs = fs or LocalFileSystem()
        self.local_files = []
        self.local_files_simple = []

        if args:
            for arg in args:
                if isinstance(arg, str):
                    if self.fs.isfile(arg):
                        if ntpath.splitext(arg)[-1] in [".apk", ".zip"]:
                            self.load_zip_file(arg)
                        else:
                            self.path = ntpath.dirname(arg)
                            if reSplit.match(arg):
                                self.load_files([arg])
                            else:
                                self.load_file(arg)
                    elif self.fs.isdir(arg):
                        self.path = arg
                        self.load_folder(arg)
                else:
                    self.path = None
                    self.load_file(file=arg)

        if len(self.files) == 1:
            self.file = list(self.files.values())[0]

        if self.path == "":
            self.path = os.getcwd()

letting sf.environment.path = os.cwd() in the above example seems to resolve the error for me, but I'm not sure if it's causing other issues later on. Either way, it seems as though either Environment.load_file should handle the case where self.path is None or environments should always be initialized such that self.path is a string. In particular, the case

    if self.path == "":
            self.path = os.getcwd()

seems to be intending to account for the instance where self.path is set by any keyword arguments, but it seems as though if that is the case, that self.path will be None.

For a temporary workaround, I just set sf.environment.path = os.getcwd() before trying to unpack any samples.

To Reproduce

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions