/* 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 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 #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_` and `LBPS_SERIALIZE_`. */ #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 */