mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-09 20:41:29 -04:00
fe15366912
* Make Ganondorf cape tearing deterministic when using texture replacements * GXIsTexObjReplaced -> has_replacement * Only check for a replacement once during creation * Update Aurora
190 lines
5.5 KiB
Python
190 lines
5.5 KiB
Python
"""
|
|
Loads d_a_mant.rel from CWD, applies tears using seed (66, 16983, 855) and cut type 1, writes PNGs to tear_textures/
|
|
Requires pillow and xxhash.
|
|
"""
|
|
|
|
import math
|
|
import struct
|
|
import xxhash
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
|
|
def yaz0_decompress(data):
|
|
expand_size = struct.unpack_from(">I", data, 4)[0]
|
|
out = bytearray(expand_size)
|
|
src_pos = 0x10
|
|
dst_pos = 0
|
|
chunk_bits_left = 0
|
|
chunk_bits = 0
|
|
|
|
while dst_pos < expand_size:
|
|
if chunk_bits_left == 0:
|
|
chunk_bits = data[src_pos]
|
|
src_pos += 1
|
|
chunk_bits_left = 8
|
|
|
|
if chunk_bits & 0x80:
|
|
out[dst_pos] = data[src_pos]
|
|
src_pos += 1
|
|
dst_pos += 1
|
|
else:
|
|
b0 = data[src_pos]
|
|
b1 = data[src_pos + 1]
|
|
src_pos += 2
|
|
dist = ((b0 & 0x0F) << 8) | b1
|
|
count = b0 >> 4
|
|
if count == 0:
|
|
count = data[src_pos] + 0x12
|
|
src_pos += 1
|
|
else:
|
|
count += 2
|
|
|
|
copy_pos = dst_pos - dist - 1
|
|
for _ in range(count):
|
|
out[dst_pos] = out[copy_pos]
|
|
dst_pos += 1
|
|
copy_pos += 1
|
|
|
|
chunk_bits <<= 1
|
|
chunk_bits_left -= 1
|
|
|
|
return bytes(out)
|
|
|
|
SINCOS_TABLE = tuple(
|
|
(
|
|
math.sin((i * math.tau) / (1 << 13)),
|
|
math.cos((i * math.tau) / (1 << 13)),
|
|
)
|
|
for i in range(1 << 13)
|
|
)
|
|
|
|
def rnd(rng):
|
|
rng[0] = (rng[0] * 171) % 30269
|
|
rng[1] = (rng[1] * 172) % 30307
|
|
rng[2] = (rng[2] * 170) % 30323
|
|
value = rng[0] / 30269.0 + rng[1] / 30307.0 + rng[2] / 30323.0
|
|
return abs(value % 1.0)
|
|
|
|
def rnd_f(rng, max_value):
|
|
return rnd(rng) * max_value
|
|
|
|
def rnd_fx(rng, max_value):
|
|
return max_value * (rnd(rng) - 0.5) * 2.0
|
|
|
|
def linear_index_to_swizzled(linear_index):
|
|
within_tile_x = linear_index & 0x7
|
|
tile_row_offset = (linear_index & 0x78) * 4
|
|
tile_column_offset = (linear_index >> 4) & 0x18
|
|
macro_row_offset = linear_index & 0x3E00
|
|
|
|
return within_tile_x + tile_row_offset + tile_column_offset + macro_row_offset
|
|
|
|
SWIZZLED_TO_XY = [(0, 0)] * 0x4000
|
|
for linear_index in range(0x4000):
|
|
x = linear_index & 0x7F
|
|
y = linear_index >> 7
|
|
swizzled_index = linear_index_to_swizzled(linear_index)
|
|
SWIZZLED_TO_XY[swizzled_index] = (x, y)
|
|
|
|
NEIGHBOR_OFFSETS = (0, 1, 0x80, 0x81, 2, 0x82, 0x102, 0x101, 0x100)
|
|
|
|
def write_c8_texture(c8_data, stage, output_dir, palette, palette_data):
|
|
min_index = min(c8_data)
|
|
max_index = max(c8_data)
|
|
tlut_offset = 2 * min_index
|
|
tlut_size = 2 * (max_index + 1 - min_index)
|
|
tlut_end = tlut_offset + tlut_size
|
|
path = output_dir / (
|
|
f"[{stage:02d}] tex1_{0x80}x{0x80}_"
|
|
f"{xxhash.xxh64(c8_data, seed=0).intdigest():016x}_"
|
|
f"{xxhash.xxh64(palette_data[tlut_offset:tlut_end], seed=0).intdigest():016x}_"
|
|
f"{0x9}.png"
|
|
)
|
|
|
|
rgba = Image.new("RGBA", (0x80, 0x80))
|
|
pixels = rgba.load()
|
|
for swizzled_index, palette_index in enumerate(c8_data):
|
|
x, y = SWIZZLED_TO_XY[swizzled_index]
|
|
pixels[x, y] = palette[palette_index]
|
|
rgba.save(path)
|
|
return path
|
|
|
|
def write_stage(tex, tex_u, pal, stage, output_dir, palette):
|
|
return [
|
|
write_c8_texture(bytes(tex), stage, output_dir, palette, pal),
|
|
write_c8_texture(bytes(tex_u), stage, output_dir, palette, pal),
|
|
]
|
|
|
|
def export_mant_tears():
|
|
rel = yaz0_decompress(Path("d_a_mant.rel").read_bytes())
|
|
tex = bytearray(rel[0x1C00 : 0x1C00 + 0x4000])
|
|
tex_u = bytearray([6] * 0x4000)
|
|
pal = bytes(rel[0x9C00 : 0x9C00 + 0x60])
|
|
rng = [int(66), int(16983), int(855)]
|
|
|
|
Path("tear_textures").mkdir(parents=True, exist_ok=True)
|
|
written = []
|
|
|
|
rgba_palette = []
|
|
for offset in range(0, len(pal), 2):
|
|
color16 = struct.unpack_from(">H", pal, offset)[0]
|
|
if color16 & 0x8000:
|
|
r = (color16 >> 7) & 0xF8
|
|
r |= r >> 5
|
|
g = (color16 >> 2) & 0xF8
|
|
g |= g >> 5
|
|
b = (color16 << 3) & 0xF8
|
|
b |= b >> 5
|
|
a = 255
|
|
else:
|
|
r = (color16 >> 4) & 0xF0
|
|
r |= r >> 4
|
|
g = color16 & 0xF0
|
|
g |= g >> 4
|
|
b = (color16 << 4) & 0xF0
|
|
b |= b >> 4
|
|
a = (color16 >> 7) & 0xE0
|
|
a |= (a >> 3) | (a >> 6)
|
|
rgba_palette.append((r, g, b, a))
|
|
while len(rgba_palette) < 256:
|
|
rgba_palette.append((0, 0, 0, 0))
|
|
|
|
written.extend(write_stage(tex, tex_u, pal, 0, Path("tear_textures"), rgba_palette))
|
|
|
|
cut_step = 0
|
|
for _ in range(15):
|
|
cut_step += 1
|
|
|
|
angle = int(rnd_f(rng, 65536.0)) & 0xFFFF
|
|
if angle >= 0x8000:
|
|
angle -= 0x10000
|
|
|
|
x = rnd_fx(rng, 32.0)
|
|
y = rnd_fx(rng, 32.0)
|
|
sincos_index = (angle & 0xFFFF) >> (16 - 13)
|
|
sin_v, cos_v = SINCOS_TABLE[sincos_index]
|
|
|
|
for i, texel_count in enumerate(
|
|
tuple(
|
|
1 if i <= 3 or i >= 26 else (9 if 12 <= i <= 18 else 4)
|
|
for i in range(30)
|
|
)
|
|
):
|
|
x += sin_v
|
|
y -= cos_v
|
|
|
|
packed = int(x + 64.0) | (int(y + 64.0) << 7)
|
|
for j in range(texel_count):
|
|
u_var1 = packed + NEIGHBOR_OFFSETS[j]
|
|
if 0 <= u_var1 < 0x4000:
|
|
i_var5 = linear_index_to_swizzled(u_var1)
|
|
tex[i_var5] = 0
|
|
tex_u[i_var5] = 0
|
|
|
|
written.extend(write_stage(tex, tex_u, pal, cut_step, Path("tear_textures"), rgba_palette))
|
|
|
|
return written
|
|
|
|
if __name__ == "__main__":
|
|
export_mant_tears()
|