├── .gitattributes ├── .gitignore ├── EndianBinaryIO.sln ├── LICENSE.md ├── README.md ├── Source ├── Attributes.cs ├── EndianBinaryIO.csproj ├── EndianBinaryPrimitives.cs ├── EndianBinaryPrimitives_Unsafe.cs ├── EndianBinaryReader.cs ├── EndianBinaryReader_Reflection.cs ├── EndianBinaryWriter.cs ├── EndianBinaryWriter_Reflection.cs ├── Enums.cs ├── IBinarySerializable.cs └── Utils.cs └── Testing ├── BasicTests.cs ├── ByteTests.cs ├── CharTests.cs ├── DecimalTests.cs ├── DoubleTests.cs ├── EndianBinaryTesting.csproj ├── HalfTests.cs ├── Int128Tests.cs ├── Int16Tests.cs ├── Int32Tests.cs ├── Int64Tests.cs ├── LengthsTests.cs ├── SByteTests.cs ├── SingleTests.cs ├── TestUtils.cs ├── UInt128Tests.cs ├── UInt16Tests.cs ├── UInt32Tests.cs ├── UInt64Tests.cs └── _NumTestUtils.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text eol=lf 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /EndianBinaryIO.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2035 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EndianBinaryIO", "Source\EndianBinaryIO.csproj", "{0AEF89A6-2AC2-4ED4-AE05-CB4195E6B92A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EndianBinaryTesting", "Testing\EndianBinaryTesting.csproj", "{31B95673-DFD2-4613-BAD6-7892CB29F4B9}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {0AEF89A6-2AC2-4ED4-AE05-CB4195E6B92A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {0AEF89A6-2AC2-4ED4-AE05-CB4195E6B92A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {0AEF89A6-2AC2-4ED4-AE05-CB4195E6B92A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {0AEF89A6-2AC2-4ED4-AE05-CB4195E6B92A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {31B95673-DFD2-4613-BAD6-7892CB29F4B9}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {C1D4E49D-C209-4E60-8F11-9E31F2B0125F} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kermalis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📖 EndianBinaryIO 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/EndianBinaryIO.svg)](https://www.nuget.org/packages/EndianBinaryIO) 4 | [![NuGet downloads](https://img.shields.io/nuget/dt/EndianBinaryIO)](https://www.nuget.org/packages/EndianBinaryIO) 5 | 6 | This .NET library provides a simple API to read/write bytes from/to streams and spans using user-specified endianness. 7 | By default, supported types include primitives, enums, arrays, strings, and some common .NET struct types. 8 | Objects can also be read/written from/to streams via reflection and attributes. 9 | The developer can use the API even if their target behavior or data is not directly supported by using the `IBinarySerializable` interface, inheritting from the reader/writer, or using the manual `Span`/`ReadOnlySpan` methods without streams. 10 | Performance is the focus when not using reflection; no allocations unless absolutely necessary! 11 | 12 | The `IBinarySerializable` interface allows an object to be read and written in a customizable fashion during reflection. 13 | Also included are attributes that can make reading and writing objects less of a headache. 14 | For example, classes and structs in C# cannot have ignored members when marshalling, but **EndianBinaryIO** has a `BinaryIgnoreAttribute` that will ignore properties when reading and writing. 15 | 16 | The `EndianBinaryPrimitives` static class which resembles `System.Buffers.Binary.BinaryPrimitives` is an API that converts to/from data types using `Span`/`ReadOnlySpan` with specific endianness, rather than streams. 17 | 18 | ---- 19 | ## Changelog For v2.1.1 20 | Check the comment on [the release page](https://github.com/Kermalis/EndianBinaryIO/releases/tag/v2.1.0)! 21 | 22 | ---- 23 | ## 🚀 Usage: 24 | Add the [EndianBinaryIO](https://www.nuget.org/packages/EndianBinaryIO) NuGet package to your project or download the .dll from [the releases tab](https://github.com/Kermalis/EndianBinaryIO/releases). 25 | 26 | ---- 27 | ## Examples: 28 | Assume we have the following definitions: 29 | ### C#: 30 | ```cs 31 | enum ByteSizedEnum : byte 32 | { 33 | Val1 = 0x20, 34 | Val2 = 0x80, 35 | } 36 | enum ShortSizedEnum : short 37 | { 38 | Val1 = 0x40, 39 | Val2 = 0x800, 40 | } 41 | 42 | class MyBasicObj 43 | { 44 | // Properties 45 | public ShortSizedEnum Type { get; set; } 46 | public short Version { get; set; } 47 | public DateTime Date { get; set; } 48 | public Int128 Int128 { get; set; } 49 | 50 | // Property that is ignored when reading and writing 51 | [BinaryIgnore] 52 | public ByteSizedEnum DoNotReadOrWrite { get; set; } 53 | 54 | // Arrays work as well 55 | [BinaryArrayFixedLength(16)] 56 | public uint[] ArrayWith16Elements { get; set; } 57 | 58 | // Boolean that occupies 4 bytes instead of one 59 | [BinaryBooleanSize(BooleanSize.U32)] 60 | public bool Bool32 { get; set; } 61 | 62 | // String encoded in ASCII 63 | // Reads chars until the stream encounters a '\0' 64 | // Writing will append a '\0' at the end of the string 65 | [BinaryASCII] 66 | [BinaryStringNullTerminated] 67 | public string NullTerminatedASCIIString { get; set; } 68 | 69 | // String encoded in UTF16-LE that will only read/write 10 chars 70 | // The BinaryStringTrimNullTerminatorsAttribute will indicate that every char from the first \0 will be removed from the string. This attribute also works with char arrays 71 | [BinaryStringFixedLength(10)] 72 | [BinaryStringTrimNullTerminators] 73 | public string UTF16String { get; set; } 74 | } 75 | ``` 76 | And assume these are our input bytes (in little endian): 77 | ### Input Bytes (Little Endian): 78 | ```cs 79 | 0x00, 0x08, // ShortSizedEnum.Val2 80 | 0xFF, 0x01, // (short)511 81 | 0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, // (DateTime)Dec. 30, 1998 82 | 0x48, 0x49, 0x80, 0x44, 0x82, 0x44, 0x88, 0xC0, 0x42, 0x24, 0x88, 0x12, 0x44, 0x44, 0x25, 0x24, // (Int128)48,045,707,429,126,174,655,160,174,263,614,327,112 83 | 84 | 0x00, 0x00, 0x00, 0x00, // (uint)0 85 | 0x01, 0x00, 0x00, 0x00, // (uint)1 86 | 0x02, 0x00, 0x00, 0x00, // (uint)2 87 | 0x03, 0x00, 0x00, 0x00, // (uint)3 88 | 0x04, 0x00, 0x00, 0x00, // (uint)4 89 | 0x05, 0x00, 0x00, 0x00, // (uint)5 90 | 0x06, 0x00, 0x00, 0x00, // (uint)6 91 | 0x07, 0x00, 0x00, 0x00, // (uint)7 92 | 0x08, 0x00, 0x00, 0x00, // (uint)8 93 | 0x09, 0x00, 0x00, 0x00, // (uint)9 94 | 0x0A, 0x00, 0x00, 0x00, // (uint)10 95 | 0x0B, 0x00, 0x00, 0x00, // (uint)11 96 | 0x0C, 0x00, 0x00, 0x00, // (uint)12 97 | 0x0D, 0x00, 0x00, 0x00, // (uint)13 98 | 0x0E, 0x00, 0x00, 0x00, // (uint)14 99 | 0x0F, 0x00, 0x00, 0x00, // (uint)15 100 | 101 | 0x00, 0x00, 0x00, 0x00, // (bool32)false 102 | 103 | 0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, // (ASCII)"EndianBinaryIO\0" 104 | 105 | 0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, // (UTF16-LE)"Kermalis\0\0" 106 | ``` 107 | 108 | We can read/write the object manually or automatically (with reflection): 109 | ### Reading Manually: 110 | ```cs 111 | var reader = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); 112 | var obj = new MyBasicObj(); 113 | 114 | obj.Type = reader.ReadEnum(); // Reads the enum type based on the amount of bytes of the enum's underlying type (short/2 in this case) 115 | obj.Version = reader.ReadInt16(); // Reads a 'short' (2 bytes) 116 | obj.Date = reader.ReadDateTime(); // Reads a 'DateTime' (8 bytes) 117 | obj.Int128 = reader.ReadInt128(); // Reads an 'Int128' (16 bytes) 118 | 119 | obj.ArrayWith16Elements = new uint[16]; 120 | reader.ReadUInt32s(obj.ArrayWith16Elements); // Reads 16 'uint's (4 bytes each) 121 | 122 | obj.Bool32 = reader.ReadBoolean(); // Reads a 'bool' (4 bytes in this case, since the reader's current bool state is BooleanSize.U32) 123 | 124 | reader.ASCII = true; // Set the reader's ASCII state to true 125 | obj.NullTerminatedASCIIString = reader.ReadString_NullTerminated(); // Reads ASCII chars until a '\0' is read, then returns a 'string' 126 | 127 | reader.ASCII = false; // Set the reader's ASCII state to false (UTF16-LE) 128 | obj.UTF16String = reader.ReadString_Count_TrimNullTerminators(10); // Reads 10 UTF16-LE chars as a 'string' with the '\0's removed 129 | ``` 130 | ### Reading Automatically (With Reflection): 131 | ```cs 132 | var reader = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian); 133 | var obj = reader.ReadObject(); // Create a 'MyBasicObj' and read all properties in order, ignoring any with a 'BinaryIgnoreAttribute' 134 | // Other objects that are properties in this object will also be read in the same way recursively 135 | ``` 136 | 137 | ### Writing Manually: 138 | ```cs 139 | var obj = new MyBasicObj 140 | { 141 | Type = ShortSizedEnum.Val2, 142 | Version = 511, 143 | Date = new DateTime(1998, 12, 30), 144 | Int128 = Int128.Parse("48,045,707,429,126,174,655,160,174,263,614,327,112", NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo), 145 | 146 | DoNotReadOrWrite = ByteSizedEnum.Val1, 147 | 148 | ArrayWith16Elements = new uint[16] 149 | { 150 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 151 | }, 152 | 153 | Bool32 = false, 154 | 155 | NullTerminatedASCIIString = "EndianBinaryIO", 156 | UTF16String = "Kermalis", 157 | }; 158 | 159 | var writer = new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); 160 | writer.WriteEnum(obj.Type); // Writes the enum type based on the amount of bytes of the enum's underlying type (short/2 in this case) 161 | writer.WriteInt16(obj.Version); // Writes a 'short' (2 bytes) 162 | writer.WriteDateTime(obj.Date); // Writes a 'DateTime' (8 bytes) 163 | writer.WriteInt128(obj.Int128); // Writes an 'Int128' (16 bytes) 164 | writer.WriteUInt32s(obj.ArrayWith16Elements); // Writes 16 'uint's (4 bytes each) 165 | writer.WriteBoolean(obj.Bool32); // Writes a 'bool' (4 bytes in this case, since the reader's current bool state is BooleanSize.U32) 166 | 167 | writer.ASCII = true; // Set the reader's ASCII state to true 168 | writer.WriteChars_NullTerminated(obj.NullTerminatedASCIIString); // Writes the chars in the 'string' as ASCII and appends a '\0' at the end 169 | 170 | writer.ASCII = false; // Set the reader's ASCII state to false (UTF16-LE) 171 | writer.WriteChars_Count(obj.UTF16String, 10); // Writes 10 UTF16-LE chars as a 'string'. If the string has more than 10 chars, it is truncated; if it has less, it is padded with '\0' 172 | ``` 173 | ### Writing Automatically (With Reflection): 174 | ```cs 175 | var writer = new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian); 176 | writer.Write(obj); // Write all properties in the 'MyBasicObj' in order, ignoring any with a 'BinaryIgnoreAttribute' 177 | // Other objects that are properties in this object will also be written in the same way recursively 178 | ``` 179 | 180 | ### EndianBinaryPrimitives Example: 181 | ```cs 182 | byte[] bytes = new byte[] { 0xFF, 0x00, 0x00, 0x00, 0xBB, 0xEE, 0xEE, 0xFF }; 183 | uint value = EndianBinaryPrimitives.ReadUInt32(bytes, Endianness.LittleEndian); // Will return 255 184 | 185 | value = 128; 186 | EndianBinaryPrimitives.WriteUInt32(bytes.AsSpan(4, 4), value, Endianness.LittleEndian); // bytes is now { 0xFF, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 } 187 | ``` 188 | 189 | ---- 190 | ## EndianBinaryIOTests Uses: 191 | * [xUnit.net](https://github.com/xunit/xunit) -------------------------------------------------------------------------------- /Source/Attributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.EndianBinaryIO; 4 | 5 | public interface IBinaryAttribute 6 | { 7 | T Value { get; } 8 | } 9 | 10 | [AttributeUsage(AttributeTargets.Property)] 11 | public sealed class BinaryIgnoreAttribute : Attribute, IBinaryAttribute 12 | { 13 | public bool Value { get; } 14 | 15 | public BinaryIgnoreAttribute(bool ignore = true) 16 | { 17 | Value = ignore; 18 | } 19 | } 20 | [AttributeUsage(AttributeTargets.Property)] 21 | public sealed class BinaryBooleanSizeAttribute : Attribute, IBinaryAttribute 22 | { 23 | public BooleanSize Value { get; } 24 | 25 | public BinaryBooleanSizeAttribute(BooleanSize booleanSize) 26 | { 27 | if (booleanSize >= BooleanSize.MAX) 28 | { 29 | throw new ArgumentOutOfRangeException(nameof(booleanSize), $"{nameof(BinaryBooleanSizeAttribute)} cannot be created with a size of {booleanSize}."); 30 | } 31 | Value = booleanSize; 32 | } 33 | } 34 | [AttributeUsage(AttributeTargets.Property)] 35 | public sealed class BinaryASCIIAttribute : Attribute, IBinaryAttribute 36 | { 37 | public bool Value { get; } 38 | 39 | public BinaryASCIIAttribute(bool ascii = true) 40 | { 41 | Value = ascii; 42 | } 43 | } 44 | [AttributeUsage(AttributeTargets.Property)] 45 | public sealed class BinaryStringNullTerminatedAttribute : Attribute, IBinaryAttribute 46 | { 47 | public bool Value { get; } 48 | 49 | public BinaryStringNullTerminatedAttribute(bool nullTerminated = true) 50 | { 51 | Value = nullTerminated; 52 | } 53 | } 54 | [AttributeUsage(AttributeTargets.Property)] 55 | public sealed class BinaryArrayFixedLengthAttribute : Attribute, IBinaryAttribute 56 | { 57 | public int Value { get; } 58 | 59 | public BinaryArrayFixedLengthAttribute(int length) 60 | { 61 | if (length < 0) 62 | { 63 | throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(BinaryArrayFixedLengthAttribute)} cannot be created with a length of {length}. Length must be 0 or greater."); 64 | } 65 | Value = length; 66 | } 67 | } 68 | [AttributeUsage(AttributeTargets.Property)] 69 | public sealed class BinaryArrayVariableLengthAttribute : Attribute, IBinaryAttribute 70 | { 71 | public string Value { get; } 72 | 73 | public BinaryArrayVariableLengthAttribute(string anchor) 74 | { 75 | Value = anchor; 76 | } 77 | } 78 | [AttributeUsage(AttributeTargets.Property)] 79 | public sealed class BinaryStringFixedLengthAttribute : Attribute, IBinaryAttribute 80 | { 81 | public int Value { get; } 82 | 83 | public BinaryStringFixedLengthAttribute(int length) 84 | { 85 | if (length < 0) 86 | { 87 | throw new ArgumentOutOfRangeException(nameof(length), $"{nameof(BinaryStringFixedLengthAttribute)} cannot be created with a length of {length}. Length must be 0 or greater."); 88 | } 89 | Value = length; 90 | } 91 | } 92 | [AttributeUsage(AttributeTargets.Property)] 93 | public sealed class BinaryStringVariableLengthAttribute : Attribute, IBinaryAttribute 94 | { 95 | public string Value { get; } 96 | 97 | public BinaryStringVariableLengthAttribute(string anchor) 98 | { 99 | Value = anchor; 100 | } 101 | } 102 | [AttributeUsage(AttributeTargets.Property)] 103 | public sealed class BinaryStringTrimNullTerminatorsAttribute : Attribute, IBinaryAttribute 104 | { 105 | public bool Value { get; } 106 | 107 | public BinaryStringTrimNullTerminatorsAttribute(bool trim = true) 108 | { 109 | Value = trim; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Source/EndianBinaryIO.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0;net8.0 5 | latest 6 | Library 7 | Kermalis.EndianBinaryIO 8 | enable 9 | IDE0270,IDE0290 10 | 11 | Kermalis 12 | Kermalis 13 | EndianBinaryIO 14 | EndianBinaryIO 15 | EndianBinaryIO 16 | EndianBinaryIO 17 | 2.1.1 18 | https://github.com/Kermalis/EndianBinaryIO 19 | git 20 | This .NET library provides a simple API to read/write bytes from/to streams and spans using user-specified endianness. 21 | By default, supported types include primitives, enums, arrays, strings, and some common .NET struct types. 22 | Objects can also be read/written from/to streams via reflection and attributes. 23 | The developer can use the API even if their target behavior or data is not directly supported by using the IBinarySerializable interface, inheritting from the reader/writer, or using the manual Span methods without streams. 24 | Performance is the focus when not using reflection; no allocations unless absolutely necessary! 25 | 26 | Project URL and Samples ― https://github.com/Kermalis/EndianBinaryIO 27 | https://github.com/Kermalis/EndianBinaryIO 28 | en-001 29 | Serialization;Reflection;Endianness;LittleEndian;BigEndian;EndianBinaryIO 30 | README.md 31 | LICENSE.md 32 | # Version 2.1.1 Changelog: 33 | * .NET 7.0 and .NET 8.0 support. 34 | * Small optimization when reading empty strings. 35 | * Can set the `Stream` on `EndianBinaryReader` and `EndianBinaryWriter`. 36 | 37 | No breaking changes from v2.1.0 38 | 39 | 40 | 41 | Auto 42 | none 43 | false 44 | 45 | 46 | 47 | false 48 | DEBUG;TRACE 49 | full 50 | true 51 | 52 | 53 | 54 | 55 | True 56 | \ 57 | 58 | 59 | True 60 | \ 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Source/EndianBinaryPrimitives.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Kermalis.EndianBinaryIO; 6 | 7 | public static partial class EndianBinaryPrimitives 8 | { 9 | public static readonly Endianness SystemEndianness = BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; 10 | 11 | public static int GetBytesForBooleanSize(BooleanSize boolSize) 12 | { 13 | switch (boolSize) 14 | { 15 | case BooleanSize.U8: return 1; 16 | case BooleanSize.U16: return 2; 17 | case BooleanSize.U32: return 4; 18 | default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); 19 | } 20 | } 21 | 22 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 23 | public static void TrimNullTerminators(ref char[] chars) 24 | { 25 | int i = Array.IndexOf(chars, '\0'); 26 | if (i != -1) 27 | { 28 | Array.Resize(ref chars, i); 29 | } 30 | } 31 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 32 | public static void TrimNullTerminators(ref Span chars) 33 | { 34 | int i = chars.IndexOf('\0'); 35 | if (i != -1) 36 | { 37 | chars = chars.Slice(0, i); 38 | } 39 | } 40 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 41 | public static void TrimNullTerminators(ref ReadOnlySpan chars) 42 | { 43 | int i = chars.IndexOf('\0'); 44 | if (i != -1) 45 | { 46 | chars = chars.Slice(0, i); 47 | } 48 | } 49 | 50 | #region Read 51 | 52 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 53 | public static void ReadSBytes(this ReadOnlySpan src, Span dest) 54 | { 55 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length); 56 | src.ReadSBytes_Unsafe(dest); 57 | } 58 | 59 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 60 | public static short ReadInt16(this ReadOnlySpan src, Endianness endianness) 61 | { 62 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(short)); 63 | Utils.ThrowIfInvalidEndianness(endianness); 64 | return src.ReadInt16_Unsafe(endianness); 65 | } 66 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 67 | public static void ReadInt16s(this ReadOnlySpan src, Span dest, Endianness endianness) 68 | { 69 | if (dest.Length != 0) 70 | { 71 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(short)); 72 | Utils.ThrowIfInvalidEndianness(endianness); 73 | src.ReadInt16s_Unsafe(dest, endianness); 74 | } 75 | } 76 | 77 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 78 | public static ushort ReadUInt16(this ReadOnlySpan src, Endianness endianness) 79 | { 80 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(ushort)); 81 | Utils.ThrowIfInvalidEndianness(endianness); 82 | return src.ReadUInt16_Unsafe(endianness); 83 | } 84 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 85 | public static void ReadUInt16s(this ReadOnlySpan src, Span dest, Endianness endianness) 86 | { 87 | if (dest.Length != 0) 88 | { 89 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(ushort)); 90 | Utils.ThrowIfInvalidEndianness(endianness); 91 | src.ReadUInt16s_Unsafe(dest, endianness); 92 | } 93 | } 94 | 95 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 96 | public static int ReadInt32(this ReadOnlySpan src, Endianness endianness) 97 | { 98 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(int)); 99 | Utils.ThrowIfInvalidEndianness(endianness); 100 | return src.ReadInt32_Unsafe(endianness); 101 | } 102 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 103 | public static void ReadInt32s(this ReadOnlySpan src, Span dest, Endianness endianness) 104 | { 105 | if (dest.Length != 0) 106 | { 107 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(int)); 108 | Utils.ThrowIfInvalidEndianness(endianness); 109 | src.ReadInt32s_Unsafe(dest, endianness); 110 | } 111 | } 112 | 113 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 114 | public static uint ReadUInt32(this ReadOnlySpan src, Endianness endianness) 115 | { 116 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(uint)); 117 | Utils.ThrowIfInvalidEndianness(endianness); 118 | return src.ReadUInt32_Unsafe(endianness); 119 | } 120 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 121 | public static void ReadUInt32s(this ReadOnlySpan src, Span dest, Endianness endianness) 122 | { 123 | if (dest.Length != 0) 124 | { 125 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(uint)); 126 | Utils.ThrowIfInvalidEndianness(endianness); 127 | src.ReadUInt32s_Unsafe(dest, endianness); 128 | } 129 | } 130 | 131 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 132 | public static long ReadInt64(this ReadOnlySpan src, Endianness endianness) 133 | { 134 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(long)); 135 | Utils.ThrowIfInvalidEndianness(endianness); 136 | return src.ReadInt64_Unsafe(endianness); 137 | } 138 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 139 | public static void ReadInt64s(this ReadOnlySpan src, Span dest, Endianness endianness) 140 | { 141 | if (dest.Length != 0) 142 | { 143 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(long)); 144 | Utils.ThrowIfInvalidEndianness(endianness); 145 | src.ReadInt64s_Unsafe(dest, endianness); 146 | } 147 | } 148 | 149 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 150 | public static ulong ReadUInt64(this ReadOnlySpan src, Endianness endianness) 151 | { 152 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(ulong)); 153 | Utils.ThrowIfInvalidEndianness(endianness); 154 | return src.ReadUInt64_Unsafe(endianness); 155 | } 156 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 157 | public static void ReadUInt64s(this ReadOnlySpan src, Span dest, Endianness endianness) 158 | { 159 | if (dest.Length != 0) 160 | { 161 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(ulong)); 162 | Utils.ThrowIfInvalidEndianness(endianness); 163 | src.ReadUInt64s_Unsafe(dest, endianness); 164 | } 165 | } 166 | 167 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 168 | public static Int128 ReadInt128(this ReadOnlySpan src, Endianness endianness) 169 | { 170 | Utils.ThrowIfSrcTooSmall(src.Length, 2 * sizeof(ulong)); 171 | Utils.ThrowIfInvalidEndianness(endianness); 172 | return src.ReadInt128_Unsafe(endianness); 173 | } 174 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 175 | public static void ReadInt128s(this ReadOnlySpan src, Span dest, Endianness endianness) 176 | { 177 | if (dest.Length != 0) 178 | { 179 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * 2 * sizeof(ulong)); 180 | Utils.ThrowIfInvalidEndianness(endianness); 181 | src.ReadInt128s_Unsafe(dest, endianness); 182 | } 183 | } 184 | 185 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 186 | public static UInt128 ReadUInt128(this ReadOnlySpan src, Endianness endianness) 187 | { 188 | Utils.ThrowIfSrcTooSmall(src.Length, 2 * sizeof(ulong)); 189 | Utils.ThrowIfInvalidEndianness(endianness); 190 | return src.ReadUInt128_Unsafe(endianness); 191 | } 192 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 193 | public static void ReadUInt128s(this ReadOnlySpan src, Span dest, Endianness endianness) 194 | { 195 | if (dest.Length != 0) 196 | { 197 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * 2 * sizeof(ulong)); 198 | Utils.ThrowIfInvalidEndianness(endianness); 199 | src.ReadUInt128s_Unsafe(dest, endianness); 200 | } 201 | } 202 | 203 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 204 | public static Half ReadHalf(this ReadOnlySpan src, Endianness endianness) 205 | { 206 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(ushort)); 207 | Utils.ThrowIfInvalidEndianness(endianness); 208 | return src.ReadHalf_Unsafe(endianness); 209 | } 210 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 211 | public static void ReadHalves(this ReadOnlySpan src, Span dest, Endianness endianness) 212 | { 213 | if (dest.Length != 0) 214 | { 215 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(ushort)); 216 | Utils.ThrowIfInvalidEndianness(endianness); 217 | src.ReadHalves_Unsafe(dest, endianness); 218 | } 219 | } 220 | 221 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 222 | public static float ReadSingle(this ReadOnlySpan src, Endianness endianness) 223 | { 224 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(float)); 225 | Utils.ThrowIfInvalidEndianness(endianness); 226 | return src.ReadSingle_Unsafe(endianness); 227 | } 228 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 229 | public static void ReadSingles(this ReadOnlySpan src, Span dest, Endianness endianness) 230 | { 231 | if (dest.Length != 0) 232 | { 233 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(float)); 234 | Utils.ThrowIfInvalidEndianness(endianness); 235 | src.ReadSingles_Unsafe(dest, endianness); 236 | } 237 | } 238 | 239 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 240 | public static double ReadDouble(this ReadOnlySpan src, Endianness endianness) 241 | { 242 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(double)); 243 | Utils.ThrowIfInvalidEndianness(endianness); 244 | return src.ReadDouble_Unsafe(endianness); 245 | } 246 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 247 | public static void ReadDoubles(this ReadOnlySpan src, Span dest, Endianness endianness) 248 | { 249 | if (dest.Length != 0) 250 | { 251 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(double)); 252 | Utils.ThrowIfInvalidEndianness(endianness); 253 | src.ReadDoubles_Unsafe(dest, endianness); 254 | } 255 | } 256 | 257 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 258 | public static decimal ReadDecimal(this ReadOnlySpan src, Endianness endianness) 259 | { 260 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(decimal)); 261 | Utils.ThrowIfInvalidEndianness(endianness); 262 | return src.ReadDecimal_Unsafe(endianness); 263 | } 264 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 265 | public static void ReadDecimals(this ReadOnlySpan src, Span dest, Endianness endianness) 266 | { 267 | if (dest.Length != 0) 268 | { 269 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(decimal)); 270 | Utils.ThrowIfInvalidEndianness(endianness); 271 | src.ReadDecimals_Unsafe(dest, endianness); 272 | } 273 | } 274 | 275 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 276 | public static bool ReadBoolean8(this ReadOnlySpan src) 277 | { 278 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(byte)); 279 | return src[0] != 0; 280 | } 281 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 282 | public static void ReadBoolean8s(this ReadOnlySpan src, Span dest) 283 | { 284 | if (dest.Length != 0) 285 | { 286 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length); 287 | src.ReadBoolean8s_Unsafe(dest); 288 | } 289 | } 290 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 291 | public static bool ReadBoolean16(this ReadOnlySpan src, Endianness endianness) 292 | { 293 | return src.ReadUInt16(endianness) != 0; 294 | } 295 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 296 | public static void ReadBoolean16s(this ReadOnlySpan src, Span dest, Endianness endianness) 297 | { 298 | if (dest.Length != 0) 299 | { 300 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(ushort)); 301 | Utils.ThrowIfInvalidEndianness(endianness); 302 | src.ReadBoolean16s_Unsafe(dest, endianness); 303 | } 304 | } 305 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 306 | public static bool ReadBoolean32(this ReadOnlySpan src, Endianness endianness) 307 | { 308 | return src.ReadUInt32(endianness) != 0; 309 | } 310 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 311 | public static void ReadBoolean32s(this ReadOnlySpan src, Span dest, Endianness endianness) 312 | { 313 | if (dest.Length != 0) 314 | { 315 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(uint)); 316 | Utils.ThrowIfInvalidEndianness(endianness); 317 | src.ReadBoolean32s_Unsafe(dest, endianness); 318 | } 319 | } 320 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 321 | public static bool ReadBoolean(this ReadOnlySpan src, Endianness endianness, BooleanSize boolSize) 322 | { 323 | return src.ReadBoolean(endianness, GetBytesForBooleanSize(boolSize)); 324 | } 325 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 326 | public static void ReadBooleans(this ReadOnlySpan src, Span dest, Endianness endianness, BooleanSize boolSize) 327 | { 328 | src.ReadBooleans(dest, endianness, GetBytesForBooleanSize(boolSize)); 329 | } 330 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 331 | public static bool ReadBoolean(this ReadOnlySpan src, Endianness endianness, int boolSize) 332 | { 333 | switch (boolSize) 334 | { 335 | case 1: return src.ReadBoolean8(); 336 | case 2: return src.ReadBoolean16(endianness); 337 | case 4: return src.ReadBoolean32(endianness); 338 | default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); 339 | } 340 | } 341 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 342 | public static void ReadBooleans(this ReadOnlySpan src, Span dest, Endianness endianness, int boolSize) 343 | { 344 | switch (boolSize) 345 | { 346 | case 1: src.ReadBoolean8s(dest); break; 347 | case 2: src.ReadBoolean16s(dest, endianness); break; 348 | case 4: src.ReadBoolean32s(dest, endianness); break; 349 | default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); 350 | } 351 | } 352 | 353 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 354 | public static TEnum ReadEnum(this ReadOnlySpan src, Endianness endianness) 355 | where TEnum : unmanaged, Enum 356 | { 357 | int size = Unsafe.SizeOf(); 358 | if (size == 1) 359 | { 360 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(byte)); 361 | byte b = src[0]; 362 | return Unsafe.As(ref b); 363 | } 364 | if (size == 2) 365 | { 366 | ushort s = src.ReadUInt16(endianness); 367 | return Unsafe.As(ref s); 368 | } 369 | if (size == 4) 370 | { 371 | uint i = src.ReadUInt32(endianness); 372 | return Unsafe.As(ref i); 373 | } 374 | ulong l = src.ReadUInt64(endianness); 375 | return Unsafe.As(ref l); 376 | } 377 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 378 | public static void ReadEnums(this ReadOnlySpan src, Span dest, Endianness endianness) 379 | where TEnum : unmanaged, Enum 380 | { 381 | if (dest.Length == 0) 382 | { 383 | return; 384 | } 385 | 386 | int size = Unsafe.SizeOf(); 387 | if (size == sizeof(byte)) 388 | { 389 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length); 390 | src.ReadCast(dest.Length).CopyTo(dest); 391 | } 392 | else if (size == sizeof(ushort)) 393 | { 394 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(ushort)); 395 | Utils.ThrowIfInvalidEndianness(endianness); 396 | if (endianness == SystemEndianness) 397 | { 398 | src.ReadCast(dest.Length).CopyTo(dest); 399 | } 400 | else 401 | { 402 | ReverseEndianness(src.ReadCast(dest.Length), dest.WriteCast(dest.Length)); 403 | } 404 | } 405 | else if (size == sizeof(uint)) 406 | { 407 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(uint)); 408 | Utils.ThrowIfInvalidEndianness(endianness); 409 | if (endianness == SystemEndianness) 410 | { 411 | src.ReadCast(dest.Length).CopyTo(dest); 412 | } 413 | else 414 | { 415 | ReverseEndianness(src.ReadCast(dest.Length), dest.WriteCast(dest.Length)); 416 | } 417 | } 418 | else 419 | { 420 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(ulong)); 421 | Utils.ThrowIfInvalidEndianness(endianness); 422 | if (endianness == SystemEndianness) 423 | { 424 | src.ReadCast(dest.Length).CopyTo(dest); 425 | } 426 | else 427 | { 428 | ReverseEndianness(src.ReadCast(dest.Length), dest.WriteCast(dest.Length)); 429 | } 430 | } 431 | } 432 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 433 | public static object ReadEnum(this ReadOnlySpan src, Endianness endianness, Type enumType) 434 | { 435 | // Type.IsEnum is also false for base Enum type, so don't worry about it 436 | Type underlyingType = Enum.GetUnderlyingType(enumType); 437 | switch (Type.GetTypeCode(underlyingType)) 438 | { 439 | case TypeCode.SByte: 440 | { 441 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(sbyte)); 442 | return Enum.ToObject(enumType, (sbyte)src[0]); 443 | } 444 | case TypeCode.Byte: 445 | { 446 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(byte)); 447 | return Enum.ToObject(enumType, src[0]); 448 | } 449 | case TypeCode.Int16: 450 | { 451 | return Enum.ToObject(enumType, src.ReadInt16(endianness)); 452 | } 453 | case TypeCode.UInt16: 454 | { 455 | return Enum.ToObject(enumType, src.ReadUInt16(endianness)); 456 | } 457 | case TypeCode.Int32: 458 | { 459 | return Enum.ToObject(enumType, src.ReadInt32(endianness)); 460 | } 461 | case TypeCode.UInt32: 462 | { 463 | return Enum.ToObject(enumType, src.ReadUInt32(endianness)); 464 | } 465 | case TypeCode.Int64: 466 | { 467 | return Enum.ToObject(enumType, src.ReadInt64(endianness)); 468 | } 469 | case TypeCode.UInt64: 470 | { 471 | return Enum.ToObject(enumType, src.ReadUInt64(endianness)); 472 | } 473 | } 474 | throw new ArgumentOutOfRangeException(nameof(enumType), enumType, null); 475 | } 476 | 477 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 478 | public static DateTime ReadDateTime(this ReadOnlySpan src, Endianness endianness) 479 | { 480 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(long)); 481 | Utils.ThrowIfInvalidEndianness(endianness); 482 | return src.ReadDateTime_Unsafe(endianness); 483 | } 484 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 485 | public static void ReadDateTimes(this ReadOnlySpan src, Span dest, Endianness endianness) 486 | { 487 | if (dest.Length != 0) 488 | { 489 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(long)); 490 | Utils.ThrowIfInvalidEndianness(endianness); 491 | src.ReadDateTimes_Unsafe(dest, endianness); 492 | } 493 | } 494 | 495 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 496 | public static DateOnly ReadDateOnly(this ReadOnlySpan src, Endianness endianness) 497 | { 498 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(int)); 499 | Utils.ThrowIfInvalidEndianness(endianness); 500 | return src.ReadDateOnly_Unsafe(endianness); 501 | } 502 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 503 | public static void ReadDateOnlys(this ReadOnlySpan src, Span dest, Endianness endianness) 504 | { 505 | if (dest.Length != 0) 506 | { 507 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(int)); 508 | Utils.ThrowIfInvalidEndianness(endianness); 509 | src.ReadDateOnlys_Unsafe(dest, endianness); 510 | } 511 | } 512 | 513 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 514 | public static TimeOnly ReadTimeOnly(this ReadOnlySpan src, Endianness endianness) 515 | { 516 | Utils.ThrowIfSrcTooSmall(src.Length, sizeof(long)); 517 | Utils.ThrowIfInvalidEndianness(endianness); 518 | return src.ReadTimeOnly_Unsafe(endianness); 519 | } 520 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 521 | public static void ReadTimeOnlys(this ReadOnlySpan src, Span dest, Endianness endianness) 522 | { 523 | if (dest.Length != 0) 524 | { 525 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * sizeof(long)); 526 | Utils.ThrowIfInvalidEndianness(endianness); 527 | src.ReadTimeOnlys_Unsafe(dest, endianness); 528 | } 529 | } 530 | 531 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 532 | public static Vector2 ReadVector2(this ReadOnlySpan src, Endianness endianness) 533 | { 534 | Utils.ThrowIfSrcTooSmall(src.Length, 2 * sizeof(float)); 535 | Utils.ThrowIfInvalidEndianness(endianness); 536 | return src.ReadVector2_Unsafe(endianness); 537 | } 538 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 539 | public static void ReadVector2s(this ReadOnlySpan src, Span dest, Endianness endianness) 540 | { 541 | if (dest.Length != 0) 542 | { 543 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * 2 * sizeof(float)); 544 | Utils.ThrowIfInvalidEndianness(endianness); 545 | src.ReadVector2s_Unsafe(dest, endianness); 546 | } 547 | } 548 | 549 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 550 | public static Vector3 ReadVector3(this ReadOnlySpan src, Endianness endianness) 551 | { 552 | Utils.ThrowIfSrcTooSmall(src.Length, 3 * sizeof(float)); 553 | Utils.ThrowIfInvalidEndianness(endianness); 554 | return src.ReadVector3_Unsafe(endianness); 555 | } 556 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 557 | public static void ReadVector3s(this ReadOnlySpan src, Span dest, Endianness endianness) 558 | { 559 | if (dest.Length != 0) 560 | { 561 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * 3 * sizeof(float)); 562 | Utils.ThrowIfInvalidEndianness(endianness); 563 | src.ReadVector3s_Unsafe(dest, endianness); 564 | } 565 | } 566 | 567 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 568 | public static Vector4 ReadVector4(this ReadOnlySpan src, Endianness endianness) 569 | { 570 | Utils.ThrowIfSrcTooSmall(src.Length, 4 * sizeof(float)); 571 | Utils.ThrowIfInvalidEndianness(endianness); 572 | return src.ReadVector4_Unsafe(endianness); 573 | } 574 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 575 | public static void ReadVector4s(this ReadOnlySpan src, Span dest, Endianness endianness) 576 | { 577 | if (dest.Length != 0) 578 | { 579 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * 4 * sizeof(float)); 580 | Utils.ThrowIfInvalidEndianness(endianness); 581 | src.ReadVector4s_Unsafe(dest, endianness); 582 | } 583 | } 584 | 585 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 586 | public static Quaternion ReadQuaternion(this ReadOnlySpan src, Endianness endianness) 587 | { 588 | Utils.ThrowIfSrcTooSmall(src.Length, 4 * sizeof(float)); 589 | Utils.ThrowIfInvalidEndianness(endianness); 590 | return src.ReadQuaternion_Unsafe(endianness); 591 | } 592 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 593 | public static void ReadQuaternions(this ReadOnlySpan src, Span dest, Endianness endianness) 594 | { 595 | if (dest.Length != 0) 596 | { 597 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * 4 * sizeof(float)); 598 | Utils.ThrowIfInvalidEndianness(endianness); 599 | src.ReadQuaternions_Unsafe(dest, endianness); 600 | } 601 | } 602 | 603 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 604 | public static Matrix4x4 ReadMatrix4x4(this ReadOnlySpan src, Endianness endianness) 605 | { 606 | Utils.ThrowIfSrcTooSmall(src.Length, 16 * sizeof(float)); 607 | Utils.ThrowIfInvalidEndianness(endianness); 608 | return src.ReadMatrix4x4_Unsafe(endianness); 609 | } 610 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 611 | public static void ReadMatrix4x4s(this ReadOnlySpan src, Span dest, Endianness endianness) 612 | { 613 | if (dest.Length != 0) 614 | { 615 | Utils.ThrowIfSrcTooSmall(src.Length, dest.Length * 16 * sizeof(float)); 616 | Utils.ThrowIfInvalidEndianness(endianness); 617 | src.ReadMatrix4x4s_Unsafe(dest, endianness); 618 | } 619 | } 620 | 621 | #endregion 622 | 623 | #region Write 624 | 625 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 626 | public static void WriteSBytes(this Span dest, ReadOnlySpan src) 627 | { 628 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length); 629 | dest.WriteSBytes_Unsafe(src); 630 | } 631 | 632 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 633 | public static void WriteInt16(this Span dest, short value, Endianness endianness) 634 | { 635 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(short)); 636 | Utils.ThrowIfInvalidEndianness(endianness); 637 | dest.WriteInt16_Unsafe(value, endianness); 638 | } 639 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 640 | public static void WriteInt16s(this Span dest, ReadOnlySpan src, Endianness endianness) 641 | { 642 | if (src.Length != 0) 643 | { 644 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(short)); 645 | Utils.ThrowIfInvalidEndianness(endianness); 646 | dest.WriteInt16s_Unsafe(src, endianness); 647 | } 648 | } 649 | 650 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 651 | public static void WriteUInt16(this Span dest, ushort value, Endianness endianness) 652 | { 653 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(ushort)); 654 | Utils.ThrowIfInvalidEndianness(endianness); 655 | dest.WriteUInt16_Unsafe(value, endianness); 656 | } 657 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 658 | public static void WriteUInt16s(this Span dest, ReadOnlySpan src, Endianness endianness) 659 | { 660 | if (src.Length != 0) 661 | { 662 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(ushort)); 663 | Utils.ThrowIfInvalidEndianness(endianness); 664 | dest.WriteUInt16s_Unsafe(src, endianness); 665 | } 666 | } 667 | 668 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 669 | public static void WriteInt32(this Span dest, int value, Endianness endianness) 670 | { 671 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(int)); 672 | Utils.ThrowIfInvalidEndianness(endianness); 673 | dest.WriteInt32_Unsafe(value, endianness); 674 | } 675 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 676 | public static void WriteInt32s(this Span dest, ReadOnlySpan src, Endianness endianness) 677 | { 678 | if (src.Length != 0) 679 | { 680 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(int)); 681 | Utils.ThrowIfInvalidEndianness(endianness); 682 | dest.WriteInt32s_Unsafe(src, endianness); 683 | } 684 | } 685 | 686 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 687 | public static void WriteUInt32(this Span dest, uint value, Endianness endianness) 688 | { 689 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(uint)); 690 | Utils.ThrowIfInvalidEndianness(endianness); 691 | dest.WriteUInt32_Unsafe(value, endianness); 692 | } 693 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 694 | public static void WriteUInt32s(this Span dest, ReadOnlySpan src, Endianness endianness) 695 | { 696 | if (src.Length != 0) 697 | { 698 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(uint)); 699 | Utils.ThrowIfInvalidEndianness(endianness); 700 | dest.WriteUInt32s_Unsafe(src, endianness); 701 | } 702 | } 703 | 704 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 705 | public static void WriteInt64(this Span dest, long value, Endianness endianness) 706 | { 707 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(long)); 708 | Utils.ThrowIfInvalidEndianness(endianness); 709 | dest.WriteInt64_Unsafe(value, endianness); 710 | } 711 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 712 | public static void WriteInt64s(this Span dest, ReadOnlySpan src, Endianness endianness) 713 | { 714 | if (src.Length != 0) 715 | { 716 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(long)); 717 | Utils.ThrowIfInvalidEndianness(endianness); 718 | dest.WriteInt64s_Unsafe(src, endianness); 719 | } 720 | } 721 | 722 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 723 | public static void WriteUInt64(this Span dest, ulong value, Endianness endianness) 724 | { 725 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(ulong)); 726 | Utils.ThrowIfInvalidEndianness(endianness); 727 | dest.WriteUInt64_Unsafe(value, endianness); 728 | } 729 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 730 | public static void WriteUInt64s(this Span dest, ReadOnlySpan src, Endianness endianness) 731 | { 732 | if (src.Length != 0) 733 | { 734 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(ulong)); 735 | Utils.ThrowIfInvalidEndianness(endianness); 736 | dest.WriteUInt64s_Unsafe(src, endianness); 737 | } 738 | } 739 | 740 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 741 | public static void WriteInt128(this Span dest, Int128 value, Endianness endianness) 742 | { 743 | Utils.ThrowIfDestTooSmall(dest.Length, 2 * sizeof(ulong)); 744 | Utils.ThrowIfInvalidEndianness(endianness); 745 | dest.WriteInt128_Unsafe(value, endianness); 746 | } 747 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 748 | public static void WriteInt128s(this Span dest, ReadOnlySpan src, Endianness endianness) 749 | { 750 | if (src.Length != 0) 751 | { 752 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * 2 * sizeof(ulong)); 753 | Utils.ThrowIfInvalidEndianness(endianness); 754 | dest.WriteInt128s_Unsafe(src, endianness); 755 | } 756 | } 757 | 758 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 759 | public static void WriteUInt128(this Span dest, UInt128 value, Endianness endianness) 760 | { 761 | Utils.ThrowIfDestTooSmall(dest.Length, 2 * sizeof(ulong)); 762 | Utils.ThrowIfInvalidEndianness(endianness); 763 | dest.WriteUInt128_Unsafe(value, endianness); 764 | } 765 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 766 | public static void WriteUInt128s(this Span dest, ReadOnlySpan src, Endianness endianness) 767 | { 768 | if (src.Length != 0) 769 | { 770 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * 2 * sizeof(ulong)); 771 | Utils.ThrowIfInvalidEndianness(endianness); 772 | dest.WriteUInt128s_Unsafe(src, endianness); 773 | } 774 | } 775 | 776 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 777 | public static void WriteHalf(this Span dest, Half value, Endianness endianness) 778 | { 779 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(ushort)); 780 | Utils.ThrowIfInvalidEndianness(endianness); 781 | dest.WriteHalf_Unsafe(value, endianness); 782 | } 783 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 784 | public static void WriteHalves(this Span dest, ReadOnlySpan src, Endianness endianness) 785 | { 786 | if (src.Length != 0) 787 | { 788 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(ushort)); 789 | Utils.ThrowIfInvalidEndianness(endianness); 790 | dest.WriteHalves_Unsafe(src, endianness); 791 | } 792 | } 793 | 794 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 795 | public static void WriteSingle(this Span dest, float value, Endianness endianness) 796 | { 797 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(float)); 798 | Utils.ThrowIfInvalidEndianness(endianness); 799 | dest.WriteSingle_Unsafe(value, endianness); 800 | } 801 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 802 | public static void WriteSingles(this Span dest, ReadOnlySpan src, Endianness endianness) 803 | { 804 | if (src.Length != 0) 805 | { 806 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(float)); 807 | Utils.ThrowIfInvalidEndianness(endianness); 808 | dest.WriteSingles_Unsafe(src, endianness); 809 | } 810 | } 811 | 812 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 813 | public static void WriteDouble(this Span dest, double value, Endianness endianness) 814 | { 815 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(double)); 816 | Utils.ThrowIfInvalidEndianness(endianness); 817 | dest.WriteDouble_Unsafe(value, endianness); 818 | } 819 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 820 | public static void WriteDoubles(this Span dest, ReadOnlySpan src, Endianness endianness) 821 | { 822 | if (src.Length != 0) 823 | { 824 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(double)); 825 | Utils.ThrowIfInvalidEndianness(endianness); 826 | dest.WriteDoubles_Unsafe(src, endianness); 827 | } 828 | } 829 | 830 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 831 | public static void WriteDecimal(this Span dest, in decimal value, Endianness endianness) 832 | { 833 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(decimal)); 834 | Utils.ThrowIfInvalidEndianness(endianness); 835 | dest.WriteDecimal_Unsafe(value, endianness); 836 | } 837 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 838 | public static void WriteDecimals(this Span dest, ReadOnlySpan src, Endianness endianness) 839 | { 840 | if (src.Length != 0) 841 | { 842 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(decimal)); 843 | Utils.ThrowIfInvalidEndianness(endianness); 844 | dest.WriteDecimals_Unsafe(src, endianness); 845 | } 846 | } 847 | 848 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 849 | public static void WriteBoolean8(this Span dest, bool value) 850 | { 851 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(byte)); 852 | dest[0] = (byte)(value ? 1 : 0); 853 | } 854 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 855 | public static void WriteBoolean8s(this Span dest, ReadOnlySpan src) 856 | { 857 | if (src.Length != 0) 858 | { 859 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length); 860 | dest.WriteBoolean8s_Unsafe(src); 861 | } 862 | } 863 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 864 | public static void WriteBoolean16(this Span dest, bool value, Endianness endianness) 865 | { 866 | dest.WriteUInt16((ushort)(value ? 1 : 0), endianness); 867 | } 868 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 869 | public static void WriteBoolean16s(this Span dest, ReadOnlySpan src, Endianness endianness) 870 | { 871 | if (src.Length != 0) 872 | { 873 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(ushort)); 874 | Utils.ThrowIfInvalidEndianness(endianness); 875 | dest.WriteBoolean16s_Unsafe(src, endianness); 876 | } 877 | } 878 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 879 | public static void WriteBoolean32(this Span dest, bool value, Endianness endianness) 880 | { 881 | dest.WriteUInt32(value ? 1u : 0, endianness); 882 | } 883 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 884 | public static void WriteBoolean32s(this Span dest, ReadOnlySpan src, Endianness endianness) 885 | { 886 | if (src.Length != 0) 887 | { 888 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(uint)); 889 | Utils.ThrowIfInvalidEndianness(endianness); 890 | dest.WriteBoolean32s_Unsafe(src, endianness); 891 | } 892 | } 893 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 894 | public static void WriteBoolean(this Span dest, bool value, Endianness endianness, BooleanSize boolSize) 895 | { 896 | dest.WriteBoolean(value, endianness, GetBytesForBooleanSize(boolSize)); 897 | } 898 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 899 | public static void WriteBooleans(this Span dest, ReadOnlySpan src, Endianness endianness, BooleanSize boolSize) 900 | { 901 | dest.WriteBooleans(src, endianness, GetBytesForBooleanSize(boolSize)); 902 | } 903 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 904 | public static void WriteBoolean(this Span dest, bool value, Endianness endianness, int boolSize) 905 | { 906 | switch (boolSize) 907 | { 908 | case 1: dest.WriteBoolean8(value); break; 909 | case 2: dest.WriteBoolean16(value, endianness); break; 910 | case 4: dest.WriteBoolean32(value, endianness); break; 911 | default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); 912 | } 913 | } 914 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 915 | public static void WriteBooleans(this Span dest, ReadOnlySpan src, Endianness endianness, int boolSize) 916 | { 917 | switch (boolSize) 918 | { 919 | case 1: dest.WriteBoolean8s(src); break; 920 | case 2: dest.WriteBoolean16s(src, endianness); break; 921 | case 4: dest.WriteBoolean32s(src, endianness); break; 922 | default: throw new ArgumentOutOfRangeException(nameof(boolSize), boolSize, null); 923 | } 924 | } 925 | 926 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 927 | public static void WriteEnum(this Span dest, TEnum value, Endianness endianness) 928 | where TEnum : unmanaged, Enum 929 | { 930 | int size = Unsafe.SizeOf(); 931 | if (size == 1) 932 | { 933 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(byte)); 934 | dest[0] = Unsafe.As(ref value); 935 | } 936 | else if (size == 2) 937 | { 938 | dest.WriteUInt16(Unsafe.As(ref value), endianness); 939 | } 940 | else if (size == 4) 941 | { 942 | dest.WriteUInt32(Unsafe.As(ref value), endianness); 943 | } 944 | else 945 | { 946 | dest.WriteUInt64(Unsafe.As(ref value), endianness); 947 | } 948 | } 949 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 950 | public static void WriteEnums(this Span dest, ReadOnlySpan src, Endianness endianness) 951 | where TEnum : unmanaged, Enum 952 | { 953 | if (src.Length == 0) 954 | { 955 | return; 956 | } 957 | 958 | int size = Unsafe.SizeOf(); 959 | if (size == sizeof(byte)) 960 | { 961 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length); 962 | src.CopyTo(dest.WriteCast(src.Length)); 963 | } 964 | else if (size == sizeof(ushort)) 965 | { 966 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(ushort)); 967 | Utils.ThrowIfInvalidEndianness(endianness); 968 | if (endianness == SystemEndianness) 969 | { 970 | src.CopyTo(dest.WriteCast(src.Length)); 971 | } 972 | else 973 | { 974 | ReverseEndianness(src.ReadCast(src.Length), dest.WriteCast(src.Length)); 975 | } 976 | } 977 | else if (size == sizeof(uint)) 978 | { 979 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(uint)); 980 | Utils.ThrowIfInvalidEndianness(endianness); 981 | if (endianness == SystemEndianness) 982 | { 983 | src.CopyTo(dest.WriteCast(src.Length)); 984 | } 985 | else 986 | { 987 | ReverseEndianness(src.ReadCast(src.Length), dest.WriteCast(src.Length)); 988 | } 989 | } 990 | else 991 | { 992 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(ulong)); 993 | Utils.ThrowIfInvalidEndianness(endianness); 994 | if (endianness == SystemEndianness) 995 | { 996 | src.CopyTo(dest.WriteCast(src.Length)); 997 | } 998 | else 999 | { 1000 | ReverseEndianness(src.ReadCast(src.Length), dest.WriteCast(src.Length)); 1001 | } 1002 | } 1003 | } 1004 | // #13 - Allow writing the abstract "Enum" type 1005 | // For example, EndianBinaryPrimitives.WriteEnum((Enum)Enum.Parse(enumType, value)) 1006 | // Don't allow writing Enum[] though, since there is no way to read that 1007 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1008 | public static void WriteEnum(this Span dest, Enum value, Endianness endianness) 1009 | { 1010 | Type underlyingType = Enum.GetUnderlyingType(value.GetType()); 1011 | ref byte data = ref Utils.GetRawData(value); // Use memory tricks to skip object header of generic Enum 1012 | switch (Type.GetTypeCode(underlyingType)) 1013 | { 1014 | case TypeCode.SByte: 1015 | case TypeCode.Byte: 1016 | { 1017 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(byte)); 1018 | dest[0] = data; 1019 | break; 1020 | } 1021 | case TypeCode.Int16: 1022 | case TypeCode.UInt16: 1023 | { 1024 | dest.WriteUInt16(Unsafe.As(ref data), endianness); 1025 | break; 1026 | } 1027 | case TypeCode.Int32: 1028 | case TypeCode.UInt32: 1029 | { 1030 | dest.WriteUInt32(Unsafe.As(ref data), endianness); 1031 | break; 1032 | } 1033 | case TypeCode.Int64: 1034 | case TypeCode.UInt64: 1035 | { 1036 | dest.WriteUInt64(Unsafe.As(ref data), endianness); 1037 | break; 1038 | } 1039 | } 1040 | } 1041 | 1042 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1043 | public static void WriteDateTime(this Span dest, DateTime value, Endianness endianness) 1044 | { 1045 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(long)); 1046 | Utils.ThrowIfInvalidEndianness(endianness); 1047 | dest.WriteDateTime_Unsafe(value, endianness); 1048 | } 1049 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1050 | public static void WriteDateTimes(this Span dest, ReadOnlySpan src, Endianness endianness) 1051 | { 1052 | if (src.Length != 0) 1053 | { 1054 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(long)); 1055 | Utils.ThrowIfInvalidEndianness(endianness); 1056 | dest.WriteDateTimes_Unsafe(src, endianness); 1057 | } 1058 | } 1059 | 1060 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1061 | public static void WriteDateOnly(this Span dest, DateOnly value, Endianness endianness) 1062 | { 1063 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(int)); 1064 | Utils.ThrowIfInvalidEndianness(endianness); 1065 | dest.WriteDateOnly_Unsafe(value, endianness); 1066 | } 1067 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1068 | public static void WriteDateOnlys(this Span dest, ReadOnlySpan src, Endianness endianness) 1069 | { 1070 | if (src.Length != 0) 1071 | { 1072 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(int)); 1073 | Utils.ThrowIfInvalidEndianness(endianness); 1074 | dest.WriteDateOnlys_Unsafe(src, endianness); 1075 | } 1076 | } 1077 | 1078 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1079 | public static void WriteTimeOnly(this Span dest, TimeOnly value, Endianness endianness) 1080 | { 1081 | Utils.ThrowIfDestTooSmall(dest.Length, sizeof(long)); 1082 | Utils.ThrowIfInvalidEndianness(endianness); 1083 | dest.WriteTimeOnly_Unsafe(value, endianness); 1084 | } 1085 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1086 | public static void WriteTimeOnlys(this Span dest, ReadOnlySpan src, Endianness endianness) 1087 | { 1088 | if (src.Length != 0) 1089 | { 1090 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * sizeof(long)); 1091 | Utils.ThrowIfInvalidEndianness(endianness); 1092 | dest.WriteTimeOnlys_Unsafe(src, endianness); 1093 | } 1094 | } 1095 | 1096 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1097 | public static void WriteVector2(this Span dest, Vector2 value, Endianness endianness) 1098 | { 1099 | Utils.ThrowIfDestTooSmall(dest.Length, 2 * sizeof(float)); 1100 | Utils.ThrowIfInvalidEndianness(endianness); 1101 | dest.WriteVector2_Unsafe(value, endianness); 1102 | } 1103 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1104 | public static void WriteVector2s(this Span dest, ReadOnlySpan src, Endianness endianness) 1105 | { 1106 | if (src.Length != 0) 1107 | { 1108 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * 2 * sizeof(float)); 1109 | Utils.ThrowIfInvalidEndianness(endianness); 1110 | dest.WriteVector2s_Unsafe(src, endianness); 1111 | } 1112 | } 1113 | 1114 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1115 | public static void WriteVector3(this Span dest, Vector3 value, Endianness endianness) 1116 | { 1117 | Utils.ThrowIfDestTooSmall(dest.Length, 3 * sizeof(float)); 1118 | Utils.ThrowIfInvalidEndianness(endianness); 1119 | dest.WriteVector3_Unsafe(value, endianness); 1120 | } 1121 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1122 | public static void WriteVector3s(this Span dest, ReadOnlySpan src, Endianness endianness) 1123 | { 1124 | if (src.Length != 0) 1125 | { 1126 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * 3 * sizeof(float)); 1127 | Utils.ThrowIfInvalidEndianness(endianness); 1128 | dest.WriteVector3s_Unsafe(src, endianness); 1129 | } 1130 | } 1131 | 1132 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1133 | public static void WriteVector4(this Span dest, in Vector4 value, Endianness endianness) 1134 | { 1135 | Utils.ThrowIfDestTooSmall(dest.Length, 4 * sizeof(float)); 1136 | Utils.ThrowIfInvalidEndianness(endianness); 1137 | dest.WriteVector4_Unsafe(value, endianness); 1138 | } 1139 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1140 | public static void WriteVector4s(this Span dest, ReadOnlySpan src, Endianness endianness) 1141 | { 1142 | if (src.Length != 0) 1143 | { 1144 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * 4 * sizeof(float)); 1145 | Utils.ThrowIfInvalidEndianness(endianness); 1146 | dest.WriteVector4s_Unsafe(src, endianness); 1147 | } 1148 | } 1149 | 1150 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1151 | public static void WriteQuaternion(this Span dest, in Quaternion value, Endianness endianness) 1152 | { 1153 | Utils.ThrowIfDestTooSmall(dest.Length, 4 * sizeof(float)); 1154 | Utils.ThrowIfInvalidEndianness(endianness); 1155 | dest.WriteQuaternion_Unsafe(value, endianness); 1156 | } 1157 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1158 | public static void WriteQuaternions(this Span dest, ReadOnlySpan src, Endianness endianness) 1159 | { 1160 | if (src.Length != 0) 1161 | { 1162 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * 4 * sizeof(float)); 1163 | Utils.ThrowIfInvalidEndianness(endianness); 1164 | dest.WriteQuaternions_Unsafe(src, endianness); 1165 | } 1166 | } 1167 | 1168 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1169 | public static void WriteMatrix4x4(this Span dest, in Matrix4x4 value, Endianness endianness) 1170 | { 1171 | Utils.ThrowIfDestTooSmall(dest.Length, 16 * sizeof(float)); 1172 | Utils.ThrowIfInvalidEndianness(endianness); 1173 | dest.WriteMatrix4x4_Unsafe(value, endianness); 1174 | } 1175 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 1176 | public static void WriteMatrix4x4s(this Span dest, ReadOnlySpan src, Endianness endianness) 1177 | { 1178 | if (src.Length != 0) 1179 | { 1180 | Utils.ThrowIfDestTooSmall(dest.Length, src.Length * 16 * sizeof(float)); 1181 | Utils.ThrowIfInvalidEndianness(endianness); 1182 | dest.WriteMatrix4x4s_Unsafe(src, endianness); 1183 | } 1184 | } 1185 | 1186 | #endregion 1187 | } 1188 | -------------------------------------------------------------------------------- /Source/EndianBinaryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.IO; 4 | using System.Numerics; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace Kermalis.EndianBinaryIO; 9 | 10 | public partial class EndianBinaryReader 11 | { 12 | protected const int BUF_LEN = 64; // Must be a multiple of 64 13 | 14 | private Stream _stream; 15 | public Stream Stream 16 | { 17 | get => _stream; 18 | [MemberNotNull(nameof(_stream))] 19 | set 20 | { 21 | if (!value.CanRead) 22 | { 23 | throw new ArgumentOutOfRangeException(nameof(value), "Stream is not open for reading."); 24 | } 25 | _stream = value; 26 | } 27 | } 28 | private Endianness _endianness; 29 | public Endianness Endianness 30 | { 31 | get => _endianness; 32 | set 33 | { 34 | Utils.ThrowIfInvalidEndianness(value); 35 | _endianness = value; 36 | } 37 | } 38 | private BooleanSize _booleanSize; 39 | public BooleanSize BooleanSize 40 | { 41 | get => _booleanSize; 42 | set 43 | { 44 | if (value >= BooleanSize.MAX) 45 | { 46 | throw new ArgumentOutOfRangeException(nameof(value), value, null); 47 | } 48 | _booleanSize = value; 49 | } 50 | } 51 | public bool ASCII { get; set; } 52 | 53 | protected readonly byte[] _buffer; 54 | 55 | public EndianBinaryReader(Stream stream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8, bool ascii = false) 56 | { 57 | Stream = stream; 58 | Endianness = endianness; 59 | BooleanSize = booleanSize; 60 | ASCII = ascii; 61 | _buffer = new byte[BUF_LEN]; 62 | } 63 | 64 | protected delegate void ReadArrayMethod(ReadOnlySpan src, Span dest, Endianness endianness); 65 | protected void ReadArray(Span dest, int elementSize, ReadArrayMethod readArray) 66 | { 67 | int numBytes = dest.Length * elementSize; 68 | int start = 0; 69 | while (numBytes != 0) 70 | { 71 | int consumeBytes = Math.Min(numBytes, BUF_LEN); 72 | 73 | Span buffer = _buffer.AsSpan(0, consumeBytes); 74 | ReadBytes(buffer); 75 | readArray(buffer, dest.Slice(start, consumeBytes / elementSize), Endianness); 76 | 77 | numBytes -= consumeBytes; 78 | start += consumeBytes / elementSize; 79 | } 80 | } 81 | private void ReadBoolArray(Span dest, int boolSize) 82 | { 83 | int numBytes = dest.Length * boolSize; 84 | int start = 0; 85 | while (numBytes != 0) 86 | { 87 | int consumeBytes = Math.Min(numBytes, BUF_LEN); 88 | 89 | Span buffer = _buffer.AsSpan(0, consumeBytes); 90 | ReadBytes(buffer); 91 | EndianBinaryPrimitives.ReadBooleans_Unsafe(buffer, dest.Slice(start, consumeBytes / boolSize), Endianness, boolSize); 92 | 93 | numBytes -= consumeBytes; 94 | start += consumeBytes / boolSize; 95 | } 96 | } 97 | 98 | public byte PeekByte() 99 | { 100 | long offset = _stream.Position; 101 | 102 | Span buffer = _buffer.AsSpan(0, 1); 103 | ReadBytes(buffer); 104 | 105 | _stream.Position = offset; 106 | return buffer[0]; 107 | } 108 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 109 | public void PeekBytes(Span dest) 110 | { 111 | long offset = _stream.Position; 112 | 113 | ReadBytes(dest); 114 | 115 | _stream.Position = offset; 116 | } 117 | 118 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 119 | public sbyte ReadSByte() 120 | { 121 | Span buffer = _buffer.AsSpan(0, 1); 122 | ReadBytes(buffer); 123 | return (sbyte)buffer[0]; 124 | } 125 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 126 | public void ReadSBytes(Span dest) 127 | { 128 | Span buffer = dest.WriteCast(dest.Length); 129 | ReadBytes(buffer); 130 | } 131 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 132 | public byte ReadByte() 133 | { 134 | Span buffer = _buffer.AsSpan(0, 1); 135 | ReadBytes(buffer); 136 | return buffer[0]; 137 | } 138 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 139 | public void ReadBytes(Span dest) 140 | { 141 | if (_stream.Read(dest) != dest.Length) 142 | { 143 | throw new EndOfStreamException(); 144 | } 145 | } 146 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 147 | public short ReadInt16() 148 | { 149 | Span buffer = _buffer.AsSpan(0, 2); 150 | ReadBytes(buffer); 151 | return EndianBinaryPrimitives.ReadInt16_Unsafe(buffer, Endianness); 152 | } 153 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 154 | public void ReadInt16s(Span dest) 155 | { 156 | ReadArray(dest, 2, EndianBinaryPrimitives.ReadInt16s_Unsafe); 157 | } 158 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 159 | public ushort ReadUInt16() 160 | { 161 | Span buffer = _buffer.AsSpan(0, 2); 162 | ReadBytes(buffer); 163 | return EndianBinaryPrimitives.ReadUInt16_Unsafe(buffer, Endianness); 164 | } 165 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 166 | public void ReadUInt16s(Span dest) 167 | { 168 | ReadArray(dest, 2, EndianBinaryPrimitives.ReadUInt16s_Unsafe); 169 | } 170 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 171 | public int ReadInt32() 172 | { 173 | Span buffer = _buffer.AsSpan(0, 4); 174 | ReadBytes(buffer); 175 | return EndianBinaryPrimitives.ReadInt32_Unsafe(buffer, Endianness); 176 | } 177 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 178 | public void ReadInt32s(Span dest) 179 | { 180 | ReadArray(dest, 4, EndianBinaryPrimitives.ReadInt32s_Unsafe); 181 | } 182 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 183 | public uint ReadUInt32() 184 | { 185 | Span buffer = _buffer.AsSpan(0, 4); 186 | ReadBytes(buffer); 187 | return EndianBinaryPrimitives.ReadUInt32_Unsafe(buffer, Endianness); 188 | } 189 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 190 | public void ReadUInt32s(Span dest) 191 | { 192 | ReadArray(dest, 4, EndianBinaryPrimitives.ReadUInt32s_Unsafe); 193 | } 194 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 195 | public long ReadInt64() 196 | { 197 | Span buffer = _buffer.AsSpan(0, 8); 198 | ReadBytes(buffer); 199 | return EndianBinaryPrimitives.ReadInt64_Unsafe(buffer, Endianness); 200 | } 201 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 202 | public void ReadInt64s(Span dest) 203 | { 204 | ReadArray(dest, 8, EndianBinaryPrimitives.ReadInt64s_Unsafe); 205 | } 206 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 207 | public ulong ReadUInt64() 208 | { 209 | Span buffer = _buffer.AsSpan(0, 8); 210 | ReadBytes(buffer); 211 | return EndianBinaryPrimitives.ReadUInt64_Unsafe(buffer, Endianness); 212 | } 213 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 214 | public void ReadUInt64s(Span dest) 215 | { 216 | ReadArray(dest, 8, EndianBinaryPrimitives.ReadUInt64s_Unsafe); 217 | } 218 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 219 | public Int128 ReadInt128() 220 | { 221 | Span buffer = _buffer.AsSpan(0, 16); 222 | ReadBytes(buffer); 223 | return EndianBinaryPrimitives.ReadInt128_Unsafe(buffer, Endianness); 224 | } 225 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 226 | public void ReadInt128s(Span dest) 227 | { 228 | ReadArray(dest, 16, EndianBinaryPrimitives.ReadInt128s_Unsafe); 229 | } 230 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 231 | public UInt128 ReadUInt128() 232 | { 233 | Span buffer = _buffer.AsSpan(0, 16); 234 | ReadBytes(buffer); 235 | return EndianBinaryPrimitives.ReadUInt128_Unsafe(buffer, Endianness); 236 | } 237 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 238 | public void ReadUInt128s(Span dest) 239 | { 240 | ReadArray(dest, 16, EndianBinaryPrimitives.ReadUInt128s_Unsafe); 241 | } 242 | 243 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 244 | public Half ReadHalf() 245 | { 246 | Span buffer = _buffer.AsSpan(0, 2); 247 | ReadBytes(buffer); 248 | return EndianBinaryPrimitives.ReadHalf_Unsafe(buffer, Endianness); 249 | } 250 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 251 | public void ReadHalves(Span dest) 252 | { 253 | ReadArray(dest, 2, EndianBinaryPrimitives.ReadHalves_Unsafe); 254 | } 255 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 256 | public float ReadSingle() 257 | { 258 | Span buffer = _buffer.AsSpan(0, 4); 259 | ReadBytes(buffer); 260 | return EndianBinaryPrimitives.ReadSingle_Unsafe(buffer, Endianness); 261 | } 262 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 263 | public void ReadSingles(Span dest) 264 | { 265 | ReadArray(dest, 4, EndianBinaryPrimitives.ReadSingles_Unsafe); 266 | } 267 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 268 | public double ReadDouble() 269 | { 270 | Span buffer = _buffer.AsSpan(0, 8); 271 | ReadBytes(buffer); 272 | return EndianBinaryPrimitives.ReadDouble_Unsafe(buffer, Endianness); 273 | } 274 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 275 | public void ReadDoubles(Span dest) 276 | { 277 | ReadArray(dest, 8, EndianBinaryPrimitives.ReadDoubles_Unsafe); 278 | } 279 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 280 | public decimal ReadDecimal() 281 | { 282 | Span buffer = _buffer.AsSpan(0, 16); 283 | ReadBytes(buffer); 284 | return EndianBinaryPrimitives.ReadDecimal_Unsafe(buffer, Endianness); 285 | } 286 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 287 | public void ReadDecimals(Span dest) 288 | { 289 | ReadArray(dest, 16, EndianBinaryPrimitives.ReadDecimals_Unsafe); 290 | } 291 | 292 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 293 | public bool ReadBoolean8() 294 | { 295 | return ReadByte() != 0; 296 | } 297 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 298 | public void ReadBoolean8s(Span dest) 299 | { 300 | ReadBoolArray(dest, sizeof(byte)); 301 | } 302 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 303 | public bool ReadBoolean16() 304 | { 305 | return ReadUInt16() != 0; 306 | } 307 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 308 | public void ReadBoolean16s(Span dest) 309 | { 310 | ReadBoolArray(dest, sizeof(ushort)); 311 | } 312 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 313 | public bool ReadBoolean32() 314 | { 315 | return ReadUInt32() != 0; 316 | } 317 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 318 | public void ReadBoolean32s(Span dest) 319 | { 320 | ReadBoolArray(dest, sizeof(uint)); 321 | } 322 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 323 | public bool ReadBoolean() 324 | { 325 | switch (BooleanSize) 326 | { 327 | case BooleanSize.U8: return ReadByte() != 0; 328 | case BooleanSize.U16: return ReadUInt16() != 0; 329 | case BooleanSize.U32: return ReadUInt32() != 0; 330 | default: throw new InvalidOperationException(); 331 | } 332 | } 333 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 334 | public void ReadBooleans(Span dest) 335 | { 336 | ReadBoolArray(dest, EndianBinaryPrimitives.GetBytesForBooleanSize(BooleanSize)); 337 | } 338 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 339 | public TEnum ReadEnum() 340 | where TEnum : unmanaged, Enum 341 | { 342 | int size = Unsafe.SizeOf(); 343 | if (size == 1) 344 | { 345 | byte b = ReadByte(); 346 | return Unsafe.As(ref b); 347 | } 348 | if (size == 2) 349 | { 350 | ushort s = ReadUInt16(); 351 | return Unsafe.As(ref s); 352 | } 353 | if (size == 4) 354 | { 355 | uint i = ReadUInt32(); 356 | return Unsafe.As(ref i); 357 | } 358 | ulong l = ReadUInt64(); 359 | return Unsafe.As(ref l); 360 | } 361 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 362 | public void ReadEnums(Span dest) 363 | where TEnum : unmanaged, Enum 364 | { 365 | int size = Unsafe.SizeOf(); 366 | if (size == 1) 367 | { 368 | ReadBytes(dest.WriteCast(dest.Length)); 369 | } 370 | else if (size == 2) 371 | { 372 | ReadUInt16s(dest.WriteCast(dest.Length * 2)); 373 | } 374 | else if (size == 4) 375 | { 376 | ReadUInt32s(dest.WriteCast(dest.Length * 4)); 377 | } 378 | else 379 | { 380 | ReadUInt64s(dest.WriteCast(dest.Length * 8)); 381 | } 382 | } 383 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 384 | public object ReadEnum(Type enumType) 385 | { 386 | // Type.IsEnum is also false for base Enum type, so don't worry about it 387 | Type underlyingType = Enum.GetUnderlyingType(enumType); 388 | switch (Type.GetTypeCode(underlyingType)) 389 | { 390 | case TypeCode.SByte: return Enum.ToObject(enumType, ReadSByte()); 391 | case TypeCode.Byte: return Enum.ToObject(enumType, ReadByte()); 392 | case TypeCode.Int16: return Enum.ToObject(enumType, ReadInt16()); 393 | case TypeCode.UInt16: return Enum.ToObject(enumType, ReadUInt16()); 394 | case TypeCode.Int32: return Enum.ToObject(enumType, ReadInt32()); 395 | case TypeCode.UInt32: return Enum.ToObject(enumType, ReadUInt32()); 396 | case TypeCode.Int64: return Enum.ToObject(enumType, ReadInt64()); 397 | case TypeCode.UInt64: return Enum.ToObject(enumType, ReadUInt64()); 398 | } 399 | throw new ArgumentOutOfRangeException(nameof(enumType), enumType, null); 400 | } 401 | 402 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 403 | public DateTime ReadDateTime() 404 | { 405 | Span buffer = _buffer.AsSpan(0, 8); 406 | ReadBytes(buffer); 407 | return EndianBinaryPrimitives.ReadDateTime_Unsafe(buffer, Endianness); 408 | } 409 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 410 | public void ReadDateTimes(Span dest) 411 | { 412 | ReadArray(dest, 8, EndianBinaryPrimitives.ReadDateTimes_Unsafe); 413 | } 414 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 415 | public DateOnly ReadDateOnly() 416 | { 417 | Span buffer = _buffer.AsSpan(0, 4); 418 | ReadBytes(buffer); 419 | return EndianBinaryPrimitives.ReadDateOnly_Unsafe(buffer, Endianness); 420 | } 421 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 422 | public void ReadDateOnlys(Span dest) 423 | { 424 | ReadArray(dest, 4, EndianBinaryPrimitives.ReadDateOnlys_Unsafe); 425 | } 426 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 427 | public TimeOnly ReadTimeOnly() 428 | { 429 | Span buffer = _buffer.AsSpan(0, 8); 430 | ReadBytes(buffer); 431 | return EndianBinaryPrimitives.ReadTimeOnly_Unsafe(buffer, Endianness); 432 | } 433 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 434 | public void ReadTimeOnlys(Span dest) 435 | { 436 | ReadArray(dest, 8, EndianBinaryPrimitives.ReadTimeOnlys_Unsafe); 437 | } 438 | 439 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 440 | public Vector2 ReadVector2() 441 | { 442 | Span buffer = _buffer.AsSpan(0, 8); 443 | ReadBytes(buffer); 444 | return EndianBinaryPrimitives.ReadVector2_Unsafe(buffer, Endianness); 445 | } 446 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 447 | public void ReadVector2s(Span dest) 448 | { 449 | ReadArray(dest, 8, EndianBinaryPrimitives.ReadVector2s_Unsafe); 450 | } 451 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 452 | public Vector3 ReadVector3() 453 | { 454 | Span buffer = _buffer.AsSpan(0, 12); 455 | ReadBytes(buffer); 456 | return EndianBinaryPrimitives.ReadVector3_Unsafe(buffer, Endianness); 457 | } 458 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 459 | public void ReadVector3s(Span dest) 460 | { 461 | ReadArray(dest, 12, EndianBinaryPrimitives.ReadVector3s_Unsafe); 462 | } 463 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 464 | public Vector4 ReadVector4() 465 | { 466 | Span buffer = _buffer.AsSpan(0, 16); 467 | ReadBytes(buffer); 468 | return EndianBinaryPrimitives.ReadVector4_Unsafe(buffer, Endianness); 469 | } 470 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 471 | public void ReadVector4s(Span dest) 472 | { 473 | ReadArray(dest, 16, EndianBinaryPrimitives.ReadVector4s_Unsafe); 474 | } 475 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 476 | public Quaternion ReadQuaternion() 477 | { 478 | Span buffer = _buffer.AsSpan(0, 16); 479 | ReadBytes(buffer); 480 | return EndianBinaryPrimitives.ReadQuaternion_Unsafe(buffer, Endianness); 481 | } 482 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 483 | public void ReadQuaternions(Span dest) 484 | { 485 | ReadArray(dest, 16, EndianBinaryPrimitives.ReadQuaternions_Unsafe); 486 | } 487 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 488 | public Matrix4x4 ReadMatrix4x4() 489 | { 490 | Span buffer = _buffer.AsSpan(0, 64); 491 | ReadBytes(buffer); 492 | return EndianBinaryPrimitives.ReadMatrix4x4_Unsafe(buffer, Endianness); 493 | } 494 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 495 | public void ReadMatrix4x4s(Span dest) 496 | { 497 | ReadArray(dest, 64, EndianBinaryPrimitives.ReadMatrix4x4s_Unsafe); 498 | } 499 | 500 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 501 | public char ReadChar() 502 | { 503 | return ASCII ? (char)ReadByte() : (char)ReadUInt16(); 504 | } 505 | public void ReadChars(Span dest) 506 | { 507 | if (ASCII) 508 | { 509 | // Read ASCII chars into 2nd half of dest, then populate first half with those buffered chars 510 | Span buffer = dest.WriteCast(dest.Length * 2).Slice(dest.Length); 511 | ReadBytes(buffer); 512 | for (int i = 0; i < dest.Length; i++) 513 | { 514 | dest[i] = (char)buffer[i]; 515 | } 516 | } 517 | else 518 | { 519 | Span buffer = dest.WriteCast(dest.Length); 520 | ReadUInt16s(buffer); 521 | } 522 | } 523 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 524 | public char[] ReadChars_TrimNullTerminators(int charCount) 525 | { 526 | if (charCount == 0) 527 | { 528 | return []; 529 | } 530 | 531 | char[] chars = new char[charCount]; 532 | ReadChars(chars); 533 | EndianBinaryPrimitives.TrimNullTerminators(ref chars); 534 | return chars; 535 | } 536 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 537 | public string ReadString_Count(int charCount) 538 | { 539 | if (charCount == 0) 540 | { 541 | return string.Empty; 542 | } 543 | 544 | void Create(Span dest, byte _) 545 | { 546 | ReadChars(dest); 547 | } 548 | return string.Create(charCount, byte.MinValue, Create); 549 | } 550 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 551 | public void ReadStrings_Count(Span dest, int charCount) 552 | { 553 | for (int i = 0; i < dest.Length; i++) 554 | { 555 | dest[i] = ReadString_Count(charCount); 556 | } 557 | } 558 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 559 | public string ReadString_Count_TrimNullTerminators(int charCount) 560 | { 561 | if (charCount == 0) 562 | { 563 | return string.Empty; 564 | } 565 | 566 | char[] chars = new char[charCount]; 567 | ReadChars(chars); 568 | 569 | // Trim '\0's 570 | int i = Array.IndexOf(chars, '\0'); 571 | if (i != -1) 572 | { 573 | return new string(chars, 0, i); 574 | } 575 | return new string(chars); 576 | } 577 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 578 | public void ReadStrings_Count_TrimNullTerminators(Span dest, int charCount) 579 | { 580 | for (int i = 0; i < dest.Length; i++) 581 | { 582 | dest[i] = ReadString_Count_TrimNullTerminators(charCount); 583 | } 584 | } 585 | public string ReadString_NullTerminated() 586 | { 587 | var v = new StringBuilder(); 588 | while (true) 589 | { 590 | char c = ReadChar(); 591 | if (c == '\0') 592 | { 593 | break; 594 | } 595 | v.Append(c); 596 | } 597 | return v.ToString(); // Returns string.Empty if length is 0 598 | } 599 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 600 | public void ReadStrings_NullTerminated(Span dest) 601 | { 602 | for (int i = 0; i < dest.Length; i++) 603 | { 604 | dest[i] = ReadString_NullTerminated(); 605 | } 606 | } 607 | } 608 | -------------------------------------------------------------------------------- /Source/EndianBinaryReader_Reflection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Numerics; 4 | using System.Reflection; 5 | 6 | namespace Kermalis.EndianBinaryIO; 7 | 8 | public partial class EndianBinaryReader 9 | { 10 | public void ReadIntoObject(object obj) 11 | { 12 | if (obj is IBinarySerializable bs) 13 | { 14 | bs.Read(this); 15 | return; 16 | } 17 | 18 | Type objType = obj.GetType(); 19 | Utils.ThrowIfCannotReadWriteType(objType); 20 | 21 | // Get public non-static properties 22 | foreach (PropertyInfo propertyInfo in objType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) 23 | { 24 | if (Utils.AttributeValueOrDefault(propertyInfo, false)) 25 | { 26 | continue; // Skip properties with BinaryIgnoreAttribute 27 | } 28 | 29 | Type propertyType = propertyInfo.PropertyType; 30 | object value = propertyType.IsArray 31 | ? ReadPropertyValue_Array(obj, objType, propertyInfo, propertyType) 32 | : ReadPropertyValue_NonArray(obj, objType, propertyInfo, propertyType); 33 | 34 | propertyInfo.SetValue(obj, value); // Set the value into the property 35 | } 36 | } 37 | public T ReadObject() 38 | { 39 | return (T)ReadObject(typeof(T)); 40 | } 41 | public object ReadObject(Type objType) 42 | { 43 | if (!TryReadSupportedObject(objType, out object? obj)) 44 | { 45 | Utils.ThrowIfCannotReadWriteType(objType); 46 | obj = Activator.CreateInstance(objType)!; 47 | ReadIntoObject(obj); 48 | } 49 | return obj; 50 | } 51 | 52 | private bool TryReadSupportedObject(Type objType, [NotNullWhen(true)] out object? obj) 53 | { 54 | switch (objType) 55 | { 56 | case Type t when t == typeof(sbyte): { obj = ReadSByte(); return true; } 57 | case Type t when t == typeof(byte): { obj = ReadByte(); return true; } 58 | case Type t when t == typeof(short): { obj = ReadInt16(); return true; } 59 | case Type t when t == typeof(ushort): { obj = ReadUInt16(); return true; } 60 | case Type t when t == typeof(int): { obj = ReadInt32(); return true; } 61 | case Type t when t == typeof(uint): { obj = ReadUInt32(); return true; } 62 | case Type t when t == typeof(long): { obj = ReadInt64(); return true; } 63 | case Type t when t == typeof(ulong): { obj = ReadUInt64(); return true; } 64 | case Type t when t == typeof(Int128): { obj = ReadInt128(); return true; } 65 | case Type t when t == typeof(UInt128): { obj = ReadUInt128(); return true; } 66 | case Type t when t == typeof(Half): { obj = ReadHalf(); return true; } 67 | case Type t when t == typeof(float): { obj = ReadSingle(); return true; } 68 | case Type t when t == typeof(double): { obj = ReadDouble(); return true; } 69 | case Type t when t == typeof(decimal): { obj = ReadDecimal(); return true; } 70 | case Type t when t == typeof(bool): { obj = ReadBoolean(); return true; } 71 | case Type t when t.IsEnum: { obj = ReadEnum(t); return true; } 72 | case Type t when t == typeof(DateTime): { obj = ReadDateTime(); return true; } 73 | case Type t when t == typeof(DateOnly): { obj = ReadDateOnly(); return true; } 74 | case Type t when t == typeof(TimeOnly): { obj = ReadTimeOnly(); return true; } 75 | case Type t when t == typeof(Vector2): { obj = ReadVector2(); return true; } 76 | case Type t when t == typeof(Vector3): { obj = ReadVector3(); return true; } 77 | case Type t when t == typeof(Vector4): { obj = ReadVector4(); return true; } 78 | case Type t when t == typeof(Quaternion): { obj = ReadQuaternion(); return true; } 79 | case Type t when t == typeof(Matrix4x4): { obj = ReadMatrix4x4(); return true; } 80 | case Type t when t == typeof(char): { obj = ReadChar(); return true; } 81 | case Type t when t == typeof(string): { obj = ReadString_NullTerminated(); return true; } 82 | } 83 | 84 | obj = default; 85 | return false; 86 | } 87 | 88 | private object ReadPropertyValue_NonArray(object obj, Type objType, PropertyInfo propertyInfo, Type propertyType) 89 | { 90 | switch (propertyType) 91 | { 92 | case Type t when t == typeof(bool): 93 | { 94 | BooleanSize old = BooleanSize; 95 | BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); 96 | bool value = ReadBoolean(); 97 | BooleanSize = old; 98 | return value; 99 | } 100 | case Type t when t == typeof(char): 101 | { 102 | bool old = ASCII; 103 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 104 | char value = ReadChar(); 105 | ASCII = old; 106 | return value; 107 | } 108 | case Type t when t == typeof(string): 109 | { 110 | Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); 111 | 112 | bool old = ASCII; 113 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 114 | string value; 115 | if (nullTerminated == true) 116 | { 117 | value = ReadString_NullTerminated(); 118 | } 119 | else 120 | { 121 | bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); 122 | if (trimNullTerminators) 123 | { 124 | value = ReadString_Count_TrimNullTerminators(stringLength); 125 | } 126 | else 127 | { 128 | value = ReadString_Count(stringLength); 129 | } 130 | } 131 | ASCII = old; 132 | return value; 133 | } 134 | default: 135 | { 136 | return ReadObject(propertyType); 137 | } 138 | } 139 | } 140 | private object ReadPropertyValue_Array(object obj, Type objType, PropertyInfo propertyInfo, Type propertyType) 141 | { 142 | int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); 143 | Type elementType = propertyType.GetElementType()!; 144 | if (arrayLength == 0) 145 | { 146 | return Array.CreateInstance(elementType, 0); // Create 0 length array regardless of type 147 | } 148 | 149 | switch (elementType) 150 | { 151 | case Type t when t == typeof(sbyte): 152 | { 153 | sbyte[] value = new sbyte[arrayLength]; 154 | ReadSBytes(value); 155 | return value; 156 | } 157 | case Type t when t == typeof(byte): 158 | { 159 | byte[] value = new byte[arrayLength]; 160 | ReadBytes(value); 161 | return value; 162 | } 163 | case Type t when t == typeof(short): 164 | { 165 | short[] value = new short[arrayLength]; 166 | ReadInt16s(value); 167 | return value; 168 | } 169 | case Type t when t == typeof(ushort): 170 | { 171 | ushort[] value = new ushort[arrayLength]; 172 | ReadUInt16s(value); 173 | return value; 174 | } 175 | case Type t when t == typeof(int): 176 | { 177 | int[] value = new int[arrayLength]; 178 | ReadInt32s(value); 179 | return value; 180 | } 181 | case Type t when t == typeof(uint): 182 | { 183 | uint[] value = new uint[arrayLength]; 184 | ReadUInt32s(value); 185 | return value; 186 | } 187 | case Type t when t == typeof(long): 188 | { 189 | long[] value = new long[arrayLength]; 190 | ReadInt64s(value); 191 | return value; 192 | } 193 | case Type t when t == typeof(ulong): 194 | { 195 | ulong[] value = new ulong[arrayLength]; 196 | ReadUInt64s(value); 197 | return value; 198 | } 199 | case Type t when t == typeof(Int128): 200 | { 201 | var value = new Int128[arrayLength]; 202 | ReadInt128s(value); 203 | return value; 204 | } 205 | case Type t when t == typeof(UInt128): 206 | { 207 | var value = new UInt128[arrayLength]; 208 | ReadUInt128s(value); 209 | return value; 210 | } 211 | case Type t when t == typeof(Half): 212 | { 213 | var value = new Half[arrayLength]; 214 | ReadHalves(value); 215 | return value; 216 | } 217 | case Type t when t == typeof(float): 218 | { 219 | float[] value = new float[arrayLength]; 220 | ReadSingles(value); 221 | return value; 222 | } 223 | case Type t when t == typeof(double): 224 | { 225 | double[] value = new double[arrayLength]; 226 | ReadDoubles(value); 227 | return value; 228 | } 229 | case Type t when t == typeof(decimal): 230 | { 231 | decimal[] value = new decimal[arrayLength]; 232 | ReadDecimals(value); 233 | return value; 234 | } 235 | case Type t when t == typeof(bool): 236 | { 237 | BooleanSize old = BooleanSize; 238 | BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); 239 | bool[] value = new bool[arrayLength]; 240 | ReadBooleans(value); 241 | BooleanSize = old; 242 | return value; 243 | } 244 | case Type t when t.IsEnum: 245 | { 246 | var value = Array.CreateInstance(elementType, arrayLength); 247 | for (int i = 0; i < arrayLength; i++) 248 | { 249 | value.SetValue(ReadEnum(elementType), i); 250 | } 251 | return value; 252 | } 253 | case Type t when t == typeof(DateTime): 254 | { 255 | var value = new DateTime[arrayLength]; 256 | ReadDateTimes(value); 257 | return value; 258 | } 259 | case Type t when t == typeof(DateOnly): 260 | { 261 | var value = new DateOnly[arrayLength]; 262 | ReadDateOnlys(value); 263 | return value; 264 | } 265 | case Type t when t == typeof(TimeOnly): 266 | { 267 | var value = new TimeOnly[arrayLength]; 268 | ReadTimeOnlys(value); 269 | return value; 270 | } 271 | case Type t when t == typeof(Vector2): 272 | { 273 | var value = new Vector2[arrayLength]; 274 | ReadVector2s(value); 275 | return value; 276 | } 277 | case Type t when t == typeof(Vector3): 278 | { 279 | var value = new Vector3[arrayLength]; 280 | ReadVector3s(value); 281 | return value; 282 | } 283 | case Type t when t == typeof(Vector4): 284 | { 285 | var value = new Vector4[arrayLength]; 286 | ReadVector4s(value); 287 | return value; 288 | } 289 | case Type t when t == typeof(Quaternion): 290 | { 291 | var value = new Quaternion[arrayLength]; 292 | ReadQuaternions(value); 293 | return value; 294 | } 295 | case Type t when t == typeof(Matrix4x4): 296 | { 297 | var value = new Matrix4x4[arrayLength]; 298 | ReadMatrix4x4s(value); 299 | return value; 300 | } 301 | case Type t when t == typeof(char): 302 | { 303 | bool old = ASCII; 304 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 305 | bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); 306 | char[] value; 307 | if (trimNullTerminators) 308 | { 309 | value = ReadChars_TrimNullTerminators(arrayLength); 310 | } 311 | else 312 | { 313 | value = new char[arrayLength]; 314 | ReadChars(value); 315 | } 316 | ASCII = old; 317 | return value; 318 | } 319 | case Type t when t == typeof(string): 320 | { 321 | Utils.GetStringLength(obj, objType, propertyInfo, true, out bool? nullTerminated, out int stringLength); 322 | 323 | bool old = ASCII; 324 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 325 | string[] value = new string[arrayLength]; 326 | if (nullTerminated == true) 327 | { 328 | ReadStrings_NullTerminated(value); 329 | } 330 | else 331 | { 332 | bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); 333 | if (trimNullTerminators) 334 | { 335 | ReadStrings_Count_TrimNullTerminators(value, stringLength); 336 | } 337 | else 338 | { 339 | ReadStrings_Count(value, stringLength); 340 | } 341 | } 342 | ASCII = old; 343 | return value; 344 | } 345 | default: 346 | { 347 | var value = Array.CreateInstance(elementType, arrayLength); 348 | for (int i = 0; i < arrayLength; i++) 349 | { 350 | value.SetValue(ReadObject(elementType), i); 351 | } 352 | return value; 353 | } 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /Source/EndianBinaryWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.IO; 4 | using System.Numerics; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Kermalis.EndianBinaryIO; 8 | 9 | public partial class EndianBinaryWriter 10 | { 11 | protected const int BUF_LEN = 64; // Must be a multiple of 64 12 | 13 | private Stream _stream; 14 | public Stream Stream 15 | { 16 | get => _stream; 17 | [MemberNotNull(nameof(_stream))] 18 | set 19 | { 20 | if (!value.CanWrite) 21 | { 22 | throw new ArgumentOutOfRangeException(nameof(value), "Stream is not open for writing."); 23 | } 24 | _stream = value; 25 | } 26 | } 27 | private Endianness _endianness; 28 | public Endianness Endianness 29 | { 30 | get => _endianness; 31 | set 32 | { 33 | Utils.ThrowIfInvalidEndianness(value); 34 | _endianness = value; 35 | } 36 | } 37 | private BooleanSize _booleanSize; 38 | public BooleanSize BooleanSize 39 | { 40 | get => _booleanSize; 41 | set 42 | { 43 | if (value >= BooleanSize.MAX) 44 | { 45 | throw new ArgumentOutOfRangeException(nameof(value), value, null); 46 | } 47 | _booleanSize = value; 48 | } 49 | } 50 | public bool ASCII { get; set; } 51 | 52 | protected readonly byte[] _buffer; 53 | 54 | public EndianBinaryWriter(Stream stream, Endianness endianness = Endianness.LittleEndian, BooleanSize booleanSize = BooleanSize.U8, bool ascii = false) 55 | { 56 | Stream = stream; 57 | Endianness = endianness; 58 | BooleanSize = booleanSize; 59 | ASCII = ascii; 60 | _buffer = new byte[BUF_LEN]; 61 | } 62 | 63 | protected delegate void WriteArrayMethod(Span dest, ReadOnlySpan src, Endianness endianness); 64 | protected void WriteArray(ReadOnlySpan src, int elementSize, WriteArrayMethod writeArray) 65 | { 66 | int numBytes = src.Length * elementSize; 67 | int start = 0; 68 | while (numBytes != 0) 69 | { 70 | int consumeBytes = Math.Min(numBytes, BUF_LEN); 71 | 72 | Span buffer = _buffer.AsSpan(0, consumeBytes); 73 | writeArray(buffer, src.Slice(start, consumeBytes / elementSize), Endianness); 74 | _stream.Write(buffer); 75 | 76 | numBytes -= consumeBytes; 77 | start += consumeBytes / elementSize; 78 | } 79 | } 80 | private void WriteBoolArray(ReadOnlySpan src, int elementSize) 81 | { 82 | int numBytes = src.Length * elementSize; 83 | int start = 0; 84 | while (numBytes != 0) 85 | { 86 | int consumeBytes = Math.Min(numBytes, BUF_LEN); 87 | 88 | Span buffer = _buffer.AsSpan(0, consumeBytes); 89 | buffer.WriteBooleans_Unsafe(src.Slice(start, consumeBytes / elementSize), Endianness, elementSize); 90 | _stream.Write(buffer); 91 | 92 | numBytes -= consumeBytes; 93 | start += consumeBytes / elementSize; 94 | } 95 | } 96 | private void WriteASCIIArray(ReadOnlySpan src) 97 | { 98 | int numBytes = src.Length; 99 | int start = 0; 100 | while (numBytes != 0) 101 | { 102 | int consumeBytes = Math.Min(numBytes, BUF_LEN); 103 | 104 | Span buffer = _buffer.AsSpan(0, consumeBytes); 105 | for (int i = 0; i < consumeBytes; i++) 106 | { 107 | buffer[i] = (byte)src[i + start]; 108 | } 109 | _stream.Write(buffer); 110 | 111 | numBytes -= consumeBytes; 112 | start += consumeBytes; 113 | } 114 | } 115 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 116 | public void WriteZeroes(int numBytes) 117 | { 118 | bool cleared = false; 119 | while (numBytes != 0) 120 | { 121 | int consumeBytes = Math.Min(numBytes, BUF_LEN); 122 | 123 | Span buffer = _buffer.AsSpan(0, consumeBytes); 124 | if (!cleared) 125 | { 126 | buffer.Clear(); 127 | cleared = true; 128 | } 129 | _stream.Write(buffer); 130 | 131 | numBytes -= consumeBytes; 132 | } 133 | } 134 | 135 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 136 | public void WriteSByte(sbyte value) 137 | { 138 | Span buffer = _buffer.AsSpan(0, 1); 139 | buffer[0] = (byte)value; 140 | _stream.Write(buffer); 141 | } 142 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 143 | public void WriteSBytes(ReadOnlySpan values) 144 | { 145 | ReadOnlySpan buffer = values.ReadCast(values.Length); 146 | _stream.Write(buffer); 147 | } 148 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 149 | public void WriteByte(byte value) 150 | { 151 | Span buffer = _buffer.AsSpan(0, 1); 152 | buffer[0] = value; 153 | _stream.Write(buffer); 154 | } 155 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 156 | public void WriteBytes(ReadOnlySpan values) 157 | { 158 | _stream.Write(values); 159 | } 160 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 161 | public void WriteInt16(short value) 162 | { 163 | Span buffer = _buffer.AsSpan(0, 2); 164 | buffer.WriteInt16_Unsafe(value, Endianness); 165 | _stream.Write(buffer); 166 | } 167 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 168 | public void WriteInt16s(ReadOnlySpan values) 169 | { 170 | WriteArray(values, 2, EndianBinaryPrimitives.WriteInt16s_Unsafe); 171 | } 172 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 173 | public void WriteUInt16(ushort value) 174 | { 175 | Span buffer = _buffer.AsSpan(0, 2); 176 | buffer.WriteUInt16_Unsafe(value, Endianness); 177 | _stream.Write(buffer); 178 | } 179 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 180 | public void WriteUInt16s(ReadOnlySpan values) 181 | { 182 | WriteArray(values, 2, EndianBinaryPrimitives.WriteUInt16s_Unsafe); 183 | } 184 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 185 | public void WriteInt32(int value) 186 | { 187 | Span buffer = _buffer.AsSpan(0, 4); 188 | buffer.WriteInt32_Unsafe(value, Endianness); 189 | _stream.Write(buffer); 190 | } 191 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 192 | public void WriteInt32s(ReadOnlySpan values) 193 | { 194 | WriteArray(values, 4, EndianBinaryPrimitives.WriteInt32s_Unsafe); 195 | } 196 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 197 | public void WriteUInt32(uint value) 198 | { 199 | Span buffer = _buffer.AsSpan(0, 4); 200 | buffer.WriteUInt32_Unsafe(value, Endianness); 201 | _stream.Write(buffer); 202 | } 203 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 204 | public void WriteUInt32s(ReadOnlySpan values) 205 | { 206 | WriteArray(values, 4, EndianBinaryPrimitives.WriteUInt32s_Unsafe); 207 | } 208 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 209 | public void WriteInt64(long value) 210 | { 211 | Span buffer = _buffer.AsSpan(0, 8); 212 | buffer.WriteInt64_Unsafe(value, Endianness); 213 | _stream.Write(buffer); 214 | } 215 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 216 | public void WriteInt64s(ReadOnlySpan values) 217 | { 218 | WriteArray(values, 8, EndianBinaryPrimitives.WriteInt64s_Unsafe); 219 | } 220 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 221 | public void WriteUInt64(ulong value) 222 | { 223 | Span buffer = _buffer.AsSpan(0, 8); 224 | buffer.WriteUInt64_Unsafe(value, Endianness); 225 | _stream.Write(buffer); 226 | } 227 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 228 | public void WriteUInt64s(ReadOnlySpan values) 229 | { 230 | WriteArray(values, 8, EndianBinaryPrimitives.WriteUInt64s_Unsafe); 231 | } 232 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 233 | public void WriteInt128(in Int128 value) 234 | { 235 | Span buffer = _buffer.AsSpan(0, 16); 236 | buffer.WriteInt128_Unsafe(value, Endianness); 237 | _stream.Write(buffer); 238 | } 239 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 240 | public void WriteInt128s(ReadOnlySpan values) 241 | { 242 | WriteArray(values, 16, EndianBinaryPrimitives.WriteInt128s_Unsafe); 243 | } 244 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 245 | public void WriteUInt128(in UInt128 value) 246 | { 247 | Span buffer = _buffer.AsSpan(0, 16); 248 | buffer.WriteUInt128_Unsafe(value, Endianness); 249 | _stream.Write(buffer); 250 | } 251 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 252 | public void WriteUInt128s(ReadOnlySpan values) 253 | { 254 | WriteArray(values, 16, EndianBinaryPrimitives.WriteUInt128s_Unsafe); 255 | } 256 | 257 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 258 | public void WriteHalf(Half value) 259 | { 260 | Span buffer = _buffer.AsSpan(0, 2); 261 | buffer.WriteHalf_Unsafe(value, Endianness); 262 | _stream.Write(buffer); 263 | } 264 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 265 | public void WriteHalves(ReadOnlySpan values) 266 | { 267 | WriteArray(values, 2, EndianBinaryPrimitives.WriteHalves_Unsafe); 268 | } 269 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 270 | public void WriteSingle(float value) 271 | { 272 | Span buffer = _buffer.AsSpan(0, 4); 273 | buffer.WriteSingle_Unsafe(value, Endianness); 274 | _stream.Write(buffer); 275 | } 276 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 277 | public void WriteSingles(ReadOnlySpan values) 278 | { 279 | WriteArray(values, 4, EndianBinaryPrimitives.WriteSingles_Unsafe); 280 | } 281 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 282 | public void WriteDouble(double value) 283 | { 284 | Span buffer = _buffer.AsSpan(0, 8); 285 | buffer.WriteDouble_Unsafe(value, Endianness); 286 | _stream.Write(buffer); 287 | } 288 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 289 | public void WriteDoubles(ReadOnlySpan values) 290 | { 291 | WriteArray(values, 8, EndianBinaryPrimitives.WriteDoubles_Unsafe); 292 | } 293 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 294 | public void WriteDecimal(in decimal value) 295 | { 296 | Span buffer = _buffer.AsSpan(0, 16); 297 | buffer.WriteDecimal_Unsafe(value, Endianness); 298 | _stream.Write(buffer); 299 | } 300 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 301 | public void WriteDecimals(ReadOnlySpan values) 302 | { 303 | WriteArray(values, 16, EndianBinaryPrimitives.WriteDecimals_Unsafe); 304 | } 305 | 306 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 307 | public void WriteBoolean8(bool value) 308 | { 309 | WriteByte((byte)(value ? 1 : 0)); 310 | } 311 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 312 | public void WriteBoolean8s(ReadOnlySpan values) 313 | { 314 | WriteBoolArray(values, sizeof(byte)); 315 | } 316 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 317 | public void WriteBoolean16(bool value) 318 | { 319 | WriteUInt16((ushort)(value ? 1 : 0)); 320 | } 321 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 322 | public void WriteBoolean16s(ReadOnlySpan values) 323 | { 324 | WriteBoolArray(values, sizeof(ushort)); 325 | } 326 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 327 | public void WriteBoolean32(bool value) 328 | { 329 | WriteUInt32(value ? 1u : 0); 330 | } 331 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 332 | public void WriteBoolean32s(ReadOnlySpan values) 333 | { 334 | WriteBoolArray(values, sizeof(uint)); 335 | } 336 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 337 | public void WriteBoolean(bool value) 338 | { 339 | switch (BooleanSize) 340 | { 341 | case BooleanSize.U8: WriteByte((byte)(value ? 1 : 0)); break; 342 | case BooleanSize.U16: WriteUInt16((ushort)(value ? 1 : 0)); break; 343 | case BooleanSize.U32: WriteUInt32(value ? 1u : 0); break; 344 | default: throw new InvalidOperationException(); 345 | } 346 | } 347 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 348 | public void WriteBooleans(ReadOnlySpan values) 349 | { 350 | WriteBoolArray(values, EndianBinaryPrimitives.GetBytesForBooleanSize(BooleanSize)); 351 | } 352 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 353 | public void WriteEnum(TEnum value) 354 | where TEnum : unmanaged, Enum 355 | { 356 | int size = Unsafe.SizeOf(); 357 | if (size == 1) 358 | { 359 | WriteByte(Unsafe.As(ref value)); 360 | } 361 | else if (size == 2) 362 | { 363 | WriteUInt16(Unsafe.As(ref value)); 364 | } 365 | else if (size == 4) 366 | { 367 | WriteUInt32(Unsafe.As(ref value)); 368 | } 369 | else 370 | { 371 | WriteUInt64(Unsafe.As(ref value)); 372 | } 373 | } 374 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 375 | public void WriteEnums(ReadOnlySpan values) 376 | where TEnum : unmanaged, Enum 377 | { 378 | int size = Unsafe.SizeOf(); 379 | if (size == 1) 380 | { 381 | WriteBytes(values.ReadCast(values.Length)); 382 | } 383 | else if (size == 2) 384 | { 385 | WriteUInt16s(values.ReadCast(values.Length * 2)); 386 | } 387 | else if (size == 4) 388 | { 389 | WriteUInt32s(values.ReadCast(values.Length * 4)); 390 | } 391 | else 392 | { 393 | WriteUInt64s(values.ReadCast(values.Length * 8)); 394 | } 395 | } 396 | // #13 - Allow writing the abstract "Enum" type 397 | // For example, writer.WriteEnum((Enum)Enum.Parse(enumType, value)) 398 | // Don't allow writing Enum[] though, since there is no way to read that 399 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 400 | public void WriteEnum(Enum value) 401 | { 402 | Type underlyingType = Enum.GetUnderlyingType(value.GetType()); 403 | ref byte data = ref Utils.GetRawData(value); // Use memory tricks to skip object header of generic Enum 404 | switch (Type.GetTypeCode(underlyingType)) 405 | { 406 | case TypeCode.SByte: 407 | case TypeCode.Byte: WriteByte(data); break; 408 | case TypeCode.Int16: 409 | case TypeCode.UInt16: WriteUInt16(Unsafe.As(ref data)); break; 410 | case TypeCode.Int32: 411 | case TypeCode.UInt32: WriteUInt32(Unsafe.As(ref data)); break; 412 | case TypeCode.Int64: 413 | case TypeCode.UInt64: WriteUInt64(Unsafe.As(ref data)); break; 414 | } 415 | } 416 | 417 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 418 | public void WriteDateTime(DateTime value) 419 | { 420 | Span buffer = _buffer.AsSpan(0, 8); 421 | buffer.WriteDateTime_Unsafe(value, Endianness); 422 | _stream.Write(buffer); 423 | } 424 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 425 | public void WriteDateTimes(ReadOnlySpan values) 426 | { 427 | WriteArray(values, 8, EndianBinaryPrimitives.WriteDateTimes_Unsafe); 428 | } 429 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 430 | public void WriteDateOnly(DateOnly value) 431 | { 432 | Span buffer = _buffer.AsSpan(0, 4); 433 | buffer.WriteDateOnly_Unsafe(value, Endianness); 434 | _stream.Write(buffer); 435 | } 436 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 437 | public void WriteDateOnlys(ReadOnlySpan values) 438 | { 439 | WriteArray(values, 4, EndianBinaryPrimitives.WriteDateOnlys_Unsafe); 440 | } 441 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 442 | public void WriteTimeOnly(TimeOnly value) 443 | { 444 | Span buffer = _buffer.AsSpan(0, 8); 445 | buffer.WriteTimeOnly_Unsafe(value, Endianness); 446 | _stream.Write(buffer); 447 | } 448 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 449 | public void WriteTimeOnlys(ReadOnlySpan values) 450 | { 451 | WriteArray(values, 8, EndianBinaryPrimitives.WriteTimeOnlys_Unsafe); 452 | } 453 | 454 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 455 | public void WriteVector2(Vector2 value) 456 | { 457 | Span buffer = _buffer.AsSpan(0, 8); 458 | buffer.WriteVector2_Unsafe(value, Endianness); 459 | _stream.Write(buffer); 460 | } 461 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 462 | public void WriteVector2s(ReadOnlySpan values) 463 | { 464 | WriteArray(values, 8, EndianBinaryPrimitives.WriteVector2s_Unsafe); 465 | } 466 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 467 | public void WriteVector3(Vector3 value) 468 | { 469 | Span buffer = _buffer.AsSpan(0, 12); 470 | buffer.WriteVector3_Unsafe(value, Endianness); 471 | _stream.Write(buffer); 472 | } 473 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 474 | public void WriteVector3s(ReadOnlySpan values) 475 | { 476 | WriteArray(values, 12, EndianBinaryPrimitives.WriteVector3s_Unsafe); 477 | } 478 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 479 | public void WriteVector4(in Vector4 value) 480 | { 481 | Span buffer = _buffer.AsSpan(0, 16); 482 | buffer.WriteVector4_Unsafe(value, Endianness); 483 | _stream.Write(buffer); 484 | } 485 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 486 | public void WriteVector4s(ReadOnlySpan values) 487 | { 488 | WriteArray(values, 16, EndianBinaryPrimitives.WriteVector4s_Unsafe); 489 | } 490 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 491 | public void WriteQuaternion(in Quaternion value) 492 | { 493 | Span buffer = _buffer.AsSpan(0, 16); 494 | buffer.WriteQuaternion_Unsafe(value, Endianness); 495 | _stream.Write(buffer); 496 | } 497 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 498 | public void WriteQuaternions(ReadOnlySpan values) 499 | { 500 | WriteArray(values, 16, EndianBinaryPrimitives.WriteQuaternions_Unsafe); 501 | } 502 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 503 | public void WriteMatrix4x4(in Matrix4x4 value) 504 | { 505 | Span buffer = _buffer.AsSpan(0, 64); 506 | buffer.WriteMatrix4x4_Unsafe(value, Endianness); 507 | _stream.Write(buffer); 508 | } 509 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 510 | public void WriteMatrix4x4s(ReadOnlySpan values) 511 | { 512 | WriteArray(values, 64, EndianBinaryPrimitives.WriteMatrix4x4s_Unsafe); 513 | } 514 | 515 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 516 | public void WriteChar(char v) 517 | { 518 | if (ASCII) 519 | { 520 | WriteByte((byte)v); 521 | } 522 | else 523 | { 524 | WriteUInt16(v); 525 | } 526 | } 527 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 528 | public void WriteChars(ReadOnlySpan values) 529 | { 530 | if (values.Length == 0) 531 | { 532 | return; 533 | } 534 | if (ASCII) 535 | { 536 | WriteASCIIArray(values); 537 | } 538 | else 539 | { 540 | ReadOnlySpan buffer = values.ReadCast(values.Length); 541 | WriteUInt16s(buffer); 542 | } 543 | } 544 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 545 | public void WriteChars_Count(ReadOnlySpan values, int charCount) 546 | { 547 | if (charCount == 0) 548 | { 549 | return; 550 | } 551 | if (values.Length >= charCount) 552 | { 553 | WriteChars(values.Slice(0, charCount)); 554 | } 555 | else // Less 556 | { 557 | WriteChars(values); 558 | 559 | // Append '\0' 560 | int emptyBytes = charCount - values.Length; 561 | if (!ASCII) 562 | { 563 | emptyBytes *= 2; 564 | } 565 | WriteZeroes(emptyBytes); 566 | } 567 | } 568 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 569 | public void WriteChars_NullTerminated(ReadOnlySpan values) 570 | { 571 | WriteChars(values); 572 | WriteChar('\0'); 573 | } 574 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 575 | public void WriteStrings(ReadOnlySpan values) 576 | { 577 | for (int i = 0; i < values.Length; i++) 578 | { 579 | WriteChars(values[i]); 580 | } 581 | } 582 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 583 | public void WriteStrings_Count(ReadOnlySpan values, int charCount) 584 | { 585 | for (int i = 0; i < values.Length; i++) 586 | { 587 | WriteChars_Count(values[i], charCount); 588 | } 589 | } 590 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 591 | public void WriteStrings_NullTerminated(ReadOnlySpan values) 592 | { 593 | for (int i = 0; i < values.Length; i++) 594 | { 595 | WriteChars_NullTerminated(values[i]); 596 | } 597 | } 598 | } 599 | -------------------------------------------------------------------------------- /Source/EndianBinaryWriter_Reflection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using System.Reflection; 4 | 5 | namespace Kermalis.EndianBinaryIO; 6 | 7 | public partial class EndianBinaryWriter 8 | { 9 | public void WriteObject(object obj) 10 | { 11 | WriteObject(obj, false); 12 | } 13 | private void WriteObject(object obj, bool skipCheck) 14 | { 15 | if (!skipCheck && TryWriteSupportedObject(obj)) 16 | { 17 | return; 18 | } 19 | 20 | Type objType = obj.GetType(); 21 | Utils.ThrowIfCannotReadWriteType(objType); 22 | 23 | // Get public non-static properties 24 | foreach (PropertyInfo propertyInfo in objType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) 25 | { 26 | if (Utils.AttributeValueOrDefault(propertyInfo, false)) 27 | { 28 | continue; // Skip properties with BinaryIgnoreAttribute 29 | } 30 | 31 | object? value = propertyInfo.GetValue(obj); 32 | if (value is null) 33 | { 34 | throw new NullReferenceException($"Null property in {objType.FullName} ({propertyInfo.Name})"); 35 | } 36 | if (value is Array arr) 37 | { 38 | int arrayLength = Utils.GetArrayLength(obj, objType, propertyInfo); 39 | if (arrayLength != 0) // Do not need to do anything for length 0 40 | { 41 | if (arrayLength > arr.Length) 42 | { 43 | throw new InvalidOperationException($"Expected too many elements for array in {objType.FullName} (expected {arrayLength}, have {arr.Length})."); 44 | } 45 | WritePropertyValue_Array(obj, objType, propertyInfo, arr, arrayLength); 46 | } 47 | } 48 | else 49 | { 50 | WritePropertyValue_NonArray(obj, objType, propertyInfo, value); 51 | } 52 | } 53 | } 54 | 55 | private bool TryWriteSupportedObject(object obj) 56 | { 57 | return obj is Array arr ? TryWriteSupportedObject_Array(arr, arr.Length) : TryWriteSupportedObject_NonArray(obj); 58 | } 59 | private bool TryWriteSupportedObject_NonArray(object obj) 60 | { 61 | switch (obj) 62 | { 63 | case sbyte v: WriteSByte(v); return true; 64 | case byte v: WriteByte(v); return true; 65 | case short v: WriteInt16(v); return true; 66 | case ushort v: WriteUInt16(v); return true; 67 | case int v: WriteInt32(v); return true; 68 | case uint v: WriteUInt32(v); return true; 69 | case long v: WriteInt64(v); return true; 70 | case ulong v: WriteUInt64(v); return true; 71 | case Int128 v: WriteInt128(v); return true; 72 | case UInt128 v: WriteUInt128(v); return true; 73 | case Half v: WriteHalf(v); return true; 74 | case float v: WriteSingle(v); return true; 75 | case double v: WriteDouble(v); return true; 76 | case decimal v: WriteDecimal(v); return true; 77 | case bool v: WriteBoolean(v); return true; 78 | case Enum v: WriteEnum(v); return true; 79 | case DateTime v: WriteDateTime(v); return true; 80 | case DateOnly v: WriteDateOnly(v); return true; 81 | case TimeOnly v: WriteTimeOnly(v); return true; 82 | case Vector2 v: WriteVector2(v); return true; 83 | case Vector3 v: WriteVector3(v); return true; 84 | case Vector4 v: WriteVector4(v); return true; 85 | case Quaternion v: WriteQuaternion(v); return true; 86 | case Matrix4x4 v: WriteMatrix4x4(v); return true; 87 | case char v: WriteChar(v); return true; 88 | case string v: WriteChars_NullTerminated(v); return true; 89 | case IBinarySerializable v: v.Write(this); return true; 90 | } 91 | return false; 92 | } 93 | private bool TryWriteSupportedObject_Array(Array obj, int length) 94 | { 95 | Type elementType = obj.GetType().GetElementType()!; 96 | if (elementType.IsEnum) 97 | { 98 | for (int i = 0; i < length; i++) 99 | { 100 | WriteObject(obj.GetValue(i)!); 101 | } 102 | return true; 103 | } 104 | 105 | switch (obj) 106 | { 107 | case sbyte[] v: WriteSBytes(v.AsSpan(0, length)); return true; 108 | case byte[] v: WriteBytes(v.AsSpan(0, length)); return true; 109 | case short[] v: WriteInt16s(v.AsSpan(0, length)); return true; 110 | case ushort[] v: WriteUInt16s(v.AsSpan(0, length)); return true; 111 | case int[] v: WriteInt32s(v.AsSpan(0, length)); return true; 112 | case uint[] v: WriteUInt32s(v.AsSpan(0, length)); return true; 113 | case long[] v: WriteInt64s(v.AsSpan(0, length)); return true; 114 | case ulong[] v: WriteUInt64s(v.AsSpan(0, length)); return true; 115 | case Int128[] v: WriteInt128s(v.AsSpan(0, length)); return true; 116 | case UInt128[] v: WriteUInt128s(v.AsSpan(0, length)); return true; 117 | case Half[] v: WriteHalves(v.AsSpan(0, length)); return true; 118 | case float[] v: WriteSingles(v.AsSpan(0, length)); return true; 119 | case double[] v: WriteDoubles(v.AsSpan(0, length)); return true; 120 | case decimal[] v: WriteDecimals(v.AsSpan(0, length)); return true; 121 | case bool[] v: WriteBooleans(v.AsSpan(0, length)); return true; 122 | case DateTime[] v: WriteDateTimes(v.AsSpan(0, length)); return true; 123 | case DateOnly[] v: WriteDateOnlys(v.AsSpan(0, length)); return true; 124 | case TimeOnly[] v: WriteTimeOnlys(v.AsSpan(0, length)); return true; 125 | case Vector2[] v: WriteVector2s(v.AsSpan(0, length)); return true; 126 | case Vector3[] v: WriteVector3s(v.AsSpan(0, length)); return true; 127 | case Vector4[] v: WriteVector4s(v.AsSpan(0, length)); return true; 128 | case Quaternion[] v: WriteQuaternions(v.AsSpan(0, length)); return true; 129 | case Matrix4x4[] v: WriteMatrix4x4s(v.AsSpan(0, length)); return true; 130 | case char[] v: WriteChars(v.AsSpan(0, length)); return true; 131 | case string[] v: WriteStrings_NullTerminated(v.AsSpan(0, length)); return true; 132 | } 133 | return false; 134 | } 135 | 136 | private void WritePropertyValue_NonArray(object obj, Type objType, PropertyInfo propertyInfo, object value) 137 | { 138 | switch (value) 139 | { 140 | case bool v: 141 | { 142 | BooleanSize old = BooleanSize; 143 | BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); 144 | WriteBoolean(v); 145 | BooleanSize = old; 146 | break; 147 | } 148 | case char v: 149 | { 150 | bool old = ASCII; 151 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 152 | WriteChar(v); 153 | ASCII = old; 154 | break; 155 | } 156 | case string v: 157 | { 158 | Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); 159 | 160 | bool old = ASCII; 161 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 162 | if (nullTerminated == true) 163 | { 164 | WriteChars_NullTerminated(v); 165 | } 166 | else if (nullTerminated == false) 167 | { 168 | WriteChars(v); 169 | } 170 | else 171 | { 172 | WriteChars_Count(v, stringLength); 173 | } 174 | ASCII = old; 175 | break; 176 | } 177 | default: 178 | { 179 | WriteObject(value); 180 | break; 181 | } 182 | } 183 | } 184 | private void WritePropertyValue_Array(object obj, Type objType, PropertyInfo propertyInfo, Array value, int arrayLength) 185 | { 186 | switch (value) 187 | { 188 | case bool[] v: 189 | { 190 | BooleanSize old = BooleanSize; 191 | BooleanSize = Utils.AttributeValueOrDefault(propertyInfo, old); 192 | WriteBooleans(v.AsSpan(0, arrayLength)); 193 | BooleanSize = old; 194 | break; 195 | } 196 | case char[] v: 197 | { 198 | bool old = ASCII; 199 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 200 | WriteChars(v.AsSpan(0, arrayLength)); 201 | ASCII = old; 202 | break; 203 | } 204 | case string[] v: 205 | { 206 | Utils.GetStringLength(obj, objType, propertyInfo, false, out bool? nullTerminated, out int stringLength); 207 | 208 | bool old = ASCII; 209 | ASCII = Utils.AttributeValueOrDefault(propertyInfo, old); 210 | if (nullTerminated == true) 211 | { 212 | WriteStrings_NullTerminated(v.AsSpan(0, arrayLength)); 213 | } 214 | else if (nullTerminated == false) 215 | { 216 | WriteStrings(v.AsSpan(0, arrayLength)); 217 | } 218 | else 219 | { 220 | WriteStrings_Count(v.AsSpan(0, arrayLength), stringLength); 221 | } 222 | ASCII = old; 223 | break; 224 | } 225 | default: 226 | { 227 | if (!TryWriteSupportedObject_Array(value, arrayLength)) 228 | { 229 | for (int i = 0; i < arrayLength; i++) 230 | { 231 | object? val = value.GetValue(i); 232 | if (val is null) 233 | { 234 | throw new NullReferenceException("Array element was null."); 235 | } 236 | WriteObject(val, true); 237 | } 238 | } 239 | break; 240 | } 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Source/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.EndianBinaryIO; 2 | 3 | public enum BooleanSize : byte 4 | { 5 | U8, 6 | U16, 7 | U32, 8 | MAX 9 | } 10 | 11 | public enum Endianness : byte 12 | { 13 | LittleEndian, 14 | BigEndian, 15 | MAX 16 | } -------------------------------------------------------------------------------- /Source/IBinarySerializable.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.EndianBinaryIO; 2 | 3 | public interface IBinarySerializable 4 | { 5 | void Read(EndianBinaryReader r); 6 | void Write(EndianBinaryWriter w); 7 | } 8 | -------------------------------------------------------------------------------- /Source/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Kermalis.EndianBinaryIO; 7 | 8 | internal static class Utils 9 | { 10 | private sealed class RawData 11 | { 12 | public byte Data; 13 | } 14 | // This is a copy of what Enum uses internally 15 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 16 | public static ref byte GetRawData(T value) where T : class 17 | { 18 | return ref Unsafe.As(value).Data; // Skip object header 19 | } 20 | 21 | private static bool TryConvertToInt32(object? obj, out int value) 22 | { 23 | try 24 | { 25 | value = Convert.ToInt32(obj); 26 | return true; 27 | } 28 | catch 29 | { 30 | value = -1; 31 | return false; 32 | } 33 | } 34 | 35 | public static bool TryGetAttribute(PropertyInfo propertyInfo, [NotNullWhen(true)] out TAttribute? attribute) 36 | where TAttribute : Attribute 37 | { 38 | object[] attributes = propertyInfo.GetCustomAttributes(typeof(TAttribute), true); 39 | if (attributes.Length == 1) 40 | { 41 | attribute = (TAttribute)attributes[0]; 42 | return true; 43 | } 44 | attribute = default; 45 | return false; 46 | } 47 | public static TValue GetAttributeValue(TAttribute attribute) 48 | where TAttribute : Attribute, IBinaryAttribute 49 | { 50 | return (TValue)typeof(TAttribute).GetProperty(nameof(IBinaryAttribute.Value))!.GetValue(attribute)!; 51 | } 52 | public static TValue AttributeValueOrDefault(PropertyInfo propertyInfo, TValue defaultValue) 53 | where TAttribute : Attribute, IBinaryAttribute 54 | { 55 | if (TryGetAttribute(propertyInfo, out TAttribute? attribute)) 56 | { 57 | return GetAttributeValue(attribute); 58 | } 59 | return defaultValue; 60 | } 61 | 62 | public static void ThrowIfCannotReadWriteType(Type type) 63 | { 64 | if (type.IsArray || type.IsEnum || type.IsInterface || type.IsPointer || type.IsPrimitive) 65 | { 66 | throw new ArgumentException($"Cannot read/write \"{type.FullName}\" objects.", nameof(type)); 67 | } 68 | } 69 | public static void ThrowIfDestTooSmall(int dst, int req) 70 | { 71 | if (dst < req) 72 | { 73 | throw new ArgumentException("Destination length was less than " + req); 74 | } 75 | } 76 | public static void ThrowIfSrcTooSmall(int src, int req) 77 | { 78 | if (src < req) 79 | { 80 | throw new ArgumentException("Source length was less than " + req); 81 | } 82 | } 83 | public static void ThrowIfInvalidEndianness(Endianness endianness) 84 | { 85 | if (endianness >= Endianness.MAX) 86 | { 87 | throw new ArgumentOutOfRangeException(nameof(endianness), endianness, null); 88 | } 89 | } 90 | 91 | public static int GetArrayLength(object obj, Type objType, PropertyInfo propertyInfo) 92 | { 93 | if (TryGetAttribute(propertyInfo, out BinaryArrayFixedLengthAttribute? fixedLenAttribute)) 94 | { 95 | if (propertyInfo.IsDefined(typeof(BinaryArrayVariableLengthAttribute))) 96 | { 97 | throw new ArgumentException($"An array property in \"{objType.FullName}\" has two array length attributes. Only one should be provided."); 98 | } 99 | return GetAttributeValue(fixedLenAttribute); 100 | } 101 | 102 | if (TryGetAttribute(propertyInfo, out BinaryArrayVariableLengthAttribute? varLenAttribute)) 103 | { 104 | string anchorName = GetAttributeValue(varLenAttribute); 105 | PropertyInfo? anchor = objType.GetProperty(anchorName, BindingFlags.Instance | BindingFlags.Public); 106 | if (anchor is null) 107 | { 108 | throw new MissingMemberException($"An array property in \"{objType.FullName}\" has an invalid {nameof(BinaryArrayVariableLengthAttribute)} ({anchorName})."); 109 | } 110 | 111 | object? anchorValue = anchor.GetValue(obj); 112 | if (!TryConvertToInt32(anchorValue, out int length) || length < 0) 113 | { 114 | throw new InvalidOperationException($"An array property in \"{objType.FullName}\" has an invalid length attribute ({anchorName} = {anchorValue})."); 115 | } 116 | return length; 117 | } 118 | 119 | throw new MissingMemberException($"An array property in \"{objType.FullName}\" is missing an array length attribute. One should be provided."); 120 | } 121 | public static void GetStringLength(object obj, Type objType, PropertyInfo propertyInfo, bool forReads, out bool? nullTerminated, out int stringLength) 122 | { 123 | if (TryGetAttribute(propertyInfo, out BinaryStringNullTerminatedAttribute? nullTermAttribute)) 124 | { 125 | if (propertyInfo.IsDefined(typeof(BinaryStringFixedLengthAttribute)) || propertyInfo.IsDefined(typeof(BinaryStringVariableLengthAttribute))) 126 | { 127 | throw new ArgumentException($"A string property in \"{objType.FullName}\" has a string length attribute and a {nameof(BinaryStringNullTerminatedAttribute)}; cannot use both."); 128 | } 129 | if (propertyInfo.IsDefined(typeof(BinaryStringTrimNullTerminatorsAttribute))) 130 | { 131 | throw new ArgumentException($"A string property in \"{objType.FullName}\" has a {nameof(BinaryStringNullTerminatedAttribute)} and a {nameof(BinaryStringTrimNullTerminatorsAttribute)}; cannot use both."); 132 | } 133 | 134 | bool nt = GetAttributeValue(nullTermAttribute); 135 | if (forReads && !nt) // Not forcing BinaryStringNullTerminatedAttribute to be treated as true since you may only write objects without reading them. 136 | { 137 | throw new ArgumentException($"A string property in \"{objType.FullName}\" has a {nameof(BinaryStringNullTerminatedAttribute)} that's set to false." + 138 | $" Must use null termination or provide a string length when reading."); 139 | } 140 | 141 | nullTerminated = nt; 142 | stringLength = -1; 143 | return; 144 | } 145 | 146 | if (TryGetAttribute(propertyInfo, out BinaryStringFixedLengthAttribute? fixedLenAttribute)) 147 | { 148 | if (propertyInfo.IsDefined(typeof(BinaryStringVariableLengthAttribute))) 149 | { 150 | throw new ArgumentException($"A string property in \"{objType.FullName}\" has two string length attributes. Only one should be provided."); 151 | } 152 | 153 | nullTerminated = null; 154 | stringLength = GetAttributeValue(fixedLenAttribute); 155 | return; 156 | } 157 | 158 | if (TryGetAttribute(propertyInfo, out BinaryStringVariableLengthAttribute? varLenAttribute)) 159 | { 160 | string anchorName = GetAttributeValue(varLenAttribute); 161 | PropertyInfo? anchor = objType.GetProperty(anchorName, BindingFlags.Instance | BindingFlags.Public); 162 | if (anchor is null) 163 | { 164 | throw new MissingMemberException($"A string property in \"{objType.FullName}\" has an invalid {nameof(BinaryStringVariableLengthAttribute)} ({anchorName})."); 165 | } 166 | 167 | nullTerminated = null; 168 | object? anchorValue = anchor.GetValue(obj); 169 | if (!TryConvertToInt32(anchorValue, out stringLength) || stringLength < 0) 170 | { 171 | throw new InvalidOperationException($"A string property in \"{objType.FullName}\" has an invalid length attribute ({anchorName} = {stringLength})."); 172 | } 173 | return; 174 | } 175 | 176 | throw new MissingMemberException($"A string property in \"{objType.FullName}\" is missing a string length attribute and has no {nameof(BinaryStringNullTerminatedAttribute)}. One should be provided."); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Testing/BasicTests.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace Kermalis.EndianBinaryIOTests; 9 | 10 | public sealed class BasicTests 11 | { 12 | private sealed class MyBasicObj 13 | { 14 | // Properties 15 | public ShortSizedEnum Type { get; set; } 16 | public short Version { get; set; } 17 | public DateTime Date { get; set; } 18 | public Int128 Int128 { get; set; } 19 | 20 | // Property that is ignored when reading and writing 21 | [BinaryIgnore] 22 | public ByteSizedEnum DoNotReadOrWrite { get; set; } 23 | 24 | // Arrays work as well 25 | [BinaryArrayFixedLength(16)] 26 | public uint[] ArrayWith16Elements { get; set; } 27 | 28 | // Boolean that occupies 4 bytes instead of one 29 | [BinaryBooleanSize(BooleanSize.U32)] 30 | public bool Bool32 { get; set; } 31 | 32 | // String encoded in ASCII 33 | // Reads chars until the stream encounters a '\0' 34 | // Writing will append a '\0' at the end of the string 35 | [BinaryASCII] 36 | [BinaryStringNullTerminated] 37 | public string NullTerminatedASCIIString { get; set; } 38 | 39 | // String encoded in UTF16-LE that will only read/write 10 chars 40 | [BinaryStringFixedLength(10)] 41 | [BinaryStringTrimNullTerminators] 42 | public string UTF16String { get; set; } 43 | } 44 | 45 | #region Constants 46 | 47 | private static readonly DateTime _expectedDateTime = new(1998, 12, 30); 48 | private static readonly Int128 _expectedInt128 = Int128.Parse("48,045,707,429,126,174,655,160,174,263,614,327,112", NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo); 49 | private static readonly byte[] _bytes = new byte[] 50 | { 51 | 0x00, 0x08, // ShortSizedEnum.Val2 52 | 0xFF, 0x01, // (short)511 53 | 0x00, 0x00, 0x4A, 0x7A, 0x9E, 0x01, 0xC0, 0x08, // (DateTime)Dec. 30, 1998 54 | 0x48, 0x49, 0x80, 0x44, 0x82, 0x44, 0x88, 0xC0, 0x42, 0x24, 0x88, 0x12, 0x44, 0x44, 0x25, 0x24, // (Int128)48,045,707,429,126,174,655,160,174,263,614,327,112 55 | 56 | 0x00, 0x00, 0x00, 0x00, // (uint)0 57 | 0x01, 0x00, 0x00, 0x00, // (uint)1 58 | 0x02, 0x00, 0x00, 0x00, // (uint)2 59 | 0x03, 0x00, 0x00, 0x00, // (uint)3 60 | 0x04, 0x00, 0x00, 0x00, // (uint)4 61 | 0x05, 0x00, 0x00, 0x00, // (uint)5 62 | 0x06, 0x00, 0x00, 0x00, // (uint)6 63 | 0x07, 0x00, 0x00, 0x00, // (uint)7 64 | 0x08, 0x00, 0x00, 0x00, // (uint)8 65 | 0x09, 0x00, 0x00, 0x00, // (uint)9 66 | 0x0A, 0x00, 0x00, 0x00, // (uint)10 67 | 0x0B, 0x00, 0x00, 0x00, // (uint)11 68 | 0x0C, 0x00, 0x00, 0x00, // (uint)12 69 | 0x0D, 0x00, 0x00, 0x00, // (uint)13 70 | 0x0E, 0x00, 0x00, 0x00, // (uint)14 71 | 0x0F, 0x00, 0x00, 0x00, // (uint)15 72 | 73 | 0x00, 0x00, 0x00, 0x00, // (bool32)false 74 | 75 | 0x45, 0x6E, 0x64, 0x69, 0x61, 0x6E, 0x42, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x49, 0x4F, 0x00, // (ASCII)"EndianBinaryIO\0" 76 | 77 | 0x4B, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, // (UTF16-LE)"Kermalis\0\0" 78 | }; 79 | private static MyBasicObj GetObj() 80 | { 81 | return new MyBasicObj 82 | { 83 | Type = ShortSizedEnum.Val2, 84 | Version = 511, 85 | Date = _expectedDateTime, 86 | Int128 = _expectedInt128, 87 | 88 | DoNotReadOrWrite = ByteSizedEnum.Val1, 89 | 90 | ArrayWith16Elements = new uint[16] 91 | { 92 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 93 | }, 94 | 95 | Bool32 = false, 96 | 97 | NullTerminatedASCIIString = "EndianBinaryIO", 98 | UTF16String = "Kermalis", 99 | }; 100 | } 101 | 102 | #endregion 103 | 104 | [Fact] 105 | public void ReadObject() 106 | { 107 | MyBasicObj obj; 108 | using (var stream = new MemoryStream(_bytes)) 109 | { 110 | obj = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian).ReadObject(); 111 | } 112 | 113 | Assert.Equal(ShortSizedEnum.Val2, obj.Type); // Enum works 114 | Assert.Equal(511, obj.Version); // short works 115 | Assert.Equal(_expectedDateTime, obj.Date); // DateTime works 116 | Assert.Equal(_expectedInt128, obj.Int128); // Int128 works 117 | 118 | Assert.Equal(default, obj.DoNotReadOrWrite); // Ignored 119 | 120 | Assert.Equal(16, obj.ArrayWith16Elements.Length); // Fixed size array works 121 | for (uint i = 0; i < 16; i++) 122 | { 123 | Assert.Equal(i, obj.ArrayWith16Elements[i]); // Array works 124 | } 125 | 126 | Assert.False(obj.Bool32); // bool32 works 127 | 128 | Assert.Equal("EndianBinaryIO", obj.NullTerminatedASCIIString); // Stops reading at null terminator 129 | Assert.Equal("Kermalis", obj.UTF16String); // Fixed size (10 chars) UTF16-LE, with the \0s trimmed 130 | } 131 | 132 | [Fact] 133 | public void ReadManually() 134 | { 135 | using (var stream = new MemoryStream(_bytes)) 136 | { 137 | var reader = new EndianBinaryReader(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); 138 | var obj = new MyBasicObj(); 139 | 140 | obj.Type = reader.ReadEnum(); 141 | Assert.Equal(ShortSizedEnum.Val2, obj.Type); // Enum works 142 | obj.Version = reader.ReadInt16(); 143 | Assert.Equal(511, obj.Version); // short works 144 | obj.Date = reader.ReadDateTime(); 145 | Assert.Equal(_expectedDateTime, obj.Date); // DateTime works 146 | obj.Int128 = reader.ReadInt128(); 147 | Assert.Equal(_expectedInt128, obj.Int128); // Int128 works 148 | 149 | obj.ArrayWith16Elements = new uint[16]; 150 | reader.ReadUInt32s(obj.ArrayWith16Elements); 151 | for (uint i = 0; i < 16; i++) 152 | { 153 | Assert.Equal(i, obj.ArrayWith16Elements[i]); // Array works 154 | } 155 | 156 | obj.Bool32 = reader.ReadBoolean(); 157 | Assert.False(obj.Bool32); // bool32 works 158 | 159 | reader.ASCII = true; 160 | obj.NullTerminatedASCIIString = reader.ReadString_NullTerminated(); 161 | Assert.Equal("EndianBinaryIO", obj.NullTerminatedASCIIString); // Stops reading at null terminator 162 | 163 | reader.ASCII = false; 164 | obj.UTF16String = reader.ReadString_Count_TrimNullTerminators(10); 165 | Assert.Equal("Kermalis", obj.UTF16String); // Fixed size (10 chars) UTF16-LE, with the \0s trimmed 166 | } 167 | } 168 | 169 | [Fact] 170 | public void WriteObject() 171 | { 172 | byte[] bytes = new byte[_bytes.Length]; 173 | using (var stream = new MemoryStream(bytes)) 174 | { 175 | new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian).WriteObject(GetObj()); 176 | } 177 | 178 | Assert.True(bytes.SequenceEqual(_bytes)); 179 | } 180 | 181 | [Fact] 182 | public void WriteManually() 183 | { 184 | MyBasicObj obj = GetObj(); 185 | 186 | byte[] bytes = new byte[_bytes.Length]; 187 | using (var stream = new MemoryStream(bytes)) 188 | { 189 | var writer = new EndianBinaryWriter(stream, endianness: Endianness.LittleEndian, booleanSize: BooleanSize.U32); 190 | writer.WriteEnum(obj.Type); 191 | writer.WriteInt16(obj.Version); 192 | writer.WriteDateTime(obj.Date); 193 | writer.WriteInt128(obj.Int128); 194 | 195 | writer.WriteUInt32s(obj.ArrayWith16Elements); 196 | 197 | writer.WriteBoolean(obj.Bool32); 198 | 199 | writer.ASCII = true; 200 | writer.WriteChars_NullTerminated(obj.NullTerminatedASCIIString); 201 | writer.ASCII = false; 202 | writer.WriteChars_Count(obj.UTF16String, 10); 203 | } 204 | 205 | Assert.True(bytes.SequenceEqual(_bytes)); 206 | } 207 | 208 | [Fact] 209 | public void SpanIsProperlyTrimmed() 210 | { 211 | Span test = stackalloc char[] { 'K', 'e', 'r', 'm', 'a', 'l', 'i', 's', '\0', '\0', }; 212 | EndianBinaryPrimitives.TrimNullTerminators(ref test); 213 | 214 | Assert.True(test.SequenceEqual("Kermalis")); 215 | } 216 | 217 | [Fact] 218 | public void ReadOnlySpanIsProperlyTrimmed() 219 | { 220 | ReadOnlySpan test = "Kermalis\0\0"; 221 | EndianBinaryPrimitives.TrimNullTerminators(ref test); 222 | 223 | Assert.True(test.SequenceEqual("Kermalis")); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /Testing/ByteTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class ByteTests 6 | { 7 | #region Constants 8 | 9 | private const byte TEST_VAL = 210; 10 | private static readonly byte[] _testValBytes = new byte[1] 11 | { 12 | 0xD2, 13 | }; 14 | 15 | private static readonly byte[] _testArr = new byte[4] 16 | { 17 | 99, 18 | 209, 19 | 3, 20 | 64, 21 | }; 22 | private static readonly byte[] _testArrBytes = new byte[4] 23 | { 24 | 0x63, 25 | 0xD1, 26 | 0x03, 27 | 0x40, 28 | }; 29 | 30 | #endregion 31 | 32 | [Fact] 33 | public void ReadByte() 34 | { 35 | NumTestUtils.ReadValue(TEST_VAL, _testValBytes, 36 | (r) => r.ReadByte()); 37 | } 38 | [Fact] 39 | public void ReadBytes() 40 | { 41 | NumTestUtils.ReadValues(_testArr, _testArrBytes, 42 | (r, v) => r.ReadBytes(v)); 43 | } 44 | [Fact] 45 | public void WriteByte() 46 | { 47 | NumTestUtils.WriteValue(TEST_VAL, _testValBytes, 48 | (w, v) => w.WriteByte(v)); 49 | } 50 | [Fact] 51 | public void WriteBytes() 52 | { 53 | NumTestUtils.WriteValues(_testArr, _testArrBytes, 54 | (w, v) => w.WriteBytes(v)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Testing/CharTests.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | namespace Kermalis.EndianBinaryIOTests; 8 | 9 | public sealed class CharTests 10 | { 11 | private sealed class CharObj 12 | { 13 | public byte Len { get; set; } 14 | [BinaryStringVariableLength(nameof(Len))] 15 | public string Str { get; set; } 16 | } 17 | 18 | #region Constants 19 | 20 | private const string TEST_STR_ASCII = "Jummy\r\nFunnies"; 21 | private static readonly byte[] _testBytes_ASCII = new byte[] 22 | { 23 | 0x0E, // Len 24 | 0x4A, 0x75, 0x6D, 0x6D, 0x79, // "Jummy" 25 | 0x0D, 0x0A, // "\r\n" 26 | 0x46, 0x75, 0x6E, 0x6E, 0x69, 0x65, 0x73 // "Funnies" 27 | }; 28 | private const string TEST_STR_UTF16LE = "Jummy😀\r\n😳Funnies"; 29 | private static readonly byte[] _testBytes_UTF16LE = new byte[] 30 | { 31 | 0x12, // Len 32 | 0x4A, 0x00, 0x75, 0x00, 0x6D, 0x00, 0x6D, 0x00, 0x79, 0x00, // "Jummy" 33 | 0x3D, 0xD8, 0x00, 0xDE, // "😀" 34 | 0x0D, 0x00, 0x0A, 0x00, // "\r\n" 35 | 0x3D, 0xD8, 0x33, 0xDE, // "😳" 36 | 0x46, 0x00, 0x75, 0x00, 0x6E, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x65, 0x00, 0x73, 0x00 // "Funnies" 37 | }; 38 | 39 | #endregion 40 | 41 | private static void Get(bool ascii, out byte[] input, out string str) 42 | { 43 | if (ascii) 44 | { 45 | input = _testBytes_ASCII; 46 | str = TEST_STR_ASCII; 47 | } 48 | else 49 | { 50 | input = _testBytes_UTF16LE; 51 | str = TEST_STR_UTF16LE; 52 | } 53 | } 54 | private static void TestRead(bool ascii, byte[] input, string str) 55 | { 56 | using (var stream = new MemoryStream(input)) 57 | { 58 | CharObj obj = new EndianBinaryReader(stream, ascii: ascii).ReadObject(); 59 | Assert.Equal(str, obj.Str); 60 | } 61 | } 62 | private static void TestWrite(bool ascii, byte[] input, string str) 63 | { 64 | byte[] bytes = new byte[input.Length]; 65 | using (var stream = new MemoryStream(bytes)) 66 | { 67 | var obj = new CharObj 68 | { 69 | Len = (byte)str.Length, 70 | Str = str, 71 | }; 72 | new EndianBinaryWriter(stream, ascii: ascii).WriteObject(obj); 73 | } 74 | Assert.True(bytes.SequenceEqual(input)); 75 | } 76 | 77 | [Theory] 78 | [InlineData(true)] 79 | [InlineData(false)] 80 | public void ReadDefaults(bool ascii) 81 | { 82 | Get(ascii, out byte[] input, out string str); 83 | TestRead(ascii, input, str); 84 | } 85 | 86 | [Theory] 87 | [InlineData(true)] 88 | [InlineData(false)] 89 | public void WriteDefaults(bool ascii) 90 | { 91 | Get(ascii, out byte[] input, out string str); 92 | TestWrite(ascii, input, str); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Testing/DecimalTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class DecimalTests 6 | { 7 | #region Constants 8 | 9 | private const decimal TEST_VAL = 12_345_678_909_876_543_210.123456789m; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(decimal)] 11 | { 12 | 0x15, 0x71, 0x84, 0xA0, /**/ 0x75, 0x40, 0xAD, 0xBE, /**/ 0x32, 0x1B, 0xE4, 0x27, /**/ 0x00, 0x00, 0x09, 0x00, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(decimal)] 15 | { 16 | 0xA0, 0x84, 0x71, 0x15, /**/ 0xBE, 0xAD, 0x40, 0x75, /**/ 0x27, 0xE4, 0x1B, 0x32, /**/ 0x00, 0x09, 0x00, 0x00, 17 | }; 18 | 19 | private static readonly decimal[] _testArr = new decimal[4] 20 | { 21 | -18_185_544_635_427_120_524.93305179m, 22 | -22_010_447_631_927_599_247.18039726m, 23 | 41_455_770_299_484_821_081.65900781m, 24 | 22_442_965_292_979_427_993.29821457m, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(decimal)] 27 | { 28 | 0x5B, 0xC5, 0x4B, 0x5E, /**/ 0x65, 0xE6, 0xFF, 0x01, /**/ 0xE3, 0x45, 0xE0, 0x05, /**/ 0x00, 0x00, 0x08, 0x80, 29 | 0xAE, 0xF2, 0x4B, 0xBB, /**/ 0xA4, 0x20, 0x17, 0xB3, /**/ 0x5B, 0xA9, 0x1C, 0x07, /**/ 0x00, 0x00, 0x08, 0x80, 30 | 0xED, 0xC9, 0xFE, 0x4A, /**/ 0x54, 0x20, 0x93, 0x1A, /**/ 0x15, 0x24, 0x65, 0x0D, /**/ 0x00, 0x00, 0x08, 0x00, 31 | 0x11, 0x83, 0x14, 0x50, /**/ 0x63, 0x4A, 0x69, 0xA3, /**/ 0x46, 0x70, 0x40, 0x07, /**/ 0x00, 0x00, 0x08, 0x00, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(decimal)] 34 | { 35 | 0x5E, 0x4B, 0xC5, 0x5B, /**/ 0x01, 0xFF, 0xE6, 0x65, /**/ 0x05, 0xE0, 0x45, 0xE3, /**/ 0x80, 0x08, 0x00, 0x00, 36 | 0xBB, 0x4B, 0xF2, 0xAE, /**/ 0xB3, 0x17, 0x20, 0xA4, /**/ 0x07, 0x1C, 0xA9, 0x5B, /**/ 0x80, 0x08, 0x00, 0x00, 37 | 0x4A, 0xFE, 0xC9, 0xED, /**/ 0x1A, 0x93, 0x20, 0x54, /**/ 0x0D, 0x65, 0x24, 0x15, /**/ 0x00, 0x08, 0x00, 0x00, 38 | 0x50, 0x14, 0x83, 0x11, /**/ 0xA3, 0x69, 0x4A, 0x63, /**/ 0x07, 0x40, 0x70, 0x46, /**/ 0x00, 0x08, 0x00, 0x00, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadDecimal(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadDecimal()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadDecimals(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadDecimals(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteDecimal(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(decimal), 65 | (w, v) => w.WriteDecimal(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteDecimals(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(decimal), 73 | (w, v) => w.WriteDecimals(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/DoubleTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class DoubleTests 6 | { 7 | #region Constants 8 | 9 | private const double TEST_VAL = 12_345_678.12345678d; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(double)] 11 | { 12 | 0xA2, 0x5B, 0xF3, 0xC3, 0x29, 0x8C, 0x67, 0x41, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(double)] 15 | { 16 | 0x41, 0x67, 0x8C, 0x29, 0xC3, 0xF3, 0x5B, 0xA2, 17 | }; 18 | 19 | private static readonly double[] _testArr = new double[4] 20 | { 21 | -51_692_240.59455357d, 22 | -68_231_145.04473292d, 23 | 98_110_687.70543043d, 24 | 75_442_096.25828312d, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(double)] 27 | { 28 | 0x4D, 0xA5, 0xC1, 0x84, 0x16, 0xA6, 0x88, 0xC1, 29 | 0x77, 0xCE, 0x2D, 0xA4, 0x7F, 0x44, 0x90, 0xC1, 30 | 0x5B, 0x5C, 0xD2, 0x7E, 0x33, 0x64, 0x97, 0x41, 31 | 0x5F, 0x7B, 0x08, 0xC1, 0x9E, 0xFC, 0x91, 0x41, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(double)] 34 | { 35 | 0xC1, 0x88, 0xA6, 0x16, 0x84, 0xC1, 0xA5, 0x4D, 36 | 0xC1, 0x90, 0x44, 0x7F, 0xA4, 0x2D, 0xCE, 0x77, 37 | 0x41, 0x97, 0x64, 0x33, 0x7E, 0xD2, 0x5C, 0x5B, 38 | 0x41, 0x91, 0xFC, 0x9E, 0xC1, 0x08, 0x7B, 0x5F, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadDouble(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadDouble()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadDoubles(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadDoubles(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteDouble(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(double), 65 | (w, v) => w.WriteDouble(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteDoubles(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(double), 73 | (w, v) => w.WriteDoubles(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/EndianBinaryTesting.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0;net8.0 5 | latest 6 | Kermalis.EndianBinaryIOTests 7 | false 8 | true 9 | IDE0230 10 | 11 | Kermalis 12 | Kermalis 13 | https://github.com/Kermalis/EndianBinaryIO 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Testing/HalfTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Xunit; 4 | 5 | namespace Kermalis.EndianBinaryIOTests; 6 | 7 | public sealed class HalfTests 8 | { 9 | #region Constants 10 | 11 | private const int SIZEOF_HALF = sizeof(ushort); 12 | 13 | private static readonly Half _testVal = Parse("12.34"); 14 | private static readonly byte[] _testValBytesLE = new byte[SIZEOF_HALF] 15 | { 16 | 0x2C, 0x4A, 17 | }; 18 | private static readonly byte[] _testValBytesBE = new byte[SIZEOF_HALF] 19 | { 20 | 0x4A, 0x2C, 21 | }; 22 | 23 | private static readonly Half[] _testArr = new Half[4] 24 | { 25 | Half.MinValue, 26 | Parse("-15.25"), 27 | Parse("1,234.5"), 28 | Half.MaxValue, 29 | }; 30 | private static readonly byte[] _testArrBytesLE = new byte[4 * SIZEOF_HALF] 31 | { 32 | 0xFF, 0xFB, 33 | 0xA0, 0xCB, 34 | 0xD2, 0x64, 35 | 0xFF, 0x7B, 36 | }; 37 | private static readonly byte[] _testArrBytesBE = new byte[4 * SIZEOF_HALF] 38 | { 39 | 0xFB, 0xFF, 40 | 0xCB, 0xA0, 41 | 0x64, 0xD2, 42 | 0x7B, 0xFF, 43 | }; 44 | 45 | private static Half Parse(string num) 46 | { 47 | return Half.Parse(num, style: NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands, provider: NumberFormatInfo.InvariantInfo); 48 | } 49 | 50 | #endregion 51 | 52 | [Theory] 53 | [InlineData(true)] 54 | [InlineData(false)] 55 | public void ReadHalf(bool le) 56 | { 57 | NumTestUtils.ReadValue(le, _testVal, _testValBytesLE, _testValBytesBE, 58 | (r) => r.ReadHalf()); 59 | } 60 | [Theory] 61 | [InlineData(true)] 62 | [InlineData(false)] 63 | public void ReadHalves(bool le) 64 | { 65 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 66 | (r, v) => r.ReadHalves(v)); 67 | } 68 | [Theory] 69 | [InlineData(true)] 70 | [InlineData(false)] 71 | public void WriteHalf(bool le) 72 | { 73 | NumTestUtils.WriteValue(le, _testVal, _testValBytesLE, _testValBytesBE, SIZEOF_HALF, 74 | (w, v) => w.WriteHalf(v)); 75 | } 76 | [Theory] 77 | [InlineData(true)] 78 | [InlineData(false)] 79 | public void WriteHalves(bool le) 80 | { 81 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, SIZEOF_HALF, 82 | (w, v) => w.WriteHalves(v)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Testing/Int128Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Xunit; 4 | 5 | namespace Kermalis.EndianBinaryIOTests; 6 | 7 | public sealed class Int128Tests 8 | { 9 | #region Constants 10 | 11 | private const int SIZEOF_INT128 = sizeof(ulong) * 2; 12 | 13 | private static readonly Int128 _testVal = Parse("-9,183,616,886,827,840,433,572,302"); 14 | private static readonly byte[] _testValBytesLE = new byte[SIZEOF_INT128] 15 | { 16 | 0x32, 0xB6, 0x07, 0x84, 0xAF, 0x4D, 0x89, 0x21, 0x4B, 0x67, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 17 | }; 18 | private static readonly byte[] _testValBytesBE = new byte[SIZEOF_INT128] 19 | { 20 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x67, 0x4B, 0x21, 0x89, 0x4D, 0xAF, 0x84, 0x07, 0xB6, 0x32, 21 | }; 22 | 23 | private static readonly Int128[] _testArr = new Int128[4] 24 | { 25 | Parse("-5,484,660,365,300,721,980,907,729"), 26 | Parse("-3,392,080,724,347,328,401,378,812"), 27 | Parse("1,850,493,629,233,363,857,060,483"), 28 | Parse("7,096,725,192,671,155,766,764,600"), 29 | }; 30 | private static readonly byte[] _testArrBytesLE = new byte[4 * SIZEOF_INT128] 31 | { 32 | 0x2F, 0x5F, 0xD2, 0x2C, 0x05, 0x9F, 0x40, 0xF7, 0x93, 0x76, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 33 | 0x04, 0xF2, 0x61, 0x35, 0x07, 0x04, 0x7B, 0xEF, 0xB2, 0x31, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 34 | 0x83, 0x32, 0x5A, 0x49, 0x4D, 0x1C, 0xED, 0x75, 0xDB, 0x87, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x38, 0x24, 0x25, 0x27, 0xE1, 0xB0, 0x5A, 0x3E, 0xCA, 0xDE, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | }; 37 | private static readonly byte[] _testArrBytesBE = new byte[4 * SIZEOF_INT128] 38 | { 39 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x76, 0x93, 0xF7, 0x40, 0x9F, 0x05, 0x2C, 0xD2, 0x5F, 0x2F, 40 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x31, 0xB2, 0xEF, 0x7B, 0x04, 0x07, 0x35, 0x61, 0xF2, 0x04, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x87, 0xDB, 0x75, 0xED, 0x1C, 0x4D, 0x49, 0x5A, 0x32, 0x83, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xDE, 0xCA, 0x3E, 0x5A, 0xB0, 0xE1, 0x27, 0x25, 0x24, 0x38, 43 | }; 44 | 45 | private static Int128 Parse(string num) 46 | { 47 | return Int128.Parse(num, NumberStyles.AllowLeadingSign | NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo); 48 | } 49 | 50 | #endregion 51 | 52 | [Theory] 53 | [InlineData(true)] 54 | [InlineData(false)] 55 | public void ReadInt128(bool le) 56 | { 57 | NumTestUtils.ReadValue(le, _testVal, _testValBytesLE, _testValBytesBE, 58 | (r) => r.ReadInt128()); 59 | } 60 | [Theory] 61 | [InlineData(true)] 62 | [InlineData(false)] 63 | public void ReadInt128s(bool le) 64 | { 65 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 66 | (r, v) => r.ReadInt128s(v)); 67 | } 68 | [Theory] 69 | [InlineData(true)] 70 | [InlineData(false)] 71 | public void WriteInt128(bool le) 72 | { 73 | NumTestUtils.WriteValue(le, _testVal, _testValBytesLE, _testValBytesBE, SIZEOF_INT128, 74 | (w, v) => w.WriteInt128(v)); 75 | } 76 | [Theory] 77 | [InlineData(true)] 78 | [InlineData(false)] 79 | public void WriteInt128s(bool le) 80 | { 81 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, SIZEOF_INT128, 82 | (w, v) => w.WriteInt128s(v)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Testing/Int16Tests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class Int16Tests 6 | { 7 | #region Constants 8 | 9 | private const short TEST_VAL = -6_969; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(short)] 11 | { 12 | 0xC7, 0xE4, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(short)] 15 | { 16 | 0xE4, 0xC7, 17 | }; 18 | 19 | private static readonly short[] _testArr = new short[4] 20 | { 21 | -8_517, 22 | -22_343, 23 | 26_381, 24 | 2_131, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(short)] 27 | { 28 | 0xBB, 0xDE, 29 | 0xB9, 0xA8, 30 | 0x0D, 0x67, 31 | 0x53, 0x08, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(short)] 34 | { 35 | 0xDE, 0xBB, 36 | 0xA8, 0xB9, 37 | 0x67, 0x0D, 38 | 0x08, 0x53, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadInt16(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadInt16()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadInt16s(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadInt16s(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteInt16(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(short), 65 | (w, v) => w.WriteInt16(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteInt16s(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(short), 73 | (w, v) => w.WriteInt16s(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/Int32Tests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class Int32Tests 6 | { 7 | #region Constants 8 | 9 | private const int TEST_VAL = -13_477; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(int)] 11 | { 12 | 0x5B, 0xCB, 0xFF, 0xFF, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(int)] 15 | { 16 | 0xFF, 0xFF, 0xCB, 0x5B, 17 | }; 18 | 19 | private static readonly int[] _testArr = new int[4] 20 | { 21 | -2_024_826_956, 22 | -570_721_400, 23 | 1_250_296_726, 24 | 1_161_309_695, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(int)] 27 | { 28 | 0xB4, 0x97, 0x4F, 0x87, 29 | 0x88, 0x7B, 0xFB, 0xDD, 30 | 0x96, 0x03, 0x86, 0x4A, 31 | 0xFF, 0x2D, 0x38, 0x45, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(int)] 34 | { 35 | 0x87, 0x4F, 0x97, 0xB4, 36 | 0xDD, 0xFB, 0x7B, 0x88, 37 | 0x4A, 0x86, 0x03, 0x96, 38 | 0x45, 0x38, 0x2D, 0xFF, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadInt32(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadInt32()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadInt32s(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadInt32s(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteInt32(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(int), 65 | (w, v) => w.WriteInt32(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteInt32s(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(int), 73 | (w, v) => w.WriteInt32s(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/Int64Tests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class Int64Tests 6 | { 7 | #region Constants 8 | 9 | private const long TEST_VAL = -8_574_924_182_750_059_124; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(long)] 11 | { 12 | 0x8C, 0xA5, 0x46, 0x3F, 0xE3, 0xBF, 0xFF, 0x88, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(long)] 15 | { 16 | 0x88, 0xFF, 0xBF, 0xE3, 0x3F, 0x46, 0xA5, 0x8C, 17 | }; 18 | 19 | private static readonly long[] _testArr = new long[4] 20 | { 21 | -6_378_121_417_350_903_592, 22 | -397_400_959_663_200_194, 23 | 7_340_833_089_393_364_811, 24 | 8_772_498_435_351_010_701, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(long)] 27 | { 28 | 0xD8, 0x08, 0x7D, 0x18, 0x5D, 0x5C, 0x7C, 0xA7, 29 | 0x3E, 0x28, 0xE0, 0xC5, 0xEE, 0x25, 0x7C, 0xFA, 30 | 0x4B, 0x8F, 0xB9, 0x28, 0xCD, 0xE0, 0xDF, 0x65, 31 | 0x8D, 0x5D, 0xC4, 0x27, 0xD9, 0x2C, 0xBE, 0x79, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(long)] 34 | { 35 | 0xA7, 0x7C, 0x5C, 0x5D, 0x18, 0x7D, 0x08, 0xD8, 36 | 0xFA, 0x7C, 0x25, 0xEE, 0xC5, 0xE0, 0x28, 0x3E, 37 | 0x65, 0xDF, 0xE0, 0xCD, 0x28, 0xB9, 0x8F, 0x4B, 38 | 0x79, 0xBE, 0x2C, 0xD9, 0x27, 0xC4, 0x5D, 0x8D, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadInt64(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadInt64()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadInt64s(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadInt64s(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteInt64(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(long), 65 | (w, v) => w.WriteInt64(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteInt64s(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(long), 73 | (w, v) => w.WriteInt64s(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/LengthsTests.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | namespace Kermalis.EndianBinaryIOTests; 8 | 9 | public sealed class LengthsTests 10 | { 11 | private sealed class MyLengthyObj 12 | { 13 | [BinaryArrayFixedLength(3)] 14 | [BinaryASCII] 15 | [BinaryStringNullTerminated] 16 | public string[] NullTerminatedStringArray { get; set; } 17 | 18 | [BinaryArrayFixedLength(3)] 19 | [BinaryASCII] 20 | [BinaryStringFixedLength(5)] 21 | public string[] SizedStringArray { get; set; } 22 | 23 | public byte VariableLengthProperty { get; set; } 24 | [BinaryArrayVariableLength(nameof(VariableLengthProperty))] 25 | public ShortSizedEnum[] VariableSizedArray { get; set; } 26 | } 27 | private sealed class ZeroLenArrayObj 28 | { 29 | [BinaryArrayFixedLength(0)] 30 | public byte[] SizedArray { get; set; } 31 | 32 | public byte VariableLength { get; set; } 33 | [BinaryArrayVariableLength(nameof(VariableLength))] 34 | public byte[] VariableArray { get; set; } 35 | } 36 | 37 | #region Constants 38 | 39 | private static readonly byte[] _lengthyObjBytes = new byte[] 40 | { 41 | 0x48, 0x69, 0x00, // "Hi\0" 42 | 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, // "Hello\0" 43 | 0x48, 0x6F, 0x6C, 0x61, 0x00, // "Hola\0" 44 | 45 | 0x53, 0x65, 0x65, 0x79, 0x61, // "Seeya" 46 | 0x42, 0x79, 0x65, 0x00, 0x00, // "Bye\0\0" 47 | 0x41, 0x64, 0x69, 0x6F, 0x73, // "Adios" 48 | 49 | 0x02, // (byte)2 50 | 0x40, 0x00, // ShortSizedEnum.Val1 51 | 0x00, 0x08, // ShortSizedEnum.Val2 52 | }; 53 | private static readonly byte[] _zeroLenArrayObjBytes = new byte[] 54 | { 55 | 0x00, // (byte)0 56 | }; 57 | 58 | #endregion 59 | 60 | [Fact] 61 | public void ReadLengthyObject() 62 | { 63 | MyLengthyObj obj; 64 | using (var stream = new MemoryStream(_lengthyObjBytes)) 65 | { 66 | obj = new EndianBinaryReader(stream, Endianness.LittleEndian).ReadObject(); 67 | } 68 | 69 | Assert.Equal(3, obj.NullTerminatedStringArray.Length); // Fixed size array works 70 | Assert.Equal("Hi", obj.NullTerminatedStringArray[0]); // Null terminated strings 71 | Assert.Equal("Hello", obj.NullTerminatedStringArray[1]); 72 | Assert.Equal("Hola", obj.NullTerminatedStringArray[2]); 73 | 74 | Assert.Equal(3, obj.SizedStringArray.Length); // Fixed size array again 75 | Assert.Equal("Seeya", obj.SizedStringArray[0]); // Strings 5 chars long 76 | Assert.Equal("Bye\0\0", obj.SizedStringArray[1]); 77 | Assert.Equal("Adios", obj.SizedStringArray[2]); 78 | 79 | Assert.Equal(2, obj.VariableLengthProperty); // This determines how long the following array is 80 | Assert.Equal(2, obj.VariableSizedArray.Length); // Retrieves the proper size 81 | Assert.Equal(ShortSizedEnum.Val1, obj.VariableSizedArray[0]); 82 | Assert.Equal(ShortSizedEnum.Val2, obj.VariableSizedArray[1]); 83 | } 84 | 85 | [Fact] 86 | public void WriteLengthyObject() 87 | { 88 | byte[] bytes = new byte[_lengthyObjBytes.Length]; 89 | using (var stream = new MemoryStream(bytes)) 90 | { 91 | new EndianBinaryWriter(stream, Endianness.LittleEndian).WriteObject(new MyLengthyObj 92 | { 93 | NullTerminatedStringArray = new string[3] 94 | { 95 | "Hi", "Hello", "Hola", 96 | }, 97 | 98 | SizedStringArray = new string[3] 99 | { 100 | "Seeya", "Bye", "Adios", 101 | }, 102 | 103 | VariableLengthProperty = 2, 104 | VariableSizedArray = new ShortSizedEnum[2] 105 | { 106 | ShortSizedEnum.Val1, ShortSizedEnum.Val2, 107 | }, 108 | }); 109 | } 110 | Assert.True(bytes.SequenceEqual(_lengthyObjBytes)); 111 | } 112 | 113 | [Fact] 114 | public void ReadZeroLenArrayObject() 115 | { 116 | ZeroLenArrayObj obj; 117 | using (var stream = new MemoryStream(_zeroLenArrayObjBytes)) 118 | { 119 | obj = new EndianBinaryReader(stream, Endianness.LittleEndian).ReadObject(); 120 | } 121 | 122 | Assert.Empty(obj.SizedArray); // Fixed size array works 123 | 124 | Assert.Equal(0, obj.VariableLength); // This determines how long the following array is 125 | Assert.Empty(obj.VariableArray); // Retrieves the proper size 126 | } 127 | 128 | [Fact] 129 | public void WriteZeroLenArrayObject() 130 | { 131 | byte[] bytes = new byte[_zeroLenArrayObjBytes.Length]; 132 | using (var stream = new MemoryStream(bytes)) 133 | { 134 | new EndianBinaryWriter(stream, Endianness.LittleEndian).WriteObject(new ZeroLenArrayObj 135 | { 136 | SizedArray = Array.Empty(), 137 | 138 | VariableLength = 0, 139 | VariableArray = Array.Empty(), 140 | }); 141 | } 142 | Assert.True(bytes.SequenceEqual(_zeroLenArrayObjBytes)); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Testing/SByteTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class SByteTests 6 | { 7 | #region Constants 8 | 9 | private const sbyte TEST_VAL = -93; 10 | private static readonly byte[] _testValBytes = new byte[1] 11 | { 12 | 0xA3, 13 | }; 14 | 15 | private static readonly sbyte[] _testArr = new sbyte[4] 16 | { 17 | -21, 18 | 6, 19 | 17, 20 | -100, 21 | }; 22 | private static readonly byte[] _testArrBytes = new byte[4] 23 | { 24 | 0xEB, 25 | 0x06, 26 | 0x11, 27 | 0x9C, 28 | }; 29 | 30 | #endregion 31 | 32 | [Fact] 33 | public void ReadSByte() 34 | { 35 | NumTestUtils.ReadValue(TEST_VAL, _testValBytes, 36 | (r) => r.ReadSByte()); 37 | } 38 | [Fact] 39 | public void ReadSBytes() 40 | { 41 | NumTestUtils.ReadValues(_testArr, _testArrBytes, 42 | (r, v) => r.ReadSBytes(v)); 43 | } 44 | [Fact] 45 | public void WriteSByte() 46 | { 47 | NumTestUtils.WriteValue(TEST_VAL, _testValBytes, 48 | (w, v) => w.WriteSByte(v)); 49 | } 50 | [Fact] 51 | public void WriteSBytes() 52 | { 53 | NumTestUtils.WriteValues(_testArr, _testArrBytes, 54 | (w, v) => w.WriteSBytes(v)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Testing/SingleTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class SingleTests 6 | { 7 | #region Constants 8 | 9 | private const float TEST_VAL = 1_234.1234f; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(float)] 11 | { 12 | 0xF3, 0x43, 0x9A, 0x44, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(float)] 15 | { 16 | 0x44, 0x9A, 0x43, 0xF3, 17 | }; 18 | 19 | private static readonly float[] _testArr = new float[4] 20 | { 21 | -6_814.5127f, 22 | -3_391.6581f, 23 | 8_710.1492f, 24 | 3_065.2182f, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(float)] 27 | { 28 | 0x1A, 0xF4, 0xD4, 0xC5, 29 | 0x88, 0xFA, 0x53, 0xC5, 30 | 0x99, 0x18, 0x08, 0x46, 31 | 0x7E, 0x93, 0x3F, 0x45, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(float)] 34 | { 35 | 0xC5, 0xD4, 0xF4, 0x1A, 36 | 0xC5, 0x53, 0xFA, 0x88, 37 | 0x46, 0x08, 0x18, 0x99, 38 | 0x45, 0x3F, 0x93, 0x7E, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadSingle(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadSingle()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadSingles(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadSingles(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteSingle(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(float), 65 | (w, v) => w.WriteSingle(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteSingles(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(float), 73 | (w, v) => w.WriteSingles(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/TestUtils.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.EndianBinaryIOTests; 2 | 3 | internal enum ByteSizedEnum : byte 4 | { 5 | Val1 = 0x20, 6 | Val2 = 0x80, 7 | } 8 | internal enum ShortSizedEnum : short 9 | { 10 | Val1 = 0x40, 11 | Val2 = 0x800, 12 | } 13 | -------------------------------------------------------------------------------- /Testing/UInt128Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Xunit; 4 | 5 | namespace Kermalis.EndianBinaryIOTests; 6 | 7 | public sealed class UInt128Tests 8 | { 9 | #region Constants 10 | 11 | private const int SIZEOF_UINT128 = sizeof(ulong) * 2; 12 | 13 | private static readonly UInt128 _testVal = Parse("4,441,948,164,730,199,290,118,182"); 14 | private static readonly byte[] _testValBytesLE = new byte[SIZEOF_UINT128] 15 | { 16 | 0x26, 0x80, 0x67, 0x38, 0x19, 0x5C, 0x15, 0x7E, 0x9E, 0xAC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | }; 18 | private static readonly byte[] _testValBytesBE = new byte[SIZEOF_UINT128] 19 | { 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAC, 0x9E, 0x7E, 0x15, 0x5C, 0x19, 0x38, 0x67, 0x80, 0x26, 21 | }; 22 | 23 | private static readonly UInt128[] _testArr = new UInt128[4] 24 | { 25 | Parse("8,427,008,177,709,125,582,861,203"), 26 | Parse("5,054,904,958,755,150,864,423,633"), 27 | Parse("1,381,684,868,511,170,617,938,143"), 28 | Parse("3,631,327,785,041,621,745,644,989"), 29 | }; 30 | private static readonly byte[] _testArrBytesLE = new byte[4 * SIZEOF_UINT128] 31 | { 32 | 0x93, 0x33, 0xB6, 0xAF, 0x96, 0x4F, 0x58, 0x07, 0x7D, 0xF8, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0xD1, 0x9A, 0xB9, 0x15, 0x83, 0x01, 0x68, 0xF2, 0x6A, 0x2E, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0xDF, 0x10, 0x74, 0x14, 0xBC, 0x26, 0x6C, 0x49, 0x95, 0x24, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0xBD, 0xB5, 0x27, 0x86, 0x2D, 0x20, 0x76, 0xAC, 0xF6, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | }; 37 | private static readonly byte[] _testArrBytesBE = new byte[4 * SIZEOF_UINT128] 38 | { 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xF8, 0x7D, 0x07, 0x58, 0x4F, 0x96, 0xAF, 0xB6, 0x33, 0x93, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x2E, 0x6A, 0xF2, 0x68, 0x01, 0x83, 0x15, 0xB9, 0x9A, 0xD1, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x24, 0x95, 0x49, 0x6C, 0x26, 0xBC, 0x14, 0x74, 0x10, 0xDF, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xF6, 0xAC, 0x76, 0x20, 0x2D, 0x86, 0x27, 0xB5, 0xBD, 43 | }; 44 | 45 | private static UInt128 Parse(string num) 46 | { 47 | return UInt128.Parse(num, NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo); 48 | } 49 | 50 | #endregion 51 | 52 | [Theory] 53 | [InlineData(true)] 54 | [InlineData(false)] 55 | public void ReadUInt128(bool le) 56 | { 57 | NumTestUtils.ReadValue(le, _testVal, _testValBytesLE, _testValBytesBE, 58 | (r) => r.ReadUInt128()); 59 | } 60 | [Theory] 61 | [InlineData(true)] 62 | [InlineData(false)] 63 | public void ReadUInt128s(bool le) 64 | { 65 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 66 | (r, v) => r.ReadUInt128s(v)); 67 | } 68 | [Theory] 69 | [InlineData(true)] 70 | [InlineData(false)] 71 | public void WriteUInt128(bool le) 72 | { 73 | NumTestUtils.WriteValue(le, _testVal, _testValBytesLE, _testValBytesBE, SIZEOF_UINT128, 74 | (w, v) => w.WriteUInt128(v)); 75 | } 76 | [Theory] 77 | [InlineData(true)] 78 | [InlineData(false)] 79 | public void WriteUInt128s(bool le) 80 | { 81 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, SIZEOF_UINT128, 82 | (w, v) => w.WriteUInt128s(v)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Testing/UInt16Tests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class UInt16Tests 6 | { 7 | #region Constants 8 | 9 | private const ushort TEST_VAL = 20_901; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(ushort)] 11 | { 12 | 0xA5, 0x51, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(ushort)] 15 | { 16 | 0x51, 0xA5, 17 | }; 18 | 19 | private static readonly ushort[] _testArr = new ushort[4] 20 | { 21 | 6_861, 22 | 37_712, 23 | 09_515, 24 | 46_233, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(ushort)] 27 | { 28 | 0xCD, 0x1A, 29 | 0x50, 0x93, 30 | 0x2B, 0x25, 31 | 0x99, 0xB4, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(ushort)] 34 | { 35 | 0x1A, 0xCD, 36 | 0x93, 0x50, 37 | 0x25, 0x2B, 38 | 0xB4, 0x99, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadUInt16(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadUInt16()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadUInt16s(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadUInt16s(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteUInt16(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(ushort), 65 | (w, v) => w.WriteUInt16(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteUInt16s(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(ushort), 73 | (w, v) => w.WriteUInt16s(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/UInt32Tests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class UInt32Tests 6 | { 7 | #region Constants 8 | 9 | private const uint TEST_VAL = 2_307_841_074; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(uint)] 11 | { 12 | 0x32, 0xDC, 0x8E, 0x89, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(uint)] 15 | { 16 | 0x89, 0x8E, 0xDC, 0x32, 17 | }; 18 | 19 | private static readonly uint[] _testArr = new uint[4] 20 | { 21 | 2_091_540_746, 22 | 411_473_902, 23 | 1_365_957_744, 24 | 3_249_860_615, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(uint)] 27 | { 28 | 0x0A, 0x61, 0xAA, 0x7C, 29 | 0xEE, 0x97, 0x86, 0x18, 30 | 0x70, 0xDC, 0x6A, 0x51, 31 | 0x07, 0xF0, 0xB4, 0xC1, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(uint)] 34 | { 35 | 0x7C, 0xAA, 0x61, 0x0A, 36 | 0x18, 0x86, 0x97, 0xEE, 37 | 0x51, 0x6A, 0xDC, 0x70, 38 | 0xC1, 0xB4, 0xF0, 0x07, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadUInt32(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadUInt32()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadUInt32s(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadUInt32s(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteUInt32(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(uint), 65 | (w, v) => w.WriteUInt32(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteUInt32s(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(uint), 73 | (w, v) => w.WriteUInt32s(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/UInt64Tests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Kermalis.EndianBinaryIOTests; 4 | 5 | public sealed class UInt64Tests 6 | { 7 | #region Constants 8 | 9 | private const ulong TEST_VAL = 14_793_741_306_955_655_192; 10 | private static readonly byte[] _testValBytesLE = new byte[sizeof(ulong)] 11 | { 12 | 0x18, 0x68, 0x1D, 0x8C, 0x56, 0xED, 0x4D, 0xCD, 13 | }; 14 | private static readonly byte[] _testValBytesBE = new byte[sizeof(ulong)] 15 | { 16 | 0xCD, 0x4D, 0xED, 0x56, 0x8C, 0x1D, 0x68, 0x18, 17 | }; 18 | 19 | private static readonly ulong[] _testArr = new ulong[4] 20 | { 21 | 16_488_541_351_461_240_347, 22 | 4_889_897_707_926_465_544, 23 | 13_989_148_393_676_279_722, 24 | 13_184_186_537_684_656_338, 25 | }; 26 | private static readonly byte[] _testArrBytesLE = new byte[4 * sizeof(ulong)] 27 | { 28 | 0x1B, 0xBE, 0x2F, 0xC6, 0xFF, 0x10, 0xD3, 0xE4, 29 | 0x08, 0x2C, 0xF4, 0xBC, 0x0E, 0x68, 0xDC, 0x43, 30 | 0xAA, 0x0F, 0x4D, 0xAB, 0x58, 0x70, 0x23, 0xC2, 31 | 0xD2, 0x60, 0x2E, 0x9F, 0xCD, 0xA3, 0xF7, 0xB6, 32 | }; 33 | private static readonly byte[] _testArrBytesBE = new byte[4 * sizeof(ulong)] 34 | { 35 | 0xE4, 0xD3, 0x10, 0xFF, 0xC6, 0x2F, 0xBE, 0x1B, 36 | 0x43, 0xDC, 0x68, 0x0E, 0xBC, 0xF4, 0x2C, 0x08, 37 | 0xC2, 0x23, 0x70, 0x58, 0xAB, 0x4D, 0x0F, 0xAA, 38 | 0xB6, 0xF7, 0xA3, 0xCD, 0x9F, 0x2E, 0x60, 0xD2, 39 | }; 40 | 41 | #endregion 42 | 43 | [Theory] 44 | [InlineData(true)] 45 | [InlineData(false)] 46 | public void ReadUInt64(bool le) 47 | { 48 | NumTestUtils.ReadValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, 49 | (r) => r.ReadUInt64()); 50 | } 51 | [Theory] 52 | [InlineData(true)] 53 | [InlineData(false)] 54 | public void ReadUInt64s(bool le) 55 | { 56 | NumTestUtils.ReadValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, 57 | (r, v) => r.ReadUInt64s(v)); 58 | } 59 | [Theory] 60 | [InlineData(true)] 61 | [InlineData(false)] 62 | public void WriteUInt64(bool le) 63 | { 64 | NumTestUtils.WriteValue(le, TEST_VAL, _testValBytesLE, _testValBytesBE, sizeof(ulong), 65 | (w, v) => w.WriteUInt64(v)); 66 | } 67 | [Theory] 68 | [InlineData(true)] 69 | [InlineData(false)] 70 | public void WriteUInt64s(bool le) 71 | { 72 | NumTestUtils.WriteValues(le, _testArr, _testArrBytesLE, _testArrBytesBE, sizeof(ulong), 73 | (w, v) => w.WriteUInt64s(v)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Testing/_NumTestUtils.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | namespace Kermalis.EndianBinaryIOTests; 8 | 9 | internal static class NumTestUtils 10 | { 11 | public static void ReadValue(bool le, T value, byte[] arrLE, byte[] arrBE, 12 | Func read) 13 | { 14 | byte[] input = le ? arrLE : arrBE; 15 | Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; 16 | 17 | T val; 18 | using (var stream = new MemoryStream(input)) 19 | { 20 | val = read(new EndianBinaryReader(stream, endianness: e)); 21 | } 22 | Assert.Equal(value, val); 23 | } 24 | public static void ReadValues(bool le, T[] values, byte[] arrLE, byte[] arrBE, 25 | Action read) 26 | { 27 | byte[] input = le ? arrLE : arrBE; 28 | Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; 29 | 30 | var arr = new T[values.Length]; 31 | using (var stream = new MemoryStream(input)) 32 | { 33 | read(new EndianBinaryReader(stream, endianness: e), arr); 34 | } 35 | Assert.True(arr.SequenceEqual(values)); 36 | } 37 | public static void WriteValue(bool le, T value, byte[] arrLE, byte[] arrBE, int sizeOf, 38 | Action write) 39 | { 40 | byte[] input = le ? arrLE : arrBE; 41 | Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; 42 | 43 | byte[] bytes = new byte[sizeOf]; 44 | using (var stream = new MemoryStream(bytes)) 45 | { 46 | write(new EndianBinaryWriter(stream, endianness: e), value); 47 | } 48 | Assert.True(bytes.SequenceEqual(input)); 49 | } 50 | public static void WriteValues(bool le, T[] values, byte[] arrLE, byte[] arrBE, int sizeOf, 51 | Action write) 52 | { 53 | byte[] input = le ? arrLE : arrBE; 54 | Endianness e = le ? Endianness.LittleEndian : Endianness.BigEndian; 55 | 56 | byte[] bytes = new byte[values.Length * sizeOf]; 57 | using (var stream = new MemoryStream(bytes)) 58 | { 59 | write(new EndianBinaryWriter(stream, endianness: e), values); 60 | } 61 | Assert.True(bytes.SequenceEqual(input)); 62 | } 63 | 64 | #region SByte/Byte 65 | 66 | public static void ReadValue(T value, byte[] input, 67 | Func read) 68 | { 69 | T val; 70 | using (var stream = new MemoryStream(input)) 71 | { 72 | val = read(new EndianBinaryReader(stream)); 73 | } 74 | Assert.Equal(value, val); 75 | } 76 | public static void ReadValues(T[] values, byte[] input, 77 | Action read) 78 | { 79 | var arr = new T[values.Length]; 80 | using (var stream = new MemoryStream(input)) 81 | { 82 | read(new EndianBinaryReader(stream), arr); 83 | } 84 | Assert.True(arr.SequenceEqual(values)); 85 | } 86 | public static void WriteValue(T value, byte[] input, 87 | Action write) 88 | { 89 | byte[] bytes = new byte[1]; 90 | using (var stream = new MemoryStream(bytes)) 91 | { 92 | write(new EndianBinaryWriter(stream), value); 93 | } 94 | Assert.True(bytes.SequenceEqual(input)); 95 | } 96 | public static void WriteValues(T[] values, byte[] input, 97 | Action write) 98 | { 99 | byte[] bytes = new byte[values.Length]; 100 | using (var stream = new MemoryStream(bytes)) 101 | { 102 | write(new EndianBinaryWriter(stream), values); 103 | } 104 | Assert.True(bytes.SequenceEqual(input)); 105 | } 106 | 107 | #endregion 108 | } 109 | --------------------------------------------------------------------------------