Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
# coding=utf-8
# Work-in-progress Nintendo texture library by Zhenёq
# https://github.com/Zheneq/Noesis-Plugins
# Acknowledgements:
# http://wiki.tockdom.com - format specs
from inc_noesis import *
import rapi
NINTEX_VERSION = 20180721
NINTEX_I4 = 0x00
NINTEX_I8 = 0x01
NINTEX_IA4 = 0x02
NINTEX_IA8 = 0x03
NINTEX_RGB565 = 0x04
NINTEX_RGB5A3 = 0x05
NINTEX_RGBA32 = 0x06
NINTEX_C4 = 0x08
NINTEX_C8 = 0x09
NINTEX_C14X2 = 0x0A
NINTEX_CMPR = 0x0E
class pixelParser:
@staticmethod
def i4(rawPixel):
t = bytearray(4)
t[0] = rawPixel * 0x11
t[1] = rawPixel * 0x11
t[2] = rawPixel * 0x11
t[3] = 0xFF
return t
@staticmethod
def i8(rawPixel):
t = bytearray(4)
t[0] = rawPixel
t[1] = rawPixel
t[2] = rawPixel
t[3] = 0xFF
return t
@staticmethod
def ia4(rawPixel):
t = bytearray(4)
t[0] = (rawPixel & 0xF) * 0x11
t[1] = (rawPixel & 0xF) * 0x11
t[2] = (rawPixel & 0xF) * 0x11
t[3] = (rawPixel >> 4) * 0x11
return t
@staticmethod
def ia8(rawPixel):
t = bytearray(4)
t[0] = rawPixel & 0xFF
t[1] = rawPixel & 0xFF
t[2] = rawPixel & 0xFF
t[3] = rawPixel >> 8
return t
@staticmethod
def rgba32(rawPixel):
t = bytearray(4)
t[0] = (rawPixel >> 24) & 0xFF
t[1] = (rawPixel >> 16) & 0xFF
t[2] = (rawPixel >> 8) & 0xFF
t[3] = (rawPixel >> 0) & 0xFF
return t
@staticmethod
def rgb5a3(rawPixel):
t = bytearray(4)
if rawPixel & 0x8000 != 0: # r5g5b5
t[0] = (((rawPixel >> 10) & 0x1F) * 0xFF // 0x1F)
t[1] = (((rawPixel >> 5) & 0x1F) * 0xFF // 0x1F)
t[2] = (((rawPixel >> 0) & 0x1F) * 0xFF // 0x1F)
t[3] = 0xFF
else: # r4g4b4a3
t[0] = (((rawPixel >> 8) & 0x0F) * 0xFF // 0x0F)
t[1] = (((rawPixel >> 4) & 0x0F) * 0xFF // 0x0F)
t[2] = (((rawPixel >> 0) & 0x0F) * 0xFF // 0x0F)
t[3] = (((rawPixel >> 12) & 0x07) * 0xFF // 0x07)
return t
@staticmethod
def rgb565(rawPixel):
t = bytearray(4)
t[0] = (((rawPixel >> 11) & 0x1F) * 0xFF // 0x1F)
t[1] = (((rawPixel >> 5) & 0x3F) * 0xFF // 0x3F)
t[2] = (((rawPixel >> 0) & 0x1F) * 0xFF // 0x1F)
t[3] = 0xFF
return t
@staticmethod
def ia8(rawPixel):
t = bytearray(4)
t[0] = rawPixel & 0xFF
t[1] = t[0]
t[2] = t[0]
t[3] = (rawPixel >> 8) & 0xFF
return t
@staticmethod
def to_rgb565(r, g, b, a = 0):
r = r * 0x1f // 0xff & 0x1f
g = g * 0x3f // 0xff & 0x3f
b = b * 0x1f // 0xff & 0x1f
return r << 11 + g << 5 + b
class textureParser:
@staticmethod
def cmpr(buffer, width, height, paletteBuffer=None, pixelFormat=None):
df = NINTEX_CMPR
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[df]
bs = NoeBitStream(buffer, NOE_BIGENDIAN)
_width, _height = getStorageWH(width, height, df)
textureData = bytearray(_width * _height * 4)
for y in range(0, _height, bh):
for x in range(0, _width, bw):
for y2 in range(0, bh, 4):
for x2 in range(0, bw, 4):
c0 = bs.readUShort()
c1 = bs.readUShort()
c = [
pixelParser.rgb565(c0),
pixelParser.rgb565(c1),
bytearray(4),
bytearray(4)
]
if c0 > c1:
for i in range(4):
c[2][i] = int((2 * c[0][i] + c[1][i]) / 3)
c[3][i] = int((2 * c[1][i] + c[0][i]) / 3)
else:
for i in range(4):
c[2][i] = int((c[0][i] + c[1][i]) * .5)
c[3][i] = 0
for y3 in range(4):
b = bs.readUByte()
for x3 in range(4):
idx = (((y + y2 + y3) * _width) + (x + x2 + x3)) * 4
textureData[idx : idx + 4] = c[(b >> (6 - (x3 * 2))) & 0x3]
textureData = crop(textureData, _width, _height, 32, width, height)
return NoeTexture("default", width, height, textureData, noesis.NOESISTEX_RGBA32)
@staticmethod
def rgba32(buffer, width, height, paletteBuffer=None, pixelFormat=None):
df = NINTEX_RGBA32
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[df]
_width, _height = getStorageWH(width, height, df)
textureData = bytearray(_width * _height * 4)
offset = 0
for y in range(0, _height, bh):
for x in range(0, _width, bw):
for y2 in range(bh):
for x2 in range(bw):
idx = (((y + y2) * _width) + (x + x2)) * 4
textureData[idx + 0] = buffer[offset + 33]
textureData[idx + 1] = buffer[offset + 32]
textureData[idx + 2] = buffer[offset + 1]
textureData[idx + 3] = buffer[offset + 0]
offset += 2
offset += 32
textureData = crop(textureData, _width, _height, 32, width, height)
return NoeTexture("default", width, height, textureData, noesis.NOESISTEX_RGBA32)
@staticmethod
def indexed(dataFormat, buffer, width, height, paletteBuffer, pixelFormat):
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[dataFormat]
textureData = bytearray(width * height * 4)
bs = NoeBitStream(paletteBuffer, NOE_BIGENDIAN)
palette = [pixelFormats[pixelFormat](bs.readUShort()) for i in range(paletteLen)]
tex = unswizzle(buffer, width, height, dataFormat)
bs = NoeBitStream(tex, NOE_BIGENDIAN)
if bpp == 16:
for i in range(width * height):
textureData[i * 4:(i + 1) * 4] = palette[bs.readUShort()]
elif bpp == 8:
for i in range(width * height):
textureData[i * 4:(i + 1) * 4] = palette[bs.readUByte()]
elif bpp == 4:
for i in range(0, width * height, 2):
b = bs.readUByte()
textureData[i * 4:(i + 1) * 4] = palette[(b >> 4) & 0xf]
textureData[(i + 1) * 4:(i + 2) * 4] = palette[b & 0xf]
return NoeTexture("default", width, height, textureData, noesis.NOESISTEX_RGBA32)
@staticmethod
def c4(buffer, width, height, paletteBuffer, pixelFormat):
return indexed(0x08, dataFormat, buffer, width, height, paletteBuffer, pixelFormat)
@staticmethod
def c8(buffer, width, height, paletteBuffer, pixelFormat):
return indexed(0x09, dataFormat, buffer, width, height, paletteBuffer, pixelFormat)
@staticmethod
def c14x2(buffer, width, height, paletteBuffer, pixelFormat):
return indexed(0x0A, dataFormat, buffer, width, height, paletteBuffer, pixelFormat)
dataFormats = {
# code: decoder, bpp, block width, block height, bSimple, palette len
0x00: ("i4", pixelParser.i4, 4, 8, 8, True, 0),
0x01: ("i8", pixelParser.i8, 8, 8, 4, True, 0),
0x02: ("ia4", pixelParser.ia4, 8, 8, 4, True, 0),
0x03: ("ia8", pixelParser.ia8, 16, 4, 4, True, 0),
0x04: ("rgb565", pixelParser.rgb565, 16, 4, 4, True, 0),
0x05: ("rgb5a3", pixelParser.rgb5a3, 16, 4, 4, True, 0),
0x06: ("rgba32", textureParser.rgba32, 32, 4, 4, False, 0),
0x08: ("c4", textureParser.c4, 4, 8, 8, False, 0x10),
0x09: ("c8", textureParser.c8, 8, 8, 4, False, 0x100),
0x0A: ("c14x2", textureParser.c14x2, 16, 4, 4, False, 0x400),
0x0E: ("cmpr", textureParser.cmpr, 4, 8, 8, False, 0)
}
pixelFormats = {
0x00 : pixelParser.ia8,
0x01 : pixelParser.rgb565,
0x02 : pixelParser.rgb5a3
}
def crop(buffer, width, height, bpp, newWidth, newHeight):
if width == newWidth and height == newHeight:
return buffer
res = bytearray(newWidth * newHeight * bpp // 8)
lw = min(width, newWidth) * bpp // 8
for y in range(0, min(height, newHeight)):
dst = y * newWidth * bpp // 8
src = y * width * bpp // 8
res[dst: dst + lw] = buffer[src: src + lw]
return res
def getStorageWH(width, height, df):
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[df]
width = (width + bw - 1) // bw * bw
height = (height + bh - 1) // bh * bh
return width, height
def unswizzle(buffer, width, height, df):
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[df]
stripSize = bpp * bw // 8
_width, _height = getStorageWH(width, height, df)
result = bytearray(_width * _height * bpp // 8)
ptr = 0
for y in range(0, _height, bh):
for x in range(0, _width, bw):
for y2 in range(bh):
idx = (((y + y2) * _width) + x) * bpp // 8
result[idx : idx+stripSize] = buffer[ptr : ptr+stripSize]
ptr += stripSize
return crop(result, _width, _height, bpp, width, height)
def swizzle(buffer, width, height, df):
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[df]
stripSize = bpp * bw // 8
_width, _height = getStorageWH(width, height, df)
result = bytearray(_width * _height * bpp // 8)
ptr = 0
for y in range(0, _height, bh):
for x in range(0, _width, bw):
for y2 in range(bh):
idx = (((y + y2) * width) + x ) * bpp // 8
result[ptr : ptr+stripSize] = buffer[idx : idx+stripSize]
ptr += stripSize
return result
def convert(buffer, width, height, dataFormat, palette=None, pixelFormat=None):
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[dataFormat]
if bSimple:
tex = unswizzle(buffer, width, height, dataFormat)
bs = NoeBitStream(tex, NOE_BIGENDIAN)
textureData = bytearray(width * height * 4)
if bpp == 32:
for i in range(width * height):
textureData[i*4:(i+1)*4] = decoder(bs.readUInt())
elif bpp == 16:
for i in range(width * height):
textureData[i*4:(i+1)*4] = decoder(bs.readUShort())
elif bpp == 8:
for i in range(width * height):
textureData[i*4:(i+1)*4] = decoder(bs.readUByte())
elif bpp == 4:
for i in range(0, width * height, 2):
b = bs.readUByte()
textureData[i*4:(i+1)*4] = decoder((b >> 4) & 0xf )
textureData[(i+1)*4:(i+2)*4] = decoder(b & 0xf)
return NoeTexture("default", width, height, textureData, noesis.NOESISTEX_RGBA32)
else:
return decoder(buffer, width, height, palette, pixelFormat)
def encode(buffer, width, height, dataFormat, palette=None, pixelFormat=None):
if dataFormat != NINTEX_RGB565:
raise ValueError("Data format not supported!")
res = rapi.swapEndianArray(rapi.imageEncodeRaw(buffer, width, height, "b5g6r5"), 2)
# res = NoeBitStream(bigEndian=NOE_BIGENDIAN)
# for i in range(height * width):
# res.writeUShort(pixelParser.to_rgb565(buffer[4*i+0], buffer[4*i+1], buffer[4*i+2], buffer[4*i+3]))
return swizzle(res, width, height, dataFormat)
def readTexture(bs, width, height, dataFormat, palette=None, pixelFormat=None):
size = getTextureSizeInBytes(width, height, dataFormat)
tex = bs.getBuffer(bs.tell(), bs.tell() + size)
return convert(tex, width, height, dataFormat, palette, pixelFormat)
def writeTexture(buffer, width, height, dataFormat, palette=None, pixelFormat=None):
return encode(buffer, width, height, dataFormat, palette, pixelFormat)
def getTextureSizeInBytes(width, height, dataFormat):
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[dataFormat]
return bpp * ((width + bw - 1) // bw * bw) * ((height + bh - 1) // bh * bh) // 8
def getPaletteSizeInBytes(dataFormat, paletteLenOverride = 0):
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[dataFormat]
if paletteLenOverride > 0:
paletteLen = paletteLenOverride
return paletteLen * 2 # palettes are always 16-bpp
def registerNoesisTypes():
handle = noesis.register("Nintendo Texture Finder", ".nintex")
noesis.setHandlerTypeCheck(handle, lambda x: 1)
noesis.setHandlerLoadRGBA(handle, nintexLoadRGBA)
return 1
def nintexLoadRGBA(data, texList):
width = 256
bs = NoeBitStream(data, NOE_BIGENDIAN)
# newbs = NoeBitStream()
# for i in range(bs.getSize() // 8):
# newbs.writeUInt64(bs.readUInt64())
# bs = NoeBitStream(newbs.getBuffer(0x10, newbs.getSize()), NOE_BIGENDIAN)
texList.append(NoeTexture("dxt1", width, (bs.getSize() // width) & 0xFFFFFFFC, bs.getBuffer(), noesis.NOESISTEX_DXT1))
texList.append(NoeTexture("dxt3", width, (bs.getSize() // width) & 0xFFFFFFFC, bs.getBuffer(), noesis.NOESISTEX_DXT3))
texList.append(NoeTexture("dxt5", width, (bs.getSize() // width) & 0xFFFFFFFC, bs.getBuffer(), noesis.NOESISTEX_DXT5))
for dataFormat in dataFormats:
name, decoder, bpp, bw, bh, bSimple, paletteLen = dataFormats[dataFormat]
if paletteLen:
continue
height = ((bs.getSize() * 8) // (width * bpp)) & (~7)
texList.append(readTexture(bs, width, height, dataFormat))
texList[-1].name = name
return len(texList)