/*
oh4_lbp_serializer.h - v0.1 - public domain
Authored 2026 by Eric Scrivner
no warranty implied; use at your own risk
Before including,
#define OH4_LBP_SERIALIZER_IMPLEMENTATION
in the file that you want to have the implementation.
ABOUT:
Provides simple, versioned, little-endian binary serialization in the
style of Media Molecule / Little Big Planet. Multi-byte values are read
and written as native little-endian host data; big-endian hosts are not
supported.
See: https://handmade.network/p/29/swedish-cubes-for-unity/blog/p/2723-how_media_molecule_does_serialization
WORKFLOW:
Define one serialize function per type. The same function handles both
reading and writing. Fields are gated by a single monolithic
`DataVersion`.
LBPS_Serializer s = LBPS_Measure(CurrentVersion);
MyTypeSerialize(&s, &value);
required = LBPS_BytesRequired(&s);
LBPS_Serializer w = LBPS_Write(buffer, required, CurrentVersion);
MyTypeSerialize(&w, &value);
LBPS_Serializer r = LBPS_Read(buffer, used, CurrentVersion);
r.DataVersion = file_version;
MyTypeSerialize(&r, &value);
For bounded chunks and backpatched container headers:
LBPS_PatchSite size_site = LBPS_ReserveU32(&w);
LBPS_SIZE chunk_begin = LBPS_BytesUsed(&w);
WriteChunkPayload(&w);
LBPS_PatchU32(&w, size_site, (LBPS_U32)(LBPS_BytesUsed(&w) - chunk_begin));
LBPS_Serializer chunk = LBPS_Chunk(&r, chunk_size);
ReadChunkPayload(&chunk);
LBPS_Finish(&chunk, kLBPS_Finish_Exact);
MODES:
Read consumes bytes from a caller-provided buffer
Write writes bytes into a caller-provided buffer
Measure computes the required byte count without touching memory
I/O:
By default, LBPS works on caller-provided memory buffers. For larger
files or custom storage backends, LBPS also supports stream serializers
through user-provided absolute-offset callbacks:
LBPS_IOReadAtFn
LBPS_IOWriteAtFn
LBPS_IOFlushFn
LBPS_StreamRead(...)
LBPS_StreamWrite(...)
LBPS_Flush(...)
The stream API is intentionally generic and does not depend on stdio or
any platform-specific file layer. Callers provide the read/write/flush
callbacks that make sense for their runtime. Stream callbacks operate on
absolute byte offsets and may be backed by files, mapped memory, archive
subranges, or any other random-access storage.
FIELD MACROS:
LBPS_ADD(s, datum, version_added, TypeTag, FieldName)
LBPS_ADD_BYTES(s, datum, version_added, FieldName, ByteCount)
LBPS_REM(s, datum, version_added, version_removed, TypeTag, FieldName, DefaultValue)
`LBPS_ADD` serializes a field that exists in the current runtime struct.
`LBPS_ADD_BYTES` serializes a field as raw bytes with an explicit byte count.
`LBPS_REM` materializes a temporary local for an older field that no
longer exists in the runtime struct, allowing upgrade logic after the
field list has been visited.
CONTAINER HELPERS:
LBPS_BytesRemaining(s)
LBPS_Finish(s, FinishMode)
LBPS_Chunk(parent, Size)
`LBPS_Chunk` creates a bounded child serializer over `Size` bytes while
advancing the parent stream by that same amount. This is useful for
size-delimited chunks and optional trailing sections. In Measure mode,
bounded chunks are validated when you call `LBPS_Finish` on the child.
BACKPATCH HELPERS:
LBPS_Reserve(s, Size)
LBPS_ReserveU32(s)
LBPS_PatchBytes(s, Site, Data, Size)
LBPS_PatchU32(s, Site, Value)
Reserve helpers advance the stream and hand back a patch site that can be
filled later once the final size, offset, or checksum is known.
TYPE TAGS:
Built-in tags:
U8 S8 U16 S16 U32 S32 F32
U64 S64 F64 (unless LBPS_NO_64_BIT is defined)
Custom tags can be added by defining:
#define LBPS_TYPE_MyTag MyType
#define LBPS_SERIALIZE_MyTag MyTypeSerialize
OVERRIDES:
#define LBPS_TYPES_ALREADY_DEFINED
Provide your own LBPS_U8/LBPS_S32/etc typedefs.
#define LBPS_NO_64_BIT
Remove 64-bit integer and F64 helpers.
#define LBPS_COPY_BYTES(dst, src, size)
Override byte copy. If not provided, memcpy is used by default. Define
LBPS_NO_CRT to force the internal byte-copy fallback instead.
EXAMPLE:
enum {
SV_Initial = 1,
SV_Fouls,
SV_NoFouls,
};
typedef struct ScoreState ScoreState;
struct ScoreState {
LBPS_S32 P1Score;
LBPS_S32 P2Score;
};
static void ScoreStateSerialize(LBPS_Serializer* s, ScoreState* datum) {
LBPS_ADD(s, datum, SV_Initial, S32, P1Score);
LBPS_ADD(s, datum, SV_Initial, S32, P2Score);
LBPS_REM(s, datum, SV_Fouls, SV_NoFouls, S32, P1Fouls, 0);
LBPS_REM(s, datum, SV_Fouls, SV_NoFouls, S32, P2Fouls, 0);
if ( s->Mode == kLBPS_Mode_Read && s->DataVersion < SV_NoFouls ) {
datum->P1Score -= P1Fouls;
datum->P2Score -= P2Fouls;
}
}
LBPS_Serializer m = LBPS_Measure(SV_NoFouls);
ScoreStateSerialize(&m, &state);
LBPS_SIZE needed = LBPS_BytesRequired(&m);
LBPS_Serializer w = LBPS_Write(buffer, needed, SV_NoFouls);
ScoreStateSerialize(&w, &state);
typedef struct MyStream MyStream;
LBPS_SIZE MyReadAt(void* user, LBPS_SIZE offset, void* dst, LBPS_SIZE size);
LBPS_SIZE MyWriteAt(void* user, LBPS_SIZE offset, void* src, LBPS_SIZE size);
LBPS_IO io = {
.User = stream,
.ReadAt = MyReadAt,
.WriteAt = MyWriteAt,
};
LBPS_Serializer sw = LBPS_StreamWrite(&io, SV_NoFouls);
ScoreStateSerialize(&sw, &state);
LBPS_Flush(&sw);
*/
#ifndef OH4_LBP_SERIALIZER_H
#define OH4_LBP_SERIALIZER_H
#ifndef LBPS_DEF
#ifdef OH4_LBPS_STATIC
#define LBPS_DEF static
#else
#ifdef __cplusplus
#define LBPS_DEF extern "C"
#else
#define LBPS_DEF extern
#endif
#endif
#endif
/* === Configuration === */
#ifndef LBPS_TYPES_ALREADY_DEFINED
#include <stdint.h>
typedef uint8_t LBPS_U8;
typedef int8_t LBPS_S8;
typedef uint16_t LBPS_U16;
typedef int16_t LBPS_S16;
typedef uint32_t LBPS_U32;
typedef int32_t LBPS_S32;
typedef float LBPS_F32;
#ifndef LBPS_NO_64_BIT
typedef uint64_t LBPS_U64;
typedef int64_t LBPS_S64;
typedef double LBPS_F64;
#endif
#endif
#if !defined(LBPS_COPY_BYTES) && !defined(LBPS_NO_CRT)
#include <string.h>
#endif
#ifndef LBPS_SIZE_TYPE_DEFINED
#ifndef LBPS_NO_64_BIT
typedef LBPS_U64 LBPS_SIZE;
#else
typedef LBPS_U32 LBPS_SIZE;
#endif
#endif
/* === Enums === */
typedef enum eLBPS_Mode {
kLBPS_Mode_Read,
kLBPS_Mode_Write,
kLBPS_Mode_Measure,
kLBPS_Mode_COUNT
} eLBPS_Mode;
typedef enum eLBPS_Error {
kLBPS_Error_None,
kLBPS_Error_EndOfBuffer,
kLBPS_Error_BadSize,
kLBPS_Error_BadData,
kLBPS_Error_OutOfSpace,
kLBPS_Error_COUNT
} eLBPS_Error;
typedef enum eLBPS_FinishMode {
kLBPS_Finish_Exact,
kLBPS_Finish_AllowTrailing,
kLBPS_FinishMode_COUNT
} eLBPS_FinishMode;
/* === Structs === */
/* Absolute-offset stream callbacks for generic random-access storage. */
typedef LBPS_SIZE LBPS_IOReadAtFn(void* user, LBPS_SIZE offset, void* dst, LBPS_SIZE size);
typedef LBPS_SIZE LBPS_IOWriteAtFn(void* user, LBPS_SIZE offset, void* src, LBPS_SIZE size);
typedef LBPS_S32 LBPS_IOFlushFn(void* user);
typedef struct LBPS_IO LBPS_IO;
struct LBPS_IO {
void* User;
LBPS_IOReadAtFn* ReadAt;
LBPS_IOWriteAtFn* WriteAt;
LBPS_IOFlushFn* Flush;
};
typedef struct LBPS_Serializer LBPS_Serializer;
struct LBPS_Serializer {
LBPS_S32 IsWriting; /* 1 = writing or measuring, 0 = reading */
eLBPS_Mode Mode;
eLBPS_Error Error;
LBPS_U32 DataVersion;
LBPS_U32 LatestVersion;
LBPS_SIZE Offset;
LBPS_SIZE Required;
LBPS_S32 IsBounded;
LBPS_SIZE Limit;
LBPS_SIZE BaseOffset;
LBPS_S32 IsStream;
LBPS_IO* IO;
LBPS_U8* Start;
LBPS_U8* Ptr;
LBPS_U8* End;
};
typedef struct LBPS_PatchSite LBPS_PatchSite;
struct LBPS_PatchSite {
LBPS_SIZE Offset;
LBPS_SIZE Size;
};
/* === Functions === */
/* Constructors */
LBPS_DEF LBPS_Serializer LBPS_Read(void* data, LBPS_SIZE size, LBPS_U32 latest_version);
LBPS_DEF LBPS_Serializer LBPS_Write(void* data, LBPS_SIZE size, LBPS_U32 version);
LBPS_DEF LBPS_Serializer LBPS_Measure(LBPS_U32 version);
/* Unbuffered stream serializers over user-provided absolute-offset callbacks. */
LBPS_DEF LBPS_Serializer LBPS_StreamRead(LBPS_IO* io, LBPS_SIZE size, LBPS_U32 latest_version);
LBPS_DEF LBPS_Serializer LBPS_StreamWrite(LBPS_IO* io, LBPS_U32 version);
/* State helpers */
LBPS_DEF LBPS_S32 LBPS_IsValid(LBPS_Serializer* s);
LBPS_DEF void LBPS_SetError(LBPS_Serializer* s, eLBPS_Error error);
LBPS_DEF LBPS_SIZE LBPS_BytesUsed(LBPS_Serializer* s);
LBPS_DEF LBPS_SIZE LBPS_BytesRequired(LBPS_Serializer* s);
LBPS_DEF LBPS_SIZE LBPS_BytesRemaining(LBPS_Serializer* s);
LBPS_DEF LBPS_S32 LBPS_Finish(LBPS_Serializer* s, eLBPS_FinishMode mode);
LBPS_DEF LBPS_S32 LBPS_Flush(LBPS_Serializer* s);
LBPS_DEF LBPS_Serializer LBPS_Chunk(LBPS_Serializer* parent, LBPS_SIZE size);
/* Backpatch helpers */
LBPS_DEF LBPS_PatchSite LBPS_Reserve(LBPS_Serializer* s, LBPS_SIZE size);
LBPS_DEF LBPS_PatchSite LBPS_ReserveU32(LBPS_Serializer* s);
LBPS_DEF LBPS_S32 LBPS_PatchBytes(LBPS_Serializer* s, LBPS_PatchSite site, void* data, LBPS_SIZE size);
LBPS_DEF LBPS_S32 LBPS_PatchU32(LBPS_Serializer* s, LBPS_PatchSite site, LBPS_U32 value);
/* Primitive serialization */
LBPS_DEF void LBPS_SerializeU8(LBPS_Serializer* s, LBPS_U8* value);
LBPS_DEF void LBPS_SerializeS8(LBPS_Serializer* s, LBPS_S8* value);
LBPS_DEF void LBPS_SerializeU16(LBPS_Serializer* s, LBPS_U16* value);
LBPS_DEF void LBPS_SerializeS16(LBPS_Serializer* s, LBPS_S16* value);
LBPS_DEF void LBPS_SerializeU32(LBPS_Serializer* s, LBPS_U32* value);
LBPS_DEF void LBPS_SerializeS32(LBPS_Serializer* s, LBPS_S32* value);
LBPS_DEF void LBPS_SerializeF32(LBPS_Serializer* s, LBPS_F32* value);
LBPS_DEF void LBPS_SerializeBytes(LBPS_Serializer* s, void* data, LBPS_SIZE size);
LBPS_DEF void LBPS_Skip(LBPS_Serializer* s, LBPS_SIZE size);
LBPS_DEF void LBPS_Align(LBPS_Serializer* s, LBPS_SIZE alignment, LBPS_U8 pad_byte);
#ifndef LBPS_NO_64_BIT
LBPS_DEF void LBPS_SerializeU64(LBPS_Serializer* s, LBPS_U64* value);
LBPS_DEF void LBPS_SerializeS64(LBPS_Serializer* s, LBPS_S64* value);
LBPS_DEF void LBPS_SerializeF64(LBPS_Serializer* s, LBPS_F64* value);
#endif
/*
=== Typed Field Helpers ===
Typed field helpers for LBP-style field lists.
Built-in tags are provided for primitives. Custom tags can be added by
defining both `LBPS_TYPE_<Tag>` and `LBPS_SERIALIZE_<Tag>`.
*/
#define LBPS_TYPE_U8 LBPS_U8
#define LBPS_TYPE_S8 LBPS_S8
#define LBPS_TYPE_U16 LBPS_U16
#define LBPS_TYPE_S16 LBPS_S16
#define LBPS_TYPE_U32 LBPS_U32
#define LBPS_TYPE_S32 LBPS_S32
#define LBPS_TYPE_F32 LBPS_F32
#ifndef LBPS_NO_64_BIT
#define LBPS_TYPE_U64 LBPS_U64
#define LBPS_TYPE_S64 LBPS_S64
#define LBPS_TYPE_F64 LBPS_F64
#endif
#define LBPS_SERIALIZE_U8 LBPS_SerializeU8
#define LBPS_SERIALIZE_S8 LBPS_SerializeS8
#define LBPS_SERIALIZE_U16 LBPS_SerializeU16
#define LBPS_SERIALIZE_S16 LBPS_SerializeS16
#define LBPS_SERIALIZE_U32 LBPS_SerializeU32
#define LBPS_SERIALIZE_S32 LBPS_SerializeS32
#define LBPS_SERIALIZE_F32 LBPS_SerializeF32
#ifndef LBPS_NO_64_BIT
#define LBPS_SERIALIZE_U64 LBPS_SerializeU64
#define LBPS_SERIALIZE_S64 LBPS_SerializeS64
#define LBPS_SERIALIZE_F64 LBPS_SerializeF64
#endif
#define LBPS_ADD(_s, _datum, _field_added_version, _type_tag, _field_name) \
if ( (_s)->DataVersion >= (_field_added_version) ) { \
LBPS_SERIALIZE_##_type_tag((_s), &((_datum)->_field_name)); \
}
#define LBPS_ADD_BYTES(_s, _datum, _field_added_version, _field_name, _byte_count) \
if ( (_s)->DataVersion >= (_field_added_version) ) { \
LBPS_SerializeBytes((_s), (_datum)->_field_name, (LBPS_SIZE)(_byte_count)); \
}
#define LBPS_REM(_s, _datum, _field_added_version, _field_removed_version, _type_tag, _field_name, _default_value) \
LBPS_TYPE_##_type_tag _field_name = (_default_value); \
if ( (_s)->DataVersion >= (_field_added_version) && \
(_s)->DataVersion < (_field_removed_version) ) { \
LBPS_SERIALIZE_##_type_tag((_s), &(_field_name)); \
}
#if defined(OH4_LBP_SERIALIZER_IMPLEMENTATION)
#if !defined(LBPS_COPY_BYTES)
#if defined(LBPS_NO_CRT)
static void LBPS__FallbackCopyBytes(LBPS_U8* dst, LBPS_U8* src, LBPS_SIZE size) {
while ( size > 0 ) {
*dst++ = *src++;
size -= 1;
}
}
#define LBPS_COPY_BYTES(dst, src, size) LBPS__FallbackCopyBytes((LBPS_U8*)(dst), (LBPS_U8*)(src), (size))
#else
#define LBPS_COPY_BYTES(dst, src, size) memcpy((dst), (src), (size))
#endif
#endif
static LBPS_S32 LBPS__AdvanceChecked(LBPS_Serializer* s, LBPS_SIZE size) {
LBPS_SIZE max_value = (LBPS_SIZE)~(LBPS_SIZE)0;
if ( s->Offset > max_value - size ) {
LBPS_SetError(s, kLBPS_Error_BadSize);
return( 0 );
}
return( 1 );
}
static void LBPS__Advance(LBPS_Serializer* s, LBPS_SIZE size) {
s->Offset += size;
if ( s->Mode != kLBPS_Mode_Read ) {
s->Required = s->Offset;
}
if ( s->Mode != kLBPS_Mode_Measure && !s->IsStream ) {
s->Ptr += size;
}
}
static LBPS_S32 LBPS__AbsoluteOffset(LBPS_Serializer* s, LBPS_SIZE relative, LBPS_SIZE* out_offset) {
if ( out_offset == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( s->BaseOffset > (LBPS_SIZE)~(LBPS_SIZE)0 - relative ) {
LBPS_SetError(s, kLBPS_Error_BadSize);
return( 0 );
}
*out_offset = s->BaseOffset + relative;
return( 1 );
}
static LBPS_S32 LBPS__StreamWriteFill(LBPS_Serializer* s, LBPS_SIZE size, LBPS_U8 value) {
LBPS_SIZE abs_off = 0;
LBPS_SIZE relative = 0;
LBPS_SIZE remaining = 0;
LBPS_U8 temp[64];
LBPS_SIZE fill_size = 0;
if ( s->IO == 0 || s->IO->WriteAt == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( size == 0 ) {
return( 1 );
}
while ( fill_size < (LBPS_SIZE)sizeof(temp) ) {
temp[fill_size] = value;
fill_size += 1;
}
relative = s->Offset;
remaining = size;
while ( remaining > 0 ) {
LBPS_SIZE chunk_size = remaining;
if ( chunk_size > (LBPS_SIZE)sizeof(temp) ) {
chunk_size = (LBPS_SIZE)sizeof(temp);
}
if ( !LBPS__AbsoluteOffset(s, relative, &abs_off) ) {
return( 0 );
}
if ( s->IO->WriteAt(s->IO->User, abs_off, temp, chunk_size) != chunk_size ) {
LBPS_SetError(s, kLBPS_Error_OutOfSpace);
return( 0 );
}
relative += chunk_size;
remaining -= chunk_size;
}
return( 1 );
}
static LBPS_S32 LBPS__StreamReadExact(LBPS_Serializer* s, LBPS_SIZE relative, void* dst, LBPS_SIZE size) {
LBPS_SIZE abs_off = 0;
LBPS_SIZE read_size = 0;
if ( s->IO == 0 || s->IO->ReadAt == 0 || ( dst == 0 && size != 0 ) ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( !LBPS__AbsoluteOffset(s, relative, &abs_off) ) {
return( 0 );
}
read_size = s->IO->ReadAt(s->IO->User, abs_off, dst, size);
if ( read_size != size ) {
LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
return( 0 );
}
return( 1 );
}
static LBPS_S32 LBPS__StreamWriteExact(LBPS_Serializer* s, LBPS_SIZE relative, void* src, LBPS_SIZE size) {
LBPS_SIZE abs_off = 0;
LBPS_SIZE write_size = 0;
if ( s->IO == 0 || s->IO->WriteAt == 0 || ( src == 0 && size != 0 ) ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( !LBPS__AbsoluteOffset(s, relative, &abs_off) ) {
return( 0 );
}
write_size = s->IO->WriteAt(s->IO->User, abs_off, src, size);
if ( write_size != size ) {
LBPS_SetError(s, kLBPS_Error_OutOfSpace);
return( 0 );
}
return( 1 );
}
static LBPS_S32 LBPS__CanRead(LBPS_Serializer* s, LBPS_SIZE size) {
LBPS_SIZE remaining = 0;
if ( !LBPS__AdvanceChecked(s, size) ) {
return( 0 );
}
if ( size == 0 ) {
return( 1 );
}
if ( s->IsStream ) {
if ( s->IO == 0 || s->IO->ReadAt == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( s->IsBounded ) {
remaining = LBPS_BytesRemaining(s);
if ( remaining < size ) {
LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
return( 0 );
}
}
return( 1 );
}
if ( s->Ptr == 0 || s->End == 0 ) {
LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
return( 0 );
}
remaining = (LBPS_SIZE)(s->End - s->Ptr);
if ( remaining < size ) {
LBPS_SetError(s, kLBPS_Error_EndOfBuffer);
return( 0 );
}
return( 1 );
}
static LBPS_S32 LBPS__CanWrite(LBPS_Serializer* s, LBPS_SIZE size) {
LBPS_SIZE remaining = 0;
if ( !LBPS__AdvanceChecked(s, size) ) {
return( 0 );
}
if ( size == 0 ) {
return( 1 );
}
if ( s->IsStream ) {
if ( s->IO == 0 || s->IO->WriteAt == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( s->IsBounded ) {
remaining = LBPS_BytesRemaining(s);
if ( remaining < size ) {
LBPS_SetError(s, kLBPS_Error_OutOfSpace);
return( 0 );
}
}
return( 1 );
}
if ( s->Ptr == 0 || s->End == 0 ) {
LBPS_SetError(s, kLBPS_Error_OutOfSpace);
return( 0 );
}
remaining = (LBPS_SIZE)(s->End - s->Ptr);
if ( remaining < size ) {
LBPS_SetError(s, kLBPS_Error_OutOfSpace);
return( 0 );
}
return( 1 );
}
static LBPS_S32 LBPS__PatchSiteInBounds(LBPS_Serializer* s, LBPS_PatchSite site, LBPS_SIZE size) {
LBPS_SIZE end = 0;
LBPS_SIZE limit = 0;
if ( size != site.Size ) {
LBPS_SetError(s, kLBPS_Error_BadSize);
return( 0 );
}
if ( site.Offset > (LBPS_SIZE)~(LBPS_SIZE)0 - size ) {
LBPS_SetError(s, kLBPS_Error_BadSize);
return( 0 );
}
end = site.Offset + size;
limit = s->Required;
if ( end > limit ) {
LBPS_SetError(s, kLBPS_Error_BadSize);
return( 0 );
}
return( 1 );
}
LBPS_DEF LBPS_Serializer LBPS_Read(void* data, LBPS_SIZE size, LBPS_U32 latest_version) {
LBPS_Serializer result = { 0 };
LBPS_U8* start = (LBPS_U8*)data;
result.IsWriting = 0;
result.Mode = kLBPS_Mode_Read;
result.Error = ( start == 0 && size != 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
result.DataVersion = 0;
result.LatestVersion = latest_version;
result.Offset = 0;
result.Required = 0;
result.IsBounded = 1;
result.Limit = size;
result.BaseOffset = 0;
result.IsStream = 0;
result.IO = 0;
result.Start = start;
result.Ptr = start;
result.End = (start != 0) ? (start + size) : 0;
return( result );
}
LBPS_DEF LBPS_Serializer LBPS_Write(void* data, LBPS_SIZE size, LBPS_U32 version) {
LBPS_Serializer result = { 0 };
LBPS_U8* start = (LBPS_U8*)data;
result.IsWriting = 1;
result.Mode = kLBPS_Mode_Write;
result.Error = ( start == 0 && size != 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
result.DataVersion = version;
result.LatestVersion = version;
result.Offset = 0;
result.Required = 0;
result.IsBounded = 1;
result.Limit = size;
result.BaseOffset = 0;
result.IsStream = 0;
result.IO = 0;
result.Start = start;
result.Ptr = start;
result.End = (start != 0) ? (start + size) : 0;
return( result );
}
LBPS_DEF LBPS_Serializer LBPS_Measure(LBPS_U32 version) {
LBPS_Serializer result = { 0 };
result.IsWriting = 1;
result.Mode = kLBPS_Mode_Measure;
result.Error = kLBPS_Error_None;
result.DataVersion = version;
result.LatestVersion = version;
result.Offset = 0;
result.Required = 0;
result.IsBounded = 0;
result.Limit = 0;
result.BaseOffset = 0;
result.IsStream = 0;
result.IO = 0;
result.Start = 0;
result.Ptr = 0;
result.End = 0;
return( result );
}
LBPS_DEF LBPS_Serializer LBPS_StreamRead(LBPS_IO* io, LBPS_SIZE size, LBPS_U32 latest_version) {
LBPS_Serializer result = { 0 };
result.IsWriting = 0;
result.Mode = kLBPS_Mode_Read;
result.Error = ( io == 0 || io->ReadAt == 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
result.DataVersion = 0;
result.LatestVersion = latest_version;
result.Offset = 0;
result.Required = 0;
result.IsBounded = 1;
result.Limit = size;
result.BaseOffset = 0;
result.IsStream = 1;
result.IO = io;
result.Start = 0;
result.Ptr = 0;
result.End = 0;
return( result );
}
LBPS_DEF LBPS_Serializer LBPS_StreamWrite(LBPS_IO* io, LBPS_U32 version) {
LBPS_Serializer result = { 0 };
result.IsWriting = 1;
result.Mode = kLBPS_Mode_Write;
result.Error = ( io == 0 || io->WriteAt == 0 ) ? kLBPS_Error_BadData : kLBPS_Error_None;
result.DataVersion = version;
result.LatestVersion = version;
result.Offset = 0;
result.Required = 0;
result.IsBounded = 0;
result.Limit = 0;
result.BaseOffset = 0;
result.IsStream = 1;
result.IO = io;
result.Start = 0;
result.Ptr = 0;
result.End = 0;
return( result );
}
LBPS_DEF LBPS_S32 LBPS_IsValid(LBPS_Serializer* s) {
return( s->Error == kLBPS_Error_None );
}
LBPS_DEF void LBPS_SetError(LBPS_Serializer* s, eLBPS_Error error) {
if ( s->Error == kLBPS_Error_None ) {
s->Error = error;
}
}
LBPS_DEF LBPS_SIZE LBPS_BytesUsed(LBPS_Serializer* s) {
return( s->Offset );
}
LBPS_DEF LBPS_SIZE LBPS_BytesRequired(LBPS_Serializer* s) {
return( s->Required );
}
LBPS_DEF LBPS_SIZE LBPS_BytesRemaining(LBPS_Serializer* s) {
LBPS_SIZE result = 0;
if ( s != 0 && s->IsBounded ) {
if ( s->Offset < s->Limit ) {
result = s->Limit - s->Offset;
}
}
return( result );
}
LBPS_DEF LBPS_S32 LBPS_Finish(LBPS_Serializer* s, eLBPS_FinishMode mode) {
LBPS_S32 result = 0;
if ( s == 0 ) {
return( 0 );
}
if ( s->Error != kLBPS_Error_None ) {
return( 0 );
}
if ( s->IsBounded ) {
if ( mode == kLBPS_Finish_Exact ) {
if ( s->Offset != s->Limit ) {
LBPS_SetError(s, kLBPS_Error_BadSize);
}
}
else if ( mode == kLBPS_Finish_AllowTrailing ) {
if ( s->Offset > s->Limit ) {
LBPS_SetError(s, kLBPS_Error_BadSize);
}
}
else {
LBPS_SetError(s, kLBPS_Error_BadData);
}
}
else if ( mode != kLBPS_Finish_Exact && mode != kLBPS_Finish_AllowTrailing ) {
LBPS_SetError(s, kLBPS_Error_BadData);
}
result = ( s->Error == kLBPS_Error_None );
return( result );
}
LBPS_DEF LBPS_S32 LBPS_Flush(LBPS_Serializer* s) {
LBPS_S32 result = 0;
if ( s == 0 ) {
return( 0 );
}
if ( s->Error != kLBPS_Error_None ) {
return( 0 );
}
result = 1;
if ( s->IsStream && s->IO != 0 && s->IO->Flush != 0 ) {
result = s->IO->Flush(s->IO->User);
if ( !result ) {
LBPS_SetError(s, kLBPS_Error_BadData);
}
}
return( result && s->Error == kLBPS_Error_None );
}
LBPS_DEF LBPS_Serializer LBPS_Chunk(LBPS_Serializer* parent, LBPS_SIZE size) {
LBPS_Serializer result = { 0 };
if ( parent == 0 ) {
result.Error = kLBPS_Error_BadData;
return( result );
}
result.IsWriting = parent->IsWriting;
result.Mode = parent->Mode;
result.Error = parent->Error;
result.DataVersion = parent->DataVersion;
result.LatestVersion = parent->LatestVersion;
result.Offset = 0;
result.Required = 0;
result.IsBounded = 1;
result.Limit = size;
result.BaseOffset = 0;
result.IsStream = parent->IsStream;
result.IO = parent->IO;
result.Start = parent->Ptr;
result.Ptr = parent->Ptr;
result.End = ( parent->Ptr != 0 ) ? ( parent->Ptr + size ) : 0;
if ( parent->Error != kLBPS_Error_None ) {
return( result );
}
if ( !LBPS__AbsoluteOffset(parent, parent->Offset, &result.BaseOffset) ) {
result.Error = parent->Error;
result.Start = 0;
result.Ptr = 0;
result.End = 0;
return( result );
}
if ( parent->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(parent, size) ) {
result.Error = parent->Error;
result.Start = 0;
result.Ptr = 0;
result.End = 0;
return( result );
}
}
else if ( parent->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(parent, size) ) {
result.Error = parent->Error;
result.Start = 0;
result.Ptr = 0;
result.End = 0;
return( result );
}
}
else {
if ( !LBPS__AdvanceChecked(parent, size) ) {
result.Error = parent->Error;
result.Start = 0;
result.Ptr = 0;
result.End = 0;
return( result );
}
result.Start = 0;
result.Ptr = 0;
result.End = 0;
}
if ( parent->IsStream ) {
result.Start = 0;
result.Ptr = 0;
result.End = 0;
}
LBPS__Advance(parent, size);
return( result );
}
LBPS_DEF LBPS_PatchSite LBPS_Reserve(LBPS_Serializer* s, LBPS_SIZE size) {
LBPS_PatchSite result = { 0 };
if ( s == 0 ) {
return( result );
}
if ( s->Mode == kLBPS_Mode_Read ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( result );
}
result.Offset = s->Offset;
result.Size = size;
LBPS_Skip(s, size);
return( result );
}
LBPS_DEF LBPS_PatchSite LBPS_ReserveU32(LBPS_Serializer* s) {
return( LBPS_Reserve(s, 4) );
}
LBPS_DEF LBPS_S32 LBPS_PatchBytes(LBPS_Serializer* s, LBPS_PatchSite site, void* data, LBPS_SIZE size) {
LBPS_S32 result = 0;
LBPS_SIZE abs_off = 0;
if ( s == 0 ) {
return( 0 );
}
if ( s->Error != kLBPS_Error_None ) {
return( 0 );
}
if ( s->Mode == kLBPS_Mode_Read ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( size != 0 && data == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( !LBPS__PatchSiteInBounds(s, site, size) ) {
return( 0 );
}
if ( s->Mode == kLBPS_Mode_Write && size != 0 ) {
if ( s->IsStream ) {
if ( !LBPS__AbsoluteOffset(s, site.Offset, &abs_off) ) {
return( 0 );
}
if ( s->IO == 0 || s->IO->WriteAt == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
if ( s->IO->WriteAt(s->IO->User, abs_off, data, size) != size ) {
LBPS_SetError(s, kLBPS_Error_OutOfSpace);
return( 0 );
}
}
else {
if ( s->Start == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return( 0 );
}
LBPS_COPY_BYTES(s->Start + site.Offset, (void*)data, size);
}
}
result = 1;
return( result );
}
LBPS_DEF LBPS_S32 LBPS_PatchU32(LBPS_Serializer* s, LBPS_PatchSite site, LBPS_U32 value) {
return( LBPS_PatchBytes(s, site, &value, 4) );
}
LBPS_DEF void LBPS_SerializeU8(LBPS_Serializer* s, LBPS_U8* value) {
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(s, 1) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamReadExact(s, s->Offset, value, 1) ) {
return;
}
}
else {
*value = s->Ptr[0];
}
}
else if ( s->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(s, 1) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamWriteExact(s, s->Offset, value, 1) ) {
return;
}
}
else {
s->Ptr[0] = *value;
}
}
else {
if ( !LBPS__AdvanceChecked(s, 1) ) {
return;
}
}
LBPS__Advance(s, 1);
}
LBPS_DEF void LBPS_SerializeS8(LBPS_Serializer* s, LBPS_S8* value) {
LBPS_U8 bits = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode != kLBPS_Mode_Read ) {
LBPS_COPY_BYTES(&bits, value, 1);
}
LBPS_SerializeU8(s, &bits);
if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
LBPS_COPY_BYTES(value, &bits, 1);
}
}
LBPS_DEF void LBPS_SerializeU16(LBPS_Serializer* s, LBPS_U16* value) {
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(s, 2) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamReadExact(s, s->Offset, value, 2) ) {
return;
}
}
else {
LBPS_COPY_BYTES(value, s->Ptr, 2);
}
}
else if ( s->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(s, 2) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamWriteExact(s, s->Offset, value, 2) ) {
return;
}
}
else {
LBPS_COPY_BYTES(s->Ptr, value, 2);
}
}
else {
if ( !LBPS__AdvanceChecked(s, 2) ) {
return;
}
}
LBPS__Advance(s, 2);
}
LBPS_DEF void LBPS_SerializeS16(LBPS_Serializer* s, LBPS_S16* value) {
LBPS_U16 bits = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode != kLBPS_Mode_Read ) {
LBPS_COPY_BYTES(&bits, value, 2);
}
LBPS_SerializeU16(s, &bits);
if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
LBPS_COPY_BYTES(value, &bits, 2);
}
}
LBPS_DEF void LBPS_SerializeU32(LBPS_Serializer* s, LBPS_U32* value) {
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(s, 4) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamReadExact(s, s->Offset, value, 4) ) {
return;
}
}
else {
LBPS_COPY_BYTES(value, s->Ptr, 4);
}
}
else if ( s->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(s, 4) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamWriteExact(s, s->Offset, value, 4) ) {
return;
}
}
else {
LBPS_COPY_BYTES(s->Ptr, value, 4);
}
}
else {
if ( !LBPS__AdvanceChecked(s, 4) ) {
return;
}
}
LBPS__Advance(s, 4);
}
LBPS_DEF void LBPS_SerializeS32(LBPS_Serializer* s, LBPS_S32* value) {
LBPS_U32 bits = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode != kLBPS_Mode_Read ) {
LBPS_COPY_BYTES(&bits, value, 4);
}
LBPS_SerializeU32(s, &bits);
if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
LBPS_COPY_BYTES(value, &bits, 4);
}
}
LBPS_DEF void LBPS_SerializeF32(LBPS_Serializer* s, LBPS_F32* value) {
LBPS_U32 bits = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode != kLBPS_Mode_Read ) {
LBPS_COPY_BYTES(&bits, value, 4);
}
LBPS_SerializeU32(s, &bits);
if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
LBPS_COPY_BYTES(value, &bits, 4);
}
}
LBPS_DEF void LBPS_SerializeBytes(LBPS_Serializer* s, void* data, LBPS_SIZE size) {
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( size == 0 ) {
return;
}
if ( data == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(s, size) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamReadExact(s, s->Offset, data, size) ) {
return;
}
}
else {
LBPS_COPY_BYTES(data, s->Ptr, size);
}
}
else if ( s->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(s, size) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamWriteExact(s, s->Offset, data, size) ) {
return;
}
}
else {
LBPS_COPY_BYTES(s->Ptr, data, size);
}
}
else {
if ( !LBPS__AdvanceChecked(s, size) ) {
return;
}
}
LBPS__Advance(s, size);
}
LBPS_DEF void LBPS_Skip(LBPS_Serializer* s, LBPS_SIZE size) {
LBPS_SIZE idx = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( size == 0 ) {
return;
}
if ( s->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(s, size) ) {
return;
}
}
else if ( s->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(s, size) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamWriteFill(s, size, 0) ) {
return;
}
}
else {
for ( idx = 0; idx < size; ++idx ) {
s->Ptr[idx] = 0;
}
}
}
else {
if ( !LBPS__AdvanceChecked(s, size) ) {
return;
}
}
LBPS__Advance(s, size);
}
LBPS_DEF void LBPS_Align(LBPS_Serializer* s, LBPS_SIZE alignment, LBPS_U8 pad_byte) {
LBPS_SIZE pad_size = 0;
LBPS_SIZE idx = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( alignment == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( alignment == 1 ) {
return;
}
pad_size = s->Offset % alignment;
if ( pad_size != 0 ) {
pad_size = alignment - pad_size;
}
if ( pad_size == 0 ) {
return;
}
if ( s->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(s, pad_size) ) {
return;
}
}
else if ( s->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(s, pad_size) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamWriteFill(s, pad_size, pad_byte) ) {
return;
}
}
else {
for ( idx = 0; idx < pad_size; ++idx ) {
s->Ptr[idx] = pad_byte;
}
}
}
else {
if ( !LBPS__AdvanceChecked(s, pad_size) ) {
return;
}
}
LBPS__Advance(s, pad_size);
}
#ifndef LBPS_NO_64_BIT
LBPS_DEF void LBPS_SerializeU64(LBPS_Serializer* s, LBPS_U64* value) {
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode == kLBPS_Mode_Read ) {
if ( !LBPS__CanRead(s, 8) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamReadExact(s, s->Offset, value, 8) ) {
return;
}
}
else {
LBPS_COPY_BYTES(value, s->Ptr, 8);
}
}
else if ( s->Mode == kLBPS_Mode_Write ) {
if ( !LBPS__CanWrite(s, 8) ) {
return;
}
if ( s->IsStream ) {
if ( !LBPS__StreamWriteExact(s, s->Offset, value, 8) ) {
return;
}
}
else {
LBPS_COPY_BYTES(s->Ptr, value, 8);
}
}
else {
if ( !LBPS__AdvanceChecked(s, 8) ) {
return;
}
}
LBPS__Advance(s, 8);
}
LBPS_DEF void LBPS_SerializeS64(LBPS_Serializer* s, LBPS_S64* value) {
LBPS_U64 bits = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode != kLBPS_Mode_Read ) {
LBPS_COPY_BYTES(&bits, value, 8);
}
LBPS_SerializeU64(s, &bits);
if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
LBPS_COPY_BYTES(value, &bits, 8);
}
}
LBPS_DEF void LBPS_SerializeF64(LBPS_Serializer* s, LBPS_F64* value) {
LBPS_U64 bits = 0;
if ( s->Error != kLBPS_Error_None ) {
return;
}
if ( value == 0 ) {
LBPS_SetError(s, kLBPS_Error_BadData);
return;
}
if ( s->Mode != kLBPS_Mode_Read ) {
LBPS_COPY_BYTES(&bits, value, 8);
}
LBPS_SerializeU64(s, &bits);
if ( s->Mode == kLBPS_Mode_Read && s->Error == kLBPS_Error_None ) {
LBPS_COPY_BYTES(value, &bits, 8);
}
}
#endif /* LBPS_NO_64_BIT */
#endif /* OH4_LBP_SERIALIZER_IMPLEMENTATION */
#endif /* OH4_LBP_SERIALIZER_H */