/** * Ashita SDK - Copyright (c) 2023 Ashita Development Team * Contact: https://www.ashitaxi.com/ * Contact: https://discord.gg/Ashita * * This file is part of Ashita. * * Ashita is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Ashita 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Ashita. If not, see . */ #ifndef ASHITA_SDK_BINARYDATA_H_INCLUDED #define ASHITA_SDK_BINARYDATA_H_INCLUDED #if defined(_MSC_VER) && (_MSC_VER >= 1020) #pragma once #endif #include /** * Credits to the original ProjectXI authors that made the original versions of these functions. */ namespace Ashita { class BinaryData { public: /** * Packs a value into the given buffer. (Big Endian) * * @param {uint8_t*} data - The data to pack the value into. * @param {uint64_t} value - The value to pack. * @param {uint32_t} byteOffset - The byte offset to pack the value at. * @param {uint32_t} bitOffset - The bit offset to pack the value at. * @param {uint8_t} len - The length of the value being packed. * @return {uint32_t} The bit offset where the value ends. */ static uint32_t PackBitsBE(uint8_t* data, uint64_t value, uint32_t byteOffset, uint32_t bitOffset, const uint8_t len) { // Adjust the offsets as needed for bit alignment.. byteOffset += bitOffset >> 3; bitOffset %= 8; // Prepare the bit mask and value.. auto bitmask = (uint64_t)0xFFFFFFFFFFFFFFFFLL; bitmask >>= 64 - len; bitmask <<= bitOffset; value <<= bitOffset; value &= bitmask; bitmask ^= 0xFFFFFFFFFFFFFFFFLL; // Pack the data based on the size (type).. if (len + bitOffset <= 8) { const auto ptr = &data[byteOffset]; const auto mask = (uint8_t)bitmask; const auto val = (uint8_t)value; *ptr &= mask; *ptr |= val; } else if (len + bitOffset <= 16) { const auto ptr = (uint16_t*)&data[byteOffset]; const auto mask = (uint16_t)bitmask; const auto val = (uint16_t)value; *ptr &= mask; *ptr |= val; } else if (len + bitOffset <= 32) { const auto ptr = (uint32_t*)&data[byteOffset]; const auto mask = (uint32_t)bitmask; const auto val = (uint32_t)value; *ptr &= mask; *ptr |= val; } else if (len + bitOffset <= 64) { const auto ptr = (uint64_t*)&data[byteOffset]; *ptr &= bitmask; *ptr |= value; } else { // This should never be hit. (Data size > 64bits.) } return (byteOffset << 3) + bitOffset + len; } /** * Packs a value into the given buffer. (Big Endian) * * @param {uint8_t*} data - The data to pack the value into. * @param {uint64_t} value - The value to pack. * @param {uint32_t} offset - The bit offset to pack the value at. * @param {uint8_t} len - The length of the value being packed. * @return {uint32_t} The bit offset where the value ends. */ static uint32_t PackBitsBE(uint8_t* data, const uint64_t value, const uint32_t offset, const uint8_t len) { return Ashita::BinaryData::PackBitsBE(data, value, 0, offset, len); } /** * Packs a value into the given buffer. (Little Endian) * * @param {uint8_t*} data - The data to pack the value into. * @param {uint64_t} value - The value to pack. * @param {uint32_t} byteOffset - The byte offset to pack the value at. * @param {uint32_t} bitOffset - The bit offset to pack the value at. * @param {uint8_t} len - The length of the value being packed. * @return {uint32_t} The bit offset where the value ends. */ static uint32_t PackBitsLE(uint8_t* data, const uint64_t value, uint32_t byteOffset, uint32_t bitOffset, const uint8_t len) { // Adjust the offsets as needed for bit alignment.. byteOffset += bitOffset >> 3; bitOffset %= 8; // Determine the bytes required.. uint8_t bytesNeeded; if (bitOffset + len <= 8) bytesNeeded = 1; else if (bitOffset + len <= 16) bytesNeeded = 2; else if (bitOffset + len <= 32) bytesNeeded = 4; else if (bitOffset + len <= 64) bytesNeeded = 8; else { // This should never be hit. (Data size > 64bits.) return 0; } // Write the packed data.. auto modified = new uint8_t[bytesNeeded]; for (uint8_t c = 0; c < bytesNeeded; ++c) modified[c] = data[byteOffset + (bytesNeeded - 1) - c]; const int32_t nbo = (bytesNeeded << 3) - (bitOffset + len); Ashita::BinaryData::PackBitsBE(&modified[0], value, 0, nbo, len); for (uint8_t c = 0; c < bytesNeeded; ++c) data[byteOffset + (bytesNeeded - 1) - c] = modified[c]; // Cleanup.. if (modified) { delete[] modified; modified = nullptr; } return (byteOffset << 3) + bitOffset + len; } /** * Packs a value into the given buffer. (Little Endian) * * @param {uint8_t*} data - The data to pack the value into. * @param {uint64_t} value - The value to pack. * @param {uint32_t} offset - The bit offset to pack the value at. * @param {uint8_t} len - The length of the value being packed. * @return {uint32_t} The bit offset where the value ends. */ static uint32_t PackBitsLE(uint8_t* data, const uint64_t value, const uint32_t offset, const uint8_t len) { return Ashita::BinaryData::PackBitsLE(data, value, 0, offset, len); } /** * Unpacks a value from the given buffer. (Big Endian) * * @param {uint8_t*} data - The data to unpack the value from. * @param {uint32_t} byteOffset - The byte offset to unpack the value at. * @param {uint32_t} bitOffset - The bit offset to unpack the value at. * @param {uint8_t} len - The length of bits to unpack. * @return {uint64_t} The unpacked value. */ static uint64_t UnpackBitsBE(uint8_t* data, uint32_t byteOffset, uint32_t bitOffset, const uint8_t len) { // Adjust the offsets as needed for bit alignment.. byteOffset += bitOffset >> 3; bitOffset %= 8; // Prepare the bit mask.. auto bitmask = (uint64_t)0xFFFFFFFFFFFFFFFFLL; bitmask >>= 64 - len; bitmask <<= bitOffset; // Unpack the value based on the size (type).. uint64_t ret; if (len + bitOffset <= 8) { const auto ptr = &data[byteOffset]; ret = (*ptr & (uint8_t)bitmask) >> bitOffset; } else if (len + bitOffset <= 16) { const auto ptr = (uint16_t*)&data[byteOffset]; ret = (*ptr & (uint16_t)bitmask) >> bitOffset; } else if (len + bitOffset <= 32) { const auto ptr = (uint32_t*)&data[byteOffset]; ret = (*ptr & (uint32_t)bitmask) >> bitOffset; } else if (len + bitOffset <= 64) { const auto ptr = (uint64_t*)&data[byteOffset]; ret = (*ptr & bitmask) >> bitOffset; } else { // This should never be hit. (Data size > 64bits.) return 0; } return ret; } /** * Unpacks a value from the given buffer. (Big Endian) * * @param {uint8_t*} data - The data to unpack the value from. * @param {uint32_t} offset - The bit offset to unpack the value at. * @param {uint8_t} len - The length of bits to unpack. * @return {uint64_t} The unpacked value. */ static uint64_t UnpackBitsBE(uint8_t* data, const uint32_t offset, const uint8_t len) { return Ashita::BinaryData::UnpackBitsBE(data, 0, offset, len); } /** * Unpacks a value from the given buffer. (Little Endian) * * @param {uint8_t*} data - The data to unpack the value from. * @param {uint32_t} byteOffset - The byte offset to unpack the value at. * @param {uint32_t} bitOffset - The bit offset to unpack the value at. * @param {uint8_t} len - The length of bits to unpack. * @return {uint64_t} The unpacked value. */ static uint64_t UnpackBitsLE(uint8_t* data, uint32_t byteOffset, uint32_t bitOffset, const uint8_t len) { // Adjust the offsets as needed for bit alignment.. byteOffset += bitOffset >> 3; bitOffset %= 8; // Determine the bytes required.. uint8_t bytesNeeded; if (bitOffset + len <= 8) bytesNeeded = 1; else if (bitOffset + len <= 16) bytesNeeded = 2; else if (bitOffset + len <= 32) bytesNeeded = 4; else if (bitOffset + len <= 64) bytesNeeded = 8; else { // This should never be hit. (Data size > 64bits.) return 0; } // Unpack the value based on the size (type).. uint64_t ret; auto modified = new uint8_t[bytesNeeded]; for (uint8_t c = 0; c < bytesNeeded; ++c) modified[c] = data[byteOffset + (bytesNeeded - 1) - c]; if (bytesNeeded == 1) { const uint8_t mask = 0xFF >> bitOffset; ret = (uint64_t)(modified[0] & mask) >> (8 - (len + bitOffset)); } else { const int32_t nbo = (bytesNeeded * 8) - (bitOffset + len); ret = Ashita::BinaryData::UnpackBitsBE(&modified[0], 0, nbo, len); } if (modified) { delete[] modified; modified = nullptr; } return ret; } /** * Unpacks a value from the given buffer. (Little Endian) * * @param {uint8_t*} data - The data to unpack the value from. * @param {uint32_t} offset - The bit offset to unpack the value at. * @param {uint8_t} len - The length of bits to unpack. * @return {uint64_t} The unpacked value. */ static uint64_t UnpackBitsLE(uint8_t* data, const uint32_t offset, const uint8_t len) { return Ashita::BinaryData::UnpackBitsLE(data, 0, offset, len); } /** * Tests if a bit is set within the given data. * * @param {uint16_t} value - The bit to test. * @param {uint8_t*} data - The data to check within. * @param {uint32_t} size - The size of the bits data. * @return {bool} True if set, false otherwise. */ static bool HasBit(const uint16_t value, uint8_t* data, const uint32_t size) { // Ensure the bit position doesn't exceed the size.. if (value >= size * 8) return false; return (data[value >> 3] & (1 << (value % 8))) > 0; } /** * Sets a bit within the given data. * * @param {uint16_t} value - The bit to set. * @param {uint8_t*} data - The data to set within. * @param {uint32_t} size - The size of the bits data. * @return {bool} True if set, false otherwise. */ static bool SetBit(const uint16_t value, uint8_t* data, const uint32_t size) { // Test if the bit is already set and that the bit position doesn't exceed the size.. if (!Ashita::BinaryData::HasBit(value, data, size) && (value < size * 8)) { // Set the bit.. data[value >> 3] |= (1 << (value % 8)); return true; } return false; } /** * Unsets a bit within the given data. * * @param {uint16_t} value - The bit to unset. * @param {uint8_t*} data - The data to unset within. * @param {uint32_t} size - The size of the bits data. * @return {bool} True if unset, false otherwise. */ static bool UnsetBit(const uint16_t value, uint8_t* data, const uint32_t size) { // Test if the bit is already set and that the bit position doesn't exceed the size.. if (Ashita::BinaryData::HasBit(value, data, size) && (value < size * 8)) { // Set the bit.. data[value >> 3] &= ~(1 << (value % 8)); return true; } return false; } }; } // namespace Ashita #endif // ASHITA_SDK_BINARYDATA_H_INCLUDED