Skip to content

Commit 737deb9

Browse files
authored
1.10.0 - virtual filesystem, better dependency resolving, not breaking ContainerDict (#186)
* MeshRenderExporter - use m_Name instead of name * Texture2DConverter - image - error if there is no image data * Texture2D - load image data from resource lazily * rework dependency loading and resolving * SerializedFile - rework container handling * Object - use new container handling and fix NodeHelper error with tuples * add optional wrap argument for reading typetrees * File - fix read_files * implement custom filesystem handling * Update README.md * EndianBinaryReader - fix passing of LocalFileOpenener * Readme.md - min python 3.6.0 -> 3.7.0 * setup.py - possible legacy egg fix * pyproject.toml - remove 3.6 support, add 3.10 and 3.11 support
1 parent 6cdafe3 commit 737deb9

17 files changed

+341
-216
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ if UnityPy.__version__ != '1.9.6':
3737
2. [Example](#example)
3838
3. [Important Classes](#important-classes)
3939
4. [Important Object Types](#important-object-types)
40-
5. [Credits](#credits)
40+
5. [Custom Fileystem](#custom-filesystem)
41+
6. [Credits](#credits)
4142

4243
## Installation
4344

44-
**Python 3.6.0 or higher is required**
45+
**Python 3.7.0 or higher is required**
4546

4647
via pypi
4748

@@ -364,6 +365,22 @@ if mesh_renderer.m_GameObject:
364365
mesh_renderer.export(export_dir)
365366
```
366367

368+
## Custom-Filesystem
369+
370+
UnityPy uses [fsspec](https://github.com/fsspec/filesystem_spec) under the hood to manage all filesystem interactions.
371+
This allows using various different types of filesystems without having to change UnityPy's code.
372+
It also means that you can use your own custom filesystem to e.g. handle indirection via catalog files, load assets on demand from a server, or decrypt files.
373+
374+
Following methods of the filesystem have to be implemented for using it in UnityPy.
375+
376+
- sep (not a function, just the seperator as character)
377+
- isfile(self, path: str) -> bool
378+
- isdir(self, path: str) -> bool
379+
- exists(self, path: str, \*\*kwargs) -> bool
380+
- walk(self, path: str, \*\*kwargs) -> Iterable[List[str], List[str], List[str]]
381+
- open(self, path: str, mode: str = "rb", \*\*kwargs) -> file ("rb" mode required, "wt" required for ModelExporter)
382+
- makedirs(self, path: str, exist_ok: bool = False) -> bool
383+
367384
## Credits
368385

369386
First of all,

UnityPy/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
__version__ = "1.9.28"
1+
__version__ = "1.10.0"
22

33
from .environment import Environment
44
from .helpers.ArchiveStorageManager import set_assetbundle_decrypt_key
55

66

7-
def load(*args):
8-
return Environment(*args)
7+
def load(*args, fs=None, **kwargs):
8+
return Environment(*args, fs=fs, **kwargs)
99

1010

1111
# backward compatibility

UnityPy/classes/Object.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from ..files import ObjectReader
66
import types
77
from ..exceptions import TypeTreeError as TypeTreeError
8-
from .. import classes
98

109

1110
class Object(object):
@@ -26,11 +25,7 @@ def __init__(self, reader: ObjectReader):
2625
if self.platform == BuildTarget.NoTarget:
2726
self._object_hide_flags = reader.read_u_int()
2827

29-
self.container = (
30-
self.assets_file._container[self.path_id]
31-
if self.path_id in self.assets_file._container
32-
else None
33-
)
28+
self.container = self.assets_file.container.path_dict.get(self.path_id)
3429

3530
self.reader.reset()
3631
if type(self) == Object:
@@ -46,10 +41,10 @@ def dump_typetree(self, nodes: list = None) -> str:
4641
def dump_typetree_structure(self) -> str:
4742
return self.reader.dump_typetree_structure()
4843

49-
def read_typetree(self, nodes: list = None) -> dict:
44+
def read_typetree(self, nodes: list = None, wrap: bool = False) -> dict:
5045
tree = self.reader.read_typetree(nodes)
5146
self.type_tree = NodeHelper(tree, self.assets_file)
52-
return tree
47+
return self.type_tree if wrap else tree
5348

5449
def save_typetree(self, nodes: list = None, writer: EndianBinaryWriter = None):
5550
def class_to_dict(value):
@@ -145,6 +140,8 @@ def __new__(cls, data, assets_file):
145140
return super(NodeHelper, cls).__new__(cls)
146141
elif isinstance(data, list):
147142
return [NodeHelper(x, assets_file) for x in data]
143+
elif isinstance(data, tuple):
144+
return tuple(NodeHelper(x, assets_file) for x in data)
148145
return data
149146

150147
def __getitem__(self, item):

UnityPy/classes/PPtr.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
from ..files import ObjectReader
22
from ..streams import EndianBinaryWriter
3-
from ..helpers import ImportHelper
4-
from .. import files
5-
from ..enums import FileType, ClassIDType
6-
import os
7-
from .. import environment
3+
from ..enums import ClassIDType
84

95

106
def save_ptr(obj, writer: EndianBinaryWriter):
@@ -33,7 +29,9 @@ def save(self, writer: EndianBinaryWriter):
3329
def get_obj(self):
3430
if self._obj != None:
3531
return self._obj
32+
3633
manager = None
34+
3735
if self.file_id == 0:
3836
manager = self.assets_file
3937

@@ -43,27 +41,13 @@ def get_obj(self):
4341
external_name = self.external_name
4442
# try to find it in the already registered cabs
4543
manager = environment.get_cab(external_name)
46-
44+
# not found, load all dependencies and try again
4745
if not manager:
48-
# guess we have to try to find it as file then
49-
path = environment.path
50-
if path is not None:
51-
basename = os.path.basename(external_name)
52-
possible_names = [basename, basename.lower(), basename.upper()]
53-
for root, dirs, files in os.walk(path):
54-
for name in files:
55-
if name in possible_names:
56-
manager = environment.load_file(
57-
os.path.join(root, name)
58-
)
59-
environment.register_cab(name, manager)
60-
break
61-
else:
62-
# else is reached if the previous loop didn't break
63-
continue
64-
break
65-
if manager and self.path_id in manager.objects:
66-
self._obj = manager.objects[self.path_id]
46+
self.assets_file.load_dependencies([external_name])
47+
manager = environment.get_cab(external_name)
48+
49+
if manager is not None:
50+
self._obj = manager.objects.get(self.path_id)
6751
else:
6852
self._obj = None
6953
if self.external_name:

UnityPy/classes/Texture2D.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ def image(self, img):
4747

4848
@property
4949
def image_data(self):
50+
if not self._image_data and self.m_StreamData is not None:
51+
self._image_data = get_resource_data(
52+
self.m_StreamData.path,
53+
self.assets_file,
54+
self.m_StreamData.offset,
55+
self.m_StreamData.size,
56+
)
5057
return self._image_data
5158

5259
def reset_streamdata(self):
@@ -166,13 +173,8 @@ def __init__(self, reader):
166173
if version >= (5, 3): # 5.3 and up
167174
# always read the StreamingInfo for resaving
168175
self.m_StreamData = StreamingInfo(reader, version)
169-
if image_data_size == 0 and self.m_StreamData.path:
170-
self._image_data = get_resource_data(
171-
self.m_StreamData.path,
172-
self.assets_file,
173-
self.m_StreamData.offset,
174-
self.m_StreamData.size,
175-
)
176+
# don't read the data directly,
177+
# as we don't want the parser break if the file is missing
176178

177179
def save(self, writer: EndianBinaryWriter = None):
178180
if writer is None:

0 commit comments

Comments
 (0)