forked from K0lb3/UnityPy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFile.py
More file actions
247 lines (198 loc) · 6.93 KB
/
Copy pathFile.py
File metadata and controls
247 lines (198 loc) · 6.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
from __future__ import annotations
from abc import ABC, ABCMeta, abstractmethod
from collections import defaultdict
from typing import (
TYPE_CHECKING,
Any,
Dict,
Iterator,
List,
Optional,
Self,
Type,
TypeVar,
cast,
)
from attrs import define
from ..streams import EndianBinaryReader, EndianBinaryWriter
if TYPE_CHECKING:
from ..classes import Object, PPtr
from .ObjectReader import ObjectReader
PARSEABLE_FILETYPES: List[Type[File]] = []
T = TypeVar("T")
def parseable_filetype(cls: Type[T]) -> Type[T]:
"""Decorator to register a class as parseable."""
assert issubclass(cls, File)
PARSEABLE_FILETYPES.append(cls)
return cls
def parse_file(
reader: EndianBinaryReader,
name: str,
path: Optional[str] = None,
parent: Optional[ContainerFile] = None,
is_dependency: bool = False,
) -> Optional[File]:
start_pos = reader.tell()
for cls in PARSEABLE_FILETYPES:
try:
probe_result = cls.probe(reader)
except (EOFError, UnicodeDecodeError, IndexError):
probe_result = False
reader.seek(start_pos)
if probe_result:
file = cls(name, path, parent, is_dependency)
file.parse(reader)
return file
class File(ABC, metaclass=ABCMeta):
name: str
path: str
parent: Optional[ContainerFile]
is_dependency: bool
reader: Optional[EndianBinaryReader]
def __init__(
self,
name: str,
path: str,
parent: Optional[ContainerFile] = None,
is_dependency: bool = False,
reader: Optional[EndianBinaryReader] = None,
):
self.name = name
self.path = path or name
self.parent = parent
self.is_dependency = is_dependency
self.reader = reader
@classmethod
@abstractmethod
def probe(cls, reader: EndianBinaryReader) -> bool:
"""Check if the file is of this type.
Args:
reader (EndianBinaryReader): The reader to read from.
Returns:
bool: Whether the file is of this type.
"""
pass
@abstractmethod
def parse(self, reader: Optional[EndianBinaryReader] = None) -> Self:
"""Parse the file from the reader.
Args:
reader (EndianBinaryReader): The reader to read from.
"""
pass
@abstractmethod
def dump(self, writer: Optional[EndianBinaryWriter] = None) -> EndianBinaryWriter:
"""Dump the file to the writer.
Args:
writer (EndianBinaryWriter): The writer to write to.
"""
pass
def __enter__(self):
return self
def __exit__(self):
pass
@abstractmethod
def get_objects(self) -> List[ObjectReader[Any]]:
"""Get all objects contained in this file and its childs.
Returns:
List[Object]: The objects in this file.
"""
pass
@abstractmethod
def get_containers(self) -> Dict[str, List[PPtr[Object]]]:
"""Get all containers contained in this file and its childs.
Returns:
List[Tuple[str, Object]]: The containers in this file.
"""
pass
def _opt_get_set_reader(
self, reader: Optional[EndianBinaryReader] = None
) -> EndianBinaryReader:
if reader is not None:
self.reader = reader
if self.reader is None:
raise ValueError("No reader provided")
return self.reader
class ResourceFile(File):
reader: EndianBinaryReader # type: ignore
@classmethod
def probe(cls, reader: EndianBinaryReader) -> bool:
return True
def parse(self, reader: Optional[EndianBinaryReader] = None) -> Self:
if reader is not None:
self.reader = reader # type: ignore
return self
def dump(self, writer: Optional[EndianBinaryWriter] = None) -> EndianBinaryWriter:
if writer is None:
writer = EndianBinaryWriter(endian="<")
writer.write_bytes(self.reader.get_bytes())
return writer
def get_objects(self) -> List[ObjectReader[Any]]:
raise ValueError("ResourceFile does not contain objects")
def get_containers(self) -> Dict[str, List[ObjectReader[Any]]]:
raise ValueError("ResourceFile does not contain containers")
@define(slots=True)
class DirectoryInfo:
offset: int
size: int
path: str
flags: Optional[int] = None
# TODO: refractor name to avoid confusion with get_container / container paths
class ContainerFile(File, ABC, metaclass=ABCMeta):
childs: List[File]
directory_infos: List[DirectoryInfo]
directory_reader: Optional[EndianBinaryReader]
# @abstractmethod
# def get_serialized_files(self) -> List[SerializedFile]:
# """Get all serialized files contained in this file and its childs.
# Returns:
# List[SerializedFile]: The serialized files in this file.
# """
# pass
def __init__(
self,
name: str,
path: str,
parent: Optional[ContainerFile] = None,
is_dependency: bool = False,
reader: Optional[EndianBinaryReader] = None,
):
super().__init__(name, path, parent, is_dependency, reader)
self.childs = []
self.directory_infos = []
self.directory_reader = None
def parse_files(self):
"""Parse the directory infos and all files in this file."""
if self.directory_reader is None:
raise ValueError("directory_reader is not set")
for info in self.directory_infos:
path_parts = info.path.split("/")
name = path_parts[-1]
path = f"{self.path}/{info.path}"
# subreader = self.directory_reader.create_sub_reader(info.offset, info.size)
# subreader.seek(0)
self.directory_reader.seek(info.offset)
file = parse_file(
self.directory_reader, name, path, self, self.is_dependency
)
self.childs.append(
file
if file is not None
else ResourceFile(name, path, self, False, self.directory_reader)
)
def traverse(self) -> Iterator[File]:
if not self.childs and self.directory_infos:
self.parse_files()
for child in self.childs:
if isinstance(child, ContainerFile):
yield from child.traverse()
elif isinstance(child, ResourceFile):
continue
yield child
def get_objects(self) -> List[ObjectReader[Any]]:
return [obj for child in self.traverse() for obj in child.get_objects()]
def get_containers(self) -> Dict[str, List[ObjectReader[Any]]]:
containers: Dict[str, List[ObjectReader[Any]]] = defaultdict(list)
for child in self.traverse():
for container_path, obj in child.get_containers().items():
containers[container_path].extend(obj)
return cast(Dict[str, List[ObjectReader[Any]]], containers)