-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathUnityAssetObjRipper.py
205 lines (159 loc) · 7.33 KB
/
UnityAssetObjRipper.py
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
# Original .js version created by maierfelix https://gist.github.com/maierfelix/1802f9523dee4090e16dc44a4ac70176
# Python implementation written and adapted by crazicrafter1
import sys
import time
from os import listdir
import os
def hex2float(num):
sign = -1 if (num & 0x80000000) else 1
exponent = ((num >> 23) & 0xff) - 127
mantissa = 1 + ((num & 0x7fffff) / 0x7fffff)
return sign * mantissa * pow(2, exponent)
def swap16(val):
return ((val & 0xFF) << 8) | ((val >> 8) & 0xFF)
def swap32(val):
return (
((val << 24) & 0xff000000) |
((val << 8) & 0xff0000) |
((val >> 8) & 0xff00) |
((val >> 24) & 0xff)
)
class DecompiledAsset:
def __init__(self, file_name):
self.__mesh = "" # m_IndexBuffer
self.__index = "" # _typelessdata
self.__vertices = []
self.__normals = []
self.__uvs = []
self.__slices = []
self.__indices = []
self.format = 12 # vert_chunk_size
# check if format can be changed
with open(file_name, "r") as file:
for line in file:
if "m_IndexBuffer" in line:
self.__index = line.replace("\n", "")[line.find(":") + 2:]
elif "_typelessdata" in line:
self.__mesh = line.replace("\n", "")[line.find(":") + 2:]
elif "format" in line:
# check if contains a number greater than 0, if so, then
# change format to that
fmt = int(line.replace("\n", "")[line.find(":") + 2:])
print("format:", fmt)
if fmt >= 6: # arbitrary number
self.format = fmt
def parse(self, vert_chunk_size, offset_v1, offset_v2, offset_v3, debug=False):
for i in range(0, len(self.__mesh) // 8):
_slice = float("%.04f" % hex2float(swap32(int(self.__mesh[i * 8: i * 8 + 8], 16))))
self.__slices.append(_slice)
if debug:
print("slices: ", len(self.__slices) // 3)
for i in range(0, len(self.__slices) // vert_chunk_size):
offset = i * vert_chunk_size
for e in range(0, vert_chunk_size):
print(f"{self.__slices[offset + e]} ", sep='\t', end='')
print()
"""
deciphered format (per chunk size) (unity 2011 at least):
-x y z ?nx ?ny ?nz
"""
for i in range(0, len(self.__slices) // vert_chunk_size):
offset = i * vert_chunk_size
v1 = -self.__slices[offset + offset_v1]
v2 = self.__slices[offset + offset_v2]
v3 = self.__slices[offset + offset_v3]
# n0 = self.__slices[offset + 3]
# n1 = self.__slices[offset + 4]
# n2 = self.__slices[offset + 5]
self.__vertices.extend((v1, v2, v3)) # extend adds all the elements (instead of multiple appends)
# self.__normals.extend((n0, n1, n2))
# uvs.extend((u0, u1))
for i in range(0, len(self.__index) // 4):
# index[i*4: i*4 + 4] for quads
self.__indices.append(swap16(int(self.__index[i * 4: i * 4 + 4], 16)))
def dump(self, primitive, out):
with open(out, "w") as outfile:
outfile.write("# generated by asset_to_obj.py OBJ File: ")
outfile.write("\n")
# iterate the vertices, writing:
if primitive == 3:
for vi in range(0, len(self.__vertices), 3):
outfile.write("v " +
"%.06f" % self.__vertices[vi + 0] + " " +
"%.06f" % self.__vertices[vi + 1] + " " +
"%.06f" % self.__vertices[vi + 2])
outfile.write("\n")
elif primitive == 4:
for vi in range(0, len(self.__vertices), 4):
outfile.write("v " +
"%.06f" % self.__vertices[vi + 0] + " " +
"%.06f" % self.__vertices[vi + 1] + " " +
"%.06f" % self.__vertices[vi + 2] + " " +
"%.06f" % self.__vertices[vi + 3])
outfile.write("\n")
outfile.write("s off")
outfile.write("\n")
for f in range(0, len(self.__indices), 3):
outfile.write("f " +
str(self.__indices[f + 0] + 1) + " " +
str(self.__indices[f + 1] + 1) + " " +
str(self.__indices[f + 2] + 1))
outfile.write("\n")
def main():
first_time = time.time()
# [inputpath] [outputpath] <(-all | -debug)> [vertchunksize] [v1] [v2] [v3]
if len(sys.argv) < 3:
# print("[inputpath] [outputpath] <(-all | -debug)> [vertchunksize] [offset_v1] [offset_v2] [offset_v3]")
print("[inputpath] [outputpath] <(-all | -debug)>")
print("examples:")
print("python.exe UnityAssetObjRipper.py computer_monitor.asset obj_output")
print("python.exe UnityAssetObjRipper.py assets obj_output -all")
print("do not enter / slashes at the end of a path")
# print("python.exe D:/assets_to_obj.py D:/assets D:/obj_output -all")
exit(0)
input_path = sys.argv[1]
output_path = sys.argv[2]
if not os.path.exists(input_path):
print("that path does not exist")
exit(0)
if not os.path.exists(output_path):
os.makedirs(output_path)
input_name = os.path.basename(input_path)
if "-all" in sys.argv:
# then parse all files
print("reading all files in", input_path, ". . .")
files = [f for f in listdir(input_path) if os.path.isfile(input_path + "/" + f)]
# wall.asset
n = 0
for f in files:
if not f.endswith(".asset"):
continue
print("decompiling", f)
n += 1
asset = DecompiledAsset(input_path + "/" + f)
asset.parse(asset.format, 0, 1, 2, True if "-debug" in sys.argv else False)
asset.dump(3, output_path + "/" + f + ".obj")
print("decompiled", n, "assets")
else:
if not input_path.endswith(".asset"):
print("not a Unity asset")
exit(0)
asset = DecompiledAsset(input_path)
asset.parse(asset.format, 0, 1, 2, True if "-debug" in sys.argv else False)
asset.dump(3, output_path + "/" + input_name + ".obj")
total_time = (time.time() - first_time)
print("took", ("%.02f" % total_time), "s")
print("if the model appears janky, you might want to change the vert-chunk-sizes and offsets")
print("asset.parse(12, 0, 1, 2, True if \"-debug\" in sys.argv else False)")
print(" ^ ^ ^ ^")
print("")
print("the 12 is the chunk size")
print("and the 1, 2, 3 are the vert offsets, respectively the x,y,z from the beginning of each chunk")
print("")
print("this tool currently only writes vertices and faces to the created .obj file")
print("normals, uvs, and everything else is not written")
print("")
print("this tool might not write the model correctly, since fairly small simple \n"
"models from the Lurking sound horror game were tested, and not larger models")
if __name__ == "__main__":
main()