• Welcome to Smashboards, the world's largest Super Smash Brothers community! Over 250,000 Smash Bros. fans from around the world have come to discuss these great games in over 19 million posts!

    You are currently viewing our boards as a visitor. Click here to sign up right now and start on your path in the Smash community!

MDL0 file information needed for Converter

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
alright...
can someone explain, or simply add comments to the code for williams converter??

(only for importing...)

thanx in return
 

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
Unfortunately, I know nothing about Python, and I'm too busy right now to go back and comment Will's code (probably not the best source anyway). I can provide you with specs if you need them though, as well as some bookmarked Hex Editor Neo files that I've found indispensable. And if you're ever going to be up around Belleville, or if there's any chance of you making it up to West Bloomfield 4 let me know.

EDIT: One more thing, if you're trying to convert actual character files right now, DON'T. Start with trophies, since you don't have too worry about applying the weights and the transformations that go with them. After that, just add support for the weights and you'll be set.
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
Unfortunately, I know nothing about Python, and I'm too busy right now to go back and comment Will's code (probably not the best source anyway). I can provide you with specs if you need them though, as well as some bookmarked Hex Editor Neo files that I've found indispensable. And if you're ever going to be up around Belleville, or if there's any chance of you making it up to West Bloomfield 4 let me know.
yea... I don't think so...
sry :(

have you tried my script yet??

EDIT: One more thing, if you're trying to convert actual character files right now, DON'T. Start with trophies, since you don't have too worry about applying the weights and the transformations that go with them. After that, just add support for the weights and you'll be set.
yes I am working with fighter files right now...
I hate having to modify my code to get it to work right...

Williams converter works better than AIS at getting a complete model...
that's all I want right now...

I'm just worried about verticies right now...
but I expect the head to be down by the feet and etc...

any chance I could just get the verticies imported??
(then I'll modify the code to import faces)

I'll worry about the bones, data trees and everything after I get any model to import with faces...

EDIT:
can I get Pikachu to import like this:


once I get that...
then I'll worry about VGroups, bones, uv's, etc...

yes, that's what I got with Williams converter...
only the model...

I can read C++
but only with comments can I fully understand it...
still don't understand it to be able to read it without comments...

that's why I wanted comments on Will's
 
Last edited:

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
If there's a source for AIS...
I'd rather read that, seeing as it works better with everything else...

however...
when I import into blender (to get a perfect import like Pikachu)
since AIS can't export a perfect model...
I have to use an export from williams converter to manually copy/paste the triangles into the AIS export...
after that I have to use the collada 1.4.0 importer on the AIS export just to get a flawless, or near flawless bone structure...
and finally use the collada 1.4.1 importer on the AIS export to get the injected model with uv's, vgroups, etc...
delete the 1.4.1 bone structure (in edit mode) as all the bones are facing backwards... then delete the bone object...
and parent the 1.4.0 bone structure to the 1.4.1 model


this has only worked for Pikachu... (out of about 6 other chars I've tried)


but if I can get the source for AIS, it would help out by alot
 
Last edited:

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
how come nobody's helping me...
I come to the best, and this is the treatment I get...
emutalk is way better...

it's been abt 2 weeks now and I still can't get no furthur than this:

imported Pikachu's verts (exported from BrBx and saved as a .ddv file)

I thought I could get help here...
but it turns out I'm doing most of the work myself...

I've asked quite enough times what I'm doing wrong...
supplied enough images and everything...
what more can I do to get some help from the best... D:
 
Last edited:

pikazz

Smash Lord
Joined
Dec 6, 2009
Messages
1,868
Location
Sweden, Umeå (Currently in Seattle)
NNID
pikamaxi
you're taking good and big steps for MLD0 converter :D
wish I could help but I would be in way and I dont have enough knoweche between mld0 - "other model format" to help ;_;
how come nobody's helping me...
I come to the best, and this is the treatment I get...
emutalk is way better...
dont know D:
it has been quiet lately
and it seems only then I am here ;_;
 

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
My work probably isn't the best source either, but if you want I uploaded the source for the most recent (unreleased) version of my converter here (wow, I'm such a horrible programmer :(). If you want a good source I would recommend using Sabretooth's converter and BrawlBox.

As for why you're not getting help, the simple answer is, there aren't many people who can help you. There are maybe three people who actually know the format well, and this place seems to have really died down recently. I'm extremely busy, so while I'll try to help with specific questions I don't have much spare time I can contribute, and I'm not too experienced in these matters anyway.
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
O.o didn't realize this was your program...
well...
nice job on the data structures... :D
you get uv's and bones imported perfectly.
can't say the same for faces however...
if you'd like help with faces...
you can use my melee converter source in the code below...
or you could use will's source...

but yea... thanx for the source...
too bad it's uncommented ;_;
but I'll see if I can understand some of the important things...
_____________________________________________________________

as for people...
I only had 2 people to dev my melee importer...
(Milun and Revel8n)
bit still... I was able to get alot more done in a minute than on here...
but I understand time consumption... and I'm sorry if I sounded like a nag or whatever...

anyways...
here's my version 1.4a code:
Code:
import struct as S
dat = open("import.dat", 'rb')
obj = open("export a.obj", 'w')
#mtl = open("export.mtl", 'w')
def HexToDec(h):
    return float(S.unpack("<h", S.pack("<H", int((h.encode('hex')), 16)))[0])
def vert(v): 
    return (HexToDec(v)* 0.01).__str__()
def f():
    f = int(HexToDec(dat.read(2))+1).__str__()
    uv = int(HexToDec(dat.read(2))+1).__str__()
    nor = int(HexToDec(dat.read(2))+1).__str__()
    
    if (int(f, 10) >= 0):
        return f+"/"+uv+"/"+nor
t0 = dat.read(32)
g = 0
a = 0
while (a == 0):
    v = "v "+vert(dat.read(2))+" "+vert(dat.read(2))+" "+vert(dat.read(2))
    if (v == 'v 0.0 0.0 0.0'):
        a = 1
        while (a == 1):
          s = dat.read(6)
          s = dat.read(1).encode('hex')
          if (s == '00'):
            a = 2
            s = dat.seek(1,1)
            obj.write("\n")
            while (a == 2):
              vn = "vn "+vert(dat.read(2))+" "+vert(dat.read(2))+" "+vert(dat.read(2))
              if (vn == 'vn 0.0 0.0 0.0'):
                a = 3
                while (a == 3):
                  s = dat.read(6)
                  s = dat.read(1).encode('hex')
                  if (s == '00'):
                    a = 3.1
                    s = dat.seek(1,1)
                    obj.write("\n")
                    while (a == 3.1):
                      vt  = "vt "+vert(dat.read(2))+" "+vert(dat.read(2))
                      if (vt == 'vt 0.0 0.0'):
                        a = 3.5
 
                        obj.write("\n"+'o object'+g.__str__()+"\n")
                        while (a == 3.5):
                            g = g+1
 
                            # attempt to ensure processing starts at the beginning of a data section
                            #h = dat.read(1).encode('hex')+dat.read(1).encode('hex')+dat.read(1).encode('hex')
                            #if (h == '0000B8' or h == '0000A8' or h == '0000B0' or h == '000090' or h == '000098' or h == '0000A0' or h == '000080'):  #this may also work and find even more matches, need to update backtrack offset if used
                            h = dat.read(1).encode('hex')+dat.read(1).encode('hex')+dat.read(1).encode('hex')+dat.read(1).encode('hex')
                            if (h == '0000B800' or h == '0000A800' or h == '0000B000' or h == '00009000' or h == '00009800' or h == '0000A000' or h == '00008000'):
                                a = 4
                                s = dat.seek(-2,1)
                                pos = dat.tell()
                                #print pos.__str__()+": "+h
                                while (a == 4):
                                    pos = dat.tell()
                                    h = dat.read(1).encode('hex') #+dat.read(1).encode('hex')
                                    #print pos.__str__()+": "+h
                                    if (h == 'B8' or h == 'A8' or h == 'B0' or h == '90' or h == '98' or h == 'A0' or h == '80'):
                                        #s = dat.seek(-1,1)
                                        index = HexToDec(dat.read(2))#index count
                                        if (h == 'B8'): #point
                                            while (index > 0):
                                                f0 = f()
                                                index = index - 1
                                                print "f "+f0
                                                obj.write("f "+f0+"\n")
 
                                        elif (h == 'A8'): #line
                                            while (index > 1):
                                                f0 = f()
                                                f1 = f()
 
                                                index = index - 2
                                                print "f "+f0+' '+f1
                                                obj.write("f "+f0+' '+f1+"\n")
 
                                        elif (h == 'B0'): #line strip
                                            while (index > 1):
                                                f0 = f()
                                                f1 = f()
 
                                                index = index - 1
                                                if (index > 1):
                                                    seek = dat.seek(-6,1)
                                                if (f0 != f1):
                                                    print "f "+f0+' '+f1
                                                    obj.write("f "+f0+' '+f1+"\n")
 
 
                                        elif (h == '90'): #triangle
                                            while (index > 2):
                                                f0 = f()
                                                f1 = f()
                                                f2 = f()
 
                                                index = index - 3
                                                print "f "+f0+' '+f1+' '+f2
                                                obj.write("f "+f0+' '+f1+' '+f2+"\n")
 
                                        elif (h == '98'): #triangle strip
                                            flip = 1
                                            while (index > 2):
                                                f0 = f()
                                                f1 = f()
                                                f2 = f()
 
                                                index = index - 1
                                                if (index > 2):
                                                    seek = dat.seek(-12,1)
                                                if (f0 != f1 and f1 != f2 and f2 != f0):
                                                    if (flip):
                                                        print "f "+f0+' '+f2+' '+f1
                                                        obj.write("f "+f0+' '+f2+' '+f1+"\n")
                                                    else:
                                                        print "f "+f0+' '+f1+' '+f2
                                                        obj.write("f "+f0+' '+f1+' '+f2+"\n")
                                                flip = 1 - flip
 
                                        elif (h == 'A0'): #triangle fan
                                            f0 = f()
                                            index = index - 1
 
                                            while (index > 1):
                                                f1 = f()
                                                f2 = f()
 
                                                index = index - 1
                                                if (index > 1):
                                                    seek = dat.seek(-6,1)
 
                                                if (f0 != f1 and f1 != f2 and f2 != f0):
                                                    print "f "+f0+' '+f1+' '+f2
                                                    obj.write("f "+f0+' '+f1+' '+f2+"\n")
 
                                        elif (h == '80'): #quad
                                            while (index > 3):
                                                f0 = f()
                                                f1 = f()
                                                f2 = f()
                                                f3 = f()
 
                                                index = index - 4
                                                print "f "+f0+' '+f1+' '+f2+' '+f3
                                                obj.write("f "+f0+' '+f1+' '+f2+' '+f3+"\n")
                                        else:
                                            a = 3 # should no longer get here, but just in case...
                                    else:
                                        # backtrack over invalid primitive type bytes
                                        a = 3
                                        s = dat.seek(-1,1)
                            else:        
                                 #backtrack over trailing bytes so that
                                 #the entire file is scanned a byte at a time
                                 s = dat.seek(-3,1)
                      else:
                        print vt
                        obj.write(vt+"\n")
 
              else:
                print vn
                obj.write(vn+"\n")
 
    else:
        print v
        obj.write(v+"\n")
hope this helps you out some... :)
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
your code is very confusing...
not used to working with multiple files...

I like everything small and compact...
(everything in one file)

that's why I don't like C++
and also because C++ is very user unfriendly XD
I'd probably be into it alot more if anything I did actually worked. :p
and if it was interpreted...

eh... I'll just stick with Python...
 

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
Yeah, that version is actually really old. Most of the problems you might find with it I've fixed in future revisions, but none of those future revisions actually made it nearly as far as that version I uploaded (that one's actually close to achieving working mdl0 import export). I scrapped it after realizing Collada probably wasn't the best choice for full model conversion. After Kryal arrived I taught myself C# so I could understand his code and I've been using that ever since (much more user friendly than C++).
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
yea KK...
C, C#, C++, all look the same to me...

so like...
what's up with C#
how can I understand it...
I can understand the vars and everything...
I can't understand (nor do I like) the multiple file usage...

how could I get all that code you gave me into a single file??
just copy the stuff, paste it in the main file and delete the "#include 'filename.h'"??

IDK...
it'd be easier to read though (for finding stuff)
and maybe easier on the program... (if compiled)
I've noticed thing tend to go faster when put in only one file...
(eg my website)
only 4 html pages (1 online (the rest will come soon (got a ton of code to write)))
but what you see is controlled by a series of tables and DIV's...

anyways...
point being, it has to load everything at once instead of when it's called...
meaning responce time is faster and what not...

sorry if I turned this into a lecture :(
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
actually Pharrox...

I do have 1 major Q...
every converter I've noticed has a series of test functions in a 'common' file...
how would I go about selecting the right data type after reading an offset from the relocation table??
also...
I'm not smart enough to know how to use the data stored in the header :embarrass

and can you help me with just getting this DDV file imported?? (custom filename)
it's the verts for Pikachu's body...
all I really need is a conversion method...
I added a bunch of '00' to the end of the file to give the converter a stop point...
the real stop point in the MDL0 will be the offset for the next data patch... (not eing sure what to call it)
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
hey pharrox :D

look at this bit of progress I made:


it's better than that jumbled mess I last posted...
but it still needs some work, as you can only see 4/10 verts...
can you help me at all??
 
Last edited:

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
gah...!!
now I'm mad :mad:

no-one has touched this thread in over a month

screw it...
I'm gonna go back and try the dreaded C++ tutorials again...
see if I can't actually understand this unlike last time... DX
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
alright...
so the tutorials still don't work out...
however...
can anyone compile this for me??
Code:
#include <iostream>
#include <dae.h>
#include <dom/domCOLLADA.h>
#include <ctime>
#include <sstream>
#include <iostream>
#include <string>
#include <fstream>
#include <cmath>

typedef unsigned char uchar;
typedef unsigned int uint;
class Vec3 {
public:
 Vec3() {
  this->x = 0;
  this->y = 0;
  this->z = 0;
 }
 Vec3(float x, float y, float z) {
  this->x = x;
  this->y = y;
  this->z = z;
 }
 float X() {
  return x;
 }
 float Y() {
  return y;
 }
 float Z() {
  return z;
 }
protected:
 float x;
 float y;
 float z;
};
class Vec2 {
public:
 Vec2() {
  this->u = 0;
  this->v = 0;
 }
 Vec2(float u, float v) {
  this->u = u;
  this->v = v;
 }
 float U() {
  return u;
 }
 float V() {
  return v;
 }
protected:
 float u;
 float v;
};
class RGBA {
public:
 RGBA(uchar r, uchar g, uchar b, uchar a) {
  this->r = r;
  this->g = g;
  this->b = b;
  this->a = a;
 }
 uchar R() {
  return r;
 }
 uchar G() {
  return g;
 }
 uchar B() {
  return b;
 }
 uchar A() {
  return a;
 }
protected:
 uchar r;
 uchar g;
 uchar b;
 uchar a;
};
class Vec3Group: public std::vector<Vec3> {
public:
 Vec3Group() {
  set = false;
 }
 void SetBound(Vec3 min, Vec3 max) {
  if (!set) {
   this->min = min;
   this->max = max;
   set = true;
  }
 }
 Vec3 BoundingMin() {
  return min;
 }
 Vec3 BoundingMax() {
  return max;
 }
protected:
 bool set;
 Vec3 min;
 Vec3 max;
};
class Vec2Group: public std::vector<Vec2> {
public:
 Vec2Group() {
  set = false;
 }
 void SetBound(Vec2 min, Vec2 max) {
  if (!set) {
   this->min = min;
   this->max = max;
   set = true;
  }
 }
 Vec2 BoundingMin() {
  return min;
 }
 Vec2 BoundingMax() {
  return max;
 }
protected:
 bool set;
 Vec2 min;
 Vec2 max;
};
typedef std::vector<RGBA> RGBAGroup;
class Vertex {
public:
 Vertex() {
  vertex = 0;
  normal = 0;
  color = 0;
  //uv = NULL;
  uvCount = 0;
 }
 ~Vertex() {
  /*if (uv != NULL) {
   delete[] uv;
   uv = NULL;
   }*/
 }
 void Set(uint16_t vertex, uint16_t normal, uint16_t color, uint16_t uv[],
   int numUV) {
  this->vertex = vertex;
  this->normal = normal;
  this->color = color;
  //this->uv = new uint16_t[numUV];
  for (int i = 0; i < numUV; i++) {
   this->uv[i] = uv[i];
  }
  this->uvCount = numUV;
 }
 uint16_t GetVertex() {
  return vertex;
 }
 uint16_t GetNormal() {
  return normal;
 }
 uint16_t GetColor() {
  return color;
 }
 uint16_t GetUV(int id) {
  if (id >= 0 && id < uvCount) {
   return uv[id];
  }
  else
   return 0;
 }
 int UVCount() {
  return uvCount;
 }
private:
 uint16_t vertex;
 uint16_t normal;
 uint16_t color;
 uint16_t uv[8];
 int uvCount;
};
class TriangleStrip {
public:
 TriangleStrip() {
  locked = false;
 }
 ~TriangleStrip() {
 }
 Vertex& operator[](size_t index) {
  return verts[index];
 }
 void Add(Vertex vert) {
  if (!locked) {
   verts.push_back(vert);
  }
 }
 void Lock() {
  locked = true;
 }
 size_t Count() {
  return verts.size();
 }
private:
 bool locked;
 std::vector<Vertex> verts;
};
class TriangleFan {
public:
 TriangleFan(Vertex origin) {
  this->origin = origin;
  locked = false;
 }
 ~TriangleFan() {
 }
 Vertex& operator[](size_t index) {
  return verts[index];
 }
 void Add(Vertex vert) {
  if (!locked) {
   verts.push_back(vert);
  }
 }
 void Lock() {
  locked = true;
 }
 size_t Count() {
  return verts.size();
 }
 Vertex GetOriginVertex() {
  return origin;
 }
private:
 bool locked;
 Vertex origin;
 std::vector<Vertex> verts;
};
class Triangle {
public:
 Triangle(Vertex p1, Vertex p2, Vertex p3) {
  this->p1 = p1;
  this->p2 = p2;
  this->p3 = p3;
 }
 ~Triangle() {
 }
 Vertex& operator[](size_t index) {
  switch (index) {
  case 0:
   return p1;
  case 1:
   return p2;
  case 2:
   return p3;
  default:
   return p3;
  }
 }
private:
 Vertex p1;
 Vertex p2;
 Vertex p3;
};
class Quad {
public:
 Quad(Vertex p1, Vertex p2, Vertex p3, Vertex p4) {
  this->p1 = p1;
  this->p2 = p2;
  this->p3 = p3;
  this->p4 = p4;
 }
 ~Quad() {
 }
 Vertex& operator[](size_t index) {
  switch (index) {
  case 0:
   return p1;
  case 1:
   return p2;
  case 2:
   return p3;
  case 3:
   return p4;
  default:
   return p4;
  }
 }
private:
 Vertex p1;
 Vertex p2;
 Vertex p3;
 Vertex p4;
};
class PolyGroup {
public:
 PolyGroup() {
  locked = false;
  vertex = 0;
  vertexPtr = NULL;
  normal = 0;
  normalPtr = NULL;
  color = 0;
  colorPtr = NULL;
 }
 ~PolyGroup() {
 }
 void SetVertex(uint16_t vertex, Vec3Group* vertexPtr) {
  if (!locked) {
   this->vertex = vertex;
   this->vertexPtr = vertexPtr;
  }
 }
 void SetNormal(uint16_t normal, Vec3Group* normalPtr) {
  if (!locked) {
   this->normal = normal;
   this->normalPtr = normalPtr;
  }
 }
 void SetColor(uint16_t color, RGBAGroup* colorPtr) {
  if (!locked) {
   this->color = color;
   this->colorPtr = colorPtr;
  }
 }
 void AddUV(uint16_t uvid, Vec2Group* uvPtr) {
  if (!locked) {
   this->uvIDs.push_back(uvid);
   this->uvPtrs.push_back(uvPtr);
  }
 }
 void Lock() {
  locked = true;
 }
 void AddTriangleStrip(TriangleStrip tristrip) {
  if (!locked) {
   triangleStrips.push_back(tristrip);
  }
 }
 void AddTriangleFan(TriangleFan trifan) {
  if (!locked) {
   triangleFans.push_back(trifan);
  }
 }
 void AddTriangle(Triangle triangle) {
  if (!locked) {
   triangles.push_back(triangle);
  }
 }
 void AddQuad(Quad quad) {
  if (!locked) {
   quads.push_back(quad);
  }
 }
 size_t TriangleStripCount() {
  return triangleStrips.size();
 }
 size_t TriangleFanCount() {
  return triangleFans.size();
 }
 size_t TriangleCount() {
  return triangles.size();
 }
 size_t QuadCount() {
  return quads.size();
 }
 TriangleStrip GetTriangleStrip(size_t index) {
  return triangleStrips[index];
 }
 TriangleFan GetTriangleFan(size_t index) {
  return triangleFans[index];
 }
 Triangle GetTriangle(size_t index) {
  return triangles[index];
 }
 Quad GetQuad(size_t index) {
  return quads[index];
 }
 uint16_t VertexGroup() {
  return vertex;
 }
 Vec3Group* VertexGroupPtr() {
  return vertexPtr;
 }
 uint16_t NormalGroup() {
  return normal;
 }
 Vec3Group* NormalGroupPtr() {
  return normalPtr;
 }
 uint16_t ColorGroup() {
  return color;
 }
 RGBAGroup* ColorGroupPtr() {
  return colorPtr;
 }
 uint16_t UVGroup(uint id) {
  if (id < uvIDs.size())
   return uvIDs[id];
 }
 Vec2Group* UVGroupPtr(uint id) {
  if (id < uvPtrs.size())
   return uvPtrs[id];
  else
   return NULL;
 }
 int UVGroupCount() {
  return uvIDs.size();
 }
private:
 bool locked;
 uint16_t vertex;
 Vec3Group* vertexPtr;
 uint16_t normal;
 Vec3Group* normalPtr;
 uint16_t color;
 RGBAGroup* colorPtr;
 std::vector<uint16_t> uvIDs;
 std::vector<Vec2Group*> uvPtrs;
 std::vector<TriangleStrip> triangleStrips;
 std::vector<TriangleFan> triangleFans;
 std::vector<Triangle> triangles;
 std::vector<Quad> quads;
};
 

extern float BigFloat(const char* data, unsigned int offset=0);
extern uint32_t BigUInt32(const char* data, unsigned int offset=0);
extern uint16_t BigUInt16(const char* data, unsigned int offset=0);
extern int16_t BigSInt16(const char* data, unsigned int offset=0); 
 
 
float BigFloat(const char* data, unsigned int offset) {
 union {
  float f;
  unsigned char b[4];
 } dat;
 dat.b[0] = data[offset + 3];
 dat.b[1] = data[offset + 2];
 dat.b[2] = data[offset + 1];
 dat.b[3] = data[offset];
 return dat.f;
}
uint32_t BigUInt32(const char* data, unsigned int offset) {
 union {
  uint32_t u;
  unsigned char b[4];
 } dat;
 dat.b[0] = data[offset + 3];
 dat.b[1] = data[offset + 2];
 dat.b[2] = data[offset + 1];
 dat.b[3] = data[offset];
 return dat.u;
}
uint16_t BigUInt16(const char* data, unsigned int offset) {
 union {
  uint16_t u;
  unsigned char b[2];
 } dat;
 dat.b[0] = data[offset + 1];
 dat.b[1] = data[offset];
 return dat.u;
}
int16_t BigSInt16(const char* data, unsigned int offset) {
 union {
  int16_t u;
  unsigned char b[2];
 } dat;
 dat.b[0] = data[offset + 1];
 dat.b[1] = data[offset];
 return dat.u;
}
 
 
typedef enum {
 LO_CONV_TO_TRIS = 1 // converts all triangle strips/fans and quads to triangles
} LoadOptions;
class MDL0File {
public:
 MDL0File();
 virtual ~MDL0File();
 /**
  * Load an MDL0 file.
  *
  * @param data An array containing the all the data in the file.
  * @return True on a success, false on failure.
  */
 bool Load(const char* data, uint length, uint options);
 void Unload();
 PolyGroup* GetPolygonGroup(int id);
 int PolygonGroupCount();
 Vec3Group* GetVertexGroup(int id);
 int VertexGroupCount();
 RGBAGroup* GetColorGroup(int id);
 int ColorGroupCount();
 Vec3Group* GetNormalGroup(int id);
 int NormalGroupCount();
 Vec2Group* GetUVGroup(int id);
 int UVGroupCount();
private:
 bool loadGroup(const char* data, int type);
 bool loadVec3Group(const char* data, Vec3Group* group);
 bool loadVec2Group(const char* data, Vec2Group* group);
 bool loadRGBAGroup(const char* data, RGBAGroup* group);
 bool loadPolyGroup(const char* data, PolyGroup* group);
 Vertex loadVertex(const char* data, int offset, std::vector<int> fmt,
   bool hasNormal, bool hasColor, int unknown, int uv, int& outLength);
 bool loaded;
 uint options;
 PolyGroup* polyGroups;
 int polyGroupCount;
 Vec3Group* vertexGroups;
 int vertexGroupCount;
 RGBAGroup* colorGroups;
 int colorGroupCount;
 Vec3Group* normalGroups;
 int normalGroupCount;
 Vec2Group* uvGroups;
 int uvGroupCount;
};
 
 
MDL0File::MDL0File() {
 loaded = false;
 polyGroups = NULL;
 polyGroupCount = 0;
 vertexGroups = NULL;
 vertexGroupCount = 0;
 colorGroups = NULL;
 colorGroupCount = 0;
 normalGroups = NULL;
 normalGroupCount = 0;
 uvGroups = NULL;
 uvGroupCount = 0;
 options = 0;
}
MDL0File::~MDL0File() {
 if (vertexGroups != NULL) {
  delete[] vertexGroups;
  vertexGroups = NULL;
 }
 if (normalGroups != NULL) {
  delete[] normalGroups;
  normalGroups = NULL;
 }
 if (colorGroups != NULL) {
  delete[] colorGroups;
  colorGroups = NULL;
 }
 if (uvGroups != NULL) {
  delete[] uvGroups;
  uvGroups = NULL;
 }
 if (polyGroups != NULL) {
  delete[] polyGroups;
  polyGroups = NULL;
 }
}
bool MDL0File::Load(const char* data, uint length, uint convToTris) {
 if (data == NULL) {
  return false;
 }
 // we need the array to be at least as long as the header
 if (length < 64)
  return false;
 // check for "MDL0"
 if (data[0] != 'M' || data[1] != 'D' || data[2] != 'L' || data[3] != '0')
  return false;
 this->options = options;
 const char *groupPtrs[11];
 for (int i = 0; i < 11; i++) {
  uint32_t ptr = BigUInt32(data, 16 + i * 4);
  if (ptr >= length)
   return false; // pointer is out of range
  if (ptr == 0)
   groupPtrs[i] = NULL;
  else
   groupPtrs[i] = data + ptr;
 }
 for (int i = 0; i < 11; i++) {
  if (groupPtrs[i] != NULL) {
   loadGroup(groupPtrs[i], i);
  }
 }
 loaded = true;
 return true;
}
void MDL0File::Unload() {
}
PolyGroup* MDL0File::GetPolygonGroup(int id) {
 if (id < polyGroupCount)
  return &polyGroups[id];
 else
  return NULL;
}
int MDL0File::PolygonGroupCount() {
 return polyGroupCount;
}
Vec3Group* MDL0File::GetVertexGroup(int id) {
 if (id < vertexGroupCount)
  return &vertexGroups[id];
 else
  return NULL;
}
int MDL0File::VertexGroupCount() {
 return vertexGroupCount;
}
RGBAGroup* MDL0File::GetColorGroup(int id) {
 if (id < colorGroupCount)
  return &colorGroups[id];
 else
  return NULL;
}
int MDL0File::ColorGroupCount() {
 return colorGroupCount;
}
Vec3Group* MDL0File::GetNormalGroup(int id) {
 if (id < normalGroupCount)
  return &normalGroups[id];
 else
  return NULL;
}
int MDL0File::NormalGroupCount() {
 return normalGroupCount;
}
Vec2Group* MDL0File::GetUVGroup(int id) {
 if (id < uvGroupCount)
  return &uvGroups[id];
 else
  return NULL;
}
int MDL0File::UVGroupCount() {
 return uvGroupCount;
}
bool MDL0File::loadGroup(const char* data, int type) {
 uint32_t itemcount = BigUInt32(data, 4);
 switch (type) {
 case 0: // unknown
  break;
 case 1: // probably bones, don't know how to load yet
  break;
 case 2: // vertices
  vertexGroups = new Vec3Group[itemcount];
  vertexGroupCount = itemcount;
  break;
 case 3: // normals
  normalGroups = new Vec3Group[itemcount];
  normalGroupCount = itemcount;
  break;
 case 4: // colors
  colorGroups = new RGBAGroup[itemcount];
  colorGroupCount = itemcount;
  break;
 case 5: // uv coordinates
  uvGroups = new Vec2Group[itemcount];
  uvGroupCount = itemcount;
  break;
 case 6: // unknown
  break;
 case 7: // unknown
  break;
 case 8: // polygon groups
  polyGroups = new PolyGroup[itemcount];
  polyGroupCount = itemcount;
  break;
 case 9: // unknown
  break;
 case 10: // unknown
  break;
 }
 for (uint i = 0; i < itemcount; i++) {
  uint32_t dataoffset = BigUInt32(data, 36 + 16 * i);
  switch (type) {
  case 0: // unknown
   break;
  case 1: // probably bones, don't know how to load yet
   break;
  case 2: // vertices
   if (!loadVec3Group(data + dataoffset, &vertexGroups[i]))
    return false;
   break;
  case 3: // normals
   if (!loadVec3Group(data + dataoffset, &normalGroups[i]))
    return false;
   break;
  case 4: // colors
   if (!loadRGBAGroup(data + dataoffset, &colorGroups[i]))
    return false;
   break;
  case 5: // uv coordinates
   if (!loadVec2Group(data + dataoffset, &uvGroups[i]))
    return false;
   break;
  case 6: // unknown
   break;
  case 7: // unknown
   break;
  case 8: // polygon groups
   if (!loadPolyGroup(data + dataoffset, &polyGroups[i]))
    return false;
   break;
  case 9: // unknown
   break;
  case 10: // unknown
   break;
  }
 }
 return true;
}
bool MDL0File::loadVec3Group(const char* data, Vec3Group* group) {
 bool boundingbox = data[23];
 char datatype = data[27];
 char fixedpointbits = data[28];
 uint16_t datacount = BigUInt16(data, 30);
 int datastart = 32;
 if (boundingbox) {
  float minx = BigFloat(data, 32);
  float miny = BigFloat(data, 36);
  float minz = BigFloat(data, 40);
  float maxx = BigFloat(data, 44);
  float maxy = BigFloat(data, 48);
  float maxz = BigFloat(data, 52);
  group->SetBound(Vec3(minx, miny, minz), Vec3(maxx, maxy, maxz));
  datastart = 64;
 } else {
  group->SetBound(Vec3(0, 0, 0), Vec3(0, 0, 0));
 }
 switch (datatype) {
 case 0: // fixed point u8
  for (int i = 0; i < datacount; i++) {
   float x = (unsigned char) data[datastart + i * 6] / pow(2.0f,
     fixedpointbits);
   float y = (unsigned char) data[datastart + 1 + i * 6] / pow(2.0f,
     fixedpointbits);
   float z = (unsigned char) data[datastart + 2 + i * 6] / pow(2.0f,
     fixedpointbits);
   group->push_back(Vec3(x, y, z));
  }
  break;
 case 1: // fixed point s8
  for (int i = 0; i < datacount; i++) {
   float x = data[datastart + i * 6] / pow(2.0f, fixedpointbits);
   float y = data[datastart + 1 + i * 6] / pow(2.0f, fixedpointbits);
   float z = data[datastart + 2 + i * 6] / pow(2.0f, fixedpointbits);
   group->push_back(Vec3(x, y, z));
  }
  break;
 case 2: // fixed point u16
  for (int i = 0; i < datacount; i++) {
   float x = BigUInt16(data, datastart + i * 6) / pow(2.0f,
     fixedpointbits);
   float y = BigUInt16(data, datastart + 2 + i * 6) / pow(2.0f,
     fixedpointbits);
   float z = BigUInt16(data, datastart + 4 + i * 6) / pow(2.0f,
     fixedpointbits);
   group->push_back(Vec3(x, y, z));
  }
  break;
 case 3: // fixed point s16
  for (int i = 0; i < datacount; i++) {
   float x = BigSInt16(data, datastart + i * 6) / pow(2.0f,
     fixedpointbits);
   float y = BigSInt16(data, datastart + 2 + i * 6) / pow(2.0f,
     fixedpointbits);
   float z = BigSInt16(data, datastart + 4 + i * 6) / pow(2.0f,
     fixedpointbits);
   group->push_back(Vec3(x, y, z));
  }
  break;
 case 4: // floating point
  for (int i = 0; i < datacount; i++) {
   float x = BigFloat(data, datastart + i * 12);
   float y = BigFloat(data, datastart + 4 + i * 12);
   float z = BigFloat(data, datastart + 8 + i * 12);
   group->push_back(Vec3(x, y, z));
  }
  break;
 default:
  return false;
 }
 return true;
}
bool MDL0File::loadVec2Group(const char* data, Vec2Group* group) {
 bool boundingbox = data[23];
 char datatype = data[27];
 char fixedpointbits = data[28];
 uint16_t datacount = BigUInt16(data, 30);
 int datastart = 32;
 if (boundingbox) {
  float minu = BigFloat(data, 32);
  float minv = BigFloat(data, 36);
  float maxu = BigFloat(data, 40);
  float maxv = BigFloat(data, 44);
  group->SetBound(Vec2(minu, minv), Vec2(maxu, maxv));
  datastart = 64;
 } else {
  group->SetBound(Vec2(0, 0), Vec2(0, 0));
 }
 switch (datatype) {
 case 0: // fixed point u8
  for (int i = 0; i < datacount; i++) {
   float u = (unsigned char) data[datastart + i * 6] / pow(2.0f,
     fixedpointbits);
   float v = (unsigned char) data[datastart + 1 + i * 6] / pow(2.0f,
     fixedpointbits);
   group->push_back(Vec2(u, v));
  }
  break;
 case 1: // fixed point s8
  for (int i = 0; i < datacount; i++) {
   float u = data[datastart + i * 6] / pow(2.0f, fixedpointbits);
   float v = data[datastart + 1 + i * 6] / pow(2.0f, fixedpointbits);
   group->push_back(Vec2(u, v));
  }
  break;
 case 2: // fixed point u16
  for (int i = 0; i < datacount; i++) {
   float u = BigUInt16(data, datastart + i * 4) / pow(2.0f,
     fixedpointbits);
   float v = BigUInt16(data, datastart + 2 + i * 4) / pow(2.0f,
     fixedpointbits);
   group->push_back(Vec2(u, v));
  }
 case 3: // fixed point s16
  for (int i = 0; i < datacount; i++) {
   float u = BigSInt16(data, datastart + i * 4) / pow(2.0f,
     fixedpointbits);
   float v = BigSInt16(data, datastart + 2 + i * 4) / pow(2.0f,
     fixedpointbits);
   group->push_back(Vec2(u, v));
  }
  break;
 case 4: // floating point
  for (int i = 0; i < datacount; i++) {
   float u = BigFloat(data, datastart + i * 8);
   float v = BigFloat(data, datastart + 4 + i * 8);
   group->push_back(Vec2(u, v));
  }
  break;
 default:
  return false;
 }
 return true;
}
bool MDL0File::loadRGBAGroup(const char* data, RGBAGroup* group) {
 char datatype = data[27];
 uint16_t datacount = BigUInt16(data, 30);
 int datastart = 32;
 switch (datatype) {
 case 0: // RGB565
  for (int i = 0; i < datacount; i++) {
   char r = ((data[datastart + 2 * i] & 0xF8) >> 3) / 0x1F * 0xFF;
   char g = (((data[datastart + 2 * i] & 0x7) << 3) | ((data[datastart
     + 1 + 2 * i] & 0xE0) >> 5)) / 0x3F * 0xFF;
   char b = (data[datastart + 1 + 2 * i] & 0x1F) / 0x1F * 0xFF;
   group->push_back(RGBA(r, g, b, 0xFF));
  }
  break;
 case 1: // RGB8
  for (int i = 0; i < datacount; i++) {
   char r = data[datastart + 3 * i];
   char g = data[datastart + 1 + 3 * i];
   char b = data[datastart + 2 + 3 * i];
   group->push_back(RGBA(r, g, b, 0xFF));
  }
  break;
 case 2: // RGBX8
  for (int i = 0; i < datacount; i++) {
   char r = data[datastart + 4 * i];
   char g = data[datastart + 1 + 4 * i];
   char b = data[datastart + 2 + 4 * i];
   group->push_back(RGBA(r, g, b, 0xFF));
  }
  break;
 case 3: // RGBA4
  for (int i = 0; i < datacount; i++) {
   char r = (data[datastart + 2 * i] >> 4) * 0x11;
   char g = data[datastart + 2 * i] * 0x11;
   char b = (data[datastart + 1 + 2 * i] >> 4) * 0x11;
   char a = data[datastart + 1 + 2 * i] * 0x11;
   group->push_back(RGBA(r, g, b, a));
  }
  break;
 case 4: // RGBA6
  for (int i = 0; i < datacount; i++) {
   char r = ((data[datastart + 3 * i] & 0x3F) >> 2) / 0x3F * 0xFF;
   char g = (((data[datastart + 3 * i] & 0x3) << 4) | ((data[datastart
     + 1 + 3 * i] & 0xF0) >> 4)) / 0x3F * 0xFF;
   char b = (((data[datastart + 1 + 3 * i] & 0xF) << 4)
     | ((data[datastart + 2 + 3 * i] & 0xC0) >> 6)) / 0x3F
     * 0xFF;
   char a = (data[datastart + 2 + 3 * i] & 0x3F) / 0x3F * 0xFF;
   group->push_back(RGBA(r, g, b, a));
  }
  break;
 case 5: // RGBA8
  for (int i = 0; i < datacount; i++) {
   unsigned char r = data[datastart + 4 * i];
   unsigned char g = data[datastart + 1 + 4 * i];
   unsigned char b = data[datastart + 2 + 4 * i];
   unsigned char a = data[datastart + 3 + 4 * i];
   group->push_back(RGBA(r, g, b, a));
  }
  break;
 default:
  return false;
 }
 return true;
}
bool MDL0File::loadPolyGroup(const char* data, PolyGroup* group) {
 uint16_t vertexid = BigUInt16(data, 72);
 uint16_t normalid = BigUInt16(data, 74);
 uint16_t colorid = BigUInt16(data, 76);
 uint16_t texcoordid[8];
 for (int i = 0; i < 8; i++)
  texcoordid[i] = BigUInt16(data, 80 + 2 * i);
 uint16_t unknowncount = BigUInt16(data, 102);
 // find the start of the array format
 int loc = 104 + unknowncount * 2;
 while (data[loc] == 0) {
  loc++;
 }
 // read array format
 std::vector<int> fmt;
 bool normalfmt = false;
 bool colorfmt = false;
 int unknownfmt = 0;
 int uvfmt = 0;
 int tempfmt = 0;
 if (data[loc] != 0x08 && data[loc + 1] != 0x50)
  return false;
 tempfmt = data[loc + 5] & 3;
 if (tempfmt) {
  unknownfmt = 1;
 }
 tempfmt = (data[loc + 5] & 12) >> 2;
 if (tempfmt) {
  unknownfmt++;
 }
 tempfmt = (data[loc + 5] & 48) >> 4;
 if (tempfmt) {
  unknownfmt++;
 }
 tempfmt = (data[loc + 5] & 192) >> 6;
 if (tempfmt) {
  unknownfmt++;
 }
 tempfmt = (data[loc + 4] & 6) >> 1;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 4] & 24) >> 3;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 4] & 96) >> 5;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 if (data[loc + 6] != 0x08 && data[loc + 7] != 0x60)
  return false;
 tempfmt = data[loc + 11] & 3;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 11] & 12) >> 2;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 11] & 48) >> 4;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 11] & 192) >> 6;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = data[loc + 10] & 3;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 10] & 12) >> 2;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 10] & 48) >> 4;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 tempfmt = (data[loc + 10] & 192) >> 6;
 if (tempfmt)
  fmt.push_back(tempfmt - 1);
 if (data[loc + 16] != 0x08 && data[loc + 17] != 0x00)
  return false;
 colorfmt = data[loc + 20] & 3;
 normalfmt = (data[loc + 20] & 12) >> 2;
 uvfmt = (data[loc + 20] & 240) >> 4;
 // find the start of the data
 loc += 40;
 while (data[loc] == 0) {
  loc++;
 }
 // read data
 bool reading = true;
 do {
  char type = data[loc];
  loc++;
  switch (type) {
  case 0x30:
   loc += 4;
   break;
  case 0x20:
   loc += 4;
   break;
  case 0x28:
   loc += 4;
   break;
  case 0x98: {
   uint16_t count = BigUInt16(data, loc);
   loc += 2;
   if (options & LO_CONV_TO_TRIS) {
    int length;
    for (int i = 0; i < count - 2; i++) {
     Vertex p1, p2, p3;
     if (i % 2) {
      p3 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
        unknownfmt, uvfmt, length);
      loc += length;
      p2 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
        unknownfmt, uvfmt, length);
      loc += length;
      p1 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
        unknownfmt, uvfmt, length);
      loc -= length;
     } else {
      p1 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
        unknownfmt, uvfmt, length);
      loc += length;
      p2 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
        unknownfmt, uvfmt, length);
      loc += length;
      p3 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
        unknownfmt, uvfmt, length);
      loc -= length;
     }
     Triangle tri(p1, p2, p3);
     group->AddTriangle(tri);
    }
    loc += length * 2;
   } else {
    TriangleStrip strip;
    for (int i = 0; i < count; i++) {
     int length;
     strip.Add(loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length));
     loc += length;
    }
    strip.Lock();
    group->AddTriangleStrip(strip);
   }
  }
   break;
  case 0x90: {
   uint16_t count = BigUInt16(data, loc);
   loc += 2;
   for (int i = 0; i < count; i += 3) {
    int length;
    Vertex p1 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
      unknownfmt, uvfmt, length);
    loc += length;
    Vertex p2 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
      unknownfmt, uvfmt, length);
    loc += length;
    Vertex p3 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
      unknownfmt, uvfmt, length);
    loc += length;
    Triangle tri(p1, p2, p3);
    group->AddTriangle(tri);
   }
  }
   break;
  case 0xA0: {
   uint16_t count = BigUInt16(data, loc);
   loc += 2;
   if (options & LO_CONV_TO_TRIS) {
    int length;
    Vertex origin = loadVertex(data, loc, fmt, normalfmt, colorfmt,
      unknownfmt, uvfmt, length);
    loc += length;
    for (int i = 1; i < count - 1; i++) {
     Vertex p1 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Vertex p2 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     Triangle tri(origin, p1, p2);
     group->AddTriangle(tri);
    }
    loc += length;
   } else {
    int length;
    Vertex origin = loadVertex(data, loc, fmt, normalfmt, colorfmt,
      unknownfmt, uvfmt, length);
    loc += length;
    TriangleFan fan(origin);
    for (int i = 1; i < count; i++) {
     fan.Add(loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length));
     loc += length;
    }
    fan.Lock();
    group->AddTriangleFan(fan);
   }
  }
   break;
  case 0x80: {
   uint16_t count = BigUInt16(data, loc);
   loc += 2;
   if (options & LO_CONV_TO_TRIS) {
    for (int i = 0; i < count; i += 4) {
     int length;
     Vertex p1 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Vertex p2 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Vertex p3 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Vertex p4 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Triangle tri1(p1, p2, p3);
     Triangle tri2(p1, p3, p4);
     group->AddTriangle(tri1);
     group->AddTriangle(tri2);
    }
   } else {
    for (int i = 0; i < count; i += 4) {
     int length;
     Vertex p1 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Vertex p2 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Vertex p3 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Vertex p4 = loadVertex(data, loc, fmt, normalfmt, colorfmt,
       unknownfmt, uvfmt, length);
     loc += length;
     Quad quad(p1, p2, p3, p4);
     group->AddQuad(quad);
    }
   }
  }
   break;
  case 0x00:
   reading = false;
   break;
  default:
   return false;
  }
 } while (reading);
 group->SetVertex(vertexid, &vertexGroups[vertexid]);
 if (normalid != 0xFFFF)
  group->SetNormal(normalid, &normalGroups[normalid]);
 if (colorid != 0xFFFF)
  group->SetColor(colorid, &colorGroups[colorid]);
 for (int i = 0; i < 8; i++) {
  if (texcoordid[i] != 0xFFFF) {
   group->AddUV(texcoordid[i], &uvGroups[texcoordid[i]]);
  }
 }
 group->Lock();
 return true;
}
Vertex MDL0File::loadVertex(const char* data, int offset, std::vector<int> fmt,
  bool hasNormal, bool hasColor, int unknown, int uv, int& outLength) {
 uint16_t vert = 0;
 uint16_t norm = 0;
 uint16_t color = 0;
 uint16_t uvs[8];
 int length = 0;
 // assuming all unknowns are one byte long for now.
 length += unknown;
 if (fmt[0] == 1) {
  vert = (unsigned char) data[offset + length];
  length += 1;
 } else {
  vert = BigUInt16(data, offset + length);
  length += 2;
 }
 if (hasNormal) {
  if (fmt[1] == 1) {
   norm = (unsigned char) data[offset + length];
   length += 1;
  } else {
   norm = BigUInt16(data, offset + length);
   length += 2;
  }
 }
 if (hasColor) {
  if (fmt[1 + hasNormal] == 1) {
   color = (unsigned char) data[offset + length];
   length += 1;
  } else {
   color = BigUInt16(data, offset + length);
   length += 2;
  }
 }
 for (int i = 0; i < uv; i++) {
  if (fmt[1 + hasNormal + hasColor + i] == 1) {
   uvs[i] = (unsigned char) data[offset + length];
   length += 1;
  } else {
   uvs[i] = BigUInt16(data, offset + length);
   length += 2;
  }
 }
 Vertex outvert;
 outvert.Set(vert, norm, color, uvs, uv);
 outLength = length;
 return outvert;
}
 

using namespace std;
class COLLADAExport {
public:
 COLLADAExport();
 virtual ~COLLADAExport();
 bool Export(MDL0File* mdl0, const char* destfilename);
private:
 void addMetadata(domCOLLADA* root);
 void addLibraryGeometry(domCOLLADA* root, MDL0File* mdl0);
 void addPositionArray(daeElement* mesh, Vec3Group* group, string geomName);
 void addTexcoordArray(daeElement* mesh, Vec2Group* group, string geomName);
 void addTriangleStrips(daeElement* mesh, PolyGroup* group, string name);
 void addTriangleFans(daeElement* mesh, PolyGroup* group, string name);
 void addTriangles(daeElement* mesh, PolyGroup* group, string name);
 void addQuads(daeElement* mesh, PolyGroup* group, string name);
 void addInputs(daeElement* parent, PolyGroup* group, string name);
 string intToString(int num);

};
 
 
COLLADAExport::COLLADAExport() {
 // TODO Auto-generated constructor stub
}
COLLADAExport::~COLLADAExport() {
 // TODO Auto-generated destructor stub
}
bool COLLADAExport::Export(MDL0File* mdl0, const char* destfilename) {
 DAE dae;
 domCOLLADA* root = dae.add(destfilename);
 if (root == NULL)
  return false;
 addMetadata(root);
 addLibraryGeometry(root, mdl0);
 dae.writeAll();
 return true;
}
void COLLADAExport::addMetadata(domCOLLADA* root) {
 daeElement* asset = root->add("asset");
 // add contributer
 daeElement* contributor = asset->add("contributor");
 //daeElement* author = asset->add("author");
 daeElement* authoring_tool = contributor->add("authoring_tool");
 authoring_tool->setCharData("MDL0 to COLLADA converter");
 daeElement* comments = contributor->add("comments");
 comments->setCharData(
   "This file was created by the mdl0converter, written by William Hahne. The copyright of this file belongs to the creator of the original input file.");
 // get the current time and then format it correctly for XML
 ostringstream strm;
 time_t t = time(NULL);
 struct tm* lt = localtime(&t);
 strm << lt->tm_year + 1900 << "-" << lt->tm_mon << "-" << lt->tm_mday
   << "T" << lt->tm_hour << ":" << lt->tm_min << ":" << lt->tm_sec
   << "Z";
 string current_time = strm.str();
 // set created and modified time
 daeElement* created = asset->add("created");
 created->setCharData(current_time);
 daeElement* modified = asset->add("modified");
 modified->setCharData(current_time);
}
void COLLADAExport::addLibraryGeometry(domCOLLADA* root, MDL0File* mdl0) {
 daeElement* library_geometries = root->add("library_geometries");
 for (int i = 0; i < mdl0->PolygonGroupCount(); i++) {
  PolyGroup* group = mdl0->GetPolygonGroup(i);
  daeElement* geometry = library_geometries->add("geometry");
  ostringstream strm;
  strm << "Polygon" << i;
  string name = strm.str();
  geometry->setAttribute("id", name.c_str());
  daeElement* mesh = geometry->add("mesh");
  addPositionArray(mesh, group->VertexGroupPtr(), name + "-position");
  addPositionArray(mesh, group->NormalGroupPtr(), name + "-normal");
  for (int i = 0; i < group->UVGroupCount(); i++)
   addTexcoordArray(mesh, group->UVGroupPtr(i), name + "-texcoord"
     + intToString(i));
  daeElement* vertices = mesh->add("vertices");
  string vertName(name + "-vertex");
  vertices->setAttribute("id", vertName.c_str());
  daeElement* input = vertices->add("input");
  input->setAttribute("semantic", "POSITION");
  string sourceName("#" + name + "-position");
  input->setAttribute("source", sourceName.c_str());
  if (group->TriangleStripCount() > 0)
   addTriangleStrips(mesh, group, name);
  if (group->TriangleFanCount() > 0)
   addTriangleFans(mesh, group, name);
  if (group->TriangleCount() > 0)
   addTriangles(mesh, group, name);
 }
 // temporary until I (hopefully) find a better solution
 daeElement* visual_scene = root->add("library_visual_scenes visual_scene");
 visual_scene->setAttribute("id", "testscene");
 for (int i = 0; i < mdl0->PolygonGroupCount(); i++) {
  daeElement* instance_geometry = visual_scene->add(
    "node instance_geometry");
  string name("#Polygon" + intToString(i));
  instance_geometry->setAttribute("url", name.c_str());
 }
 daeElement* instance_visual_scene =
   root->add("scene instance_visual_scene");
 instance_visual_scene->setAttribute("url", "#testscene");
}
void COLLADAExport::addPositionArray(daeElement* mesh, Vec3Group* group,
  string posName) {
 daeElement* source = mesh->add("source");
 source->setAttribute("id", posName.c_str());
 daeElement* float_array = source->add("float_array");
 string arrayPosName(posName + "-array");
 float_array->setAttribute("id", arrayPosName.c_str());
 float_array->setAttribute("count", intToString(group->size() * 3).c_str());
 ostringstream strm;
 for (uint i = 0; i < group->size(); i++) {
  Vec3 vec = (*group)[i];
  strm << -vec.X() << " " << vec.Y() << " " << vec.Z() << "\n";
 }
 float_array->setCharData(strm.str());
 daeElement* accessor = source->add("technique_common accessor");
 string arrayPosSourceName("#" + arrayPosName);
 accessor->setAttribute("source", arrayPosSourceName.c_str());
 accessor->setAttribute("count", intToString(group->size()).c_str());
 accessor->setAttribute("stride", "3");
 daeElement* paramX = accessor->add("param");
 paramX->setAttribute("name", "X");
 paramX->setAttribute("type", "float");
 daeElement* paramY = accessor->add("param");
 paramY->setAttribute("name", "Y");
 paramY->setAttribute("type", "float");
 daeElement* paramZ = accessor->add("param");
 paramZ->setAttribute("name", "Z");
 paramZ->setAttribute("type", "float");
}
void COLLADAExport::addTexcoordArray(daeElement* mesh, Vec2Group* group,
  string posName) {
 daeElement* source = mesh->add("source");
 source->setAttribute("id", posName.c_str());
 daeElement* float_array = source->add("float_array");
 string arrayPosName(posName + "-array");
 float_array->setAttribute("id", arrayPosName.c_str());
 float_array->setAttribute("count", intToString(group->size() * 2).c_str());
 ostringstream strm;
 for (uint i = 0; i < group->size(); i++) {
  Vec2 vec = (*group)[i];
  strm << vec.U() << " " << vec.V() << " ";
 }
 float_array->setCharData(strm.str());
 daeElement* accessor = source->add("technique_common accessor");
 string arrayPosSourceName("#" + arrayPosName);
 accessor->setAttribute("source", arrayPosSourceName.c_str());
 accessor->setAttribute("count", intToString(group->size()).c_str());
 accessor->setAttribute("stride", "2");
 daeElement* paramU = accessor->add("param");
 paramU->setAttribute("name", "U");
 paramU->setAttribute("type", "float");
 daeElement* paramV = accessor->add("param");
 paramV->setAttribute("name", "V");
 paramV->setAttribute("type", "float");
}
void COLLADAExport::addTriangleStrips(daeElement* mesh, PolyGroup* group,
  string name) {
 daeElement* tristrips = mesh->add("tristrips");
 tristrips->setAttribute("count",
   intToString(group->TriangleStripCount()).c_str());
 addInputs(tristrips, group, name);
 for (uint i = 0; i < group->TriangleStripCount(); i++) {
  daeElement* p = tristrips->add("p");
  TriangleStrip strip = group->GetTriangleStrip(i);
  ostringstream strm;
  for (uint j = 0; j < strip.Count(); j++) {
   Vertex vert = strip[j];
   if (vert.GetVertex() >= group->VertexGroupPtr()->size())
    std::cout << "WARNING: Tristrip vertex out of range in "
      << name << endl;
   if (vert.GetNormal() >= group->NormalGroupPtr()->size())
    std::cout << "WARNING: Tristrip normal out of range in "
      << name << endl;
   strm << vert.GetVertex() << " " << vert.GetNormal() << "  ";
   for (int k = 0; k < vert.UVCount(); k++) {
    strm << vert.GetUV(k) << " ";
   }
  }
  p->setCharData(strm.str());
 }
}
void COLLADAExport::addTriangleFans(daeElement* mesh, PolyGroup* group,
  string name) {
 daeElement* trifans = mesh->add("trifans");
 trifans->setAttribute("count",
   intToString(group->TriangleFanCount()).c_str());
 addInputs(trifans, group, name);
 for (uint i = 0; i < group->TriangleFanCount(); i++) {
  daeElement* p = trifans->add("p");
  TriangleFan fan = group->GetTriangleFan(i);
  ostringstream strm;
  Vertex origin = fan.GetOriginVertex();
  strm << origin.GetVertex() << " " << origin.GetNormal() << "  ";
  for (uint j = 0; j < fan.Count(); j++) {
   Vertex vert = fan[j];
   if (vert.GetVertex() >= group->VertexGroupPtr()->size())
    std::cout << "WARNING: Triangle Fan vertex out of range in "
      << name << endl;
   if (vert.GetNormal() >= group->NormalGroupPtr()->size())
    std::cout << "WARNING: Triangle Fan normal out of range in "
      << name << endl;
   strm << vert.GetVertex() << " " << vert.GetNormal() << " ";
   for (int k = 0; k < vert.UVCount(); k++) {
    strm << vert.GetUV(k) << " ";
   }
  }
  p->setCharData(strm.str());
 }
}
void COLLADAExport::addTriangles(daeElement* mesh, PolyGroup* group,
  string name) {
 daeElement* tris = mesh->add("triangles");
 tris->setAttribute("count", intToString(group->TriangleCount()).c_str());
 addInputs(tris, group, name);
 daeElement* p = tris->add("p");
 ostringstream strm;
 for (uint i = 0; i < group->TriangleCount(); i++) {
  Triangle tri = group->GetTriangle(i);
  for (int j = 0; j < 3; j++) {
   Vertex vert = tri[j];
   if (vert.GetVertex() >= group->VertexGroupPtr()->size())
    std::cout << "TRIANGLE WARNING: Vertex out of range in "
      << name << endl;
   if (vert.GetNormal() >= group->NormalGroupPtr()->size())
    std::cout << "TRIANGLE WARNING: Normal out of range in "
      << name << endl;
   strm << vert.GetVertex() << " " << vert.GetNormal() << " ";
   for (int k = 0; k < vert.UVCount(); k++) {
    strm << vert.GetUV(k) << " ";
   }
  }
  strm << "\n";
 }
 p->setCharData(strm.str());
}
void COLLADAExport::addQuads(daeElement* mesh, PolyGroup* group, string name) {
}
void COLLADAExport::addInputs(daeElement* parent, PolyGroup* group, string name) {
 daeElement* inputVert = parent->add("input");
 inputVert->setAttribute("semantic", "VERTEX");
 string vertSourceName("#" + name + "-vertex");
 inputVert->setAttribute("source", vertSourceName.c_str());
 inputVert->setAttribute("offset", "0");
 daeElement* inputNorm = parent->add("input");
 inputNorm->setAttribute("semantic", "NORMAL");
 string normSourceName("#" + name + "-normal");
 inputNorm->setAttribute("source", normSourceName.c_str());
 inputNorm->setAttribute("offset", "1");
 for (int i = 0; i < group->UVGroupCount(); i++) {
  daeElement* inputTex = parent->add("input");
  inputTex->setAttribute("semantic", "TEXCOORD");
  string texSourceName("#" + name + "-texcoord" + intToString(i));
  inputTex->setAttribute("source", texSourceName.c_str());
  inputTex->setAttribute("offset", intToString(2 + i).c_str());
  inputTex->setAttribute("set", "0");
 }
}
string COLLADAExport::intToString(int num) {
 ostringstream strm;
 strm << num;
 return strm.str();
}
 

using namespace std;
void printUsage() {
 cout << "Usage:" << endl;
 cout << "mdl0converter [-t] input_file output_file" << endl << endl;
 cout << "Options:" << endl;
 cout << "   -t    Convert all triangle strips, triangle fans, and quads to triangles." << endl << endl;
 cout << "Copyright (C) 2008 William Hahne" << endl;
}
void printCredits() {
 // todo: add credits
}
int main(int argc, char *argv[]) {
 char* inputfile = NULL;
 char* outputfile = NULL;
 uint opts = 0;
 for (int i = 1; i < argc; i++) {
  if (argv[i][0] == '-') {
   switch (argv[i][1]) {
   case 't':
    opts |= LO_CONV_TO_TRIS;
    break;
   default:
    cout << "Invalid option: " << argv[i] << endl;
    printUsage();
    return 0;
   }
  } else {
   if (inputfile == NULL) {
    inputfile = argv[i];
   }
   else if (inputfile != NULL && outputfile == NULL) {
    outputfile = argv[i];
   }
  }
 }
 if (inputfile == NULL || outputfile == NULL) {
  cout << "Not enough arguments." << endl;
  printUsage();
  return 0;
 }
 ifstream f;
 f.open(inputfile, ios::binary | ios::in);
 if (!f.good() || !f.is_open()) {
  cout << "Error opening file." << endl << endl;
  printUsage();
  return 0;
 }
 f.seekg(0, ios::beg);
 int begin = f.tellg();
 f.seekg(0, ios::end);
 int size = (int) f.tellg() - begin;
 f.seekg(0, ios::beg);
 char* data = new char[size];
 f.read(data, size);
 MDL0File file;
 file.Load(data, size, opts);
 // convert to collada
 COLLADAExport daeExport;
 outputfile = inputfile + ".DAE"
 if (!daeExport.Export(&file, outputfile)) {
  cout << "Error writing output file." << endl << endl;
  printUsage();
  return 0;
 }
 cout << "Success" << endl;
 return 0;
}
I expact there to be tons of errors...
I know nothing of C/C++
I used my knowledge of py and js to make that possible...

it's the code for Will's converter...
you guys already know how that works...
I just put it into a single file, and I want to see if it works...
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
I'm still having trouble and getting no help with it...

I'm not gonna let these threads die now...
comon...

how do I read a vertex??
is it by 2 (such as 'FE 08')
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
well...
daniweb is helping me out :D

here's a few progress shots:


after editing:


anyone here think they can help??
 
Last edited:

Oshtoby

Smash Apprentice
Joined
Sep 6, 2006
Messages
181
Location
Burholme, Philadelphia, PA, USA, North America, Ea
I really wish I could help you, man. This looks amazing. I hope you don't fall under the curse, though. Everyone who tries an MDL0 importer/exporter only gets so far before quitting the scene entirely. It's strange, but I'm hoping you'll be the one to overcome it.
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
I really wish I could help you, man. This looks amazing. I hope you don't fall under the curse, though. Everyone who tries an MDL0 importer/exporter only gets so far before quitting the scene entirely. It's strange, but I'm hoping you'll be the one to overcome it.
dude I sooooo intend to complete this :D
I need it for a movie I'm working on...
(this includes melee as well)

I won't quit this at all... you can count on that :)
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
can anyone explaing to me any info in the header of the vert data??

*can't restore untitled.jpg*
IDK if the grey area is correct...
grey = header
blue = vert data
red = extra data (to fill the datasize specified in the header)

thanx :)

EDIT:
first block is the file size
 
Last edited:

Matando

Smash Rookie
Joined
Jul 14, 2009
Messages
1
Through Comparison with BrawlBox...

Blocks 52-53: Number of Vertices(Hex)
Blocks 56-57: Number of Faces(Hex)
Blocks 5E-5F: Number of Nodes(Hex)
Blocks 60-61: Version Number?(Hex)

I'd Recommend looking at brawl box a lot, it could help greatly. If you can't figure out the c# code from brawl lib and brawlbox, just opening the mdl0 file in brawlbox and comparing data in a hex editor will help.
 

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
0x00: Block size
0x04: MDL0 Offset
0x08: Data Offset
0x0C: String Offset
0x10: Index
0x14: Bounding Box (1 == true)
0x1A: Data type (4 == float)
0x1C: Modifier (always 0 for floats)
0x1D: Entry size
0x1E: Number of entries
0x20: Bounding box data
0x40: Position data

I won't quit this at all... you can count on that :)
I remember when I said this...


EDIT: Oh, and one other thing. I don't know much about the program you're using, but it looks like you're trying to look at the MDL0 block at the block level (one block in a file). I cannot stress how BAD this method is. In order to get the full picture you must work at a brres level.

The MDL0 and all its blocks are capable of referencing data outside of the MDL0 scope (such as block titles). Also, the blocks are almost always referencing one another in some way (materials reference joints, materials reference shaders and image references, joints reference weights, and polylists reference just about everything).

I would definitely say the best way to work is to have the entire file open and displayed with numerous bookmarks at all the block entry points. This allows you to skip around the file with ease and see what cross block references are being made. Again, I don't know if you're already doing this, and just posted the pic that way for display sake, but it's so important that I couldn't let it go without addressing it.
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
0x00: Block size
0x04: MDL0 Offset
0x08: Data Offset
0x0C: String Offset
0x10: Index
0x14: Bounding Box (1 == true)
0x1A: Data type (4 == float)
0x1C: Modifier (always 0 for floats)
0x1D: Entry size
0x1E: Number of entries
0x20: Bounding box data
0x40: Position data
thanx :)

0x00: Block size
I remember when I said this....
lol :D ^^

you should've kept going :D
you do have a good converter :)
but use the code from Will's converter to get a better model...
(instead of just random triangles (you know what I mean :p))

collada is an OK format...
you can still pretty much get anything to do with the model imported...

EDIT: Oh, and one other thing. I don't know much about the program you're using, but it looks like you're trying to look at the MDL0 block at the block level (one block in a file). I cannot stress how BAD this method is. In order to get the full picture you must work at a brres level.

The MDL0 and all its blocks are capable of referencing data outside of the MDL0 scope (such as block titles). Also, the blocks are almost always referencing one another in some way (materials reference joints, materials reference shaders and image references, joints reference weights, and polylists reference just about everything).

I would definitely say the best way to work is to have the entire file open and displayed with numerous bookmarks at all the block entry points. This allows you to skip around the file with ease and see what cross block references are being made. Again, I don't know if you're already doing this, and just posted the pic that way for display sake, but it's so important that I couldn't let it go without addressing it.
this is only my first importer pharrox...
thanx though...
I'm only working at mdl0 level right now... (the finished importer will be able to import pac/pcs)
I can reference the textures outside of blender... (the pac version will import textures into blender as well)

but right now I'm really just beginning to get to know the mdl0...
I know how materials and shaders work (not in mdl0) as I'm a game developer...
(still a few things I'm learing though)

I learnd some if the format from building a brute forcing (melee dat) to obj converter
(I still am working on a better program (I can't program a GUI for crap))

even the vert importer sucks...
it skips the header entirely and just converts the data...
until it reaches a ' ' or a null... (this is why I'm asking about the header)

also:
do you know anything on the CHR0 files??
I plan to build an importer for those as well :)
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
anyone wanna check these notes for me??

Code:
mdl0 header:
0x00(4)  MDL0 Identifier
0x04(4)  MDL0 Size
0x08(4)  Sections
0x0C(4)  Model Nodes Offset
0x10(4)  Definitions List
0x14(4)  Bones List
0x18(4)  Vertices List
0x1C(4)  Normals List
0x20(4)  Colors List
0x24(4)  UV Points List
0x28(4)  Materials 1 List
0x2C(4)  Materials 2 List
0x30(4)  Polygons List
0x34(4)  Textures 1 List
0x38(4)  Textures 2 List
0x3C(4)  Model Name Offset
0x40(4)  Header Length
0x44(4)  Header Offset
0x48(4)  Unknown 1
0x4C(4)  Unknown 2
0x50(4)  # of Vertices
0x54(4)  # of Faces
0x58(4)  Unknown 3
0x5C(4)  # of Nodes
0x60(4)  Version
0x62(2)  Unknown 4
0x64(2)  Unknown 5
0x68(4)  Box Min. X
0x6C(4)  Box Min. Y
0x70(4)  Box Min. Z
0x74(4)  Box Max. X
0x78(4)  Box Max. Y
0x7C(4)  Box Max. Z
0x80(4)  # of Nodes (Copy?)
 
vertex data header:
0x00(4): Block size (including header)
0x04(4): MDL0 Offset
0x08(4): Data Offset (always 64)
0x0C(4): String Offset
0x10(4): Index
0x14(4): Bounding Box (1 == true)
0x1A(4): Data type (4 == float)
0x1C(1): Modifier (always 0 for floats)
0x1D(1): Entry size
0x1E(2): Number of entries
0x20(32): Bounding box data
0x40: vertex data
 
Bone datah eader:
0x00 - Header Length (Usually 00D0)
0x04 - MDL0 Offset
0x08 - String Offset
0x0C - Bone Index
0x10 - Node ID
0x14 - Flags
0x18 - Pad 1
0x1C - Pad 2
0x20 - Scale X
0x24 - Scale Y
0x28 - Scale Z
0x2C - Rotation X
0x30 - Rotation Y
0x34 - Rotation Z
0x38 - Translation X
0x3C - Translation Y
0x40 - Translation Z
0x44 - Box Min. X
0x48 - Box Min. Y
0x4C - Box Min. Z
0x50 - Box Max. X
0x54 - Box Max. Y
0x58 - Box Max. Z
0x5C - Parent Offset
0x60 - First Child Offset
0x64 - Next Offset
0x68 - Previous Offset
0x6C - Bone Strings/Properties Offset
0x70 - FrameMatrix Set 1 Float 1
0x74 - FrameMatrix Set 2 Float 1
0x78 - FrameMatrix Set 3 Float 1
0x7C - FrameMatrix Set 4 Float 1
0x80 - FrameMatrix Set 1 Float 2
0x84 - FrameMatrix Set 2 Float 2
0x88 - FrameMatrix Set 3 Float 2
0x8C - FrameMatrix Set 4 Float 2
0x90 - FrameMatrix Set 1 Float 3
0x94 - FrameMatrix Set 2 Float 3
0x98 - FrameMatrix Set 3 Float 3
0x9C - FrameMatrix Set 4 Float 3
0xA0 - InverseBindMatrix Set 1 Float 1
0xA4 - InverseBindMatrix Set 2 Float 1
0xA8 - InverseBindMatrix Set 3 Float 1
0xAC - InverseBindMatrix Set 4 Float 1
0xB0 - InverseBindMatrix Set 1 Float 2
0xB4 - InverseBindMatrix Set 2 Float 2
0xB8 - InverseBindMatrix Set 3 Float 2
0xBC - InverseBindMatrix Set 4 Float 2
0xC0 - InverseBindMatrix Set 1 Float 3
0xC4 - InverseBindMatrix Set 2 Float 3
0xC8 - InverseBindMatrix Set 3 Float 3
0xCC - InverseBindMatrix Set 4 Float 3
flags:
0x0001 - NoTransform
0x0002 - FixedTranslation
0x0004 - FixedRotation
0x0008 - FixedScale
0x0010 - Unk1
0x0100 - Unk2
0x0200 - HasGeometry
 
other useful data: these notes are kind of out of date.
All data is big endian.
BRES header:
 u32 magic = 'bres'
 u32 unknown
 u32 filesize
 u32 unknown
 u32 other_magic = 'root'
 u32 root directory length
BRES root directory @0x18:
 u32 unknown 
 u32 ndirents
 16 bytes unknown = ff ff 00 00 00 01 00 00
 ndirents * struct {
  u16 unknown[2]
  u16 unknown[2]
  u32 offset to dirname from start of directory
   dirname is a null terminated string
  u32 offset to data for entry from start of dir
 
 
 }
 
MDL0 header:
 u32 magic = 'MDL0'
 u32 filesize
 u32 (one less than offset index count?)
 u32 unknown
 u32 offsets to data block index blocks[]
data block index block { (0x260)
 u32 index block length
 u32 entry count (10 @ 0x264)
 u32 unknown[2]
 u32 zero[2]
 0x278 - 0x378: array {
  u16 model_id -- maps position blocks to normal blocks?
  u16 zero
  u16 unknown
  u16 unknown
  uint32 unknown (like 0x001a2f34):
   model id? when added to start offset of this data block -- consistent between positions and normals...
  uint32 offset to vertex position data block from start of this data block
 } [10]
}
another data block index at 0x118, unknown0 = 0x0148, count=13
NodeMix block {
 stream:
 u8 nexttype {
  00: done
  02: mapping data, u16 -> u16
  03: weighting data
 }
 nexttype == 02: {
  u16 unknown1
  u16 unknown2
 }
 nexttype == 03: {
  u16 (some id?)
  u8 number of weights
  struct {
   u16 transform_id
   float weight
  }[] weight_set
 }
}
vertex position data block {
 0x40 byte header {
  uint32_t length of this block
  int32_t offset to beginning of file (always negative)
  ...
  uint32_t at 0x10:
   index # of block (starts at 0, should be no gaps)
  uint32_t at 0x14: {
   0x01 = positions, has bounding box + zero pad
   0x00 = normals, no bounding box data + zero pad
  }
  ...
  uint16_t at 0x1c: bytes per vertex (0x04 * #components) {
   0x08 = UVs
   0x0c = vertex and normal data
  }
  uint16_t at 0x1e = number of vertices to follow
  0x20 {
  float[6] (bounding box?)
  uint32_t[2] zero
  } (not present for normals)
 } 
 component data follows: #vertices * #components float32
}
 
vertex array header: {
 0x140 bytes long, almost completely unknown
 u8 @ 0x0e {
  0x14 or 0x54 for u8 datas
  0x5e for u16 datas
 }
 u16 @ 0x1e (vertex count for a position data block) is:
  0x80 for a 4 or 7 byte vertex struct layout
  0xc0 for an 8 byte vertex struct layout
 u16 @ 0x94 = byte length of 3rd component in the struct
  only if @0x1e != 0xc0 and @0x0e != 0x14
}
vertex array data: {
 u8 list type to follow {
  0x00 == stop processing
  0xB8 == points
  0xA8 == lines
  0xB0 == line strip
  0x90 == triangles
  0x98 == triangle strip
  0xA0 == triangle fan
  0x80 == quads
 }
 u16 = #verts to follow
 if (@0x1e in header == 0xc0) {
  u8 position idx
  u8 unknown1
  u16 unknown2
  u8[4] more unknowns
 } else {
  component lengths defined in header (@0x0e, @0x94):
  position idx
  (uv or normal idx)
  (uv or normal idx)
 }
}
texture block: {
 u32 entry count
 entry (8 bytes) {
  u32 unknown
  u32 rel_offset to rel_offset to texture name (useless...)
 }
}
It'd be much appreciated :)
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
update:

0x00(4): Block size (including header)
0x04(4): MDL0 Offset
0x08(4): Data Offset (always 64)
0x0C(4): String Offset
0x10(4): Index
0x14(4): Bounding Box (1 == true)
0x1A(4): Data type (4 == float)
0x1C(1): Modifier (always 0 for floats)
0x1D(1): Entry size
0x1E(2): Number of entries
0x20(32): Bounding box data
0x40(~): vetex data
 
Last edited:

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
I know I'm repeating myself, but I just have to say this one more time, Take what you will from this post.

This may be your first importer, but that isn't reason to rush in without understanding a few key things. Firstly, any program targeting any data below brres level will need to be completely rewritten to consider the brres its entry point eventually.

The brres is not just a container file, it is the graphics file, and the structure and values of its child nodes can vary based on the way it's configured (ex: having a model's textures stored in the same brres with the model will impact the way the MDL0 references those textures).

It is possible to work at a purely MDL0 level, but it will be impossible to add truly new content because the resulting MDL0 will need some kind of reference to know how to address such issues. Working below the brres level will require a source to be used, which will result in only being able to replace content, and only if the new content meets the restrictions imposed by the source. I will admit that this is still theory on my part, but I have found a decent number of variables which seem to point to this conclusion.

Secondly, if you target Collada as your interchange format, you will be forced to find workarounds to it's numerous shortcomings. The biggest, most glaring flaw being its lack of support for multitexture. If I had to venture a guess I would say that at least a quarter of the (important) models in game support this. There is also the matter of no texture animation (PAT0) or support for half the Wii's shader functions.

As such you will either be forced to handle them in program, including a grapchis pipeline emulator for best results (a pain for you to code (trust me), and anyone doing the modeling to have to switch back and forth) or use your own format to fill the holes which will of course lead to compatibility problems.

I'm not meaning to sound harsh or superior, but I have spent so much time and effort paying for these mistakes, I would hate to see another developer go though the same thing because they had the same mindset I had. Trust me, a little extra work and planning now, will save you so much time and frustration in the future.



As for CHR0, I used to have them pretty well figured out, but I haven't messed with 'em in a while. Though, IIRC, they're pretty easy to figure out, as are most of Brawl's animation files.
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
I know I'm repeating myself, but I just have to say this one more time, Take what you will from this post.
sry bout that... :urg:

This may be your first importer, but that isn't reason to rush in without understanding a few key things. Firstly, any program targeting any data below brres level will need to be completely rewritten to consider the brres its entry point eventually.

The brres is not just a container file, it is the graphics file, and the structure and values of its child nodes can vary based on the way it's configured (ex: having a model's textures stored in the same brres with the model will impact the way the MDL0 references those textures).
KK I get what yur saying...
and yes, I do consider that... but... I don't know enough to go that far yet...
I'll get to there after first gaining everything I need to know about the mdl0 file (smallest portion)
words of wisdom: (working your way up is what makes you successful)

EDIT:
actually pharrox...
it will work...
blender can reference internal or external textures...
the mdl0 setting will depend on if the textures are an internal, or external reference
(when exporting back to mdl0)

It is possible to work at a purely MDL0 level, but it will be impossible to add truly new content because the resulting MDL0 will need some kind of reference to know how to address such issues. Working below the brres level will require a source to be used, which will result in only being able to replace content, and only if the new content meets the restrictions imposed by the source. I will admit that this is still theory on my part, but I have found a decent number of variables which seem to point to this conclusion.
same here...

Secondly, if you target Collada as your interchange format, you will be forced to find workarounds to it's numerous shortcomings. The biggest, most glaring flaw being its lack of support for multitexture. If I had to venture a guess I would say that at least a quarter of the (important) models in game support this. There is also the matter of no texture animation (PAT0) or support for half the Wii's shader functions.
I see...

As such you will either be forced to handle them in program, including a grapchis pipeline emulator for best results (a pain for you to code (trust me), and anyone doing the modeling to have to switch back and forth) or use your own format to fill the holes which will of course lead to compatibility problems.
blender's .blend format would fit perfectly for the .pac/pcs format you know :D

I'm not meaning to sound harsh or superior, but I have spent so much time and effort paying for these mistakes, I would hate to see another developer go though the same thing because they had the same mindset I had. Trust me, a little extra work and planning now, will save you so much time and frustration in the future.
I see...
and thanx...
knowledge is a very useful tool... (once you obtain it) :laugh:

As for CHR0, I used to have them pretty well figured out, but I haven't messed with 'em in a while. Though, IIRC, they're pretty easy to figure out, as are most of Brawl's animation files.
I see...
well I've tried doing manual animations in blender using the 'eular to quat' method...
that didn't work...
so yea... I'm kinda stuck there...
IDK how to do that correctly as blender doesn't use eular coords for it's Action's (brawl = CHR0 as blender = Action)
do you have a clues that could help me??
thanx :)
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
hey pharrox...

do you know what the bytes are from '84' to '~~' in the mdl0 header??
(just before the cluster of 'FF FF')
I don't have notes on those

also...
what is the modifier, entry size, and number of entries??
what do those do exactly??
(just making sure I don't have the wrong idea of it...)
 

AMKalmar

Smash Ace
Joined
Mar 10, 2009
Messages
887
Location
Hamilton ON CA
The entry size is how many bytes each entry is in a list. For vertices, if the data type is 3 (float), the entry size will be C. 3 floating points for x, y, and z, each being 4 bytes long.

The number of entries is just that.
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
The entry size is how many bytes each entry is in a list. For vertices, if the data type is 3 (float), the entry size will be C. 3 floating points for x, y, and z, each being 4 bytes long.

The number of entries is just that.
KK I get it
and I've renamed a few verts to match...

but the datatypes go as this:
def vert():
if data_type==0 :return ord(mdl0.read(1)) #fixed point u8
if data_type==1 :return thebyte(mdl0.read(1)) #fixed point s8
if data_type==2 :return utwobyte(mdl0.read(2)) #fixed point u16
if data_type==3 :return stwobyte(mdl0.read(2)) #fixed point s16
if data_type==4 :return fourbyte(mdl0.read(4)) #floating point
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
Pretty sure that's just there so that the entries start on a new line.
ah ok... so to make sure the header is exactly 64... or something like that...
so what does the modifier(Divisor) do exactly??

well...
I'm officially finished with the verts...
now I need/want to do the uv's

they are worked just like verts...
but read by 16bit instead of 32bit

I can't seem to get the right number (scale wise)... they're fine position wise...
(using the 3D editor to import them)
z=0.0
x and y = vector(datatype2)
return U_2byte(mdl0.read(2))

where datatype2 is what's listed in my last informal post...
'U_2byte' stands for unsigned 16 bit float
 

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
While I never use it, I would assume blender would work fine. Pretty much any program specific format should work since it needs a way to save everything the program can do.

0x80 is the number of weights in the model. 0x84+ are the initial weight declarations. Rather than try to explain (cause I suck at that), I'll give an example.

If a model has 5 joints, and 9 weights, and joints 0 and 3 do not affect geometry the block would look like this:

Code:
00000001 00000002 00000004 FFFFFFFF
FFFFFFFF FFFFFFFF FFFFFFFF 00000000
00000003 Tablestart
So the weights with one joint affecting geometry come first, the weights with multiple influences (all as 0xFFFFFFFF), and finally the weights for joints with no geometry.

The indexes in the Nodemix block will match up to the weights position here, and the 0x10 block in a joint will be the index of its weight as it appears here (So the value of 0x10 in joint 0 above would be 0x00000007).

The modifier I'll have to get back to you on, it's so rare that I've never actively worked with a file that used it, so I don't entirely remember how it works. It's something along the lines of all the values in the data are multiplied by this value to get their true value.

And yeah, the padding is just their for consistency. The uv blocks which are exactly like the position blocks have a bounding box as well, but since it's only x and y, the box is only 0x10 byte long. Regardless, there is always another 0x10 byte line of 0's to bring the header up to 0x40 (rather than the 8 in positions). If no bounding box is used, the header becomes 0x20 and does not pad up to 0x40.
 

Tcll

Smash Lord
Joined
Jul 10, 2010
Messages
1,780
Location
The Gates of Darkness
NNID
Tcll5850
While I never use it, I would assume blender would work fine. Pretty much any program specific format should work since it needs a way to save everything the program can do.
so... 3DS works too I assume

0x80 is the number of weights in the model. 0x84+ are the initial weight declarations. Rather than try to explain (cause I suck at that), I'll give an example.

If a model has 5 joints, and 9 weights, and joints 0 and 3 do not affect geometry the block would look like this:

Code:
00000001 00000002 00000004 FFFFFFFF
FFFFFFFF FFFFFFFF FFFFFFFF 00000000
00000003 Tablestart
So the weights with one joint affecting geometry come first, the weights with multiple influences (all as 0xFFFFFFFF), and finally the weights for joints with no geometry.

The indexes in the Nodemix block will match up to the weights position here, and the 0x10 block in a joint will be the index of its weight as it appears here (So the value of 0x10 in joint 0 above would be 0x00000007).
umm... I'm lost :p
it's ok :)
I'll eventually get it if I keep looking at it...

The modifier I'll have to get back to you on, it's so rare that I've never actively worked with a file that used it, so I don't entirely remember how it works. It's something along the lines of all the values in the data are multiplied by this value to get their true value..
so the modifier is just multiply every xy/xyz value by the mod number??

And yeah, the padding is just their for consistency. The uv blocks which are exactly like the position blocks have a bounding box as well, but since it's only x and y, the box is only 0x10 byte long. Regardless, there is always another 0x10 byte line of 0's to bring the header up to 0x40 (rather than the 8 in positions). If no bounding box is used, the header becomes 0x20 and does not pad up to 0x40.
that part was pretty easy to figure out :p
I was asking because of the modifier...

the verts also do that...
(you prbly already know that)

in other news:
I'm already writing a vert export script...

ADD:
@Pharrox

for the bounding box...

are you sure of the order of the data being (xyzXYZ), or could it possibly be (xXyYzZ)??
(CAPS ar the max data)
 

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
Can't say I'm positive, but I believe it's xyzXYZ. Either way, it doesn't really seem to matter much. Changing the data there has no noticeable effects in game.
 
Top Bottom