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

Noesis plugin (.fmt) for XMX/LDMX models โ€“ broken mesh due to index/stride issues

Featured Replies

๐Ÿงต [REQUEST] Noesis plugin for XMX/LDMX โ€“ mesh corruption (index/stride issue)

Hi,

I'm reverse engineering a proprietary XMX / LDMX model format and I'm getting consistent mesh corruption when parsing it.

I'd like help identifying the correct vertex layout and eventually building a Noesis plugin (.fmt or Python) to export to FBX/OBJ.


๐Ÿ“ฆ Format info (confirmed)

Header:

4C 44 4D 58 โ†’ "LDMX"

Chunks found:

  • EMAN (likely "NAME", reversed)

  • MNXT (textures)

  • VPRG (vertex block)

  • INDX (index buffer)


๐Ÿ”น Vertex data

Float data detected:

E0 FE 48 BE โ†’ -0.196
48 E7 E7 BE โ†’ -0.453
80 09 A7 3C โ†’ 0.020

Likely:

  • Position (float3)

  • followed by normals / UVs / etc.

โ— Unknown:

  • vertex stride (suspected 32 / 36 / 40 bytes)


๐Ÿ”น Index buffer

58 44 4E 49 โ†’ "INDX"
  • Appears to be uint16 (little endian)

Example:

01 00 02 00 03 00

โš ๏ธ Problem

Mesh is corrupted:

  • degenerate triangles like:

22 00 22 00 22 00
25 00 25 00 25 00

Result:

  • collapsed faces

  • broken topology


๐Ÿง  Suspected causes

  • Wrong vertex stride

  • Incorrect index offset

  • Vertex count mismatch

  • Possible mixed endianness


๐Ÿ“‚ Textures

Paths inside file:

d:\FS\tex_zb01.zip/:xxxx.dds

โ†’ DDS inside ZIP archives


๐ŸŽฏ What I need

  • Help identifying correct vertex layout / stride

  • Eventually a Noesis plugin that:

    • reads vertices correctly

    • fixes mesh

    • exports to FBX/OBJ


๐Ÿงช I can provide

  • Sample files

  • Hex dumps

  • Test results


โ“ Question

Has anyone seen a similar format or can help determine the correct vertex layout / stride?

Best result, but it's not entirely correct; it always reads shitty padding or interspersed garbage. One or more triangles always come out wrong, but it's the best versionโ€”the one where the mesh turns out almost perfect...

Almost all the files to be converted are located at: https://github.com/randalfcastro-tech/xmx-testing


from inc_noesis import *
import noesis
import rapi
import struct

# =========================
# REGISTER
# =========================

def registerNoesisTypes():
handle = noesis.register("XMX REAL FINAL STABLE", ".xmx")
noesis.setHandlerTypeCheck(handle, xmxCheckType)
noesis.setHandlerLoadModel(handle, xmxLoadModel)
return 1

def xmxCheckType(data):
return 1 if data[:4] == b"LDMX" else 0


# =========================
# FIND REAL REGION
# =========================

def findRealMeshRegion(data):
vprg = data.find(b"VPRG")
if vprg == -1:
return -1, -1

xtrv = data.find(b"XTRV", vprg)
xdni = data.find(b"XDNI", xtrv)

return xtrv, xdni


# =========================
# VERTICES (REAL)
# =========================

def parseVertices(data):
xtrv, xdni = findRealMeshRegion(data)

if xtrv == -1 or xdni == -1:
print(" Missing real mesh blocks")
return None

start = xtrv + 4
end = xdni

bs = NoeBitStream(data)
bs.seek(start)

verts = []
norms = []
uvs = []

stride = 32
MAX_VERTS = 200000

while bs.tell() + stride <= end and len(verts) < MAX_VERTS:
try:
x = bs.readFloat()
y = bs.readFloat()
z = bs.readFloat()

nx = bs.readFloat()
ny = bs.readFloat()
nz = bs.readFloat()

u = bs.readFloat()
v = bs.readFloat()

# sanity check (evita basura)
if abs(x) > 10000 or abs(y) > 10000 or abs(z) > 10000:
break

verts.append((x,y,z))
norms.append((nx,ny,nz))
uvs.append((u,1.0-v))

except:
break

print(" Vertices:", len(verts))
return verts, norms, uvs


# =========================
# INDICES (REAL)
# =========================

def parseIndices(data):
_, xdni = findRealMeshRegion(data)

if xdni == -1:
print(" No XDNI")
return None

bs = NoeBitStream(data)
bs.seek(xdni + 4)

indices = []
MAX_INDICES = 500000

while bs.tell() + 2 <= len(data) and len(indices) < MAX_INDICES:
indices.append(bs.readUShort())

print(" Indices:", len(indices))
return indices


# =========================
# STRIP โ†’ TRIANGLES
# =========================

RESTART = 0xFFFF

def stripToTriangles(indices, vertCount):
tris = []
flip = False

for i in range(len(indices)-2):
a = indices[i]
b = indices[i+1]
c = indices[i+2]

if a == RESTART or b == RESTART or c == RESTART:
flip = False
continue

if a == b or b == c or a == c:
flip = not flip
continue

if a >= vertCount or b >= vertCount or c >= vertCount:
continue

if flip:
tris.extend([b, a, c])
else:
tris.extend([a, b, c])

flip = not flip

return tris


# =========================
# BUILD
# =========================

def buildMesh(verts, norms, uvs, indices):

tris = stripToTriangles(indices, len(verts))

# sanitizar
clean = []
maxVert = len(verts)

for i in tris:
if not isinstance(i, int):
continue
if i < 0 or i >= maxVert:
continue
clean.append(i)

clean = clean[:(len(clean)//3)*3]

if len(clean) < 3:
print(" No valid triangles")
return

vb = b''.join([struct.pack("3f", *v) for v in verts])
nb = b''.join([struct.pack("3f", *n) for n in norms])
ub = b''.join([struct.pack("2f", *u) for u in uvs])

rapi.rpgBindPositionBuffer(vb, noesis.RPGEODATA_FLOAT, 12)
rapi.rpgBindNormalBuffer(nb, noesis.RPGEODATA_FLOAT, 12)
rapi.rpgBindUV1Buffer(ub, noesis.RPGEODATA_FLOAT, 8)

try:
ib = struct.pack("%dH" % len(clean), *clean)
except Exception as e:
print(" PACK ERROR:", e)
return

rapi.rpgCommitTriangles(
ib,
noesis.RPGEODATA_USHORT,
len(clean),
noesis.RPGEO_TRIANGLE,
1
)


# =========================
# MAIN
# =========================

def xmxLoadModel(data, mdlList):
ctx = rapi.rpgCreateContext()

parsed = parseVertices(data)
if not parsed:
return 0

verts, norms, uvs = parsed

indices = parseIndices(data)
if not indices:
return 0

print(" Building mesh")

buildMesh(verts, norms, uvs, indices)

mdl = rapi.rpgConstructModel()
mdlList.append(mdl)

return 1

zb01_chain.xmx.png

stc_capsule.xmx.png

zc04_sk013.xmx.png

zb03_sk_c01.xmx.png

fmt_parser_xmx.py

Edited by Ralp1670

  • Supporter

degenerated triangles: just a wild guess (without checking the details): maybe the indices of those models with that problem are DWords, not shorts?

  • Author
15 minutes ago, shak-otay said:

degenerated triangles: just a wild guess (without checking the details): maybe the indices of those models with that problem are DWords, not shorts?

๐Ÿ‘‰ NOT DWORD (uint32)
โœ” These models correctly use UINT16.

  • Author
6 minutes ago, shak-otay said:

Your disrespect forces me to leave the thread. Good luck.

6 minutes ago, shak-otay said:

Your disrespect forces me to leave the thread. Good luck.

Friend, I thought you would ask questions or present arguments, but you are taking things for granted that simply aren't true; even if it sounds like a clever answer, it isn't backed by factsโ€”and Iโ€™ve tried to be as polite as possible...

  • Localization
4 hours ago, Ralp1670 said:

I'm reverse engineering a proprietary XMX / LDMX model format and I'm getting consistent mesh corruption when parsing it.

Can you tell us which game (?) uses this .xmx format?

  • Supporter

Since there is FFFF strip terminator it could be some xbox / 360 game. But now it's a history. Also i can't see any samples.

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.