Jump to content

Recommended Posts

Posted (edited)

Hello. I'm looking in how to extract these bnk files from the Xbox 360 version of Test Drive Unlimited.

These bnk files contain the following:

3DG (this is the file format for the meshes) and 2db (this is the file format for the textures)

The PC version of this game also has bnk files which can be opened in software like 3dsimed however for the Xbox 360 version of the game they cannot be opened.

The main reason as to why they won't open is due to the files from the Xbox 360 version are in big endian whereas the ones in the PC version are in little endian...

Is it possible to convert big endian to little endian?  And if not is it possible to extract the files individually from these bnk files?

I've posted  samples below:

The _I in these bnk files represent the interior of the said vehicle.

Rims are stored separately in a folder.

For more context these are the DLC 6 vehicles which was the last DLC for the game and were never ported to the PC version of the game.

Vehicles.rar

Edited by Darius6
  • Engineers
Posted (edited)
On 3/23/2025 at 12:30 AM, Darius6 said:

Hello.

...

Is it possible to convert big endian to little endian?

Speaking of files? Then usually the answer is "no", because you would need the offset of every byte , DWORD, float and string in the source file to treat them accordingly.

(If the file were a block of floats, for example, it would be possible.)

 

Getting something useful out of bnk will require some fidling, I guess:

TDU-bnk.png

TDU-x-bnk.png

TDU-Daytona-part.png

Point_cloud_Daytona_I.png

Edited by shak-otay
  • Like 1
  • Engineers
Posted (edited)

Harder than expected without a decent magic table of counts (if any):

Daytona.png

Handlebar had to be positioned manually:

Daytona.png

Solstice_interior.png

Solstice.png

Edited by shak-otay
  • Like 1
  • Engineers
Posted (edited)

I finally managed to get rid of a problem where a wronlgy determined face index count led to corrupted obj files.

I didn't find a simple solution with Make_H2O_pub, so I decided to fix that bug using hex2obj (which required a special TDUnlimited version to be created).

So if you don't want to trick around with a dozen *.objx files (from old hex2obj versions) it's important to use the updated exe (TDUnl) from here.

Said exe will create *_corr.obj (instead of *.objx) which means that they can be imported without renaming.

(Heaven will tell whether _corr(ection) is correct, always.)

The process is like so (read How-to-use_TDUnl_bnk.txt from zip) :

  • Make_H2O exe creates H2O files from bnk files
  • hex2obj creates obj files from bnk using said H2O files
  • bunch of obj files to be imported into blender using included .py file

Found sub meshes might be uncomplete - tested three (+ 2 interior) bnk files only

Have fun.

edit: forgot to mention that doors, steering wheel etc need a positional correction.

(The offsets are probably in the bnk files but I'll leave this to you.)

And yeah, uvs are missing - progress: 50% for interior (see exe in ...F2.zip)

(For chassis uvs need a recheck.)

 

edit: the above mentioned hex2obj version was optimized for creating multiple H2O files (File\SaveAs Mmesh) from TDunlimited bnk files.

The caveat is that handling single H2O files is restricted. So don't press the go1 button here.

Just the "mesh" button (and maybe correct the vertex count if you're told to do so).

 

 

Make_H2O_TDU_bnk.zip

350Z_Rst.png

F2 - exe only - you need the files from the previous zip!

Make_H2O-TDUnl-bnk-F2.zip

350Z_Rst_Interior.png

Edited by shak-otay
  • Like 2
  • Engineers
Posted (edited)

Honestly these tri-strips with FFFF annoying me so much... How do you deal with them if i may ask? snippet maybe?

Anyway I came across:

vertices > 3 x float

normals > 3 x hfloat + 1 x Null

colors > 4 x ubyte

uv indices > 2 x hfloat

Partialy reversed, PRIM struct is submesh. Multi UV sets. Textures are tiled. No problem for Noesis. Already made a script.

tdu.jpg

Edited by h3x3r
  • Like 1
  • Engineers
Posted (edited)
1 hour ago, h3x3r said:

Honestly these tri-strips with FFFF annoying me so much... How do you deal with them if i may ask? snippet maybe?

Welcome to the club.  I got a tri strips with terminal (0xFFFF) function from howfie on xentax ages ago. Took me a whole day to make it work.

(I'll pm you.)

Edited by shak-otay
typo
  • Thanks 1
Posted
1 hour ago, h3x3r said:

Honestly these tri-strips with FFFF annoying me so much... How do you deal with them if i may ask? snippet maybe?

Anyway I came across:

vertices > 3 x float

normals > 3 x hfloat + 1 x Null

colors > 4 x ubyte

uv indices > 2 x hfloat

Partialy reversed, PRIM struct is submesh. Multi UV sets. Textures are tiled. No problem for Noesis. Already made a script.

tdu.jpg

 

If you're using Noesis, you can use the rapi.rpgSetStripEnder(0xFFFF) function to set it to 0xFFFF and then just process each tri strip as normal.

 

 

  • Thanks 1
  • Engineers
Posted (edited)
On 3/23/2025 at 12:30 AM, Darius6 said:

Is it possible to convert big endian to little endian?  And if not is it possible to extract the files individually from these bnk files?

Sure if you know full struct.

Anyway here's bms for unpacking individual files. I have covered 4 file formats so far...

###################################
get BaseFileName basename

endian big

get Unknown_0_Offset uint32
get Unknown_CRC uint32
idstring BANK
getdstring Dummy 0x0C
get TotalFileSize uint32
get Unknown_0 uint32
get Unknown_1 uint32
get Unknown_2 uint32
get ResourceCount uint32
get Unknown_3 uint32
get ResourceTableOffset uint32
get Unknown_1_Offset uint32
get Unknown_2_Offset uint32
get Unknown_3_Offset uint32

goto ResourceTableOffset

get ResourceTableSize uint32
get Unknown_CRC uint32

for i = 0 < ResourceCount
	get ResourceOffset uint32
	get ResourceSize uint32
	get ResourceType uint32
	get ResourceNameCRC uint32
	savepos cPos
	goto ResourceOffset
	getdstring Dummy 0xC
	getdstring Sign 0x3
	if Sign == "DD3"
		set Ext string "3dd" # Hiearchy
	elif Sign == "GD3"
		set Ext string "3dg" # Geometry
	elif Sign == "MD2"
		set Ext string "2dm" # Material
	elif Sign == "BD2"
		set Ext string "2db" # Texture
	else
		set Ext string "dat" # ?
	endif
	goto cPos
	string FileName p= "%s/0x%08x.%s" BaseFileName ResourceOffset Ext
	log FileName ResourceOffset ResourceSize
next i

And here Noesis for textures...

from inc_noesis import *
import noesis
import rapi
import os

def registerNoesisTypes():
   handle = noesis.register("Test Drive Unlimited - Xbox 360 Texture", ".2db")
   noesis.setHandlerTypeCheck(handle, noepyCheckType)
   noesis.setHandlerLoadRGBA(handle, noepyLoadRGBA)
   noesis.logPopup()
   return 1
        
def noepyCheckType(data):
   bs = NoeBitStream(data,NOE_BIGENDIAN)
   if len(data) < 20:
      return 0
   return 1
   
def noepyLoadRGBA(data, texList):
    bs = NoeBitStream(data,NOE_BIGENDIAN)
    BaseName = rapi.getExtensionlessName(rapi.getLocalFileName(rapi.getInputName()))
    bs.readBytes(40)
    Width = bs.readUShort()
    Height = bs.readUShort()
    Unknown_0 = bs.readUByte()
    Unknown_1 = bs.readUByte()
    Unknown_2 = bs.readUByte()
    Unknown_3 = bs.readUByte()
    PixelFormat = bs.readUInt()
    Unknown_5 = bs.readUInt()
    Unknown_6 = bs.readUInt()
    Unknown_7 = bs.readUInt()
    bs.readBytes(4032)
        
    if PixelFormat == 132:
        bs.texSize = Width * Height //2
        print("Pixel Format > DXT1")
    elif PixelFormat == 136:
        bs.texSize = Width * Height
        print("Pixel Format > DXT5")
    elif PixelFormat == 144:
        bs.texSize = Width * Height * 4
        print("Pixel Format > RGBA32")
        
    data = bs.readBytes(bs.texSize)
    if PixelFormat == 132:
        data = rapi.imageUntile360DXT(rapi.swapEndianArray(data, 2), Width, Height, 8)
        texFmt = noesis.NOESISTEX_DXT1
    elif PixelFormat == 136:
        data = rapi.imageUntile360DXT(rapi.swapEndianArray(data, 2), Width, Height, 16)
        texFmt = noesis.NOESISTEX_DXT5
    elif PixelFormat == 144:
        data = rapi.imageUntile360Raw(data, Width, Height, 4)
        texFmt = noesis.NOESISTEX_RGBA32
            
    texList.append(NoeTexture(rapi.getInputName(), Width, Height, data, texFmt))
    return 1

Try to post one sample from x360 and another from pc, files must be same. I mean file name...

 

EDiT: Has Noesis UInt64 data type? Thanks!

Edited by h3x3r
  • Like 2
Posted (edited)
On 3/27/2025 at 10:32 PM, shak-otay said:

I finally managed to get rid of a problem where a wronlgy determined face index count led to corrupted obj files.

I didn't find a simple solution with Make_H2O_pub, so I decided to fix that bug using hex2obj (which required a special TDUnlimited version to be created).

So if you don't want to trick around with a dozen *.objx files (from old hex2obj versions) it's important to use the updated exe (TDUnl) from here.

Said exe will create *_corr.obj (instead of *.objx) which means that they can be imported without renaming.

(Heaven will tell whether _corr(ection) is correct, always.)

The process is like so (read How-to-use_TDUnl_bnk.txt from zip) :

  • Make_H2O exe creates H2O files from bnk files
  • hex2obj creates obj files from bnk using said H2O files
  • bunch of obj files to be imported into blender using included .py file

Found sub meshes might be uncomplete - tested three (+ 2 interior) bnk files only

Have fun.

edit: forgot to mention that doors, steering wheel etc need a positional correction.

(The offsets are probably in the bnk files but I'll leave this to you.)

And yeah, uvs are missing - maybe later...

 

 

Make_H2O_TDU_bnk.zip 74.37 kB · 1 download

350Z_Rst.png

Hey thanks! this works!

2025_03.30-19_15.thumb.png.c69d2fca0411fbdbda95f48d318010d0.png

The only problem however is that the make H20 cannot read some bnk files like for an example: interior for the 350Z Roadster, both files for the Dino, and the interior of the A4 DTM car. As for the rims  it only managed to export the rims from the Lamborghini Diablo GT correctly  however for the rest when i import in blender i get a small black dot?

2025_03.30-19_25.png.260b1941d4f37a5a4bd324c8bbb93dd7.png

(its on the upper right)

As for the textures they can be dumped with a ripper

4 hours ago, h3x3r said:

Sure if you know full struct.

Anyway here's bms for unpacking individual files. I have covered 4 file formats so far...

###################################
get BaseFileName basename

endian big

get Unknown_0_Offset uint32
get Unknown_CRC uint32
idstring BANK
getdstring Dummy 0x0C
get TotalFileSize uint32
get Unknown_0 uint32
get Unknown_1 uint32
get Unknown_2 uint32
get ResourceCount uint32
get Unknown_3 uint32
get ResourceTableOffset uint32
get Unknown_1_Offset uint32
get Unknown_2_Offset uint32
get Unknown_3_Offset uint32

goto ResourceTableOffset

get ResourceTableSize uint32
get Unknown_CRC uint32

for i = 0 < ResourceCount
	get ResourceOffset uint32
	get ResourceSize uint32
	get ResourceType uint32
	get ResourceNameCRC uint32
	savepos cPos
	goto ResourceOffset
	getdstring Dummy 0xC
	getdstring Sign 0x3
	if Sign == "DD3"
		set Ext string "3dd" # Hiearchy
	elif Sign == "GD3"
		set Ext string "3dg" # Geometry
	elif Sign == "MD2"
		set Ext string "2dm" # Material
	elif Sign == "BD2"
		set Ext string "2db" # Texture
	else
		set Ext string "dat" # ?
	endif
	goto cPos
	string FileName p= "%s/0x%08x.%s" BaseFileName ResourceOffset Ext
	log FileName ResourceOffset ResourceSize
next i

And here Noesis for textures...

from inc_noesis import *
import noesis
import rapi
import os

def registerNoesisTypes():
   handle = noesis.register("Test Drive Unlimited - Xbox 360 Texture", ".2db")
   noesis.setHandlerTypeCheck(handle, noepyCheckType)
   noesis.setHandlerLoadRGBA(handle, noepyLoadRGBA)
   noesis.logPopup()
   return 1
        
def noepyCheckType(data):
   bs = NoeBitStream(data,NOE_BIGENDIAN)
   if len(data) < 20:
      return 0
   return 1
   
def noepyLoadRGBA(data, texList):
    bs = NoeBitStream(data,NOE_BIGENDIAN)
    BaseName = rapi.getExtensionlessName(rapi.getLocalFileName(rapi.getInputName()))
    bs.readBytes(40)
    Width = bs.readUShort()
    Height = bs.readUShort()
    Unknown_0 = bs.readUByte()
    Unknown_1 = bs.readUByte()
    Unknown_2 = bs.readUByte()
    Unknown_3 = bs.readUByte()
    PixelFormat = bs.readUInt()
    Unknown_5 = bs.readUInt()
    Unknown_6 = bs.readUInt()
    Unknown_7 = bs.readUInt()
    bs.readBytes(4032)
        
    if PixelFormat == 132:
        bs.texSize = Width * Height //2
        print("Pixel Format > DXT1")
    elif PixelFormat == 136:
        bs.texSize = Width * Height
        print("Pixel Format > DXT5")
    elif PixelFormat == 144:
        bs.texSize = Width * Height * 4
        print("Pixel Format > RGBA32")
        
    data = bs.readBytes(bs.texSize)
    if PixelFormat == 132:
        data = rapi.imageUntile360DXT(rapi.swapEndianArray(data, 2), Width, Height, 8)
        texFmt = noesis.NOESISTEX_DXT1
    elif PixelFormat == 136:
        data = rapi.imageUntile360DXT(rapi.swapEndianArray(data, 2), Width, Height, 16)
        texFmt = noesis.NOESISTEX_DXT5
    elif PixelFormat == 144:
        data = rapi.imageUntile360Raw(data, Width, Height, 4)
        texFmt = noesis.NOESISTEX_RGBA32
            
    texList.append(NoeTexture(rapi.getInputName(), Width, Height, data, texFmt))
    return 1

Try to post one sample from x360 and another from pc, files must be same. I mean file name...

 

EDiT: Has Noesis UInt64 data type? Thanks!

I've posted examples for the B. Engineering Edonis below.

One folder is from the PC version of the game whereas the other one is from 360.

Edonis.rar

Edited by Darius6
  • Like 1
  • Engineers
Posted (edited)

Thanks for reporting!

Here's a quick fix (F1) tested with the 350Z Roadster interior bnk only (didn't check the other problem bnks and not the working ones, so keep the first released exe).

350Z_Rst_Interior.png

 

Surprisingly I found the correct uv address here, for sub mesh 3:

350Z_Rst_Int_SM_3.png

edit: shxt, the formula was to simple to apply to the uv addresses of all sub meshes.

        Only about 50% are correct for 350Z_Rst_I(nterior).bnk, see ...F2.exe in zip here

Corrections for chassis uvs are a little bit hard to achieve.

Use the updated hex2obj exe (for TDUnlimited) from here.

Example for 350Z_Rst.bnk_0042.h2o, that's sub mesh 42 of the 350Z_Rst (uv correction not required any more with Fix3):

350Z_Rst-SM_42.png

Correction for sub mesh 39 of the 350Z_Rst steering wheel (uv correction not required any more with Fix3):

350Z_Rst-SM_39.png

Edited by shak-otay
  • Like 1
  • Engineers
Posted (edited)

Got some progress too... but now when i enable Normals it pops this error > RuntimeError: Normal buffer would have been read out of bounds by provided indices.

image.thumb.png.c0d5dfe8ba1c2aa439d350dc50ae34d6.png

And same for the UV.

Edited by h3x3r
  • Like 1
  • Engineers
Posted (edited)
14 hours ago, h3x3r said:

Got some progress too... but now when i enable Normals it pops this error > RuntimeError: Normal buffer would have been read out of bounds by provided indices.

 

And same for the UV.

Well, had a short discussion with Rick, the MASTER himself, ages ago, and he got a little bit angry about it.

In short, he wrote something like, "It's not a problem of Noesis."

And yes, he was right, but you need to insert some print lines for logging addresses, counts and buffer sizes to find out the reason.

Usually you'll find that one of "your" counts (x FVFsize, for example) exceeds the given buffer size.

 

edit: updated Make_H2O tool to F2 (UVs, 50% of sub meshes correct)

        (use with interiors)

edit 2: Fix 3 is my last version, probably, not all uvs are correct, not tested with interiors

       Exe only - you need the files from the first zip here.

 

360Z_Rst.png

Make_H2O-TDUnl-bnk-F3.zip

Edited by shak-otay
  • Like 1
  • Engineers
Posted

I think there is some confusion in endianess. I had same problem with vertices. I just add rapi.rpgSetOption(noesis.RPGOPT_BIGENDIAN, 1) and error with vertices disappear...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...