April 25Apr 25 Hi, I'm trying to reverse engineer model files from Iron Sky: Invasion (Reality Pump / GRACE2 engine), and I’m stuck with .mod files that appear to contain mesh data. Standard tools do NOT work: *Game Extractor → fails *3D Object Converter → fails *Noesis (no plugin found that works) *Blender → no importer available File structure: The files start with: 46 4F 52 4D 00 00 E2 45 4D 45 53 48 48 41 53 48 49 4E 46 4F ... 52 50 45 78 70 56 65 72 3A 35 ASCII: FORM MESHHASH INFO RPExpVer:5 So this clearly looks like:-IFF-style chunk format (FORM)/ -internal mesh reference (MESHHASH) / -exporter tag from Reality Pump (RPExpVer:5) I’m trying to: extract mesh (vertices / indices / UVs) convert to OBJ / FBX Questions Has anyone worked with GRACE2 / Reality Pump formats from Iron Sky: Invasion? Are there any existing Noesis plugins or scripts for this specific format? Are these .mod files standalone meshes or references to data stored elsewhere (e.g. .wd containers)? Any documentation about the chunk structure after FORM? Notes These are NOT audio/video .mod files They definitely contain structured binary mesh-related data Tools for Two Worlds 2 don’t seem directly compatible I can also provide multiple samples if needed for pattern analysis. I've uploaded an .ani file and several .mod files to GitHub, along with a hexadecimal dump of them. I hope someone can help me. https://github.com/randalfcastro-tech/IronSkyInvasion Any help or pointers would be appreciated. Thanks. Edited April 25Apr 25 by Ralp1670
April 25Apr 25 Localization The MOD files are structured something like this (in ImHex Pattern Language). This structure fits with barrel.mod and turret.mod, at least. #pragma endian big struct NullString { char text[]; }; struct Marker { u8; u8; u16; } [[single_color]]; struct Vertex { float pos[3]; u8 color[4]; float; float; float uv[2]; } [[single_color]]; struct Triangle { u16; u16; u16; }; struct STRPBlock { char strp[4]; u32; u32 string_count; NullString strings[string_count]; }; struct File { char form[4]; u32 payload_size; char mesh_hash[8]; padding[12]; char info[4]; s32; s32; s32; s32; s32; s32; s32 strp_off; s32; char rpexp[10]; padding[6]; char file[4]; u32; float; u32; u32; Marker; u32; Marker; u32; Marker; Marker; u32; Marker; padding[12]; Marker; padding[12]; float matrix44[16]; Marker; u32; Marker; u32; u32; Marker; u32; Marker; u32; Marker; float; Marker; u32; Marker; u32; u32; Marker; u32; Marker; u32; Marker; float floats[8]; Marker; u32; u32; Marker; u32; Marker; u32; Marker; float floats_1[9]; Marker; u32; u32; u32 vert_block_size; Vertex vertices[vert_block_size / 32]; Marker; u32 vert_count; Marker; u32; u32; Marker; u32; u32; u32 tri_block_size; Triangle triangles[tri_block_size / 6]; Marker; u32 tri_idx_count; u32; u32; Marker; u32; u32; Marker; u32; Marker; u32; Marker; padding[24]; Marker; u32; u32; Marker; u32; Marker; u32; Marker; STRPBlock strp_block @ strp_off; }; File file_at_0x00 @ 0x00; Edited April 25Apr 25 by jmancoder
April 26Apr 26 Author Did anyone manage to get anything out? There are larger files; I've been trying all day to convert a small .mod file to an obj, but the resulting shapes don't match the model. Any ideas? I can upload more samples to GitHub. I thought a forum of experts in game model conversion would find the key.
April 26Apr 26 Author Look, zeppelin.mod is huge, but with noesis it produces strange shapes, and no matter how hard I try, I can't even get the model from ammo.mod. : https://github.com/randalfcastro-tech/IronSkyInvasion Edited April 26Apr 26 by Ralp1670
April 26Apr 26 The files are broken into hundreds of smaller meshes - this is one of the larger ones in zeppelin:
April 26Apr 26 Author So far I have achieved this with noesis: Plugin fm_convert_mod_ironsky: Quote from inc_noesis import * def registerNoesisTypes(): handle = noesis.register("Iron Sky Invasion Final", ".mod") noesis.setHandlerTypeCheck(handle, noepyCheckType) noesis.setHandlerLoadModel(handle, noepyLoadModel) return 1 def noepyCheckType(data): return 1 if data[:4] == b'FORM' else 0 def noepyLoadModel(data, mdlList): bs = NoeBitStream(data) bs.setEndian(1) # Big Endian #1. GEOMETRY (Fit for Ammo: Stride 32, Offset 0x288) # By using Offset 0x288, we skip the garbage and align X,Y,Z. v_ofs = 0x288 stride = 32 v_count = 95 scale = 50.0 vert_list = [] bs.seek(v_ofs) for i in range(v_count): try: vx = bs.readFloat() * scale vy = bs.readFloat() * scale vz = bs.readFloat() * scale # Orientation to display the volume: (vx, vz, -vy) vert_list.append(NoeVec3((vx, vz, -vy))) # Exact 32-byte jump to the start of the next vertex bs.seek(v_ofs + ((i + 1) * stride)) except: break # 2. GENERATE FACES (Manual Triangle Strip to clean the shape) indices = [] for i in range(len(vert_list) - 2): if i % 2 == 0: indices.extend([i, i + 1, i + 2]) else: indices.extend([i + 1, i, i + 2]) #3. FINAL CONSTRUCTION if vert_list: mesh = NoeMesh(indices, vert_list, "mesh_main") mdl = NoeModel([mesh]) mdlList.append(mdl) print(">>> REBUILT MODEL: Generated solid with Stride 32 at 0x288.") return 1 return 0 🛠 ResHax Technical Specifications: Iron Sky Invasion (.MOD) Game: Iron Sky Invasion Engine: Reality Pump (Grace version) File type: .mod (3D Mesh) Detected version: RPExpVer:5 (Reality Pump Exporter Version 5) 1. Header Structure (FORM Container) The file uses a RIFF/IFF container in Big Endian. Signature: 46 4F 52 4D (FORM) Main chunks: MESH, INFO, FILE. 2.Control text: Contains the string RPExpVer:5 indicating that it is the modern version of the engine (similar to Two Worlds II but with variations). Geometry Block (Vertices) Start marker: 82 80 00 18 (followed by 4 bytes indicating the block size). Typical offset: 0x280 (in ammo.mod). Stride (Size per vertex): For small objects (ammo.mod): 32 bytes. For large ships (zeppelin.mod, ships): 44 bytes. Vertex Layout (32 bytes): float pos[3] (12 bytes) u8 packed_normal[4] or color[4] (4 bytes) float unknown[2] (8 bytes) float uv[2] (8 bytes) -> Located at the end of the 32-byte block. 3.Topology Block (Faces/Indices) Start marker: 82 80 00 1A (followed by 4 bytes with the block size). Index format: uint16 (2 bytes per index). Mesh type: Standard Triangle List, but the engine sometimes uses Triangle Strips depending on the flag in the header. Pattern detected: 00 00 00 01 00 02 00 02 00 01 00 03 (This confirms they are individual triangles). 4. Texture Block (STRP) Bookmark: 53 54 52 50 (STRP). Contents: Table of strings with .dds filenames (e.g., iss.dds, iss_bump.dds). Location: Almost always at the end of the file. I think that in Reshax, perhaps the Zaramot or Shakotay members were experts in this game engine. Could you provide me with a Noesis Python script where the parser works by reading all meshes and submeshes of all .mod, as well as their UVs? Thank you very much. The photos look great, but I'm having trouble with stride when reading the .mod files. This is ammo.mod but it doesn't work correctly as shown in the image, and the script doesn't export anything in larger sizes. Edited April 26Apr 26 by Ralp1670
April 26Apr 26 Localization Solution It seems to be a tagged format. Try the attached Noesis plugin. It is far from perfect and skips most of the tags and ints currently. Also, I think each vertex stores a binormal or something similar right after the position vector, which I did not read yet. There are two constants at the start of the plugin. Setting VERBOSE to True will print a messy list of tags that I was using for debugging. Setting LOG_STRP to True will simply print the STRP strings at the end of the file. fmt_iron_sky_invasion_mod.py
April 26Apr 26 Author 32 minutes ago, jmancoder said: It seems to be a tagged format. Try the attached Noesis plugin. It is far from perfect and skips most of the tags and ints currently. Also, I think each vertex stores a binormal or something similar right after the position vector, which I did not read yet. There are two constants at the start of the plugin. Setting VERBOSE to True will print a messy list of tags that I was using for debugging. Setting LOG_STRP to True will simply print the STRP strings at the end of the file. fmt_iron_sky_invasion_mod.py 4.41 kB · 1 download Friend, I just have one question; are the UVs being exported correctly?
April 26Apr 26 Localization 14 minutes ago, Ralp1670 said: are the UVs being exported correctly? It's hard to be certain without texture files to test it with. The STRP block often includes DDS filenames, so I would try finding the correct textures for one mesh first and seeing how they fit with the UV map(s). It would probably be best to do this with a mesh without lots of parts so you don't need to worry about material IDs.
April 26Apr 26 Author 13 minutes ago, jmancoder said: It's hard to be certain without texture files to test it with. The STRP block often includes DDS filenames, so I would try finding the correct textures for one mesh first and seeing how they fit with the UV map(s). It would probably be best to do this with a mesh without lots of parts so you don't need to worry about material IDs. Okay, tell me the name of a texture that will help you get the UVs right; that's what's missing. With VERBOSE, it comes out a bit messy, but I think it works with all of them. I'm giving you ISS.dds, which is the one ammo.mod uses, but if you need another one for the script to work perfectly, great. You deserve a round of applause! I thought I'd never get the models, and you've made it happen. All my respect to Reshax. iss.dds iss_bump.dds Edited April 26Apr 26 by Ralp1670
April 26Apr 26 Author I'm also leaving you the textures from Zeppelin.mod; with those tests the script will be very, very robust. 💯 zeppelin_texture_2_bump.dds zeppelin_texture_3.dds zeppelin_texture_4.dds zeppelin_texture_4_bump.dds zeppelin_light_hull.dds zeppelin_light2.dds zeppelin_texture_2.dds Edited April 26Apr 26 by Ralp1670
April 26Apr 26 Localization They seem to align with the UV maps, yes. I didn't map out the STRP block, but like I said, you can set LOG_STRP to True and VERBOSE to False and it should print a flat list of properties that includes the texture names, e.g. after Tex0Name or $Merged$. That should give you a decent guess as to which textures apply to which sub-meshes. Edited April 26Apr 26 by jmancoder
Create an account or sign in to comment