├── .editorconfig ├── .gitignore ├── README.md ├── appveyor.yml ├── docs ├── Changelog.md ├── Doxyfile └── LICENSE ├── fNbt.Test ├── CompoundTests.cs ├── ListTests.cs ├── MiscTests.cs ├── NbtFileTests.cs ├── NbtReaderTests.cs ├── NbtWriterTest.cs ├── NonSeekableStream.cs ├── PartialReadStream.cs ├── Properties │ └── AssemblyInfo.cs ├── ShortcutTests.cs ├── TagSelectorTests.cs ├── TestFiles.cs ├── TestFiles │ ├── bigtest.nbt │ ├── bigtest.nbt.gz │ ├── bigtest.nbt.z │ ├── test.nbt │ ├── test.nbt.gz │ └── test.nbt.z └── fNbt.Test.csproj ├── fNbt.sln ├── fNbt.sln.DotSettings └── fNbt ├── ByteCountingStream.cs ├── InvalidReaderStateException.cs ├── NbtBinaryReader.cs ├── NbtBinaryWriter.cs ├── NbtCompression.cs ├── NbtFile.cs ├── NbtFlavor.cs ├── NbtFormatException.cs ├── NbtParseState.cs ├── NbtReader.cs ├── NbtReaderNode.cs ├── NbtTagType.cs ├── NbtWriter.cs ├── NbtWriterNode.cs ├── NullableSupport.cs ├── Properties └── AssemblyInfo.cs ├── TagSelector.cs ├── Tags ├── NbtByte.cs ├── NbtByteArray.cs ├── NbtCompound.cs ├── NbtDouble.cs ├── NbtFloat.cs ├── NbtInt.cs ├── NbtIntArray.cs ├── NbtList.cs ├── NbtLong.cs ├── NbtLongArray.cs ├── NbtShort.cs ├── NbtString.cs ├── NbtTag.cs └── NbtTagCollection.cs ├── ZLibStream.cs ├── fNbt.csproj └── fNbt.nuspec /.editorconfig: -------------------------------------------------------------------------------- 1 | # Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\matv8981.AVWORLD\source\fNbt codebase based on best match to current usage at 8/10/2021 2 | # You can modify the rules from these initially generated values to suit your own policies 3 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference 4 | [*.cs] 5 | 6 | 7 | #Core editorconfig formatting - indentation 8 | 9 | #use soft tabs (spaces) for indentation 10 | indent_style = space 11 | 12 | #Formatting - indentation options 13 | 14 | #indent switch case contents. 15 | csharp_indent_case_contents = true 16 | #indent switch labels 17 | csharp_indent_switch_labels = true 18 | 19 | #Formatting - new line options 20 | csharp_new_line_before_open_brace = false 21 | csharp_new_line_before_else = false 22 | csharp_new_line_before_members_in_object_initializers = false 23 | csharp_new_line_before_members_in_anonymous_types = false 24 | csharp_new_line_before_catch = false 25 | csharp_new_line_before_finally = false 26 | 27 | #Formatting - organize using options 28 | 29 | #sort System.* using directives alphabetically, and place them before other usings 30 | dotnet_sort_system_directives_first = true 31 | 32 | #Formatting - spacing options 33 | 34 | #require NO space between a cast and the value 35 | csharp_space_after_cast = false 36 | #require a space before the colon for bases or interfaces in a type declaration 37 | csharp_space_after_colon_in_inheritance_clause = true 38 | #require a space after a keyword in a control flow statement such as a for loop 39 | csharp_space_after_keywords_in_control_flow_statements = true 40 | #require a space before the colon for bases or interfaces in a type declaration 41 | csharp_space_before_colon_in_inheritance_clause = true 42 | #remove space within empty argument list parentheses 43 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 44 | #remove space between method call name and opening parenthesis 45 | csharp_space_between_method_call_name_and_opening_parenthesis = false 46 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call 47 | csharp_space_between_method_call_parameter_list_parentheses = false 48 | #remove space within empty parameter list parentheses for a method declaration 49 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 50 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. 51 | csharp_space_between_method_declaration_parameter_list_parentheses = false 52 | 53 | #Formatting - wrapping options 54 | 55 | #leave code block on separate lines 56 | csharp_preserve_single_line_blocks = true 57 | #leave statements and member declarations on the same line 58 | csharp_preserve_single_line_statements = true 59 | 60 | #Style - Code block preferences 61 | 62 | #prefer curly braces even for one line of code 63 | csharp_prefer_braces = true:suggestion 64 | 65 | #Style - expression bodied member options 66 | 67 | #prefer block bodies for accessors 68 | csharp_style_expression_bodied_accessors = false:suggestion 69 | #prefer block bodies for constructors 70 | csharp_style_expression_bodied_constructors = false:suggestion 71 | #prefer block bodies for indexers 72 | csharp_style_expression_bodied_indexers = false:suggestion 73 | #prefer block bodies for methods 74 | csharp_style_expression_bodied_methods = false:suggestion 75 | #prefer block bodies for properties 76 | csharp_style_expression_bodied_properties = false:suggestion 77 | 78 | #Style - expression level options 79 | 80 | #prefer out variables to be declared before the method call 81 | csharp_style_inlined_variable_declaration = false:suggestion 82 | #prefer the type name for member access expressions, instead of the language keyword 83 | dotnet_style_predefined_type_for_member_access = false:suggestion 84 | 85 | #Style - Expression-level preferences 86 | 87 | #prefer objects to be initialized using object initializers when possible 88 | dotnet_style_object_initializer = true:suggestion 89 | 90 | #Style - implicit and explicit types 91 | 92 | #prefer explicit type over var in all cases, unless overridden by another code style rule 93 | csharp_style_var_elsewhere = false:suggestion 94 | #prefer explicit type over var to declare variables with built-in system types such as int 95 | csharp_style_var_for_built_in_types = false:suggestion 96 | #prefer var when the type is already mentioned on the right-hand side of a declaration expression 97 | csharp_style_var_when_type_is_apparent =false:suggestion 98 | 99 | #Style - language keyword and framework type options 100 | 101 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them 102 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 103 | 104 | #Style - modifier options 105 | 106 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. 107 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 108 | 109 | #Style - Modifier preferences 110 | 111 | #when this rule is set to a list of modifiers, prefer the specified ordering. 112 | csharp_preferred_modifier_order = public,internal,private,override,static,readonly,sealed,abstract,new,virtual,unsafe:suggestion 113 | 114 | #Style - Pattern matching 115 | 116 | #prefer is expression with type casts instead of pattern matching 117 | csharp_style_pattern_matching_over_as_with_null_check = false:suggestion 118 | 119 | #Style - qualification options 120 | 121 | #prefer fields not to be prefaced with this. or Me. in Visual Basic 122 | dotnet_style_qualification_for_field = false:suggestion 123 | #prefer methods not to be prefaced with this. or Me. in Visual Basic 124 | dotnet_style_qualification_for_method = false:suggestion 125 | #prefer properties not to be prefaced with this. or Me. in Visual Basic 126 | dotnet_style_qualification_for_property = false:suggestion 127 | csharp_indent_labels = one_less_than_current 128 | csharp_using_directive_placement = outside_namespace:silent 129 | csharp_prefer_simple_using_statement = true:suggestion 130 | csharp_style_namespace_declarations = block_scoped:silent 131 | csharp_style_prefer_method_group_conversion = true:silent 132 | csharp_style_prefer_top_level_statements = true:silent 133 | csharp_style_prefer_primary_constructors = true:suggestion 134 | csharp_style_expression_bodied_operators = false:silent 135 | csharp_style_expression_bodied_lambdas = true:silent 136 | csharp_style_expression_bodied_local_functions = false:silent 137 | 138 | [*.{cs,vb}] 139 | dotnet_style_predefined_type_for_locals_parameters_members=true:warning 140 | [*.{cs,vb}] 141 | #### Naming styles #### 142 | 143 | # Naming rules 144 | 145 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 146 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 147 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 148 | 149 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 150 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 151 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 152 | 153 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 154 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 155 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 156 | 157 | # Symbol specifications 158 | 159 | dotnet_naming_symbols.interface.applicable_kinds = interface 160 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 161 | dotnet_naming_symbols.interface.required_modifiers = 162 | 163 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 164 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 165 | dotnet_naming_symbols.types.required_modifiers = 166 | 167 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 168 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 169 | dotnet_naming_symbols.non_field_members.required_modifiers = 170 | 171 | # Naming styles 172 | 173 | dotnet_naming_style.begins_with_i.required_prefix = I 174 | dotnet_naming_style.begins_with_i.required_suffix = 175 | dotnet_naming_style.begins_with_i.word_separator = 176 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 177 | 178 | dotnet_naming_style.pascal_case.required_prefix = 179 | dotnet_naming_style.pascal_case.required_suffix = 180 | dotnet_naming_style.pascal_case.word_separator = 181 | dotnet_naming_style.pascal_case.capitalization = pascal_case 182 | 183 | dotnet_naming_style.pascal_case.required_prefix = 184 | dotnet_naming_style.pascal_case.required_suffix = 185 | dotnet_naming_style.pascal_case.word_separator = 186 | dotnet_naming_style.pascal_case.capitalization = pascal_case 187 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 188 | tab_width = 4 189 | indent_size = 4 190 | end_of_line = crlf 191 | dotnet_style_coalesce_expression = true:suggestion 192 | dotnet_style_null_propagation = true:suggestion 193 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 194 | dotnet_style_prefer_auto_properties = true:silent 195 | dotnet_style_object_initializer = true:suggestion 196 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _ReSharper.LibNbt 2 | fNbt/obj 3 | fNbt.Test/obj 4 | fNbt.Serialization/obj 5 | fNbt.Serialization.Test/obj 6 | bin/ 7 | packages/ 8 | *.user 9 | *.suo 10 | .vs 11 | .idea/.idea.fNbt/.idea/workspace.xml 12 | 13 | fNbt.sln.DotSettings 14 | *.xml 15 | *.iml 16 | *.DotSettings 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/vcdkhya4u6h26qr2/branch/master?svg=true)](https://ci.appveyor.com/project/fragmer/fnbt/branch/master) 2 | 3 | [Named Binary Tag (NBT)](https://minecraft.gamepedia.com/NBT_format) is a structured binary file format used by Minecraft. 4 | fNbt is a small library, written in C# for .NET 3.5+. It provides functionality 5 | to create, load, traverse, modify, and save NBT files and streams. 6 | 7 | Current released version is 0.6.4 (6 July 2018). 8 | 9 | fNbt is based in part on Erik Davidson's (aphistic's) original LibNbt library, 10 | now completely rewritten by Matvei Stefarov (fragmer). 11 | 12 | Note that fNbt.Test.dll and nunit.framework.dll do NOT need to be bundled with 13 | applications that use fNbt; they are only used for testing. 14 | 15 | 16 | ## FEATURES 17 | - Load and save uncompressed, GZip-, and ZLib-compressed files/streams. 18 | - Easily create, traverse, and modify NBT documents. 19 | - Simple indexer-based syntax for accessing compound, list, and nested tags. 20 | - Shortcut properties to access tags' values without unnecessary type casts. 21 | - Compound tags implement `ICollection` and List tags implement `IList`, for easy traversal and LINQ integration. 22 | - Good performance and low memory overhead. 23 | - Built-in pretty-printing of individual tags or whole files. 24 | - Every class and method are fully documented, annotated, and unit-tested. 25 | - Can work with both big-endian and little-endian NBT data and systems. 26 | - Optional high-performance reader/writer for working with streams directly. 27 | 28 | 29 | ## DOWNLOAD 30 | Latest version of fNbt requires .NET Framework 3.5+ (client or full profile). 31 | 32 | - **Package @ NuGet:** https://www.nuget.org/packages/fNbt/ 33 | 34 | - **Compiled binary:** https://fcraft.net/fnbt/fNbt_v0.6.4.zip 35 |
SHA1: 600853530fd538e614b6cb4722ced81917e9615d 36 | 37 | - **Amalgamation** (single source file): 38 | * Non-annotated: https://fcraft.net/fnbt/fNbt_v0.6.4.cs 39 |
SHA1: 9298dbe00d080bcf5d32299415aaf856590ba3bf 40 | * Annotated (using [JetBrains.Annotations](https://blog.jetbrains.com/dotnet/2018/05/03/what-are-jetbrains-annotations/)): 41 | https://fcraft.net/fnbt/fNbt_v0.6.4_Annotated.cs 42 |
SHA1: ae096d83b57bf59c708ad66168d45c1ea9b58175 43 | 44 | 45 | ## EXAMPLES 46 | #### Loading a gzipped file 47 | ```cs 48 | var myFile = new NbtFile(); 49 | myFile.LoadFromFile("somefile.nbt.gz"); 50 | var myCompoundTag = myFile.RootTag; 51 | ``` 52 | 53 | #### Accessing tags (long/strongly-typed style) 54 | ```cs 55 | int intVal = myCompoundTag.Get("intTagsName").Value; 56 | string listItem = myStringList.Get(0).Value; 57 | byte nestedVal = myCompTag.Get("nestedTag") 58 | .Get("someByteTag") 59 | .Value; 60 | ``` 61 | 62 | #### Accessing tags (shortcut style) 63 | ```cs 64 | int intVal = myCompoundTag["intTagsName"].IntValue; 65 | string listItem = myStringList[0].StringValue; 66 | byte nestedVal = myCompTag["nestedTag"]["someByteTag"].ByteValue; 67 | ``` 68 | 69 | #### Iterating over all tags in a compound/list 70 | ```cs 71 | foreach( NbtTag tag in myCompoundTag.Values ){ 72 | Console.WriteLine( tag.Name + " = " + tag.TagType ); 73 | } 74 | foreach( string tagName in myCompoundTag.Names ){ 75 | Console.WriteLine( tagName ); 76 | } 77 | for( int i = 0; i < myListTag.Count; i++ ){ 78 | Console.WriteLine( myListTag[i] ); 79 | } 80 | foreach( NbtInt intItem in myIntList.ToArray() ){ 81 | Console.WriteLine( intItem.Value ); 82 | } 83 | ``` 84 | 85 | #### Constructing a new document 86 | ```cs 87 | var serverInfo = new NbtCompound("Server"); 88 | serverInfo.Add( new NbtString("Name", "BestServerEver") ); 89 | serverInfo.Add( new NbtInt("Players", 15) ); 90 | serverInfo.Add( new NbtInt("MaxPlayers", 20) ); 91 | var serverFile = new NbtFile(serverInfo); 92 | serverFile.SaveToFile( "server.nbt", NbtCompression.None ); 93 | ``` 94 | 95 | #### Constructing using collection initializer notation 96 | ```cs 97 | var compound = new NbtCompound("root"){ 98 | new NbtInt("someInt", 123), 99 | new NbtList("byteList") { 100 | new NbtByte(1), 101 | new NbtByte(2), 102 | new NbtByte(3) 103 | }, 104 | new NbtCompound("nestedCompound") { 105 | new NbtDouble("pi", 3.14) 106 | } 107 | }; 108 | ``` 109 | 110 | #### Pretty-printing file structure 111 | ```cs 112 | Console.WriteLine( myFile.ToString("\t") ); // tabs 113 | Console.WriteLine( myRandomTag.ToString(" ") ); // spaces 114 | ``` 115 | 116 | #### Check out unit tests in fNbt.Test for more examples. 117 | 118 | 119 | ## API REFERENCE 120 | Online reference can be found at http://www.fcraft.net/fnbt/v0.6.4/ 121 | 122 | 123 | ## LICENSING 124 | fNbt v0.5.0+ is licensed under 3-Clause BSD license; 125 | see [docs/LICENSE](docs/LICENSE). 126 | LibNbt2012 up to and including v0.4.1 kept LibNbt's original license (LGPLv3). 127 | 128 | 129 | ## VERSION HISTORY 130 | See [docs/Changelog.md](docs/Changelog.md) 131 | 132 | 133 | ## OLD VERSIONS 134 | If you need .NET 2.0 support, stick to using fNbt version 0.5.1. 135 | Note that this 0.5.x branch of fNbt is no longer supported or updated. 136 | 137 | - **Compiled binary:** https://fcraft.net/fnbt/fNbt_v0.5.1.zip 138 | 139 | - **Amalgamation** (single source file): 140 | - Non-annotated: https://fcraft.net/fnbt/fNbt_v0.5.1.cs 141 | - Annotated: https://fcraft.net/fnbt/fNbt_v0.5.1_Annotated.cs 142 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '0.6.4.{build}' 2 | before_build: 3 | - nuget restore -verbosity detailed 4 | -------------------------------------------------------------------------------- /docs/Changelog.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 (fNbt) 2 | - Library now targets .NET Standard 2.0 instead of .NET Framework, which 3 | allows fNbt to be used in more types of projects (e.g. .NET 8 or UWP). 4 | - Support TAG_Long_Array. 5 | - Fix some edge-cases related to reading corrupted NBT files. 6 | - Switch from JetBrains' annotations to .NET's built-in annotations. 7 | 8 | ## 0.6.4 (fNbt) 9 | - Fixed a case where NbtBinaryReader.ReadString read too many bytes (#26). 10 | - Fixed NbtList.Contains(null) throwing exception instead of returning false. 11 | - Reduced NbtBinaryReader's maximum chunk size to 4 MB. This reduces peak 12 | memory use when reading huge files without affecting performance. 13 | 14 | ## 0.6.3 (fNbt) 15 | - Empty NbtLists now allow "TAG_End" as its ListType (#12). 16 | 17 | ## 0.6.2 (fNbt) 18 | - NbtTags now implement ICloneable and provide copy constructors (#10). 19 | - fNbt is now compatible with /checked compiler option. 20 | - Fixed an OverflowException in .NET 4.0+ when writing arrays of size 1 GiB 21 | (or larger) to a BufferedStream. 22 | - Fixed a few edge cases in NbtReader when reading corrupt files. 23 | - Minor optimizations and documentation fixes. 24 | 25 | ## 0.6.1 (fNbt) 26 | - NbtReader now supports non-seekable streams. 27 | - Fixed issues loading from/saving to non-seekable steams in NbtFile. 28 | - NbtFile.LoadFromStream/SaveToStream now accurately report bytes read/written 29 | for NBT data over 2 GiB in size. 30 | - API change: 31 | All NbtFile loading/saving methods now return long instead of int. 32 | 33 | ## 0.6.0 (fNbt) 34 | - Raised .NET framework requirements from 2.0+ to 3.5+ 35 | - Added NbtWriter, for linearly writing NBT streams, similarly to XmlWriter. 36 | It enables high-performance writing, without creating temp NbtTag objects. 37 | - Fixed handling of lists-of-lists and lists-of-compound-tags in NbtReader. 38 | - Fixed being able to add an NbtList to itself. 39 | - API changes: 40 | Removed NbtCompound.ToArray(), use NbtCompound.Tags.ToArray() instead. 41 | Removed NbtCompound.ToNameArray(), use NbtCompound.Names.ToArray() instead. 42 | - Improved tag reading and writing performance. 43 | - Expanded unit test coverage. 44 | 45 | ## 0.5.1 (fNbt) 46 | - Fixed ToString() methods of NbtReader and some NbtTags not respecting the 47 | NbtTag.DefaultIndentString setting. 48 | - Fixed being able to add a Compound tag to itself. 49 | - Fixed NbtString value defaulting to null, instead of an empty string. 50 | - Fixed a number of bugs in NbtReader.ReadListAsArray(). 51 | - API additions: 52 | New NbtReader property: bool IsAtStreamEnd 53 | New NbtReader overload: string ToString(bool,string) 54 | - Expanded unit test coverage. 55 | 56 | ## 0.5.0 (fNbt) 57 | - Added NbtReader, for linearly reading NBT streams, similarly to XmlReader. 58 | - API additions: 59 | New NbtCompound method: bool TryGet(string,out NbtTag) 60 | New NbtCompound overload: NbtTag Get(string) 61 | New NbtTag property: bool HasValue 62 | - License changed from LGPL to to 3-Clause BSD, since none of the original 63 | libnbt source code remains. 64 | 65 | ## 0.4.1 (LibNbt2012) 66 | - Added a way to set up default indent for NbtTag.ToString() methods, using 67 | NbtTag.DefaultIndentString static property. 68 | - Added a way to control/disable buffering when reading tags, using properties 69 | NbtFile.DefaultBufferSize (static) and "nbtFile.BufferSize" (instance). 70 | - Simplified renaming tags. Instead of using NbtFile.RenameRootTag or 71 | NbtCompound.RenameTag, you can now set tag's Name property directly. It 72 | will check parent tag automatically, and throw ArgumentException or 73 | ArgumentNullException is renaming is not possible. 74 | - NbtFile() constructor now initializes RootTag to an empty NbtCompound(""). 75 | - Added LoadFro* overloads that do not require a TagSelector parameter. 76 | 77 | ## 0.4.0 (LibNbt2012) 78 | - Changed the way NbtFiles are constructed. Data is not loaded in the 79 | constructor itself any more, use LoadFrom* method. 80 | - Added a way to load NBT data directly from byte arrays, and to save them to 81 | byte arrays. 82 | - All LoadFrom-/SaveTo- methods now return an int, indicating the number of 83 | bytes read/written. 84 | - Updated NbtFile to override ToString. 85 | - Added a way to control endianness when reading/writing NBT files. 86 | 87 | ## 0.3.4 (LibNbt2012) 88 | - Added a way to rename tags inside NbtCompound and NbtFile. 89 | 90 | ## 0.3.3 (LibNbt2012) 91 | - Added a way to skip certain tags at load-time, using a TagSelector callback. 92 | 93 | ## 0.3.2 (LibNbt2012) 94 | - Added a way to easily identify files, using static NbtFile.ReadRootTagName. 95 | - Added NbtTag.Parent (automatically set/reset by NbtList and NbtCompound). 96 | - Added NbtTag.Path (which includes parents' names, and list indices). 97 | - Added NbtCompound.Names and NbtCompound.Values enumerators. 98 | 99 | ## 0.3.1 (LibNbt2012) 100 | - Added indexers to NbtTag base class, to make nested compound/list tags easier 101 | to work with. 102 | - Added shortcut properties for getting tag values. 103 | - Added a ToArray() overload to NbtList, to automate casting to a specific 104 | tag type. 105 | - Improved .ToString() pretty-printing, now with consistent and configurable 106 | indentation. 107 | 108 | ## 0.3.0 (LibNbt2012) 109 | - Auto-detection of NBT file compression. 110 | - Loading and saving of ZLib (RFC-1950) compresessed NBT files. 111 | - Reduced loading/saving CPU use by 15%, and memory use by 40% 112 | - Full support for TAG_Int_Array 113 | - NbtCompound now implements ICollection and ICollection 114 | - NbtList now implements IList and IList 115 | - More constraint checks to tag loading, modification, and saving. 116 | - Replaced getter/setter methods with properties, wherever possible. 117 | - Expanded unit test coverage. 118 | - Fully documented everything. 119 | - Made tag names immutable. 120 | - Removed tag queries. 121 | 122 | ## 0.2.0 (libnbt) 123 | - Implemented tag queries. 124 | - Created unit tests for the larger portions of the code. 125 | - Marked tag constructors that take only tag values as obsolete, use the 126 | constructor that has name and value instead. 127 | 128 | ## 0.1.2 (libnbt) 129 | - Added a GetTagType() function to the tag classes. 130 | - Fixed saving NbtList tags. 131 | 132 | ## 0.1.1 (libnbt) 133 | - Initial release. 134 | - Modified the tag constructors to be consistant with each other. 135 | - Changed NbtFile to allow some functions to be overridden. 136 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2018, Matvei "fragmer" Stefarov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 3. Neither the name of fNbt nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 22 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | -------------------------------------------------------------------------------- /fNbt.Test/CompoundTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using NUnit.Framework; 6 | 7 | namespace fNbt.Test { 8 | [TestFixture] 9 | public sealed class CompoundTests { 10 | [Test] 11 | public void InitializingCompoundFromCollectionTest() { 12 | NbtTag[] allNamed = { 13 | new NbtShort("allNamed1", 1), 14 | new NbtLong("allNamed2", 2), 15 | new NbtInt("allNamed3", 3) 16 | }; 17 | 18 | NbtTag[] someUnnamed = { 19 | new NbtInt("someUnnamed1", 1), 20 | new NbtInt(2), 21 | new NbtInt("someUnnamed3", 3) 22 | }; 23 | 24 | NbtTag[] someNull = { 25 | new NbtInt("someNull1", 1), 26 | null, 27 | new NbtInt("someNull3", 3) 28 | }; 29 | 30 | NbtTag[] dupeNames = { 31 | new NbtInt("dupeNames1", 1), 32 | new NbtInt("dupeNames2", 2), 33 | new NbtInt("dupeNames1", 3) 34 | }; 35 | 36 | // null collection, should throw 37 | Assert.Throws(() => new NbtCompound("nullTest", null)); 38 | 39 | // proper initialization 40 | NbtCompound allNamedTest = null; 41 | Assert.DoesNotThrow(() => allNamedTest = new NbtCompound("allNamedTest", allNamed)); 42 | CollectionAssert.AreEquivalent(allNamed, allNamedTest); 43 | 44 | // some tags are unnamed, should throw 45 | Assert.Throws(() => new NbtCompound("someUnnamedTest", someUnnamed)); 46 | 47 | // some tags are null, should throw 48 | Assert.Throws(() => new NbtCompound("someNullTest", someNull)); 49 | 50 | // some tags have same names, should throw 51 | Assert.Throws(() => new NbtCompound("dupeNamesTest", dupeNames)); 52 | } 53 | 54 | 55 | [Test] 56 | public void GettersAndSetters() { 57 | // construct a document for us to test. 58 | var nestedChild = new NbtCompound("NestedChild"); 59 | var nestedInt = new NbtInt(1); 60 | var nestedChildList = new NbtList("NestedChildList") { 61 | nestedInt 62 | }; 63 | var child = new NbtCompound("Child") { 64 | nestedChild, 65 | nestedChildList 66 | }; 67 | var childList = new NbtList("ChildList") { 68 | new NbtInt(1) 69 | }; 70 | var parent = new NbtCompound("Parent") { 71 | child, 72 | childList 73 | }; 74 | 75 | // Accessing nested compound tags using indexers 76 | Assert.AreEqual(nestedChild, parent["Child"]["NestedChild"]); 77 | Assert.AreEqual(nestedChildList, parent["Child"]["NestedChildList"]); 78 | Assert.AreEqual(nestedInt, parent["Child"]["NestedChildList"][0]); 79 | 80 | // Accessing nested compound tags using Get and Get 81 | Assert.Throws(() => parent.Get(null)); 82 | Assert.IsNull(parent.Get("NonExistingChild")); 83 | Assert.AreEqual(nestedChild, parent.Get("Child").Get("NestedChild")); 84 | Assert.AreEqual(nestedChildList, parent.Get("Child").Get("NestedChildList")); 85 | Assert.AreEqual(nestedInt, parent.Get("Child").Get("NestedChildList")[0]); 86 | Assert.Throws(() => parent.Get(null)); 87 | Assert.IsNull(parent.Get("NonExistingChild")); 88 | Assert.AreEqual(nestedChild, (parent.Get("Child") as NbtCompound).Get("NestedChild")); 89 | Assert.AreEqual(nestedChildList, (parent.Get("Child") as NbtCompound).Get("NestedChildList")); 90 | Assert.AreEqual(nestedInt, (parent.Get("Child") as NbtCompound).Get("NestedChildList")[0]); 91 | 92 | // Accessing with Get and an invalid given type 93 | Assert.Throws(() => parent.Get("Child")); 94 | 95 | // Using TryGet and TryGet 96 | NbtTag dummyTag; 97 | Assert.Throws(() => parent.TryGet(null, out dummyTag)); 98 | Assert.IsFalse(parent.TryGet("NonExistingChild", out dummyTag)); 99 | Assert.IsTrue(parent.TryGet("Child", out dummyTag)); 100 | NbtCompound dummyCompoundTag; 101 | Assert.Throws(() => parent.TryGet(null, out dummyCompoundTag)); 102 | Assert.IsFalse(parent.TryGet("NonExistingChild", out dummyCompoundTag)); 103 | Assert.IsTrue(parent.TryGet("Child", out dummyCompoundTag)); 104 | 105 | // Trying to use integer indexers on non-NbtList tags 106 | Assert.Throws(() => parent[0] = nestedInt); 107 | Assert.Throws(() => nestedInt[0] = nestedInt); 108 | 109 | // Trying to use string indexers on non-NbtCompound tags 110 | Assert.Throws(() => dummyTag = childList["test"]); 111 | Assert.Throws(() => childList["test"] = nestedInt); 112 | Assert.Throws(() => nestedInt["test"] = nestedInt); 113 | 114 | // Trying to get a non-existent element by name 115 | Assert.IsNull(parent.Get("NonExistentTag")); 116 | Assert.IsNull(parent["NonExistentTag"]); 117 | 118 | // Null indices on NbtCompound 119 | Assert.Throws(() => parent.Get(null)); 120 | Assert.Throws(() => parent[null] = new NbtInt(1)); 121 | Assert.Throws(() => nestedInt = (NbtInt)parent[null]); 122 | 123 | // Out-of-range indices on NbtList 124 | Assert.Throws(() => nestedInt = (NbtInt)childList[-1]); 125 | Assert.Throws(() => childList[-1] = new NbtInt(1)); 126 | Assert.Throws(() => nestedInt = childList.Get(-1)); 127 | Assert.Throws(() => nestedInt = (NbtInt)childList[childList.Count]); 128 | Assert.Throws(() => nestedInt = childList.Get(childList.Count)); 129 | 130 | // Using setter correctly 131 | parent["NewChild"] = new NbtByte("NewChild"); 132 | 133 | // Using setter incorrectly 134 | object dummyObject; 135 | Assert.Throws(() => parent["Child"] = null); 136 | Assert.NotNull(parent["Child"]); 137 | Assert.Throws(() => parent["Child"] = new NbtByte("NotChild")); 138 | Assert.Throws(() => dummyObject = parent[0]); 139 | Assert.Throws(() => parent[0] = new NbtByte("NewerChild")); 140 | 141 | // Try adding tag to self 142 | var selfTest = new NbtCompound("SelfTest"); 143 | Assert.Throws(() => selfTest["SelfTest"] = selfTest); 144 | 145 | // Try adding a tag that already has a parent 146 | Assert.Throws(() => selfTest[child.Name] = child); 147 | } 148 | 149 | 150 | [Test] 151 | public void Renaming() { 152 | var tagToRename = new NbtInt("DifferentName", 1); 153 | var compound = new NbtCompound { 154 | new NbtInt("SameName", 1), 155 | tagToRename 156 | }; 157 | 158 | // proper renaming, should not throw 159 | tagToRename.Name = "SomeOtherName"; 160 | 161 | // attempting to use a duplicate name 162 | Assert.Throws(() => tagToRename.Name = "SameName"); 163 | 164 | // assigning a null name to a tag inside a compound; should throw 165 | Assert.Throws(() => tagToRename.Name = null); 166 | 167 | // assigning a null name to a tag that's been removed; should not throw 168 | compound.Remove(tagToRename); 169 | tagToRename.Name = null; 170 | } 171 | 172 | 173 | [Test] 174 | public void AddingAndRemoving() { 175 | var foo = new NbtInt("Foo"); 176 | var test = new NbtCompound { 177 | foo 178 | }; 179 | 180 | // adding duplicate object 181 | Assert.Throws(() => test.Add(foo)); 182 | 183 | // adding duplicate name 184 | Assert.Throws(() => test.Add(new NbtByte("Foo"))); 185 | 186 | // adding unnamed tag 187 | Assert.Throws(() => test.Add(new NbtInt())); 188 | 189 | // adding null 190 | Assert.Throws(() => test.Add(null)); 191 | 192 | // adding tag to self 193 | Assert.Throws(() => test.Add(test)); 194 | 195 | // contains existing name/object 196 | Assert.IsTrue(test.Contains("Foo")); 197 | Assert.IsTrue(test.Contains(foo)); 198 | Assert.Throws(() => test.Contains((string)null)); 199 | Assert.Throws(() => test.Contains((NbtTag)null)); 200 | 201 | // contains non-existent name 202 | Assert.IsFalse(test.Contains("Bar")); 203 | 204 | // contains existing name / different object 205 | Assert.IsFalse(test.Contains(new NbtInt("Foo"))); 206 | 207 | // removing non-existent name 208 | Assert.Throws(() => test.Remove((string)null)); 209 | Assert.IsFalse(test.Remove("Bar")); 210 | 211 | // removing existing name 212 | Assert.IsTrue(test.Remove("Foo")); 213 | 214 | // removing non-existent name 215 | Assert.IsFalse(test.Remove("Foo")); 216 | 217 | // re-adding object 218 | test.Add(foo); 219 | 220 | // removing existing object 221 | Assert.Throws(() => test.Remove((NbtTag)null)); 222 | Assert.IsTrue(test.Remove(foo)); 223 | Assert.IsFalse(test.Remove(foo)); 224 | 225 | // clearing an empty NbtCompound 226 | Assert.AreEqual(0, test.Count); 227 | test.Clear(); 228 | 229 | // re-adding after clearing 230 | test.Add(foo); 231 | Assert.AreEqual(1, test.Count); 232 | 233 | // clearing a non-empty NbtCompound 234 | test.Clear(); 235 | Assert.AreEqual(0, test.Count); 236 | } 237 | 238 | 239 | [Test] 240 | public void UtilityMethods() { 241 | NbtTag[] testThings = { 242 | new NbtShort("Name1", 1), 243 | new NbtInt("Name2", 2), 244 | new NbtLong("Name3", 3) 245 | }; 246 | var compound = new NbtCompound(); 247 | 248 | // add range 249 | compound.AddRange(testThings); 250 | 251 | // add range with duplicates 252 | Assert.Throws(() => compound.AddRange(testThings)); 253 | } 254 | 255 | 256 | [Test] 257 | public void InterfaceImplementations() { 258 | NbtTag[] tagList = { 259 | new NbtByte("First", 1), new NbtShort("Second", 2), new NbtInt("Third", 3), 260 | new NbtLong("Fourth", 4L) 261 | }; 262 | 263 | // test NbtCompound(IEnumerable) constructor 264 | var comp = new NbtCompound(tagList); 265 | 266 | // test .Names and .Tags collections 267 | CollectionAssert.AreEquivalent(new[] { 268 | "First", "Second", "Third", "Fourth" 269 | }, comp.Names); 270 | CollectionAssert.AreEquivalent(tagList, comp.Tags); 271 | 272 | // test ICollection and ICollection boilerplate properties 273 | ICollection iGenCollection = comp; 274 | Assert.IsFalse(iGenCollection.IsReadOnly); 275 | ICollection iCollection = comp; 276 | Assert.NotNull(iCollection.SyncRoot); 277 | Assert.IsFalse(iCollection.IsSynchronized); 278 | 279 | // test CopyTo() 280 | var tags = new NbtTag[iCollection.Count]; 281 | iCollection.CopyTo(tags, 0); 282 | CollectionAssert.AreEquivalent(comp, tags); 283 | 284 | // test non-generic GetEnumerator() 285 | var enumeratedTags = comp.ToList(); 286 | CollectionAssert.AreEquivalent(tagList, enumeratedTags); 287 | 288 | // test generic GetEnumerator() 289 | List enumeratedTags2 = new List(); 290 | var enumerator = comp.GetEnumerator(); 291 | while (enumerator.MoveNext()) { 292 | enumeratedTags2.Add(enumerator.Current); 293 | } 294 | CollectionAssert.AreEquivalent(tagList, enumeratedTags2); 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /fNbt.Test/ListTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using NUnit.Framework; 6 | 7 | namespace fNbt.Test { 8 | [TestFixture] 9 | public sealed class ListTests { 10 | [Test] 11 | public void InterfaceImplementation() { 12 | // prepare our test lists 13 | var referenceList = new List { 14 | new NbtInt(1), 15 | new NbtInt(2), 16 | new NbtInt(3) 17 | }; 18 | var testTag = new NbtInt(4); 19 | var originalList = new NbtList(referenceList); 20 | 21 | // check IList implementation 22 | IList iList = originalList; 23 | CollectionAssert.AreEqual(referenceList, iList); 24 | 25 | // check IList implementation 26 | IList iGenericList = originalList; 27 | CollectionAssert.AreEqual(referenceList, iGenericList); 28 | Assert.IsFalse(iGenericList.IsReadOnly); 29 | 30 | // check IList.Add 31 | referenceList.Add(testTag); 32 | iList.Add(testTag); 33 | CollectionAssert.AreEqual(referenceList, iList); 34 | 35 | // check IList.IndexOf 36 | Assert.AreEqual(referenceList.IndexOf(testTag), iList.IndexOf(testTag)); 37 | Assert.IsTrue(iList.IndexOf(null) < 0); 38 | 39 | // check IList.IndexOf 40 | Assert.AreEqual(referenceList.IndexOf(testTag), iGenericList.IndexOf(testTag)); 41 | Assert.IsTrue(iGenericList.IndexOf(null) < 0); 42 | 43 | // check IList.Contains 44 | Assert.IsTrue(iList.Contains(testTag)); 45 | Assert.IsFalse(iList.Contains(null)); 46 | 47 | // check IList.Remove 48 | iList.Remove(testTag); 49 | Assert.IsFalse(iList.Contains(testTag)); 50 | 51 | // check IList.Insert 52 | iList.Insert(0, testTag); 53 | Assert.AreEqual(0, iList.IndexOf(testTag)); 54 | 55 | // check IList.RemoveAt 56 | iList.RemoveAt(0); 57 | Assert.IsFalse(iList.Contains(testTag)); 58 | 59 | // check misc IList properties 60 | Assert.IsFalse(iList.IsFixedSize); 61 | Assert.IsFalse(iList.IsReadOnly); 62 | Assert.IsFalse(iList.IsSynchronized); 63 | Assert.NotNull(iList.SyncRoot); 64 | 65 | // check IList.CopyTo 66 | var exportTest = new NbtInt[iList.Count]; 67 | iList.CopyTo(exportTest, 0); 68 | CollectionAssert.AreEqual(iList, exportTest); 69 | 70 | // check IList.this[int] 71 | for (int i = 0; i < iList.Count; i++) { 72 | Assert.AreEqual(originalList[i], iList[i]); 73 | iList[i] = new NbtInt(i); 74 | } 75 | 76 | // check IList.Clear 77 | iList.Clear(); 78 | Assert.AreEqual(0, iList.Count); 79 | Assert.Less(iList.IndexOf(testTag), 0); 80 | } 81 | 82 | 83 | [Test] 84 | public void IndexerTest() { 85 | NbtByte ourTag = new NbtByte(1); 86 | var secondList = new NbtList { 87 | new NbtByte() 88 | }; 89 | 90 | var testList = new NbtList(); 91 | // Trying to set an out-of-range element 92 | Assert.Throws(() => testList[0] = new NbtByte(1)); 93 | 94 | // Make sure that setting did not affect ListType 95 | Assert.AreEqual(NbtTagType.Unknown, testList.ListType); 96 | Assert.AreEqual(0, testList.Count); 97 | testList.Add(ourTag); 98 | 99 | // set a tag to null 100 | Assert.Throws(() => testList[0] = null); 101 | 102 | // set a tag to itself 103 | Assert.Throws(() => testList[0] = testList); 104 | 105 | // give a named tag where an unnamed tag was expected 106 | Assert.Throws(() => testList[0] = new NbtByte("NamedTag")); 107 | 108 | // give a tag of wrong type 109 | Assert.Throws(() => testList[0] = new NbtInt(0)); 110 | 111 | // give an unnamed tag that already has a parent 112 | Assert.Throws(() => testList[0] = secondList[0]); 113 | 114 | // Make sure that none of the failed insertions went through 115 | Assert.AreEqual(ourTag, testList[0]); 116 | } 117 | 118 | 119 | [Test] 120 | public void InitializingListFromCollection() { 121 | // auto-detecting list type 122 | var test1 = new NbtList("Test1", new NbtTag[] { 123 | new NbtInt(1), 124 | new NbtInt(2), 125 | new NbtInt(3) 126 | }); 127 | Assert.AreEqual(NbtTagType.Int, test1.ListType); 128 | 129 | // check pre-conditions 130 | Assert.Throws(() => new NbtList((NbtTag[])null)); 131 | Assert.Throws(() => new NbtList(null, null)); 132 | Assert.DoesNotThrow(() => new NbtList((string)null, NbtTagType.Unknown)); 133 | Assert.Throws(() => new NbtList((NbtTag[])null, NbtTagType.Unknown)); 134 | 135 | // correct explicitly-given list type 136 | Assert.DoesNotThrow(() => new NbtList("Test2", new NbtTag[] { 137 | new NbtInt(1), 138 | new NbtInt(2), 139 | new NbtInt(3) 140 | }, NbtTagType.Int)); 141 | 142 | // wrong explicitly-given list type 143 | Assert.Throws(() => new NbtList("Test3", new NbtTag[] { 144 | new NbtInt(1), 145 | new NbtInt(2), 146 | new NbtInt(3) 147 | }, NbtTagType.Float)); 148 | 149 | // auto-detecting mixed list given 150 | Assert.Throws(() => new NbtList("Test4", new NbtTag[] { 151 | new NbtFloat(1), 152 | new NbtByte(2), 153 | new NbtInt(3) 154 | })); 155 | 156 | // using AddRange 157 | Assert.DoesNotThrow(() => new NbtList().AddRange(new NbtTag[] { 158 | new NbtInt(1), 159 | new NbtInt(2), 160 | new NbtInt(3) 161 | })); 162 | Assert.Throws(() => new NbtList().AddRange(null)); 163 | } 164 | 165 | 166 | [Test] 167 | public void ManipulatingList() { 168 | var sameTags = new NbtTag[] { 169 | new NbtInt(0), 170 | new NbtInt(1), 171 | new NbtInt(2) 172 | }; 173 | 174 | var list = new NbtList("Test1", sameTags); 175 | 176 | // testing enumerator, indexer, Contains, and IndexOf 177 | int j = 0; 178 | foreach (NbtTag tag in list) { 179 | Assert.IsTrue(list.Contains(sameTags[j])); 180 | Assert.AreEqual(sameTags[j], tag); 181 | Assert.AreEqual(j, list.IndexOf(tag)); 182 | j++; 183 | } 184 | 185 | // adding an item of correct type 186 | list.Add(new NbtInt(3)); 187 | list.Insert(3, new NbtInt(4)); 188 | 189 | // adding an item of wrong type 190 | Assert.Throws(() => list.Add(new NbtString())); 191 | Assert.Throws(() => list.Insert(3, new NbtString())); 192 | Assert.Throws(() => list.Insert(3, null)); 193 | 194 | // testing array contents 195 | for (int i = 0; i < sameTags.Length; i++) { 196 | Assert.AreSame(sameTags[i], list[i]); 197 | Assert.AreEqual(i, ((NbtInt)list[i]).Value); 198 | } 199 | 200 | // test removal 201 | Assert.IsFalse(list.Remove(new NbtInt(5))); 202 | Assert.IsTrue(list.Remove(sameTags[0])); 203 | Assert.Throws(() => list.Remove(null)); 204 | list.RemoveAt(0); 205 | Assert.Throws(() => list.RemoveAt(10)); 206 | 207 | // Test some failure scenarios for Add: 208 | // adding a list to itself 209 | var loopList = new NbtList(); 210 | Assert.AreEqual(NbtTagType.Unknown, loopList.ListType); 211 | Assert.Throws(() => loopList.Add(loopList)); 212 | 213 | // adding same tag to multiple lists 214 | Assert.Throws(() => loopList.Add(list[0])); 215 | Assert.Throws(() => loopList.Insert(0, list[0])); 216 | 217 | // adding null tag 218 | Assert.Throws(() => loopList.Add(null)); 219 | 220 | // make sure that all those failed adds didn't affect the tag 221 | Assert.AreEqual(0, loopList.Count); 222 | Assert.AreEqual(NbtTagType.Unknown, loopList.ListType); 223 | 224 | // try creating a list with invalid tag type 225 | Assert.Throws(() => new NbtList((NbtTagType)200)); 226 | } 227 | 228 | 229 | [Test] 230 | public void ChangingListTagType() { 231 | var list = new NbtList(); 232 | 233 | // changing list type to an out-of-range type 234 | Assert.Throws(() => list.ListType = (NbtTagType)200); 235 | 236 | // failing to add or insert a tag should not change ListType 237 | Assert.Throws(() => list.Insert(-1, new NbtInt())); 238 | Assert.Throws(() => list.Add(new NbtInt("namedTagWhereUnnamedIsExpected"))); 239 | Assert.AreEqual(NbtTagType.Unknown, list.ListType); 240 | 241 | // changing the type of an empty list to "End" is allowed, see https://github.com/fragmer/fNbt/issues/12 242 | Assert.DoesNotThrow(() => list.ListType = NbtTagType.End); 243 | Assert.AreEqual(list.ListType, NbtTagType.End); 244 | 245 | // changing the type of an empty list back to "Unknown" is allowed too! 246 | Assert.DoesNotThrow(() => list.ListType = NbtTagType.Unknown); 247 | Assert.AreEqual(list.ListType, NbtTagType.Unknown); 248 | 249 | // adding the first element should set the tag type 250 | list.Add(new NbtInt()); 251 | Assert.AreEqual(list.ListType, NbtTagType.Int); 252 | 253 | // setting correct type for a non-empty list 254 | Assert.DoesNotThrow(() => list.ListType = NbtTagType.Int); 255 | 256 | // changing list type to an incorrect type 257 | Assert.Throws(() => list.ListType = NbtTagType.Short); 258 | 259 | // after the list is cleared, we should once again be allowed to change its TagType 260 | list.Clear(); 261 | Assert.DoesNotThrow(() => list.ListType = NbtTagType.Short); 262 | } 263 | 264 | 265 | [Test] 266 | public void SerializingWithoutListType() { 267 | var root = new NbtCompound("root") { 268 | new NbtList("list") 269 | }; 270 | var file = new NbtFile(root); 271 | 272 | using (var ms = new MemoryStream()) { 273 | // list should throw NbtFormatException, because its ListType is Unknown 274 | Assert.Throws(() => file.SaveToStream(ms, NbtCompression.None)); 275 | } 276 | } 277 | 278 | 279 | [Test] 280 | public void Serializing1() { 281 | // check the basics of saving/loading 282 | const NbtTagType expectedListType = NbtTagType.Int; 283 | const int elements = 10; 284 | 285 | // construct nbt file 286 | var writtenFile = new NbtFile(new NbtCompound("ListTypeTest")); 287 | var writtenList = new NbtList("Entities", null, expectedListType); 288 | for (int i = 0; i < elements; i++) { 289 | writtenList.Add(new NbtInt(i)); 290 | } 291 | writtenFile.RootTag.Add(writtenList); 292 | 293 | // test saving 294 | byte[] data = writtenFile.SaveToBuffer(NbtCompression.None); 295 | 296 | // test loading 297 | var readFile = new NbtFile(); 298 | long bytesRead = readFile.LoadFromBuffer(data, 0, data.Length, NbtCompression.None); 299 | Assert.AreEqual(bytesRead, data.Length); 300 | 301 | // check contents of loaded file 302 | Assert.NotNull(readFile.RootTag); 303 | Assert.IsInstanceOf(readFile.RootTag["Entities"]); 304 | var readList = (NbtList)readFile.RootTag["Entities"]; 305 | Assert.AreEqual(writtenList.ListType, readList.ListType); 306 | Assert.AreEqual(readList.Count, writtenList.Count); 307 | 308 | // check .ToArray 309 | CollectionAssert.AreEquivalent(readList, readList.ToArray()); 310 | CollectionAssert.AreEquivalent(readList, readList.ToArray()); 311 | 312 | // check contents of loaded list 313 | for (int i = 0; i < elements; i++) { 314 | Assert.AreEqual(readList.Get(i).Value, writtenList.Get(i).Value); 315 | } 316 | } 317 | 318 | 319 | [Test] 320 | public void Serializing2() { 321 | // check saving/loading lists of all possible value types 322 | var testFile = new NbtFile(TestFiles.MakeListTest()); 323 | byte[] buffer = testFile.SaveToBuffer(NbtCompression.None); 324 | long bytesRead = testFile.LoadFromBuffer(buffer, 0, buffer.Length, NbtCompression.None); 325 | Assert.AreEqual(bytesRead, buffer.Length); 326 | } 327 | 328 | 329 | [Test] 330 | public void SerializingEmpty() { 331 | // check saving/loading lists of all possible value types 332 | var testFile = new NbtFile(new NbtCompound("root") { 333 | new NbtList("emptyList", NbtTagType.End), 334 | new NbtList("listyList", NbtTagType.List) { 335 | new NbtList(NbtTagType.End) 336 | } 337 | }); 338 | byte[] buffer = testFile.SaveToBuffer(NbtCompression.None); 339 | 340 | testFile.LoadFromBuffer(buffer, 0, buffer.Length, NbtCompression.None); 341 | 342 | NbtList list1 = testFile.RootTag.Get("emptyList"); 343 | Assert.AreEqual(list1.Count, 0); 344 | Assert.AreEqual(list1.ListType, NbtTagType.End); 345 | 346 | NbtList list2 = testFile.RootTag.Get("listyList"); 347 | Assert.AreEqual(list2.Count, 1); 348 | Assert.AreEqual(list2.ListType, NbtTagType.List); 349 | Assert.AreEqual(list2.Get(0).Count, 0); 350 | Assert.AreEqual(list2.Get(0).ListType, NbtTagType.End); 351 | } 352 | 353 | 354 | [Test] 355 | public void NestedListAndCompoundTest() { 356 | byte[] data; 357 | { 358 | var root = new NbtCompound("Root"); 359 | var outerList = new NbtList("OuterList", NbtTagType.Compound); 360 | var outerCompound = new NbtCompound(); 361 | var innerList = new NbtList("InnerList", NbtTagType.Compound); 362 | var innerCompound = new NbtCompound(); 363 | 364 | innerList.Add(innerCompound); 365 | outerCompound.Add(innerList); 366 | outerList.Add(outerCompound); 367 | root.Add(outerList); 368 | 369 | var file = new NbtFile(root); 370 | data = file.SaveToBuffer(NbtCompression.None); 371 | } 372 | { 373 | var file = new NbtFile(); 374 | long bytesRead = file.LoadFromBuffer(data, 0, data.Length, NbtCompression.None); 375 | Assert.AreEqual(bytesRead, data.Length); 376 | Assert.AreEqual(1, file.RootTag.Get("OuterList").Count); 377 | Assert.AreEqual(null, file.RootTag.Get("OuterList").Get(0).Name); 378 | Assert.AreEqual(1, 379 | file.RootTag.Get("OuterList") 380 | .Get(0) 381 | .Get("InnerList") 382 | .Count); 383 | Assert.AreEqual(null, 384 | file.RootTag.Get("OuterList") 385 | .Get(0) 386 | .Get("InnerList") 387 | .Get(0) 388 | .Name); 389 | } 390 | } 391 | 392 | 393 | [Test] 394 | public void FirstInsertTest() { 395 | NbtList list = new NbtList(); 396 | Assert.AreEqual(NbtTagType.Unknown, list.ListType); 397 | list.Insert(0, new NbtInt(123)); 398 | // Inserting a tag should set ListType 399 | Assert.AreEqual(NbtTagType.Int, list.ListType); 400 | } 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /fNbt.Test/MiscTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace fNbt.Test { 5 | [TestFixture] 6 | public class MiscTests { 7 | [Test] 8 | public void CopyConstructorTest() { 9 | NbtByte byteTag = new NbtByte("byteTag", 1); 10 | NbtByte byteTagClone = (NbtByte)byteTag.Clone(); 11 | Assert.AreNotSame(byteTag, byteTagClone); 12 | Assert.AreEqual(byteTag.Name, byteTagClone.Name); 13 | Assert.AreEqual(byteTag.Value, byteTagClone.Value); 14 | Assert.Throws(() => new NbtByte((NbtByte)null)); 15 | 16 | NbtByteArray byteArrTag = new NbtByteArray("byteArrTag", new byte[] { 1, 2, 3, 4 }); 17 | NbtByteArray byteArrTagClone = (NbtByteArray)byteArrTag.Clone(); 18 | Assert.AreNotSame(byteArrTag, byteArrTagClone); 19 | Assert.AreEqual(byteArrTag.Name, byteArrTagClone.Name); 20 | Assert.AreNotSame(byteArrTag.Value, byteArrTagClone.Value); 21 | CollectionAssert.AreEqual(byteArrTag.Value, byteArrTagClone.Value); 22 | Assert.Throws(() => new NbtByteArray((NbtByteArray)null)); 23 | 24 | NbtCompound compTag = new NbtCompound("compTag", new NbtTag[] { new NbtByte("innerTag", 1) }); 25 | NbtCompound compTagClone = (NbtCompound)compTag.Clone(); 26 | Assert.AreNotSame(compTag, compTagClone); 27 | Assert.AreEqual(compTag.Name, compTagClone.Name); 28 | Assert.AreNotSame(compTag["innerTag"], compTagClone["innerTag"]); 29 | Assert.AreEqual(compTag["innerTag"].Name, compTagClone["innerTag"].Name); 30 | Assert.AreEqual(compTag["innerTag"].ByteValue, compTagClone["innerTag"].ByteValue); 31 | Assert.Throws(() => new NbtCompound((NbtCompound)null)); 32 | 33 | NbtDouble doubleTag = new NbtDouble("doubleTag", 1); 34 | NbtDouble doubleTagClone = (NbtDouble)doubleTag.Clone(); 35 | Assert.AreNotSame(doubleTag, doubleTagClone); 36 | Assert.AreEqual(doubleTag.Name, doubleTagClone.Name); 37 | Assert.AreEqual(doubleTag.Value, doubleTagClone.Value); 38 | Assert.Throws(() => new NbtDouble((NbtDouble)null)); 39 | 40 | NbtFloat floatTag = new NbtFloat("floatTag", 1); 41 | NbtFloat floatTagClone = (NbtFloat)floatTag.Clone(); 42 | Assert.AreNotSame(floatTag, floatTagClone); 43 | Assert.AreEqual(floatTag.Name, floatTagClone.Name); 44 | Assert.AreEqual(floatTag.Value, floatTagClone.Value); 45 | Assert.Throws(() => new NbtFloat((NbtFloat)null)); 46 | 47 | NbtInt intTag = new NbtInt("intTag", 1); 48 | NbtInt intTagClone = (NbtInt)intTag.Clone(); 49 | Assert.AreNotSame(intTag, intTagClone); 50 | Assert.AreEqual(intTag.Name, intTagClone.Name); 51 | Assert.AreEqual(intTag.Value, intTagClone.Value); 52 | Assert.Throws(() => new NbtInt((NbtInt)null)); 53 | 54 | NbtIntArray intArrTag = new NbtIntArray("intArrTag", new[] { 1, 2, 3, 4 }); 55 | NbtIntArray intArrTagClone = (NbtIntArray)intArrTag.Clone(); 56 | Assert.AreNotSame(intArrTag, intArrTagClone); 57 | Assert.AreEqual(intArrTag.Name, intArrTagClone.Name); 58 | Assert.AreNotSame(intArrTag.Value, intArrTagClone.Value); 59 | CollectionAssert.AreEqual(intArrTag.Value, intArrTagClone.Value); 60 | Assert.Throws(() => new NbtIntArray((NbtIntArray)null)); 61 | 62 | NbtLongArray longArrTag = new NbtLongArray("longArrTag", new long[] { 1, 2, 3, 4 }); 63 | NbtLongArray longArrTagClone = (NbtLongArray)longArrTag.Clone(); 64 | Assert.AreNotSame(longArrTag, longArrTagClone); 65 | Assert.AreEqual(longArrTag.Name, longArrTagClone.Name); 66 | Assert.AreNotSame(longArrTag.Value, longArrTagClone.Value); 67 | CollectionAssert.AreEqual(longArrTag.Value, longArrTagClone.Value); 68 | Assert.Throws(() => new NbtLongArray((NbtLongArray)null)); 69 | 70 | NbtList listTag = new NbtList("listTag", new NbtTag[] { new NbtByte(1) }); 71 | NbtList listTagClone = (NbtList)listTag.Clone(); 72 | Assert.AreNotSame(listTag, listTagClone); 73 | Assert.AreEqual(listTag.Name, listTagClone.Name); 74 | Assert.AreNotSame(listTag[0], listTagClone[0]); 75 | Assert.AreEqual(listTag[0].ByteValue, listTagClone[0].ByteValue); 76 | Assert.Throws(() => new NbtList((NbtList)null)); 77 | 78 | NbtLong longTag = new NbtLong("longTag", 1); 79 | NbtLong longTagClone = (NbtLong)longTag.Clone(); 80 | Assert.AreNotSame(longTag, longTagClone); 81 | Assert.AreEqual(longTag.Name, longTagClone.Name); 82 | Assert.AreEqual(longTag.Value, longTagClone.Value); 83 | Assert.Throws(() => new NbtLong((NbtLong)null)); 84 | 85 | NbtShort shortTag = new NbtShort("shortTag", 1); 86 | NbtShort shortTagClone = (NbtShort)shortTag.Clone(); 87 | Assert.AreNotSame(shortTag, shortTagClone); 88 | Assert.AreEqual(shortTag.Name, shortTagClone.Name); 89 | Assert.AreEqual(shortTag.Value, shortTagClone.Value); 90 | Assert.Throws(() => new NbtShort((NbtShort)null)); 91 | 92 | NbtString stringTag = new NbtString("stringTag", "foo"); 93 | NbtString stringTagClone = (NbtString)stringTag.Clone(); 94 | Assert.AreNotSame(stringTag, stringTagClone); 95 | Assert.AreEqual(stringTag.Name, stringTagClone.Name); 96 | Assert.AreEqual(stringTag.Value, stringTagClone.Value); 97 | Assert.Throws(() => new NbtString((NbtString)null)); 98 | } 99 | 100 | 101 | [Test] 102 | public void ByteArrayIndexerTest() { 103 | // test getting/settings values of byte array tag via indexer 104 | var byteArray = new NbtByteArray("Test"); 105 | CollectionAssert.AreEqual(new byte[0], byteArray.Value); 106 | byteArray.Value = new byte[] { 107 | 1, 2, 3 108 | }; 109 | Assert.AreEqual(1, byteArray[0]); 110 | Assert.AreEqual(2, byteArray[1]); 111 | Assert.AreEqual(3, byteArray[2]); 112 | byteArray[0] = 4; 113 | Assert.AreEqual(4, byteArray[0]); 114 | } 115 | 116 | 117 | [Test] 118 | public void IntArrayIndexerTest() { 119 | // test getting/settings values of int array tag via indexer 120 | var intArray = new NbtIntArray("Test"); 121 | CollectionAssert.AreEqual(new int[0], intArray.Value); 122 | intArray.Value = new[] { 123 | 1, 2000, -3000000 124 | }; 125 | Assert.AreEqual(1, intArray[0]); 126 | Assert.AreEqual(2000, intArray[1]); 127 | Assert.AreEqual(-3000000, intArray[2]); 128 | intArray[0] = 4; 129 | Assert.AreEqual(4, intArray[0]); 130 | } 131 | 132 | 133 | [Test] 134 | public void LongArrayIndexerTest() { 135 | var longArray = new NbtLongArray("Test"); 136 | CollectionAssert.AreEqual(new long[0], longArray.Value); 137 | longArray.Value = new[] { 138 | 1, 139 | Int64.MaxValue, 140 | Int64.MinValue 141 | }; 142 | Assert.AreEqual(1, longArray[0]); 143 | Assert.AreEqual(Int64.MaxValue, longArray[1]); 144 | Assert.AreEqual(Int64.MinValue, longArray[2]); 145 | longArray[0] = 4; 146 | Assert.AreEqual(4, longArray[0]); 147 | } 148 | 149 | 150 | [Test] 151 | public void DefaultValueTest() { 152 | // test default values of all value tags 153 | Assert.AreEqual(0, new NbtByte("test").Value); 154 | CollectionAssert.AreEqual(new byte[0], new NbtByteArray("test").Value); 155 | Assert.AreEqual(0d, new NbtDouble("test").Value); 156 | Assert.AreEqual(0f, new NbtFloat("test").Value); 157 | Assert.AreEqual(0, new NbtInt("test").Value); 158 | CollectionAssert.AreEqual(new int[0], new NbtIntArray("test").Value); 159 | CollectionAssert.AreEqual(new long[0], new NbtLongArray("test").Value); 160 | Assert.AreEqual(0L, new NbtLong("test").Value); 161 | Assert.AreEqual(0, new NbtShort("test").Value); 162 | Assert.AreEqual("", new NbtString().Value); 163 | } 164 | 165 | 166 | [Test] 167 | public void NullValueTest() { 168 | Assert.Throws(() => new NbtByteArray().Value = null); 169 | Assert.Throws(() => new NbtIntArray().Value = null); 170 | Assert.Throws(() => new NbtLongArray().Value = null); 171 | Assert.Throws(() => new NbtString().Value = null); 172 | } 173 | 174 | 175 | [Test] 176 | public void NbtTagNameTest() { 177 | Assert.AreEqual("TAG_End", NbtTag.GetCanonicalTagName(NbtTagType.End)); 178 | Assert.IsNull(NbtTag.GetCanonicalTagName((NbtTagType)255)); 179 | } 180 | 181 | 182 | [Test] 183 | public void PathTest() { 184 | // test NbtTag.Path property 185 | var testComp = new NbtCompound { 186 | new NbtCompound("Compound") { 187 | new NbtCompound("InsideCompound") 188 | }, 189 | new NbtList("List") { 190 | new NbtCompound { 191 | new NbtInt("InsideCompoundAndList") 192 | } 193 | } 194 | }; 195 | 196 | // parent-less tag with no name has empty string for a path 197 | Assert.AreEqual("", testComp.Path); 198 | Assert.AreEqual(".Compound", testComp["Compound"].Path); 199 | Assert.AreEqual(".Compound.InsideCompound", testComp["Compound"]["InsideCompound"].Path); 200 | Assert.AreEqual(".List", testComp["List"].Path); 201 | 202 | // tags inside lists have no name, but they do have an index 203 | Assert.AreEqual(".List[0]", testComp["List"][0].Path); 204 | Assert.AreEqual(".List[0].InsideCompoundAndList", testComp["List"][0]["InsideCompoundAndList"].Path); 205 | } 206 | 207 | 208 | [Test] 209 | public void BadParamsTest() { 210 | Assert.Throws(() => new NbtByteArray((byte[])null)); 211 | Assert.Throws(() => new NbtIntArray((int[])null)); 212 | Assert.Throws(() => new NbtLongArray((long[])null)); 213 | Assert.Throws(() => new NbtString((string)null)); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /fNbt.Test/NbtFileTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using NUnit.Framework; 4 | 5 | namespace fNbt.Test { 6 | [TestFixture] 7 | public class NbtFileTests { 8 | const string TestDirName = "NbtFileTests"; 9 | 10 | 11 | [SetUp] 12 | public void NbtFileTestSetup() { 13 | Directory.CreateDirectory(TestDirName); 14 | } 15 | 16 | 17 | #region Loading Small Nbt Test File 18 | 19 | [Test] 20 | public void TestNbtSmallFileLoadingUncompressed() { 21 | var file = new NbtFile(TestFiles.Small); 22 | Assert.AreEqual(TestFiles.Small, file.FileName); 23 | Assert.AreEqual(NbtCompression.None, file.FileCompression); 24 | TestFiles.AssertNbtSmallFile(file); 25 | } 26 | 27 | 28 | [Test] 29 | public void LoadingSmallFileGZip() { 30 | var file = new NbtFile(TestFiles.SmallGZip); 31 | Assert.AreEqual(TestFiles.SmallGZip, file.FileName); 32 | Assert.AreEqual(NbtCompression.GZip, file.FileCompression); 33 | TestFiles.AssertNbtSmallFile(file); 34 | } 35 | 36 | 37 | [Test] 38 | public void LoadingSmallFileZLib() { 39 | var file = new NbtFile(TestFiles.SmallZLib); 40 | Assert.AreEqual(TestFiles.SmallZLib, file.FileName); 41 | Assert.AreEqual(NbtCompression.ZLib, file.FileCompression); 42 | TestFiles.AssertNbtSmallFile(file); 43 | } 44 | 45 | #endregion 46 | 47 | 48 | #region Loading Big Nbt Test File 49 | 50 | [Test] 51 | public void LoadingBigFileUncompressed() { 52 | var file = new NbtFile(); 53 | long length = file.LoadFromFile(TestFiles.Big); 54 | TestFiles.AssertNbtBigFile(file); 55 | Assert.AreEqual(length, new FileInfo(TestFiles.Big).Length); 56 | } 57 | 58 | 59 | [Test] 60 | public void LoadingBigFileGZip() { 61 | var file = new NbtFile(); 62 | long length = file.LoadFromFile(TestFiles.BigGZip); 63 | TestFiles.AssertNbtBigFile(file); 64 | Assert.AreEqual(length, new FileInfo(TestFiles.BigGZip).Length); 65 | } 66 | 67 | 68 | [Test] 69 | public void LoadingBigFileZLib() { 70 | var file = new NbtFile(); 71 | long length = file.LoadFromFile(TestFiles.BigZLib); 72 | TestFiles.AssertNbtBigFile(file); 73 | Assert.AreEqual(length, new FileInfo(TestFiles.BigZLib).Length); 74 | } 75 | 76 | 77 | [Test] 78 | public void LoadingBigFileBuffer() { 79 | byte[] fileBytes = File.ReadAllBytes(TestFiles.Big); 80 | var file = new NbtFile(); 81 | 82 | Assert.Throws( 83 | () => file.LoadFromBuffer(null, 0, fileBytes.Length, NbtCompression.AutoDetect, null)); 84 | 85 | long length = file.LoadFromBuffer(fileBytes, 0, fileBytes.Length, NbtCompression.AutoDetect, null); 86 | TestFiles.AssertNbtBigFile(file); 87 | Assert.AreEqual(length, new FileInfo(TestFiles.Big).Length); 88 | } 89 | 90 | 91 | [Test] 92 | public void LoadingBigFileStream() { 93 | byte[] fileBytes = File.ReadAllBytes(TestFiles.Big); 94 | using (var ms = new MemoryStream(fileBytes)) { 95 | using (var nss = new NonSeekableStream(ms)) { 96 | var file = new NbtFile(); 97 | long length = file.LoadFromStream(nss, NbtCompression.None, null); 98 | TestFiles.AssertNbtBigFile(file); 99 | Assert.AreEqual(length, new FileInfo(TestFiles.Big).Length); 100 | } 101 | } 102 | } 103 | 104 | #endregion 105 | 106 | 107 | [Test] 108 | public void TestNbtSmallFileSavingUncompressed() { 109 | NbtFile file = TestFiles.MakeSmallFile(); 110 | string testFileName = Path.Combine(TestDirName, "test.nbt"); 111 | file.SaveToFile(testFileName, NbtCompression.None); 112 | FileAssert.AreEqual(TestFiles.Small, testFileName); 113 | } 114 | 115 | 116 | [Test] 117 | public void TestNbtSmallFileSavingUncompressedStream() { 118 | NbtFile file = TestFiles.MakeSmallFile(); 119 | var nbtStream = new MemoryStream(); 120 | Assert.Throws(() => file.SaveToStream(null, NbtCompression.None)); 121 | Assert.Throws(() => file.SaveToStream(nbtStream, NbtCompression.AutoDetect)); 122 | Assert.Throws(() => file.SaveToStream(nbtStream, (NbtCompression)255)); 123 | file.SaveToStream(nbtStream, NbtCompression.None); 124 | FileStream testFileStream = File.OpenRead(TestFiles.Small); 125 | FileAssert.AreEqual(testFileStream, nbtStream); 126 | } 127 | 128 | 129 | [Test] 130 | public void ReloadFile() { 131 | ReloadFileInternal("bigtest.nbt", NbtCompression.None, true, true); 132 | ReloadFileInternal("bigtest.nbt.gz", NbtCompression.GZip, true, true); 133 | ReloadFileInternal("bigtest.nbt.z", NbtCompression.ZLib, true, true); 134 | ReloadFileInternal("bigtest.nbt", NbtCompression.None, false, true); 135 | ReloadFileInternal("bigtest.nbt.gz", NbtCompression.GZip, false, true); 136 | ReloadFileInternal("bigtest.nbt.z", NbtCompression.ZLib, false, true); 137 | } 138 | 139 | 140 | [Test] 141 | public void ReloadFileUnbuffered() { 142 | ReloadFileInternal("bigtest.nbt", NbtCompression.None, true, false); 143 | ReloadFileInternal("bigtest.nbt.gz", NbtCompression.GZip, true, false); 144 | ReloadFileInternal("bigtest.nbt.z", NbtCompression.ZLib, true, false); 145 | ReloadFileInternal("bigtest.nbt", NbtCompression.None, false, false); 146 | ReloadFileInternal("bigtest.nbt.gz", NbtCompression.GZip, false, false); 147 | ReloadFileInternal("bigtest.nbt.z", NbtCompression.ZLib, false, false); 148 | } 149 | 150 | 151 | void ReloadFileInternal(String fileName, NbtCompression compression, bool bigEndian, bool buffered) { 152 | var loadedFile = new NbtFile(Path.Combine(TestFiles.DirName, fileName)); 153 | loadedFile.Flavor = new NbtFlavor(bigEndian); // Change after loading 154 | if (!buffered) { 155 | loadedFile.BufferSize = 0; 156 | } 157 | long bytesWritten = loadedFile.SaveToFile(Path.Combine(TestDirName, fileName), compression); 158 | long bytesRead = loadedFile.LoadFromFile(Path.Combine(TestDirName, fileName), NbtCompression.AutoDetect, 159 | null); 160 | Assert.AreEqual(bytesWritten, bytesRead); 161 | TestFiles.AssertNbtBigFile(loadedFile); 162 | } 163 | 164 | 165 | [Test] 166 | public void ReloadNonSeekableStream() { 167 | var loadedFile = new NbtFile(TestFiles.Big); 168 | using (var ms = new MemoryStream()) { 169 | using (var nss = new NonSeekableStream(ms)) { 170 | long bytesWritten = loadedFile.SaveToStream(nss, NbtCompression.None); 171 | ms.Position = 0; 172 | Assert.Throws(() => loadedFile.LoadFromStream(nss, NbtCompression.AutoDetect)); 173 | ms.Position = 0; 174 | Assert.Throws(() => loadedFile.LoadFromStream(nss, NbtCompression.ZLib)); 175 | ms.Position = 0; 176 | long bytesRead = loadedFile.LoadFromStream(nss, NbtCompression.None); 177 | Assert.AreEqual(bytesWritten, bytesRead); 178 | TestFiles.AssertNbtBigFile(loadedFile); 179 | } 180 | } 181 | } 182 | 183 | 184 | [Test] 185 | public void LoadFromStream() { 186 | LoadFromStreamInternal(TestFiles.Big, NbtCompression.None); 187 | LoadFromStreamInternal(TestFiles.BigGZip, NbtCompression.GZip); 188 | LoadFromStreamInternal(TestFiles.BigZLib, NbtCompression.ZLib); 189 | } 190 | 191 | 192 | void LoadFromStreamInternal(String fileName, NbtCompression compression) { 193 | var file = new NbtFile(); 194 | byte[] fileBytes = File.ReadAllBytes(fileName); 195 | using (var ms = new MemoryStream(fileBytes)) { 196 | file.LoadFromStream(ms, compression); 197 | } 198 | } 199 | 200 | 201 | [Test] 202 | public void SaveToBuffer() { 203 | var littleTag = new NbtCompound("Root"); 204 | var testFile = new NbtFile(littleTag); 205 | 206 | byte[] buffer1 = testFile.SaveToBuffer(NbtCompression.None); 207 | var buffer2 = new byte[buffer1.Length]; 208 | Assert.AreEqual(testFile.SaveToBuffer(buffer2, 0, NbtCompression.None), buffer2.Length); 209 | CollectionAssert.AreEqual(buffer1, buffer2); 210 | } 211 | 212 | 213 | [Test] 214 | public void PrettyPrint() { 215 | var loadedFile = new NbtFile(TestFiles.Big); 216 | Assert.AreEqual(loadedFile.RootTag.ToString(), loadedFile.ToString()); 217 | Assert.AreEqual(loadedFile.RootTag.ToString(" "), loadedFile.ToString(" ")); 218 | Assert.Throws(() => loadedFile.ToString(null)); 219 | Assert.Throws(() => NbtTag.DefaultIndentString = null); 220 | } 221 | 222 | 223 | [Test] 224 | public void ReadRootTag() { 225 | Assert.Throws(() => NbtFile.ReadRootTagName("NonExistentFile")); 226 | 227 | ReadRootTagInternal(TestFiles.Big, NbtCompression.None); 228 | ReadRootTagInternal(TestFiles.BigGZip, NbtCompression.GZip); 229 | ReadRootTagInternal(TestFiles.BigZLib, NbtCompression.ZLib); 230 | } 231 | 232 | 233 | void ReadRootTagInternal(String fileName, NbtCompression compression) { 234 | Assert.Throws(() => NbtFile.ReadRootTagName(fileName, compression, true, -1)); 235 | Assert.Throws(() => NbtFile.ReadRootTagName(fileName, (NbtCompression)255, true, 0)); 236 | 237 | Assert.AreEqual("Level", NbtFile.ReadRootTagName(fileName)); 238 | Assert.AreEqual("Level", NbtFile.ReadRootTagName(fileName, compression, true, 0)); 239 | 240 | byte[] fileBytes = File.ReadAllBytes(fileName); 241 | using (var ms = new MemoryStream(fileBytes)) { 242 | using (var nss = new NonSeekableStream(ms)) { 243 | Assert.Throws( 244 | () => NbtFile.ReadRootTagName(nss, compression, true, -1)); 245 | NbtFile.ReadRootTagName(nss, compression, true, 0); 246 | } 247 | } 248 | } 249 | 250 | 251 | [Test] 252 | public void GlobalsTest() { 253 | Assert.AreEqual(NbtFile.DefaultBufferSize, new NbtFile(new NbtCompound("Foo")).BufferSize); 254 | Assert.Throws(() => NbtFile.DefaultBufferSize = -1); 255 | NbtFile.DefaultBufferSize = 12345; 256 | Assert.AreEqual(12345, NbtFile.DefaultBufferSize); 257 | 258 | // Newly-created NbtFiles should use default buffer size 259 | NbtFile tempFile = new NbtFile(new NbtCompound("Foo")); 260 | Assert.AreEqual(NbtFile.DefaultBufferSize, tempFile.BufferSize); 261 | Assert.Throws(() => tempFile.BufferSize = -1); 262 | tempFile.BufferSize = 54321; 263 | Assert.AreEqual(54321, tempFile.BufferSize); 264 | 265 | // Changing default buffer size should not retroactively change already-existing NbtFiles' buffer size. 266 | NbtFile.DefaultBufferSize = 8192; 267 | Assert.AreEqual(54321, tempFile.BufferSize); 268 | } 269 | 270 | 271 | [Test] 272 | public void HugeNbtFileTest() { 273 | // Tests writing byte arrays that exceed the max NbtBinaryWriter chunk size 274 | byte[] val = new byte[5 * 1024 * 1024]; 275 | NbtCompound root = new NbtCompound("root") { 276 | new NbtByteArray("payload1") { 277 | Value = val 278 | } 279 | }; 280 | NbtFile file = new NbtFile(root); 281 | file.SaveToStream(Stream.Null, NbtCompression.None); 282 | } 283 | 284 | 285 | [Test] 286 | public void RootTagTest() { 287 | NbtCompound oldRoot = new NbtCompound("defaultRoot"); 288 | NbtFile newFile = new NbtFile(oldRoot); 289 | 290 | // Ensure that inappropriate tags are not accepted as RootTag 291 | Assert.Throws(() => newFile.RootTag = null); 292 | Assert.Throws(() => newFile.RootTag = new NbtCompound()); 293 | 294 | // Ensure that the root has not changed 295 | Assert.AreSame(oldRoot, newFile.RootTag); 296 | 297 | // Invalidate the root tag, and ensure that expected exception is thrown 298 | oldRoot.Name = null; 299 | Assert.Throws(() => newFile.SaveToBuffer(NbtCompression.None)); 300 | } 301 | 302 | 303 | [Test] 304 | public void NullParameterTest() { 305 | Assert.Throws(() => new NbtFile((NbtCompound)null)); 306 | Assert.Throws(() => new NbtFile((string)null)); 307 | 308 | NbtFile file = new NbtFile(); 309 | Assert.Throws(() => file.LoadFromBuffer(null, 0, 1, NbtCompression.None)); 310 | Assert.Throws(() => file.LoadFromBuffer(null, 0, 1, NbtCompression.None, tag => true)); 311 | Assert.Throws(() => file.LoadFromFile(null)); 312 | Assert.Throws(() => file.LoadFromFile(null, NbtCompression.None, tag => true)); 313 | Assert.Throws(() => file.LoadFromStream(null, NbtCompression.AutoDetect)); 314 | Assert.Throws(() => file.LoadFromStream(null, NbtCompression.AutoDetect, tag => true)); 315 | 316 | Assert.Throws(() => file.SaveToBuffer(null, 0, NbtCompression.None)); 317 | Assert.Throws(() => file.SaveToFile(null, NbtCompression.None)); 318 | Assert.Throws(() => file.SaveToStream(null, NbtCompression.None)); 319 | 320 | Assert.Throws(() => NbtFile.ReadRootTagName(null)); 321 | Assert.Throws( 322 | () => NbtFile.ReadRootTagName((Stream)null, NbtCompression.None, true, 0)); 323 | 324 | } 325 | 326 | 327 | [TearDown] 328 | public void NbtFileTestTearDown() { 329 | if (Directory.Exists(TestDirName)) { 330 | foreach (string file in Directory.GetFiles(TestDirName)) { 331 | File.Delete(file); 332 | } 333 | Directory.Delete(TestDirName); 334 | } 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /fNbt.Test/NonSeekableStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace fNbt.Test { 5 | internal class NonSeekableStream : Stream { 6 | readonly Stream stream; 7 | 8 | 9 | public NonSeekableStream(Stream baseStream) { 10 | stream = baseStream; 11 | } 12 | 13 | 14 | public override bool CanRead { 15 | get { return stream.CanRead; } 16 | } 17 | 18 | public override bool CanSeek { 19 | get { return false; } 20 | } 21 | 22 | public override bool CanWrite { 23 | get { return stream.CanWrite; } 24 | } 25 | 26 | 27 | public override void Flush() { 28 | stream.Flush(); 29 | } 30 | 31 | 32 | public override long Length { 33 | get { throw new NotSupportedException(); } 34 | } 35 | 36 | public override long Position { 37 | get { throw new NotSupportedException(); } 38 | set { throw new NotSupportedException(); } 39 | } 40 | 41 | 42 | public override int Read(byte[] buffer, int offset, int count) { 43 | return stream.Read(buffer, offset, count); 44 | } 45 | 46 | 47 | public override long Seek(long offset, SeekOrigin origin) { 48 | throw new NotImplementedException(); 49 | } 50 | 51 | 52 | public override void SetLength(long value) { 53 | throw new NotSupportedException(); 54 | } 55 | 56 | 57 | public override void Write(byte[] buffer, int offset, int count) { 58 | stream.Write(buffer, offset, count); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /fNbt.Test/PartialReadStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace fNbt.Test { 5 | class PartialReadStream : Stream { 6 | readonly Stream baseStream; 7 | readonly int increment; 8 | 9 | public PartialReadStream(Stream baseStream) 10 | : this(baseStream, 1) { } 11 | 12 | 13 | public PartialReadStream(Stream baseStream, int increment) { 14 | if (baseStream == null) throw new ArgumentNullException(nameof(baseStream)); 15 | this.baseStream = baseStream; 16 | this.increment = increment; 17 | } 18 | 19 | 20 | public override void Flush() { 21 | baseStream.Flush(); 22 | } 23 | 24 | 25 | public override long Seek(long offset, SeekOrigin origin) { 26 | return baseStream.Seek(offset, origin); 27 | } 28 | 29 | 30 | public override void SetLength(long value) { 31 | baseStream.SetLength(value); 32 | } 33 | 34 | 35 | public override int Read(byte[] buffer, int offset, int count) { 36 | int bytesToRead = Math.Min(increment, count); 37 | return baseStream.Read(buffer, offset, bytesToRead); 38 | } 39 | 40 | 41 | public override void Write(byte[] buffer, int offset, int count) { 42 | throw new NotSupportedException(); 43 | } 44 | 45 | 46 | public override bool CanRead { 47 | get { return true; } 48 | } 49 | 50 | public override bool CanSeek { 51 | get { return baseStream.CanSeek; } 52 | } 53 | 54 | public override bool CanWrite { 55 | get { return false; } 56 | } 57 | 58 | public override long Length { 59 | get { return baseStream.Length; } 60 | } 61 | 62 | public override long Position { 63 | get { return baseStream.Position; } 64 | set { baseStream.Position = value; } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /fNbt.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("fNbt.Test")] 9 | [assembly: AssemblyDescription("NUnit tests for fNbt library.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("github.com/mstefarov/fNbt")] 12 | [assembly: AssemblyProduct("fNbt.Test")] 13 | [assembly: AssemblyCopyright("2012-2024 Matvei Stefarov")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("37d4ff8c-3e40-4b11-a147-add3cdfb1c5f")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("0.6.4.0")] 39 | [assembly: AssemblyFileVersion("0.6.4.0")] 40 | -------------------------------------------------------------------------------- /fNbt.Test/ShortcutTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using NUnit.Framework; 4 | 5 | namespace fNbt.Test { 6 | [TestFixture] 7 | public class ShortcutTests { 8 | [Test] 9 | public void NbtByteTest() { 10 | object dummy; 11 | NbtTag test = new NbtByte(250); 12 | Assert.Throws(() => dummy = test.ByteArrayValue); 13 | Assert.AreEqual(250, test.ByteValue); 14 | Assert.AreEqual((double)250, test.DoubleValue); 15 | Assert.AreEqual((float)250, test.FloatValue); 16 | Assert.Throws(() => dummy = test.IntArrayValue); 17 | Assert.AreEqual(250, test.IntValue); 18 | Assert.AreEqual(250L, test.LongValue); 19 | Assert.AreEqual(250, test.ShortValue); 20 | Assert.AreEqual("250", test.StringValue); 21 | Assert.Throws(() => dummy = test.LongArrayValue); 22 | Assert.IsTrue(test.HasValue); 23 | } 24 | 25 | 26 | [Test] 27 | public void NbtByteArrayTest() { 28 | object dummy; 29 | byte[] bytes = { 1, 2, 3, 4, 5 }; 30 | NbtTag test = new NbtByteArray(bytes); 31 | CollectionAssert.AreEqual(bytes, test.ByteArrayValue); 32 | Assert.Throws(() => dummy = test.ByteValue); 33 | Assert.Throws(() => dummy = test.DoubleValue); 34 | Assert.Throws(() => dummy = test.FloatValue); 35 | Assert.Throws(() => dummy = test.IntArrayValue); 36 | Assert.Throws(() => dummy = test.IntValue); 37 | Assert.Throws(() => dummy = test.LongValue); 38 | Assert.Throws(() => dummy = test.ShortValue); 39 | Assert.Throws(() => dummy = test.StringValue); 40 | Assert.Throws(() => dummy = test.LongArrayValue); 41 | Assert.IsTrue(test.HasValue); 42 | } 43 | 44 | 45 | [Test] 46 | public void NbtCompoundTest() { 47 | object dummy; 48 | NbtTag test = new NbtCompound("Derp"); 49 | Assert.Throws(() => dummy = test.ByteArrayValue); 50 | Assert.Throws(() => dummy = test.ByteValue); 51 | Assert.Throws(() => dummy = test.DoubleValue); 52 | Assert.Throws(() => dummy = test.FloatValue); 53 | Assert.Throws(() => dummy = test.IntArrayValue); 54 | Assert.Throws(() => dummy = test.IntValue); 55 | Assert.Throws(() => dummy = test.LongValue); 56 | Assert.Throws(() => dummy = test.ShortValue); 57 | Assert.Throws(() => dummy = test.StringValue); 58 | Assert.Throws(() => dummy = test.LongArrayValue); 59 | Assert.IsFalse(test.HasValue); 60 | } 61 | 62 | 63 | [Test] 64 | public void NbtDoubleTest() { 65 | object dummy; 66 | NbtTag test = new NbtDouble(0.4931287132182315); 67 | Assert.Throws(() => dummy = test.ByteArrayValue); 68 | Assert.Throws(() => dummy = test.ByteValue); 69 | Assert.AreEqual(0.4931287132182315, test.DoubleValue); 70 | Assert.AreEqual((float)0.4931287132182315, test.FloatValue); 71 | Assert.Throws(() => dummy = test.IntArrayValue); 72 | Assert.Throws(() => dummy = test.IntValue); 73 | Assert.Throws(() => dummy = test.LongValue); 74 | Assert.Throws(() => dummy = test.ShortValue); 75 | Assert.AreEqual((0.4931287132182315).ToString(CultureInfo.InvariantCulture), test.StringValue); 76 | Assert.Throws(() => dummy = test.LongArrayValue); 77 | Assert.IsTrue(test.HasValue); 78 | } 79 | 80 | 81 | [Test] 82 | public void NbtFloatTest() { 83 | object dummy; 84 | NbtTag test = new NbtFloat(0.49823147f); 85 | Assert.Throws(() => dummy = test.ByteArrayValue); 86 | Assert.Throws(() => dummy = test.ByteValue); 87 | Assert.AreEqual((double)0.49823147f, test.DoubleValue); 88 | Assert.AreEqual(0.49823147f, test.FloatValue); 89 | Assert.Throws(() => dummy = test.IntArrayValue); 90 | Assert.Throws(() => dummy = test.IntValue); 91 | Assert.Throws(() => dummy = test.LongValue); 92 | Assert.Throws(() => dummy = test.ShortValue); 93 | Assert.AreEqual((0.49823147f).ToString(CultureInfo.InvariantCulture), test.StringValue); 94 | Assert.Throws(() => dummy = test.LongArrayValue); 95 | Assert.IsTrue(test.HasValue); 96 | } 97 | 98 | 99 | [Test] 100 | public void NbtIntTest() { 101 | object dummy; 102 | NbtTag test = new NbtInt(2147483647); 103 | Assert.Throws(() => dummy = test.ByteArrayValue); 104 | Assert.Throws(() => dummy = test.ByteValue); 105 | Assert.AreEqual((double)2147483647, test.DoubleValue); 106 | Assert.AreEqual((float)2147483647, test.FloatValue); 107 | Assert.Throws(() => dummy = test.IntArrayValue); 108 | Assert.AreEqual(2147483647, test.IntValue); 109 | Assert.AreEqual(2147483647L, test.LongValue); 110 | Assert.Throws(() => dummy = test.ShortValue); 111 | Assert.AreEqual("2147483647", test.StringValue); 112 | Assert.Throws(() => dummy = test.LongArrayValue); 113 | Assert.IsTrue(test.HasValue); 114 | } 115 | 116 | 117 | [Test] 118 | public void NbtIntArrayTest() { 119 | object dummy; 120 | int[] ints = { 1111, 2222, 3333, 4444, 5555 }; 121 | NbtTag test = new NbtIntArray(ints); 122 | Assert.Throws(() => dummy = test.ByteArrayValue); 123 | Assert.Throws(() => dummy = test.ByteValue); 124 | Assert.Throws(() => dummy = test.DoubleValue); 125 | Assert.Throws(() => dummy = test.FloatValue); 126 | CollectionAssert.AreEqual(ints, test.IntArrayValue); 127 | Assert.Throws(() => dummy = test.IntValue); 128 | Assert.Throws(() => dummy = test.LongValue); 129 | Assert.Throws(() => dummy = test.ShortValue); 130 | Assert.Throws(() => dummy = test.StringValue); 131 | Assert.Throws(() => dummy = test.LongArrayValue); 132 | Assert.IsTrue(test.HasValue); 133 | } 134 | 135 | 136 | [Test] 137 | public void NbtListTest() { 138 | object dummy; 139 | NbtTag test = new NbtList("Derp"); 140 | Assert.Throws(() => dummy = test.ByteArrayValue); 141 | Assert.Throws(() => dummy = test.ByteValue); 142 | Assert.Throws(() => dummy = test.DoubleValue); 143 | Assert.Throws(() => dummy = test.FloatValue); 144 | Assert.Throws(() => dummy = test.IntArrayValue); 145 | Assert.Throws(() => dummy = test.IntValue); 146 | Assert.Throws(() => dummy = test.LongValue); 147 | Assert.Throws(() => dummy = test.ShortValue); 148 | Assert.Throws(() => dummy = test.StringValue); 149 | Assert.Throws(() => dummy = test.LongArrayValue); 150 | Assert.IsFalse(test.HasValue); 151 | } 152 | 153 | 154 | [Test] 155 | public void NbtLongTest() { 156 | object dummy; 157 | NbtTag test = new NbtLong(9223372036854775807); 158 | Assert.Throws(() => dummy = test.ByteArrayValue); 159 | Assert.Throws(() => dummy = test.ByteValue); 160 | Assert.AreEqual((double)9223372036854775807, test.DoubleValue); 161 | Assert.AreEqual((float)9223372036854775807, test.FloatValue); 162 | Assert.Throws(() => dummy = test.IntArrayValue); 163 | Assert.Throws(() => dummy = test.IntValue); 164 | Assert.AreEqual(9223372036854775807, test.LongValue); 165 | Assert.Throws(() => dummy = test.ShortValue); 166 | Assert.AreEqual("9223372036854775807", test.StringValue); 167 | Assert.Throws(() => dummy = test.LongArrayValue); 168 | Assert.IsTrue(test.HasValue); 169 | } 170 | 171 | 172 | [Test] 173 | public void NbtShortTest() { 174 | object dummy; 175 | NbtTag test = new NbtShort(32767); 176 | Assert.Throws(() => dummy = test.ByteArrayValue); 177 | Assert.Throws(() => dummy = test.ByteValue); 178 | Assert.AreEqual((double)32767, test.DoubleValue); 179 | Assert.AreEqual((float)32767, test.FloatValue); 180 | Assert.Throws(() => dummy = test.IntArrayValue); 181 | Assert.AreEqual(32767, test.IntValue); 182 | Assert.AreEqual(32767L, test.LongValue); 183 | Assert.AreEqual(32767, test.ShortValue); 184 | Assert.AreEqual("32767", test.StringValue); 185 | Assert.Throws(() => dummy = test.LongArrayValue); 186 | Assert.IsTrue(test.HasValue); 187 | } 188 | 189 | 190 | [Test] 191 | public void NbtStringTest() { 192 | object dummy; 193 | NbtTag test = new NbtString("HELLO WORLD THIS IS A TEST STRING ÅÄÖ!"); 194 | Assert.Throws(() => dummy = test.ByteArrayValue); 195 | Assert.Throws(() => dummy = test.ByteValue); 196 | Assert.Throws(() => dummy = test.DoubleValue); 197 | Assert.Throws(() => dummy = test.FloatValue); 198 | Assert.Throws(() => dummy = test.IntArrayValue); 199 | Assert.Throws(() => dummy = test.IntValue); 200 | Assert.Throws(() => dummy = test.LongValue); 201 | Assert.Throws(() => dummy = test.ShortValue); 202 | Assert.AreEqual("HELLO WORLD THIS IS A TEST STRING ÅÄÖ!", test.StringValue); 203 | Assert.Throws(() => dummy = test.LongArrayValue); 204 | Assert.IsTrue(test.HasValue); 205 | } 206 | 207 | 208 | [Test] 209 | public void NbtLongArrayTest() { 210 | object dummy; 211 | long[] longs = { 1111, 2222, 3333, 4444, 5555 }; 212 | NbtTag test = new NbtLongArray(longs); 213 | Assert.Throws(() => dummy = test.ByteArrayValue); 214 | Assert.Throws(() => dummy = test.ByteValue); 215 | Assert.Throws(() => dummy = test.DoubleValue); 216 | Assert.Throws(() => dummy = test.FloatValue); 217 | Assert.Throws(() => dummy = test.IntArrayValue); 218 | Assert.Throws(() => dummy = test.IntValue); 219 | Assert.Throws(() => dummy = test.LongValue); 220 | Assert.Throws(() => dummy = test.ShortValue); 221 | Assert.Throws(() => dummy = test.StringValue); 222 | CollectionAssert.AreEqual(longs, test.LongArrayValue); 223 | Assert.IsTrue(test.HasValue); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /fNbt.Test/TagSelectorTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace fNbt.Test { 4 | [TestFixture] 5 | public sealed class TagSelectorTests { 6 | [Test] 7 | public void SkippingTagsOnFileLoad() { 8 | var loadedFile = new NbtFile(); 9 | loadedFile.LoadFromFile(TestFiles.Big, 10 | NbtCompression.None, 11 | tag => tag.Name != "nested compound test"); 12 | Assert.IsFalse(loadedFile.RootTag.Contains("nested compound test")); 13 | Assert.IsTrue(loadedFile.RootTag.Contains("listTest (long)")); 14 | 15 | loadedFile.LoadFromFile(TestFiles.Big, 16 | NbtCompression.None, 17 | tag => tag.TagType != NbtTagType.Float || tag.Parent.Name != "Level"); 18 | Assert.IsFalse(loadedFile.RootTag.Contains("floatTest")); 19 | Assert.AreEqual(0.75f, loadedFile.RootTag["nested compound test"]["ham"]["value"].FloatValue); 20 | 21 | loadedFile.LoadFromFile(TestFiles.Big, 22 | NbtCompression.None, 23 | tag => tag.Name != "listTest (long)"); 24 | Assert.IsFalse(loadedFile.RootTag.Contains("listTest (long)")); 25 | Assert.IsTrue(loadedFile.RootTag.Contains("byteTest")); 26 | 27 | loadedFile.LoadFromFile(TestFiles.Big, 28 | NbtCompression.None, 29 | tag => false); 30 | Assert.AreEqual(0, loadedFile.RootTag.Count); 31 | } 32 | 33 | 34 | [Test] 35 | public void SkippingLists() { 36 | { 37 | var file = new NbtFile(TestFiles.MakeListTest()); 38 | byte[] savedFile = file.SaveToBuffer(NbtCompression.None); 39 | file.LoadFromBuffer(savedFile, 0, savedFile.Length, NbtCompression.None, 40 | tag => tag.TagType != NbtTagType.List); 41 | Assert.AreEqual(0, file.RootTag.Count); 42 | } 43 | { 44 | // Check list-compound interaction 45 | NbtCompound comp = new NbtCompound("root") { 46 | new NbtCompound("compOfLists") { 47 | new NbtList("listOfComps") { 48 | new NbtCompound { 49 | new NbtList("emptyList", NbtTagType.Compound) 50 | } 51 | } 52 | } 53 | }; 54 | var file = new NbtFile(comp); 55 | byte[] savedFile = file.SaveToBuffer(NbtCompression.None); 56 | file.LoadFromBuffer(savedFile, 0, savedFile.Length, NbtCompression.None, 57 | tag => tag.TagType != NbtTagType.List); 58 | Assert.AreEqual(1, file.RootTag.Count); 59 | } 60 | } 61 | 62 | 63 | [Test] 64 | public void SkippingValuesInCompoundTest() { 65 | NbtCompound root = TestFiles.MakeValueTest(); 66 | NbtCompound nestedComp = TestFiles.MakeValueTest(); 67 | nestedComp.Name = "NestedComp"; 68 | root.Add(nestedComp); 69 | 70 | var file = new NbtFile(root); 71 | byte[] savedFile = file.SaveToBuffer(NbtCompression.None); 72 | file.LoadFromBuffer(savedFile, 0, savedFile.Length, NbtCompression.None, tag => false); 73 | Assert.AreEqual(0, file.RootTag.Count); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /fNbt.Test/TestFiles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using NUnit.Framework; 4 | 5 | namespace fNbt.Test { 6 | public static class TestFiles { 7 | public static readonly string DirName = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestFiles"); 8 | public static readonly string Small = Path.Combine(DirName, "test.nbt"); 9 | public static readonly string SmallGZip = Path.Combine(DirName, "test.nbt.gz"); 10 | public static readonly string SmallZLib = Path.Combine(DirName, "test.nbt.z"); 11 | public static readonly string Big = Path.Combine(DirName, "bigtest.nbt"); 12 | public static readonly string BigGZip = Path.Combine(DirName, "bigtest.nbt.gz"); 13 | public static readonly string BigZLib = Path.Combine(DirName, "bigtest.nbt.z"); 14 | 15 | 16 | // creates a compound containing lists of every kind of tag 17 | public static NbtCompound MakeListTest() { 18 | return new NbtCompound("Root") { 19 | new NbtList("ByteList") { 20 | new NbtByte(100), 21 | new NbtByte(20), 22 | new NbtByte(3) 23 | }, 24 | new NbtList("DoubleList") { 25 | new NbtDouble(1d), 26 | new NbtDouble(2000d), 27 | new NbtDouble(-3000000d) 28 | }, 29 | new NbtList("FloatList") { 30 | new NbtFloat(1f), 31 | new NbtFloat(2000f), 32 | new NbtFloat(-3000000f) 33 | }, 34 | new NbtList("IntList") { 35 | new NbtInt(1), 36 | new NbtInt(2000), 37 | new NbtInt(-3000000) 38 | }, 39 | new NbtList("LongList") { 40 | new NbtLong(1L), 41 | new NbtLong(2000L), 42 | new NbtLong(-3000000L) 43 | }, 44 | new NbtList("ShortList") { 45 | new NbtShort(1), 46 | new NbtShort(200), 47 | new NbtShort(-30000) 48 | }, 49 | new NbtList("StringList") { 50 | new NbtString("one"), 51 | new NbtString("two thousand"), 52 | new NbtString("negative three million") 53 | }, 54 | new NbtList("CompoundList") { 55 | new NbtCompound(), 56 | new NbtCompound(), 57 | new NbtCompound() 58 | }, 59 | new NbtList("ListList") { 60 | new NbtList(NbtTagType.List), 61 | new NbtList(NbtTagType.List), 62 | new NbtList(NbtTagType.List) 63 | }, 64 | new NbtList("ByteArrayList") { 65 | new NbtByteArray(new byte[] { 66 | 1, 2, 3 67 | }), 68 | new NbtByteArray(new byte[] { 69 | 11, 12, 13 70 | }), 71 | new NbtByteArray(new byte[] { 72 | 21, 22, 23 73 | }) 74 | }, 75 | new NbtList("IntArrayList") { 76 | new NbtIntArray(new[] { 77 | 1, -2, 3 78 | }), 79 | new NbtIntArray(new[] { 80 | 1000, -2000, 3000 81 | }), 82 | new NbtIntArray(new[] { 83 | 1000000, -2000000, 3000000 84 | }) 85 | }, 86 | new NbtList("LongArrayList") { 87 | new NbtLongArray(new long[] { 88 | 10, -20, 30 89 | }), 90 | new NbtLongArray(new long[] { 91 | 100, -200, 300 92 | }), 93 | new NbtLongArray(new long[] { 94 | 100, -200, 300 95 | }) 96 | } 97 | }; 98 | } 99 | 100 | 101 | // creates a file with lots of compounds and lists, used to test NbtReader compliance 102 | public static Stream MakeReaderTest() { 103 | var root = new NbtCompound("root") { 104 | new NbtInt("first"), 105 | new NbtInt("second"), 106 | new NbtCompound("third-comp") { 107 | new NbtInt("inComp1"), 108 | new NbtInt("inComp2"), 109 | new NbtInt("inComp3") 110 | }, 111 | new NbtList("fourth-list") { 112 | new NbtList { 113 | new NbtCompound { 114 | new NbtCompound("inList1") 115 | } 116 | }, 117 | new NbtList { 118 | new NbtCompound { 119 | new NbtCompound("inList2") 120 | } 121 | }, 122 | new NbtList { 123 | new NbtCompound { 124 | new NbtCompound("inList3") 125 | } 126 | } 127 | }, 128 | new NbtInt("fifth"), 129 | new NbtByteArray("hugeArray", new byte[1024*1024]) 130 | }; 131 | byte[] testData = new NbtFile(root).SaveToBuffer(NbtCompression.None); 132 | return new MemoryStream(testData); 133 | } 134 | 135 | 136 | // creates an NbtFile with contents identical to "test.nbt" 137 | public static NbtFile MakeSmallFile() { 138 | return new NbtFile(new NbtCompound("hello world") { 139 | new NbtString("name", "Bananrama") 140 | }); 141 | } 142 | 143 | 144 | public static void AssertNbtSmallFile(NbtFile file) { 145 | Assert.IsInstanceOf(file.RootTag); 146 | 147 | NbtCompound root = file.RootTag; 148 | Assert.AreEqual("hello world", root.Name); 149 | Assert.AreEqual(1, root.Count); 150 | 151 | Assert.IsInstanceOf(root["name"]); 152 | 153 | var node = (NbtString)root["name"]; 154 | Assert.AreEqual("name", node.Name); 155 | Assert.AreEqual("Bananrama", node.Value); 156 | } 157 | 158 | 159 | public static void AssertNbtBigFile(NbtFile file) { 160 | Assert.IsInstanceOf(file.RootTag); 161 | 162 | NbtCompound root = file.RootTag; 163 | Assert.AreEqual("Level", root.Name); 164 | Assert.AreEqual(13, root.Count); 165 | 166 | Assert.IsInstanceOf(root["longTest"]); 167 | NbtTag node = root["longTest"]; 168 | Assert.AreEqual("longTest", node.Name); 169 | Assert.AreEqual(9223372036854775807, ((NbtLong)node).Value); 170 | 171 | Assert.IsInstanceOf(root["shortTest"]); 172 | node = root["shortTest"]; 173 | Assert.AreEqual("shortTest", node.Name); 174 | Assert.AreEqual(32767, ((NbtShort)node).Value); 175 | 176 | Assert.IsInstanceOf(root["stringTest"]); 177 | node = root["stringTest"]; 178 | Assert.AreEqual("stringTest", node.Name); 179 | Assert.AreEqual("HELLO WORLD THIS IS A TEST STRING ÅÄÖ!", ((NbtString)node).Value); 180 | 181 | Assert.IsInstanceOf(root["floatTest"]); 182 | node = root["floatTest"]; 183 | Assert.AreEqual("floatTest", node.Name); 184 | Assert.AreEqual(0.49823147f, ((NbtFloat)node).Value); 185 | 186 | Assert.IsInstanceOf(root["intTest"]); 187 | node = root["intTest"]; 188 | Assert.AreEqual("intTest", node.Name); 189 | Assert.AreEqual(2147483647, ((NbtInt)node).Value); 190 | 191 | Assert.IsInstanceOf(root["nested compound test"]); 192 | node = root["nested compound test"]; 193 | Assert.AreEqual("nested compound test", node.Name); 194 | Assert.AreEqual(2, ((NbtCompound)node).Count); 195 | 196 | // First nested test 197 | Assert.IsInstanceOf(node["ham"]); 198 | var subNode = (NbtCompound)node["ham"]; 199 | Assert.AreEqual("ham", subNode.Name); 200 | Assert.AreEqual(2, subNode.Count); 201 | 202 | // Checking sub node values 203 | Assert.IsInstanceOf(subNode["name"]); 204 | Assert.AreEqual("name", subNode["name"].Name); 205 | Assert.AreEqual("Hampus", ((NbtString)subNode["name"]).Value); 206 | 207 | Assert.IsInstanceOf(subNode["value"]); 208 | Assert.AreEqual("value", subNode["value"].Name); 209 | Assert.AreEqual(0.75, ((NbtFloat)subNode["value"]).Value); 210 | // End sub node 211 | 212 | // Second nested test 213 | Assert.IsInstanceOf(node["egg"]); 214 | subNode = (NbtCompound)node["egg"]; 215 | Assert.AreEqual("egg", subNode.Name); 216 | Assert.AreEqual(2, subNode.Count); 217 | 218 | // Checking sub node values 219 | Assert.IsInstanceOf(subNode["name"]); 220 | Assert.AreEqual("name", subNode["name"].Name); 221 | Assert.AreEqual("Eggbert", ((NbtString)subNode["name"]).Value); 222 | 223 | Assert.IsInstanceOf(subNode["value"]); 224 | Assert.AreEqual("value", subNode["value"].Name); 225 | Assert.AreEqual(0.5, ((NbtFloat)subNode["value"]).Value); 226 | // End sub node 227 | 228 | Assert.IsInstanceOf(root["listTest (long)"]); 229 | node = root["listTest (long)"]; 230 | Assert.AreEqual("listTest (long)", node.Name); 231 | Assert.AreEqual(5, ((NbtList)node).Count); 232 | 233 | // The values should be: 11, 12, 13, 14, 15 234 | for (int nodeIndex = 0; nodeIndex < ((NbtList)node).Count; nodeIndex++) { 235 | Assert.IsInstanceOf(node[nodeIndex]); 236 | Assert.AreEqual(null, node[nodeIndex].Name); 237 | Assert.AreEqual(nodeIndex + 11, ((NbtLong)node[nodeIndex]).Value); 238 | } 239 | 240 | Assert.IsInstanceOf(root["listTest (compound)"]); 241 | node = root["listTest (compound)"]; 242 | Assert.AreEqual("listTest (compound)", node.Name); 243 | Assert.AreEqual(2, ((NbtList)node).Count); 244 | 245 | // First Sub Node 246 | Assert.IsInstanceOf(node[0]); 247 | subNode = (NbtCompound)node[0]; 248 | 249 | // First node in sub node 250 | Assert.IsInstanceOf(subNode["name"]); 251 | Assert.AreEqual("name", subNode["name"].Name); 252 | Assert.AreEqual("Compound tag #0", ((NbtString)subNode["name"]).Value); 253 | 254 | // Second node in sub node 255 | Assert.IsInstanceOf(subNode["created-on"]); 256 | Assert.AreEqual("created-on", subNode["created-on"].Name); 257 | Assert.AreEqual(1264099775885, ((NbtLong)subNode["created-on"]).Value); 258 | 259 | // Second Sub Node 260 | Assert.IsInstanceOf(node[1]); 261 | subNode = (NbtCompound)node[1]; 262 | 263 | // First node in sub node 264 | Assert.IsInstanceOf(subNode["name"]); 265 | Assert.AreEqual("name", subNode["name"].Name); 266 | Assert.AreEqual("Compound tag #1", ((NbtString)subNode["name"]).Value); 267 | 268 | // Second node in sub node 269 | Assert.IsInstanceOf(subNode["created-on"]); 270 | Assert.AreEqual("created-on", subNode["created-on"].Name); 271 | Assert.AreEqual(1264099775885, ((NbtLong)subNode["created-on"]).Value); 272 | 273 | Assert.IsInstanceOf(root["byteTest"]); 274 | node = root["byteTest"]; 275 | Assert.AreEqual("byteTest", node.Name); 276 | Assert.AreEqual(127, ((NbtByte)node).Value); 277 | 278 | const string byteArrayName = 279 | "byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"; 280 | Assert.IsInstanceOf(root[byteArrayName]); 281 | node = root[byteArrayName]; 282 | Assert.AreEqual(byteArrayName, node.Name); 283 | Assert.AreEqual(1000, ((NbtByteArray)node).Value.Length); 284 | 285 | // Values are: the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...) 286 | for (int n = 0; n < 1000; n++) { 287 | Assert.AreEqual((n * n * 255 + n * 7) % 100, ((NbtByteArray)node)[n]); 288 | } 289 | 290 | Assert.IsInstanceOf(root["doubleTest"]); 291 | node = root["doubleTest"]; 292 | Assert.AreEqual("doubleTest", node.Name); 293 | Assert.AreEqual(0.4931287132182315, ((NbtDouble)node).Value); 294 | 295 | Assert.IsInstanceOf(root["intArrayTest"]); 296 | var intArrayTag = root.Get("intArrayTest"); 297 | Assert.IsNotNull(intArrayTag); 298 | Assert.AreEqual(10, intArrayTag.Value.Length); 299 | var rand = new Random(0); 300 | for (int i = 0; i < 10; i++) { 301 | Assert.AreEqual(rand.Next(), intArrayTag.Value[i]); 302 | } 303 | 304 | Assert.IsInstanceOf(root["longArrayTest"]); 305 | var longArrayTag = root.Get("longArrayTest"); 306 | Assert.IsNotNull(longArrayTag); 307 | Assert.AreEqual(5, longArrayTag.Value.Length); 308 | var rand2 = new Random(0); 309 | for (int i = 0; i < 5; i++) { 310 | Assert.AreEqual(((long)rand2.Next() << 32) | (uint)rand2.Next(), longArrayTag.Value[i]); 311 | } 312 | } 313 | 314 | 315 | #region Value test 316 | 317 | // creates an NbtCompound with one of tag of each value-type 318 | public static NbtCompound MakeValueTest() { 319 | return new NbtCompound("root") { 320 | new NbtByte("byte", 1), 321 | new NbtShort("short", 2), 322 | new NbtInt("int", 3), 323 | new NbtLong("long", 4L), 324 | new NbtFloat("float", 5f), 325 | new NbtDouble("double", 6d), 326 | new NbtByteArray("byteArray", new byte[] { 10, 11, 12 }), 327 | new NbtIntArray("intArray", new[] { 20, 21, 22 }), 328 | new NbtLongArray("longArray", new long[] { 200, 210, 220 }), 329 | new NbtString("string", "123") 330 | }; 331 | } 332 | 333 | 334 | public static void AssertValueTest(NbtFile file) { 335 | Assert.IsInstanceOf(file.RootTag); 336 | 337 | NbtCompound root = file.RootTag; 338 | Assert.AreEqual("root", root.Name); 339 | Assert.AreEqual(10, root.Count); 340 | 341 | Assert.IsInstanceOf(root["byte"]); 342 | NbtTag node = root["byte"]; 343 | Assert.AreEqual("byte", node.Name); 344 | Assert.AreEqual(1, node.ByteValue); 345 | 346 | Assert.IsInstanceOf(root["short"]); 347 | node = root["short"]; 348 | Assert.AreEqual("short", node.Name); 349 | Assert.AreEqual(2, node.ShortValue); 350 | 351 | Assert.IsInstanceOf(root["int"]); 352 | node = root["int"]; 353 | Assert.AreEqual("int", node.Name); 354 | Assert.AreEqual(3, node.IntValue); 355 | 356 | Assert.IsInstanceOf(root["long"]); 357 | node = root["long"]; 358 | Assert.AreEqual("long", node.Name); 359 | Assert.AreEqual(4L, node.LongValue); 360 | 361 | Assert.IsInstanceOf(root["float"]); 362 | node = root["float"]; 363 | Assert.AreEqual("float", node.Name); 364 | Assert.AreEqual(5f, node.FloatValue); 365 | 366 | Assert.IsInstanceOf(root["double"]); 367 | node = root["double"]; 368 | Assert.AreEqual("double", node.Name); 369 | Assert.AreEqual(6d, node.DoubleValue); 370 | 371 | Assert.IsInstanceOf(root["byteArray"]); 372 | node = root["byteArray"]; 373 | Assert.AreEqual("byteArray", node.Name); 374 | CollectionAssert.AreEqual(new byte[] { 10, 11, 12 }, node.ByteArrayValue); 375 | 376 | Assert.IsInstanceOf(root["intArray"]); 377 | node = root["intArray"]; 378 | Assert.AreEqual("intArray", node.Name); 379 | CollectionAssert.AreEqual(new[] { 20, 21, 22 }, node.IntArrayValue); 380 | 381 | Assert.IsInstanceOf(root["longArray"]); 382 | node = root["longArray"]; 383 | Assert.AreEqual("longArray", node.Name); 384 | CollectionAssert.AreEqual(new long[] { 200, 210, 220 }, node.LongArrayValue); 385 | 386 | Assert.IsInstanceOf(root["string"]); 387 | node = root["string"]; 388 | Assert.AreEqual("string", node.Name); 389 | Assert.AreEqual("123", node.StringValue); 390 | } 391 | 392 | #endregion 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /fNbt.Test/TestFiles/bigtest.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstefarov/fNbt/0a8c21f760008a21de771bdd8d717c047197916f/fNbt.Test/TestFiles/bigtest.nbt -------------------------------------------------------------------------------- /fNbt.Test/TestFiles/bigtest.nbt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstefarov/fNbt/0a8c21f760008a21de771bdd8d717c047197916f/fNbt.Test/TestFiles/bigtest.nbt.gz -------------------------------------------------------------------------------- /fNbt.Test/TestFiles/bigtest.nbt.z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstefarov/fNbt/0a8c21f760008a21de771bdd8d717c047197916f/fNbt.Test/TestFiles/bigtest.nbt.z -------------------------------------------------------------------------------- /fNbt.Test/TestFiles/test.nbt: -------------------------------------------------------------------------------- 1 | 2 | hello worldname Bananrama -------------------------------------------------------------------------------- /fNbt.Test/TestFiles/test.nbt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstefarov/fNbt/0a8c21f760008a21de771bdd8d717c047197916f/fNbt.Test/TestFiles/test.nbt.gz -------------------------------------------------------------------------------- /fNbt.Test/TestFiles/test.nbt.z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstefarov/fNbt/0a8c21f760008a21de771bdd8d717c047197916f/fNbt.Test/TestFiles/test.nbt.z -------------------------------------------------------------------------------- /fNbt.Test/fNbt.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net461 4 | Library 5 | false 6 | true 7 | 10 8 | 9 | 10 | ..\bin\Debug\ 11 | 12 | 13 | false 14 | ..\bin\Release\ 15 | 16 | 17 | 18 | PreserveNewest 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | PreserveNewest 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /fNbt.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31515.178 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{83C41F58-F6F7-46F2-85C3-CDD69E7FBEE6}" 7 | ProjectSection(SolutionItems) = preProject 8 | docs\Changelog.md = docs\Changelog.md 9 | docs\LICENSE = docs\LICENSE 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fNbt", "fNbt\fNbt.csproj", "{4488498D-976D-4DA3-BF72-109531AF0488}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fNbt.Test", "fNbt.Test\fNbt.Test.csproj", "{8DD93C35-682E-4C62-9012-EF15C80FFFEA}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{11566ECB-1DEE-478C-9B9A-9FEC0DCA4637}" 18 | ProjectSection(SolutionItems) = preProject 19 | .editorconfig = .editorconfig 20 | EndProjectSection 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {4488498D-976D-4DA3-BF72-109531AF0488}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {4488498D-976D-4DA3-BF72-109531AF0488}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {4488498D-976D-4DA3-BF72-109531AF0488}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {4488498D-976D-4DA3-BF72-109531AF0488}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {8DD93C35-682E-4C62-9012-EF15C80FFFEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {8DD93C35-682E-4C62-9012-EF15C80FFFEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {8DD93C35-682E-4C62-9012-EF15C80FFFEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {8DD93C35-682E-4C62-9012-EF15C80FFFEA}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {3540E880-9914-4B89-A93D-4D4945671DF3} 42 | EndGlobalSection 43 | GlobalSection(MonoDevelopProperties) = preSolution 44 | StartupItem = fNbt.csproj 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /fNbt/ByteCountingStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace fNbt { 4 | // Class used to count bytes read-from/written-to non-seekable streams. 5 | internal sealed class ByteCountingStream : Stream { 6 | readonly Stream baseStream; 7 | 8 | // These are necessary to avoid counting bytes twice if ReadByte/WriteByte call Read/Write internally. 9 | bool readingOneByte; 10 | bool writingOneByte; 11 | 12 | // These are necessary to avoid counting bytes twice if Read/Write call ReadByte/WriteByte internally. 13 | bool readingManyBytes; 14 | bool writingManyBytes; 15 | 16 | 17 | public ByteCountingStream(Stream stream) { 18 | NullableSupport.Assert(stream != null); 19 | baseStream = stream; 20 | } 21 | 22 | 23 | public override void Flush() { 24 | baseStream.Flush(); 25 | } 26 | 27 | 28 | public override long Seek(long offset, SeekOrigin origin) { 29 | return baseStream.Seek(offset, origin); 30 | } 31 | 32 | 33 | public override void SetLength(long value) { 34 | baseStream.SetLength(value); 35 | } 36 | 37 | 38 | public override int Read(byte[] buffer, int offset, int count) { 39 | readingManyBytes = true; 40 | int bytesActuallyRead = baseStream.Read(buffer, offset, count); 41 | readingManyBytes = false; 42 | if (!readingOneByte) BytesRead += bytesActuallyRead; 43 | return bytesActuallyRead; 44 | } 45 | 46 | 47 | public override void Write(byte[] buffer, int offset, int count) { 48 | writingManyBytes = true; 49 | baseStream.Write(buffer, offset, count); 50 | writingManyBytes = false; 51 | if (!writingOneByte) BytesWritten += count; 52 | } 53 | 54 | 55 | public override int ReadByte() { 56 | readingOneByte = true; 57 | int value = base.ReadByte(); 58 | readingOneByte = false; 59 | if (value >= 0 && !readingManyBytes) BytesRead++; 60 | return value; 61 | } 62 | 63 | 64 | public override void WriteByte(byte value) { 65 | writingOneByte = true; 66 | base.WriteByte(value); 67 | writingOneByte = false; 68 | if (!writingManyBytes) BytesWritten++; 69 | } 70 | 71 | 72 | public override bool CanRead { 73 | get { return baseStream.CanRead; } 74 | } 75 | 76 | public override bool CanSeek { 77 | get { return baseStream.CanSeek; } 78 | } 79 | 80 | public override bool CanWrite { 81 | get { return baseStream.CanWrite; } 82 | } 83 | 84 | public override long Length { 85 | get { return baseStream.Length; } 86 | } 87 | 88 | public override long Position { 89 | get { return baseStream.Position; } 90 | set { baseStream.Position = value; } 91 | } 92 | 93 | public long BytesRead { get; private set; } 94 | public long BytesWritten { get; private set; } 95 | 96 | 97 | protected override void Dispose(bool disposing) { 98 | base.Dispose(disposing); 99 | baseStream.Dispose(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /fNbt/InvalidReaderStateException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace fNbt { 4 | /// Exception thrown when an operation is attempted on an NbtReader that 5 | /// cannot recover from a previous parsing error. 6 | [Serializable] 7 | public sealed class InvalidReaderStateException : InvalidOperationException { 8 | internal InvalidReaderStateException(string message) 9 | : base(message) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /fNbt/NbtBinaryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace fNbt { 7 | /// BinaryReader wrapper that takes care of reading primitives from an NBT stream, 8 | /// while taking care of endianness, string encoding, and skipping. 9 | internal sealed class NbtBinaryReader : BinaryReader { 10 | readonly byte[] buffer = new byte[sizeof(double)]; 11 | 12 | byte[]? seekBuffer; 13 | const int SeekBufferSize = 8 * 1024; 14 | readonly bool swapNeeded; 15 | readonly byte[] stringConversionBuffer = new byte[64]; 16 | 17 | 18 | public NbtBinaryReader(Stream input, bool bigEndian) 19 | : base(input) { 20 | swapNeeded = (BitConverter.IsLittleEndian == bigEndian); 21 | } 22 | 23 | 24 | public NbtTagType ReadTagType() { 25 | int type = ReadByte(); 26 | if (type < 0) { 27 | throw new EndOfStreamException(); 28 | } else if (type > (int)NbtTagType.LongArray) { 29 | throw new NbtFormatException("NBT tag type out of range: " + type); 30 | } 31 | return (NbtTagType)type; 32 | } 33 | 34 | 35 | public override short ReadInt16() { 36 | if (swapNeeded) { 37 | return Swap(base.ReadInt16()); 38 | } else { 39 | return base.ReadInt16(); 40 | } 41 | } 42 | 43 | 44 | public override int ReadInt32() { 45 | if (swapNeeded) { 46 | return Swap(base.ReadInt32()); 47 | } else { 48 | return base.ReadInt32(); 49 | } 50 | } 51 | 52 | 53 | public override long ReadInt64() { 54 | if (swapNeeded) { 55 | return Swap(base.ReadInt64()); 56 | } else { 57 | return base.ReadInt64(); 58 | } 59 | } 60 | 61 | 62 | public override float ReadSingle() { 63 | if (swapNeeded) { 64 | FillBuffer(sizeof(float)); 65 | Array.Reverse(buffer, 0, sizeof(float)); 66 | return BitConverter.ToSingle(buffer, 0); 67 | } else { 68 | return base.ReadSingle(); 69 | } 70 | } 71 | 72 | 73 | public override double ReadDouble() { 74 | if (swapNeeded) { 75 | FillBuffer(sizeof(double)); 76 | Array.Reverse(buffer); 77 | return BitConverter.ToDouble(buffer, 0); 78 | } 79 | return base.ReadDouble(); 80 | } 81 | 82 | 83 | public override string ReadString() { 84 | short length = ReadInt16(); 85 | if (length < 0) { 86 | throw new NbtFormatException("Negative string length given!"); 87 | } 88 | if (length < stringConversionBuffer.Length) { 89 | int stringBytesRead = 0; 90 | while (stringBytesRead < length) { 91 | int bytesToRead = length - stringBytesRead; 92 | int bytesReadThisTime = BaseStream.Read(stringConversionBuffer, stringBytesRead, bytesToRead); 93 | if (bytesReadThisTime == 0) { 94 | throw new EndOfStreamException(); 95 | } 96 | stringBytesRead += bytesReadThisTime; 97 | } 98 | return Encoding.UTF8.GetString(stringConversionBuffer, 0, length); 99 | } else { 100 | byte[] stringData = ReadBytes(length); 101 | if (stringData.Length < length) { 102 | throw new EndOfStreamException(); 103 | } 104 | return Encoding.UTF8.GetString(stringData); 105 | } 106 | } 107 | 108 | 109 | public void Skip(int bytesToSkip) { 110 | if (bytesToSkip < 0) { 111 | throw new ArgumentOutOfRangeException(nameof(bytesToSkip)); 112 | } else if (BaseStream.CanSeek) { 113 | BaseStream.Position += bytesToSkip; 114 | } else if (bytesToSkip != 0) { 115 | if (seekBuffer == null) seekBuffer = new byte[SeekBufferSize]; 116 | int bytesSkipped = 0; 117 | while (bytesSkipped < bytesToSkip) { 118 | int bytesToRead = Math.Min(SeekBufferSize, bytesToSkip - bytesSkipped); 119 | int bytesReadThisTime = BaseStream.Read(seekBuffer, 0, bytesToRead); 120 | if (bytesReadThisTime == 0) { 121 | throw new EndOfStreamException(); 122 | } 123 | bytesSkipped += bytesReadThisTime; 124 | } 125 | } 126 | } 127 | 128 | 129 | new void FillBuffer(int numBytes) { 130 | int offset = 0; 131 | do { 132 | int num = BaseStream.Read(buffer, offset, numBytes - offset); 133 | if (num == 0) throw new EndOfStreamException(); 134 | offset += num; 135 | } while (offset < numBytes); 136 | } 137 | 138 | 139 | public void SkipString() { 140 | short length = ReadInt16(); 141 | if (length < 0) { 142 | throw new NbtFormatException("Negative string length given!"); 143 | } 144 | Skip(length); 145 | } 146 | 147 | 148 | [DebuggerStepThrough] 149 | static short Swap(short v) { 150 | unchecked { 151 | return (short)((v >> 8) & 0x00FF | 152 | (v << 8) & 0xFF00); 153 | } 154 | } 155 | 156 | 157 | [DebuggerStepThrough] 158 | static int Swap(int v) { 159 | unchecked { 160 | var v2 = (uint)v; 161 | return (int)((v2 >> 24) & 0x000000FF | 162 | (v2 >> 8) & 0x0000FF00 | 163 | (v2 << 8) & 0x00FF0000 | 164 | (v2 << 24) & 0xFF000000); 165 | } 166 | } 167 | 168 | 169 | [DebuggerStepThrough] 170 | static long Swap(long v) { 171 | unchecked { 172 | return (Swap((int)v) & uint.MaxValue) << 32 | 173 | Swap((int)(v >> 32)) & uint.MaxValue; 174 | } 175 | } 176 | 177 | 178 | public TagSelector? Selector { get; set; } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /fNbt/NbtBinaryWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// BinaryWriter wrapper that writes NBT primitives to a stream, 7 | /// while taking care of endianness and string encoding, and counting bytes written. 8 | internal sealed unsafe class NbtBinaryWriter { 9 | // Write at most 4 MiB at a time. 10 | public const int MaxWriteChunk = 4 * 1024 * 1024; 11 | 12 | // Encoding can be shared among all instances of NbtBinaryWriter, because it is stateless. 13 | static readonly UTF8Encoding Encoding = new UTF8Encoding(false, true); 14 | 15 | // Each instance has to have its own encoder, because it does maintain state. 16 | readonly Encoder encoder = Encoding.GetEncoder(); 17 | 18 | public Stream BaseStream { 19 | get { 20 | stream.Flush(); 21 | return stream; 22 | } 23 | } 24 | 25 | readonly Stream stream; 26 | 27 | // Buffer used for temporary conversion 28 | const int BufferSize = 256; 29 | 30 | // UTF8 characters use at most 4 bytes each. 31 | const int MaxBufferedStringLength = BufferSize / 4; 32 | 33 | // Each NbtBinaryWriter needs to have its own instance of the buffer. 34 | readonly byte[] buffer = new byte[BufferSize]; 35 | 36 | // Swap is only needed if endianness of the runtime differs from desired NBT stream 37 | readonly bool swapNeeded; 38 | 39 | 40 | public NbtBinaryWriter(Stream input, bool bigEndian) { 41 | if (input == null) throw new ArgumentNullException(nameof(input)); 42 | if (!input.CanWrite) throw new ArgumentException("Given stream must be writable", nameof(input)); 43 | stream = input; 44 | swapNeeded = (BitConverter.IsLittleEndian == bigEndian); 45 | } 46 | 47 | 48 | public void Write(byte value) { 49 | stream.WriteByte(value); 50 | } 51 | 52 | 53 | public void Write(NbtTagType value) { 54 | stream.WriteByte((byte)value); 55 | } 56 | 57 | 58 | public void Write(short value) { 59 | unchecked { 60 | if (swapNeeded) { 61 | buffer[0] = (byte)(value >> 8); 62 | buffer[1] = (byte)value; 63 | } else { 64 | buffer[0] = (byte)value; 65 | buffer[1] = (byte)(value >> 8); 66 | } 67 | } 68 | stream.Write(buffer, 0, 2); 69 | } 70 | 71 | 72 | public void Write(int value) { 73 | unchecked { 74 | if (swapNeeded) { 75 | buffer[0] = (byte)(value >> 24); 76 | buffer[1] = (byte)(value >> 16); 77 | buffer[2] = (byte)(value >> 8); 78 | buffer[3] = (byte)value; 79 | } else { 80 | buffer[0] = (byte)value; 81 | buffer[1] = (byte)(value >> 8); 82 | buffer[2] = (byte)(value >> 16); 83 | buffer[3] = (byte)(value >> 24); 84 | } 85 | } 86 | stream.Write(buffer, 0, 4); 87 | } 88 | 89 | 90 | public void Write(long value) { 91 | unchecked { 92 | if (swapNeeded) { 93 | buffer[0] = (byte)(value >> 56); 94 | buffer[1] = (byte)(value >> 48); 95 | buffer[2] = (byte)(value >> 40); 96 | buffer[3] = (byte)(value >> 32); 97 | buffer[4] = (byte)(value >> 24); 98 | buffer[5] = (byte)(value >> 16); 99 | buffer[6] = (byte)(value >> 8); 100 | buffer[7] = (byte)value; 101 | } else { 102 | buffer[0] = (byte)value; 103 | buffer[1] = (byte)(value >> 8); 104 | buffer[2] = (byte)(value >> 16); 105 | buffer[3] = (byte)(value >> 24); 106 | buffer[4] = (byte)(value >> 32); 107 | buffer[5] = (byte)(value >> 40); 108 | buffer[6] = (byte)(value >> 48); 109 | buffer[7] = (byte)(value >> 56); 110 | } 111 | } 112 | stream.Write(buffer, 0, 8); 113 | } 114 | 115 | 116 | public void Write(float value) { 117 | ulong tmpValue = *(uint*)&value; 118 | unchecked { 119 | if (swapNeeded) { 120 | buffer[0] = (byte)(tmpValue >> 24); 121 | buffer[1] = (byte)(tmpValue >> 16); 122 | buffer[2] = (byte)(tmpValue >> 8); 123 | buffer[3] = (byte)tmpValue; 124 | } else { 125 | buffer[0] = (byte)tmpValue; 126 | buffer[1] = (byte)(tmpValue >> 8); 127 | buffer[2] = (byte)(tmpValue >> 16); 128 | buffer[3] = (byte)(tmpValue >> 24); 129 | } 130 | } 131 | stream.Write(buffer, 0, 4); 132 | } 133 | 134 | 135 | public void Write(double value) { 136 | ulong tmpValue = *(ulong*)&value; 137 | unchecked { 138 | if (swapNeeded) { 139 | buffer[0] = (byte)(tmpValue >> 56); 140 | buffer[1] = (byte)(tmpValue >> 48); 141 | buffer[2] = (byte)(tmpValue >> 40); 142 | buffer[3] = (byte)(tmpValue >> 32); 143 | buffer[4] = (byte)(tmpValue >> 24); 144 | buffer[5] = (byte)(tmpValue >> 16); 145 | buffer[6] = (byte)(tmpValue >> 8); 146 | buffer[7] = (byte)tmpValue; 147 | } else { 148 | buffer[0] = (byte)tmpValue; 149 | buffer[1] = (byte)(tmpValue >> 8); 150 | buffer[2] = (byte)(tmpValue >> 16); 151 | buffer[3] = (byte)(tmpValue >> 24); 152 | buffer[4] = (byte)(tmpValue >> 32); 153 | buffer[5] = (byte)(tmpValue >> 40); 154 | buffer[6] = (byte)(tmpValue >> 48); 155 | buffer[7] = (byte)(tmpValue >> 56); 156 | } 157 | } 158 | stream.Write(buffer, 0, 8); 159 | } 160 | 161 | 162 | // Based on BinaryWriter.Write(String) 163 | public void Write(string value) { 164 | if (value == null) { 165 | throw new ArgumentNullException(nameof(value)); 166 | } 167 | 168 | // Write out string length (as number of bytes) 169 | int numBytes = Encoding.GetByteCount(value); 170 | Write((short)numBytes); 171 | 172 | if (numBytes <= BufferSize) { 173 | // If the string fits entirely in the buffer, encode and write it as one 174 | Encoding.GetBytes(value, 0, value.Length, buffer, 0); 175 | stream.Write(buffer, 0, numBytes); 176 | } else { 177 | // Aggressively try to not allocate memory in this loop for runtime performance reasons. 178 | // Use an Encoder to write out the string correctly (handling surrogates crossing buffer 179 | // boundaries properly). 180 | int charStart = 0; 181 | int numLeft = value.Length; 182 | while (numLeft > 0) { 183 | // Figure out how many chars to process this round. 184 | int charCount = (numLeft > MaxBufferedStringLength) ? MaxBufferedStringLength : numLeft; 185 | int byteLen; 186 | fixed (char* pChars = value) { 187 | fixed (byte* pBytes = buffer) { 188 | byteLen = encoder.GetBytes(pChars + charStart, charCount, pBytes, BufferSize, 189 | charCount == numLeft); 190 | } 191 | } 192 | stream.Write(buffer, 0, byteLen); 193 | charStart += charCount; 194 | numLeft -= charCount; 195 | } 196 | } 197 | } 198 | 199 | 200 | public void Write(byte[] data, int offset, int count) { 201 | int written = 0; 202 | while (written < count) { 203 | int toWrite = Math.Min(MaxWriteChunk, count - written); 204 | stream.Write(data, offset + written, toWrite); 205 | written += toWrite; 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /fNbt/NbtCompression.cs: -------------------------------------------------------------------------------- 1 | namespace fNbt { 2 | /// Compression method used for loading/saving NBT files. 3 | public enum NbtCompression { 4 | /// Automatically detect file compression. Not a valid format for saving. 5 | AutoDetect, 6 | 7 | /// No compression. 8 | None, 9 | 10 | /// Compressed, with GZip header (default). 11 | GZip, 12 | 13 | /// Compressed, with ZLib header (RFC-1950). 14 | ZLib 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fNbt/NbtFlavor.cs: -------------------------------------------------------------------------------- 1 | namespace fNbt { 2 | /// Provides options for reading and writing NBT files and network streams in different versions of the NBT format. 3 | public sealed class NbtFlavor { 4 | /// 5 | /// Creates a new NbtFlavor with specified custom options. 6 | /// 7 | /// Whether numbers are encoded in BigEndian (Java), instead of LittleEndian (Bedrock) byte order. Default is true. 8 | /// Whether lists are allowed to be root tags. 9 | /// Whether to allow tags (1.2.1+). Default is true. 10 | /// Whether to allow tags (1.12+). Default is true. 11 | /// Whether to use unnamed compound tags for root tags. 12 | /// Whether to use VarInts with ZigZag encoding to store most numbers. 13 | public NbtFlavor(bool bigEndian = true, bool allowListRootTag = false, bool allowIntArray = true, bool allowLongArray = true, bool unnamedRootTag = false, bool useVarInt = false) { 14 | BigEndian = bigEndian; 15 | AllowListRootTag = allowListRootTag; 16 | AllowIntArray = allowIntArray; 17 | AllowLongArray = allowLongArray; 18 | UnnamedRootTag = unnamedRootTag; 19 | UseVarInt = useVarInt; 20 | } 21 | 22 | /// Whether numbers are encoded in BigEndian (Java) or LittleEndian (Bedrock) byte order. Default is true. 23 | public bool BigEndian { get; private set; } 24 | 25 | /// Whether lists are allowed to be root tags. 26 | public bool AllowListRootTag { get; private set; } 27 | 28 | /// Whether to allow tags (1.2.1+). Default is true. 29 | public bool AllowIntArray { get; private set; } 30 | 31 | /// Whether to allow tags (1.12+). Default is true. 32 | public bool AllowLongArray { get; private set; } 33 | 34 | /// Whether to use unnamed compound tags for root tags. 35 | public bool UnnamedRootTag { get; private set; } 36 | 37 | /// Whether to use VarInts with ZigZag encoding to store most numbers. 38 | public bool UseVarInt { get; private set; } 39 | 40 | /// 41 | /// Appropriate options for reading and writing NBT files and network streams in the original format, 42 | /// used for Indev, Infdev, Alpha, Beta, and Java Editions up through version 1.1. 43 | /// 44 | public static NbtFlavor JavaLegacy { get; } = new() { 45 | AllowIntArray = false, 46 | AllowLongArray = false, 47 | }; 48 | 49 | /// 50 | /// Appropriate options for reading and writing NBT files and network streams with IntArray tags, 51 | /// introduced as part of the Anvil format in Java Edition 1.2.1 and used until 1.12. 52 | /// 53 | public static NbtFlavor JavaAnvil { get; } = new() { 54 | AllowLongArray = false, 55 | }; 56 | 57 | /// 58 | /// Appropriate options for reading and writing NBT files with IntArray and LongArray tags, 59 | /// introduced by Java Edition 1.12 (World of Color) and still used in files today. 60 | /// Also works for network streams as used by Java Edition 1.12 through 1.20.1, but not later. 61 | /// 62 | public static NbtFlavor JavaWorldOfColor { get; } = new(); 63 | 64 | /// 65 | /// Appropriate options for reading and writing NBT network streams as used by Java Edition 1.20.2 and later. 66 | /// Almost identical to but usses unnamed compound tags for roots, 67 | /// which was not allowed other flavor of NBT. Should not be used for loading/saving files. 68 | /// 69 | public static NbtFlavor JavaNetwork { get; } = new() { 70 | UnnamedRootTag = true, 71 | }; 72 | 73 | /// 74 | /// Appropriate options for reading and writing NBT files and network streams in the Bedrock Edition format. 75 | /// Supports IntArrays and LongArrays, allows lists in addition to compounds as root tags, and encodes 76 | /// numbers using VarInts. 77 | /// 78 | public static NbtFlavor Bedrock { get; } = new() { 79 | BigEndian = false, 80 | AllowListRootTag = true, 81 | UseVarInt = true 82 | }; 83 | 84 | /// 85 | /// The default options for reading and writing NBT files and network streams, set to . 86 | /// 87 | public static NbtFlavor Default { get; set; } = new(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /fNbt/NbtFormatException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace fNbt { 4 | /// Exception thrown when a format violation is detected while 5 | /// parsing or serializing an NBT file. 6 | [Serializable] 7 | public sealed class NbtFormatException : Exception { 8 | internal NbtFormatException(string message) 9 | : base(message) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /fNbt/NbtParseState.cs: -------------------------------------------------------------------------------- 1 | namespace fNbt { 2 | internal enum NbtParseState { 3 | AtStreamBeginning, 4 | AtCompoundBeginning, 5 | InCompound, 6 | AtCompoundEnd, 7 | AtListBeginning, 8 | InList, 9 | AtStreamEnd, 10 | Error 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /fNbt/NbtReaderNode.cs: -------------------------------------------------------------------------------- 1 | namespace fNbt { 2 | // Represents state of a node in the NBT file tree, used by NbtReader 3 | internal sealed class NbtReaderNode { 4 | public string? ParentName; 5 | public NbtTagType ParentTagType; 6 | public NbtTagType ListType; 7 | public int ParentTagLength; 8 | public int ListIndex; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /fNbt/NbtTagType.cs: -------------------------------------------------------------------------------- 1 | namespace fNbt { 2 | /// Enumeration of named binary tag types, and their corresponding codes. 3 | public enum NbtTagType : byte { 4 | /// Placeholder TagType used to indicate unknown/undefined tag type in NbtList. 5 | Unknown = 0xff, 6 | 7 | /// TAG_End: This unnamed tag serves no purpose but to signify the end of an open TAG_Compound. 8 | End = 0x00, 9 | 10 | /// TAG_Byte: A single byte. 11 | Byte = 0x01, 12 | 13 | /// TAG_Short: A single signed 16-bit integer. 14 | Short = 0x02, 15 | 16 | /// TAG_Int: A single signed 32-bit integer. 17 | Int = 0x03, 18 | 19 | /// TAG_Long: A single signed 64-bit integer. 20 | Long = 0x04, 21 | 22 | /// TAG_Float: A single IEEE-754 single-precision floating point number. 23 | Float = 0x05, 24 | 25 | /// TAG_Double: A single IEEE-754 double-precision floating point number. 26 | Double = 0x06, 27 | 28 | /// TAG_Byte_Array: A length-prefixed array of bytes. 29 | ByteArray = 0x07, 30 | 31 | /// TAG_String: A length-prefixed UTF-8 string. 32 | String = 0x08, 33 | 34 | /// TAG_List: A list of nameless tags, all of the same type. 35 | List = 0x09, 36 | 37 | /// TAG_Compound: A set of named tags. 38 | Compound = 0x0a, 39 | 40 | /// TAG_Int_Array: A length-prefixed array of signed 32-bit integers. 41 | IntArray = 0x0b, 42 | 43 | /// TAG_Long_Array: A length-prefixed array of signed 64-bit integers. 44 | LongArray = 0x0c 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /fNbt/NbtWriterNode.cs: -------------------------------------------------------------------------------- 1 | namespace fNbt { 2 | // Represents state of a node in the NBT file tree, used by NbtWriter 3 | internal sealed class NbtWriterNode { 4 | public NbtTagType ParentType; 5 | public NbtTagType ListType; 6 | public int ListSize; 7 | public int ListIndex; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fNbt/NullableSupport.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace fNbt { 5 | internal sealed class NullableSupport { 6 | /// 7 | [Conditional("DEBUG")] 8 | public static void Assert([DoesNotReturnIf(false)] bool b) => Debug.Assert(b); 9 | } 10 | } 11 | 12 | // Copied from https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs 13 | // and changed from public to internal. 14 | namespace System.Diagnostics.CodeAnalysis 15 | { 16 | /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. 17 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 18 | internal sealed class DoesNotReturnIfAttribute : Attribute 19 | { 20 | /// Initializes the attribute with the specified parameter value. 21 | /// 22 | /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to 23 | /// the associated parameter matches this value. 24 | /// 25 | public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; 26 | 27 | /// Gets the condition parameter value. 28 | public bool ParameterValue { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /fNbt/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | [assembly: AssemblyTitle("fNbt")] 10 | [assembly: AssemblyDescription("A library for working with NBT files and streams.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("github.com/mstefarov/fNbt")] 13 | [assembly: AssemblyProduct("fNbt")] 14 | [assembly: AssemblyCopyright("2012-2024 Matvei Stefarov")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | 26 | [assembly: Guid("9253db1f-f1d4-45aa-a277-4f3ba635d651")] 27 | 28 | // Version information for an assembly consists of the following four values: 29 | // 30 | // Major Version 31 | // Minor Version 32 | // Build Number 33 | // Revision 34 | // 35 | // You can specify all the values or you can default the Build and Revision Numbers 36 | // by using the '*' as shown below: 37 | // [assembly: AssemblyVersion("1.0.*")] 38 | 39 | [assembly: AssemblyVersion("1.0.0.0")] 40 | [assembly: AssemblyFileVersion("1.0.0.0")] 41 | 42 | // Potentially speed up resource probes 43 | 44 | [assembly: NeutralResourcesLanguage("en-US")] 45 | -------------------------------------------------------------------------------- /fNbt/TagSelector.cs: -------------------------------------------------------------------------------- 1 | namespace fNbt { 2 | /// Delegate used to skip loading certain tags of an NBT stream/file. 3 | /// The callback should return "true" for any tag that should be read,and "false" for any tag that should be skipped. 4 | /// Tag that is being read. Tag's type and name are available, 5 | /// but the value has not yet been read at this time. Guaranteed to never be null. 6 | public delegate bool TagSelector(NbtTag tag); 7 | } 8 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtByte.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing a single byte. 7 | public sealed class NbtByte : NbtTag { 8 | /// Type of this tag (Byte). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.Byte; } 11 | } 12 | 13 | /// Value/payload of this tag (a single byte). 14 | public byte Value { get; set; } 15 | 16 | 17 | /// Creates an unnamed NbtByte tag with the default value of 0. 18 | public NbtByte() { } 19 | 20 | 21 | /// Creates an unnamed NbtByte tag with the given value. 22 | /// Value to assign to this tag. 23 | public NbtByte(byte value) 24 | : this(null, value) { } 25 | 26 | 27 | /// Creates an NbtByte tag with the given name and the default value of 0. 28 | /// Name to assign to this tag. May be null. 29 | public NbtByte(string? tagName) 30 | : this(tagName, 0) { } 31 | 32 | 33 | /// Creates an NbtByte tag with the given name and value. 34 | /// Name to assign to this tag. May be null. 35 | /// Value to assign to this tag. 36 | public NbtByte(string? tagName, byte value) { 37 | name = tagName; 38 | Value = value; 39 | } 40 | 41 | 42 | /// Creates a copy of given NbtByte tag. 43 | /// Tag to copy. May not be null. 44 | /// is null. 45 | public NbtByte(NbtByte other) { 46 | if (other == null) throw new ArgumentNullException(nameof(other)); 47 | name = other.name; 48 | Value = other.Value; 49 | } 50 | 51 | 52 | internal override bool ReadTag(NbtBinaryReader readStream) { 53 | if (readStream.Selector != null && !readStream.Selector(this)) { 54 | readStream.ReadByte(); 55 | return false; 56 | } 57 | Value = readStream.ReadByte(); 58 | return true; 59 | } 60 | 61 | 62 | internal override void SkipTag(NbtBinaryReader readStream) { 63 | readStream.ReadByte(); 64 | } 65 | 66 | 67 | internal override void WriteTag(NbtBinaryWriter writeStream) { 68 | writeStream.Write(NbtTagType.Byte); 69 | if (Name == null) throw new NbtFormatException("Name is null"); 70 | writeStream.Write(Name); 71 | writeStream.Write(Value); 72 | } 73 | 74 | 75 | internal override void WriteData(NbtBinaryWriter writeStream) { 76 | writeStream.Write(Value); 77 | } 78 | 79 | 80 | /// 81 | public override object Clone() { 82 | return new NbtByte(this); 83 | } 84 | 85 | 86 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 87 | for (int i = 0; i < indentLevel; i++) { 88 | sb.Append(indentString); 89 | } 90 | sb.Append("TAG_Byte"); 91 | if (!String.IsNullOrEmpty(Name)) { 92 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 93 | } 94 | sb.Append(": "); 95 | sb.Append(Value); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtByteArray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace fNbt { 7 | /// A tag containing an array of bytes. 8 | public sealed class NbtByteArray : NbtTag { 9 | /// Type of this tag (ByteArray). 10 | public override NbtTagType TagType { 11 | get { return NbtTagType.ByteArray; } 12 | } 13 | 14 | /// Value/payload of this tag (an array of bytes). Value is stored as-is and is NOT cloned. May not be null. 15 | /// is null. 16 | public byte[] Value { 17 | get { return bytes; } 18 | set { 19 | if (value == null) { 20 | throw new ArgumentNullException(nameof(value)); 21 | } 22 | bytes = value; 23 | } 24 | } 25 | 26 | byte[] bytes; 27 | 28 | 29 | /// Creates an unnamed NbtByte tag, containing an empty array of bytes. 30 | public NbtByteArray() 31 | : this((string?)null) { } 32 | 33 | 34 | /// Creates an unnamed NbtByte tag, containing the given array of bytes. 35 | /// Byte array to assign to this tag's Value. May not be null. 36 | /// is null. 37 | /// Given byte array will be cloned. To avoid unnecessary copying, call one of the other constructor 38 | /// overloads (that do not take a byte[]) and then set the Value property yourself. 39 | public NbtByteArray(byte[] value) 40 | : this(null, value) { } 41 | 42 | 43 | /// Creates an NbtByte tag with the given name, containing an empty array of bytes. 44 | /// Name to assign to this tag. May be null. 45 | public NbtByteArray(string? tagName) { 46 | name = tagName; 47 | bytes = Array.Empty(); 48 | } 49 | 50 | 51 | /// Creates an NbtByte tag with the given name, containing the given array of bytes. 52 | /// Name to assign to this tag. May be null. 53 | /// Byte array to assign to this tag's Value. May not be null. 54 | /// is null. 55 | /// Given byte array will be cloned. To avoid unnecessary copying, call one of the other constructor 56 | /// overloads (that do not take a byte[]) and then set the Value property yourself. 57 | public NbtByteArray(string? tagName, byte[] value) { 58 | if (value == null) throw new ArgumentNullException(nameof(value)); 59 | name = tagName; 60 | bytes = (byte[])value.Clone(); 61 | } 62 | 63 | 64 | /// Creates a deep copy of given NbtByteArray. 65 | /// Tag to copy. May not be null. 66 | /// is null. 67 | /// Byte array of given tag will be cloned. 68 | public NbtByteArray(NbtByteArray other) { 69 | if (other == null) throw new ArgumentNullException(nameof(other)); 70 | name = other.name; 71 | bytes = (byte[])other.Value.Clone(); 72 | } 73 | 74 | 75 | /// Gets or sets a byte at the given index. 76 | /// The zero-based index of the element to get or set. 77 | /// The byte at the specified index. 78 | /// is outside the array bounds. 79 | public new byte this[int tagIndex] { 80 | get { return Value[tagIndex]; } 81 | set { Value[tagIndex] = value; } 82 | } 83 | 84 | 85 | internal override bool ReadTag(NbtBinaryReader readStream) { 86 | int length = readStream.ReadInt32(); 87 | if (length < 0) { 88 | throw new NbtFormatException("Negative length given in TAG_Byte_Array"); 89 | } 90 | 91 | if (readStream.Selector != null && !readStream.Selector(this)) { 92 | readStream.Skip(length); 93 | return false; 94 | } 95 | Value = readStream.ReadBytes(length); 96 | if (Value.Length < length) { 97 | throw new EndOfStreamException(); 98 | } 99 | return true; 100 | } 101 | 102 | 103 | internal override void SkipTag(NbtBinaryReader readStream) { 104 | int length = readStream.ReadInt32(); 105 | if (length < 0) { 106 | throw new NbtFormatException("Negative length given in TAG_Byte_Array"); 107 | } 108 | readStream.Skip(length); 109 | } 110 | 111 | 112 | internal override void WriteTag(NbtBinaryWriter writeStream) { 113 | writeStream.Write(NbtTagType.ByteArray); 114 | if (Name == null) throw new NbtFormatException("Name is null"); 115 | writeStream.Write(Name); 116 | WriteData(writeStream); 117 | } 118 | 119 | 120 | internal override void WriteData(NbtBinaryWriter writeStream) { 121 | writeStream.Write(Value.Length); 122 | writeStream.Write(Value, 0, Value.Length); 123 | } 124 | 125 | 126 | /// 127 | public override object Clone() { 128 | return new NbtByteArray(this); 129 | } 130 | 131 | 132 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 133 | for (int i = 0; i < indentLevel; i++) { 134 | sb.Append(indentString); 135 | } 136 | sb.Append("TAG_Byte_Array"); 137 | if (!String.IsNullOrEmpty(Name)) { 138 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 139 | } 140 | sb.AppendFormat(CultureInfo.InvariantCulture, ": [{0} bytes]", bytes.Length); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtDouble.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing a double-precision floating point number. 7 | public sealed class NbtDouble : NbtTag { 8 | /// Type of this tag (Double). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.Double; } 11 | } 12 | 13 | /// Value/payload of this tag (a double-precision floating point number). 14 | public double Value { get; set; } 15 | 16 | 17 | /// Creates an unnamed NbtDouble tag with the default value of 0. 18 | public NbtDouble() { } 19 | 20 | 21 | /// Creates an unnamed NbtDouble tag with the given value. 22 | /// Value to assign to this tag. 23 | public NbtDouble(double value) 24 | : this(null, value) { } 25 | 26 | 27 | /// Creates an NbtDouble tag with the given name and the default value of 0. 28 | /// Name to assign to this tag. May be null. 29 | public NbtDouble(string? tagName) 30 | : this(tagName, 0) { } 31 | 32 | 33 | /// Creates an NbtDouble tag with the given name and value. 34 | /// Name to assign to this tag. May be null. 35 | /// Value to assign to this tag. 36 | public NbtDouble(string? tagName, double value) { 37 | name = tagName; 38 | Value = value; 39 | } 40 | 41 | 42 | /// Creates a copy of given NbtDouble tag. 43 | /// Tag to copy. May not be null. 44 | /// is null. 45 | public NbtDouble(NbtDouble other) { 46 | if (other == null) throw new ArgumentNullException(nameof(other)); 47 | name = other.name; 48 | Value = other.Value; 49 | } 50 | 51 | 52 | internal override bool ReadTag(NbtBinaryReader readStream) { 53 | if (readStream.Selector != null && !readStream.Selector(this)) { 54 | readStream.ReadDouble(); 55 | return false; 56 | } 57 | Value = readStream.ReadDouble(); 58 | return true; 59 | } 60 | 61 | 62 | internal override void SkipTag(NbtBinaryReader readStream) { 63 | readStream.ReadDouble(); 64 | } 65 | 66 | 67 | internal override void WriteTag(NbtBinaryWriter writeStream) { 68 | writeStream.Write(NbtTagType.Double); 69 | if (Name == null) throw new NbtFormatException("Name is null"); 70 | writeStream.Write(Name); 71 | writeStream.Write(Value); 72 | } 73 | 74 | 75 | internal override void WriteData(NbtBinaryWriter writeStream) { 76 | writeStream.Write(Value); 77 | } 78 | 79 | 80 | /// 81 | public override object Clone() { 82 | return new NbtDouble(this); 83 | } 84 | 85 | 86 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 87 | for (int i = 0; i < indentLevel; i++) { 88 | sb.Append(indentString); 89 | } 90 | sb.Append("TAG_Double"); 91 | if (!String.IsNullOrEmpty(Name)) { 92 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 93 | } 94 | sb.Append(": "); 95 | sb.Append(Value); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtFloat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing a single-precision floating point number. 7 | public sealed class NbtFloat : NbtTag { 8 | /// Type of this tag (Float). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.Float; } 11 | } 12 | 13 | /// Value/payload of this tag (a single-precision floating point number). 14 | public float Value { get; set; } 15 | 16 | 17 | /// Creates an unnamed NbtFloat tag with the default value of 0f. 18 | public NbtFloat() { } 19 | 20 | 21 | /// Creates an unnamed NbtFloat tag with the given value. 22 | /// Value to assign to this tag. 23 | public NbtFloat(float value) 24 | : this(null, value) { } 25 | 26 | 27 | /// Creates an NbtFloat tag with the given name and the default value of 0f. 28 | /// Name to assign to this tag. May be null. 29 | public NbtFloat(string? tagName) 30 | : this(tagName, 0) { } 31 | 32 | 33 | /// Creates an NbtFloat tag with the given name and value. 34 | /// Name to assign to this tag. May be null. 35 | /// Value to assign to this tag. 36 | public NbtFloat(string? tagName, float value) { 37 | name = tagName; 38 | Value = value; 39 | } 40 | 41 | 42 | /// Creates a copy of given NbtFloat tag. 43 | /// Tag to copy. May not be null. 44 | /// is null. 45 | public NbtFloat(NbtFloat other) { 46 | if (other == null) throw new ArgumentNullException(nameof(other)); 47 | name = other.name; 48 | Value = other.Value; 49 | } 50 | 51 | 52 | internal override bool ReadTag(NbtBinaryReader readStream) { 53 | if (readStream.Selector != null && !readStream.Selector(this)) { 54 | readStream.ReadSingle(); 55 | return false; 56 | } 57 | Value = readStream.ReadSingle(); 58 | return true; 59 | } 60 | 61 | 62 | internal override void SkipTag(NbtBinaryReader readStream) { 63 | readStream.ReadSingle(); 64 | } 65 | 66 | 67 | internal override void WriteTag(NbtBinaryWriter writeStream) { 68 | writeStream.Write(NbtTagType.Float); 69 | if (Name == null) throw new NbtFormatException("Name is null"); 70 | writeStream.Write(Name); 71 | writeStream.Write(Value); 72 | } 73 | 74 | 75 | internal override void WriteData(NbtBinaryWriter writeStream) { 76 | writeStream.Write(Value); 77 | } 78 | 79 | 80 | /// 81 | public override object Clone() { 82 | return new NbtFloat(this); 83 | } 84 | 85 | 86 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 87 | for (int i = 0; i < indentLevel; i++) { 88 | sb.Append(indentString); 89 | } 90 | sb.Append("TAG_Float"); 91 | if (!String.IsNullOrEmpty(Name)) { 92 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 93 | } 94 | sb.Append(": "); 95 | sb.Append(Value); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtInt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing a single signed 32-bit integer. 7 | public sealed class NbtInt : NbtTag { 8 | /// Type of this tag (Int). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.Int; } 11 | } 12 | 13 | /// Value/payload of this tag (a single signed 32-bit integer). 14 | public int Value { get; set; } 15 | 16 | 17 | /// Creates an unnamed NbtInt tag with the default value of 0. 18 | public NbtInt() { } 19 | 20 | 21 | /// Creates an unnamed NbtInt tag with the given value. 22 | /// Value to assign to this tag. 23 | public NbtInt(int value) 24 | : this(null, value) { } 25 | 26 | 27 | /// Creates an NbtInt tag with the given name and the default value of 0. 28 | /// Name to assign to this tag. May be null. 29 | public NbtInt(string? tagName) 30 | : this(tagName, 0) { } 31 | 32 | 33 | /// Creates an NbtInt tag with the given name and value. 34 | /// Name to assign to this tag. May be null. 35 | /// Value to assign to this tag. 36 | public NbtInt(string? tagName, int value) { 37 | name = tagName; 38 | Value = value; 39 | } 40 | 41 | 42 | /// Creates a copy of given NbtInt tag. 43 | /// Tag to copy. May not be null. 44 | /// is null. 45 | public NbtInt(NbtInt other) { 46 | if (other == null) throw new ArgumentNullException(nameof(other)); 47 | name = other.name; 48 | Value = other.Value; 49 | } 50 | 51 | 52 | internal override bool ReadTag(NbtBinaryReader readStream) { 53 | if (readStream.Selector != null && !readStream.Selector(this)) { 54 | readStream.ReadInt32(); 55 | return false; 56 | } 57 | Value = readStream.ReadInt32(); 58 | return true; 59 | } 60 | 61 | 62 | internal override void SkipTag(NbtBinaryReader readStream) { 63 | readStream.ReadInt32(); 64 | } 65 | 66 | 67 | internal override void WriteTag(NbtBinaryWriter writeStream) { 68 | writeStream.Write(NbtTagType.Int); 69 | if (Name == null) throw new NbtFormatException("Name is null"); 70 | writeStream.Write(Name); 71 | writeStream.Write(Value); 72 | } 73 | 74 | 75 | internal override void WriteData(NbtBinaryWriter writeStream) { 76 | writeStream.Write(Value); 77 | } 78 | 79 | 80 | /// 81 | public override object Clone() { 82 | return new NbtInt(this); 83 | } 84 | 85 | 86 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 87 | for (int i = 0; i < indentLevel; i++) { 88 | sb.Append(indentString); 89 | } 90 | sb.Append("TAG_Int"); 91 | if (!String.IsNullOrEmpty(Name)) { 92 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 93 | } 94 | sb.Append(": "); 95 | sb.Append(Value); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtIntArray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing an array of signed 32-bit integers. 7 | public sealed class NbtIntArray : NbtTag { 8 | /// Type of this tag (ByteArray). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.IntArray; } 11 | } 12 | 13 | /// Value/payload of this tag (an array of signed 32-bit integers). Value is stored as-is and is NOT cloned. May not be null. 14 | /// is null. 15 | public int[] Value { 16 | get { return ints; } 17 | set { 18 | if (value == null) { 19 | throw new ArgumentNullException(nameof(value)); 20 | } 21 | ints = value; 22 | } 23 | } 24 | 25 | int[] ints; 26 | 27 | 28 | /// Creates an unnamed NbtIntArray tag, containing an empty array of ints. 29 | public NbtIntArray() 30 | : this((string?)null) { } 31 | 32 | 33 | /// Creates an unnamed NbtIntArray tag, containing the given array of ints. 34 | /// Int array to assign to this tag's Value. May not be null. 35 | /// is null. 36 | /// Given int array will be cloned. To avoid unnecessary copying, call one of the other constructor 37 | /// overloads (that do not take a int[]) and then set the Value property yourself. 38 | public NbtIntArray(int[] value) 39 | : this(null, value) { } 40 | 41 | 42 | /// Creates an NbtIntArray tag with the given name, containing an empty array of ints. 43 | /// Name to assign to this tag. May be null. 44 | public NbtIntArray(string? tagName) { 45 | name = tagName; 46 | ints = Array.Empty(); 47 | } 48 | 49 | 50 | /// Creates an NbtIntArray tag with the given name, containing the given array of ints. 51 | /// Name to assign to this tag. May be null. 52 | /// Int array to assign to this tag's Value. May not be null. 53 | /// is null. 54 | /// Given int array will be cloned. To avoid unnecessary copying, call one of the other constructor 55 | /// overloads (that do not take a int[]) and then set the Value property yourself. 56 | public NbtIntArray(string? tagName, int[] value) { 57 | if (value == null) throw new ArgumentNullException(nameof(value)); 58 | name = tagName; 59 | ints = (int[])value.Clone(); 60 | } 61 | 62 | 63 | /// Creates a deep copy of given NbtIntArray. 64 | /// Tag to copy. May not be null. 65 | /// is null. 66 | /// Int array of given tag will be cloned. 67 | public NbtIntArray(NbtIntArray other) { 68 | if (other == null) throw new ArgumentNullException(nameof(other)); 69 | name = other.name; 70 | ints = (int[])other.Value.Clone(); 71 | } 72 | 73 | 74 | /// Gets or sets an integer at the given index. 75 | /// The zero-based index of the element to get or set. 76 | /// The integer at the specified index. 77 | /// is outside the array bounds. 78 | public new int this[int tagIndex] { 79 | get { return Value[tagIndex]; } 80 | set { Value[tagIndex] = value; } 81 | } 82 | 83 | 84 | internal override bool ReadTag(NbtBinaryReader readStream) { 85 | int length = readStream.ReadInt32(); 86 | if (length < 0) { 87 | throw new NbtFormatException("Negative length given in TAG_Int_Array"); 88 | } 89 | 90 | if (readStream.Selector != null && !readStream.Selector(this)) { 91 | readStream.Skip(length * sizeof(int)); 92 | return false; 93 | } 94 | 95 | Value = new int[length]; 96 | for (int i = 0; i < length; i++) { 97 | Value[i] = readStream.ReadInt32(); 98 | } 99 | return true; 100 | } 101 | 102 | 103 | internal override void SkipTag(NbtBinaryReader readStream) { 104 | int length = readStream.ReadInt32(); 105 | if (length < 0) { 106 | throw new NbtFormatException("Negative length given in TAG_Int_Array"); 107 | } 108 | readStream.Skip(length * sizeof(int)); 109 | } 110 | 111 | 112 | internal override void WriteTag(NbtBinaryWriter writeStream) { 113 | writeStream.Write(NbtTagType.IntArray); 114 | if (Name == null) throw new NbtFormatException("Name is null"); 115 | writeStream.Write(Name); 116 | WriteData(writeStream); 117 | } 118 | 119 | 120 | internal override void WriteData(NbtBinaryWriter writeStream) { 121 | writeStream.Write(Value.Length); 122 | for (int i = 0; i < Value.Length; i++) { 123 | writeStream.Write(Value[i]); 124 | } 125 | } 126 | 127 | 128 | /// 129 | public override object Clone() { 130 | return new NbtIntArray(this); 131 | } 132 | 133 | 134 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 135 | for (int i = 0; i < indentLevel; i++) { 136 | sb.Append(indentString); 137 | } 138 | sb.Append("TAG_Int_Array"); 139 | if (!String.IsNullOrEmpty(Name)) { 140 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 141 | } 142 | sb.AppendFormat(CultureInfo.InvariantCulture, ": [{0} ints]", ints.Length); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtLong.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing a single signed 64-bit integer. 7 | public sealed class NbtLong : NbtTag { 8 | /// Type of this tag (Long). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.Long; } 11 | } 12 | 13 | /// Value/payload of this tag (a single signed 64-bit integer). 14 | public long Value { get; set; } 15 | 16 | 17 | /// Creates an unnamed NbtLong tag with the default value of 0. 18 | public NbtLong() { } 19 | 20 | 21 | /// Creates an unnamed NbtLong tag with the given value. 22 | /// Value to assign to this tag. 23 | public NbtLong(long value) 24 | : this(null, value) { } 25 | 26 | 27 | /// Creates an NbtLong tag with the given name and the default value of 0. 28 | /// Name to assign to this tag. May be null. 29 | public NbtLong(string? tagName) 30 | : this(tagName, 0) { } 31 | 32 | 33 | /// Creates an NbtLong tag with the given name and value. 34 | /// Name to assign to this tag. May be null. 35 | /// Value to assign to this tag. 36 | public NbtLong(string? tagName, long value) { 37 | name = tagName; 38 | Value = value; 39 | } 40 | 41 | 42 | /// Creates a copy of given NbtLong tag. 43 | /// Tag to copy. May not be null. 44 | /// is null. 45 | public NbtLong(NbtLong other) { 46 | if (other == null) throw new ArgumentNullException(nameof(other)); 47 | name = other.name; 48 | Value = other.Value; 49 | } 50 | 51 | 52 | #region Reading / Writing 53 | 54 | internal override bool ReadTag(NbtBinaryReader readStream) { 55 | if (readStream.Selector != null && !readStream.Selector(this)) { 56 | readStream.ReadInt64(); 57 | return false; 58 | } 59 | Value = readStream.ReadInt64(); 60 | return true; 61 | } 62 | 63 | 64 | internal override void SkipTag(NbtBinaryReader readStream) { 65 | readStream.ReadInt64(); 66 | } 67 | 68 | 69 | internal override void WriteTag(NbtBinaryWriter writeStream) { 70 | writeStream.Write(NbtTagType.Long); 71 | if (Name == null) throw new NbtFormatException("Name is null"); 72 | writeStream.Write(Name); 73 | writeStream.Write(Value); 74 | } 75 | 76 | 77 | internal override void WriteData(NbtBinaryWriter writeStream) { 78 | writeStream.Write(Value); 79 | } 80 | 81 | #endregion 82 | 83 | 84 | /// 85 | public override object Clone() { 86 | return new NbtLong(this); 87 | } 88 | 89 | 90 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 91 | for (int i = 0; i < indentLevel; i++) { 92 | sb.Append(indentString); 93 | } 94 | sb.Append("TAG_Long"); 95 | if (!String.IsNullOrEmpty(Name)) { 96 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 97 | } 98 | sb.Append(": "); 99 | sb.Append(Value); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtLongArray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing an array of signed 64-bit integers. 7 | public sealed class NbtLongArray : NbtTag { 8 | /// Type of this tag (LongArray). 9 | public override NbtTagType TagType { 10 | get { 11 | return NbtTagType.LongArray; 12 | } 13 | } 14 | 15 | /// Value/payload of this tag (an array of signed 64-bit integers). Value is stored as-is and is NOT cloned. May not be null. 16 | /// is null. 17 | public long[] Value { 18 | get { return longs; } 19 | set { 20 | if (value == null) { 21 | throw new ArgumentNullException(nameof(value)); 22 | } 23 | 24 | longs = value; 25 | } 26 | } 27 | 28 | private long[] longs; 29 | 30 | /// Creates an unnamed NbtLongArray tag, containing an empty array of longs. 31 | public NbtLongArray() 32 | : this((string?)null) { } 33 | 34 | /// Creates an unnamed NbtLongArray tag, containing the given array of longs. 35 | /// Long array to assign to this tag's Value. May not be null. 36 | /// is null. 37 | /// Given long array will be cloned. To avoid unnecessary copying, call one of the other constructor 38 | /// overloads (that do not take a long[]) and then set the Value property yourself. 39 | public NbtLongArray(long[] value) 40 | : this(null, value) { } 41 | 42 | /// Creates an NbtLongArray tag with the given name, containing an empty array of longs. 43 | /// Name to assign to this tag. May be null. 44 | public NbtLongArray(string? tagName) { 45 | name = tagName; 46 | longs = Array.Empty(); 47 | } 48 | 49 | /// Creates an NbtLongArray tag with the given name, containing the given array of longs. 50 | /// Name to assign to this tag. May be null. 51 | /// Long array to assign to this tag's Value. May not be null. 52 | /// is null. 53 | /// Given long array will be cloned. To avoid unnecessary copying, call one of the other constructor 54 | /// overloads (that do not take a long[]) and then set the Value property yourself. 55 | public NbtLongArray(string? tagName, long[] value) { 56 | if (value == null) throw new ArgumentNullException(nameof(value)); 57 | name = tagName; 58 | longs = (long[])value.Clone(); 59 | } 60 | 61 | 62 | /// Creates a deep copy of given NbtLongArray. 63 | /// Tag to copy. May not be null. 64 | /// is null. 65 | /// Long array of given tag will be cloned. 66 | public NbtLongArray(NbtLongArray other) { 67 | if (other == null) { 68 | throw new ArgumentNullException(nameof(other)); 69 | } 70 | 71 | name = other.name; 72 | longs = (long[])other.longs.Clone(); 73 | } 74 | 75 | 76 | /// Gets or sets a long at the given index. 77 | /// The zero-based index of the element to get or set. 78 | /// The long at the specified index. 79 | /// is outside the array bounds. 80 | public new long this[int index] { 81 | get { return Value[index]; } 82 | set { Value[index] = value; } 83 | } 84 | 85 | 86 | internal override bool ReadTag(NbtBinaryReader readStream) { 87 | int length = readStream.ReadInt32(); 88 | 89 | if (length < 0) { 90 | throw new NbtFormatException("Negative length given in TAG_Long_Array"); 91 | } 92 | 93 | if (readStream.Selector != null && !readStream.Selector(this)) { 94 | readStream.Skip(length * sizeof(long)); 95 | return false; 96 | } 97 | 98 | Value = new long[length]; 99 | 100 | for (int i = 0; i < length; i++) { 101 | Value[i] = readStream.ReadInt64(); 102 | } 103 | 104 | return true; 105 | } 106 | 107 | 108 | internal override void SkipTag(NbtBinaryReader readStream) { 109 | int length = readStream.ReadInt32(); 110 | 111 | if (length < 0) { 112 | throw new NbtFormatException("Negative length given in TAG_Long_Array"); 113 | } 114 | 115 | readStream.Skip(length * sizeof(long)); 116 | } 117 | 118 | 119 | internal override void WriteTag(NbtBinaryWriter writeStream) { 120 | writeStream.Write(NbtTagType.LongArray); 121 | 122 | if (Name == null) { 123 | throw new NbtFormatException("Name is null"); 124 | } 125 | 126 | writeStream.Write(Name); 127 | WriteData(writeStream); 128 | } 129 | 130 | 131 | internal override void WriteData(NbtBinaryWriter writeStream) { 132 | writeStream.Write(Value.Length); 133 | 134 | for (int i = 0; i < Value.Length; i++) { 135 | writeStream.Write(Value[i]); 136 | } 137 | } 138 | 139 | 140 | /// 141 | public override object Clone() { 142 | return new NbtLongArray(this); 143 | } 144 | 145 | 146 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 147 | for (int i = 0; i < indentLevel; i++) { 148 | sb.Append(indentString); 149 | } 150 | 151 | sb.Append("TAG_Long_Array"); 152 | 153 | if (!String.IsNullOrEmpty(Name)) { 154 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 155 | } 156 | 157 | sb.AppendFormat(CultureInfo.InvariantCulture, ": [{0} longs]", Value.Length); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtShort.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing a single signed 16-bit integer. 7 | public sealed class NbtShort : NbtTag { 8 | /// Type of this tag (Short). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.Short; } 11 | } 12 | 13 | /// Value/payload of this tag (a single signed 16-bit integer). 14 | public short Value { get; set; } 15 | 16 | 17 | /// Creates an unnamed NbtShort tag with the default value of 0. 18 | public NbtShort() { } 19 | 20 | 21 | /// Creates an unnamed NbtShort tag with the given value. 22 | /// Value to assign to this tag. 23 | public NbtShort(short value) 24 | : this(null, value) { } 25 | 26 | 27 | /// Creates an NbtShort tag with the given name and the default value of 0. 28 | /// Name to assign to this tag. May be null. 29 | public NbtShort(string? tagName) 30 | : this(tagName, 0) { } 31 | 32 | 33 | /// Creates an NbtShort tag with the given name and value. 34 | /// Name to assign to this tag. May be null. 35 | /// Value to assign to this tag. 36 | public NbtShort(string? tagName, short value) { 37 | name = tagName; 38 | Value = value; 39 | } 40 | 41 | 42 | /// Creates a copy of given NbtShort tag. 43 | /// Tag to copy. May not be null. 44 | /// is null. 45 | public NbtShort(NbtShort other) { 46 | if (other == null) throw new ArgumentNullException(nameof(other)); 47 | name = other.name; 48 | Value = other.Value; 49 | } 50 | 51 | 52 | #region Reading / Writing 53 | 54 | internal override bool ReadTag(NbtBinaryReader readStream) { 55 | if (readStream.Selector != null && !readStream.Selector(this)) { 56 | readStream.ReadInt16(); 57 | return false; 58 | } 59 | Value = readStream.ReadInt16(); 60 | return true; 61 | } 62 | 63 | 64 | internal override void SkipTag(NbtBinaryReader readStream) { 65 | readStream.ReadInt16(); 66 | } 67 | 68 | 69 | internal override void WriteTag(NbtBinaryWriter writeStream) { 70 | writeStream.Write(NbtTagType.Short); 71 | if (Name == null) throw new NbtFormatException("Name is null"); 72 | writeStream.Write(Name); 73 | writeStream.Write(Value); 74 | } 75 | 76 | 77 | internal override void WriteData(NbtBinaryWriter writeStream) { 78 | writeStream.Write(Value); 79 | } 80 | 81 | #endregion 82 | 83 | 84 | /// 85 | public override object Clone() { 86 | return new NbtShort(this); 87 | } 88 | 89 | 90 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 91 | for (int i = 0; i < indentLevel; i++) { 92 | sb.Append(indentString); 93 | } 94 | sb.Append("TAG_Short"); 95 | if (!String.IsNullOrEmpty(Name)) { 96 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 97 | } 98 | sb.Append(": "); 99 | sb.Append(Value); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// A tag containing a single string. String is stored in UTF-8 encoding. 7 | public sealed class NbtString : NbtTag { 8 | /// Type of this tag (String). 9 | public override NbtTagType TagType { 10 | get { return NbtTagType.String; } 11 | } 12 | 13 | /// Value/payload of this tag (a single string). May not be null. 14 | public string Value { 15 | get { return stringVal; } 16 | set { 17 | if (value == null) { 18 | throw new ArgumentNullException(nameof(value)); 19 | } 20 | stringVal = value; 21 | } 22 | } 23 | 24 | string stringVal = ""; 25 | 26 | 27 | /// Creates an unnamed NbtString tag with the default value (empty string). 28 | public NbtString() { } 29 | 30 | 31 | /// Creates an unnamed NbtString tag with the given value. 32 | /// String value to assign to this tag. May not be null. 33 | /// is null. 34 | public NbtString(string value) 35 | : this(null, value) { } 36 | 37 | 38 | /// Creates an NbtString tag with the given name and value. 39 | /// Name to assign to this tag. May be null. 40 | /// String value to assign to this tag. May not be null. 41 | /// is null. 42 | public NbtString(string? tagName, string value) { 43 | if (value == null) throw new ArgumentNullException(nameof(value)); 44 | name = tagName; 45 | Value = value; 46 | } 47 | 48 | 49 | /// Creates a copy of given NbtString tag. 50 | /// Tag to copy. May not be null. 51 | /// is null. 52 | public NbtString(NbtString other) { 53 | if (other == null) throw new ArgumentNullException(nameof(other)); 54 | name = other.name; 55 | Value = other.Value; 56 | } 57 | 58 | 59 | #region Reading / Writing 60 | 61 | internal override bool ReadTag(NbtBinaryReader readStream) { 62 | if (readStream.Selector != null && !readStream.Selector(this)) { 63 | readStream.SkipString(); 64 | return false; 65 | } 66 | Value = readStream.ReadString(); 67 | return true; 68 | } 69 | 70 | 71 | internal override void SkipTag(NbtBinaryReader readStream) { 72 | readStream.SkipString(); 73 | } 74 | 75 | 76 | internal override void WriteTag(NbtBinaryWriter writeStream) { 77 | writeStream.Write(NbtTagType.String); 78 | if (Name == null) throw new NbtFormatException("Name is null"); 79 | writeStream.Write(Name); 80 | writeStream.Write(Value); 81 | } 82 | 83 | 84 | internal override void WriteData(NbtBinaryWriter writeStream) { 85 | writeStream.Write(Value); 86 | } 87 | 88 | #endregion 89 | 90 | 91 | /// 92 | public override object Clone() { 93 | return new NbtString(this); 94 | } 95 | 96 | 97 | internal override void PrettyPrint(StringBuilder sb, string indentString, int indentLevel) { 98 | for (int i = 0; i < indentLevel; i++) { 99 | sb.Append(indentString); 100 | } 101 | sb.Append("TAG_String"); 102 | if (!String.IsNullOrEmpty(Name)) { 103 | sb.AppendFormat(CultureInfo.InvariantCulture, "(\"{0}\")", Name); 104 | } 105 | sb.Append(": \""); 106 | sb.Append(Value); 107 | sb.Append('"'); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace fNbt { 6 | /// Base class for different kinds of named binary tags. 7 | public abstract class NbtTag : ICloneable { 8 | /// Parent compound tag, either NbtList or NbtCompound, if any. 9 | /// May be null for detached tags. 10 | public NbtTag? Parent { get; internal set; } 11 | 12 | /// Type of this tag. 13 | public abstract NbtTagType TagType { get; } 14 | 15 | /// Returns true if tags of this type have a value attached. 16 | /// All tags except Compound, List, and End have values. 17 | public bool HasValue { 18 | get { 19 | switch (TagType) { 20 | case NbtTagType.Compound: 21 | case NbtTagType.End: 22 | case NbtTagType.List: 23 | case NbtTagType.Unknown: 24 | return false; 25 | default: 26 | return true; 27 | } 28 | } 29 | } 30 | 31 | /// Name of this tag. Immutable, and set by the constructor. May be null. 32 | /// If is null, and Parent tag is an NbtCompound. 33 | /// Name of tags inside an NbtCompound may not be null. 34 | /// If this tag resides in an NbtCompound, and a sibling tag with the name already exists. 35 | public string? Name { 36 | get { return name; } 37 | set { 38 | if (name == value) { 39 | return; 40 | } 41 | 42 | if (Parent is NbtCompound parentAsCompound) { 43 | if (value == null) { 44 | throw new ArgumentNullException(nameof(value), 45 | "Name of tags inside an NbtCompound may not be null."); 46 | } else if (name != null) { 47 | parentAsCompound.RenameTag(name, value); 48 | } 49 | } 50 | 51 | name = value; 52 | } 53 | } 54 | 55 | // Used by impls to bypass setter checks (and avoid side effects) when initializing state 56 | internal string? name; 57 | 58 | /// Gets the full name of this tag, including all parent tag names, separated by dots. 59 | /// Unnamed tags show up as empty strings. 60 | public string Path { 61 | get { 62 | if (Parent == null) { 63 | return Name ?? ""; 64 | } 65 | if (Parent is NbtList parentAsList) { 66 | return parentAsList.Path + '[' + parentAsList.IndexOf(this) + ']'; 67 | } else { 68 | return Parent.Path + '.' + Name; 69 | } 70 | } 71 | } 72 | 73 | internal abstract bool ReadTag(NbtBinaryReader readStream); 74 | 75 | internal abstract void SkipTag(NbtBinaryReader readStream); 76 | 77 | internal abstract void WriteTag(NbtBinaryWriter writeReader); 78 | 79 | // WriteData does not write the tag's ID byte or the name 80 | internal abstract void WriteData(NbtBinaryWriter writeStream); 81 | 82 | 83 | #region Shortcuts 84 | 85 | /// Gets or sets the tag with the specified name. May return null. 86 | /// The tag with the specified key. Null if tag with the given name was not found. 87 | /// The name of the tag to get or set. Must match tag's actual name. 88 | /// If used on a tag that is not NbtCompound. 89 | /// ONLY APPLICABLE TO NbtCompound OBJECTS! 90 | /// Included in NbtTag base class for programmers' convenience, to avoid extra type casts. 91 | public virtual NbtTag? this[string tagName] { 92 | get { throw new InvalidOperationException("String indexers only work on NbtCompound tags."); } 93 | set { throw new InvalidOperationException("String indexers only work on NbtCompound tags."); } 94 | } 95 | 96 | /// Gets or sets the tag at the specified index. 97 | /// The tag at the specified index. 98 | /// The zero-based index of the tag to get or set. 99 | /// tagIndex is not a valid index in this tag. 100 | /// Given tag is null. 101 | /// Given tag's type does not match ListType. 102 | /// If used on a tag that is not NbtList, NbtByteArray, or NbtIntArray. 103 | /// ONLY APPLICABLE TO NbtList, NbtByteArray, and NbtIntArray OBJECTS! 104 | /// Included in NbtTag base class for programmers' convenience, to avoid extra type casts. 105 | public virtual NbtTag this[int tagIndex] { 106 | get { throw new InvalidOperationException("Integer indexers only work on NbtList tags."); } 107 | set { throw new InvalidOperationException("Integer indexers only work on NbtList tags."); } 108 | } 109 | 110 | /// Returns the value of this tag, cast as a byte. 111 | /// Only supported by NbtByte tags. 112 | /// When used on a tag other than NbtByte. 113 | public byte ByteValue { 114 | get { 115 | if (TagType == NbtTagType.Byte) { 116 | return ((NbtByte)this).Value; 117 | } else { 118 | throw new InvalidCastException("Cannot get ByteValue from " + GetCanonicalTagName(TagType)); 119 | } 120 | } 121 | } 122 | 123 | /// Returns the value of this tag, cast as a short (16-bit signed integer). 124 | /// Only supported by NbtByte and NbtShort. 125 | /// When used on an unsupported tag. 126 | public short ShortValue { 127 | get { 128 | switch (TagType) { 129 | case NbtTagType.Byte: 130 | return ((NbtByte)this).Value; 131 | case NbtTagType.Short: 132 | return ((NbtShort)this).Value; 133 | default: 134 | throw new InvalidCastException("Cannot get ShortValue from " + GetCanonicalTagName(TagType)); 135 | } 136 | } 137 | } 138 | 139 | /// Returns the value of this tag, cast as an int (32-bit signed integer). 140 | /// Only supported by NbtByte, NbtShort, and NbtInt. 141 | /// When used on an unsupported tag. 142 | public int IntValue { 143 | get { 144 | switch (TagType) { 145 | case NbtTagType.Byte: 146 | return ((NbtByte)this).Value; 147 | case NbtTagType.Short: 148 | return ((NbtShort)this).Value; 149 | case NbtTagType.Int: 150 | return ((NbtInt)this).Value; 151 | default: 152 | throw new InvalidCastException("Cannot get IntValue from " + GetCanonicalTagName(TagType)); 153 | } 154 | } 155 | } 156 | 157 | /// Returns the value of this tag, cast as a long (64-bit signed integer). 158 | /// Only supported by NbtByte, NbtShort, NbtInt, and NbtLong. 159 | /// When used on an unsupported tag. 160 | public long LongValue { 161 | get { 162 | switch (TagType) { 163 | case NbtTagType.Byte: 164 | return ((NbtByte)this).Value; 165 | case NbtTagType.Short: 166 | return ((NbtShort)this).Value; 167 | case NbtTagType.Int: 168 | return ((NbtInt)this).Value; 169 | case NbtTagType.Long: 170 | return ((NbtLong)this).Value; 171 | default: 172 | throw new InvalidCastException("Cannot get LongValue from " + GetCanonicalTagName(TagType)); 173 | } 174 | } 175 | } 176 | 177 | /// Returns the value of this tag, cast as a long (64-bit signed integer). 178 | /// Only supported by NbtFloat and, with loss of precision, by NbtDouble, NbtByte, NbtShort, NbtInt, and NbtLong. 179 | /// When used on an unsupported tag. 180 | public float FloatValue { 181 | get { 182 | switch (TagType) { 183 | case NbtTagType.Byte: 184 | return ((NbtByte)this).Value; 185 | case NbtTagType.Short: 186 | return ((NbtShort)this).Value; 187 | case NbtTagType.Int: 188 | return ((NbtInt)this).Value; 189 | case NbtTagType.Long: 190 | return ((NbtLong)this).Value; 191 | case NbtTagType.Float: 192 | return ((NbtFloat)this).Value; 193 | case NbtTagType.Double: 194 | return (float)((NbtDouble)this).Value; 195 | default: 196 | throw new InvalidCastException("Cannot get FloatValue from " + GetCanonicalTagName(TagType)); 197 | } 198 | } 199 | } 200 | 201 | /// Returns the value of this tag, cast as a long (64-bit signed integer). 202 | /// Only supported by NbtFloat, NbtDouble, and, with loss of precision, by NbtByte, NbtShort, NbtInt, and NbtLong. 203 | /// When used on an unsupported tag. 204 | public double DoubleValue { 205 | get { 206 | switch (TagType) { 207 | case NbtTagType.Byte: 208 | return ((NbtByte)this).Value; 209 | case NbtTagType.Short: 210 | return ((NbtShort)this).Value; 211 | case NbtTagType.Int: 212 | return ((NbtInt)this).Value; 213 | case NbtTagType.Long: 214 | return ((NbtLong)this).Value; 215 | case NbtTagType.Float: 216 | return ((NbtFloat)this).Value; 217 | case NbtTagType.Double: 218 | return ((NbtDouble)this).Value; 219 | default: 220 | throw new InvalidCastException("Cannot get DoubleValue from " + GetCanonicalTagName(TagType)); 221 | } 222 | } 223 | } 224 | 225 | /// Returns the value of this tag, cast as a byte array. 226 | /// Only supported by NbtByteArray tags. 227 | /// When used on a tag other than NbtByteArray. 228 | public byte[] ByteArrayValue { 229 | get { 230 | if (TagType == NbtTagType.ByteArray) { 231 | return ((NbtByteArray)this).Value; 232 | } else { 233 | throw new InvalidCastException("Cannot get ByteArrayValue from " + GetCanonicalTagName(TagType)); 234 | } 235 | } 236 | } 237 | 238 | /// Returns the value of this tag, cast as an int array. 239 | /// Only supported by NbtIntArray tags. 240 | /// When used on a tag other than NbtIntArray. 241 | public int[] IntArrayValue { 242 | get { 243 | if (TagType == NbtTagType.IntArray) { 244 | return ((NbtIntArray)this).Value; 245 | } else { 246 | throw new InvalidCastException("Cannot get IntArrayValue from " + GetCanonicalTagName(TagType)); 247 | } 248 | } 249 | } 250 | 251 | /// Returns the value of this tag, cast as a long array. 252 | /// Only supported by NbtLongArray tags. 253 | /// When used on a tag other than NbtLongArray. 254 | public long[] LongArrayValue { 255 | get { 256 | if (TagType == NbtTagType.LongArray) { 257 | return ((NbtLongArray)this).Value; 258 | } else { 259 | throw new InvalidCastException("Cannot get LongArrayValue from " + GetCanonicalTagName(TagType)); 260 | } 261 | } 262 | } 263 | 264 | /// Returns the value of this tag, cast as a string. 265 | /// Returns exact value for NbtString, and stringified (using InvariantCulture) value for NbtByte, NbtDouble, NbtFloat, NbtInt, NbtLong, and NbtShort. 266 | /// Not supported by NbtCompound, NbtList, NbtByteArray, NbtIntArray, or NbtLongArray. 267 | /// When used on an unsupported tag. 268 | public string StringValue { 269 | get { 270 | switch (TagType) { 271 | case NbtTagType.String: 272 | return ((NbtString)this).Value; 273 | case NbtTagType.Byte: 274 | return ((NbtByte)this).Value.ToString(CultureInfo.InvariantCulture); 275 | case NbtTagType.Double: 276 | return ((NbtDouble)this).Value.ToString(CultureInfo.InvariantCulture); 277 | case NbtTagType.Float: 278 | return ((NbtFloat)this).Value.ToString(CultureInfo.InvariantCulture); 279 | case NbtTagType.Int: 280 | return ((NbtInt)this).Value.ToString(CultureInfo.InvariantCulture); 281 | case NbtTagType.Long: 282 | return ((NbtLong)this).Value.ToString(CultureInfo.InvariantCulture); 283 | case NbtTagType.Short: 284 | return ((NbtShort)this).Value.ToString(CultureInfo.InvariantCulture); 285 | default: 286 | throw new InvalidCastException("Cannot get StringValue from " + GetCanonicalTagName(TagType)); 287 | } 288 | } 289 | } 290 | 291 | #endregion 292 | 293 | 294 | /// Returns a canonical (Notchy) name for the given NbtTagType, 295 | /// e.g. "TAG_Byte_Array" for NbtTagType.ByteArray 296 | /// NbtTagType to name. 297 | /// String representing the canonical name of a tag, 298 | /// or null of given TagType does not have a canonical name (e.g. Unknown). 299 | public static string? GetCanonicalTagName(NbtTagType type) { 300 | return type switch { 301 | NbtTagType.Byte => "TAG_Byte", 302 | NbtTagType.ByteArray => "TAG_Byte_Array", 303 | NbtTagType.Compound => "TAG_Compound", 304 | NbtTagType.Double => "TAG_Double", 305 | NbtTagType.End => "TAG_End", 306 | NbtTagType.Float => "TAG_Float", 307 | NbtTagType.Int => "TAG_Int", 308 | NbtTagType.IntArray => "TAG_Int_Array", 309 | NbtTagType.LongArray => "TAG_Long_Array", 310 | NbtTagType.List => "TAG_List", 311 | NbtTagType.Long => "TAG_Long", 312 | NbtTagType.Short => "TAG_Short", 313 | NbtTagType.String => "TAG_String", 314 | _ => null, 315 | }; 316 | } 317 | 318 | 319 | /// Prints contents of this tag, and any child tags, to a string. 320 | /// Indents the string using multiples of the given indentation string. 321 | /// A string representing contents of this tag, and all child tags (if any). 322 | public override string ToString() { 323 | return ToString(DefaultIndentString); 324 | } 325 | 326 | 327 | /// Creates a deep copy of this tag. 328 | /// A new NbtTag object that is a deep copy of this instance. 329 | public abstract object Clone(); 330 | 331 | 332 | /// Prints contents of this tag, and any child tags, to a string. 333 | /// Indents the string using multiples of the given indentation string. 334 | /// String to be used for indentation. 335 | /// A string representing contents of this tag, and all child tags (if any). 336 | /// is null. 337 | public string ToString(string indentString) { 338 | if (indentString == null) throw new ArgumentNullException(nameof(indentString)); 339 | var sb = new StringBuilder(); 340 | PrettyPrint(sb, indentString, 0); 341 | return sb.ToString(); 342 | } 343 | 344 | 345 | internal abstract void PrettyPrint(StringBuilder sb, string indentString, int indentLevel); 346 | 347 | /// String to use for indentation in NbtTag's and NbtFile's ToString() methods by default. 348 | /// is null. 349 | public static string DefaultIndentString { 350 | get { return defaultIndentString; } 351 | set { 352 | if (value == null) throw new ArgumentNullException(nameof(value)); 353 | defaultIndentString = value; 354 | } 355 | } 356 | 357 | static string defaultIndentString = " "; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /fNbt/Tags/NbtTagCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace fNbt { 6 | public abstract class NbtTagCollection : NbtTag, ICollection, ICollection { 7 | public abstract int Count { get; } 8 | public abstract object SyncRoot { get; } 9 | 10 | public abstract void Add(NbtTag item); 11 | public abstract void Clear(); 12 | public abstract bool Contains(NbtTag item); 13 | public abstract void CopyTo(NbtTag[] array, int arrayIndex); 14 | public abstract IEnumerator GetEnumerator(); 15 | public abstract bool Remove(NbtTag item); 16 | 17 | IEnumerator IEnumerable.GetEnumerator() { 18 | return GetEnumerator(); 19 | } 20 | 21 | bool ICollection.IsReadOnly { 22 | get { return false; } 23 | } 24 | 25 | void ICollection.CopyTo(Array array, int index) { 26 | CopyTo((NbtTag[])array, index); 27 | } 28 | 29 | bool ICollection.IsSynchronized { 30 | get { return false; } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /fNbt/ZLibStream.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.IO.Compression; 4 | 5 | namespace fNbt { 6 | /// DeflateStream wrapper that calculates Adler32 checksum of the written data, 7 | /// to allow writing ZLib header (RFC-1950). 8 | internal sealed class ZLibStream : DeflateStream { 9 | int adler32A = 1, 10 | adler32B; 11 | 12 | const int ChecksumModulus = 65521; 13 | 14 | public int Checksum { 15 | get { return unchecked((adler32B * 65536) + adler32A); } 16 | } 17 | 18 | 19 | void UpdateChecksum(IList data, int offset, int length) { 20 | for (int counter = 0; counter < length; ++counter) { 21 | adler32A = (adler32A + (data[offset + counter])) % ChecksumModulus; 22 | adler32B = (adler32B + adler32A) % ChecksumModulus; 23 | } 24 | } 25 | 26 | 27 | public ZLibStream(Stream stream, CompressionMode mode, bool leaveOpen) 28 | : base(stream, mode, leaveOpen) { } 29 | 30 | 31 | public override void Write(byte[] array, int offset, int count) { 32 | UpdateChecksum(array, offset, count); 33 | base.Write(array, offset, count); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /fNbt/fNbt.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | Library 5 | false 6 | 10 7 | enable 8 | 9 | 10 | ..\bin\Debug\ 11 | false 12 | ..\bin\Debug\fNbt.xml 13 | false 14 | MinimumRecommendedRules.ruleset 15 | true 16 | 17 | 18 | ..\bin\Release\ 19 | false 20 | false 21 | ..\bin\Release\fNbt.xml 22 | MinimumRecommendedRules.ruleset 23 | true 24 | 25 | 26 | true 27 | 28 | -------------------------------------------------------------------------------- /fNbt/fNbt.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Erik Davidson (2010-2011); Matvei Stefarov (2012+) 8 | $author$ 9 | https://github.com/fragmer/fNbt/blob/v0.6.4/docs/LICENSE 10 | https://github.com/fragmer/fNbt 11 | false 12 | $description$ 13 | Bugfixes and performance improvements. 14 | Copyright 2012-2024 Matvei Stefarov 15 | nbt serialization 16 | 17 | --------------------------------------------------------------------------------