• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

Question about the MDL file format

Luis_Sá

Resource contributor
Messages
326
Country
portugal
Hello,

First I would like to thank all that contributed to the Wiki in this site and in particular to the section on MDL file formats.

I writing code to take a scenery MDL file and read the type (FSX or FS9), the Guid, the name of the model and the BoundingBox. My question - is the order of the section always the same? I only looked to a particular FSX MDL and I can not see the section SMAP that is referred in the WIKI:

# 1.1 MDLH
# 1.2 MDLG
# 1.3 MDLN
# 1.4 SMAP
# 1.5 PARA
# 1.6 CRAS
# 1.7 BBOX

Now that I am writing this, I think that I can simply jump from section into section until I find the ones that I want or I find the end of the file. Even so, I leave the question.

Kind Regards,

added later: It seems that MDL scenery files for FS9 do not have a Guid and a Friendly Name. When placing such a model the XML file "creates" the Guid as far as I see in the FS2004 BGLComp SDK:

<ModelData
name=”4a5ceec84f2a9e27f12ee7a40f0c856c”
sourceFile=”MyModel.MDL”
fileOffset=”0”/>

I still do not know if a friendly name is created or used.

Luis
 
Last edited:
Hi Luis,

No, you can not assume that the other is constant. But every RIFF section contains its length so you can read and skip them quite easy.

If you want I can post the C# code I used in my Library Creator XML tool to do exactly what you want to do.

You are right, for FS2004 the GUID is in the XML file and there is no friendly name. You could probably best use the filename as friendly name in that case.
 
Hello Arno,

Thank your for your answer. I would appreciate to see your implementation to get the MDL parameters as it will be certainly much more robust than what I wrote yesterday in VB (left here for curiosity). In particular I need to check inside BGLs for MDL sections and get Guid, Name and BBox from each MDL found. I still do not know if I should do the same for FS9 BGLs and MDLs.

Regards,

Luis Sá

Code:
Option Strict On
Public Class MDLFile

    Private ValidValue As Boolean
    Public ReadOnly Property Valid() As Boolean
        Get
            Return ValidValue
        End Get
    End Property

    Private GuidValue As String
    Public ReadOnly Property Guid() As String
        Get
            Return GuidValue
        End Get
    End Property

    Private NameValue As String
    Public ReadOnly Property Name() As String
        Get
            Return NameValue
        End Get
    End Property

    Private BBoxValue As MDLBBox
    Public ReadOnly Property BBox() As MDLBBox
        Get
            Return BBoxValue
        End Get
    End Property

    Structure MDLBBox
        Public Xmin As Single
        Public Xmax As Single
        Public Ymin As Single
        Public Ymax As Single
        Public Zmin As Single
        Public Zmax As Single
    End Structure

    Public Sub New(ByVal file As String)

        Dim GuidOK As Boolean = False
        Dim NameOK As Boolean = False
        Dim BoxOK As Boolean = False
        Dim N As Integer
        Dim R As String

        ValidValue = False
        GuidValue = ""
        NameValue = ""

        Try

            Dim fs As New FileStream(file, FileMode.Open, FileAccess.Read)
            Dim reader As New BinaryReader(fs)

            If reader.ReadChars(4) <> "RIFF" Then
                fs.Dispose()
                Exit Sub
            End If

            reader.ReadBytes(4)
            R = reader.ReadChars(4)
            If R <> "MDLX" Then
                fs.Dispose()
                Exit Sub
            End If

            R = reader.ReadChars(4)
            If R <> "MDLH" Then
                fs.Dispose()
                Exit Sub
            End If

            N = CInt(reader.ReadUInt32())
            reader.ReadBytes(N)

            Do
                R = reader.ReadChars(4)
                If R = "MDLG" Then
                    fs.Position = fs.Position + 4
                    R = Conversion.Hex(reader.ReadUInt32).PadLeft(8, CChar("0"))
                    GuidValue = "{" & R & "-"
                    R = Conversion.Hex(reader.ReadUInt16).PadLeft(4, CChar("0"))
                    GuidValue = GuidValue & R & "-"
                    R = Conversion.Hex(reader.ReadUInt16).PadLeft(4, CChar("0"))
                    GuidValue = GuidValue & R & "-"
                    R = Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    R = R & Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    GuidValue = GuidValue & R & "-"
                    R = Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    R = R & Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    R = R & Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    R = R & Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    R = R & Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    R = R & Conversion.Hex(reader.ReadByte).PadLeft(2, CChar("0"))
                    GuidValue = GuidValue & R & "}"
                    GuidOK = True
                ElseIf R = "MDLN" Then
                    N = CInt(reader.ReadUInt32())
                    NameValue = reader.ReadChars(N)
                    NameOK = True
                ElseIf R = "BBOX" Then
                    N = CInt(reader.ReadUInt32())
                    BBoxValue.Xmin = reader.ReadSingle
                    BBoxValue.Ymin = reader.ReadSingle
                    BBoxValue.Zmin = reader.ReadSingle
                    BBoxValue.Xmax = reader.ReadSingle
                    BBoxValue.Ymax = reader.ReadSingle

                    BBoxValue.Zmax = reader.ReadSingle
                    reader.ReadBytes(N - 24)
                    BoxOK = True
                ElseIf R = "SMAP" Then
                    N = CInt(reader.ReadUInt32())
                    reader.ReadBytes(N)
                ElseIf R = "PARA" Then
                    N = CInt(reader.ReadUInt32())
                    reader.ReadBytes(N)
                ElseIf R = "CRAS" Then
                    N = CInt(reader.ReadUInt32())
                    reader.ReadBytes(N)
                Else
                    Exit Do
                End If
                If GuidOK And NameOK And BoxOK Then Exit Do
            Loop

            If GuidOK And NameOK And BoxOK Then ValidValue = True
            fs.Dispose()

        Catch ex As Exception

        End Try

    End Sub

End Class
 
Hi Luis,

Here is my code to read MDL files:

Code:
    public class BoundingBox
    {
        float m_xmin;
        float m_xmax;
        float m_ymin;
        float m_ymax;
        float m_zmin;
        float m_zmax;

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:30
        // Procedure Name:                BoundingBox
        // Purpose:                       Constructor
        // ----------------------------------------------------------------------------------------
        public BoundingBox(float xmin, float ymin, float zmin,
                           float xmax, float ymax, float zmax)
        {
            m_xmin = xmin;
            m_xmax = xmax;
            m_ymin = ymin;
            m_ymax = ymax;
            m_zmin = zmin;
            m_zmax = zmax;
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:39
        // Procedure Name:                radius2
        // Purpose:                       Return the 2d radius of the object
        // ----------------------------------------------------------------------------------------
        public double radius2()
        {
            float xsize = Math.Max(Math.Abs(xmin), Math.Abs(xmax));
            float ysize = Math.Max(Math.Abs(ymin), Math.Abs(ymax));

            return Math.Sqrt(xsize * xsize + ysize * ysize);
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:39
        // Procedure Name:                radius3
        // Purpose:                       Return the 3d radius of the object
        // ----------------------------------------------------------------------------------------
        public double radius3()
        {
            float xsize = Math.Max(Math.Abs(xmin), Math.Abs(xmax));
            float ysize = Math.Max(Math.Abs(ymin), Math.Abs(ymax));
            float zsize = Math.Max(Math.Abs(zmin), Math.Abs(zmax));

            return Math.Sqrt(xsize * xsize + ysize * ysize + zsize * zsize);
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:28
        // Procedure Name:                xmin
        // Purpose:                       xmin property
        // ----------------------------------------------------------------------------------------
        public float xmin
        {
            get
            {
                return m_xmin;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:28
        // Procedure Name:                xmax
        // Purpose:                       xmax property
        // ----------------------------------------------------------------------------------------
        public float xmax
        {
            get
            {
                return m_xmax;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:28
        // Procedure Name:                ymin
        // Purpose:                       ymin property
        // ----------------------------------------------------------------------------------------
        public float ymin
        {
            get
            {
                return m_ymin;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:28
        // Procedure Name:                ymax
        // Purpose:                       ymax property
        // ----------------------------------------------------------------------------------------
        public float ymax
        {
            get
            {
                return m_ymax;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:28
        // Procedure Name:                zmin
        // Purpose:                       zmin property
        // ----------------------------------------------------------------------------------------
        public float zmin
        {
            get
            {
                return m_zmin;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:28
        // Procedure Name:                zmax
        // Purpose:                       zmax property
        // ----------------------------------------------------------------------------------------
        public float zmax
        {
            get
            {
                return m_zmax;
            }
        }
    }
 
Code:
public class MDLObject
    {
        // Enumeration of the support FS versions
        public enum FsVersion
        {          
            unknown = 0,
            Fs2004 = 9,
            FsX = 10,
        }

        // Local variables
        FsVersion mdlVersion = FsVersion.unknown;
        string sGUID;
        string sName;
        string sShortName;
        string sPath;
        BoundingBox m_bbox;
        BinaryReader mdlReader;
        bool sFound;
        
        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:53
        // Procedure Name:                MDLObject
        // Purpose:                       Constructor
        // ----------------------------------------------------------------------------------------
        public MDLObject(string mdlFile)
        {

            sPath = mdlFile

            // Set default values
            mdlVersion = FsVersion.unknown;
            sGUID = null;
            sShortName = Path.GetFileNameWithoutExtension(mdlFile);
            sName = sShortName;
            m_bbox = null;
            sFound = false;

            try
            {
                mdlReader = new BinaryReader(File.Open(mdlFile, FileMode.Open),System.Text.Encoding.Default);

                string sRIFF = new string(mdlReader.ReadChars(4));
                mdlReader.ReadInt32(); // skip length
                string sHEAD = new string(mdlReader.ReadChars(4));

                // If this is a valid RIFF file
                if (sRIFF == "RIFF")
                {
                    // If Fs2004
                    if (sHEAD == "MDL9")
                    {
                        mdlVersion = FsVersion.Fs2004;
                        readInformation(mdlReader);
                    }
                    // If FsX
                    else if (sHEAD == "MDLX")
                    {
                        mdlVersion = FsVersion.FsX;
                        readInformation(mdlReader);
                    }
                }
                mdlReader.Close();
                sFound = true;
            }catch{
                // Something wrong, but I am not going to do anything
                // special now
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:55
        // Procedure Name:                readInformation
        // Purpose:                       Read the information from the MDL file
        // ----------------------------------------------------------------------------------------
        public void readInformation(BinaryReader mdlReader)
        {
            string sLABL;
            int length;

            while (mdlReader.PeekChar() != -1)
            {
                sLABL = new string(mdlReader.ReadChars(4));
                length = mdlReader.ReadInt32();

                // If this is the GUID, read it
                if (sLABL == "MDLG")
                {
                    uint g1, g21, g22; //, g31, g32, g4;
                    byte[] g3;
                    string g3s, g4s;
                    g1 = mdlReader.ReadUInt32();
                    g21 = mdlReader.ReadUInt16();
                    g22 = mdlReader.ReadUInt16();
                    g3 = mdlReader.ReadBytes(8);
                    g3s = g3[0].ToString("X").PadLeft(2, '0') + g3[1].ToString("X").PadLeft(2, '0');
                    g4s = g3[2].ToString("X").PadLeft(2, '0') + g3[3].ToString("X").PadLeft(2, '0') +
                        g3[4].ToString("X").PadLeft(2, '0') + g3[5].ToString("X").PadLeft(2, '0') +
                        g3[6].ToString("X").PadLeft(2, '0') + g3[7].ToString("X").PadLeft(2, '0');

                    sGUID = "{" + g1.ToString("X").PadLeft(8, '0') + "-" +
                                  g21.ToString("X").PadLeft(4, '0') + "-" +
                                  g22.ToString("X").PadLeft(4, '0') + "-" +
                                  g3s + "-" + g4s + "}";
                }
                // If this is the name, read it
                else if (sLABL == "MDLN")
                {
                    sName = new string(mdlReader.ReadChars(length));
                }
                // If this is the bounding box, read it
                else if (sLABL == "BBOX")
                {
                    m_bbox = new BoundingBox(mdlReader.ReadSingle(),
                        mdlReader.ReadSingle(), mdlReader.ReadSingle(),
                        mdlReader.ReadSingle(), mdlReader.ReadSingle(),
                        mdlReader.ReadSingle());
                }
                // Else just skip the section
                else
                {
                    mdlReader.ReadBytes(length);
                }
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          17/01/2007 22:53
        // Procedure Name:                filename
        // Purpose:                       Return the MDL filename
        // ----------------------------------------------------------------------------------------
        public string filename
        {
            get
            {
                return Path.GetFileName(sPath);
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          17/01/2007 22:53
        // Procedure Name:                filename
        // Purpose:                       Return the MDL filename without extension
        // ----------------------------------------------------------------------------------------
        public string filenameWithoutExtension
        {
            get
            {

                return Path.GetFileNameWithoutExtension(sPath);
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          17/01/2007 22:52
        // Procedure Name:                path
        // Purpose:                       Path property
        // ----------------------------------------------------------------------------------------
        public string path
        {
            get
            {
                return sPath;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:56
        // Procedure Name:                version
        // Purpose:                       Version property
        // ----------------------------------------------------------------------------------------
        public FsVersion version
        {
            get
            {
                return mdlVersion;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:56
        // Procedure Name:                GUID
        // Purpose:                       GUID property
        // ----------------------------------------------------------------------------------------
        public string GUID
        {
            get
            {
                return sGUID;
            }
            set
            {
                sGUID = value;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:56
        // Procedure Name:                name
        // Purpose:                       Name property
        // ----------------------------------------------------------------------------------------
        public string name
        {
            get
            {
                return sName;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          14/01/2007 22:56
        // Procedure Name:                bbox
        // Purpose:                       Bounding box property
        // ----------------------------------------------------------------------------------------
        public BoundingBox bbox
        {
            get
            {
                return m_bbox;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          20/02/2007 21:34
        // Procedure Name:                found
        // Purpose:                       Found property
        // ----------------------------------------------------------------------------------------
        public bool found
        {
            get
            {
                return sFound;
            }
        }

        // ----------------------------------------------------------------------------------------
        // Author:                        arno
        // Date:                          04/03/2007 15:34
        // Procedure Name:                shortName
        // Purpose:                       shortName property
        // ----------------------------------------------------------------------------------------
        public string shortName
        {
            get
            {
                return sShortName;
            }
        }
    }

I think if I would write it again now, I would probably use a Guid object (default .NET type) to store the GUID. Then you can just read it directly from the stream without this trouble.
 
For reading MDL files from a BGL file I use this code. It won't compile directly because it needs a lot more classes from the ModelConverterX tool, but it should give you an idea how I implemented it. At the moment the MDL objects is the only thing I read from the BGL file.

Code:
public List<ObjectModel> Read(string filename)
        {
            RaiseLogEvent("Starting reading of file " + filename, EventLogArgs.EventLogLevel.Information);
            RaiseProgressEvent(0.0);

            List<ObjectModel> objs = new List<ObjectModel>();
            MDLXReader mdlRead = new MDLXReader();

            binReader = new BinaryReader(File.Open(filename, FileMode.Open), Encoding.ASCII);

            int bglID = binReader.ReadUInt16();                 // BGL ID
            int bglVers = binReader.ReadUInt16();               // version number?

            if( bglID == 0x201 && bglVers == 0x1992 )
            {
                // Reader header
                UInt32 headLen = binReader.ReadUInt32();        // header length
                binReader.BaseStream.Position += 12;            // skip next 12 bytes
                UInt32 numSecP = binReader.ReadUInt32();        // number of section pointers
                binReader.BaseStream.Position += 32;            // skip rest of header

                // Reader all section header pointers
                for (int i = 0; i < numSecP; i++)
                {
                    UInt32 secType = binReader.ReadUInt32();    // section type
                    binReader.BaseStream.Position += 4;         // skip next 4 bytes
                    UInt32 numSubSecP = binReader.ReadUInt32(); // number of subsection pointers
                    UInt32 secOffset = binReader.ReadUInt32();  // offset of section
                    UInt32 sizeSecH = binReader.ReadUInt32();   // size of section header

                    // Since we are only interested in the MDL data
                    if (secType == 0x2b)
                    {
                        binReader.BaseStream.Position = secOffset;

                        // Read all subsection headers
                        for (int j = 0; j < numSubSecP; j++)
                        {
                            UInt32 id = binReader.ReadUInt32();             // ID
                            UInt32 numRec = binReader.ReadUInt32();         // number of records
                            UInt32 subSecOffset = binReader.ReadUInt32();   // subsection offset
                            UInt32 subSecSize = binReader.ReadUInt32();     // subsection size

                            long secHeadPos = binReader.BaseStream.Position;
                            binReader.BaseStream.Position = subSecOffset;

                            // Read all records
                            for (int k = 0; k < numRec; k++)
                            {
                                Guid name = new Guid(binReader.ReadBytes(16));
                                UInt32 mdlOffset = binReader.ReadUInt32();
                                UInt32 mdlSize = binReader.ReadUInt32();

                                long recPos = binReader.BaseStream.Position;
                                binReader.BaseStream.Position = secHeadPos + mdlOffset;

                                ObjectModel obj = mdlRead.ReadStream(binReader, mdlSize);
                                if (obj != null)
                                {
                                    objs.Add(obj);
                                }

                                binReader.BaseStream.Position = recPos;
                            }

                            // Restore to position after reading header
                            binReader.BaseStream.Position = secHeadPos;
                        }

                        break;
                    }
                }
            }
            else
            {
                RaiseLogEvent("This is not a supported BGL file" , EventLogArgs.EventLogLevel.Error);
            }

            binReader.Close();

            // Write finished log message
            if (objs.Count == 0)
            {
                RaiseLogEvent("No objects found in file", EventLogArgs.EventLogLevel.Warning);
            }
            else
            {
                RaiseLogEvent("Finished reading objects", EventLogArgs.EventLogLevel.Information);
            }
            RaiseProgressEvent(1.0);

            return objs;
        }
 
Friendly Names

EZ-Scenery creates a library (bgl file)of objects - mdl files. It also creates an xml file and a text file. It is these last 2 that contain the friendly name and the Guid which is created when you make the library. Sometimes the xml does not have a friendly name and the text file is exactly the same as the xml. In this case all you do is to add the friendly name in the text file next to the appropriate Guid. If there is no text file then one can be created by using the usual top 4 lines from the SDK and then listing the Guids and Ffirendly names.
Hope this helps:)

John aka warmingham
 
Hello Arno,

Thank you. I think that I will save a considerable amount of time by having your code at hand in reading the MDL information from the BGL. :)

Hello John,

Thank you for your feedback. I do not know EZ Scenery nor I am familiar with modelling but I think that the Guid that you refer to is already "coded" in the MDL and it is a must for the XML placement file to "know" it. I am referring to FSX.

Kind Regards,

Luis
 
Thank you for your feedback. I do not know EZ Scenery nor I am familiar with modelling but I think that the Guid that you refer to is already "coded" in the MDL and it is a must for the XML placement file to "know" it. I am referring to FSX.

The library tool that comes with EZ-Scenery is based on my old Library Creator XML. The code I posted above is used in the new version of the tool to get the desired information. But the old one is FS2004 only indeed and for another tool author is makes much more sense to read the information from the same files as FS does.
 
Back
Top