Imports System.IO Imports System.IO.Compression Imports System.Text ''' ''' This class can be used to deal with NBT-Files, specified by Notch. ''' At now only reading is supported.. ''' Public Class NBTFile #Region "Class-Variables & Enum" ''' ''' Root-tag ''' Protected _Tags As NBTTag ''' ''' The bytes of the current NBTFile ''' Protected _aBytes As Byte() ''' ''' Current Position where to read in _aBytes ''' Protected _iByteIndex As Integer = 0 ''' ''' Name of the current file ''' Protected _sFileName As String = "" ''' ''' The Magic Numbers from a GZip-File as Hex-String ''' Protected Const GZIP_MAGIC_NUMBERS As String = "1F-8B-08" ''' ''' Enumeration for how much bytes need to be read for a numtype ''' Public Enum ByteCount Count_Byte = 1 Count_Short = 2 Count_Int = 4 Count_Long = 8 Count_Single = 4 Count_Double = 8 End Enum ''' ''' Enumeration needed to convert bytes to a number with _bytetoNum() ''' Public Enum NumTypes NUM_Short = 0 NUM_Integer = 1 NUM_Long = 2 NUM_Single = 3 NUM_Double = 4 End Enum #End Region #Region "Constructor & File-Operations" ''' ''' Skips reading a file and simply initiates via the given bytes ''' ''' Bytes from the NBT-File Public Sub New(ByVal aFileBytes() As Byte) Me.init(aFileBytes) End Sub ''' ''' Reads the file from the given filename and initiates a new NBT-File ''' ''' The Path to the file to read Public Sub New(ByVal sFileName As String) Me._sFileName = sFileName Dim aFileBytes() As Byte = Me._readFile() Me.init(aFileBytes) End Sub ''' ''' Reads all bytes from the file which path is storaged in _sFileName. ''' Additionally decompresses the file if it is compressed ''' ''' The raw/decompressed bytes from the file Protected Function _readFile() As Byte() 'read the file out Dim aBytes() As Byte = File.ReadAllBytes(Me._sFileName) 'check for compression If Me._checkFileCompression(aBytes) Then 'if compressed first decompress aBytes = Me._decompressBytes(aBytes) End If 'now return the bytes raw/decompressed Return aBytes End Function ''' ''' Decompresses the given bytes via GZipStream and returns the result as Byte-Array ''' ''' The compressed bytes ''' The decompressed bytes as Byte-Array Protected Function _decompressBytes(ByVal aBytes() As Byte) As Byte() 'create a bytestream Dim oByteStream As New MemoryStream() oByteStream.Write(aBytes, 0, aBytes.Length) oByteStream.Position = 0 'create a gzip-stream to decompress the bytestream Dim oGZipStream As New GZipStream(oByteStream, CompressionMode.Decompress) 'new byte list for individual length in result Dim bResult = New List(Of Byte) 'count of bytes read in current step Dim iBytesRead As Integer = 0 While True 'declaring buffer Dim aBuffer = New Byte(1024) {} 'read from gzip-stream till buffer is full or end of stream iBytesRead = oGZipStream.Read(aBuffer, 0, aBuffer.Length - 1) 'when bytes were read, write the buffer into the result If iBytesRead > 0 Then For iByteIndex As Integer = 0 To iBytesRead - 1 bResult.Add(aBuffer(iByteIndex)) Next 'otherwise stop reading Else Exit While End If End While 'close the streams oByteStream.Close() oGZipStream.Close() 'convert the result-list into an array and return it Return bResult.ToArray() End Function ''' ''' Compares the first 3 Bytes of the given ones with the GZIP_MAGIC_NUMBERS to ''' check if the bytes are compressed with GZip ''' ''' Bytes from File ''' True, if compressed, otherwise false Protected Function _checkFileCompression(ByVal aFileBytes() As Byte) As Boolean 'Read the magic numbers of the filebytes Dim sFileNumbers As String = BitConverter.ToString(aFileBytes, 0, 3) 'compare it with them from gzip to check if the file is compressed If sFileNumbers = NBTFile.GZIP_MAGIC_NUMBERS Then Return True Else Return False End If End Function ''' ''' Initiates a NBTFile by the given bytes, sets the bytes into the class-variable _aBytes ''' and reads till the root-compound-tag starts, then initiates reading it and saves it into ''' _Tags ''' ''' The bytes of the NBTFile Public Sub init(ByVal aBytes() As Byte) 'setting bytes in class-variable Me._aBytes = aBytes 'Read the Root-Tag Me._Tags = Me._readTag(NBTTag.TagType.TAG_Compound, True, False) End Sub #End Region #Region "Reading Tags from the Byte-Array" ''' ''' Reads the Tag on the current Position in _aBytes and returns it as NBTTag ''' ''' The Type of the Tag to read, if equals NBTTag.TagType.Empty, which is default, reads the fallowing tag-type directly from the bytes ''' If true reads the name of the current Tag, otherwise skips this step ''' If true and a TagType is forced, skips the next byte ''' Returns the read NBTTag from _aBytes as NBTTag Protected Function _readTag(Optional ByVal iTagType As NBTTag.TagType = NBTTag.TagType.Empty, Optional ByVal blNamed As Boolean = True, Optional ByVal blSkipIfForced As Boolean = True) As NBTTag Dim sTagName As String = "" 'No tag-type was given, so read the next type out If iTagType = NBTTag.TagType.Empty Then Dim bTagByte As Byte = Me._getBytes(1) iTagType = bTagByte Dim iReloaded As Integer = 0 ElseIf blSkipIfForced = True Then 'otherwise if a tag type should be forced, skip the next byte Me._incrementIndex() End If If iTagType = NBTTag.TagType.TAG_End Then 'TAG_End has no name so dont read it blNamed = False End If 'Create a new tag which will get filled with informations Dim oCurrentTag As NBTTag = New NBTTag(iTagType) 'If the tag is named, read the name out and set it to the tag If blNamed Then sTagName = Me._getCurrentTagName() oCurrentTag.setName(sTagName) End If 'Switch by tag-type Select Case (iTagType) Case NBTTag.TagType.TAG_String 'first get the length of the coming string Dim oLengthTag As NBTTag = Me._readTag(NBTTag.TagType.TAG_Short, False, False) Dim stLength As Short = oLengthTag.getValue() If stLength > 0 Then 'read the value-bytes Dim aValueBytes() As Byte If stLength = 1 Then aValueBytes = New Byte() {Me._getBytes(stLength)} Else aValueBytes = Me._getBytes(stLength) End If Dim oEncoding As Encoding = System.Text.Encoding.UTF8 'convert & save it Dim sValue As String = oEncoding.GetString(aValueBytes) oCurrentTag.setValue(sValue) Else 'We've nothing to read because the length is 0 oCurrentTag.setValue("") End If Case NBTTag.TagType.TAG_Byte Dim aValueByte = Me._getBytes(NBTFile.ByteCount.Count_Byte) oCurrentTag.setValue(aValueByte) Case NBTTag.TagType.TAG_Short Dim aValueBytes = Me._getBytes(NBTFile.ByteCount.Count_Short) Dim stValue As Short = Me._byteToNum(aValueBytes, NumTypes.NUM_Short) oCurrentTag.setValue(stValue) Case NBTTag.TagType.TAG_Int Dim aValueBytes = Me._getBytes(NBTFile.ByteCount.Count_Int) Dim iValue As Integer = Me._byteToNum(aValueBytes, NumTypes.NUM_Integer) oCurrentTag.setValue(iValue) Case NBTTag.TagType.TAG_Long Dim aValueBytes = Me._getBytes(NBTFile.ByteCount.Count_Long) Dim iValue As Long = Me._byteToNum(aValueBytes, NumTypes.NUM_Long) oCurrentTag.setValue(iValue) Case NBTTag.TagType.TAG_Float Dim aValueBytes = Me._getBytes(NBTFile.ByteCount.Count_Single) Dim sglValue As Single = Me._byteToNum(aValueBytes, NumTypes.NUM_Single) oCurrentTag.setValue(sglValue) Case NBTTag.TagType.TAG_Double Dim aValueBytes = Me._getBytes(NBTFile.ByteCount.Count_Double) Dim dblValue As Double = Me._byteToNum(aValueBytes, NumTypes.NUM_Double) oCurrentTag.setValue(dblValue) Case NBTTag.TagType.TAG_Compound oCurrentTag = Me._readCompound(sTagName) Case NBTTag.TagType.TAG_Int_Array oCurrentTag = Me._readIntArray(sTagName) Case NBTTag.TagType.TAG_Byte_Array oCurrentTag = Me._readByteArray(sTagName) Case NBTTag.TagType.TAG_List oCurrentTag = Me._readList(sTagName) End Select Return oCurrentTag End Function ''' ''' Reads a TAG_List from the current position in _aBytes ''' ''' The name of the list as string ''' The finished TAG_List as NBTTag Protected Function _readList(ByVal sTagName As String) As NBTTag Dim oTagList As NBTTag = New NBTTag(NBTTag.TagType.TAG_List) oTagList.setName(sTagName) 'read the tag-id of the list-items and their count Dim iTagId As NBTTag.TagType = Me._readTag(NBTTag.TagType.TAG_Byte, False, False).getValue() Dim iItemCount As Integer = Me._readTag(NBTTag.TagType.TAG_Int, False, False).getValue() 'go through them all and add as children For i = 0 To iItemCount - 1 Dim oCurrentTag As NBTTag = Me._readTag(iTagId, False, False) oTagList.addChild(oCurrentTag) Next Return oTagList End Function ''' ''' Reads a TAG_Byte_Array from the current position in _aBytes ''' ''' The name of the array as string ''' The finished TAG_Byte_Array as NBTTag Protected Function _readByteArray(ByVal sTagName As String) As NBTTag Dim oByteArray As NBTTag = New NBTTag(NBTTag.TagType.TAG_Byte_Array) oByteArray.setName(sTagName) 'read the count of items in array Dim iItemCount As Integer = Me._readTag(NBTTag.TagType.TAG_Int, False, False).getValue() 'read them all and add as children For i = 0 To iItemCount - 1 Dim oCurrentTag As NBTTag = Me._readTag(NBTTag.TagType.TAG_Byte, False, False) oByteArray.addChild(oCurrentTag) Next Return oByteArray End Function ''' ''' Reads a TAG_Int_Array from the current position in _aBytes ''' ''' The name of the array as string ''' The finished TAG_Int_Array as NBTTag Protected Function _readIntArray(ByVal sTagName As String) As NBTTag Dim oIntArray As NBTTag = New NBTTag(NBTTag.TagType.TAG_Int_Array) oIntArray.setName(sTagName) 'read the count of items in array Dim iItemCount As Integer = Me._readTag(NBTTag.TagType.TAG_Int, False, False).getValue() 'read them all and add as children For i = 0 To iItemCount - 1 Dim oCurrentTag As NBTTag = Me._readTag(NBTTag.TagType.TAG_Int, False, False) oIntArray.addChild(oCurrentTag) Next Return oIntArray End Function ''' ''' Reads a TAG_Compound from the current position in _aBytes ''' ''' The name of the compound as string ''' The finished TAG_Compound as NBTTag Protected Function _readCompound(ByVal sTagName As String) As NBTTag Dim oCompound As NBTTag = New NBTTag(NBTTag.TagType.TAG_Compound) oCompound.setName(sTagName) Dim oChildTag As NBTTag = Me._readTag() 'While the read tag's type not TAG_End read all tags and add them as child to the compound While oChildTag.getTagType() <> NBTTag.TagType.TAG_End oCompound.addChild(oChildTag) oChildTag = Me._readTag() End While Return oCompound End Function ''' ''' Reads the Name of the current Tag ''' ''' String Protected Function _getCurrentTagName() As String Dim oNameTag As NBTTag = Me._readTag(NBTTag.TagType.TAG_String, False, False) Return oNameTag.getValue().ToString() End Function #End Region #Region "Byte-Reading- & Converting Operations" ''' ''' Increments the index in the bytes by the given count or 1 as default ''' Protected Sub _incrementIndex(Optional ByVal iCount As Integer = 1) Me._iByteIndex += iCount End Sub ''' ''' Converts the given bytes into the given NumType ''' ''' Bytes to convert ''' The type in which the bytes should be converted to ''' The converted and casted number ''' Protected Function _byteToNum(ByVal bBytes() As Byte, ByVal iType As NBTFile.NumTypes) As Object Dim ReturnValue As Object If BitConverter.IsLittleEndian Then 'reverse the bytes to get a big endian bBytes = bBytes.Reverse().ToArray() End If Select Case iType Case NumTypes.NUM_Short ReturnValue = BitConverter.ToInt16(bBytes, 0) Case NumTypes.NUM_Integer ReturnValue = BitConverter.ToInt32(bBytes, 0) Case NumTypes.NUM_Long ReturnValue = BitConverter.ToInt64(bBytes, 0) Case NumTypes.NUM_Single ReturnValue = BitConverter.ToSingle(bBytes, 0) Case NumTypes.NUM_Double ReturnValue = BitConverter.ToDouble(bBytes, 0) Case Else 'Default just ReturnValue = normal int ReturnValue = BitConverter.ToInt32(bBytes, 0) End Select Return ReturnValue End Function ''' ''' Get an amound of bytes from the current index in the file-bytes by the given count ''' and increments the index for each byte read ''' ''' The count as integer how much bytes to read ''' If count equals 1 just returns a single byte, otherwise array of bytes Protected Function _getBytes(ByVal iCount As Integer) As Object Dim aBytes() As Byte = New Byte(iCount - 1) {} For iByteIndex As Integer = 0 To iCount - 1 Me._incrementIndex() aBytes(iByteIndex) = Me._aBytes(Me._iByteIndex) Next iByteIndex If aBytes.Count = 1 Then Return aBytes(0) End If Return aBytes End Function #End Region #Region "Reading the finished Data" ''' ''' Returns the Root-NBTTag ''' ''' The Root-NBTTag Public Function getData() As NBTTag Return Me._Tags End Function ''' ''' Tries to read a tag by the given "Path" as string, starting 1 level deeper root. ''' It should look like "Player/abilities/flying". ''' This doesn't work with lists, as their items aren't named. ''' This method goes through all tags at the current level and enters the next deeper if a matching ''' name was found, so if a tag-name in 1 level isn't unique the first in file will be entered. ''' Therefore if the whole level was scanned and no matching tag was found, returns the current level's parent-tag ''' ''' The Path to the Tag want to read, format: "Level1/Level2/TagToRead". Simply "TagToRead" is also be supported ''' Returns the found tag(or root-tag resp. current's level parent-tag) as NBTTag Public Function readTag(ByVal sReadString As String) As NBTTag 'at first split the path if more then 1 level was found Dim aPath(1) As String If sReadString.Contains("/") Then aPath = sReadString.Split("/") Else aPath(0) = sReadString End If 'the current tag is root Dim oCurTag As NBTTag = Me.getData() 'at now we didn't found any children from path Dim blChildFound As Boolean = False For Each sChildName As String In aPath 'reset found blChildFound = False If sChildName <> "" Then For Each oChildTag As NBTTag In oCurTag.getChildren() 'if current child's tagname equals the current level in path set it as current child 'and set found to true If oChildTag.getName() = sChildName Then blChildFound = True oCurTag = oChildTag End If Next If blChildFound = False Then 'didn't found any child so the path couldn't be correct Return oCurTag End If End If Next Return oCurTag End Function ''' ''' Same as readTag just returns the value of the read tag ''' ''' Path to the Tag ''' The value from the found tag as Object ''' readTag Public Function read(ByVal sPath As String) As Object Return Me.readTag(sPath).getValue() End Function #End Region End Class ''' ''' Class to Handle NBT-Tags ''' Public Class NBTTag ''' ''' The Name of the Tag ''' Protected _sName As String = "" ''' ''' Value of the Tag as Object ''' Protected _oValue As Object = 0 ''' ''' Type of the Tag as NBTTag.TagType ''' Protected _iTagType As NBTTag.TagType = NBTTag.TagType.Empty ''' ''' All Children 1 level deeper of the current Tag as List(Of NBTTag) ''' Protected _aChildTags As List(Of NBTTag) = New List(Of NBTTag) ''' ''' Enumeration to compare and define tag-types ''' Enum TagType Empty = -1 TAG_End = 0 TAG_Byte = 1 TAG_Short = 2 TAG_Int = 3 TAG_Long = 4 TAG_Float = 5 TAG_Double = 6 TAG_Byte_Array = 7 TAG_String = 8 TAG_List = 9 TAG_Compound = 10 TAG_Int_Array = 11 End Enum ''' ''' Constructor with setting the tag-type as NBTTag.TagType ''' ''' The Type of the current Tag as NBTTag.TagType Public Sub New(ByVal iTagType As TagType) Me._iTagType = iTagType End Sub ''' ''' Sets the name of the tag ''' ''' The name of the tag as string Public Sub setName(ByVal sName As String) Me._sName = sName End Sub ''' ''' Sets the value to the tag as object ''' ''' The value from the tag as object Public Sub setValue(ByVal oValue As Object) Me._oValue = oValue End Sub ''' ''' Casts the value from the current tag by the tagtype and returns it ''' ''' Casted tag's value Public Function getValue() As Object Select Case Me._iTagType Case NBTTag.TagType.TAG_Byte Return DirectCast(Me._oValue, Byte) Case NBTTag.TagType.TAG_Double Return DirectCast(Me._oValue, Double) Case NBTTag.TagType.TAG_Float Return DirectCast(Me._oValue, Single) Case NBTTag.TagType.TAG_Int Return DirectCast(Me._oValue, Integer) Case NBTTag.TagType.TAG_Long Return DirectCast(Me._oValue, Long) Case NBTTag.TagType.TAG_Short Return DirectCast(Me._oValue, Short) Case NBTTag.TagType.TAG_String Return DirectCast(Me._oValue, String) Case Else Return Me._oValue End Select End Function ''' ''' Adds the given NBTTag to the children of the tag ''' ''' The child to add as NBTTag Public Sub addChild(ByVal oChild As NBTTag) Me._aChildTags.Add(oChild) End Sub ''' ''' Getter for the TagType of the tag ''' ''' The TagType of the tag as NBTTag.TagType Public Function getTagType() As TagType Return Me._iTagType End Function ''' ''' Getter for the Children from the tag ''' ''' The Children from the tag as List(Of NBTTag) Public Function getChildren() As List(Of NBTTag) Return Me._aChildTags End Function ''' ''' Getter for the Tag's name ''' ''' The name of the tag as string Public Function getName() As String Return Me._sName End Function End Class