Skip to content

Commit d23bec2

Browse files
committed
feat: add python files used to extract PNGs from YTD files
A simple python script to turn YTD files into PNGs. It literally goes thorugh the YTD archive and extracts ALL images it can find. This is used to transform the minimap files "minimap_sea_*_*.ytd" to PNG files for use in the interface. Just type `python extract_png.py` in the same directory as the YTD files.
1 parent 05c8f99 commit d23bec2

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

images/tiles/extract_png.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
import zlib
3+
import sys
4+
from read_ytd import YTD
5+
import glob
6+
7+
#filename = input("Please enter the file name > ")
8+
9+
for ytd_file in list(glob.glob("minimap_*.ytd")):
10+
print(ytd_file)
11+
ytd = YTD(ytd_file)
12+
ytd.finished()

images/tiles/read_ytd.py

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import os
2+
import zlib
3+
from PIL import Image
4+
import io
5+
import math
6+
7+
8+
def extract_data_from_array(arr, count):
9+
return arr[:count], arr[count:]
10+
11+
12+
class TextureData:
13+
LENGTH = 64
14+
15+
def __init__(self, raw_data, reader): # raw_data = 64 bytes from system data
16+
self.VTF, raw_data = extract_data_from_array(raw_data, 4)
17+
self.unk_4h, raw_data = extract_data_from_array(raw_data, 4)
18+
self.unk_8h, raw_data = extract_data_from_array(raw_data, 4)
19+
self.unk_Ch, raw_data = extract_data_from_array(raw_data, 4)
20+
self.unk_10h, raw_data = extract_data_from_array(raw_data, 4)
21+
self.unk_14h, raw_data = extract_data_from_array(raw_data, 4)
22+
self.unk_18h, raw_data = extract_data_from_array(raw_data, 4)
23+
self.unk_1Ch, raw_data = extract_data_from_array(raw_data, 4)
24+
self.unk_20h, raw_data = extract_data_from_array(raw_data, 4)
25+
self.unk_24h, raw_data = extract_data_from_array(raw_data, 4)
26+
self.name_pointer, raw_data = extract_data_from_array(raw_data, 8)
27+
self.Unknown_30h, raw_data = extract_data_from_array(raw_data, 4)
28+
self.Unknown_34h, raw_data = extract_data_from_array(raw_data, 4)
29+
self.Unknown_38h, raw_data = extract_data_from_array(raw_data, 4)
30+
self.Unknown_3Ch, raw_data = extract_data_from_array(raw_data, 4)
31+
32+
self.name_pointer = int.from_bytes(self.name_pointer, 'little')
33+
34+
temp = reader.read(self.name_pointer, 1)
35+
how_many_read = 0
36+
name = ""
37+
while temp != b'\x00': # While not null terminator
38+
name += temp.decode("utf-8")
39+
how_many_read += 1
40+
temp = reader.read(self.name_pointer+how_many_read, 1)
41+
42+
print("Read name: %s" % name)
43+
self.name = name
44+
45+
46+
class TextureDX11(TextureData):
47+
LENGTH = 0x90
48+
49+
def __init__(self, raw_data, reader):
50+
cpy = raw_data
51+
super().__init__(raw_data, reader)
52+
53+
_, raw_data = extract_data_from_array(raw_data, TextureData.LENGTH)
54+
55+
self.Unknown_40h, raw_data = extract_data_from_array(raw_data, 4)
56+
self.Unknown_44h, raw_data = extract_data_from_array(raw_data, 4)
57+
self.Unknown_48h, raw_data = extract_data_from_array(raw_data, 4)
58+
self.Unknown_4Ch, raw_data = extract_data_from_array(raw_data, 4)
59+
self.Width, raw_data = extract_data_from_array(raw_data, 2)
60+
self.Height, raw_data = extract_data_from_array(raw_data, 2)
61+
self.Unknown_54h, raw_data = extract_data_from_array(raw_data, 2)
62+
self.Stride, raw_data = extract_data_from_array(raw_data, 2)
63+
self.Format, raw_data = extract_data_from_array(raw_data, 4)
64+
self.Unknown_5Ch, raw_data = extract_data_from_array(raw_data, 1)
65+
self.Levels, raw_data = extract_data_from_array(raw_data, 1)
66+
self.Unknown_5Eh, raw_data = extract_data_from_array(raw_data, 2)
67+
self.Unknown_60h, raw_data = extract_data_from_array(raw_data, 4)
68+
self.Unknown_64h, raw_data = extract_data_from_array(raw_data, 4)
69+
self.Unknown_68h, raw_data = extract_data_from_array(raw_data, 4)
70+
self.Unknown_6Ch, raw_data = extract_data_from_array(raw_data, 4)
71+
self.DataPointer, raw_data = extract_data_from_array(raw_data, 8)
72+
self.Unknown_78h, raw_data = extract_data_from_array(raw_data, 4)
73+
self.Unknown_7Ch, raw_data = extract_data_from_array(raw_data, 4)
74+
self.Unknown_80h, raw_data = extract_data_from_array(raw_data, 4)
75+
self.Unknown_84h, raw_data = extract_data_from_array(raw_data, 4)
76+
self.Unknown_88h, raw_data = extract_data_from_array(raw_data, 4)
77+
self.Unknown_8Ch, raw_data = extract_data_from_array(raw_data, 4)
78+
79+
self.Format = int.from_bytes(self.Format, "little")
80+
self.Width = int.from_bytes(self.Width, "little")
81+
self.Height = int.from_bytes(self.Height, "little")
82+
self.Levels = int.from_bytes(self.Levels, "little")
83+
self.Stride = int.from_bytes(self.Stride, "little")
84+
85+
textureLength = 0
86+
length = self.Stride * self.Height
87+
for i in range(0, self.Levels):
88+
textureLength += length
89+
length /= 4
90+
textureLength = math.floor(textureLength)
91+
n = 1
92+
if self.Format == 894720068:
93+
n = 3
94+
95+
textureDataRaw = reader.read(int.from_bytes(self.DataPointer, "little"), textureLength)
96+
97+
Image.frombytes('RGBA', (self.Width, self.Height), textureDataRaw, "bcn", n).save("%s.png" % self.name)
98+
print("Done")
99+
100+
101+
class SystemData:
102+
SYSTEM_CONST = ~0x50000000
103+
SYSTEM_BASE = 0x50000000
104+
105+
class GraphicsData:
106+
GFX_CONST = ~0x60000000
107+
GFX_BASE = 0x60000000
108+
109+
class Resources:
110+
111+
def read_system(self, pos, count):
112+
start = pos & SystemData.SYSTEM_CONST
113+
return self.ytd.systemData[start:start+count]
114+
115+
def read_gfx(self, pos, count):
116+
start = pos & GraphicsData.GFX_CONST
117+
return self.ytd.gfxData[start:start+count]
118+
119+
def read(self, position, bytes=16):
120+
sp = position & SystemData.SYSTEM_BASE
121+
if sp == SystemData.SYSTEM_BASE:
122+
print("Reading from system")
123+
return self.read_system(position, bytes)
124+
elif (position & GraphicsData.GFX_BASE) == GraphicsData.GFX_BASE:
125+
print("Reading from GFX")
126+
return self.read_gfx(position, bytes)
127+
else:
128+
raise Exception("Invalid position")
129+
130+
def __init__(self, ytdfile):
131+
self.ytd = ytdfile
132+
# Skip the first 48 bytes... They're not useful.. I hope
133+
resourcePointerArrayStart = 48
134+
135+
resourceData = ytdfile.systemData[resourcePointerArrayStart:resourcePointerArrayStart+16] # Read the resource data line
136+
137+
arrayPointerStart = resourceData[:8] # Where does the resource array info start?
138+
resourceData = resourceData[8:]
139+
140+
resourceCount = resourceData[:2]
141+
resourceData = resourceData[2:]
142+
143+
resourceCapacity = resourceData[:2]
144+
resourceData = resourceData[2:]
145+
146+
print("Pointer: 0x%x, Entry count: %i, Entry capacity: %i" %
147+
(int.from_bytes(arrayPointerStart, 'little'), int.from_bytes(resourceCount, 'little'),
148+
int.from_bytes(resourceCapacity, 'little')))
149+
150+
self.read(int.from_bytes(arrayPointerStart, "little"))
151+
152+
textures = []
153+
for i in range(0, int.from_bytes(resourceCount, 'little')):
154+
offset = i * 8
155+
#pointerLocation = int.from_bytes(resourcePointer, 'little') & ~0x50000000
156+
pointerDataLocation = self.read(int.from_bytes(arrayPointerStart, 'little')+offset, 8) # The real location of the textures
157+
158+
# pointersRealLocation = int.from_bytes(pointerData, 'little') & ~0x50000000
159+
textureData = TextureDX11(self.read(int.from_bytes(pointerDataLocation, 'little'), TextureDX11.LENGTH), self)
160+
textures.append(textureData)
161+
162+
163+
class YTD:
164+
DEFAULT_SIZE = 0x2000
165+
166+
def G(self, systemFlag, graphicsFlag):
167+
return int(((graphicsFlag >> 28) & 0xF) | (((systemFlag >> 28) & 0xF) << 4))
168+
169+
def sizeFromFlag(self, flag, baseSize):
170+
baseSize <<= int(flag & 0xf)
171+
size = int((((flag >> 17) & 0x7f) + (((flag >> 11) & 0x3f) << 1) + (((flag >> 7) & 0xf) << 2) + (
172+
((flag >> 5) & 0x3) << 3) + (((flag >> 4) & 0x1) << 4)) * baseSize)
173+
print("Adding size %i, basesize: %i" % (size, baseSize))
174+
i = 0
175+
while i < 4:
176+
# size += (((flag >> (24 + i)) & 1) == 1) ? (baseSize >> (1 + i)) : 0;
177+
if (flag >> (24 + i)) & 1 == 1:
178+
size += (baseSize >> (1 + i))
179+
i += 1
180+
return size
181+
182+
def inflate(self, data):
183+
decompress = zlib.decompressobj(
184+
-zlib.MAX_WBITS # see above
185+
)
186+
inflated = decompress.decompress(data)
187+
inflated += decompress.flush()
188+
return inflated
189+
190+
def calcSytemSize(self, sysFlag):
191+
s16 = int(sysFlag >> 27) & 0x1
192+
s8 = int(sysFlag >> 26) & 0x1
193+
s4 = int(sysFlag >> 25) & 0x1
194+
s2 = int(sysFlag >> 24) & 0x1
195+
m1 = int(sysFlag >> 17) & 0x7f
196+
m2 = int(sysFlag >> 11) & 0x3f
197+
m4 = int(sysFlag >> 7) & 0xf
198+
m8 = int(sysFlag >> 5) & 0x3
199+
m16 = int(sysFlag >> 4) & 0x1
200+
shiftSize = int(sysFlag >> 0) & 0xf
201+
baseSize = self.DEFAULT_SIZE << shiftSize
202+
203+
print("Shift (system) : %i" % shiftSize)
204+
205+
self.SYSTEM_SIZE = int(baseSize * s16 / 16 + \
206+
baseSize * s8 / 8 + \
207+
baseSize * s4 / 4 + \
208+
baseSize * s2 / 2 + \
209+
baseSize * m1 * 1 + \
210+
baseSize * m2 * 2 + \
211+
baseSize * m4 * 4 + \
212+
baseSize * m8 * 8 + \
213+
baseSize * m16 * 16)
214+
215+
def calculateGfxSize(self, gfxFlag):
216+
s16 = int(gfxFlag >> 27) & 0x1
217+
s8 = int(gfxFlag >> 26) & 0x1
218+
s4 = int(gfxFlag >> 25) & 0x1
219+
s2 = int(gfxFlag >> 24) & 0x1
220+
m1 = int(gfxFlag >> 17) & 0x7f
221+
m2 = int(gfxFlag >> 11) & 0x3f
222+
m4 = int(gfxFlag >> 7) & 0xf
223+
m8 = int(gfxFlag >> 5) & 0x3
224+
m16 = int(gfxFlag >> 4) & 0x1
225+
shiftSize = int(gfxFlag >> 0) & 0xf
226+
baseSize = self.DEFAULT_SIZE << shiftSize
227+
print("Shift (gfx) : %i" % shiftSize)
228+
229+
self.GFX_SIZE = int(baseSize * s16 / 16 + \
230+
baseSize * s8 / 8 + \
231+
baseSize * s4 / 4 + \
232+
baseSize * s2 / 2 + \
233+
baseSize * m1 * 1 + \
234+
baseSize * m2 * 2 + \
235+
baseSize * m4 * 4 + \
236+
baseSize * m8 * 8 + \
237+
baseSize * m16 * 16)
238+
239+
def __init__(self, filename):
240+
self.fs = f = open(filename, "rb")
241+
magic = f.read(4)
242+
version = int.from_bytes(f.read(4), "little")
243+
sysFlag = int.from_bytes(f.read(4), "little")
244+
gfxFlag = int.from_bytes(f.read(4), "little")
245+
246+
calcVersion = self.G(sysFlag, gfxFlag)
247+
248+
if version != 13:
249+
print("Not a valid YTD file")
250+
exit()
251+
252+
print("Magic: %s, 0x%x" % (str(magic), int.from_bytes(magic, "little")))
253+
print("Version in header: %i" % version)
254+
print("System flag = %i, Graphics flag = %i" % (sysFlag, gfxFlag))
255+
256+
valid = "Not valid RSC7 format"
257+
if calcVersion == version:
258+
valid = "Valid RSC7 header"
259+
260+
print("Calculated version '%i'. %s" % (calcVersion, valid))
261+
self.calcSytemSize(sysFlag)
262+
self.calculateGfxSize(gfxFlag)
263+
264+
streamLength = os.fstat(f.fileno()).st_size
265+
266+
sizeFromFlags = self.SYSTEM_SIZE + self.GFX_SIZE + 0x10
267+
print("Size calculated: %i, Size of system: %i, Size of gfx: %i. SIZE: %i"
268+
% (sizeFromFlags, self.SYSTEM_SIZE, self.GFX_SIZE, streamLength))
269+
self.decompress(sizeFromFlags)
270+
271+
self.resources = Resources(self)
272+
273+
def decompress(self, sizeFromFlags):
274+
uncompressed = self.inflate(self.fs.read(int(sizeFromFlags)))
275+
# print(uncompressed)
276+
self.systemData = uncompressed[0:int(self.SYSTEM_SIZE)]
277+
self.gfxData = uncompressed[int(self.SYSTEM_SIZE):int(self.SYSTEM_SIZE + self.GFX_SIZE)]
278+
def finished(self):
279+
self.fs.close()
280+
def dump(self):
281+
sf = open("system.dat", "wb")
282+
sf.write(self.systemData)
283+
sf.close()
284+
285+
gd = open("gfx.dat", "wb")
286+
gd.write(self.gfxData)
287+
gd.close()

0 commit comments

Comments
 (0)