/*
Copyright (C) 2019 AleaJactaEst
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.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Build :
scons platform=linux bits=64
*/
// Temporary workaround until c++11 alignas()
#ifdef __GNUC__
#define GCC_ALIGNED_8 __attribute__((aligned(8)))
#else
#define GCC_ALIGNED_8
#endif
#include "modules/bitstream/bitstream.h"
#include "modules/debug/debug.h"
#include "modules/bitstream/convert_utf16.h"
// TODO - check if on godot we have a function or variable to get if processor is little_endian or not
bool little_endian = false;
void BitStream::_bind_methods()
{
BitStream::init();
ClassDB::bind_method(D_METHOD("is_little_endian"), &BitStream::is_little_endian);
ClassDB::bind_method(D_METHOD("complet_byte"), &BitStream::complet_byte);
ClassDB::bind_method(D_METHOD("size"), &BitStream::size);
ClassDB::bind_method(D_METHOD("size_data"), &BitStream::size_data);
ClassDB::bind_method(D_METHOD("number_bit_not_read"), &BitStream::number_bit_not_read);
ClassDB::bind_method(D_METHOD("put_serial", "value", "nbits"), &BitStream::put_serial);
ClassDB::bind_method(D_METHOD("put_bool", "value"), &BitStream::put_bool);
ClassDB::bind_method(D_METHOD("put_char", "value"), &BitStream::put_char);
ClassDB::bind_method(D_METHOD("put_uint8", "value"), &BitStream::put_uint8);
ClassDB::bind_method(D_METHOD("put_sint8", "value"), &BitStream::put_sint8);
ClassDB::bind_method(D_METHOD("put_sint16", "value"), &BitStream::put_sint16);
ClassDB::bind_method(D_METHOD("put_uint16", "value"), &BitStream::put_uint16);
ClassDB::bind_method(D_METHOD("put_sint32", "value"), &BitStream::put_sint32);
ClassDB::bind_method(D_METHOD("put_uint32", "value"), &BitStream::put_uint32);
ClassDB::bind_method(D_METHOD("put_sint64", "value"), &BitStream::put_sint64);
ClassDB::bind_method(D_METHOD("put_uint64", "value"), &BitStream::put_uint64);
ClassDB::bind_method(D_METHOD("put_string", "value"), &BitStream::put_string);
ClassDB::bind_method(D_METHOD("put_string_hexa32", "hexa"), &BitStream::put_string_hexa32);
ClassDB::bind_method(D_METHOD("put_array_uint8", "value"), &BitStream::put_array_uint8);
ClassDB::bind_method(D_METHOD("put_version", "value"), &BitStream::put_version);
ClassDB::bind_method(D_METHOD("put_string_utf16", "value"), &BitStream::put_string_utf16);
ClassDB::bind_method(D_METHOD("show"), &BitStream::show);
ClassDB::bind_method(D_METHOD("show_detail"), &BitStream::show_detail);
ClassDB::bind_method(D_METHOD("show_counter"), &BitStream::show_counter);
ClassDB::bind_method(D_METHOD("get_data"), &BitStream::get_data);
ClassDB::bind_method(D_METHOD("put_data", "value"), &BitStream::put_data);
ClassDB::bind_method(D_METHOD("get_serial", "nbits"), &BitStream::get_serial);
ClassDB::bind_method(D_METHOD("get_bool"), &BitStream::get_bool);
ClassDB::bind_method(D_METHOD("get_sint8"), &BitStream::get_sint8);
ClassDB::bind_method(D_METHOD("get_uint8"), &BitStream::get_uint8);
ClassDB::bind_method(D_METHOD("get_sint16"), &BitStream::get_sint16);
ClassDB::bind_method(D_METHOD("get_uint16"), &BitStream::get_uint16);
ClassDB::bind_method(D_METHOD("get_sint32"), &BitStream::get_sint32);
ClassDB::bind_method(D_METHOD("get_uint32"), &BitStream::get_uint32);
ClassDB::bind_method(D_METHOD("get_sint64"), &BitStream::get_sint64);
ClassDB::bind_method(D_METHOD("get_uint64"), &BitStream::get_uint64);
ClassDB::bind_method(D_METHOD("get_version"), &BitStream::get_version);
ClassDB::bind_method(D_METHOD("get_array_uint8", "length"), &BitStream::get_array_uint8);
//ClassDB::bind_method(D_METHOD("get_bitstream", "length"), &BitStream::get_bitstream);
ClassDB::bind_method(D_METHOD("get_string"), &BitStream::get_string);
ClassDB::bind_method(D_METHOD("get_string_utf16"), &BitStream::get_string_utf16);
ClassDB::bind_method(D_METHOD("get_string_utf8"), &BitStream::get_string_utf8);
}
void BitStream::clear()
{
this->_pos = 0;
this->_read = 0;
}
BitStream::BitStream()
{
clear();
}
BitStream::~BitStream()
{
// add your cleanup here
}
void BitStream::init()
{
uint32_t i=0x12345678;
if ( (*((uint8_t*)(&i))) == 0x78 )
little_endian = true;
else
little_endian = false;
}
bool BitStream::is_little_endian()
{
return little_endian;
}
void BitStream::complet_byte()
{
// function to add 0 and have size multiple of 8 bit
this->_pos = ((this->_pos + 7) / 8) * 8;
}
uint32_t BitStream::size()
{
return (this->_pos + 7) / 8;
}
uint32_t BitStream::size_data()
{
return this->_pos;
}
uint32_t BitStream::number_bit_not_read()
{
return this->_pos - this->_read;
}
void BitStream::put_serial(uint32_t value, uint32_t nbits)
{
uint32_t v;
uint32_t mask;
uint32_t freeBits;
uint32_t pos;
if ( nbits == 0 )
return;
else if ( nbits > 32 )
throw "Out of range";
if ( this->_pos % 8 == 0 ) // Increase node
this->_data.append(0);
if ( nbits != 32 )
{
mask = (1 << nbits) - 1;
v = value & mask;
}
else
{
v = value;
}
freeBits = 8 - (this->_pos % 8);
pos = this->_data.size() - 1;
if(nbits > freeBits)
{
this->_data.set(pos, this->_data[pos] | (v >> (nbits - freeBits)));
this->_pos += freeBits;
this->put_serial(v, nbits - freeBits);
}
else
{
this->_data.set(pos, this->_data[pos] | (v << (freeBits - nbits)));
this->_pos += nbits;
}
}
void BitStream::put_bool(bool value)
{
this->put_serial(value, 1);
}
void BitStream::put_sint8(int8_t value)
{
this->put_serial(value, 8);
}
void BitStream::put_uint8(uint8_t value)
{
this->put_serial(value, 8);
}
void BitStream::put_sint16(int16_t value)
{
this->put_serial(value, 16);
}
void BitStream::put_uint16(uint16_t value)
{
this->put_serial(value, 16);
}
void BitStream::put_sint32(int32_t value)
{
this->put_serial(value, 32);
}
void BitStream::put_uint32(uint32_t value)
{
this->put_serial(value, 32);
}
void BitStream::put_uint64(uint64_t value)
{
// khanat-opennel-code/code/nel/include/nel/misc/stream_inline.h inline void IStream::serial(uint64 &b)
// khanat-opennel-code/code/nel/include/nel/misc/stream.h #define NLMISC_BSWAP64(src) (src) = (((src)>>56)&0xFF) | ((((src)>>48)&0xFF)<<8) | ((((src)>>40)&0xFF)<<16) | ((((src)>>32)&0xFF)<<24) | ((((src)>>24)&0xFF)<<32) | ((((src)>>16)&0xFF)<<40) | ((((src)>>8)&0xFF)<<48) | (((src)&0xFF)<<56)
union {
uint64_t full;
uint8_t key[8];
} v GCC_ALIGNED_8;
v.full = value;
if(little_endian)
{
this->put_uint8(v.key[3]);
this->put_uint8(v.key[2]);
this->put_uint8(v.key[1]);
this->put_uint8(v.key[0]);
this->put_uint8(v.key[7]);
this->put_uint8(v.key[6]);
this->put_uint8(v.key[5]);
this->put_uint8(v.key[4]);
}
else
{
this->put_uint8(v.key[0]);
this->put_uint8(v.key[1]);
this->put_uint8(v.key[2]);
this->put_uint8(v.key[3]);
this->put_uint8(v.key[4]);
this->put_uint8(v.key[5]);
this->put_uint8(v.key[6]);
this->put_uint8(v.key[7]);
}
}
void BitStream::put_sint64(int64_t value)
{
put_uint64((uint64_t) value);
}
void BitStream::put_string_hexa32(String hexa)
{
uint32_t res = 0;
CharString tmp = hexa.ascii();
for(const char * c = tmp.get_data(); *c != 0; c++)
{
res <<= 4;
if ( *c >= '0' && *c <= '9')
res += *c - '0';
else if ( *c >= 'A' && *c <= 'F')
res += *c - 'A' + 10;
}
this->put_serial(res,32);
}
void BitStream::put_char(String value)
{
if ( value.length() >= 1)
this->put_uint8( value[0] );
else
this->put_uint8( 0 );
}
void BitStream::put_string(String value)
{
CharString data = value.ascii();
int lenvalue = data.size() - 1;
this->put_uint32(lenvalue);
for(int i = 0; i < lenvalue; ++i)
{
this->put_uint8(data[i]);
}
}
void BitStream::put_array_uint8(PoolByteArray value)
{
for(int i = 0; i < value.size() ; ++i)
this->put_serial(value[i], 8);
}
void BitStream::put_bitstream(BitStream & value)
{
uint32_t last_read;
uint32_t nb;
last_read = value._read;
value._read = 0;
while ( value.number_bit_not_read() >= 32 )
put_uint32(value.get_uint32());
while ( value.number_bit_not_read() >= 16 )
put_uint16(value.get_uint16());
while ( value.number_bit_not_read() >= 8 )
put_uint8(value.get_uint8());
nb = value.number_bit_not_read();
if( nb > 0 )
put_serial(value.get_serial(nb), nb);
// restore pos read
value._read = last_read;
}
void BitStream::put_bitstream(BitStream * value)
{
uint32_t last_read;
uint32_t nb;
last_read = value->_read;
value->_read = 0;
while ( value->number_bit_not_read() >= 32 )
put_uint32(value->get_uint32());
while ( value->number_bit_not_read() >= 16 )
put_uint16(value->get_uint16());
while ( value->number_bit_not_read() >= 8 )
put_uint8(value->get_uint8());
nb = value->number_bit_not_read();
if( nb > 0 )
put_serial(value->get_serial(nb), nb);
DBG_PRINT("put_bitstream: " + itos(nb) + " / " + itos(_pos));
// restore pos read
value->_read = last_read;
}
void BitStream::put_version(uint32_t value)
{
if ( value < 255 ) // 0xff
{
this->put_uint8(value);
}
else
{
this->put_uint8(255);
this->put_uint32(value);
}
}
void BitStream::put_float(float value)
{
union {
float f;
uint32_t u;
} data GCC_ALIGNED_8;
data.f = value;
put_uint32(data.u);
}
void BitStream::put_string_utf16(String value)
{
uint16_t * u16_buf = NULL;
size_t u16_len = 0;
convert_utf8_to_utf16(value, &u16_buf, &u16_len);
this->put_uint32(u16_len);
if ( u16_len == 0 )
return;
for(uint32_t i=0;iput_uint16(u16_buf[i]);
}
delete [] u16_buf;
}
String BitStream::show()
{
String ret;
uint32_t pos = 0;
for(int i = 0; i < this->_data.size(); ++i)
{
uint8_t c = this->_data[i];
uint8_t mask = 0x80;
while(mask)
{
if ( (c & mask) != 0 )
ret += String("1");
else
ret += String("0");
mask >>= 1;
pos += 1;
if (pos >= this->_pos)
break;
}
if (pos >= this->_pos)
break;
}
return ret ;
}
String BitStream::show_detail()
{
String ret;
uint32_t pos = 0;
for(int i = 0; i < this->_data.size(); ++i)
{
uint8_t c = this->_data[i];
uint8_t mask = 0x80;
while(mask)
{
if ( (c & mask) != 0 )
ret += String("1");
else
ret += String("0");
mask >>= 1;
pos += 1;
if (pos >= this->_pos)
break;
}
if (pos >= this->_pos)
break;
}
String strsize;
uint32_t t = this->_data.size();
if ( t == 0) strsize = "0";
while ( t > 0 )
{
const char c = '0' + ( t % 10 );
CharString tmp ;
tmp += c;
strsize = tmp + strsize;
t /= 10;
}
String strpos;
t = this->_pos;
if ( t == 0) strpos = "0";
while ( t > 0 )
{
const char c = '0' + ( t % 10 );
CharString tmp ;
tmp += c;
strpos = tmp + strpos;
t /= 10;
}
return "[size:" + String(strsize) + ", pos:" + String(strpos) + "]" + ret ;
}
String BitStream::show_counter()
{
String ret = "[" + String::num_int64(this->_read) + " / " + String::num_int64(this->_pos) + "]";
return ret;
}
PoolByteArray BitStream::get_data()
{
return this->_data;
}
void BitStream::put_data(PoolByteArray value)
{
this->_data = value;
this->_pos = value.size() * 8;
}
void BitStream::copy_ref_bitstream(Ref value)
{
this->_data = value->_data; //*value.ptr();
this->_pos = value->_pos; // ->size() * 8;
this->_read = value->_read;
}
void BitStream::copy_bitstream(BitStream & value)
{
this->_data = value._data; //*value.ptr();
this->_pos = value._pos; // ->size() * 8;
this->_read = value._read;
}
uint32_t BitStream::get_serial(uint32_t nbits)
{
uint32_t value;
uint32_t pos;
uint32_t freeBits;
uint32_t v;
if ( nbits == 0 )
return 0;
else if ( nbits > 32 )
{
ERR_PRINT("Out of range (BitStream::get_serial)");
throw "Out of range";
}
if (this->_read + nbits > this->_pos)
{
ERR_PRINT("Out of range (BitStream::get_serial)");
throw "Out of range";
}
value = 0;
pos = this->_read / 8;
freeBits = 8 - (this->_read % 8);
v = this->_data[pos] & ((1 << freeBits) - 1);
if(nbits > freeBits)
{
value |= (v << (nbits-freeBits));
this->_read += freeBits;
value |= this->get_serial(nbits - freeBits);
}
else
{
value |= (v >> (freeBits-nbits));
this->_read += nbits;
}
return value;
}
bool BitStream::get_bool()
{
if(this->get_serial(1) == 0)
return false;
return true;
}
int8_t BitStream::get_sint8()
{
return this->get_serial(8);
}
uint8_t BitStream::get_uint8()
{
return this->get_serial(8);
}
int16_t BitStream::get_sint16()
{
return this->get_serial(16);
}
uint16_t BitStream::get_uint16()
{
return this->get_serial(16);
}
int32_t BitStream::get_sint32()
{
return this->get_serial(32);
}
uint32_t BitStream::get_uint32()
{
return this->get_serial(32);
}
uint64_t BitStream::get_uint64()
{
// khanat-opennel-code/code/nel/include/nel/misc/stream_inline.h inline void IStream::serial(uint64 &b)
// khanat-opennel-code/code/nel/include/nel/misc/stream.h #define NLMISC_BSWAP64(src) (src) = (((src)>>56)&0xFF) | ((((src)>>48)&0xFF)<<8) | ((((src)>>40)&0xFF)<<16) | ((((src)>>32)&0xFF)<<24) | ((((src)>>24)&0xFF)<<32) | ((((src)>>16)&0xFF)<<40) | ((((src)>>8)&0xFF)<<48) | (((src)&0xFF)<<56)
union S {
uint64_t full;
uint8_t key[8];
} v GCC_ALIGNED_8;
if(little_endian)
{
v.key[3] = this->get_uint8();
v.key[2] = this->get_uint8();
v.key[1] = this->get_uint8();
v.key[0] = this->get_uint8();
v.key[7] = this->get_uint8();
v.key[6] = this->get_uint8();
v.key[5] = this->get_uint8();
v.key[4] = this->get_uint8();
}
else
{
v.key[0] = this->get_uint8();
v.key[1] = this->get_uint8();
v.key[2] = this->get_uint8();
v.key[3] = this->get_uint8();
v.key[4] = this->get_uint8();
v.key[5] = this->get_uint8();
v.key[6] = this->get_uint8();
v.key[7] = this->get_uint8();
}
return v.full;
}
int64_t BitStream::get_sint64()
{
return (int64_t) get_uint64();
}
uint32_t BitStream::get_version()
{
uint32_t ret;
ret = this->get_uint8();
if ( ret == 255 ) // 0xff
{
ret = this->get_uint32();
}
return ret;
}
float BitStream::get_float()
{
union {
float f;
uint32_t u;
} data GCC_ALIGNED_8;
data.u = get_uint32();
return data.f;
}
PoolByteArray BitStream::get_array_uint8(uint32_t length)
{
PoolByteArray ret;
while(length > 0)
{
ret.append(this->get_serial(8));
--length;
}
/*
if (length == 1)
{
int nb = this->number_bit_not_read();
ret.append(this->get_serial(nb));
}
*/
return ret;
}
BitStream BitStream::get_bitstream(uint32_t length)
{
BitStream a;
while(length != 0)
{
a.put_uint8(this->get_serial(8));
--length;
}
return a;
}
String BitStream::get_string()
{
String ret;
uint32_t size = this->get_uint32();
while(size > 0)
{
union
{
uint8_t v;
char c;
} x GCC_ALIGNED_8;
x.v = this->get_uint8();
CharString cs;
cs += x.c;
ret += cs;
--size;
}
return ret;
}
String BitStream::get_string_utf16()
{
// https://en.wikipedia.org/wiki/UTF-16
// https://gist.github.com/rsms/716794
String ret;
uint32_t size = this->get_uint32();
uint32_t i = 0;
uint8_t tmp[size];
for(i=0;iget_uint16();
}
ret = convert_utf16_to_utf8(tmp, i);
return ret;
}
String BitStream::get_string_utf8()
{
// https://en.wikipedia.org/wiki/UTF-8
String ret;
uint32_t i;
uint32_t size = this->get_uint32();
char * src = new char[size+1];
src[size] = 0;
for(i=0;iget_uint8();
src[i] = x.c;
}
ret = String::utf8(src);
delete [] src;
return ret;
}