1 /// MDB file parsing 2 module nwn.mdb; 3 4 import std.stdint; 5 import std.typecons; 6 import nwnlibd.parseutils; 7 8 /// MDB file parsing 9 class Mdb { 10 /// 11 static align(1) struct Header { 12 static assert(this.sizeof == 12); 13 align(1): 14 char[4] type; 15 uint16_t majorVersion; 16 uint16_t minorVersion; 17 uint32_t packets_length; 18 } 19 /// 20 Header header; 21 22 /// 23 enum PacketType: char[4] { 24 RIGD = "RIGD", /// Rigid body 25 SKIN = "SKIN", /// Skin Vertex 26 COL2 = "COL2", /// Coarse collision mesh 27 COL3 = "COL3", /// Precise collision mesh 28 HOOK = "HOOK", /// Hook point 29 WALK = "WALK", /// Walk Mesh 30 COLS = "COLS", 31 TRRN = "TRRN", 32 HELM = "HELM", 33 HAIR = "HAIR" 34 } 35 36 /// 37 static align(1) struct PacketKey { 38 static assert(this.sizeof == 8); 39 align(1): 40 PacketType type; 41 uint32_t offset; 42 } 43 /// 44 PacketKey[] packet_keys; 45 46 /// 47 static struct Packet { 48 /// 49 PacketType type; 50 /// 51 package ubyte[] rawData; 52 53 /// 54 package this(PacketType type, in ubyte[] rawData){ 55 this.type = type; 56 this.rawData = rawData.dup; 57 } 58 59 /// 60 auto get(PacketType type)(){ 61 assert(type == this.type, "Wrong packet type"); 62 63 scope(exit) rawData = null; 64 65 static if(type == PacketType.WALK) return MdbWALK(rawData); 66 else static assert(0, "MDB packet "~type~" not implemented"); 67 } 68 } 69 /// 70 Packet[] packets; 71 72 73 74 /// 75 this(in ubyte[] data){ 76 auto cr = new ChunkReader(data); 77 header = cr.read!Header; 78 79 packet_keys = cr.readArray!PacketKey(header.packets_length).dup; 80 81 packets.length = header.packets_length; 82 foreach(ref packet ; packets){ 83 auto type = cr.read!PacketType; 84 auto size = cr.read!uint32_t; 85 86 packet = Packet(type, cr.readArray(size)); 87 } 88 89 assert(cr.bytesLeft == 0, "Remaining data"); 90 } 91 92 } 93 94 private align(1) union MdbVertex { 95 static assert(this.sizeof == 12); 96 align(1): 97 float[3] position; 98 99 private struct Xyz{ float x, y, z; } 100 Xyz _xyz; 101 alias _xyz this; 102 } 103 104 private align(1) struct MdbTriangle { 105 static assert(this.sizeof == 6); 106 align(1): 107 uint16_t[3] vertices; 108 } 109 110 111 /// 112 struct MdbWALK { 113 114 /// 115 static align(1) struct Header { 116 char[32] name; 117 uint32_t ui_flags; 118 uint32_t vertices_length; 119 uint32_t triangles_length; 120 } 121 /// 122 Header header; 123 /// 124 MdbVertex[] vertices; 125 126 /// 127 static align(1) struct MdbWalkTriangle { 128 align(1): 129 MdbTriangle tri; 130 alias tri this; 131 132 enum Flags : uint32_t { 133 walkable = 0b00000000_00000000_00000000_00000001, 134 _res1 = 0b00000000_00000000_00000000_00000010, 135 _res2 = 0b00000000_00000000_00000000_00000100, 136 dirt = 0b00000000_00000000_00000000_00001000, 137 grass = 0b00000000_00000000_00000000_00010000, 138 stone = 0b00000000_00000000_00000000_00100000, 139 wood = 0b00000000_00000000_00000000_01000000, 140 carpet = 0b00000000_00000000_00000000_10000000, 141 metal = 0b00000000_00000000_00000001_00000000, 142 swamp = 0b00000000_00000000_00000010_00000000, 143 mud = 0b00000000_00000000_00000100_00000000, 144 leaves = 0b00000000_00000000_00001000_00000000, 145 water = 0b00000000_00000000_00010000_00000000, 146 puddles = 0b00000000_00000000_00100000_00000000, 147 _res3 = 0b11111111_11111111_11000000_00000000, 148 } 149 uint32_t flags; 150 } 151 /// 152 MdbWalkTriangle[] triangles; 153 154 /// 155 this(in ubyte[] rawData){ 156 auto cr = ChunkReader(rawData); 157 158 header = cr.read!Header; 159 vertices = cr.readArray!MdbVertex(header.vertices_length).dup; 160 triangles = cr.readArray!MdbWalkTriangle(header.triangles_length).dup; 161 162 assert(cr.bytesLeft == 0, "Remaining data"); 163 } 164 165 /// 166 void toObj(string filePath) const { 167 import std.stdio; 168 auto obj = File(filePath, "w"); 169 obj.writeln("o ", filePath); 170 171 foreach(ref v ; vertices){ 172 writeln("z=", *cast(uint16_t*)&v.z); 173 obj.writefln("v %(%f %)", v.position); 174 } 175 foreach(ref t ; triangles){ 176 obj.writefln("f %s %s %s", t.vertices[0] + 1, t.vertices[1] + 1, t.vertices[2] + 1); 177 } 178 } 179 180 } 181 182 183 unittest { 184 const balcony = cast(ubyte[])import("PLC_MC_BALCONY3.MDB"); 185 186 auto mdb = new Mdb(balcony); 187 foreach(ref packet ; mdb.packets){ 188 if(packet.type == Mdb.PacketType.WALK){ 189 auto wm = packet.get!(Mdb.PacketType.WALK); 190 } 191 } 192 }