diff --git a/src/misc/strobf.h b/src/misc/strobf.h index aa2a1da..11cfb90 100644 --- a/src/misc/strobf.h +++ b/src/misc/strobf.h @@ -6,7 +6,7 @@ namespace str_obf { namespace internal { - template + template struct message { /* helper to access the types */ static constexpr auto _size = size; @@ -14,7 +14,7 @@ namespace str_obf { using _key_t = key_t; /* memory */ - std::array buffer{0}; + std::array buffer{0}; key_t key{}; /* some memory access helpers */ @@ -48,6 +48,11 @@ namespace str_obf { std::uint32_t rot = oldstate >> 59u; return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); } + + /* we use a buffer dividable by 8 so the compiler could do crazy shit, when loading (moving) the characters */ + constexpr size_t recommand_message_buffer(size_t message_size) noexcept { + return (message_size & 0xFFFFFFF8) + ((message_size & 0x7) > 0 ? 8 : 0); + } } template @@ -60,7 +65,17 @@ namespace str_obf { if(kbegin == kend) return; auto it = kbegin; - while(length-- > 0) { + + auto left = length; + +#ifdef __clang__ + /* + * Enforce clang here to not evaluate this loop at compile time as long its not called in a constexpr context! + * We lose compiler opts. here a bit, but cases where a xor was made over larger than 8 bit registers were really rare! + */ + #pragma nounroll +#endif + while(left-- > 0) { if(it == kend) it = kbegin; @@ -72,8 +87,9 @@ namespace str_obf { } template - constexpr inline internal::message encode(const char_t(&message)[message_size], const key_t& key) noexcept { - internal::message result{}; + constexpr inline auto encode(const char_t(&message)[message_size], const key_t& key) noexcept { + constexpr auto message_buffer_size = internal::recommand_message_buffer(message_size); + internal::message result{}; result.key = key; { @@ -83,14 +99,25 @@ namespace str_obf { size_t index = message_size; while(index-- > 0) *bit++ = *mit++; + + size_t padding = message_buffer_size - message_size; + if(padding) { /* to make the string end less obvious we add some noise here (it does not harm user performance) */ + std::uint64_t rng_seed = internal::time_seed() ^ internal::string_hash(message, 0); + std::uint64_t rng_base = rng_seed; + while(padding-- > 0) + *bit++ = internal::rng32_next(rng_base, rng_seed) & 0xFFUL; + } } - crypt(result.buffer.data(), message_size, key); + crypt(result.buffer.data(), message_size, key); return result; } - template - inline std::string decode(const internal::message& message) { + template + constexpr inline auto str_length(const char_t(&message)[length]) noexcept { return length; } + + template + inline std::string decode(const internal::message& message) { std::string result{}; result.resize(message_size); @@ -100,11 +127,27 @@ namespace str_obf { return result; } - constexpr inline std::array generate_key(const char* _str_seed, unsigned int line) noexcept { - std::uint64_t rng_seed = internal::time_seed() ^ internal::string_hash(_str_seed, 0) ^ line; + constexpr inline size_t generate_key_length(std::uint64_t seed, size_t max_size) noexcept { + if(max_size <= 8) return max_size; /* We dont need a longer key then the message itself. As well compiler opt. doesn't matter here */ + if(max_size > 64) max_size = 64; + + std::uint64_t rng_base = seed; + size_t length = 0; + do { + length = (internal::rng32_next(rng_base, seed) >> 12UL) & 0xFFUL; + } while(length < 8 || length >= max_size); + + /* it does not really matter if we have a 8 byte aligned number here, because we iterate so or so byte for byte */ + return length; + } + + template + constexpr inline auto generate_key(const char* _str_seed) noexcept { + std::uint64_t rng_seed = internal::time_seed() ^ internal::string_hash(_str_seed, 0) ^ line_number; std::uint64_t rng_base = rng_seed; - std::array result{}; + constexpr size_t key_length = generate_key_length(internal::time_seed() ^ (line_number << 37), max_size); + std::array result{}; for(auto& it : result) it = (internal::rng32_next(rng_base, rng_seed) >> 16UL) & 0xFFUL; return result; @@ -116,15 +159,20 @@ namespace str_obf { std::array buffer{0}; bool decoded = false; /* a trivial check which (if this only gets used once) the compiler could evaluate */ -#ifndef WIN32 /* else if you call string_view() or string() it wound inline this method */ - __attribute__((always_inline)) +#ifndef _MSC_VER /* else if you call string_view() or string() it wound inline this method */ + __attribute__((always_inline)) inline +#else + __forceinline #endif const char* c_str() noexcept { if(!this->decoded) { memcpy(this->buffer.data(), this->encoded.buffer.begin(), message::_size); - crypt((typename message::_char_t*) this->buffer.data(), message::_size, this->encoded.key); + crypt< + typename message::_char_t, + typename message::_key_t + >((typename message::_char_t*) &this->buffer[0], message::_size, this->encoded.key); buffer[message::_size] = 0; /* append the null terminator at the end */ - this->decoded = true; /* let the compiler combine buffer[message::_size] and this->decoded = true; */ + this->decoded = true; } return &this->buffer[0]; @@ -135,10 +183,18 @@ namespace str_obf { } inline std::string string() { return {this->c_str(), message::_size}; } + + //operator const char*() noexcept { return this->c_str(); } }; } #define strobf_define(variable_name, string) \ -constexpr const auto variable_name = ::str_obf::encode(string, str_obf::generate_key(__FILE__ __TIME__, __LINE__)) +constexpr auto variable_name = ::str_obf::encode(string, str_obf::generate_key<__LINE__, str_obf::str_length(string)>(__FILE__ __TIME__)) -#define strobf_val(variable_name) (::str_obf::decode_helper{variable_name}) \ No newline at end of file +#define strobf_val(variable_name) (str_obf::decode_helper{variable_name}) + +#define strobf(message) \ +(([]{ \ + static strobf_define(_, message); \ + return strobf_val(_); \ +})()) \ No newline at end of file