├── .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 | [](https://www.nuget.org/packages/KaitaiStruct.Runtime.CSharp/)
4 | [](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 |
--------------------------------------------------------------------------------