diff options
Diffstat (limited to 'include/rres-raylib.h')
-rw-r--r-- | include/rres-raylib.h | 1094 |
1 files changed, 1094 insertions, 0 deletions
diff --git a/include/rres-raylib.h b/include/rres-raylib.h new file mode 100644 index 0000000..71d6f2a --- /dev/null +++ b/include/rres-raylib.h @@ -0,0 +1,1094 @@ +/********************************************************************************************** +* +* rres-raylib v1.2 - rres loaders specific for raylib data structures +* +* CONFIGURATION: +* +* #define RRES_RAYLIB_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RRES_SUPPORT_COMPRESSION_LZ4 +* Support data compression algorithm LZ4, provided by lz4.h/lz4.c library +* +* #define RRES_SUPPORT_ENCRYPTION_AES +* Support data encryption algorithm AES, provided by aes.h/aes.c library +* +* #define RRES_SUPPORT_ENCRYPTION_XCHACHA20 +* Support data encryption algorithm XChaCha20-Poly1305, +* provided by monocypher.h/monocypher.c library +* +* DEPENDENCIES: +* +* - raylib.h: Data types definition and data loading from memory functions +* WARNING: raylib.h MUST be included before including rres-raylib.h +* - rres.h: Base implementation of rres specs, required to read rres files and resource chunks +* - lz4.h: LZ4 compression support (optional) +* - aes.h: AES-256 CTR encryption support (optional) +* - monocypher.h: for XChaCha20-Poly1305 encryption support (optional) +* +* VERSION HISTORY: +* +* - 1.2 (15-Apr-2023): Updated to monocypher 4.0.1 +* - 1.0 (11-May-2022): Initial implementation release +* +* +* LICENSE: MIT +* +* Copyright (c) 2020-2023 Ramon Santamaria (@raysan5) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +**********************************************************************************************/ + +#ifndef RRES_RAYLIB_H +#define RRES_RAYLIB_H + +#ifndef RRES_H + #include "rres.h" +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Global variables +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// rres data loading to raylib data structures +// NOTE: Chunk data must be provided uncompressed/unencrypted +RLAPI void *LoadDataFromResource(rresResourceChunk chunk, unsigned int *size); // Load raw data from rres resource chunk +RLAPI char *LoadTextFromResource(rresResourceChunk chunk); // Load text data from rres resource chunk +RLAPI Image LoadImageFromResource(rresResourceChunk chunk); // Load Image data from rres resource chunk +RLAPI Wave LoadWaveFromResource(rresResourceChunk chunk); // Load Wave data from rres resource chunk +RLAPI Font LoadFontFromResource(rresResourceMulti multi); // Load Font data from rres resource multiple chunks +RLAPI Mesh LoadMeshFromResource(rresResourceMulti multi); // Load Mesh data from rres resource multiple chunks + +// Unpack resource chunk data (decompres/decrypt data) +// NOTE: Function return 0 on success or other value on failure +RLAPI int UnpackResourceChunk(rresResourceChunk *chunk); // Unpack resource chunk data (decompress/decrypt) + +// Set base directory for externally linked data +// NOTE: When resource chunk contains an external link (FourCC: LINK, Type: RRES_DATA_LINK), +// a base directory is required to be prepended to link path +// If not provided, the application path is prepended to link by default +RLAPI void SetBaseDirectory(const char *baseDir); // Set base directory for externally linked data + +#if defined(__cplusplus) +} +#endif + +#endif // RRES_RAYLIB_H + +/*********************************************************************************** +* +* RRES RAYLIB IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RRES_RAYLIB_IMPLEMENTATION) + +// Compression/Encryption algorithms supported +// NOTE: They should be the same supported by the rres packaging tool (rrespacker) +// https://github.com/phoboslab/qoi +#include "external/qoi.h" // Compression algorithm: QOI (implementation in raylib) + +#if defined(RRES_SUPPORT_COMPRESSION_LZ4) + // https://github.com/lz4/lz4 + #include "external/lz4.h" // Compression algorithm: LZ4 + #include "external/lz4.c" // Compression algorithm implementation: LZ4 +#endif +#if defined(RRES_SUPPORT_ENCRYPTION_AES) + // https://github.com/kokke/tiny-AES-c + #include "external/aes.h" // Encryption algorithm: AES + #include "external/aes.c" // Encryption algorithm implementation: AES +#endif +#if defined(RRES_SUPPORT_ENCRYPTION_XCHACHA20) + // https://github.com/LoupVaillant/Monocypher + #include "external/monocypher.h" // Encryption algorithm: XChaCha20-Poly1305 + #include "external/monocypher.c" // Encryption algorithm implementation: XChaCha20-Poly1305 +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static const char *baseDir = NULL; // Base directory pointer, used on external linked data loading + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- + +// Load simple data chunks that are later required by multi-chunk resources +// NOTE: Chunk data must be provided uncompressed/unencrypted +static void *LoadDataFromResourceLink(rresResourceChunk chunk, unsigned int *size); // Load chunk: RRES_DATA_LINK +static void *LoadDataFromResourceChunk(rresResourceChunk chunk, unsigned int *size); // Load chunk: RRES_DATA_RAW +static char *LoadTextFromResourceChunk(rresResourceChunk chunk, unsigned int *codeLang); // Load chunk: RRES_DATA_TEXT +static Image LoadImageFromResourceChunk(rresResourceChunk chunk); // Load chunk: RRES_DATA_IMAGE + +static const char *GetExtensionFromProps(unsigned int ext01, unsigned int ext02); // Get file extension from RRES_DATA_RAW properties (unsigned int) +static unsigned int *ComputeMD5(unsigned char *data, int size); // Compute MD5 hash code, returns 4 integers array (static) + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Load raw data from rres resource +void *LoadDataFromResource(rresResourceChunk chunk, unsigned int *size) +{ + void *rawData = NULL; + + // Data can be provided in the resource or linked to an external file + if (rresGetDataType(chunk.info.type) == RRES_DATA_RAW) // Raw data + { + rawData = LoadDataFromResourceChunk(chunk, size); + } + else if (rresGetDataType(chunk.info.type) == RRES_DATA_LINK) // Link to external file + { + // Get raw data from external linked file + unsigned int dataSize = 0; + void *data = LoadDataFromResourceLink(chunk, &dataSize); + + rawData = data; + *size = dataSize; + } + + return rawData; +} + +// Load text data from rres resource +// NOTE: Text must be NULL terminated +char *LoadTextFromResource(rresResourceChunk chunk) +{ + char *text = NULL; + int codeLang = 0; + + if (rresGetDataType(chunk.info.type) == RRES_DATA_TEXT) // Text data + { + text = LoadTextFromResourceChunk(chunk, &codeLang); + + // TODO: Consider text code language to load shader or code scripts + } + else if (rresGetDataType(chunk.info.type) == RRES_DATA_RAW) // Raw text file + { + unsigned int size = 0; + text = LoadDataFromResourceChunk(chunk, &size); + } + else if (rresGetDataType(chunk.info.type) == RRES_DATA_LINK) // Link to external file + { + // Get raw data from external linked file + unsigned int dataSize = 0; + void *data = LoadDataFromResourceLink(chunk, &dataSize); + text = data; + } + + return text; +} + +// Load Image data from rres resource +Image LoadImageFromResource(rresResourceChunk chunk) +{ + Image image = { 0 }; + + if (rresGetDataType(chunk.info.type) == RRES_DATA_IMAGE) // Image data + { + image = LoadImageFromResourceChunk(chunk); + } + else if (rresGetDataType(chunk.info.type) == RRES_DATA_RAW) // Raw image file + { + unsigned int dataSize = 0; + unsigned char *data = LoadDataFromResourceChunk(chunk, &dataSize); + + image = LoadImageFromMemory(GetExtensionFromProps(chunk.data.props[1], chunk.data.props[2]), data, dataSize); + + RL_FREE(data); + } + else if (rresGetDataType(chunk.info.type) == RRES_DATA_LINK) // Link to external file + { + // Get raw data from external linked file + unsigned int dataSize = 0; + void *data = LoadDataFromResourceLink(chunk, &dataSize); + + // Load image from linked file data + // NOTE: Function checks internally if the file extension is supported to + // properly load the data, if it fails it logs the result and image.data = NULL + image = LoadImageFromMemory(GetFileExtension(chunk.data.raw), data, dataSize); + } + + return image; +} + +// Load Wave data from rres resource +Wave LoadWaveFromResource(rresResourceChunk chunk) +{ + Wave wave = { 0 }; + + if (rresGetDataType(chunk.info.type) == RRES_DATA_WAVE) // Wave data + { + if ((chunk.info.compType == RRES_COMP_NONE) && (chunk.info.cipherType == RRES_CIPHER_NONE)) + { + wave.frameCount = chunk.data.props[0]; + wave.sampleRate = chunk.data.props[1]; + wave.sampleSize = chunk.data.props[2]; + wave.channels = chunk.data.props[3]; + + unsigned int size = wave.frameCount*wave.sampleSize/8; + wave.data = RL_CALLOC(size, 1); + memcpy(wave.data, chunk.data.raw, size); + } + RRES_LOG("RRES: %c%c%c%c: WARNING: Data must be decompressed/decrypted\n", chunk.info.type[0], chunk.info.type[1], chunk.info.type[2], chunk.info.type[3]); + } + else if (rresGetDataType(chunk.info.type) == RRES_DATA_RAW) // Raw wave file + { + unsigned int dataSize = 0; + unsigned char *data = LoadDataFromResourceChunk(chunk, &dataSize); + + wave = LoadWaveFromMemory(GetExtensionFromProps(chunk.data.props[1], chunk.data.props[2]), data, dataSize); + + RL_FREE(data); + } + else if (rresGetDataType(chunk.info.type) == RRES_DATA_LINK) // Link to external file + { + // Get raw data from external linked file + unsigned int dataSize = 0; + void *data = LoadDataFromResourceLink(chunk, &dataSize); + + // Load wave from linked file data + // NOTE: Function checks internally if the file extension is supported to + // properly load the data, if it fails it logs the result and wave.data = NULL + wave = LoadWaveFromMemory(GetFileExtension(chunk.data.raw), data, dataSize); + } + + return wave; +} + +// Load Font data from rres resource +Font LoadFontFromResource(rresResourceMulti multi) +{ + Font font = { 0 }; + + // Font resource consist of (2) chunks: + // - RRES_DATA_FONT_GLYPHS: Basic font and glyphs properties/data + // - RRES_DATA_IMAGE: Image atlas for the font characters + if (multi.count >= 2) + { + if (rresGetDataType(multi.chunks[0].info.type) == RRES_DATA_FONT_GLYPHS) + { + if ((multi.chunks[0].info.compType == RRES_COMP_NONE) && (multi.chunks[0].info.cipherType == RRES_CIPHER_NONE)) + { + // Load font basic properties from chunk[0] + font.baseSize = multi.chunks[0].data.props[0]; // Base size (default chars height) + font.glyphCount = multi.chunks[0].data.props[1]; // Number of characters (glyphs) + font.glyphPadding = multi.chunks[0].data.props[2]; // Padding around the chars + + font.recs = (Rectangle *)RL_CALLOC(font.glyphCount, sizeof(Rectangle)); + font.glyphs = (GlyphInfo *)RL_CALLOC(font.glyphCount, sizeof(GlyphInfo)); + + for (int i = 0; i < font.glyphCount; i++) + { + // Font glyphs info comes as a data blob + font.recs[i].x = (float)((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].x; + font.recs[i].y = (float)((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].y; + font.recs[i].width = (float)((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].width; + font.recs[i].height = (float)((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].height; + + font.glyphs[i].value = ((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].value; + font.glyphs[i].offsetX = ((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].offsetX; + font.glyphs[i].offsetY = ((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].offsetY; + font.glyphs[i].advanceX = ((rresFontGlyphInfo *)multi.chunks[0].data.raw)[i].advanceX; + + // NOTE: font.glyphs[i].image is not loaded + } + } + else RRES_LOG("RRES: %s: WARNING: Data must be decompressed/decrypted\n", multi.chunks[0].info.type); + } + + // Load font image chunk + if (rresGetDataType(multi.chunks[1].info.type) == RRES_DATA_IMAGE) + { + if ((multi.chunks[0].info.compType == RRES_COMP_NONE) && (multi.chunks[0].info.cipherType == RRES_CIPHER_NONE)) + { + Image image = LoadImageFromResourceChunk(multi.chunks[1]); + font.texture = LoadTextureFromImage(image); + UnloadImage(image); + } + else RRES_LOG("RRES: %s: WARNING: Data must be decompressed/decrypted\n", multi.chunks[1].info.type); + } + } + else // One chunk of data: RRES_DATA_RAW or RRES_DATA_LINK? + { + if (rresGetDataType(multi.chunks[0].info.type) == RRES_DATA_RAW) // Raw font file + { + unsigned int dataSize = 0; + unsigned char *rawData = LoadDataFromResourceChunk(multi.chunks[0], &dataSize); + + font = LoadFontFromMemory(GetExtensionFromProps(multi.chunks[0].data.props[1], multi.chunks[0].data.props[2]), rawData, dataSize, 32, NULL, 0); + + RL_FREE(rawData); + } + if (rresGetDataType(multi.chunks[0].info.type) == RRES_DATA_LINK) // Link to external font file + { + // Get raw data from external linked file + unsigned int dataSize = 0; + void *rawData = LoadDataFromResourceLink(multi.chunks[0], &dataSize); + + // Load image from linked file data + // NOTE 1: Loading font at 32px base size and default charset (95 glyphs) + // NOTE 2: Function checks internally if the file extension is supported to + // properly load the data, if it fails it logs the result and font.texture.id = 0 + font = LoadFontFromMemory(GetFileExtension(multi.chunks[0].data.raw), rawData, dataSize, 32, NULL, 0); + + RRES_FREE(rawData); + } + } + + return font; +} + +// Load Mesh data from rres resource +// NOTE: We try to load vertex data following raylib structure constraints, +// in case data does not fit raylib Mesh structure, it is not loaded +Mesh LoadMeshFromResource(rresResourceMulti multi) +{ + Mesh mesh = { 0 }; + + // TODO: Support externally linked mesh resource? + + // Mesh resource consist of (n) chunks: + for (unsigned int i = 0; i < multi.count; i++) + { + if ((multi.chunks[0].info.compType == RRES_COMP_NONE) && (multi.chunks[0].info.cipherType == RRES_CIPHER_NONE)) + { + // NOTE: raylib only supports vertex arrays with same vertex count, + // rres.chunks[0] defined vertexCount will be the reference for the following chunks + // The only exception to vertexCount is the mesh.indices array + if (mesh.vertexCount == 0) mesh.vertexCount = multi.chunks[0].data.props[0]; + + // Verify chunk type and vertex count + if (rresGetDataType(multi.chunks[i].info.type) == RRES_DATA_VERTEX) + { + // In case vertex count do not match we skip that resource chunk + if ((multi.chunks[i].data.props[1] != RRES_VERTEX_ATTRIBUTE_INDEX) && (multi.chunks[i].data.props[0] != mesh.vertexCount)) continue; + + // NOTE: We are only loading raylib supported rresVertexFormat and raylib expected components count + switch (multi.chunks[i].data.props[1]) // Check rresVertexAttribute value + { + case RRES_VERTEX_ATTRIBUTE_POSITION: + { + // raylib expects 3 components per vertex and float vertex format + if ((multi.chunks[i].data.props[2] == 3) && (multi.chunks[i].data.props[3] == RRES_VERTEX_FORMAT_FLOAT)) + { + mesh.vertices = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float)); + memcpy(mesh.vertices, multi.chunks[i].data.raw, mesh.vertexCount*3*sizeof(float)); + } + else RRES_LOG("RRES: WARNING: MESH: Vertex attribute position not valid, componentCount/vertexFormat do not fit\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_TEXCOORD1: + { + // raylib expects 2 components per vertex and float vertex format + if ((multi.chunks[i].data.props[2] == 2) && (multi.chunks[i].data.props[3] == RRES_VERTEX_FORMAT_FLOAT)) + { + mesh.texcoords = (float *)RL_CALLOC(mesh.vertexCount*2, sizeof(float)); + memcpy(mesh.texcoords, multi.chunks[i].data.raw, mesh.vertexCount*2*sizeof(float)); + } + else RRES_LOG("RRES: WARNING: MESH: Vertex attribute texcoord1 not valid, componentCount/vertexFormat do not fit\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_TEXCOORD2: + { + // raylib expects 2 components per vertex and float vertex format + if ((multi.chunks[i].data.props[2] == 2) && (multi.chunks[i].data.props[3] == RRES_VERTEX_FORMAT_FLOAT)) + { + mesh.texcoords2 = (float *)RL_CALLOC(mesh.vertexCount*2, sizeof(float)); + memcpy(mesh.texcoords2, multi.chunks[i].data.raw, mesh.vertexCount*2*sizeof(float)); + } + else RRES_LOG("RRES: WARNING: MESH: Vertex attribute texcoord2 not valid, componentCount/vertexFormat do not fit\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_TEXCOORD3: + { + RRES_LOG("RRES: WARNING: MESH: Vertex attribute texcoord3 not supported\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_TEXCOORD4: + { + RRES_LOG("RRES: WARNING: MESH: Vertex attribute texcoord4 not supported\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_NORMAL: + { + // raylib expects 3 components per vertex and float vertex format + if ((multi.chunks[i].data.props[2] == 3) && (multi.chunks[i].data.props[3] == RRES_VERTEX_FORMAT_FLOAT)) + { + mesh.normals = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float)); + memcpy(mesh.normals, multi.chunks[i].data.raw, mesh.vertexCount*3*sizeof(float)); + } + else RRES_LOG("RRES: WARNING: MESH: Vertex attribute normal not valid, componentCount/vertexFormat do not fit\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_TANGENT: + { + // raylib expects 4 components per vertex and float vertex format + if ((multi.chunks[i].data.props[2] == 4) && (multi.chunks[i].data.props[3] == RRES_VERTEX_FORMAT_FLOAT)) + { + mesh.tangents = (float *)RL_CALLOC(mesh.vertexCount*4, sizeof(float)); + memcpy(mesh.tangents, multi.chunks[i].data.raw, mesh.vertexCount*4*sizeof(float)); + } + else RRES_LOG("RRES: WARNING: MESH: Vertex attribute tangent not valid, componentCount/vertexFormat do not fit\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_COLOR: + { + // raylib expects 4 components per vertex and unsigned char vertex format + if ((multi.chunks[i].data.props[2] == 4) && (multi.chunks[i].data.props[3] == RRES_VERTEX_FORMAT_UBYTE)) + { + mesh.colors = (unsigned char *)RL_CALLOC(mesh.vertexCount*4, sizeof(unsigned char)); + memcpy(mesh.colors, multi.chunks[i].data.raw, mesh.vertexCount*4*sizeof(unsigned char)); + } + else RRES_LOG("RRES: WARNING: MESH: Vertex attribute color not valid, componentCount/vertexFormat do not fit\n"); + + } break; + case RRES_VERTEX_ATTRIBUTE_INDEX: + { + // raylib expects 1 components per index and unsigned short vertex format + if ((multi.chunks[i].data.props[2] == 1) && (multi.chunks[i].data.props[3] == RRES_VERTEX_FORMAT_USHORT)) + { + mesh.indices = (unsigned short *)RL_CALLOC(multi.chunks[i].data.props[0], sizeof(unsigned short)); + memcpy(mesh.indices, multi.chunks[i].data.raw, multi.chunks[i].data.props[0]*sizeof(unsigned short)); + } + else RRES_LOG("RRES: WARNING: MESH: Vertex attribute index not valid, componentCount/vertexFormat do not fit\n"); + + } break; + default: break; + } + } + } + else RRES_LOG("RRES: WARNING: Vertex provided data must be decompressed/decrypted\n"); + } + + return mesh; +} + +// Unpack compressed/encrypted data from resource chunk +// In case data could not be processed by rres.h, it is just copied in chunk.data.raw for processing here +// NOTE 1: Function return 0 on success or an error code on failure +// NOTE 2: Data corruption CRC32 check has already been performed by rresLoadResourceMulti() on rres.h +int UnpackResourceChunk(rresResourceChunk *chunk) +{ + int result = 0; + bool updateProps = false; + + // Result error codes: + // 0 - No error, decompression/decryption successful + // 1 - Encryption algorithm not supported + // 2 - Invalid password on decryption + // 3 - Compression algorithm not supported + // 4 - Error on data decompression + + // NOTE 1: If data is compressed/encrypted the properties are not loaded by rres.h because + // it's up to the user to process the data; *chunk must be properly updated by this function + // NOTE 2: rres-raylib should support the same algorithms and libraries used by rrespacker tool + void *unpackedData = NULL; + + // STEP 1. Data decryption + //------------------------------------------------------------------------------------- + unsigned char *decryptedData = NULL; + + switch (chunk->info.cipherType) + { + case RRES_CIPHER_NONE: decryptedData = chunk->data.raw; break; +#if defined(RRES_SUPPORT_ENCRYPTION_AES) + case RRES_CIPHER_AES: + { + // WARNING: Implementation dependant! + // rrespacker tool appends (salt[16] + MD5[16]) to encrypted data for convenience, + // Actually, chunk->info.packedSize considers those additional elements + + // Get some memory for the possible message output + decryptedData = (unsigned char *)RL_CALLOC(chunk->info.packedSize - 16 - 16, 1); + if (decryptedData != NULL) memcpy(decryptedData, chunk->data.raw, chunk->info.packedSize - 16 - 16); + + // Required variables for key stretching + uint8_t key[32] = { 0 }; // Encryption key + uint8_t salt[16] = { 0 }; // Key stretching salt + + // Retrieve salt from chunk packed data + // salt is stored at the end of packed data, before nonce and MAC: salt[16] + MD5[16] + memcpy(salt, ((unsigned char *)chunk->data.raw) + (chunk->info.packedSize - 16 - 16), 16); + + // Key stretching configuration + crypto_argon2_config config = { + .algorithm = CRYPTO_ARGON2_I, // Algorithm: Argon2i + .nb_blocks = 16384, // Blocks: 16 MB + .nb_passes = 3, // Iterations + .nb_lanes = 1 // Single-threaded + }; + crypto_argon2_inputs inputs = { + .pass = (const uint8_t *)rresGetCipherPassword(), // User password + .pass_size = strlen(rresGetCipherPassword()), // Password length + .salt = salt, // Salt for the password + .salt_size = 16 + }; + crypto_argon2_extras extras = { 0 }; // Extra parameters unused + + void *workArea = RL_MALLOC(config.nb_blocks*1024); // Key stretching work area + + // Generate strong encryption key, generated from user password using Argon2i algorithm (256 bit) + crypto_argon2(key, 32, workArea, config, inputs, extras); + + // Wipe key generation secrets, they are no longer needed + crypto_wipe(salt, 16); + RL_FREE(workArea); + + // Required variables for decryption and message authentication + unsigned int md5[4] = { 0 }; // Message Authentication Code generated on encryption + + // Retrieve MD5 from chunk packed data + // NOTE: MD5 is stored at the end of packed data, after salt: salt[16] + MD5[16] + memcpy(md5, ((unsigned char *)chunk->data.raw) + (chunk->info.packedSize - 16), 4*sizeof(unsigned int)); + + // Message decryption, requires key + struct AES_ctx ctx = { 0 }; + AES_init_ctx(&ctx, key); + AES_CTR_xcrypt_buffer(&ctx, (uint8_t *)decryptedData, chunk->info.packedSize - 16 - 16); // AES Counter mode, stream cipher + + // Verify MD5 to check if data decryption worked + unsigned int decryptMD5[4] = { 0 }; + unsigned int *md5Ptr = ComputeMD5(decryptedData, chunk->info.packedSize - 16 - 16); + for (int i = 0; i < 4; i++) decryptMD5[i] = md5Ptr[i]; + + // Wipe secrets if they are no longer needed + crypto_wipe(key, 32); + + if (memcmp(decryptMD5, md5, 4*sizeof(unsigned int)) == 0) // Decrypted successfully! + { + chunk->info.packedSize -= (16 + 16); // We remove additional data size from packed size (salt[16] + MD5[16]) + RRES_LOG("RRES: %c%c%c%c: Data decrypted successfully (AES)\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + else + { + result = 2; // Data was not decrypted as expected, wrong password or message corrupted + RRES_LOG("RRES: WARNING: %c%c%c%c: Data decryption failed, wrong password or corrupted data\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + + } break; +#endif +#if defined(RRES_SUPPORT_ENCRYPTION_XCHACHA20) + case RRES_CIPHER_XCHACHA20_POLY1305: + { + // WARNING: Implementation dependant! + // rrespacker tool appends (salt[16] + nonce[24] + MAC[16]) to encrypted data for convenience, + // Actually, chunk->info.packedSize considers those additional elements + + // Get some memory for the possible message output + decryptedData = (unsigned char *)RL_CALLOC(chunk->info.packedSize - 16 - 24 - 16, 1); + + // Required variables for key stretching + uint8_t key[32] = { 0 }; // Encryption key + uint8_t salt[16] = { 0 }; // Key stretching salt + + // Retrieve salt from chunk packed data + // salt is stored at the end of packed data, before nonce and MAC: salt[16] + nonce[24] + MAC[16] + memcpy(salt, ((unsigned char *)chunk->data.raw) + (chunk->info.packedSize - 16 - 24 - 16), 16); + + // Key stretching configuration + crypto_argon2_config config = { + .algorithm = CRYPTO_ARGON2_I, // Algorithm: Argon2i + .nb_blocks = 16384, // Blocks: 16 MB + .nb_passes = 3, // Iterations + .nb_lanes = 1 // Single-threaded + }; + crypto_argon2_inputs inputs = { + .pass = (const uint8_t *)rresGetCipherPassword(), // User password + .pass_size = strlen(rresGetCipherPassword()), // Password length + .salt = salt, // Salt for the password + .salt_size = 16 + }; + crypto_argon2_extras extras = { 0 }; // Extra parameters unused + + void *workArea = RL_MALLOC(config.nb_blocks*1024); // Key stretching work area + + // Generate strong encryption key, generated from user password using Argon2i algorithm (256 bit) + crypto_argon2(key, 32, workArea, config, inputs, extras); + + // Wipe key generation secrets, they are no longer needed + crypto_wipe(salt, 16); + RL_FREE(workArea); + + // Required variables for decryption and message authentication + uint8_t nonce[24] = { 0 }; // nonce used on encryption, unique to processed file + uint8_t mac[16] = { 0 }; // Message Authentication Code generated on encryption + + // Retrieve nonce and MAC from chunk packed data + // nonce and MAC are stored at the end of packed data, after salt: salt[16] + nonce[24] + MAC[16] + memcpy(nonce, ((unsigned char *)chunk->data.raw) + (chunk->info.packedSize - 16 - 24), 24); + memcpy(mac, ((unsigned char *)chunk->data.raw) + (chunk->info.packedSize - 16), 16); + + // Message decryption requires key, nonce and MAC + int decryptResult = crypto_aead_unlock(decryptedData, mac, key, nonce, NULL, 0, chunk->data.raw, (chunk->info.packedSize - 16 - 24 - 16)); + + // Wipe secrets if they are no longer needed + crypto_wipe(nonce, 24); + crypto_wipe(key, 32); + + if (decryptResult == 0) // Decrypted successfully! + { + chunk->info.packedSize -= (16 + 24 + 16); // We remove additional data size from packed size + RRES_LOG("RRES: %c%c%c%c: Data decrypted successfully (XChaCha20)\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + else if (decryptResult == -1) + { + result = 2; // Wrong password or message corrupted + RRES_LOG("RRES: WARNING: %c%c%c%c: Data decryption failed, wrong password or corrupted data\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + } break; +#endif + default: + { + result = 1; // Decryption algorithm not supported + RRES_LOG("RRES: WARNING: %c%c%c%c: Chunk data encryption algorithm not supported\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + + } break; + } + + if ((result == 0) && (chunk->info.cipherType != RRES_CIPHER_NONE)) + { + // Data is not encrypted any more, register it + chunk->info.cipherType = RRES_CIPHER_NONE; + updateProps = true; + } + + // STEP 2: Data decompression (if decryption was successful) + //------------------------------------------------------------------------------------- + unsigned char *uncompData = NULL; + + if (result == 0) + { + switch (chunk->info.compType) + { + case RRES_COMP_NONE: unpackedData = decryptedData; break; + case RRES_COMP_DEFLATE: + { + int uncompDataSize = 0; + + // TODO: WARNING: Possible issue with allocators: RL_CALLOC() vs RRES_CALLOC() + uncompData = DecompressData(decryptedData, chunk->info.packedSize, &uncompDataSize); + + if ((uncompData != NULL) && (uncompDataSize > 0)) // Decompression successful + { + unpackedData = uncompData; + chunk->info.packedSize = uncompDataSize; + RRES_LOG("RRES: %c%c%c%c: Data decompressed successfully (DEFLATE)\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + else + { + result = 4; // Decompression process failed + RRES_LOG("RRES: WARNING: %c%c%c%c: Chunk data decompression failed\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + + // Security check, uncompDataSize must match the provided chunk->baseSize + if (uncompDataSize != chunk->info.baseSize) RRES_LOG("RRES: WARNING: Decompressed data could be corrupted, unexpected size\n"); + } break; +#if defined(RRES_SUPPORT_COMPRESSION_LZ4) + case RRES_COMP_LZ4: + { + int uncompDataSize = 0; + uncompData = (unsigned char *)RRES_CALLOC(chunk->info.baseSize, 1); + uncompDataSize = LZ4_decompress_safe(decryptedData, uncompData, chunk->info.packedSize, chunk->info.baseSize); + + if ((uncompData != NULL) && (uncompDataSize > 0)) // Decompression successful + { + unpackedData = uncompData; + chunk->info.packedSize = uncompDataSize; + RRES_LOG("RRES: %c%c%c%c: Data decompressed successfully (LZ4)\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + else + { + result = 4; // Decompression process failed + RRES_LOG("RRES: WARNING: %c%c%c%c: Chunk data decompression failed\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + + // WARNING: Decompression could be successful but not the original message size returned + if (uncompDataSize != chunk->info.baseSize) RRES_LOG("RRES: WARNING: Decompressed data could be corrupted, unexpected size\n"); + } break; +#endif + case RRES_COMP_QOI: + { + int uncompDataSize = 0; + qoi_desc desc = { 0 }; + + // TODO: WARNING: Possible issue with allocators: QOI_MALLOC() vs RRES_MALLOC() + uncompData = qoi_decode(decryptedData, chunk->info.packedSize, &desc, 0); + uncompDataSize = (desc.width*desc.height*desc.channels) + 20; // Add the 20 bytes of (propCount + props[4]) + + if ((uncompData != NULL) && (uncompDataSize > 0)) // Decompression successful + { + unpackedData = uncompData; + chunk->info.packedSize = uncompDataSize; + RRES_LOG("RRES: %c%c%c%c: Data decompressed successfully (QOI)\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + else + { + result = 4; // Decompression process failed + RRES_LOG("RRES: WARNING: %c%c%c%c: Chunk data decompression failed\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } + + if (uncompDataSize != chunk->info.baseSize) RRES_LOG("RRES: WARNING: Decompressed data could be corrupted, unexpected size\n"); + } break; + default: + { + result = 3; + RRES_LOG("RRES: WARNING: %c%c%c%c: Chunk data compression algorithm not supported\n", chunk->info.type[0], chunk->info.type[1], chunk->info.type[2], chunk->info.type[3]); + } break; + } + } + + if ((result == 0) && (chunk->info.compType != RRES_COMP_NONE)) + { + // Data is not encrypted any more, register it + chunk->info.compType = RRES_COMP_NONE; + updateProps = true; + } + + // Update chunk->data.propCount and chunk->data.props if required + if (updateProps && (unpackedData != NULL)) + { + // Data is decompressed/decrypted into chunk->data.raw but data.propCount and data.props[] are still empty, + // they must be filled with the just updated chunk->data.raw (that contains everything) + chunk->data.propCount = ((int *)unpackedData)[0]; + + if (chunk->data.propCount > 0) + { + chunk->data.props = (unsigned int *)RRES_CALLOC(chunk->data.propCount, sizeof(int)); + for (unsigned int i = 0; i < chunk->data.propCount; i++) chunk->data.props[i] = ((int *)unpackedData)[1 + i]; + } + + // Move chunk->data.raw pointer (chunk->data.propCount*sizeof(int)) positions + void *raw = RRES_CALLOC(chunk->info.baseSize - 20, 1); + if (raw != NULL) memcpy(raw, ((unsigned char *)unpackedData) + 20, chunk->info.baseSize - 20); + RRES_FREE(chunk->data.raw); + chunk->data.raw = raw; + RL_FREE(unpackedData); + } + + return result; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Load data chunk: RRES_DATA_LINK +static void *LoadDataFromResourceLink(rresResourceChunk chunk, unsigned int *size) +{ + unsigned char fullFilePath[2048] = { 0 }; + void *data = NULL; + *size = 0; + + // Get external link filepath + unsigned char *linkFilePath = RL_CALLOC(chunk.data.props[0], 1); + if (linkFilePath != NULL) memcpy(linkFilePath, chunk.data.raw, chunk.data.props[0]); + + // Get base directory to append filepath if not provided by user + if (baseDir == NULL) baseDir = GetApplicationDirectory(); + + strcpy(fullFilePath, baseDir); + strcat(fullFilePath, linkFilePath); + + RRES_LOG("RRES: %c%c%c%c: Data file linked externally: %s\n", chunk.info.type[0], chunk.info.type[1], chunk.info.type[2], chunk.info.type[3], linkFilePath); + + if (FileExists(fullFilePath)) + { + // Load external file as raw data + // NOTE: We check if file is a text file to allow automatic line-endings processing + if (IsFileExtension(linkFilePath, ".txt;.md;.vs;.fs;.info;.c;.h;.json;.xml;.glsl")) // Text file + { + data = LoadFileText(fullFilePath); + *size = TextLength(data); + } + else data = LoadFileData(fullFilePath, size); + + if ((data != NULL) && (*size > 0)) RRES_LOG("RRES: %c%c%c%c: External linked file loaded successfully\n", chunk.info.type[0], chunk.info.type[1], chunk.info.type[2], chunk.info.type[3]); + } + else RRES_LOG("RRES: WARNING: [%s] Linked external file could not be found\n", linkFilePath); + + return data; +} + +// Load data chunk: RRES_DATA_RAW +// NOTE: This chunk can be used raw files embedding or other binary blobs +static void *LoadDataFromResourceChunk(rresResourceChunk chunk, unsigned int *size) +{ + void *rawData = NULL; + + if ((chunk.info.compType == RRES_COMP_NONE) && (chunk.info.cipherType == RRES_CIPHER_NONE)) + { + rawData = RL_CALLOC(chunk.data.props[0], 1); + if (rawData != NULL) memcpy(rawData, chunk.data.raw, chunk.data.props[0]); + *size = chunk.data.props[0]; + } + else RRES_LOG("RRES: %c%c%c%c: WARNING: Data must be decompressed/decrypted\n", chunk.info.type[0], chunk.info.type[1], chunk.info.type[2], chunk.info.type[3]); + + return rawData; +} + +// Load data chunk: RRES_DATA_TEXT +// NOTE: This chunk can be used for shaders or other text data elements (materials?) +static char *LoadTextFromResourceChunk(rresResourceChunk chunk, unsigned int *codeLang) +{ + void *text = NULL; + + if ((chunk.info.compType == RRES_COMP_NONE) && (chunk.info.cipherType == RRES_CIPHER_NONE)) + { + text = (char *)RL_CALLOC(chunk.data.props[0] + 1, 1); // We add NULL terminator, just in case + if (text != NULL) memcpy(text, chunk.data.raw, chunk.data.props[0]); + + // TODO: We got some extra text properties, in case they could be useful for users: + // chunk.props[1]:rresTextEncoding, chunk.props[2]:rresCodeLang, chunk. props[3]:cultureCode + *codeLang = chunk.data.props[2]; + //chunks.props[3]:cultureCode could be useful for localized text + } + else RRES_LOG("RRES: %c%c%c%c: WARNING: Data must be decompressed/decrypted\n", chunk.info.type[0], chunk.info.type[1], chunk.info.type[2], chunk.info.type[3]); + + return text; +} + +// Load data chunk: RRES_DATA_IMAGE +// NOTE: Many data types use images data in some way (font, material...) +static Image LoadImageFromResourceChunk(rresResourceChunk chunk) +{ + Image image = { 0 }; + + if ((chunk.info.compType == RRES_COMP_NONE) && (chunk.info.cipherType == RRES_CIPHER_NONE)) + { + image.width = chunk.data.props[0]; + image.height = chunk.data.props[1]; + int format = chunk.data.props[2]; + + // Assign equivalent pixel formats for our engine + // NOTE: In this case rresPixelFormat defined values match raylib PixelFormat values + switch (format) + { + case RRES_PIXELFORMAT_UNCOMP_GRAYSCALE: image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; break; + case RRES_PIXELFORMAT_UNCOMP_GRAY_ALPHA: image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; break; + case RRES_PIXELFORMAT_UNCOMP_R5G6B5: image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; break; + case RRES_PIXELFORMAT_UNCOMP_R8G8B8: image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; break; + case RRES_PIXELFORMAT_UNCOMP_R5G5B5A1: image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; break; + case RRES_PIXELFORMAT_UNCOMP_R4G4B4A4: image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; break; + case RRES_PIXELFORMAT_UNCOMP_R8G8B8A8: image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; break; + case RRES_PIXELFORMAT_UNCOMP_R32: image.format = PIXELFORMAT_UNCOMPRESSED_R32; break; + case RRES_PIXELFORMAT_UNCOMP_R32G32B32: image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32; break; + case RRES_PIXELFORMAT_UNCOMP_R32G32B32A32: image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; break; + case RRES_PIXELFORMAT_COMP_DXT1_RGB: image.format = PIXELFORMAT_COMPRESSED_DXT1_RGB; break; + case RRES_PIXELFORMAT_COMP_DXT1_RGBA: image.format = PIXELFORMAT_COMPRESSED_DXT1_RGBA; break; + case RRES_PIXELFORMAT_COMP_DXT3_RGBA: image.format = PIXELFORMAT_COMPRESSED_DXT3_RGBA; break; + case RRES_PIXELFORMAT_COMP_DXT5_RGBA: image.format = PIXELFORMAT_COMPRESSED_DXT5_RGBA; break; + case RRES_PIXELFORMAT_COMP_ETC1_RGB: image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; break; + case RRES_PIXELFORMAT_COMP_ETC2_RGB: image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; break; + case RRES_PIXELFORMAT_COMP_ETC2_EAC_RGBA: image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; break; + case RRES_PIXELFORMAT_COMP_PVRT_RGB: image.format = PIXELFORMAT_COMPRESSED_PVRT_RGB; break; + case RRES_PIXELFORMAT_COMP_PVRT_RGBA: image.format = PIXELFORMAT_COMPRESSED_PVRT_RGBA; break; + case RRES_PIXELFORMAT_COMP_ASTC_4x4_RGBA: image.format = PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA; break; + case RRES_PIXELFORMAT_COMP_ASTC_8x8_RGBA: image.format = PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA; break; + default: break; + } + + image.mipmaps = chunk.data.props[3]; + + // Image data size can be computed from image properties + unsigned int size = GetPixelDataSize(image.width, image.height, image.format); + + // NOTE: Computed image data must match the data size of the chunk processed (minus propCount + props[4] size) + if (size == (chunk.info.baseSize - 20)) + { + image.data = RL_CALLOC(size, 1); + if (image.data != NULL) memcpy(image.data, chunk.data.raw, size); + } + else RRES_LOG("RRES: WARNING: IMGE: Chunk data size do not match expected image data size\n"); + } + else RRES_LOG("RRES: %c%c%c%c: WARNING: Data must be decompressed/decrypted\n", chunk.info.type[0], chunk.info.type[1], chunk.info.type[2], chunk.info.type[3]); + + return image; +} + +// Get file extension from RRES_DATA_RAW properties (unsigned int) +static const char *GetExtensionFromProps(unsigned int ext01, unsigned int ext02) +{ + static char extension[8] = { 0 }; + memset(extension, 0, 8); + + // Convert file extension provided as 2 unsigned int properties, to a char[] array + // NOTE: Extension is defined as 2 unsigned int big-endian values (4 bytes each), + // starting with a dot, i.e 0x2e706e67 => ".png" + extension[0] = (unsigned char)((ext01 & 0xff000000) >> 24); + extension[1] = (unsigned char)((ext01 & 0x00ff0000) >> 16); + extension[2] = (unsigned char)((ext01 & 0x0000ff00) >> 8); + extension[3] = (unsigned char)(ext01 & 0x000000ff); + + extension[4] = (unsigned char)((ext02 & 0xff000000) >> 24); + extension[5] = (unsigned char)((ext02 & 0x00ff0000) >> 16); + extension[6] = (unsigned char)((ext02 & 0x0000ff00) >> 8); + extension[7] = (unsigned char)(ext02 & 0x000000ff); + + return extension; +} + +// Compute MD5 hash code, returns 4 integers array (static) +static unsigned int *ComputeMD5(unsigned char *data, int size) +{ +#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) + + static unsigned int hash[4] = { 0 }; + + // NOTE: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating + + // r specifies the per-round shift amounts + unsigned int r[] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + + // Use binary integer part of the sines of integers (in radians) as constants// Initialize variables: + unsigned int k[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + hash[0] = 0x67452301; + hash[1] = 0xefcdab89; + hash[2] = 0x98badcfe; + hash[3] = 0x10325476; + + // Pre-processing: adding a single 1 bit + // Append '1' bit to message + // NOTE: The input bytes are considered as bits strings, + // where the first bit is the most significant bit of the byte + + // Pre-processing: padding with zeros + // Append '0' bit until message length in bit 448 (mod 512) + // Append length mod (2 pow 64) to message + + int newDataSize = ((((size + 8)/64) + 1)*64) - 8; + + unsigned char *msg = RL_CALLOC(newDataSize + 64, 1); // Also appends "0" bits (we alloc also 64 extra bytes...) + memcpy(msg, data, size); + msg[size] = 128; // Write the "1" bit + + unsigned int bitsLen = 8*size; + memcpy(msg + newDataSize, &bitsLen, 4); // We append the len in bits at the end of the buffer + + // Process the message in successive 512-bit chunks for each 512-bit chunk of message + for (int offset = 0; offset < newDataSize; offset += (512/8)) + { + // Break chunk into sixteen 32-bit words w[j], 0 <= j <= 15 + unsigned int *w = (unsigned int *)(msg + offset); + + // Initialize hash value for this chunk + unsigned int a = hash[0]; + unsigned int b = hash[1]; + unsigned int c = hash[2]; + unsigned int d = hash[3]; + + for (int i = 0; i < 64; i++) + { + unsigned int f, g; + + if (i < 16) + { + f = (b & c) | ((~b) & d); + g = i; + } + else if (i < 32) + { + f = (d & b) | ((~d) & c); + g = (5*i + 1)%16; + } + else if (i < 48) + { + f = b ^ c ^ d; + g = (3*i + 5)%16; + } + else + { + f = c ^ (b | (~d)); + g = (7*i)%16; + } + + unsigned int temp = d; + d = c; + c = b; + b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]); + a = temp; + } + + // Add chunk's hash to result so far + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; + } + + RL_FREE(msg); + + return hash; +} + +#endif // RRES_RAYLIB_IMPLEMENTATION |