Skip to content
View in the app

A better way to browse. Learn more.

ResHax

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
Help us keep the site running.
Zero Tolerance for Disrespect

Resident Evil Code Veronica X HD PS3 — MDL format reverse engineering / Noesis importer

Featured Replies

Hi,

I'm reverse engineering the .mdl model format used by Resident Evil Code: Veronica X HD.

I'm specifically looking for help from someone experienced with Noesis Python plugins / Capcom model formats to turn the information below into a working Noesis importer that actually displays the complete mesh correctly.

I already have a substantial amount of the format mapped, but the last part of the encoded face/UV streams is preventing me from producing a reliable complete importer.

I am not looking for an approximate mesh. The objective is a deterministic parser that reconstructs the complete geometry correctly.


1. MDL blocks

The files contain blocks identified by:

4D 44 4C BC

and sometimes:

4D 44 4C AD

The MDL blocks contain a table of entries.

Each entry is:

0x34 bytes

The important fields identified so far are:

entry + 0x08 = entry type
entry + 0x0C = relative target

The relative offsets are relative to the MDL block.


2. TYPE 0x06 / TYPE 0x16 mesh entries

TYPE 0x06 is confirmed to be a geometry/submesh descriptor.

For example, in en52a00.mdl:

MDL base       = 0xD50

entry           = base + 0x94
type            = 0x06
target          = base + 0x398

target + 0x10  -> vertex descriptor
target + 0x14  -> face-stream descriptor

Vertex descriptor:

VD                = 0x3E0 relative
vertex count      = 0x330 = 816
vertex data       = VD + 0x50

Therefore:

vertex data abs = 0x1180

The vertex data occupies:

0x1180 ... 0x7783

and the face stream begins at:

0x7784

The face-stream pointer itself is:

target + 0x14

with the actual stream beginning at:

face_pointer + 0x10

The same structure was observed in several independent models.


3. Confirmed examples

en92a00.mdl

MDL base          = 0x1884

TYPE 06 entry     = base + 0x114
target            = base + 0x654

vertex descriptor = 0x66C
vertex count      = 0x5EE = 1518

vertex data       = 0x1F40

face pointer      = relative 0xC470
face stream       = 0xDD04

Face stream starts:

13 25 00 04
7F 7F 7F FF
7F 7F 7F FF
08 34 40 09
42 02 18 C4 ...

en91a00.mdl

MDL base          = 0x1958

TYPE 06 entry     = base + 0x118
target            = base + 0x658

vertex descriptor = 0x698
vertex count      = 0x623 = 1571

vertex data       = 0x2040

face pointer      = relative 0xCB3C
face stream       = 0xE4A4

Face stream:

13 25 00 04
...
42 02 1E 80 ...

en05a00.mdl

Library sample:

file size = 58560 bytes
MDL base  = 0xF70

TYPE 06:

entry            = base + 0x84
target           = base + 0x5F8

vertex descriptor = base + 0x640
vertex count      = 0x3A8 = 936

vertex data       = 0x1600

Vertex data ends at:

0x8B00

Face stream:

face stream = 0x8B04

The beginning is:

13 25 00 04
...
08 34 40 ...
42 12 ...

There are also multiple TYPE 16 submeshes.


4. Vertex stream

The external reverse-engineering work on this format already established that the vertex/face data are encoded as multiple streams with 32-bit stream headers.

The known vertex stream example is:

00 6D 00 29

followed by:

00 12 00 00

and floating-point data.

The documented interpretation is that 0x0029 contains:

position = float3
normal   = float3

while another descriptor such as 0x0022 contains position-only data.

This agrees with the vertex buffers found in my samples.


5. Face stream

The known first stream is:

13 25 00 04

followed by RGBA data.

This is consistent with the previous reverse-engineering work which identified 0x25130004 as a face-colour stream.

The colour applies to following faces until another colour stream occurs.

The important part is the following encoded geometry streams.


6. 42 streams

The major unresolved part is the 42 stream.

Examples:

42 02 ...
42 0A ...
42 12 ...

The records following the 4-byte header consist of 16-bit values.

The important discovery is that these are not simply three fixed fields of

index, U, V

for the entire stream.

The role of the fields changes depending on the current state.


7. 42 0A — strong evidence for rotating index state

One very useful example is:

en78a00.mdl
face stream @ 0x1EB4

Header:

42 0A 00 70

Control:

00 09 FF F6

followed by 28 records.

The first records are:

0002 02DC 0143
0001 02DC 0133
0005 02F4 0143
0004 02F4 0133
0008 030C 0143
0007 030C 0133
000B 0324 0143
000A 0324 0133
000E 033C 0143
000D 033C 0133

Here the first field behaves as the vertex index.

Then:

FFF6 0000 02DC
0123 0002 02DC
0113 0003 02F4
0123 0005 02F4
0113 0006 030C
0123 0008 030C
...

The index has moved to another field.

Later:

0113 FFF6 000C
033C 0123 000D
033C 0133 0009
0324 0123 000A
...

The index has moved again.

So the evidence strongly suggests a state machine where the active index field can rotate:

field 0 -> field 1 -> field 2

and the other two fields contain texture-coordinate-related data.

This produces coherent box-like geometry when decoded with the appropriate state changes.


8. 42 12 — the remaining problem

The difficult streams are 42 12.

For example:

en05a00.mdl
face stream @ 0xCFC4

Header:

42 12 00 74

Control:

00 06 00 06

18 records:

0   000A 0391 0307
1   0000 0367 0369
2   0002 0367 0307
3   0001 0319 0369
4   0003 0319 0307
5   000B 02CB 0308
6   0004 0000 0367
7   0359 0005 0367
8   03D7 0001 0319
9   0359 0006 031A
10  03D6 0003 000B
11  0188 02D5 000A
12  01D6 02D5 0007
13  01AF 02B0 FFFB
14  000B 0188 02D5
15  0003 0188 0289
16  0007 01AF 02B0
17  0002 01D6 0288

The first several records clearly have a small first field corresponding to vertex indices, while the other fields are UV-like values.

But later records show the active index field changing.

For example:

13  01AF 02B0 FFFB

Here FFFB cannot simply be treated as a restart/index because the other values are clearly consistent with UV data.

Therefore:

FFFx is NOT universally a restart marker.

It can occur in a field which is not currently the index field.


9. Another 42 12 example

en05a00.mdl, stream around:

0xD304

contains:

42 12 00 74

with control:

00 06 00 0B

The beginning:

11 02D0 0302
03 0318 0302
01 0318 035F
02 0363 0302
00 0363 035E
0A 03AB 0302
08 03AA 035E
09 03EE 035F
0C 03A9 03D2
0D 03EF 03D2
04 0341 03FE
FFFB 0003 0187

then:

0278 0002 01D6
0275 0007 01AF
02A8 000A 01D6
02D3 000B 0188
02D5 0003 000A
03AC 0301 000B

Again, the active index field changes.

The final record is particularly interesting:

03AC 0301 000B

because the only value which fits the local vertex-index range is the third field.


10. Main 42 12 stream

The main en05a00 geometry stream begins at approximately:

0x8B14

and has:

control = 0x0175, 0x0004

The associated vertex count is:

936

There are 1332 records according to the current stream analysis.

A section around records 396-407 looks like:

396  FFF6 0083 02B3
397  0302 0086 030D
398  0302 0071 030D
399  035A 006E 036D
...
406  035A FFF5 0081
407  02B3 0302 0080

Again, this strongly suggests that the current index field is changing without necessarily having a simple explicit FFFx marker in the same field.


11. Known stream controls

Some confirmed examples:

en53:
42 02
control = (6, 13)
records = 21

en63:
42 02
control = (33, -18)
records = 105

en67:
42 02
control = (2, 3)
records = 4

en78:
42 0A
control = (9, -10)
records = 28

en05:
42 12
control = (373, 4)
records = 1332

en92:
42 02
control = (230, 3)
records = 1056

en91:
42 02
control = (297, -5)
records = 1300

en52:
42 0A
control = (52, 35)
records = 183

I don't yet want to claim that these control values are counts. They may instead be part of the state/decoder parameters.


12. Models tested

The same structure has been reproduced across multiple MDLs:

en05a00.mdl
en52a00.mdl
en53a00.mdl
en63a00.mdl
en67a00.mdl
en78a00.mdl
en81a00.mdl
en91a00.mdl
en92a00.mdl
en96a01.mdl
ob_603.mdl

Examples of exact vertex counts:

en05 = 936
en52 = 816
en78 TYPE06 = 8 / 4 / 4
en78 TYPE16 = 39
en91 = 1571
en92 = 1518

13. What I need

I would like help with a working Noesis Python importer, preferably something along these lines:

def registerNoesisTypes():
    handle = noesis.register("Resident Evil Code Veronica X HD MDL", ".mdl")
    noesis.setHandlerTypeCheck(handle, mdlCheckType)
    noesis.setHandlerLoadModel(handle, mdlLoadModel)
    return 1

The importer should:

  1. Locate MDL blocks.

  2. Parse the 0x34-byte entry table.

  3. Locate TYPE 0x06 / TYPE 0x16 geometry entries.

  4. Resolve the vertex descriptor.

  5. Read the exact vertex count.

  6. Decode the vertex stream.

  7. Decode the face stream.

  8. Correctly distinguish triangle lists and triangle strips.

  9. Decode the UV coordinates.

  10. Build the mesh with rapi.rpg....

  11. Produce a visible mesh in Noesis.

  12. Eventually expose materials/textures and skeleton/weights.

At this stage the priority is only geometry + UVs.


14. Important Noesis detail

I am using:

Noesis 4.474

The plugin environment has:

rapi.rpgCreateContext()
rapi.rpgConstructModel()

but does NOT have:

rapi.rpgDestroyContext()

So a plugin written for this environment should not call rpgDestroyContext().


15. Current state

The MDL table / mesh descriptors / vertex counts / vertex locations are already reproducible.

The remaining unknown is essentially:

How exactly does the 42 xx face/UV stream encode its current index-field/state, and how should all of its records be converted into Noesis triangles + UVs?

I would particularly appreciate either:

  • an explanation of the 42 02 / 42 0A / 42 12 stream bitfields/state machine, or

  • a working Noesis Python decoder for these streams.

Once this is solved, the rest of the model format should be considerably easier to implement.

FILES OF TEMPLATE: randalfcastro-tech/Resident-Evil-Code-Veronica-X-HD-PS3: Files of RECVXHD of PS3

Thanks!

Edited by Randalf2theReturn

  • Randalf2theReturn changed the title to Resident Evil Code Veronica X HD PS3 — MDL format reverse engineering / Noesis importer
  • Author

In addition to researching the MDL format, I have found two other related formats that I believe could be important for completing animation support for *Resident Evil Code: Veronica X HD* (PS3).

I have attached actual .RMT and .MTN files:

* rm_9100(1).rmt — 61,504 bytes

* en05ms(1).mtn — 369,152 bytes

I performed an initial hex inspection, and both use a structure that begins with:

00 00 XX XX 4D 54 4E 80

that is:

[size][MTN 80]

The .RMT file contains at least two consecutive MTN blocks:

offset 0x0000

size 0x34DC

magic MTN 80

field 0x0010000B

relative/size field 0x000034C0

offset 0x34E0

size 0xBB28

magic MTN 80

field 0x00100016

relative/size field 0x0000BB0C

The .MTN file contains many consecutive MTN blocks. For example:

0x0000 size 0x2B2C type 0x0010001C

0x2B30 size 0x0F0C type 0x0010001C

0x3A40 size 0x1598 type 0x0010001C

0x4FDC size 0x2B2C type 0x0010001C

...

The pattern repeats throughout the file. The first few bytes of en05ms.mtn are:

00 00 2B 2C

4D 54 4E 80

00 10 00 1C

00 00 2B 10

FF FF FF FF

00 00 00 00

C0 3F C2 A0

00 00 00 00

...

and those of rm_9100.rmt:

00 00 34 DC

4D 54 4E 80

00 10 00 0B

00 00 34 C0

FF FF FF FF

41 C8 00 00

41 E6 66 66

42 A0 00 00

...

The subsequent data contains numerous big-endian IEEE-754 values that appear potentially related to transformations/frames, although I do not yet want to claim they represent positions, rotations, or scales without a proven correlation.

My goal is not merely to create an importer for static MDL models. I would eventually like to achieve:

1. MDL → complete geometry.

2. MDL → materials/textures.

3. MDL → skeleton/bones.

4. MDL → weights/skin.

5. MTN/RMT → animations.

6. The ability to correctly associate an MTN/RMT animation with the corresponding MDL skeleton.

7. Importing all of this into Noesis and/or Blender while preserving the animations.

That is why I would like to know if anyone is familiar with the MTN 80 structure as well, specifically:

* the meaning of 0x0010000B, 0x00100016, and 0x0010001C; * the meaning of the DWORD located immediately after;

* the meaning of FFFFFFFF;

* the structure of the consecutive blocks;

* how frames/keyframes are stored;

* how bones/tracks are identified;

* whether .RMT is a container for one or multiple .MTN files;

* the relationship between .RMT.MTN filenames and .MDL models.

I am simultaneously reverse-engineering the .MDL format for a Noesis importer, but I would also like to tackle .MTN.RMT from the start so that the final result can import models with skeletons, weights, and animations, rather than just the static mesh.

If anyone has documentation, legacy code, or extraction tools, or is familiar with these formats from any version of *Biohazard*/*Resident Evil Code: Veronica*, any information would be extremely helpful.

Edited by Randalf2theReturn

  • 3 weeks later...
  • Author

I thought someone would respond or have an idea about the format; I made this Noesis script although the meshes look bad, but it's a start...

import struct
import rapi
import noesis
from inc_noesis import *

# ================================================================
# Resident Evil Code Veronica X HD - MDL Noesis Importer (Don't' Complete)
# ================================================================

DEBUG = True
UV_SCALE = 1024.0

def _u16be(data, off):
    if off < 0 or off + 2 > len(data): return 0
    return struct.unpack_from(">H", data, off)[0]

def _u32be(data, off):
    if off < 0 or off + 4 > len(data): return 0
    return struct.unpack_from(">I", data, off)[0]

def _log(msg):
    if DEBUG: print("[RECVX-MDL] " + msg)

def _find_mdl_blocks(data):
    # Locate all MDL blocks based on magic headers
    starts = []
    p = 0
    while p + 4 <= len(data):
        tag = data[p:p + 4]
        if tag == b"MDL\xBC" or tag == b"MDL\xAD":
            starts.append(p)
        p += 1

    blocks = []
    for i in range(len(starts)):
        start = starts[i]
        end = starts[i + 1] if i + 1 < len(starts) else len(data)
        blocks.append((start, end))
    return blocks

def _find_type16_vertex_code(data, target, block_end):
    # Safely search for the vertex block marker 0x3300 after skipping metadata
    stop = min(block_end - 8, target + 0x200)
    p = target + 0x10
    while p <= stop:
        if data[p:p + 2] == b"\x33\x00":
            count = _u32be(data, p + 4)
            if 0 < count < 100000:
                return p
        p += 4
    return -1

def _get_mesh_entry(data, block_start, block_end, entry_off):
    # Validate and extract mesh entry parameters from pointers
    entry = block_start + entry_off
    if entry + 0x34 > block_end: return None

    mesh_type = _u32be(data, entry + 8)
    if mesh_type != 0x06 and mesh_type != 0x16: return None

    target_rel = _u32be(data, entry + 0x0C)
    target = block_start + target_rel
    if target < block_start or target + 0x18 > block_end: return None

    face_rel = _u32be(data, target + 0x14)
    face_ptr = block_start + face_rel
    if face_ptr < block_start or face_ptr + 0x24 > block_end: return None

    face_stream = face_ptr + 0x20
    if face_stream + 8 > block_end or data[face_stream] != 0x42: return None

    if mesh_type == 0x06:
        v_desc_rel = _u32be(data, target + 0x10)
        v_desc = block_start + v_desc_rel
        if v_desc < block_start or v_desc + 0x54 > block_end: return None
        vertex_code = v_desc + 0x10
    else:
        vertex_code = _find_type16_vertex_code(data, target, block_end)
        if vertex_code < 0: return None

    if vertex_code + 8 > block_end or data[vertex_code:vertex_code + 2] != b"\x33\x00": return None

    vertex_count = _u32be(data, vertex_code + 4)
    if vertex_count <= 0 or vertex_count > 100000: return None

    # Dynamic offset adjustment to avoid colliding with strings like object names
    vertex_data = vertex_code + 0x40
    if vertex_data + vertex_count * 16 > block_end: return None

    stream_len = _u16be(data, face_stream + 2)
    if stream_len < 8 or face_stream + stream_len > block_end: return None

    return {
        "entry": entry_off, "type": mesh_type, "target": target,
        "vertex_code": vertex_code, "vertex_count": vertex_count,
        "vertex_data": vertex_data, "face_ptr": face_ptr,
        "face_stream": face_stream, "flag": data[face_stream + 1],
        "stream_len": stream_len, "stream_end": face_stream + stream_len
    }

def _decode_face_stream(data, stream_off, stream_end, vertex_count):
    # Decode triangle strips and UV coordinates from the face stream payload
    stream_len = _u16be(data, stream_off + 2)
    payload_bytes = stream_len - 8
    record_count = payload_bytes // 6

    state = 0
    strips, current_indices, current_uvs = [], [], []

    for i in range(record_count):
        off = stream_off + 8 + i * 6
        a, b, c = struct.unpack_from(">HHH", data, off)
        values = (a, b, c)

        marker_field = -1
        for f in range(3):
            if values[f] >= 0xFF00 and f == state:
                marker_field = f
                break

        if marker_field < 0 and values[state] >= vertex_count:
            for f in range(3):
                if f != state and values[f] >= 0xFF00:
                    marker_field = f
                    break

        if marker_field >= 0:
            if len(current_indices) >= 3:
                strips.append((current_indices, current_uvs))
            current_indices, current_uvs = [], []
            state = (marker_field + 1) % 3
            continue

        if values[state] >= vertex_count:
            new_state = -1
            for k in (1, 2):
                candidate = (state + k) % 3
                val = values[candidate]
                if val < vertex_count and val < 0xFF00:
                    new_state = candidate
                    break
            if new_state < 0: raise RuntimeError("42 state failure")
            state = new_state

        index_value = values[state]
        if index_value >= vertex_count: raise RuntimeError("Index out of range")

        uv_vals = [values[f] for f in range(3) if f != state]
        u = float(uv_vals[0]) / UV_SCALE
        v = float(uv_vals[1]) / UV_SCALE

        current_indices.append(index_value)
        current_uvs.append((u, v))

    if len(current_indices) >= 3:
        strips.append((current_indices, current_uvs))

    return strips

def _read_positions(data, vertex_data, vertex_count):
    # Read vertex position buffers as 16-bit signed integers and scale them
    positions = []
    for i in range(vertex_count):
        off = vertex_data + (i * 16)  # Using a 16-byte stride
        if off + 6 <= len(data):
            # Read as Big Endian 16-bit signed integers (short)
            sx, sy, sz = struct.unpack_from(">3h", data, off)
            
            # Standard fixed-point scale factor for SEGA/Ninja structures
            scale = 4096.0
            x = float(sx) / scale
            y = float(sy) / scale
            z = float(sz) / scale
            
            positions.append((x, y, z))
        else:
            positions.append((0.0, 0.0, 0.0))
    return positions

def _expand_strips(positions, strips):
    # Expand triangle strips into individual renderable triangles
    out_pos, out_uvs, tri_count = [], [], 0
    for indices, uvs in strips:
        if len(indices) < 3: continue
        for i in range(len(indices) - 2):
            # Correct index winding order to prevent flipped normals and malformed faces
            tri = (i, i + 1, i + 2) if (i & 1) else (i + 1, i, i + 2)
            ia, ib, ic = indices[tri[0]], indices[tri[1]], indices[tri[2]]
            if ia == ib or ib == ic or ia == ic: continue

            out_pos.extend([positions[ia], positions[ib], positions[ic]])
            out_uvs.extend([uvs[tri[0]], uvs[tri[1]], uvs[tri[2]]])
            tri_count += 1
    return out_pos, out_uvs, tri_count

def _collect_meshes(data):
    # Collect all valid mesh entries across all blocks in the file
    meshes, seen = [], set()
    blocks = _find_mdl_blocks(data)
    for b_idx, block in enumerate(blocks):
        block_start, block_end = block[0], block[1]
        max_scan = min(block_end - 0x34, block_start + 0x10000)
        for entry_abs in range(block_start, max_scan, 4):
            info = _get_mesh_entry(data, block_start, block_end, entry_abs - block_start)
            if info:
                key = (block_start, info["target"], info["face_stream"])
                if key not in seen:
                    seen.add(key)
                    info["block"] = b_idx
                    meshes.append(info)
    return meshes

def registerNoesisTypes():
    # Register plugin handlers with Noesis
    h = noesis.register("Resident Evil Code Veronica X HD MDL", ".mdl")
    noesis.setHandlerTypeCheck(h, mdlCheckType)
    noesis.setHandlerLoadModel(h, mdlLoadModel)
    return 1

def mdlCheckType(data):
    # Check if file contains valid MDL signatures
    return 1 if (b"MDL\xBC" in data or b"MDL\xAD" in data) else 0

def mdlLoadModel(data, mdlList):
    # Main model loading handler for Noesis
    ctx = rapi.rpgCreateContext()
    meshes = _collect_meshes(data)
    if not meshes: return 1

    for idx, info in enumerate(meshes):
        try:
            positions = _read_positions(data, info["vertex_data"], info["vertex_count"])
            strips = _decode_face_stream(data, info["face_stream"], info["stream_end"], info["vertex_count"])
            draw_pos, draw_uvs, tri_count = _expand_strips(positions, strips)

            if tri_count <= 0 or len(draw_pos) < 3: continue

            pos_buf = bytearray()
            for x, y, z in draw_pos: pos_buf += struct.pack("<3f", x, y, z)

            uv_buf = bytearray()
            for u, v in draw_uvs: uv_buf += struct.pack("<2f", u, v)

            idx_buf = bytearray()
            for i in range(len(draw_pos)): idx_buf += struct.pack("<H", i)

            rapi.rpgSetName(f"MDL_{idx:02d}")
            rapi.rpgBindPositionBuffer(bytes(pos_buf), noesis.RPGEODATA_FLOAT, 12)
            rapi.rpgBindUV1Buffer(bytes(uv_buf), noesis.RPGEODATA_FLOAT, 8)
            rapi.rpgCommitTriangles(bytes(idx_buf), noesis.RPGEODATA_USHORT, len(draw_pos), noesis.RPGEO_TRIANGLE, 1)
            rapi.rpgClearBufferBinds()
        except Exception as e:
            _log(f"Mesh {idx} failed: {e}")

    mdl = rapi.rpgConstructModel()
    if mdl: mdlList.append(mdl)
    return 1

image.png

It should be noted that the game is decompiled for PS2 at this link: "https://github.com/AshfordFamily/recvx-decomp/tree/master" also look for njCnk and NJS_CNK_OBJECT which I think are what the game manages in memory to understand the mdls within that directory with results in:

[Found 'njCnk'] in: recvx-decomp-master\config\reloc_addrs.txt

[Found 'njCnk'] in: recvx-decomp-master\config\symbol_addrs.txt

[Found 'NJS_CNK_MODEL'] in: recvx-decomp-master\include\ps2\veronica\prog\binfunc.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\door.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\en03.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\en13.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\en22.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\face.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\face_bh.h

[Found 'NJS_CNK_MODEL'] in: recvx-decomp-master\include\ps2\veronica\prog\hitchkl.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\itemview.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\map.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\MdlPut.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\njplus.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\playpch.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\ps2_NinjaCnk.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\include\ps2\veronica\prog\types.h

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\binfunc.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\cut.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\door.c

[Found 'njCnk'] in: recvx-decomp-master\src\ps2\veronica\prog\effect.c

[Found 'eval'] in: recvx-decomp-master\src\ps2\veronica\prog\effsub1.c

[Found 'njCnk'] in: recvx-decomp-master\src\ps2\veronica\prog\effsub1b.c

[found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\effsub2.c

[Found 'NJS_CNK_MODEL'] in: recvx-decomp-master\src\ps2\veronica\prog\effsub3.c

[Found 'njCnk'] in: recvx-decomp-master\src\ps2\veronica\prog\effsub4.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en01.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en01b.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en01sub.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en03.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en04.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en05.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en06.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en07.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en09.c

[found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en11.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en12.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en13.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en14.c

[Found 'model'] in: recvx-decomp-master\src\ps2\veronica\prog\en15.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en17.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en17sub.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en18.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en19.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en22.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en23.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en26.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\en30.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\event.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\face_bh.c

[Found 'njCnk'] in: recvx-decomp-master\src\ps2\veronica\prog\game.c

[Found 'NJS_CNK_MODEL'] in: recvx-decomp-master\src\ps2\veronica\prog\hitchkl.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\itemview.c

[Found 'njCnk'] in: recvx-decomp-master\src\ps2\veronica\prog\light.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\map.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\MdlPut.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\Motion.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\njplus.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\objitm.c

[Found 'eval'] in: recvx-decomp-master\src\ps2\veronica\prog\player.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\playpch.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\ps2_NinjaCnk.c

[Found 'njCnk'] in: recvx-decomp-master\src\ps2\veronica\prog\screen.c

[Found 'njCnk'] in: recvx-decomp-master\src\ps2\veronica\prog\sp_evt.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\weapon.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\zonzon.c

[Found 'NJS_CNK_OBJECT'] in: recvx-decomp-master\src\ps2\veronica\prog\zonzon1.c

I also found this Blender script for the PS2 version: https://github.com/kapdap/re-cvx-blender/tree/main, but it only works for environments—not for the mdl files used in the high-resolution PS3 version. I’ve been using the ARC extractor from https://github.com/kapdap/re-cvx-tools, along with Kuriimu2 for the textures. However, the models are in mdl format, and the animations—once converted—are in mnt or rmt format. How can I go about converting these, or perhaps creating a functional parser that reads the mesh indices and correctly displays the model, including the skeleton weights and the actual UVs?

The main problem is that the vertex positions in PS3 models come with a transformation vector or local bone matrix per sub-mesh, or the position floats are preceded by skinning weight data even in static objects.

If we apply an identity matrix or tell Noesis to ignore the weight bytes before each coordinate, the model should end up assembling its 3D volume completely.

If anyone could help me with this, I would be truly grateful.

Edited by Randalf2theReturn

Create an account or sign in to comment

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.