├── .gitignore ├── KaitaiStream.cs ├── KaitaiStruct.cs ├── LICENSE.txt ├── README.md ├── icon.png └── kaitai_struct_runtime_csharp.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | # From https://github.com/github/gitignore/blob/491040e8/VisualStudio.gitignore 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | ## 6 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 7 | 8 | # User-specific files 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Mono auto generated files 19 | mono_crash.* 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | [Ww][Ii][Nn]32/ 29 | [Aa][Rr][Mm]/ 30 | [Aa][Rr][Mm]64/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # ASP.NET Scaffolding 68 | ScaffoldingReadMe.txt 69 | 70 | # StyleCop 71 | StyleCopReport.xml 72 | 73 | # Files built by Visual Studio 74 | *_i.c 75 | *_p.c 76 | *_h.h 77 | *.ilk 78 | *.meta 79 | *.obj 80 | *.iobj 81 | *.pch 82 | *.pdb 83 | *.ipdb 84 | *.pgc 85 | *.pgd 86 | *.rsp 87 | *.sbr 88 | *.tlb 89 | *.tli 90 | *.tlh 91 | *.tmp 92 | *.tmp_proj 93 | *_wpftmp.csproj 94 | *.log 95 | *.tlog 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 300 | *.vbp 301 | 302 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 303 | *.dsw 304 | *.dsp 305 | 306 | # Visual Studio 6 technical files 307 | *.ncb 308 | *.aps 309 | 310 | # Visual Studio LightSwitch build output 311 | **/*.HTMLClient/GeneratedArtifacts 312 | **/*.DesktopClient/GeneratedArtifacts 313 | **/*.DesktopClient/ModelManifest.xml 314 | **/*.Server/GeneratedArtifacts 315 | **/*.Server/ModelManifest.xml 316 | _Pvt_Extensions 317 | 318 | # Paket dependency manager 319 | .paket/paket.exe 320 | paket-files/ 321 | 322 | # FAKE - F# Make 323 | .fake/ 324 | 325 | # CodeRush personal settings 326 | .cr/personal 327 | 328 | # Python Tools for Visual Studio (PTVS) 329 | __pycache__/ 330 | *.pyc 331 | 332 | # Cake - Uncomment if you are using it 333 | # tools/** 334 | # !tools/packages.config 335 | 336 | # Tabs Studio 337 | *.tss 338 | 339 | # Telerik's JustMock configuration file 340 | *.jmconfig 341 | 342 | # BizTalk build output 343 | *.btp.cs 344 | *.btm.cs 345 | *.odx.cs 346 | *.xsd.cs 347 | 348 | # OpenCover UI analysis results 349 | OpenCover/ 350 | 351 | # Azure Stream Analytics local run output 352 | ASALocalRun/ 353 | 354 | # MSBuild Binary and Structured Log 355 | *.binlog 356 | 357 | # NVidia Nsight GPU debugger configuration file 358 | *.nvuser 359 | 360 | # MFractors (Xamarin productivity tool) working folder 361 | .mfractor/ 362 | 363 | # Local History for Visual Studio 364 | .localhistory/ 365 | 366 | # Visual Studio History (VSHistory) files 367 | .vshistory/ 368 | 369 | # BeatPulse healthcheck temp database 370 | healthchecksdb 371 | 372 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 373 | MigrationBackup/ 374 | 375 | # Ionide (cross platform F# VS Code tools) working folder 376 | .ionide/ 377 | 378 | # Fody - auto-generated XML schema 379 | FodyWeavers.xsd 380 | 381 | # VS Code files for those working on multiple tools 382 | .vscode/* 383 | !.vscode/settings.json 384 | !.vscode/tasks.json 385 | !.vscode/launch.json 386 | !.vscode/extensions.json 387 | *.code-workspace 388 | 389 | # Local History for Visual Studio Code 390 | .history/ 391 | 392 | # Windows Installer files from build outputs 393 | *.cab 394 | *.msi 395 | *.msix 396 | *.msm 397 | *.msp 398 | 399 | # JetBrains Rider 400 | *.sln.iml 401 | -------------------------------------------------------------------------------- /KaitaiStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using System.Globalization; 7 | 8 | namespace Kaitai 9 | { 10 | /// 11 | /// The base Kaitai stream which exposes an API for the Kaitai Struct framework. 12 | /// It's based off a BinaryReader, which is a little-endian reader. 13 | /// 14 | public partial class KaitaiStream : BinaryReader 15 | { 16 | #region Constructors 17 | 18 | public KaitaiStream(Stream stream) : base(stream) 19 | { 20 | } 21 | 22 | /// 23 | /// Creates a KaitaiStream backed by a file (RO) 24 | /// 25 | public KaitaiStream(string file) : base(File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) 26 | { 27 | } 28 | 29 | /// 30 | ///Creates a KaitaiStream backed by a byte buffer 31 | /// 32 | public KaitaiStream(byte[] bytes) : base(new MemoryStream(bytes)) 33 | { 34 | } 35 | 36 | private int BitsLeft = 0; 37 | private ulong Bits = 0; 38 | 39 | static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; 40 | 41 | #endregion 42 | 43 | #region Stream positioning 44 | 45 | /// 46 | /// Check if the stream position is at the end of the stream 47 | /// 48 | public bool IsEof 49 | { 50 | get { return BaseStream.Position >= BaseStream.Length && BitsLeft == 0; } 51 | } 52 | 53 | /// 54 | /// Seek to a specific position from the beginning of the stream 55 | /// 56 | /// The position to seek to 57 | public void Seek(long position) 58 | { 59 | BaseStream.Seek(position, SeekOrigin.Begin); 60 | } 61 | 62 | /// 63 | /// Get the current position in the stream 64 | /// 65 | public long Pos 66 | { 67 | get { return BaseStream.Position; } 68 | } 69 | 70 | /// 71 | /// Get the total length of the stream (ie. file size) 72 | /// 73 | public long Size 74 | { 75 | get { return BaseStream.Length; } 76 | } 77 | 78 | #endregion 79 | 80 | #region Integer types 81 | 82 | #region Signed 83 | 84 | /// 85 | /// Read a signed byte from the stream 86 | /// 87 | /// 88 | public sbyte ReadS1() 89 | { 90 | return ReadSByte(); 91 | } 92 | 93 | #region Big-endian 94 | 95 | /// 96 | /// Read a signed short from the stream (big endian) 97 | /// 98 | /// 99 | public short ReadS2be() 100 | { 101 | return BitConverter.ToInt16(ReadBytesNormalisedBigEndian(2), 0); 102 | } 103 | 104 | /// 105 | /// Read a signed int from the stream (big endian) 106 | /// 107 | /// 108 | public int ReadS4be() 109 | { 110 | return BitConverter.ToInt32(ReadBytesNormalisedBigEndian(4), 0); 111 | } 112 | 113 | /// 114 | /// Read a signed long from the stream (big endian) 115 | /// 116 | /// 117 | public long ReadS8be() 118 | { 119 | return BitConverter.ToInt64(ReadBytesNormalisedBigEndian(8), 0); 120 | } 121 | 122 | #endregion 123 | 124 | #region Little-endian 125 | 126 | /// 127 | /// Read a signed short from the stream (little endian) 128 | /// 129 | /// 130 | public short ReadS2le() 131 | { 132 | return BitConverter.ToInt16(ReadBytesNormalisedLittleEndian(2), 0); 133 | } 134 | 135 | /// 136 | /// Read a signed int from the stream (little endian) 137 | /// 138 | /// 139 | public int ReadS4le() 140 | { 141 | return BitConverter.ToInt32(ReadBytesNormalisedLittleEndian(4), 0); 142 | } 143 | 144 | /// 145 | /// Read a signed long from the stream (little endian) 146 | /// 147 | /// 148 | public long ReadS8le() 149 | { 150 | return BitConverter.ToInt64(ReadBytesNormalisedLittleEndian(8), 0); 151 | } 152 | 153 | #endregion 154 | 155 | #endregion 156 | 157 | #region Unsigned 158 | 159 | /// 160 | /// Read an unsigned byte from the stream 161 | /// 162 | /// 163 | public byte ReadU1() 164 | { 165 | return ReadByte(); 166 | } 167 | 168 | #region Big-endian 169 | 170 | /// 171 | /// Read an unsigned short from the stream (big endian) 172 | /// 173 | /// 174 | public ushort ReadU2be() 175 | { 176 | return BitConverter.ToUInt16(ReadBytesNormalisedBigEndian(2), 0); 177 | } 178 | 179 | /// 180 | /// Read an unsigned int from the stream (big endian) 181 | /// 182 | /// 183 | public uint ReadU4be() 184 | { 185 | return BitConverter.ToUInt32(ReadBytesNormalisedBigEndian(4), 0); 186 | } 187 | 188 | /// 189 | /// Read an unsigned long from the stream (big endian) 190 | /// 191 | /// 192 | public ulong ReadU8be() 193 | { 194 | return BitConverter.ToUInt64(ReadBytesNormalisedBigEndian(8), 0); 195 | } 196 | 197 | #endregion 198 | 199 | #region Little-endian 200 | 201 | /// 202 | /// Read an unsigned short from the stream (little endian) 203 | /// 204 | /// 205 | public ushort ReadU2le() 206 | { 207 | return BitConverter.ToUInt16(ReadBytesNormalisedLittleEndian(2), 0); 208 | } 209 | 210 | /// 211 | /// Read an unsigned int from the stream (little endian) 212 | /// 213 | /// 214 | public uint ReadU4le() 215 | { 216 | return BitConverter.ToUInt32(ReadBytesNormalisedLittleEndian(4), 0); 217 | } 218 | 219 | /// 220 | /// Read an unsigned long from the stream (little endian) 221 | /// 222 | /// 223 | public ulong ReadU8le() 224 | { 225 | return BitConverter.ToUInt64(ReadBytesNormalisedLittleEndian(8), 0); 226 | } 227 | 228 | #endregion 229 | 230 | #endregion 231 | 232 | #endregion 233 | 234 | #region Floating point types 235 | 236 | #region Big-endian 237 | 238 | /// 239 | /// Read a single-precision floating point value from the stream (big endian) 240 | /// 241 | /// 242 | public float ReadF4be() 243 | { 244 | return BitConverter.ToSingle(ReadBytesNormalisedBigEndian(4), 0); 245 | } 246 | 247 | /// 248 | /// Read a double-precision floating point value from the stream (big endian) 249 | /// 250 | /// 251 | public double ReadF8be() 252 | { 253 | return BitConverter.ToDouble(ReadBytesNormalisedBigEndian(8), 0); 254 | } 255 | 256 | #endregion 257 | 258 | #region Little-endian 259 | 260 | /// 261 | /// Read a single-precision floating point value from the stream (little endian) 262 | /// 263 | /// 264 | public float ReadF4le() 265 | { 266 | return BitConverter.ToSingle(ReadBytesNormalisedLittleEndian(4), 0); 267 | } 268 | 269 | /// 270 | /// Read a double-precision floating point value from the stream (little endian) 271 | /// 272 | /// 273 | public double ReadF8le() 274 | { 275 | return BitConverter.ToDouble(ReadBytesNormalisedLittleEndian(8), 0); 276 | } 277 | 278 | #endregion 279 | 280 | #endregion 281 | 282 | #region Unaligned bit values 283 | 284 | public void AlignToByte() 285 | { 286 | BitsLeft = 0; 287 | Bits = 0; 288 | } 289 | 290 | /// 291 | /// Read a n-bit integer in a big-endian manner from the stream 292 | /// 293 | /// 294 | public ulong ReadBitsIntBe(int n) 295 | { 296 | ulong res = 0; 297 | 298 | int bitsNeeded = n - BitsLeft; 299 | BitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8` 300 | 301 | if (bitsNeeded > 0) 302 | { 303 | // 1 bit => 1 byte 304 | // 8 bits => 1 byte 305 | // 9 bits => 2 bytes 306 | int bytesNeeded = ((bitsNeeded - 1) / 8) + 1; // `ceil(bitsNeeded / 8)` 307 | byte[] buf = ReadBytes(bytesNeeded); 308 | for (int i = 0; i < bytesNeeded; i++) 309 | { 310 | res = res << 8 | buf[i]; 311 | } 312 | 313 | ulong newBits = res; 314 | res = res >> BitsLeft | Bits << bitsNeeded; 315 | Bits = newBits; // will be masked at the end of the function 316 | } 317 | else 318 | { 319 | res = Bits >> -bitsNeeded; // shift unneeded bits out 320 | } 321 | 322 | ulong mask = (1UL << BitsLeft) - 1; // `BitsLeft` is in range 0..7, so `(1UL << 64)` does not have to be considered 323 | Bits &= mask; 324 | 325 | return res; 326 | } 327 | 328 | [Obsolete("use ReadBitsIntBe instead")] 329 | public ulong ReadBitsInt(int n) 330 | { 331 | return ReadBitsIntBe(n); 332 | } 333 | 334 | /// 335 | /// Read a n-bit integer in a little-endian manner from the stream 336 | /// 337 | /// 338 | public ulong ReadBitsIntLe(int n) 339 | { 340 | ulong res = 0; 341 | int bitsNeeded = n - BitsLeft; 342 | 343 | if (bitsNeeded > 0) 344 | { 345 | // 1 bit => 1 byte 346 | // 8 bits => 1 byte 347 | // 9 bits => 2 bytes 348 | int bytesNeeded = ((bitsNeeded - 1) / 8) + 1; // `ceil(bitsNeeded / 8)` 349 | byte[] buf = ReadBytes(bytesNeeded); 350 | for (int i = 0; i < bytesNeeded; i++) 351 | { 352 | res |= ((ulong)buf[i]) << (i * 8); 353 | } 354 | 355 | // NB: in C#, bit shift operators on left-hand operand of type `ulong` work 356 | // as if the right-hand operand were subjected to `& 63` (`& 0b11_1111`) (see 357 | // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#shift-count-of-the-shift-operators), 358 | // so `res >> 64` is equivalent to `res >> 0` (but we don't want that) 359 | ulong newBits = bitsNeeded < 64 ? res >> bitsNeeded : 0; 360 | res = res << BitsLeft | Bits; 361 | Bits = newBits; 362 | } 363 | else 364 | { 365 | res = Bits; 366 | Bits >>= n; 367 | } 368 | 369 | BitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8` 370 | 371 | if (n < 64) 372 | { 373 | ulong mask = (1UL << n) - 1; 374 | res &= mask; 375 | } 376 | // if `n == 64`, do nothing 377 | return res; 378 | } 379 | 380 | #endregion 381 | 382 | #region Byte arrays 383 | 384 | /// 385 | /// Read a fixed number of bytes from the stream 386 | /// 387 | /// The number of bytes to read 388 | /// 389 | public byte[] ReadBytes(long count) 390 | { 391 | if (count < 0 || count > Int32.MaxValue) 392 | throw new ArgumentOutOfRangeException("requested " + count + " bytes, while only non-negative int32 amount of bytes possible"); 393 | byte[] bytes = base.ReadBytes((int) count); 394 | if (bytes.Length < count) 395 | throw new EndOfStreamException("requested " + count + " bytes, but got only " + bytes.Length + " bytes"); 396 | return bytes; 397 | } 398 | 399 | /// 400 | /// Read a fixed number of bytes from the stream 401 | /// 402 | /// The number of bytes to read 403 | /// 404 | public byte[] ReadBytes(ulong count) 405 | { 406 | if (count > Int32.MaxValue) 407 | throw new ArgumentOutOfRangeException("requested " + count + " bytes, while only non-negative int32 amount of bytes possible"); 408 | byte[] bytes = base.ReadBytes((int)count); 409 | if (bytes.Length < (int)count) 410 | throw new EndOfStreamException("requested " + count + " bytes, but got only " + bytes.Length + " bytes"); 411 | return bytes; 412 | } 413 | 414 | /// 415 | /// Read bytes from the stream in little endian format and convert them to the endianness of the current platform 416 | /// 417 | /// The number of bytes to read 418 | /// An array of bytes that matches the endianness of the current platform 419 | protected byte[] ReadBytesNormalisedLittleEndian(int count) 420 | { 421 | byte[] bytes = ReadBytes(count); 422 | if (!IsLittleEndian) Array.Reverse(bytes); 423 | return bytes; 424 | } 425 | 426 | /// 427 | /// Read bytes from the stream in big endian format and convert them to the endianness of the current platform 428 | /// 429 | /// The number of bytes to read 430 | /// An array of bytes that matches the endianness of the current platform 431 | protected byte[] ReadBytesNormalisedBigEndian(int count) 432 | { 433 | byte[] bytes = ReadBytes(count); 434 | if (IsLittleEndian) Array.Reverse(bytes); 435 | return bytes; 436 | } 437 | 438 | /// 439 | /// Read all the remaining bytes from the stream until the end is reached 440 | /// 441 | /// 442 | public byte[] ReadBytesFull() 443 | { 444 | return ReadBytes(BaseStream.Length - BaseStream.Position); 445 | } 446 | 447 | /// 448 | /// Read a terminated string from the stream 449 | /// 450 | /// The string terminator value 451 | /// True to include the terminator in the returned string 452 | /// True to consume the terminator byte before returning 453 | /// True to throw an error when the EOS was reached before the terminator 454 | /// 455 | public byte[] ReadBytesTerm(byte term, bool includeTerm, bool consumeTerm, bool eosError) 456 | { 457 | // TODO: check if `System.IO.MemoryStream` would be a better choice than `List` 458 | List bytes = new List(); 459 | while (true) 460 | { 461 | if (IsEof) 462 | { 463 | if (eosError) throw new EndOfStreamException(string.Format("End of stream reached, but no terminator `{0}` found", term)); 464 | break; 465 | } 466 | 467 | byte b = ReadByte(); 468 | if (b == term) 469 | { 470 | if (includeTerm) bytes.Add(b); 471 | if (!consumeTerm) Seek(Pos - 1); 472 | break; 473 | } 474 | bytes.Add(b); 475 | } 476 | return bytes.ToArray(); 477 | } 478 | 479 | public byte[] ReadBytesTermMulti(byte[] term, bool includeTerm, bool consumeTerm, bool eosError) 480 | { 481 | int unitSize = term.Length; 482 | // TODO: check if `System.IO.MemoryStream` would be a better choice than `List` 483 | List bytes = new List(); 484 | while (true) 485 | { 486 | byte[] c = base.ReadBytes(unitSize); 487 | if (c.Length < unitSize) 488 | { 489 | if (eosError) throw new EndOfStreamException(string.Format("End of stream reached, but no terminator `{0}` found", term)); 490 | 491 | bytes.AddRange(c); 492 | break; 493 | } 494 | if (ByteArrayCompare(c, term) == 0) 495 | { 496 | if (includeTerm) bytes.AddRange(c); 497 | if (!consumeTerm) Seek(Pos - unitSize); 498 | break; 499 | } 500 | bytes.AddRange(c); 501 | } 502 | return bytes.ToArray(); 503 | } 504 | 505 | /// 506 | /// Read a specific set of bytes and assert that they are the same as an expected result 507 | /// 508 | /// The expected result 509 | /// 510 | [Obsolete("use explicit \"if\" using ByteArrayCompare method instead")] 511 | public byte[] EnsureFixedContents(byte[] expected) 512 | { 513 | byte[] bytes = ReadBytes(expected.Length); 514 | 515 | if (bytes.Length != expected.Length) 516 | { 517 | throw new Exception(string.Format("Expected bytes: {0} ({1} bytes), Instead got: {2} ({3} bytes)", Convert.ToBase64String(expected), expected.Length, Convert.ToBase64String(bytes), bytes.Length)); 518 | } 519 | for (int i = 0; i < bytes.Length; i++) 520 | { 521 | if (bytes[i] != expected[i]) 522 | { 523 | throw new Exception(string.Format("Expected bytes: {0} ({1} bytes), Instead got: {2} ({3} bytes)", Convert.ToBase64String(expected), expected.Length, Convert.ToBase64String(bytes), bytes.Length)); 524 | } 525 | } 526 | 527 | return bytes; 528 | } 529 | 530 | public static byte[] BytesStripRight(byte[] src, byte padByte) 531 | { 532 | int newLen = src.Length; 533 | while (newLen > 0 && src[newLen - 1] == padByte) 534 | newLen--; 535 | 536 | byte[] dst = new byte[newLen]; 537 | Array.Copy(src, dst, newLen); 538 | return dst; 539 | } 540 | 541 | public static byte[] BytesTerminate(byte[] src, byte term, bool includeTerm) 542 | { 543 | int newLen = 0; 544 | int maxLen = src.Length; 545 | 546 | while (newLen < maxLen && src[newLen] != term) 547 | newLen++; 548 | 549 | if (includeTerm && newLen < maxLen) 550 | newLen++; 551 | 552 | byte[] dst = new byte[newLen]; 553 | Array.Copy(src, dst, newLen); 554 | return dst; 555 | } 556 | 557 | public static byte[] BytesTerminateMulti(byte[] src, byte[] term, bool includeTerm) 558 | { 559 | int unitSize = term.Length; 560 | if (unitSize == 0) { 561 | return new byte[0]; 562 | } 563 | int newLen = src.Length; 564 | int iTerm = 0; 565 | for (int iSrc = 0; iSrc < src.Length;) { 566 | if (src[iSrc] != term[iTerm]) { 567 | iSrc += unitSize - iTerm; 568 | iTerm = 0; 569 | continue; 570 | } 571 | iSrc++; 572 | iTerm++; 573 | if (iTerm == unitSize) { 574 | newLen = iSrc - (includeTerm ? 0 : unitSize); 575 | break; 576 | } 577 | } 578 | byte[] dst = new byte[newLen]; 579 | Array.Copy(src, dst, newLen); 580 | return dst; 581 | } 582 | 583 | #endregion 584 | 585 | #region Byte array processing 586 | 587 | /// 588 | /// Performs XOR processing with given data, XORing every byte of the input with a single value. 589 | /// 590 | /// The data toe process 591 | /// The key value to XOR with 592 | /// Processed data 593 | public byte[] ProcessXor(byte[] value, int key) 594 | { 595 | byte[] result = new byte[value.Length]; 596 | for (int i = 0; i < value.Length; i++) 597 | { 598 | result[i] = (byte)(value[i] ^ key); 599 | } 600 | return result; 601 | } 602 | 603 | /// 604 | /// Performs XOR processing with given data, XORing every byte of the input with a key 605 | /// array, repeating from the beginning of the key array if necessary 606 | /// 607 | /// The data toe process 608 | /// The key array to XOR with 609 | /// Processed data 610 | public byte[] ProcessXor(byte[] value, byte[] key) 611 | { 612 | int keyLen = key.Length; 613 | byte[] result = new byte[value.Length]; 614 | for (int i = 0, j = 0; i < value.Length; i++, j = (j + 1) % keyLen) 615 | { 616 | result[i] = (byte)(value[i] ^ key[j]); 617 | } 618 | return result; 619 | } 620 | 621 | /// 622 | /// Performs a circular left rotation shift for a given buffer by a given amount of bits. 623 | /// Pass a negative amount to rotate right. 624 | /// 625 | /// The data to rotate 626 | /// The number of bytes to rotate by 627 | /// 628 | /// 629 | public byte[] ProcessRotateLeft(byte[] data, int amount, int groupSize) 630 | { 631 | if (amount > 7 || amount < -7) throw new ArgumentException("Rotation of more than 7 cannot be performed.", "amount"); 632 | if (amount < 0) amount += 8; // Rotation of -2 is the same as rotation of +6 633 | 634 | byte[] r = new byte[data.Length]; 635 | switch (groupSize) 636 | { 637 | case 1: 638 | for (int i = 0; i < data.Length; i++) 639 | { 640 | byte bits = data[i]; 641 | // https://stackoverflow.com/a/812039 642 | r[i] = (byte) ((bits << amount) | (bits >> (8 - amount))); 643 | } 644 | break; 645 | default: 646 | throw new NotImplementedException(string.Format("Unable to rotate a group of {0} bytes yet", groupSize)); 647 | } 648 | return r; 649 | } 650 | 651 | /// 652 | /// Inflates a deflated zlib byte stream 653 | /// 654 | /// The data to deflate 655 | /// The deflated result 656 | public byte[] ProcessZlib(byte[] data) 657 | { 658 | // See RFC 1950 (https://tools.ietf.org/html/rfc1950) 659 | // zlib adds a header to DEFLATE streams - usually 2 bytes, 660 | // but can be 6 bytes if FDICT is set. 661 | // There's also 4 checksum bytes at the end of the stream. 662 | 663 | byte zlibCmf = data[0]; 664 | if ((zlibCmf & 0x0F) != 0x08) throw new NotSupportedException("Only the DEFLATE algorithm is supported for zlib data."); 665 | 666 | const int zlibFooter = 4; 667 | int zlibHeader = 2; 668 | 669 | // If the FDICT bit (0x20) is 1, then the 4-byte dictionary is included in the header, we need to skip it 670 | byte zlibFlg = data[1]; 671 | if ((zlibFlg & 0x20) == 0x20) zlibHeader += 4; 672 | 673 | using (MemoryStream ms = new MemoryStream(data, zlibHeader, data.Length - (zlibHeader + zlibFooter))) 674 | { 675 | using (DeflateStream ds = new DeflateStream(ms, CompressionMode.Decompress)) 676 | { 677 | using (MemoryStream target = new MemoryStream()) 678 | { 679 | ds.CopyTo(target); 680 | return target.ToArray(); 681 | } 682 | } 683 | } 684 | } 685 | 686 | #endregion 687 | 688 | #region Misc utility methods 689 | 690 | /// 691 | /// Performs modulo operation between two integers. 692 | /// 693 | /// 694 | /// This method is required because C# lacks a "true" modulo 695 | /// operator, the % operator rather being the "remainder" 696 | /// operator. We want mod operations to always be positive. 697 | /// 698 | /// The value to be divided 699 | /// The value to divide by. Must be greater than zero. 700 | /// The result of the modulo opertion. Will always be positive. 701 | public static int Mod(int a, int b) 702 | { 703 | if (b <= 0) throw new ArgumentException("Divisor of mod operation must be greater than zero.", "b"); 704 | int r = a % b; 705 | if (r < 0) r += b; 706 | return r; 707 | } 708 | 709 | /// 710 | /// Performs modulo operation between two integers. 711 | /// 712 | /// 713 | /// This method is required because C# lacks a "true" modulo 714 | /// operator, the % operator rather being the "remainder" 715 | /// operator. We want mod operations to always be positive. 716 | /// 717 | /// The value to be divided 718 | /// The value to divide by. Must be greater than zero. 719 | /// The result of the modulo opertion. Will always be positive. 720 | public static long Mod(long a, long b) 721 | { 722 | if (b <= 0) throw new ArgumentException("Divisor of mod operation must be greater than zero.", "b"); 723 | long r = a % b; 724 | if (r < 0) r += b; 725 | return r; 726 | } 727 | 728 | /// 729 | /// Compares two byte arrays in lexicographical order. 730 | /// 731 | /// negative number if a is less than b, 0 if a is equal to b, positive number if a is greater than b. 732 | /// First byte array to compare 733 | /// Second byte array to compare. 734 | public static int ByteArrayCompare(byte[] a, byte[] b) 735 | { 736 | if (a == b) 737 | return 0; 738 | int al = a.Length; 739 | int bl = b.Length; 740 | int minLen = al < bl ? al : bl; 741 | for (int i = 0; i < minLen; i++) { 742 | int cmp = a[i] - b[i]; 743 | if (cmp != 0) 744 | return cmp; 745 | } 746 | 747 | // Reached the end of at least one of the arrays 748 | if (al == bl) { 749 | return 0; 750 | } else { 751 | return al - bl; 752 | } 753 | } 754 | 755 | /// 756 | /// Reverses the string, Unicode-aware. 757 | /// 758 | /// taken from here 759 | public static string StringReverse(string s) 760 | { 761 | TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(s); 762 | 763 | List elements = new List(); 764 | while (enumerator.MoveNext()) 765 | elements.Add(enumerator.GetTextElement()); 766 | 767 | elements.Reverse(); 768 | return string.Concat(elements); 769 | } 770 | 771 | #endregion 772 | } 773 | } 774 | -------------------------------------------------------------------------------- /KaitaiStruct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Kaitai 5 | { 6 | public abstract class KaitaiStruct 7 | { 8 | protected KaitaiStream m_io; 9 | 10 | public KaitaiStream M_Io 11 | { 12 | get 13 | { 14 | return m_io; 15 | } 16 | } 17 | 18 | public KaitaiStruct(KaitaiStream io) 19 | { 20 | m_io = io; 21 | } 22 | } 23 | 24 | /// 25 | /// A custom decoder interface. Implementing classes can be called from 26 | /// inside a .ksy file using `process: XXX` syntax. 27 | /// 28 | public interface CustomDecoder 29 | { 30 | /// 31 | /// Decodes a given byte array, according to some custom algorithm 32 | /// (specific to implementing class) and parameters given in the 33 | /// constructor, returning another byte array. 34 | /// 35 | /// Source byte array. 36 | byte[] Decode(byte[] src); 37 | } 38 | 39 | /// 40 | /// Error that occurs when default endianness should be decided with a 41 | /// switch, but nothing matches (although using endianness expression 42 | /// implies that there should be some positive result). 43 | /// 44 | public class UndecidedEndiannessError : Exception { 45 | public UndecidedEndiannessError() 46 | : base("Unable to decide on endianness") 47 | { 48 | } 49 | public UndecidedEndiannessError(string msg) 50 | : base(msg) 51 | { 52 | } 53 | public UndecidedEndiannessError(string msg, Exception inner) 54 | : base(msg, inner) 55 | { 56 | } 57 | } 58 | 59 | /// 60 | /// Common ancestor for all error originating from Kaitai Struct usage. 61 | /// Stores KSY source path, pointing to an element supposedly guilty of 62 | /// an error. 63 | /// 64 | public class KaitaiStructError : Exception { 65 | public KaitaiStructError(string msg, string srcPath) 66 | : base(srcPath + ": " + msg) 67 | { 68 | this.srcPath = srcPath; 69 | } 70 | 71 | protected string srcPath; 72 | } 73 | 74 | /// 75 | /// Common ancestor for all validation failures. Stores pointer to 76 | /// KaitaiStream IO object which was involved in an error. 77 | /// 78 | public class ValidationFailedError : KaitaiStructError { 79 | public ValidationFailedError(string msg, KaitaiStream io, string srcPath) 80 | : base("at pos " + io.Pos + ": validation failed: " + msg, srcPath) 81 | { 82 | this.io = io; 83 | } 84 | 85 | protected KaitaiStream io; 86 | 87 | protected static string ByteArrayToHex(byte[] arr) { 88 | StringBuilder sb = new StringBuilder("["); 89 | for (int i = 0; i < arr.Length; i++) 90 | { 91 | if (i > 0) 92 | { 93 | sb.Append(' '); 94 | } 95 | sb.Append(string.Format("{0:X2}", arr[i])); 96 | } 97 | sb.Append(']'); 98 | return sb.ToString(); 99 | } 100 | } 101 | 102 | /// 103 | /// Signals validation failure: we required "actual" value to be equal to 104 | /// "expected", but it turned out that it's not. 105 | /// 106 | public class ValidationNotEqualError : ValidationFailedError { 107 | public ValidationNotEqualError(byte[] expected, byte[] actual, KaitaiStream io, string srcPath) 108 | : base("not equal, expected " + ByteArrayToHex(expected) + ", but got " + ByteArrayToHex(actual), io, srcPath) 109 | { 110 | this.expected = expected; 111 | this.actual = actual; 112 | } 113 | 114 | public ValidationNotEqualError(Object expected, Object actual, KaitaiStream io, string srcPath) 115 | : base("not equal, expected " + expected + ", but got " + actual, io, srcPath) 116 | { 117 | this.expected = expected; 118 | this.actual = actual; 119 | } 120 | 121 | protected Object expected; 122 | protected Object actual; 123 | } 124 | 125 | public class ValidationLessThanError : ValidationFailedError { 126 | public ValidationLessThanError(byte[] min, byte[] actual, KaitaiStream io, string srcPath) 127 | : base("not in range, min " + ByteArrayToHex(min) + ", but got " + ByteArrayToHex(actual), io, srcPath) 128 | { 129 | this.min = min; 130 | this.actual = actual; 131 | } 132 | 133 | public ValidationLessThanError(Object min, Object actual, KaitaiStream io, string srcPath) 134 | : base("not in range, min " + min + ", but got " + actual, io, srcPath) 135 | { 136 | this.min = min; 137 | this.actual = actual; 138 | } 139 | 140 | protected Object min; 141 | protected Object actual; 142 | } 143 | 144 | public class ValidationGreaterThanError : ValidationFailedError { 145 | public ValidationGreaterThanError(byte[] max, byte[] actual, KaitaiStream io, string srcPath) 146 | : base("not in range, max " + ByteArrayToHex(max) + ", but got " + ByteArrayToHex(actual), io, srcPath) 147 | { 148 | this.max = max; 149 | this.actual = actual; 150 | } 151 | 152 | public ValidationGreaterThanError(Object max, Object actual, KaitaiStream io, string srcPath) 153 | : base("not in range, max " + max + ", but got " + actual, io, srcPath) 154 | { 155 | this.max = max; 156 | this.actual = actual; 157 | } 158 | 159 | protected Object max; 160 | protected Object actual; 161 | } 162 | 163 | public class ValidationNotAnyOfError : ValidationFailedError { 164 | public ValidationNotAnyOfError(Object actual, KaitaiStream io, string srcPath) 165 | : base("not any of the list, got " + actual, io, srcPath) 166 | { 167 | this.actual = actual; 168 | } 169 | 170 | protected Object actual; 171 | } 172 | 173 | public class ValidationNotInEnumError : ValidationFailedError { 174 | public ValidationNotInEnumError(Object actual, KaitaiStream io, string srcPath) 175 | : base("not in the enum, got " + actual, io, srcPath) 176 | { 177 | this.actual = actual; 178 | } 179 | 180 | protected Object actual; 181 | } 182 | 183 | public class ValidationExprError : ValidationFailedError { 184 | public ValidationExprError(Object actual, KaitaiStream io, string srcPath) 185 | : base("not matching the expression, got " + actual, io, srcPath) 186 | { 187 | this.actual = actual; 188 | } 189 | 190 | protected Object actual; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016-2024 Kaitai Project: MIT license 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kaitai Struct: runtime library for C# / .NET 2 | 3 | [![NuGet version](https://img.shields.io/nuget/v/KaitaiStruct.Runtime.CSharp)](https://www.nuget.org/packages/KaitaiStruct.Runtime.CSharp/) 4 | [![NuGet downloads](https://img.shields.io/nuget/dt/KaitaiStruct.Runtime.CSharp)](https://www.nuget.org/packages/KaitaiStruct.Runtime.CSharp/#:~:text=Total) 5 | 6 | This library implements Kaitai Struct API for C#. 7 | 8 | Kaitai Struct is a declarative language used for describe various binary 9 | data structures, laid out in files or in memory: i.e. binary file 10 | formats, network stream packet formats, etc. 11 | 12 | Further reading: 13 | 14 | * [About Kaitai Struct](https://kaitai.io/) 15 | * [About API implemented in this library](https://doc.kaitai.io/stream_api.html) 16 | 17 | ## Licensing 18 | 19 | Copyright 2016-2024 Kaitai Project: MIT license 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining 22 | a copy of this software and associated documentation files (the 23 | "Software"), to deal in the Software without restriction, including 24 | without limitation the rights to use, copy, modify, merge, publish, 25 | distribute, sublicense, and/or sell copies of the Software, and to 26 | permit persons to whom the Software is furnished to do so, subject to 27 | the following conditions: 28 | 29 | The above copyright notice and this permission notice shall be 30 | included in all copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 34 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 35 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 36 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 37 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 38 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaitai-io/kaitai_struct_csharp_runtime/b41a9d0aad6af136beb81c3eeed3783a18746288/icon.png -------------------------------------------------------------------------------- /kaitai_struct_runtime_csharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 1701;1702;CS1591 7 | 8 | 9 | true 10 | 11 | 12 | true 13 | 14 | 15 | true 16 | snupkg 17 | 18 | 19 | 20 | KaitaiStruct.Runtime.CSharp 21 | 22 | Kaitai Project 23 | This library implements Kaitai Struct API for C#. 24 | Kaitai Struct Runtime 25 | Copyright © Kaitai Project 2016-2024 26 | Kaitai.Struct.Runtime 27 | Kaitai 28 | https://kaitai.io/ 29 | https://github.com/kaitai-io/kaitai_struct_csharp_runtime 30 | Kaitai Struct File-Format Binary Protocols 31 | MIT 32 | icon.png 33 | 34 | 0.10.0.0 35 | 36 | Update to version 0.10 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | --------------------------------------------------------------------------------