Monday at 09:20 AM2 days I’ve lost count of how many attempts I’ve made, but I still haven't managed to fully restore the mesh. Is there anyone knowledgeable about this who could help with the .model/.x formats from the game *300 Heroes*? I don't want to proceed any further until I've restored the mesh; I was able to extract the model, bones, and animations from the files—they aren't encrypted, just using a different structure. The problem: the rendered model has holes. I want to patch them, but nothing is working so far. When assembling the model at the vertex level, it looks perfect, but it tears apart when the triangles are assembled.Last script python 157_skin11.zip ezgame_to_fbx.py
4 hours ago4 hr Author I'm reverse engineering the model format from the Chinese MOBA 300 Heroes (300英雄), newer engine.Most of it is cracked and I can already export a rigged FBX. I'm stuck on three thingsand would appreciate any pointers — full details below so you don't have to take my word for anything.Tested on two samples: 157_skin11.model (complex character with wings and ribbons)and 334.model (simple one, used as a control).0x00 magic 'EG3D' 0x08 u32 main block size 0x0C JSON metadata, length = main block size then raw binary buffers, referenced by offsets from the JSON generator: "ezgame", version: "2.0", leftHandCoord: true.Note this is not the older jumpX (.x) format that the public jumpxfbx tool handles.This is a newer engine used by the same game — I couldn't find any existing tooling for it.JSON meta — array of 8 elementsindexcontentsmeta[0]header (generator, version, leftHandCoord)meta[1]root nodemeta[2]materialsmeta[3]groups → submeshesmeta[4]bone bind palettes (inverse-bind matrices)meta[5]skeleton nodesmeta[6]miscmeta[7]animation clipsSubmeshsm = [materialIndex, vertexCount, [attrs...], [idxOffset, idxCount]]Attributes (attr[0] = type, attr[5] = data offset):typewhatbytes/vertencoding0position6u16×3, dequant bias + raw*scale1Normals4NOT CRACKED ← question 16Uv4u16×2, dequant, V-flip (v = 1-v)5Skin weights8float16×44Bone indices4uint8×4 (index into the submesh's bind palette)2Tangents8not examinedBuffers are packed back-to-back with no padding between them.Example for 157_skin11, submesh 0 (vertexCount = 13485):positions 2711852 + 13485*6 = 2792762 (+2 pad, +12 scale, +12 bias) normals 2792788 + 13485*4 = 2846728 UV 2846728 + 13485*4 = 2900668 (+8 scale, +8 bias) weights 2900684 + 13485*8 = 3008564 bones 3008564 + 13485*4 = 3062504 tangents 3062504 + 13485*8 = 3170384 ← exactly the start of the index buffer The last attribute lands exactly on the index buffer offset, so the layout checks out byte for byte.Position dequantisationscale and bias (3 floats each) sit immediately after the u16 array, with variable padding.Don't trust attr[6] / attr[7] for this (they mean something else) — instead scan the64 bytes following the data and take the first triple where1e-9 < scale < 1e-2 and |bias| < 5.Index bufferu16, first 6 values are a header (garbage, values >= vertexCount) — skip themthen plain triples = triangle LISTI brute-forced this: it's definitely a list. Strip, fan, planar layout (three separatestreams) and 14 different skip offsets all give roughly half the manifoldness, and theplanar interpretation is absurd (surface area 2151 vs 11). The buffer is monotonic andvertex-cache optimised — index spread within a triangle has median 3, p90 = 12.SkeletonFrom the inverse-bind matrices in meta[4]: 64 bytes per bone, row-major → transpose →invert = world bind matrix. For sample 157 that's 113 bones, namedBip001 Pelvis / Spine / L Thigh … — a standard 3ds Max Biped, plus artist-added bonesBone01, Bone39..Bone59 driving ribbons and hanging ornaments.Zero conflicts across the 4 palettes.What already worksThe attached script runs inside Blender and does the whole pipeline:decode → cleanup → skeleton → skinning (4 bones/vertex) → FBX export.The result opens fine in Autodesk FBX Review with the skeleton correctly inside the mesh.Two format-specific gotchas worth sharing:1. strip→list conversion garbage. ~411 triangles out of 19074 (2% by count but22% by surface area) with an edge longer than 20% of the model diagonal. They're boundto dozens of unrelated bones — not an object, just scattered bridges. My guess is the gameconverted its older jumpX assets to EG3D and the converter baked strip stitches straightinto the triangle list. Fixed by an edge-length threshold — but measure the diagonal overthe WHOLE model, not per submesh (I lost a lot of time to that: a small submesh has asmall diagonal, so its normal triangles get flagged as giants).2. Billboard materials. A material whose name ends in _c (here 157_skin11_c) is acamera-facing card set — the engine rotates it toward the camera every frame, so in a staticviewer its geometry looks like chaos. My control model 334.model has no _c materials,which is exactly why it read clean from the start.Question 1: can't crack the normal packingAttribute type 1, exactly 4 bytes per vertex. What I tried:interpretationresultint8 xyz / 127|n| median 0.815, only 14% unit lengthuint8 (x-128)/127|n| median 1.15110:10:10:2 signed|n| median 0.950, 14% unit11:11:10 signed|n| median 0.973, 16% unitoctahedral int16×2|n| = 1.000, but agreement with face normals ~48% (i.e. random)Octahedral gives unit length but the directions don't correlate with geometric face normals,so either it's the wrong packing or the wrong component order. Raw bytes of the first vertices:[ 48 -127 81 -65] [ -49 -105 52 -66] [ 89 -14 -102 59] [-110 5 92 -76] [-110 5 92 -76]Question: has anyone seen this style of 4-byte normal packing? Could it be a QTangent(a quaternion encoding normal + tangent + binormal in one go)? That would explain why readingit directly as a normal never lines up. Note there's also a separate 8-byte tangent attribute,which argues against QTangent — but I'd rather ask than guess.Question 2: telling a real hole from a legitimate open edgeThe mesh has open boundaries. Some are legitimate (ribbon edges, cape hem, clothing slits),some look like genuine holes in the body (chest, shoulders, feet).Three metrics I tried all turned out to be useless:rendering without backface culling simply cannot show holes — the inside of the far surfacegets shaded like a normal surface, so the model always looks solid;centroid fan-filling large boundary loops creates enormous sheets across the model;a "closed empty region in the 2D projection" detector counts the space between ribbonsas holes (157 has lots of them → 9%, the simple 334 → 0.6%).Meanwhile the triangle density is correct: after welding, 15318 triangles over 8036 vertices= 1.91, where a closed manifold would be 2.0. So no triangles are missing and the decode is complete.Question: is there a reliable automated way to distinguish "hole in the body" from"legitimate open edge" on a game model full of alpha cards and ribbons? For now I've fallenback on doing it by hand in Blender via Select → All by Trait → Non Manifold.Question 3: animation transfermeta[7] holds 17 clips: bat_idle, bat_run, bat_specialidle, bingdong, dance,dead, relive, single_attack_attcom_1/2, single_chuansong, single_run,single_skill_1..4, single_touzhi_1/2.About 390 channels per clip across 139 nodes, so essentially the whole skeleton is animated.The problem: the animations are expressed in the node coordinate frame (meta[5]), whileI build the skeleton from the mesh bind matrices. No single rigid transform maps one onto theother — it looks like it needs a per-bone transfer.Question: what's the correct general approach for relating a node hierarchy to a bindskeleton in formats like this? I'm after the general method, not necessarily anything specificto this engine.Time sinks, in case they save someone elseCompute thresholds against the whole model diagonal, not per submesh.Multi-object OBJ (o s0, o s1) breaks in some viewers — faces of the second object startreferencing vertices of the first. Write a single object, or de-index the OBJ.Windows paths: "D:\157_skin11.model" without the r prefix — Python eats \157 as anoctal escape. Use forward slashes.Attachedfilewhatezgame_to_fbx.pyBlender script: .model → rigged FBXEG3D_WORK_REPORT-EN.mdfull technical write-up, including every rejected hypothesis157_FINAL.objsample decode outputAny thoughts welcome, especially on the normals and the animation transfer. ezgame_to_fbx.py 157_FINAL.zip EG3D_WORK_REPORT_EN.zip
Create an account or sign in to comment