Inspect and edit a complete binary file
Advanced · C#. Validate signature, version, count, and file length in application code. Then supply COUNT and update one fixed field. The example creates and removes its own temporary fixture file.
Run this example
Prerequisites: the repository's .NET 10 SDK and a checkout of this source. Run from the repository root:
dotnet run --project docs/examples/CStructSharp.Docs.Examples.csproj -c Release -- edit-file
The runner checks 435301020100100200A5; truncated and excessive-count fixtures rejected. Success includes PASS edit-file.
This example uses the C# API. Browser capabilities and result shapes are described in the browser guide.
Complete program
The layout, options, input bytes, helper methods, and required types are all included. To adapt it outside the repository, create a .NET 10 console project, add CStructSharp, and replace Program.cs with this complete file. These examples follow the source version; use a matching package when testing a release.
Download the complete C# source.
// Generated from executable documentation examples. Edit the source region, then regenerate.
using System;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Buffers;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using CStructSharp;
using CStructSharp.Codecs;
using CStructSharp.Diagnostics;
using CStructSharp.Introspection;
using CStructSharp.Values;
internal static partial class Program
{
public static void Main()
{
EditFile();
Console.WriteLine("PASS edit-file");
}
private static void EditFile()
{
// Teaching format: signature C S, version 1, count, then count records (uint16 id, uint8 flags).
byte[] fixture = [0x43, 0x53, 1, 2, 1, 0, 0x10, 2, 0, 0x20];
string file = Path.Combine(Path.GetTempPath(), "cstructsharp-example-" + Guid.NewGuid().ToString("N") + ".bin");
try
{
File.WriteAllBytes(file, fixture);
byte[] input = File.ReadAllBytes(file);
var headerLayout = new CStruct("struct header { uint8 signature[2]; uint8 version; uint8 count; };");
var recordsLayout = new CStruct("struct record { uint16 id; uint8 flags; }; struct data { record records[COUNT]; };");
byte[] Patch(byte[] data)
{
if (data.Length < 4 || data[0] != 0x43 || data[1] != 0x53 || data[2] != 1)
{
throw new InvalidDataException("Expected a CS file, version 1, with a complete header.");
}
int count = (byte)headerLayout.ReadValue(data.AsSpan(), "header.count")!;
if (count is < 2 or > 32 || data.Length != 4 + count * 3)
{
throw new InvalidDataException("Expected 2 through 32 complete records and no trailing data.");
}
var variables = new Dictionary<string, int> { ["COUNT"] = count };
using var stream = new MemoryStream((byte[])data.Clone());
stream.Position = 4;
Equal((ushort)2, (ushort)recordsLayout.ReadValue(stream, "data.records[1].id", variables)!);
stream.Position = 4;
recordsLayout.Update(stream, "data.records[1].flags", 0xA5, variables);
return stream.ToArray();
}
byte[] output = Patch(input);
SequenceEqual([0x43, 0x53, 1, 2, 1, 0, 0x10, 2, 0, 0xA5], output);
SequenceEqual(input[..9], output[..9]);
Throws<InvalidDataException>(() => Patch(input[..^1]));
byte[] excessive = (byte[])input.Clone();
excessive[3] = 255;
Throws<InvalidDataException>(() => Patch(excessive));
Console.WriteLine(Convert.ToHexString(output));
}
finally
{
File.Delete(file);
}
}
private static void Equal<T>(T expected, T actual)
{
if (!EqualityComparer<T>.Default.Equals(expected, actual))
{
throw new InvalidOperationException($"Expected '{expected}', received '{actual}'.");
}
}
private static void SequenceEqual(byte[] expected, byte[] actual)
{
if (!expected.AsSpan().SequenceEqual(actual))
{
throw new InvalidOperationException(
$"Expected {Convert.ToHexString(expected)}, received {Convert.ToHexString(actual)}.");
}
}
private static void Throws<TException>(Action action)
where TException : Exception
{
try
{
action();
}
catch (TException)
{
return;
}
throw new InvalidOperationException($"Expected {typeof(TException).Name}.");
}
}
Try it and diagnose mistakes
Change the record count to 255 without changing file length.
Answer: Application validation rejects it before traversal. Patching cannot insert more records or move following data. The program contains assertions for its original inputs. When changing an input intentionally, update the expected assertion too; an unchanged assertion is not evidence that the new value is wrong.
Continue with the related guide or choose another recipe.