#include #include #include #include #include namespace str_obf { namespace internal { template struct message { /* helper to access the types */ static constexpr auto _size = size; using _char_t = char_t; using _key_t = key_t; /* memory */ std::array buffer{0}; key_t key{}; /* some memory access helpers */ std::string_view string_view() const noexcept { return {this->buffer.begin(), this->length}; } std::string string() const { return {this->buffer.begin(), this->length}; } const char* c_str() const noexcept { return &this->buffer[0]; } }; constexpr auto time_seed() noexcept { std::uint64_t shifted = 0; for( const auto c : __TIME__ ) { shifted <<= 8; shifted |= c; } return shifted; } constexpr uint64_t string_hash(const char* str, int h = 0) noexcept { return !str[h] ? 5381 : (string_hash(str, h + 1) * 33) ^ str[h]; } #ifdef WIN32 #pragma warning(disable: 4146) // unary minus operator applied to unsigned type, result still unsigned #endif constexpr std::uint32_t rng32_next(std::uint64_t& state, const std::uint32_t& inc) noexcept { std::uint64_t oldstate = state; // Advance internal state state = oldstate * 6364136223846793005ULL + (inc | 1UL); // Calculate output function (XSH RR), uses old state for max ILP std::uint32_t xorshifted = (uint32_t) (((oldstate >> 18u) ^ oldstate) >> 27u); std::uint32_t rot = oldstate >> 59u; return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); } #ifdef WIN32 #pragma warning(default: 4146) // unary minus operator applied to unsigned type, result still unsigned #endif /* 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 constexpr inline void crypt(char_t* begin, size_t length, const key_t& key) noexcept { static_assert(sizeof(char_t) == 1, "Currently only 8 bit supported"); if(length == 0) return; auto kbegin = std::begin(key); auto kend = std::end(key); if(kbegin == kend) return; auto it = kbegin; 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; *begin ^= *it; it++; begin++; } } template 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; { auto bit = result.buffer.begin(); auto mit = message; 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, (uint32_t) rng_seed) & 0xFFUL; } } crypt(result.buffer.data(), message_size, key); return result; } 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); memcpy(result.data(), message.buffer.begin(), message_size); crypt(result.data(), message_size, message.key); return result; } 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 = (size_t) ((internal::rng32_next(rng_base, (uint32_t) 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; constexpr size_t key_length = generate_key_length(internal::time_seed() ^ (line_number << 37UL), max_size); std::array result{}; for(auto& it : result) it = (internal::rng32_next(rng_base, (uint32_t) rng_seed) >> 16UL) & 0xFFUL; return result; } template struct decode_helper { const message& encoded; std::array buffer{0}; bool decoded = false; /* a trivial check which (if this only gets used once) the compiler could evaluate */ #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.data(), message::_size); crypt< typename message::_char_t, typename message::_key_t >((typename message::_char_t*) &this->buffer[0], message::_size, this->encoded.key); this->decoded = true; } return &this->buffer[0]; } inline std::string_view string_view() noexcept { return {this->c_str(), message::_size - 1}; } inline std::string string() { return {this->c_str(), message::_size - 1}; } //operator const char*() noexcept { return this->c_str(); } }; } #define strobf_define(variable_name, string) \ 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}) #define strobf(message) \ (([]{ \ static strobf_define(_, message); \ return strobf_val(_); \ })())