Last active
March 25, 2024 20:50
-
-
Save penguino118/afeb198ad6ba5495311bc24fe195548e to your computer and use it in GitHub Desktop.
Phantom Blood PS2 Texture Converter ( P2TX / TEX2 -> TIM2 )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sys | |
import struct | |
import argparse | |
import subprocess | |
from pathlib import Path | |
def ru08(buf, offset): | |
return struct.unpack("<B", buf[offset:offset+1])[0] | |
def ru16(buf, offset): | |
return struct.unpack("<H", buf[offset:offset+2])[0] | |
def ru32(buf, offset): | |
return struct.unpack("<I", buf[offset:offset+4])[0] | |
def wu08(value): | |
return struct.pack("<B", value) | |
def wu16(value): | |
return struct.pack("<H", value) | |
def wu32(value): | |
return struct.pack("<I", value) | |
def find_STR(buf, string): | |
if string == 'TEX2': | |
MAGIC = bytearray((int(0x54), int(0x45), int(0x58), int(0x32))) | |
elif string == 'P2TX': | |
MAGIC = bytearray((int(0x50), int(0x32), int(0x54), int(0x58))) | |
offsets = [] | |
off_start = 0x0 | |
off_end = len(buf) | |
exit = False | |
while off_start < off_end and exit == False: | |
offset = buf.find(MAGIC, off_start, off_end) | |
if offset == -1: | |
exit = True | |
else: | |
offsets.append(offset) | |
off_start += offset+4 | |
return offsets | |
def Swizzle8to32(mode, reversed, pInTexels, width, height): | |
# https://ps2linux.no-ip.info/playstation2-linux.com/docs/howto/display_docef7c.html?docid=75 | |
if reversed: pInTexels.reverse() #For P2TX | |
pSwizTexels = pInTexels.copy() | |
for y in range(height): | |
for x in range(width): | |
uPen = pInTexels[y*width+x] | |
block_location = (y&(~0xf))*width + (x&(~0xf))*2 #every 2nd row of 4 bytes is right | |
swap_selector = (((y+2)>>2)&0x1)*4 | |
posY = (((y&(~3))>>1) + (y&1))&0x7 | |
column_location = posY*width*2 + ((x+(swap_selector))&0x7)*4 | |
byte_num = ((y>>1)&1) + ((x>>2)&2) # 0,1,2,3 | |
if mode == 'unswizzle': | |
pSwizTexels[y * width + x] = pInTexels[column_location + block_location + byte_num] | |
elif mode == 'swizzle': | |
pSwizTexels[column_location + block_location + byte_num] = uPen | |
if reversed: pSwizTexels.reverse() #For P2TX | |
# aa = pSwizTexels[:16] | |
# bb = pSwizTexels[:16] | |
return pSwizTexels | |
def Swizzle4to32(mode, reversed, pInTexels, width, height): | |
# https://ps2linux.no-ip.info/playstation2-linux.com/docs/howto/display_docef7c.html?docid=75 | |
if reversed: pInTexels.reverse() #For P2TX | |
pSwizTexels = pInTexels.copy() | |
for y in range(height): | |
for x in range(width): | |
index = y*width+x | |
uPen = (pInTexels[(index>>1)]>>((index&1)*4))&0xf | |
#uPen = pInTexels[index >> 1] & 0xff | |
# swizzle | |
pageX = x & (~0x7f) | |
pageY = y & (~0x7f) | |
pages_horz = (width+127)//128 | |
pages_vert = (height+127)//128 | |
page_number = (pageY//128)*pages_horz + (pageX//128) | |
page32Y = (page_number//pages_vert)*32 | |
page32X = (page_number%pages_vert)*64 | |
page_location = page32Y*height*2 + page32X*4 | |
locX = x & 0x7f | |
locY = y & 0x7f | |
block_location = ((locX&(~0x1f))>>1)*height + (locY&(~0xf))*2 | |
swap_selector = (((y+2)>>2)&0x1)*4 | |
posY = (((y&(~3))>>1) + (y&1))&0x7 | |
column_location = posY*height*2 + ((x+swap_selector)&0x7)*4 | |
byte_num = (x>>3)&3 # 0,1,2,3 | |
bits_set = (y>>1)&1 # 0,1 (lower/upper 4 bits) | |
if mode == 'unswizzle': | |
setPix = pInTexels[page_location + block_location + column_location + byte_num] | |
uPen = (setPix >> (bits_set * 4)) & 0xf | |
pSwizTexels[index>>1] &= ~(0xf<<((index & 1)*4)) | |
pSwizTexels[index>>1] |= uPen<<((index & 1)*4) | |
elif mode == 'swizzle': | |
setPix = pSwizTexels[page_location + block_location + column_location + byte_num] | |
setPix = (setPix & (-bits_set)) | (uPen << (bits_set * 4)) | |
pSwizTexels[page_location + block_location + column_location + byte_num] = setPix | |
if reversed: pSwizTexels.reverse() #For P2TX | |
return pSwizTexels | |
def Swizzle16to32(mode, reversed, pInTexels, width, height): | |
pSwizTexels = pInTexels.copy() | |
if reversed: pInTexels.reverse() #For P2TX | |
pSwizTexels = pInTexels.copy() | |
#height = height//2 | |
for y in range(height): | |
for x in range(width): | |
#print(y, x) | |
uCol = pInTexels[y*width+x] | |
pageX = x & (~0x3f) | |
pageY = y & (~0x3f) | |
pages_horz = (width+63)//64 | |
pages_vert = (height+63)//64 | |
page_number = (pageY//64)*pages_horz + (pageX//64) | |
page32Y = (page_number//pages_vert)*32 | |
page32X = (page_number%pages_vert)*64 | |
page_location = (page32Y*height + page32X)*2 | |
locX = x & 0x3f | |
locY = y & 0x3f | |
block_location = (locX&(~0xf))*height + (locY&(~0x7))*2 | |
column_location = ((y&0x7)*height + (x&0x7))*2 | |
short_num = (x>>3)&1 #0,1 | |
if mode == 'unswizzle': | |
pSwizTexels[y*width+x] = pInTexels[page_location + block_location + column_location + short_num] | |
elif mode == 'swizzle': | |
pSwizTexels[page_location + block_location + column_location + short_num] = uCol | |
if reversed: pSwizTexels.reverse() #For P2TX | |
return pSwizTexels | |
def TEX2toTIM2(buf, keep_alpha, keep_swizz): | |
offset = 0 | |
imgcount = ru32(buf, 0x4) | |
def get_img(buf, img_offset, img_length, resolution, type, keep_swizz): | |
image = buf[img_offset:img_length] | |
if keep_swizz == False: | |
if type == 0x501 or type == 0x502 or type == 0x504 or type == 0x508: return Swizzle4to32('unswizzle', False, image, resolution, resolution) #4bpp, 16color | |
#elif type == : return Swizzle4to32('unswizzle', image, resolution, resolution) #4bpp, 256color | |
elif type == 0x4C2 or type == 0x4C4 or type == 0x4C8: return Swizzle8to32('unswizzle', False, image, resolution, resolution) #8bpp, 256color | |
else: raise Exception(f"Unhandled swizzling ({hex(type)}), ping @penguino118 on discord 300 times") | |
else: | |
return image | |
def get_pal(buf, pal_offset, keep_alpha): | |
color_count = ru08(buf, pal_offset) * 4 | |
byte_size = ru08(buf, pal_offset+0x1) * 8 | |
pal_offset += 0x10 | |
if keep_alpha == False: #0-128 alpha to 0-255 | |
outpal = buf[pal_offset:pal_offset+color_count*4]#byte_size] | |
for y in range(color_count): | |
alpha = outpal[0x3 + (y * 4)] | |
if alpha == 128: alpha = 255 | |
else: alpha = alpha * 2 | |
outpal[0x3 + (y * 4)] = alpha | |
return outpal | |
else: | |
return buf[pal_offset:pal_offset+byte_size] | |
tex2_head_unk = ru32(buf, 0xC) | |
image_meta = [] | |
images = [] | |
palettes = [] | |
offset = 0x10 | |
for x in range(imgcount): | |
resolution = ru16(buf, offset) | |
unk0_short = ru16(buf, offset+0x2) #swizzle mode? | |
unk1_short = ru16(buf, offset+0x4) | |
unk2_short = ru16(buf, offset+0x6) | |
image_offset = ru32(buf, offset+0x8) | |
unk3_int = ru32(buf, offset+0x74) | |
palette_offset = ru32(buf, offset+0x78) | |
unk4_int = ru16(buf, offset+0x80) | |
#print(hex(offset)) | |
color_count = ru08(buf, palette_offset) * 4 | |
image_meta.append([color_count, resolution, | |
unk0_short, unk1_short, unk2_short, unk3_int, unk4_int]) | |
#print(f"{buf[offset:offset+0x4].hex()}{buf[offset+0x4:offset+0x8].hex()}{buf[offset+0x8:offset+0xC].hex()}{buf[offset+0xC:offset+0x10].hex()}") | |
images.append( get_img(buf, image_offset+0x10, palette_offset, resolution, unk0_short, keep_swizz) ) | |
palettes.append( get_pal(buf, palette_offset, keep_alpha) ) | |
offset += 0x90 | |
NewTIM2 = [] | |
NewTIM2.append(wu32(843925844)) #TIM2 | |
NewTIM2.append(wu16(4)) | |
NewTIM2.append(wu16(imgcount)) | |
NewTIM2.append(wu32(0)) | |
NewTIM2.append(wu32(0)) | |
for x in range(imgcount): | |
curr_meta = image_meta[x] | |
curr_img = images[x] | |
curr_pal = palettes[x] | |
img_length = wu32( len(curr_img) + len(curr_pal) + 0x30 ) | |
pal_length = wu32( len(curr_pal) ) | |
img_dat_length = wu32( len(curr_img) ) | |
header_length = wu16( 0x30 ) | |
color_count = wu16( curr_meta[0] ) | |
img_format = wu08( 0x00 ) | |
mipmap_count = wu08( 0x01 ) | |
pal_format = wu08( 0x03 ) | |
if curr_meta[2] == 0x4C8 or curr_meta[2] == 0x4C4 or curr_meta[2] == 0x4C2: csm_register = wu08( 0x05 ) | |
else:csm_register = wu08( 0x04 ) | |
width = wu16( curr_meta[1] ) | |
height = wu16( curr_meta[1] ) | |
GsBs0 = wu32(1668903792) | |
GsBs1 = wu16(28520) | |
GsBs2 = wu16(curr_meta[2]) #TEX(0x02) | |
GsBs3 = wu16(curr_meta[3]) #TEX(0x04) | |
GsBs4 = wu16(curr_meta[4]) #TEX(0x06) | |
GsBs5 = wu32(curr_meta[5]) #TEX(0x74) | |
GsBs6 = wu32(curr_meta[6]) #TEX(0x80) | |
GsBs7 = wu32(tex2_head_unk) | |
NewTIM2.append(img_length) | |
NewTIM2.append(pal_length) | |
NewTIM2.append(img_dat_length) | |
NewTIM2.append(header_length) | |
NewTIM2.append(color_count) | |
NewTIM2.append(img_format) | |
NewTIM2.append(mipmap_count) | |
NewTIM2.append(pal_format) | |
NewTIM2.append(csm_register) | |
NewTIM2.append(width) | |
NewTIM2.append(height) | |
NewTIM2.append(GsBs0) | |
NewTIM2.append(GsBs1) | |
NewTIM2.append(GsBs2) | |
NewTIM2.append(GsBs3) | |
NewTIM2.append(GsBs4) | |
NewTIM2.append(GsBs5) | |
NewTIM2.append(GsBs6) | |
NewTIM2.append(GsBs7) | |
for y in curr_img: NewTIM2.append(wu08(y)) | |
for y in curr_pal: NewTIM2.append(wu08(y)) | |
return NewTIM2 | |
def P2TXtoTIM2(buf, keep_alpha, keep_swizz): | |
def get_img(buf, img_offset, img_length, resolution, col_count, type, keep_swizz): | |
image = buf[img_offset:img_length] | |
if keep_swizz == False: | |
if col_count == 16 and (type == 0x5DC4 or type == 0x6208 or type == 0xDDC4): return Swizzle4to32('unswizzle', False, image, resolution, resolution) #4bpp, 16color | |
elif col_count == 256 and (type == 0x6208 or type == 0x5DC4 or type == 0x5982): return Swizzle8to32('unswizzle', True, image, resolution, resolution) #8bpp, 256color | |
else: return image | |
#else: raise Exception(f"Unhandled swizzling ({hex(type)}), ping @penguino118 on discord 300 times") | |
else: | |
return image | |
def get_pal(buf, pal_offset, keep_alpha): | |
color_count = ru08(buf, pal_offset) * 4 | |
byte_size = ru08(buf, pal_offset+0x1) * 8 | |
pal_offset += 0x10 | |
if keep_alpha == False: #0-128 alpha to 0-255 | |
outpal = buf[pal_offset:pal_offset+color_count*4]#byte_size] | |
for y in range(color_count): | |
alpha = outpal[0x3 + (y * 4)] | |
if alpha == 128: alpha = 255 | |
else: alpha = alpha * 2 | |
outpal[0x3 + (y * 4)] = alpha | |
return outpal | |
else: | |
return buf[pal_offset:pal_offset+byte_size] | |
tex2_head_unk = ru32(buf, 0xC) | |
image_meta = [] | |
images = [] | |
palettes = [] | |
offset = 0x10 | |
imgcount = ru32(buf, 0x4 ) | |
# hack for when a p2tx has exactly one swizzled iamge and it has to offset every other fucking image | |
fucking_swizzle_offset = False | |
for x in range(imgcount): | |
if ru32(buf, offset+(0x10*(x+1))) == 0: | |
fucking_swizzle_offset = True | |
print(fucking_swizzle_offset) | |
for x in range(imgcount): | |
image_offset = ru32(buf, offset) | |
palette_offset = ru32(buf, offset+0x4) | |
resolution = ru16(buf, offset+0x8) | |
unk0_short = ru16(buf, offset+0xA) # swizzle mode ? | |
unk1_short = ru16(buf, offset+0xC) | |
unk2_short = ru16(buf, offset+0xE) # related to color count // (13 = 256 colors || 14 = 16 colors) | |
print(f"index={x}, imgoffs={image_offset}, paloffs={palette_offset}") | |
color_count = ru08(buf, palette_offset) * 4 # thou still think this is more reliable than that ^ lol | |
#if unk0_short == 0x5DC4 or unk0_short == 0x6208 or unk0_short == 0xDDC4: #swizzled images have one extra line of garbage | |
if ru32(buf, offset+0x10) == 0: | |
unk3_int = ru32(buf, offset+0x1A) | |
image_meta.append([color_count, resolution, | |
unk0_short, unk1_short, unk2_short, unk3_int]) | |
#image_offset += 0x10 | |
offset += 0x10 | |
else: | |
image_meta.append([color_count, resolution, | |
unk0_short, unk1_short, unk2_short]) | |
if not fucking_swizzle_offset: | |
images.append( get_img(buf, image_offset+0x10, palette_offset, resolution, color_count, unk0_short, keep_swizz) ) | |
else: | |
images.append( get_img(buf, image_offset+0x20, palette_offset, resolution, color_count, unk0_short, keep_swizz) ) | |
palettes.append( get_pal(buf, palette_offset, keep_alpha) ) | |
offset += 0x10 | |
NewTIM2 = [] | |
NewTIM2.append(wu32(843925844)) #TIM2 | |
NewTIM2.append(wu16(4)) | |
NewTIM2.append(wu16(imgcount)) | |
NewTIM2.append(wu32(0)) | |
NewTIM2.append(wu32(0)) | |
for x in range(imgcount): | |
curr_meta = image_meta[x] | |
curr_img = images[x] | |
curr_pal = palettes[x] | |
img_length = wu32( len(curr_img) + len(curr_pal) + 0x30 ) | |
pal_length = wu32( len(curr_pal) ) | |
img_dat_length = wu32( len(curr_img) ) | |
header_length = wu16( 0x30 ) | |
color_count = wu16( curr_meta[0] ) | |
img_format = wu08( 0x00 ) | |
mipmap_count = wu08( 0x01 ) | |
pal_format = wu08( 0x03 ) | |
if int.from_bytes(color_count, "little") == 256: csm_register = wu08( 0x05 ) | |
else:csm_register = wu08( 0x04 ) | |
width = wu16( curr_meta[1] ) | |
height = wu16( curr_meta[1] ) | |
GsBs0 = wu16(65535) #ff | |
GsBs1 = wu32(1481912912) #P2TX | |
GsBs2 = wu16(65535) #ff | |
GsBs3 = wu16(curr_meta[2]) #TX (0x0A) | |
GsBs4 = wu16(curr_meta[3]) #TX (0x0C) | |
GsBs5 = wu16(curr_meta[4]) #TX (0x0E) | |
GsBs6 = wu16(65535) #ff | |
if len(curr_meta) == 6: | |
GsBs7 = wu32(curr_meta[5]) | |
else: | |
GsBs7 = wu32(65535) #ff | |
GsBs8 = wu32(0) | |
NewTIM2.append(img_length) | |
NewTIM2.append(pal_length) | |
NewTIM2.append(img_dat_length) | |
NewTIM2.append(header_length) | |
NewTIM2.append(color_count) | |
NewTIM2.append(img_format) | |
NewTIM2.append(mipmap_count) | |
NewTIM2.append(pal_format) | |
NewTIM2.append(csm_register) | |
NewTIM2.append(width) | |
NewTIM2.append(height) | |
NewTIM2.append(GsBs0) | |
NewTIM2.append(GsBs1) | |
NewTIM2.append(GsBs2) | |
NewTIM2.append(GsBs3) | |
NewTIM2.append(GsBs4) | |
NewTIM2.append(GsBs5) | |
NewTIM2.append(GsBs6) | |
NewTIM2.append(GsBs7) | |
NewTIM2.append(GsBs8) | |
for y in curr_img: NewTIM2.append(wu08(y)) | |
for y in curr_pal: NewTIM2.append(wu08(y)) | |
return NewTIM2 | |
############ TIM2 to... ############ | |
def TIM2toTEX2(buf, keep_alpha, keep_swizz): | |
minihead = wu32(134217728) | |
def get_img(buf, img_offset, img_length, type, keep_swizz): | |
image = [] | |
image_buffer = buf[img_offset:img_offset+img_length] | |
if keep_swizz == False: | |
if type == 8: image_buffer = Swizzle8to32('swizzle', False, image_buffer, resolution, resolution) | |
elif type == 4: image_buffer = Swizzle4to32('swizzle', False, image_buffer, resolution, resolution) | |
img_minihead = wu32((img_length//16)+32768) | |
image.append(img_minihead) | |
image.append(minihead) | |
image.append(wu32(0)) | |
image.append(wu32(0)) | |
for byte in image_buffer: image.append(wu08(byte)) | |
return image | |
def get_pal(buf, pal_offset, pal_length, keep_alpha): | |
palette = [] | |
palette_buffer = buf[pal_offset:pal_offset+pal_length] | |
color_count = pal_length // 4 | |
if keep_alpha == False: | |
for y in range(color_count): | |
alpha = palette_buffer[0x3 + (y * 4)] | |
if alpha == 255: alpha = 128 | |
else: alpha = alpha // 2 | |
palette_buffer[0x3 + (y * 4)] = alpha | |
pal_minihead = wu32((color_count//4)+32768) | |
palette.append(pal_minihead) | |
palette.append(minihead) | |
palette.append(wu32(0)) | |
palette.append(wu32(0)) | |
for byte in palette_buffer: palette.append(wu08(byte)) | |
return palette | |
offset = 0x10 | |
imgcount = ru16(buf, 0x6) | |
tex2_head_unk = ru32(buf, 0x3C) | |
header_length = 0x10 + (imgcount*0x90) | |
images = [] | |
palettes = [] | |
NewTEX2 = [] | |
NewTEX2.append(wu32(844645716)) #TIM2 | |
NewTEX2.append(wu32(imgcount)) | |
NewTEX2.append(wu32(0)) | |
NewTEX2.append(wu32(tex2_head_unk)) | |
prev_img_length = 0 | |
#building header | |
for x in range(imgcount): | |
#FROM TIM2 | |
total_img_length = ru32(buf, offset) | |
palette_length = ru32(buf, offset+0x4) | |
image_length = ru32(buf, offset+0x8) | |
color_count = ru16(buf, offset+0xE) | |
resolution = ru16(buf, offset+0x14) | |
unk0_short = ru16(buf, offset+0x1E) #swizzle mode? | |
unk1_short = ru16(buf, offset+0x20) | |
unk2_short = ru16(buf, offset+0x22) | |
unk3_int = ru32(buf, offset+0x24) | |
unk4_int = ru32(buf, offset+0x28) | |
if color_count == 16: type = 4 | |
else: type = 8 | |
images.append(get_img(buf, offset+0x30, image_length, type, keep_swizz)) | |
palettes.append(get_pal(buf, offset+0x30+image_length, palette_length, keep_alpha)) | |
offset += total_img_length | |
#TO TEX2 | |
tex2_img_offset = header_length + prev_img_length | |
tex2_pal_offset = header_length + prev_img_length + (image_length+0x10) | |
NewTEX2.append(wu16(resolution)) | |
NewTEX2.append(wu16(unk0_short)) | |
NewTEX2.append(wu16(unk1_short)) | |
NewTEX2.append(wu16(unk2_short)) | |
NewTEX2.append(wu32(tex2_img_offset)) | |
for i in range(26): NewTEX2.append(wu32(0x0)) #pad | |
NewTEX2.append(wu32(unk3_int)) | |
NewTEX2.append(wu32(tex2_pal_offset)) | |
NewTEX2.append(wu32(0)) #pad | |
NewTEX2.append(wu32(unk4_int)) | |
for i in range(3): NewTEX2.append(wu32(0x0)) #pad | |
prev_img_length += total_img_length - 0x10 | |
#offset = 0x10 | |
for x in range(imgcount): | |
curr_img = images[x] | |
curr_pal = palettes[x] | |
img_offset = len(NewTEX2) | |
for n in curr_img: NewTEX2.append(n) | |
pal_offset = len(NewTEX2) | |
for n in curr_pal: NewTEX2.append(n) | |
# NewTEX2[offset+0x08] = wu32(img_offset) #header img offset | |
# NewTEX2[offset+0x78] = wu32(pal_offset) #header pal offset | |
#offset += 0x90 | |
return NewTEX2 | |
def TIM2toP2TX(buf, keep_alpha, keep_swizz): | |
minihead = wu32(134217728) | |
def get_img(buf, colcount, img_offset, img_length, type, keep_swizz): | |
image = [] | |
image_buffer = buf[img_offset:img_offset+img_length] | |
if keep_swizz == False: | |
if colcount == 16 and (type == 0x5DC4 or type == 0x6208 or type == 0xDDC4): image_buffer = Swizzle4to32('swizzle', True, image_buffer, resolution, resolution) | |
elif colcount == 256 and (type == 0x6208 or type == 0x5DC4 or type == 0x5982): image_buffer = Swizzle8to32('swizzle', True, image_buffer, resolution, resolution) | |
#if col_count == 16 and (type == 0x5DC4 or type == 0x6208 or type == 0xDDC4): return Swizzle4to32('unswizzle', False, image, resolution, resolution) #4bpp, 16color | |
# elif col_count == 256 and (type == 0x6208 or type == 0x5DC4 or type == 0x5982): | |
img_minihead = wu32((img_length//16)+32768) | |
image.append(img_minihead) | |
image.append(minihead) | |
image.append(wu32(0)) | |
image.append(wu32(0)) | |
for byte in image_buffer: image.append(wu08(byte)) | |
return image | |
def get_pal(buf, pal_offset, pal_length, keep_alpha): | |
palette = [] | |
palette_buffer = buf[pal_offset:pal_offset+pal_length] | |
color_count = pal_length // 4 | |
if keep_alpha == False: | |
for y in range(color_count): | |
alpha = palette_buffer[0x3 + (y * 4)] | |
if alpha == 255: alpha = 128 | |
else: alpha = alpha // 2 | |
palette_buffer[0x3 + (y * 4)] = alpha | |
pal_minihead = wu32((color_count//4)+32768) | |
palette.append(pal_minihead) | |
palette.append(minihead) | |
palette.append(wu32(0)) | |
palette.append(wu32(0)) | |
for byte in palette_buffer: palette.append(wu08(byte)) | |
return palette | |
offset = 0x10 | |
imgcount = ru16(buf, 0x6) | |
header_length = 0x10 + (imgcount*0x10) | |
images = [] | |
palettes = [] | |
NewP2TX = [] | |
NewP2TX.append(wu32(1481912912)) #P2TX | |
NewP2TX.append(wu32(imgcount)) | |
NewP2TX.append(wu32(0)) | |
NewP2TX.append(wu32(0)) | |
prev_img_length = 0 | |
#building header | |
for x in range(imgcount): | |
#FROM TIM2 | |
total_img_length = ru32(buf, offset) | |
palette_length = ru32(buf, offset+0x4) | |
image_length = ru32(buf, offset+0x8) | |
color_count = ru16(buf, offset+0xE) | |
resolution = ru16(buf, offset+0x14) | |
unk0_short = ru16(buf, offset+0x20) #swizzle mode? | |
unk1_short = ru16(buf, offset+0x22) | |
unk2_short = ru16(buf, offset+0x24) | |
unk3_int = ru32(buf, offset+0x28) #swizzled only header val | |
#if color_count == 16: type = 4 | |
#else: type = 8 | |
images.append(get_img(buf, color_count, offset+0x30, image_length, unk0_short, keep_swizz)) | |
palettes.append(get_pal(buf, offset+0x30+image_length, palette_length, keep_alpha)) | |
offset += total_img_length | |
#SWIZZLE TRASH!!!!!!! | |
if unk3_int != 65535: # probably wrong but i cant find any cases of multi image swizzled p2txs | |
tex2_img_offset = header_length + prev_img_length | |
tex2_pal_offset = 0x10 + header_length + prev_img_length + (image_length+0x10) | |
else: | |
tex2_img_offset = header_length + prev_img_length | |
tex2_pal_offset = header_length + prev_img_length + (image_length+0x10) | |
#P2TX header | |
NewP2TX.append(wu32(tex2_img_offset)) | |
NewP2TX.append(wu32(tex2_pal_offset)) | |
NewP2TX.append(wu16(resolution)) | |
NewP2TX.append(wu16(unk0_short)) | |
NewP2TX.append(wu16(unk1_short)) | |
NewP2TX.append(wu16(unk2_short)) | |
#SWIZZLE TRASH!!!!!!! | |
if unk3_int != 65535: | |
for i in range(2): NewP2TX.append(wu32(0x0)) #pad | |
NewP2TX.append(wu16(resolution//2)) | |
NewP2TX.append(wu32(unk3_int)) | |
NewP2TX.append(wu16(0)) | |
prev_img_length += 0x10 | |
prev_img_length += total_img_length - 0x10 | |
#offset = 0x10 | |
for x in range(imgcount): | |
curr_img = images[x] | |
curr_pal = palettes[x] | |
img_offset = len(NewP2TX) | |
for n in curr_img: NewP2TX.append(n) | |
pal_offset = len(NewP2TX) | |
for n in curr_pal: NewP2TX.append(n) | |
# NewTEX2[offset+0x08] = wu32(img_offset) #header img offset | |
# NewTEX2[offset+0x78] = wu32(pal_offset) #header pal offset | |
#offset += 0x90 | |
return NewP2TX | |
def Get_Images(directory): | |
image_array = [] | |
pathlist = Path(directory).glob('*.tm2') | |
for file_path in pathlist: | |
#last term separated by '_' (offset) to INT | |
file_offset = int( str(file_path).split('_')[-1].split('.')[0] ,16) | |
file_buffer = bytearray( open(str(file_path), "rb").read() ) | |
test_str = ru32(file_buffer, 0x2A) | |
if test_str == 1481912912: # P2TX | |
output_tex = TIM2toP2TX(file_buffer, args.keepalpha, args.keepswizz) | |
elif test_str == 1869112185: # TEX2 | |
output_tex = TIM2toTEX2(file_buffer, args.keepalpha, args.keepswizz) | |
else: | |
raise Exception(f"{file_path}: Missing output format from TIM2. ({hex(test_str)})") | |
image_array.append([file_offset, output_tex]) | |
return image_array | |
def Inject_TM2s(buf, img_array): | |
output_buffer = buf.copy() | |
for image in img_array: | |
offset = int(image[0]) | |
#print(image[1]) | |
new_buffer = bytearray(b''.join(image[1])) #byte lIST to byte array LOL | |
#print(f"{offset}:{offset} + {len(new_buffer)}") | |
#print(f"{type(offset)}:{type(offset)} + {type(len(new_buffer))}") | |
output_buffer[offset:offset + len(new_buffer)] = new_buffer | |
return output_buffer | |
parser = argparse.ArgumentParser(description='TEX2 Converter') | |
parser.add_argument("-i", "--inpath", required=True, help="File Input (TEX2/TIM2)") | |
parser.add_argument("-ri", "--repack_inpath", help="Edited Image(s) Folder (TEX2/TIM2)") | |
parser.add_argument("-o", "--outpath", required=True, help="File Output (TIM2/TEX2)") | |
parser.add_argument("-sc", "--scan", action='store_true', help="Scans input file for TEX2 data and converts all found textures. Make sure the output folder exists.") | |
parser.add_argument("-ka", "--keepalpha", action='store_true', help="Keep original alpha range (0-128).") | |
parser.add_argument("-ks", "--keepswizz", action='store_true', help="Keep original swizzled/unswizzled pixel order.") | |
args = parser.parse_args() | |
if not args.repack_inpath is None: #Repack | |
print("hi") | |
with open(args.inpath, "rb") as input_file: | |
print(type(input_file)) | |
input_file_buffer = bytearray( input_file.read() ) | |
tm2_array = Get_Images(args.repack_inpath) | |
out_buffer = Inject_TM2s(input_file_buffer, tm2_array) | |
with open(args.outpath, "wb") as outfile: | |
#for byte in out_buffer: | |
# print(byte) | |
# print(byte.hex()) | |
outfile.write(out_buffer) | |
print(f"Saved repacked file to {args.outpath}") | |
elif not args.inpath.endswith(".tm2"): #TEX2 or P2TX input is assumed | |
def save_tim2(input_file_buffer, output_tm2, outpath): | |
with open(outpath, "wb") as outfile: | |
for byte in output_tm2: | |
outfile.write(byte) | |
print(f"Saved TIM2 to {outpath}") | |
if args.scan == True: #TEX2 & P2TX filescan | |
with open(args.inpath, "rb") as input_file: | |
input_file_buffer = bytearray( input_file.read() ) | |
tex2_offsets = find_STR(input_file_buffer, 'TEX2') | |
p2tx_offsets = find_STR(input_file_buffer, 'P2TX') | |
print("Scanning for TEX2 images...") | |
for offset in tex2_offsets: | |
tex2 = input_file_buffer[offset:] | |
output_tm2 = TEX2toTIM2(tex2, args.keepalpha, args.keepswizz) | |
outpath = args.outpath | |
outpath = (f"{Path(outpath).absolute()}\\{Path(args.inpath).stem}_TEX2_{hex(offset)}.tm2") | |
save_tim2(input_file_buffer, output_tm2, outpath) | |
print("Scanning for P2TX images...") | |
for offset in p2tx_offsets: | |
p2tx = input_file_buffer[offset:] | |
output_tm2 = P2TXtoTIM2(p2tx, args.keepalpha, args.keepswizz) | |
outpath = args.outpath | |
outpath = (f"{Path(outpath).absolute()}\\{Path(args.inpath).stem}_P2TX_{hex(offset)}.tm2") | |
save_tim2(input_file_buffer, output_tm2, outpath) | |
if args.outpath.endswith(".tm2"): #TEX2||P2TX TO TIM2 | |
with open(args.inpath, "rb") as input_file: | |
input_file_buffer = bytearray( input_file.read() ) | |
test_str = ru32(input_file_buffer, 0x0) | |
if test_str == 1481912912: #P2TX | |
output_tm2 = P2TXtoTIM2(input_file_buffer, args.keepalpha, args.keepswizz) | |
elif test_str == 844645716: #TEX2 | |
output_tm2 = TEX2toTIM2(input_file_buffer, args.keepalpha, args.keepswizz) | |
else: | |
raise Exception(f"Unknown image type on input file. ({hex(test_str)})") | |
save_tim2(input_file_buffer, output_tm2, args.outpath) | |
elif args.inpath.endswith(".tm2"): #TEX2||P2TX output is assumed | |
with open(args.inpath, "rb") as input_file: | |
input_file_buffer = bytearray( input_file.read() ) | |
test_str = ru32(input_file_buffer, 0x2A) | |
if test_str == 1481912912: # P2TX | |
output_tex = TIM2toP2TX(input_file_buffer, args.keepalpha, args.keepswizz) | |
elif test_str == 1869112185: # TEX2 | |
output_tex = TIM2toTEX2(input_file_buffer, args.keepalpha, args.keepswizz) | |
else: | |
raise Exception(f"Missing output format from TIM2. ({hex(test_str)})") | |
with open(args.outpath, "wb") as outfile: | |
for byte in output_tex: | |
#print(byte) | |
#print(byte.hex()) | |
outfile.write(byte) | |
print(f"Saved texture to {args.outpath}") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment