5

I'm trying to reverse engineer 3D models (cars) from a racing game from 1997 (Test Drive 4). I'm able to extract the 3D mesh and textures, but cannot figure out how UV mapping works yet.

There is only one file per car which contains everything (3D model, textures, dashboard, tuning, etc.). The 3D model section contains a vertices list and polygons list. A single polygon looks as follows:

Triangle
{
    ulong       beg = 0xffff0000
    ushort      texture     // 0 = car bottom, 3 = body
    ubyte[6]    unknown     // UV mapping???
    ulong[3]    vertices    // Indices
    ulong       end = 0x00000000
}

I found that the 6-byte unknown section contains UV data, since there is a clear difference ingame when I modify these values. It is 6 bytes long so it can hold a U & V value for each of the 3 vertices. However, I have absolutely no idea how to interpret these numbers. I've tried reading them as bytes and dividing by 255 to convert them to floats ranging from 0 to 1, without success. Are they possibly 8-bit floats? (I don't understand how these work, so can't be sure.)

Below is a hex dump of 80 polygons (one per line).

FF FF 00 00 03 00 7B 7B 7D AD BE AD 00 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00
FF FF 00 00 03 00 7B 7D 7D BE BE AD 01 00 00 00 03 00 00 00 02 00 00 00 00 00 00 00
FF FF 00 00 03 00 3E 1C 3E BE BE C9 04 00 00 00 05 00 00 00 06 00 00 00 00 00 00 00
FF FF 00 00 03 00 1C 1C 3E BE CB C9 05 00 00 00 07 00 00 00 06 00 00 00 00 00 00 00
FF FF 00 00 03 00 7B 7B 7D AD BE AD 08 00 00 00 09 00 00 00 0A 00 00 00 00 00 00 00
FF FF 00 00 03 00 7B 7D 7D BE BE AD 09 00 00 00 0B 00 00 00 0A 00 00 00 00 00 00 00
FF FF 00 00 03 00 09 09 02 AC 9E AC 0C 00 00 00 0D 00 00 00 0E 00 00 00 00 00 00 00
FF FF 00 00 03 00 09 00 02 9E 9E AC 0D 00 00 00 0F 00 00 00 0E 00 00 00 00 00 00 00
FF FF 00 00 00 00 A0 A7 A0 1D 1D 00 10 00 00 00 11 00 00 00 12 00 00 00 00 00 00 00
FF FF 00 00 00 00 A7 A7 A0 1D 00 00 11 00 00 00 13 00 00 00 12 00 00 00 00 00 00 00
FF FF 00 00 03 00 45 45 67 3F 48 3F 14 00 00 00 15 00 00 00 01 00 00 00 00 00 00 00
FF FF 00 00 03 00 44 44 46 5B 5B 48 16 00 00 00 17 00 00 00 18 00 00 00 00 00 00 00
FF FF 00 00 03 00 44 47 46 5B 49 48 17 00 00 00 19 00 00 00 18 00 00 00 00 00 00 00
FF FF 00 00 00 00 92 97 98 1D 1D 1A 1A 00 00 00 17 00 00 00 16 00 00 00 00 00 00 00
FF FF 00 00 03 00 00 19 19 7B 7E 60 1B 00 00 00 00 00 00 00 1C 00 00 00 00 00 00 00
FF FF 00 00 03 00 46 44 29 47 5B 4A 19 00 00 00 17 00 00 00 1D 00 00 00 00 00 00 00
FF FF 00 00 03 00 44 2B 29 5B 5C 4A 17 00 00 00 1A 00 00 00 1D 00 00 00 00 00 00 00
FF FF 00 00 00 00 98 97 92 03 00 00 1E 00 00 00 1F 00 00 00 20 00 00 00 00 00 00 00
FF FF 00 00 03 00 3E 3E 2D BE BB BE 14 00 00 00 1B 00 00 00 21 00 00 00 00 00 00 00
FF FF 00 00 03 00 3E 2D 2D BB BC BE 1B 00 00 00 22 00 00 00 21 00 00 00 00 00 00 00
FF FF 00 00 03 00 2D 2D 1C 90 81 90 23 00 00 00 24 00 00 00 25 00 00 00 00 00 00 00
FF FF 00 00 03 00 2D 1C 1C 81 81 90 24 00 00 00 26 00 00 00 25 00 00 00 00 00 00 00
FF FF 00 00 03 00 2D 2D 18 BE BC BE 21 00 00 00 22 00 00 00 27 00 00 00 00 00 00 00
FF FF 00 00 03 00 2D 18 18 BC BB BE 22 00 00 00 28 00 00 00 27 00 00 00 00 00 00 00
FF FF 00 00 03 00 1C 1C 09 90 81 90 25 00 00 00 26 00 00 00 29 00 00 00 00 00 00 00
FF FF 00 00 03 00 1C 12 09 81 82 90 26 00 00 00 2A 00 00 00 29 00 00 00 00 00 00 00
FF FF 00 00 00 00 92 92 91 07 00 00 2B 00 00 00 20 00 00 00 2C 00 00 00 00 00 00 00
FF FF 00 00 03 00 18 18 11 BD BB BD 27 00 00 00 28 00 00 00 2D 00 00 00 00 00 00 00
FF FF 00 00 03 00 18 11 11 BB BB BD 28 00 00 00 2E 00 00 00 2D 00 00 00 00 00 00 00
FF FF 00 00 03 00 03 0B 0C 86 8E 7F 2F 00 00 00 29 00 00 00 30 00 00 00 00 00 00 00
FF FF 00 00 03 00 0B 13 0C 8E 81 7F 29 00 00 00 2A 00 00 00 30 00 00 00 00 00 00 00
FF FF 00 00 03 00 0D 0C 00 4D 57 57 31 00 00 00 32 00 00 00 33 00 00 00 00 00 00 00
FF FF 00 00 00 00 83 81 87 03 07 01 34 00 00 00 35 00 00 00 36 00 00 00 00 00 00 00
FF FF 00 00 00 00 81 87 87 07 07 01 35 00 00 00 37 00 00 00 36 00 00 00 00 00 00 00
FF FF 00 00 03 00 5D 5E 3F BD AD BD 38 00 00 00 39 00 00 00 03 00 00 00 00 00 00 00
FF FF 00 00 03 00 5E 3F 3F AD AD BD 39 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00
FF FF 00 00 03 00 7C 5E 7C AD AD BD 3A 00 00 00 39 00 00 00 3B 00 00 00 00 00 00 00
FF FF 00 00 03 00 5E 5D 7C AD BD BD 39 00 00 00 38 00 00 00 3B 00 00 00 00 00 00 00
FF FF 00 00 00 00 AA AA AB 16 1D 1E 3C 00 00 00 3D 00 00 00 3E 00 00 00 00 00 00 00
FF FF 00 00 00 00 BC BA B5 06 01 08 3F 00 00 00 40 00 00 00 41 00 00 00 00 00 00 00
FF FF 00 00 00 00 BA B5 B5 01 00 08 40 00 00 00 42 00 00 00 41 00 00 00 00 00 00 00
FF FF 00 00 03 00 5D 5D 43 0C 1B 0C 43 00 00 00 44 00 00 00 45 00 00 00 00 00 00 00
FF FF 00 00 03 00 5D 48 43 1B 1B 0C 44 00 00 00 46 00 00 00 45 00 00 00 00 00 00 00
FF FF 00 00 03 00 7B 59 7B BF BF C9 3B 00 00 00 05 00 00 00 3A 00 00 00 00 00 00 00
FF FF 00 00 03 00 59 5A 7B BF C9 C9 05 00 00 00 47 00 00 00 3A 00 00 00 00 00 00 00
FF FF 00 00 03 00 00 00 1C DC F7 CB 48 00 00 00 40 00 00 00 07 00 00 00 00 00 00 00
FF FF 00 00 03 00 00 1C 1C F7 F3 CB 40 00 00 00 3F 00 00 00 07 00 00 00 00 00 00 00
FF FF 00 00 03 00 5F 3F 1C F2 EB F2 49 00 00 00 4A 00 00 00 3F 00 00 00 00 00 00 00
FF FF 00 00 03 00 7D 7D 60 DC C3 CE 43 00 00 00 4B 00 00 00 4C 00 00 00 00 00 00 00
FF FF 00 00 03 00 7D 5F 60 C3 BE CE 4B 00 00 00 4D 00 00 00 4C 00 00 00 00 00 00 00
FF FF 00 00 03 00 04 0C 0B B7 BE AF 4E 00 00 00 2D 00 00 00 0C 00 00 00 00 00 00 00
FF FF 00 00 03 00 0C 13 0B BE BC AF 2D 00 00 00 2E 00 00 00 0C 00 00 00 00 00 00 00
FF FF 00 00 03 00 7D 62 73 00 00 13 2F 00 00 00 33 00 00 00 4F 00 00 00 00 00 00 00
FF FF 00 00 03 00 62 5F 73 00 13 13 33 00 00 00 50 00 00 00 4F 00 00 00 00 00 00 00
FF FF 00 00 03 00 73 5E 73 1F 1F 2B 0F 00 00 00 51 00 00 00 0E 00 00 00 00 00 00 00
FF FF 00 00 03 00 5E 5F 73 1F 2B 2B 51 00 00 00 35 00 00 00 0E 00 00 00 00 00 00 00
FF FF 00 00 03 00 5E 5D 3F AD BD AD 52 00 00 00 53 00 00 00 08 00 00 00 00 00 00 00
FF FF 00 00 03 00 5D 3F 3F BD BD AD 53 00 00 00 09 00 00 00 08 00 00 00 00 00 00 00
FF FF 00 00 00 00 A7 AA AA 1D 1D 16 11 00 00 00 3D 00 00 00 3C 00 00 00 00 00 00 00
FF FF 00 00 00 00 B5 B5 B4 08 00 00 41 00 00 00 42 00 00 00 54 00 00 00 00 00 00 00
FF FF 00 00 03 00 43 48 5D 0C 1B 0C 54 00 00 00 42 00 00 00 48 00 00 00 00 00 00 00
FF FF 00 00 03 00 48 5D 5D 1B 1B 0C 42 00 00 00 40 00 00 00 48 00 00 00 00 00 00 00
FF FF 00 00 03 00 7C 5D 7C BD BD AD 4B 00 00 00 53 00 00 00 55 00 00 00 00 00 00 00
FF FF 00 00 03 00 5D 5E 7C BD AD AD 53 00 00 00 52 00 00 00 55 00 00 00 00 00 00 00
FF FF 00 00 03 00 19 18 2D 7F 82 7F 56 00 00 00 26 00 00 00 57 00 00 00 00 00 00 00
FF FF 00 00 03 00 18 2D 2D 82 82 7F 26 00 00 00 24 00 00 00 57 00 00 00 00 00 00 00
FF FF 00 00 03 00 10 12 19 7F 82 7F 30 00 00 00 2A 00 00 00 56 00 00 00 00 00 00 00
FF FF 00 00 03 00 12 18 19 82 82 7F 2A 00 00 00 26 00 00 00 56 00 00 00 00 00 00 00
FF FF 00 00 00 00 87 87 87 16 1C 1C 58 00 00 00 32 00 00 00 31 00 00 00 00 00 00 00
FF FF 00 00 03 00 06 07 1E 32 0B 3B 59 00 00 00 5A 00 00 00 5B 00 00 00 00 00 00 00
FF FF 00 00 03 00 07 1E 1E 0B 04 3B 5A 00 00 00 1C 00 00 00 5B 00 00 00 00 00 00 00
FF FF 00 00 03 00 1C 1C 2D AC BB AC 5C 00 00 00 28 00 00 00 5D 00 00 00 00 00 00 00
FF FF 00 00 03 00 1C 2D 2D BB BC AC 28 00 00 00 22 00 00 00 5D 00 00 00 00 00 00 00
FF FF 00 00 03 00 09 12 1C AC BB AC 0C 00 00 00 2E 00 00 00 5C 00 00 00 00 00 00 00
FF FF 00 00 03 00 12 1C 1C BB BB AC 2E 00 00 00 28 00 00 00 5C 00 00 00 00 00 00 00
FF FF 00 00 03 00 1C 1C 09 9E 90 9E 5E 00 00 00 25 00 00 00 0D 00 00 00 00 00 00 00
FF FF 00 00 03 00 1C 09 09 90 90 9E 25 00 00 00 29 00 00 00 0D 00 00 00 00 00 00 00
FF FF 00 00 03 00 2D 2D 1C 9E 90 9E 5F 00 00 00 23 00 00 00 5E 00 00 00 00 00 00 00
FF FF 00 00 03 00 2D 1C 1C 90 90 9E 23 00 00 00 25 00 00 00 5E 00 00 00 00 00 00 00
FF FF 00 00 03 00 3D 2D 2D 90 90 9E 59 00 00 00 23 00 00 00 5F 00 00 00 00 00 00 00

I can post the full file structure if this is required. Thanks in advance!

Edit:

Vertex3D
{
    float x, y, z
}
Vector3D
{
    float x, y, z
}

Vertex
{
    Vertex3D    vertex
    Vector3D    normal  // Sum = 1.0
    ulong       unknown
    ubyte[12]   padding = 0
}
Triangle
{
    ulong       beg = 0xffff0000
    ushort      texture     // 0 = car bottom, 3 = body
    ubyte[6]    unknown     // UV mapping???
    ulong[3]    vertices    // Indices
    ulong       end = 0x00000000
}

Model3D
{
    char[8]     identifier = "PCMODL01"
    ulong       unknown = 0x00000000
    ulong       offset1 = 44    // Vertices start offset
    ulong       offset2         // Polygons start offset

    ubyte[16]   stuff

    ulong                   n_polygons
    ulong                   n_vertices
    Vertex[n_vertices]      vertices
    Triangle[n_polygons]    polygons
}

Main
{
    // Header
    ulong   n_blocks = 7

    ulong   offset1 = 60
    ulong   length1     // Model

    ulong   offset2
    ulong   length2     // Dash lo res (320x80) RGB565

    ulong   offset3
    ulong   length3     // Dash hi res (640x160) RGB565

    ulong   offset4
    ulong   length4     // Steering wheel (256x256) RGB565

    ulong   offset5
    ulong   length5     // Car textures (128x256) RGB565

    ulong   offset6
    ulong   length6     // Tuning

    ulong   offset7
    ulong   length7     // Menu picture (400x400) Indexed color

    // offset1 onwards
    Model3D         model

    // offset2 onwards
    blob[25600]     dash_lo

    // offset3 onwards
    blob[102400]    dash_hi

    // offset4 onwards
    blob[65536]     steer_whl

    // offset5 onwards
    blob[32768]     textures

    // offset6 onwards
    blob[300]       tuning

    // offset7 onwards
    ubyte[12]       unknown1
    ubyte           menu_position
    char[3]         basename        // CAM, JAG, VET, etc.
    ulong           padding = 0x00000000
    ulong           unknown2
    blob[160000][5] menu_screens    // 5 images, one per language
}
6
  • 1
    This looks like a sequence of quads and a bit of extrapolation makes me assume that unknown[0] and unknown[3] belong to vertex 1, unknown[1] and unknown[4] belong to vertex 2 and unknown[2] and unknown[5] belong to vertex 3 Dec 3 '15 at 15:07
  • @ratchetfreak Yes, I've tried all possible combinations, but nothing is right. When I convert them (divide by 255) in the order you mentioned, or any other order, I get a lot of irrational numbers (like 0.666667).
    – Midas
    Dec 3 '15 at 16:15
  • 1
    So why can't a UV coordinate not be 2/3?
    – Jongware
    Dec 3 '15 at 22:27
  • @Jongware It would, but the UV coordinates are wrong in this case (I checked in 3D modeling software).
    – Midas
    Dec 3 '15 at 23:05
  • maybe they are indices into another buffer? Dec 4 '15 at 1:10
0

Problem is solved!

I found that the unknown values are actually coordinates in pixels on the texture map (positive integers). The car textures are 128 x 256 in size, so by dividing each U or V coordinate by the maximum value in that direction, we have the right float conversion:

vertex1.u = unknown[0] / 127.f;
vertex2.u = unknown[1] / 127.f;
vertex3.u = unknown[2] / 127.f;

vertex1.v = -unknown[3] / 255.f;
vertex2.v = -unknown[4] / 255.f;
vertex3.v = -unknown[5] / 255.f;

Besides, there were bugs in my converter that prevented this from working...

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.