none
Read Contents Of A Binary File With Extension .3DS

    Question

  • Hi, I have a binary file with extension ".3DS" which holds Mesh data exported from 3D Studio Max. When I open it with notepad I get to see these strange symbols. What can I do in C#.NET in order to look at the contents of the file and convert it to a text file so that it is readable? Any help will be appreciated. Thanks

    Regards,

    Rayyan Tahir

    Wednesday, May 01, 2013 7:11 PM

Answers

  • As promised:

    struct Polygon { public ushort a; public ushort b; public ushort c; } class Loader3DS { String MeshName = ""; ushort QtyToRead; ushort FaceFlags; int VertexCount; List<Vector3> Vertices = new List<Vector3>(); int PolygonCount; List<Polygon> Polygons = new List<Polygon>(); List<Vector2> TextureCoordinates = new List<Vector2>(); public Loader3DS(String PathName) { if (File.Exists(PathName)) { FileStream FileStream = new System.IO.FileStream(PathName, FileMode.Open); BinaryReader BinaryReader = new System.IO.BinaryReader(FileStream); Byte[] Bytes = BinaryReader.ReadBytes(Convert.ToInt32(FileStream.Length)); int ByteCount = Convert.ToInt32(FileStream.Length); int Position = 0; while (Position < ByteCount) { ushort ChunkID; String ChnkID = ""; Int32 Clength; String ChnkLength = ""; ChunkID = BitConverter.ToUInt16(Bytes, Position); ChnkID += ChunkID.ToString("X"); Position += 2; Clength = BitConverter.ToInt32(Bytes, Position); ChnkLength += Clength.ToString(); Position += 4; MessageBox.Show("Chunk ID = " + ChnkID.ToString() + " Chunk Length = " + ChnkLength.ToString()); switch (ChnkID) { case "4D4D": Position += 10; break; case "3D3D": Position += 10; break; case "4000": char[] l_meshName = new char[20]; for (int l_count = 0; l_count < 20; l_count++) { if (Bytes[Position] == 0) { break; } else { l_meshName[l_count] = (char)Bytes[Position]; Position++; } } Position++;

    MeshName = new string(l_meshName);

    MessageBox.Show("Mesh Name = " + MeshName); break; case "4100": break; case "4110": QtyToRead = BitConverter.ToUInt16(Bytes, Position); Position += 2; VertexCount = Convert.ToInt32(QtyToRead); MessageBox.Show("Vertex Count = " + VertexCount.ToString()); for (int x = 0; x < VertexCount; x++) { Vector3 Vertex; Vertex.X = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Vertex.Y = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Vertex.Z = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Vertices.Add(Vertex); } break; case "4120": QtyToRead = BitConverter.ToUInt16(Bytes, Position); Position += 2; PolygonCount = Convert.ToInt32(QtyToRead); MessageBox.Show("Polygon Count = " + PolygonCount.ToString()); for (int j = 0; j < PolygonCount; j++) { Polygon P; P.a = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; P.b = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; P.c = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; FaceFlags = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; Polygons.Add(P); } break; case "4140": QtyToRead = BitConverter.ToUInt16(Bytes, Position); Position += 2; for (ushort y = 0; y < QtyToRead; y++) { Vector2 Cord; Cord.X = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Cord.Y = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; } break; default: Position += (Clength - 6); break; } } FileStream.Close(); BinaryReader.Close(); } else { MessageBox.Show("Cannot Find File Specified On Path: " + PathName); } } }

    I am not going to go through all of the changes, because there were quite a few, but if you have specific questions, feel free to ask.

    I should mention that I do not use XNA, so I did not test the validity of any of those statements.  I would suggest that you convert these statements to something like I did with QtyToRead.


    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.


    Sunday, May 05, 2013 1:27 AM

All replies

  • You could extract the textual data from the file, which would basically amount to the readable text that you are reading inside of notepad, but this will not give you context to any of the information.  To truly "Read" the file, you would need to know how the file is constructed and use that structure to analyze what is actually in the file.

    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.

    Wednesday, May 01, 2013 7:35 PM
  • How can i find out how the file was constructed? I'm stuck
    Wednesday, May 01, 2013 7:47 PM
  • Max files are not text files, so why you want to convert, max understands what's inside it.

    Faisal Ahmed Farooqui —————————— If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Wednesday, May 01, 2013 7:50 PM
  • http://wiki.cgsociety.org/index.php/3ds_Max_File_Formats

    http://en.wikipedia.org/wiki/.3ds


    Faisal Ahmed Farooqui —————————— If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Wednesday, May 01, 2013 7:53 PM
  • The reason I want to convert it is that I want to write a loader for my 3D game. The file contains vertex, normals, mapping etc that I could pick and insert in vertex and index buffers to load a 3D mesh in my game. For that I really need to know how to get all that content.
    Wednesday, May 01, 2013 7:56 PM
  • Well, you need a 3DS reader. You may be able to find some on the Internet, for example this project contains one:

    http://www.codeproject.com/Articles/10221/Importing-3D-objects-into-Avalon-from-3DS-files

    Wednesday, May 01, 2013 8:12 PM
    Moderator
  • Yes. But I need it in SlimDX/C#.NET. I want to create a reader of my own. All I'm having trouble with is how to convert the contents of that binary file to a text file so that I could read it.
    Wednesday, May 01, 2013 8:49 PM
  • "I want to create a reader of my own. "

    Well, that code should be a starting point even if the application as a whole doesn't use SlimDX but WPF.

    "All I'm having trouble with is how to convert the contents of that binary file to a text file so that I could read it."

    You don't have to convert a binary file to a text file to read it, just read it directly. You could use a 3D editor to load the .3ds file and save it in a text based format like .obj or .dae but then you'll need to write code to read that format.

    Wednesday, May 01, 2013 10:30 PM
    Moderator
  • If you don't understand binary formatting of the file, it wont be possible for you. use some 3rd party API for this.

    Faisal Ahmed Farooqui —————————— If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Thursday, May 02, 2013 7:01 AM
  • If you don't understand binary formatting of the file, it wont be possible for you. use some 3rd party API for this.
    Can you kindly tell me a few API's that I could start with?
    Saturday, May 04, 2013 2:52 PM
  • You don't have to convert a binary file to a text file to read it, just read it directly.

    Okay, So Now I'm reading it directly by following the way this guy is doing Here. But according to his code section -> Switch statement, my code in c#.net detects the first two cases but not the third onwards. I have posted my code below. Perhaps you can help me find my errors. It would be of great help.  

    namespace SlimDXMeshLoader
    {
        struct Polygon
        {
            public ushort a;
            public ushort b;
            public ushort c;
        }
        class Loader3DS
        {
            String MeshName = "";
            ushort QtyToRead;
            ushort FaceFlags;
            int VertexCount;
            List<Vector3> Vertices = new List<Vector3>();
            int PolygonCount;
            List<Polygon> Polygons = new List<Polygon>();
            List<Vector2> TextureCoordinates = new List<Vector2>();
            public Loader3DS(String PathName)
            {
                if (File.Exists(PathName))
                {
                    FileStream FileStream = new System.IO.FileStream(PathName, FileMode.Open);
                    BinaryReader BinaryReader = new System.IO.BinaryReader(FileStream);
                    Byte[] Bytes = BinaryReader.ReadBytes(Convert.ToInt32(FileStream.Length));
                    int ByteCount = Convert.ToInt32(FileStream.Length);
                    int Position = 0;
    
                    while (Position < ByteCount - 4)
                    {
                        ushort ChunkID;
                        String ChnkID = "";
                        Int32 Clength;
                        String ChnkLength = "";
                        ChunkID = ushort.Parse(Bytes[Position].ToString());
                        ChnkID += ChunkID.ToString("X");
                        ChunkID = ushort.Parse(Bytes[Position + 1].ToString());
                        ChnkID += ChunkID.ToString("X");
                        Position += 2;
    
                        Clength = Convert.ToInt32(Bytes[Position].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Clength = Convert.ToInt32(Bytes[Position + 1].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Clength = Convert.ToInt32(Bytes[Position + 2].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Clength = Convert.ToInt32(Bytes[Position + 3].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Position += 4;
    
                        //MessageBox.Show("Chunk ID = " + ChnkID.ToString() + " Chunk Length = " + ChnkLength.ToString());
                        
                        switch (ChnkID)
                        {
                            case "4D4D":
                                Position += 10;
                                break;
                            case "3D3D":
                                Position += 10;
                                break;
                            case "4000":
                                int i = 0;
                                char[] Name = new char[20];
                                String NextHex;
                                do
                                {
                                    String Hex = Bytes[Position].ToString("X");
                                    NextHex = Bytes[Position + 1].ToString("X");
                                    int num = int.Parse(Hex, NumberStyles.AllowHexSpecifier);
                                    Name[i] = (char)(num);
                                    Position++;
                                    i++;
                                } while (NextHex != "00" && i < 20);
                                MeshName = new String(Name);
                                MessageBox.Show("Mesh Name = " + MeshName);
                                break;
                            
                            case "4100":    
                                break;
                            case "4110":
                                QtyToRead = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString());
                                Position += 2;
                                VertexCount = Convert.ToInt32(QtyToRead);
                                MessageBox.Show("Vertex Count = " + VertexCount.ToString());
                                for (int x = 0; x < VertexCount; x++)
                                {
                                    Vector3 Vertex;
                                    Vertex.X = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString());
                                    Position += 4;
                                    Vertex.Y = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString());
                                    Position += 4;
                                    Vertex.Z = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString());
                                    Position += 4;
                                    Vertices.Add(Vertex);
                                }
                                break;
                            case "4120":
                                QtyToRead = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString());
                                Position += 2;
                                PolygonCount = Convert.ToInt32(QtyToRead);
                                MessageBox.Show("Polygon Count = " + PolygonCount.ToString());
                                for (int j = 0; j < PolygonCount; j++)
                                {
                                    Polygon P;
                                    P.a = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString());
                                    Position += 2;
                                    P.b = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString());
                                    Position += 2;
                                    P.c = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString());
                                    Position += 2;
                                    FaceFlags = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString());
                                    Position += 2;
                                    Polygons.Add(P);
                                }
                                break;
                            case "4140":
                                QtyToRead = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString());
                                Position += 2;
                                for (ushort y = 0; y < QtyToRead; y++)
                                {
                                    Vector2 Cord;
                                    Cord.X = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString());
                                    Position += 4;
                                    Cord.Y = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString());
                                    Position += 4;
                                }
                                break;
                            default:
                                //Position = Convert.ToInt32(Convert.ToUInt32(ChnkLength) - (uint)6);
                                break;
                        }
                    }
                    FileStream.Close();
                    BinaryReader.Close();
                }
                else
                {
                    MessageBox.Show("Cannot Find File Specified On Path: " + PathName);
                }
            }
        }
    }


    • Edited by CoOlDud3 Saturday, May 04, 2013 3:09 PM
    Saturday, May 04, 2013 3:04 PM
  • If I had to venture a guess, it would probably have to do with this section:

           default:
                                //Position = Convert.ToInt32(Convert.ToUInt32(ChnkLength) - (uint)6);
                                break;
    

    Which appears to be compensating for unknown objects, but you have commented out the code that jumps over the unknown object.  Once the code hits this section and the unknown object is not properly jumped over, then it will render the remaining processing ineffective.

    If you are willing to post a sample project to read the file and your sample data (file) on some cloud service, such as Sky Drive, and post a link to the information here, I, or others, may be willing to take a look at it, but everything else is just a shot in the dark.

    There are other projects out there, such as SharpGL which you might find useful, as I believe that they already have solved most of these problems.

    I personally have no experience with this type of file format and am not inclined to research it to the depth that would be required to architect a general solution, but would be willing to take a look at your specific case.


    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.

    Saturday, May 04, 2013 3:44 PM
  • Additionally, your handling here is confusing:

                        ushort ChunkID;
                        String ChnkID = "";
                        Int32 Clength;
                        String ChnkLength = "";
                        ChunkID = ushort.Parse(Bytes[Position].ToString());
                        ChnkID += ChunkID.ToString("X");
                        ChunkID = ushort.Parse(Bytes[Position + 1].ToString());
                        ChnkID += ChunkID.ToString("X");
                        Position += 2;
    
                        Clength = Convert.ToInt32(Bytes[Position].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Clength = Convert.ToInt32(Bytes[Position + 1].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Clength = Convert.ToInt32(Bytes[Position + 2].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Clength = Convert.ToInt32(Bytes[Position + 3].ToString("X"), 16);
                        ChnkLength += Clength.ToString();
                        Position += 4;
    
    

    Only the last direct assignments of a given variable will have any effect, so why you are directly assigning (=) to multiple variables multiple times does not make a whole lot of sense.

    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.

    Saturday, May 04, 2013 4:17 PM
  • My project consists of only the class I mentioned above. and the link to the binary file is here which was exported from 3DS Max 2013. The basic task at hand is that I want to make a parser that reads through a binary file and gets the required data (3D Vertex Coordinates, Indices, Texture Coordinates, Mesh Name etc). Then I could use that data to draw the 3D model on Screen in a API Wrapper called "SlimDX". The only useful source for reading the binary file according to its known structure is here. I'd like to thank you for helping me.

      
    Saturday, May 04, 2013 4:19 PM
  • I have downloaded the file and the code reference that you identified is the same one that you identified earlier, which specifically states:

    The 3ds reader that we have developed here is a starting point for more complex readers. Keep in mind however that our routine can only read a 3ds file if there is only one object present and it is positioned at the center.  One of the next tutorials (the matrices tutorial), will add the functionality needed to load other objects. This will be the fun part. We have to include other spaceships right? Otherwise we won't have anything to destroy =) 

    This lesson wasn't so hard was it? After all, we have already done the big work in previous lessons. We can use all the code written so far for the next tutorial, in which we will learn how to add lighting using OpenGL functions. Bye bye for now happy coders!

    Which says that it will only read a 3DS file under specific circumstances.


    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.

    Saturday, May 04, 2013 4:23 PM
  • I have already done that. This Image shows a scene that consists of One object which is the small object with grass texture. On the bottom of the image it says that the X, Y and Z Positions are all zero which means it is at the center of the scene. It's the exact object I exported.
    Saturday, May 04, 2013 4:45 PM
  • I have to take the wife out for a while, but will take a look at it when I get back, if no one else has.  I will say that right off the bat, the algorithm that you posted has little chance of success.  When I return, I will take a look at something that will recognize the file that you posted and limit its recognition to the objects in which are in your switch statement and ignore everything else.

    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.

    Saturday, May 04, 2013 4:50 PM
  • As promised:

    struct Polygon { public ushort a; public ushort b; public ushort c; } class Loader3DS { String MeshName = ""; ushort QtyToRead; ushort FaceFlags; int VertexCount; List<Vector3> Vertices = new List<Vector3>(); int PolygonCount; List<Polygon> Polygons = new List<Polygon>(); List<Vector2> TextureCoordinates = new List<Vector2>(); public Loader3DS(String PathName) { if (File.Exists(PathName)) { FileStream FileStream = new System.IO.FileStream(PathName, FileMode.Open); BinaryReader BinaryReader = new System.IO.BinaryReader(FileStream); Byte[] Bytes = BinaryReader.ReadBytes(Convert.ToInt32(FileStream.Length)); int ByteCount = Convert.ToInt32(FileStream.Length); int Position = 0; while (Position < ByteCount) { ushort ChunkID; String ChnkID = ""; Int32 Clength; String ChnkLength = ""; ChunkID = BitConverter.ToUInt16(Bytes, Position); ChnkID += ChunkID.ToString("X"); Position += 2; Clength = BitConverter.ToInt32(Bytes, Position); ChnkLength += Clength.ToString(); Position += 4; MessageBox.Show("Chunk ID = " + ChnkID.ToString() + " Chunk Length = " + ChnkLength.ToString()); switch (ChnkID) { case "4D4D": Position += 10; break; case "3D3D": Position += 10; break; case "4000": char[] l_meshName = new char[20]; for (int l_count = 0; l_count < 20; l_count++) { if (Bytes[Position] == 0) { break; } else { l_meshName[l_count] = (char)Bytes[Position]; Position++; } } Position++;

    MeshName = new string(l_meshName);

    MessageBox.Show("Mesh Name = " + MeshName); break; case "4100": break; case "4110": QtyToRead = BitConverter.ToUInt16(Bytes, Position); Position += 2; VertexCount = Convert.ToInt32(QtyToRead); MessageBox.Show("Vertex Count = " + VertexCount.ToString()); for (int x = 0; x < VertexCount; x++) { Vector3 Vertex; Vertex.X = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Vertex.Y = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Vertex.Z = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Vertices.Add(Vertex); } break; case "4120": QtyToRead = BitConverter.ToUInt16(Bytes, Position); Position += 2; PolygonCount = Convert.ToInt32(QtyToRead); MessageBox.Show("Polygon Count = " + PolygonCount.ToString()); for (int j = 0; j < PolygonCount; j++) { Polygon P; P.a = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; P.b = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; P.c = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; FaceFlags = ushort.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString()); Position += 2; Polygons.Add(P); } break; case "4140": QtyToRead = BitConverter.ToUInt16(Bytes, Position); Position += 2; for (ushort y = 0; y < QtyToRead; y++) { Vector2 Cord; Cord.X = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; Cord.Y = float.Parse(Bytes[Position].ToString() + Bytes[Position + 1].ToString() + Bytes[Position + 2].ToString() + Bytes[Position + 3].ToString()); Position += 4; } break; default: Position += (Clength - 6); break; } } FileStream.Close(); BinaryReader.Close(); } else { MessageBox.Show("Cannot Find File Specified On Path: " + PathName); } } }

    I am not going to go through all of the changes, because there were quite a few, but if you have specific questions, feel free to ask.

    I should mention that I do not use XNA, so I did not test the validity of any of those statements.  I would suggest that you convert these statements to something like I did with QtyToRead.


    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.


    Sunday, May 05, 2013 1:27 AM
  • You Sir Are A Born Perfectionist! Thank you. Not only do I know how to make a 3DS Parser, I also know the proper way of binary formatting. A very heart felt Thank You! =).
    Sunday, May 05, 2013 10:27 AM
  • Glad I could be of assistance.

    It would be greatly appreciated if you would mark any helpful entries as helpful and if the entry answers your question, please mark it with the Answer link.

    Sunday, May 05, 2013 1:55 PM