From 489c1c9dd551310575a08d3062f466299de447a6 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 21 Apr 2026 23:56:16 +0500 Subject: [PATCH] public: add swaplib --- public/swaplib.h | 127 ++++++++++++++++++ public/tests/test_swapstruct.c | 235 +++++++++++++++++++++++++++++++++ public/wscript | 1 + 3 files changed, 363 insertions(+) create mode 100644 public/swaplib.h create mode 100644 public/tests/test_swapstruct.c diff --git a/public/swaplib.h b/public/swaplib.h new file mode 100644 index 00000000..35b08db7 --- /dev/null +++ b/public/swaplib.h @@ -0,0 +1,127 @@ +/* +swaplib.h - byte-swapping for on-disk structures +Copyright (C) 2026 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef SWAPLIB_H +#define SWAPLIB_H + +#include "build.h" +#include "xash3d_types.h" + +typedef struct swap_struct_def_s +{ + uint32_t offset; + int32_t size; + struct swap_struct_def_s *subdef; + uint32_t count; + uint32_t stride; +} swap_struct_def_t; + +static inline void swap_two_bytes_( byte *a, byte *b ) +{ + byte tmp = *a; + *a = *b; + *b = tmp; +} + +static inline void swap_field_( byte *p, int32_t size ) +{ + switch( size ) + { + case 2: + swap_two_bytes_( &p[0], &p[1] ); + break; + case 4: + swap_two_bytes_( &p[0], &p[3] ); + swap_two_bytes_( &p[1], &p[2] ); + break; + case 8: + swap_two_bytes_( &p[0], &p[7] ); + swap_two_bytes_( &p[1], &p[6] ); + swap_two_bytes_( &p[2], &p[5] ); + swap_two_bytes_( &p[3], &p[4] ); + break; + default: + break; + } +} + +static inline void swap_struct_( const swap_struct_def_t *def, size_t len, byte *data ) +{ + for( size_t i = 0; i < len; i++ ) + { + if( def[i].size < 0 ) + { + uint32_t count = def[i].count ? def[i].count : 1; + for( uint32_t j = 0; j < count; j++ ) + swap_struct_( def[i].subdef, -def[i].size, &data[def[i].offset + j * def[i].stride] ); + continue; + } + + if( def[i].count > 0 ) + { + for( uint32_t j = 0; j < def[i].count; j++ ) + swap_field_( &data[def[i].offset + j * def[i].stride], def[i].size ); + continue; + } + + swap_field_( &data[def[i].offset], def[i].size ); + } +} + +#define swap_struct( swapdef, ptr ) swap_struct_(( swapdef ), sizeof( swapdef ) / sizeof( swapdef[0] ), (byte *)( ptr )) + +#define swap_struct_begin( swapdef ) static swap_struct_def_t swapdef[] = { +#define swap_struct_field( record, field ) { .offset = offsetof( record, field ), .size = sizeof(((record *)0)->field) }, +#define swap_struct_child( record, field, child ) { .offset = offsetof( record, field ), .size = -(int32_t)(sizeof( child ) / sizeof( child[0] )), .subdef = child }, +#define swap_struct_array( record, field, cnt ) { .offset = offsetof( record, field ), .size = sizeof(((record *)0)->field[0]), .count = cnt, .stride = sizeof(((record *)0)->field[0]) }, +#define swap_struct_array_child( record, field, child, cnt ) { .offset = offsetof( record, field ), .size = -(int32_t)(sizeof( child ) / sizeof( child[0] )), .subdef = child, .count = cnt, .stride = sizeof(((record *)0)->field[0]) }, +#define swap_struct_end() } + +// this is done in macros so we can completely avoid defining this +// in little endian targets +#ifdef XASH_LITTLE_ENDIAN + #define be_struct_begin( x ) swap_struct_begin( x ) + #define be_struct_field( x, y ) swap_struct_field( x, y ) + #define be_struct_child( x, y, z ) swap_struct_child( x, y, z ) + #define be_struct_array( x, y, z ) swap_struct_array( x, y, z ) + #define be_struct_array_child( x, y, z, w ) swap_struct_array_child( x, y, z, w ) + #define be_struct_end() swap_struct_end() + #define be_struct_swap( x, y ) swap_struct( x, y ) + #define le_struct_begin( x ) + #define le_struct_field( x, y ) + #define le_struct_child( x, y, z ) + #define le_struct_array( x, y, z ) + #define le_struct_array_child( x, y, z, w ) + #define le_struct_end() + #define le_struct_swap( x, y ) (void)(y) +#else + #define le_struct_begin( x ) swap_struct_begin( x ) + #define le_struct_field( x, y ) swap_struct_field( x, y ) + #define le_struct_child( x, y, z ) swap_struct_child( x, y, z ) + #define le_struct_array( x, y, z ) swap_struct_array( x, y, z ) + #define le_struct_array_child( x, y, z, w ) swap_struct_array_child( x, y, z, w ) + #define le_struct_end() swap_struct_end() + #define le_struct_swap( x, y ) swap_struct( x, y ) + #define be_struct_begin( x ) + #define be_struct_field( x, y ) + #define be_struct_child( x, y, z ) + #define be_struct_array( x, y, z ) + #define be_struct_array_child( x, y, z, w ) + #define be_struct_end() + #define be_struct_swap( x, y ) (void)(y) +#endif + + +#endif // SWAPLIB_H diff --git a/public/tests/test_swapstruct.c b/public/tests/test_swapstruct.c new file mode 100644 index 00000000..bc400f39 --- /dev/null +++ b/public/tests/test_swapstruct.c @@ -0,0 +1,235 @@ +#include +#include "swaplib.h" + +typedef struct +{ + uint16_t x; + uint16_t y; +} inner_t; + +typedef struct +{ + uint8_t a; + uint16_t b; + uint32_t c; + inner_t inner; +} outer_t; + +swap_struct_begin( inner_def ) + swap_struct_field( inner_t, x ) + swap_struct_field( inner_t, y ) +swap_struct_end(); + +swap_struct_begin( outer_def ) + swap_struct_field( outer_t, b ) + swap_struct_field( outer_t, c ) + swap_struct_child( outer_t, inner, inner_def ) +swap_struct_end(); + +typedef struct +{ + uint32_t version; + inner_t items[4]; +} array_outer_t; + +swap_struct_begin( array_outer_def ) + swap_struct_field( array_outer_t, version ) + swap_struct_array_child( array_outer_t, items, inner_def, 4 ) +swap_struct_end(); + +typedef struct +{ + uint16_t tag; + uint32_t values[3]; +} int_array_t; + +swap_struct_begin( int_array_def ) + swap_struct_field( int_array_t, tag ) + swap_struct_array( int_array_t, values, 3 ) +swap_struct_end(); + +static int Test_SwapField( void ) +{ + inner_t data = { .x = 0x0102, .y = 0x0304 }; + + swap_struct( inner_def, &data ); + + if( data.x != 0x0201 ) + return 1; + + if( data.y != 0x0403 ) + return 2; + + // swap again, should restore original + swap_struct( inner_def, &data ); + + if( data.x != 0x0102 ) + return 3; + + if( data.y != 0x0304 ) + return 4; + + return 0; +} + +static int Test_SwapChild( void ) +{ + outer_t data = + { + .a = 0xAA, + .b = 0x0102, + .c = 0x01020304, + .inner = { .x = 0x0506, .y = 0x0708 }, + }; + + swap_struct( outer_def, &data ); + + // a is uint8_t, must be untouched + if( data.a != 0xAA ) + return 1; + + // b must be byte-swapped + if( data.b != 0x0201 ) + return 2; + + // c must be byte-swapped + if( data.c != 0x04030201 ) + return 3; + + // child fields must be byte-swapped + if( data.inner.x != 0x0605 ) + return 4; + + if( data.inner.y != 0x0807 ) + return 5; + + // swap back + swap_struct( outer_def, &data ); + + if( data.b != 0x0102 ) + return 6; + + if( data.c != 0x01020304 ) + return 7; + + if( data.inner.x != 0x0506 ) + return 8; + + if( data.inner.y != 0x0708 ) + return 9; + + return 0; +} + +static int Test_SwapArrayChild( void ) +{ + array_outer_t data = + { + .version = 0x01020304, + .items = + { + { .x = 0x0102, .y = 0x0304 }, + { .x = 0x0506, .y = 0x0708 }, + { .x = 0x090A, .y = 0x0B0C }, + { .x = 0x0D0E, .y = 0x0F10 }, + }, + }; + + swap_struct( array_outer_def, &data ); + + if( data.version != 0x04030201 ) + return 1; + + if( data.items[0].x != 0x0201 || data.items[0].y != 0x0403 ) + return 2; + + if( data.items[1].x != 0x0605 || data.items[1].y != 0x0807 ) + return 3; + + if( data.items[2].x != 0x0A09 || data.items[2].y != 0x0C0B ) + return 4; + + if( data.items[3].x != 0x0E0D || data.items[3].y != 0x100F ) + return 5; + + // swap back + swap_struct( array_outer_def, &data ); + + if( data.version != 0x01020304 ) + return 6; + + if( data.items[0].x != 0x0102 || data.items[0].y != 0x0304 ) + return 7; + + if( data.items[3].x != 0x0D0E || data.items[3].y != 0x0F10 ) + return 8; + + return 0; +} + +static int Test_SwapArray( void ) +{ + int_array_t data = + { + .tag = 0x0102, + .values = { 0x01020304, 0x05060708, 0x090A0B0C }, + }; + + swap_struct( int_array_def, &data ); + + if( data.tag != 0x0201 ) + return 1; + + if( data.values[0] != 0x04030201 ) + return 2; + + if( data.values[1] != 0x08070605 ) + return 3; + + if( data.values[2] != 0x0C0B0A09 ) + return 4; + + // swap back + swap_struct( int_array_def, &data ); + + if( data.tag != 0x0102 ) + return 5; + + if( data.values[0] != 0x01020304 ) + return 6; + + if( data.values[1] != 0x05060708 ) + return 7; + + if( data.values[2] != 0x090A0B0C ) + return 8; + + return 0; +} + +int main( void ) +{ + int ret; + + ret = Test_SwapField(); + + if( ret > 0 ) + return ret; + + ret = Test_SwapChild(); + + if( ret > 0 ) + return ret + 32; + + ret = Test_SwapArrayChild(); + + if( ret > 0 ) + return ret + 64; + + ret = Test_SwapArray(); + + if( ret > 0 ) + return ret + 96; + + return 0; +} diff --git a/public/wscript b/public/wscript index 76495b08..32ae9f01 100644 --- a/public/wscript +++ b/public/wscript @@ -158,6 +158,7 @@ def build(bld): 'atoi': 'tests/test_atoi.c', 'parsefile': 'tests/test_parsefile.c', 'atlas': 'tests/test_atlas.c', + 'swapstruct': 'tests/test_swapstruct.c', } for i in tests: