Ok someone already helped with my UV's question.
The files come in .cat containers format, inside the .cat there are .tmd (the models) and .gnf (the textures)
The playable characters and bosses are divided into 4 elements in different folders: Body, Face, Hair and Weapons, and the support characters are one whole mesh each: Strikers.
BMS Script to split Gintama Rumble .cat files.
TamsoftGintama_CAT.bms
Code: Select all
#endian big
get UNK1 long
get FILES1 long
get UNK2 Long
for i = 0 < FILES1
get FILEOFFSET1[i] Long
next i
for i = 0 < FILES1
get FILESIZE1[i] long
next i
math TMP = 0
for i = 0 < FILES1
goto FILEOFFSET1[i]
get UNK3 long
get FILES2 long
get UNK5 long
get UNK6 long
get UNK7 long
for x = 0 < FILES2
get FILEOFFSET2[x] long
next x
for x = 0 < FILES2
get FILESIZE2[x] long
next x
for x = 0 < FILES2
math FILEOFFSET2[x] + FILEOFFSET1[i]
goto FILEOFFSET2[x]
get FILEHEADER long
if FILEHEADER == 0x1
set EXT string .cat
elseif FILEHEADER == 0x30646d74
set EXT string .tmd
elseif FILEHEADER == 0x316f6d74
set EXT string .tmo
elseif FILEHEADER == 0x20464e47
set EXT string .gnf
else
set EXT string .dat
endif
get NAME basename
set FILEN string NAME
if FILETOTAL > 1
string NAME + /
string NAME + FILEN
string NAME + _
string NAME + TMP
math TMP + 1
endif
string NAME + EXT
log NAME FILEOFFSET2[x] FILESIZE2[x]
next x
next i
Batch file to extract multiple .cat files
recursive_cat.bat
Code: Select all
@echo off
set back=%cd%
for /R %%i in (.) do (
cd "%%i"
for %%f in (*.cat) do (%back%\quickbms.exe %back%\TamsoftGintama_CAT.bms %%f "%%i")
)
cd %back%
GNF texture files are already supported on Noesis so you can easily export them.
Current fmt_GintamaRumble_tmd_ps4.py script.
Code: Select all
from inc_noesis import *
import noesis
import rapi
def registerNoesisTypes():
handle = noesis.register("Gintama Rumble PS4",".tmd")
noesis.setHandlerTypeCheck(handle, noepyCheckType)
noesis.setHandlerLoadModel(handle, noepyLoadModel)
return 1
NOEPY_HEADER = "tmd0"
def noepyCheckType(data):
bs = NoeBitStream(data)
if len(data) < 4:
return 0
if bs.readBytes(4).decode("ASCII") != NOEPY_HEADER:
return 0
return 1
def noepyLoadModel(data, mdlList):
fn = rapi.getExtensionlessName(rapi.getLocalFileName(rapi.getInputName())) + '_0'
ctx = rapi.rpgCreateContext()
bs = NoeBitStream(data)
bs.seek(0x6)
UVFlag = bs.readUByte()
BoneFlag = bs.readUByte()
bs.seek(0x68)
SubmeshOffset = bs.readUInt64()
FAddress = bs.readUInt64()
bs.readBytes(0x18)
VAddress = bs.readUInt64()
bs.readBytes(0x30)
SubmeshesCount = bs.readUInt()
FCount = bs.readUInt()
bs.readBytes(0xc)
VCount = bs.readUInt()
bs.readBytes(0x10)
BoneAddress = bs.readUInt64()
BHierarchyAddress = bs.readUInt64()
bs.readUInt64()
BCount = bs.readUInt()
VBSize = (bs.getSize()-VAddress)//VCount
texList = []
matList = []
vwList = []
bones = []
BoneHash_array = []
BoneParent_array = []
bs.seek(VAddress)
VertBuff = bs.readBytes(VCount * VBSize)
rapi.rpgBindPositionBufferOfs(VertBuff, noesis.RPGEODATA_FLOAT, VBSize, 0)
if UVFlag == 0xb7:
rapi.rpgBindUV1BufferOfs(VertBuff, noesis.RPGEODATA_SHORT, VBSize, VBSize-12)
elif UVFlag == 0xf7:
rapi.rpgBindUV1BufferOfs(VertBuff, noesis.RPGEODATA_SHORT, VBSize, VBSize-20)
else:
rapi.rpgBindUV1BufferOfs(VertBuff, noesis.RPGEODATA_SHORT, VBSize, VBSize-4)
rapi.rpgSetUVScaleBias((32.0285,32.0285,32.0285),None)
if BoneFlag in [0xa4, 0x24]:
rapi.rpgBindNormalBufferOfs(VertBuff, noesis.RPGEODATA_UBYTE, VBSize, 20)
bs.seek(VAddress + 0xc)
for j in range(0, VCount):
bidx = []
bwgt = []
for k in range(0, 4):
bwgt.append(bs.readUByte())
for k in range(0, 4):
bidx.append(bs.readUByte())
vwList.append(NoeVertWeight(bidx, bwgt))
bs.readBytes(VBSize-8)
fw = NoeFlatWeights(vwList)
rapi.rpgBindBoneIndexBuffer(fw.flatW[:fw.weightValOfs], noesis.RPGEODATA_INT, 4*fw.weightsPerVert, fw.weightsPerVert)
rapi.rpgBindBoneWeightBuffer(fw.flatW[fw.weightValOfs:], noesis.RPGEODATA_FLOAT, 4*fw.weightsPerVert, fw.weightsPerVert)
bs.seek(BHierarchyAddress)
for i in range(0, BCount):
BoneHash = bs.readUInt()
bs.readBytes(0xc)
BoneParent = bs.readInt()
bs.readUInt()
BoneHash_array.append(BoneHash)
BoneParent_array.append(BoneParent)
bs.seek(BoneAddress)
for i in range(0, BCount):
BoneName = "Bone {:02x}".format(BoneHash_array[i])
BonePIdx = BoneParent_array[i]
Mat44 = NoeMat44.fromBytes(bs.readBytes(0x40))
Mat43 = Mat44.toMat43().inverse()
bones.append(NoeBone(i,BoneName, Mat43, None, BonePIdx))
elif not BoneFlag:
rapi.rpgBindNormalBufferOfs(VertBuff, noesis.RPGEODATA_UBYTE, VBSize, 12)
bs.seek(SubmeshOffset)
SubmeshesInfo = []
for j in range(SubmeshesCount):
SubmeshFaceCount = bs.readUInt()
bs.readUInt()
SubmeshBufferOffset = bs.readUInt64()
SubmeshesInfo.append((SubmeshFaceCount,SubmeshBufferOffset))
FaceBuff = bs.readBytes(FCount * 3 * 2)
for i,info in enumerate(SubmeshesInfo):
rapi.rpgSetName(fn + str(i))
material = NoeMaterial('Material ' + fn + str(i), "")
material.setTexture(fn + str(i) + '_dif')
material.setNormalTexture(fn + str(i) + '_nml')
matList.append(material)
rapi.rpgSetMaterial('Material ' + fn + str(i))
rapi.rpgCommitTriangles(FaceBuff[info[1]*3*2:info[1]*3*2+info[0]*3*2], noesis.RPGEODATA_USHORT, info[0]*3, noesis.RPGEO_TRIANGLE, 1)
mdl = rapi.rpgConstructModel()
mdl.setModelMaterials(NoeModelMaterials(texList, matList))
mdl.setBones(bones)
mdlList.append(mdl)
rapi.rpgClearBufferBinds()
return 1
I'm sharing both the cat files and extracted tmd and gnf files of 1 Boss, 1 Playable and 1 Support so you can replicate and see how the files should be.
samples
The bodies of Playable and Boss characters are in the "Chara" folder, their faces, hairs and weapons in the "Playable" folder and the support is in the "Striker" folder, that's the original structure of the game.
And this is inside the Playable folder