From 1b3f36c650c2b30f3fe45dedef0c43fa46defc12 Mon Sep 17 00:00:00 2001 From: Michael Anghelone Date: Wed, 28 May 2025 21:17:31 -0400 Subject: [PATCH] Modified base64 encode a bit. --- src/raylib.h | 2 + src/rcore.c | 106 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 563156525..41a0927b6 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1153,7 +1153,9 @@ RLAPI long GetFileModTime(const char *fileName); // Get file mo // Compression/Encoding functionality RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() +// Kept for backwards compatibility. RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() +RLAPI bool Base64Encode(const unsigned char *data, unsigned int dataSize, char ** output); RLAPI unsigned char *DecodeDataBase64(const char *text, int *outputSize); // Decode Base64 string (expected NULL terminated), memory must be MemFree() RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) diff --git a/src/rcore.c b/src/rcore.c index 5f81b7099..2e4fb9057 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2538,60 +2538,80 @@ unsigned char *DecompressData(const unsigned char *compData, int compDataSize, i return data; } -// Encode data to Base64 string -// NOTE: Returned string includes NULL terminator, considered on outputSize -char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) + +// Encode data to dynamically allocated Base64 string. +// NOTE: Output will be NULL terminated, considered on outputSize +// If return false check the output string for an error message. +bool Base64Encode(const unsigned char *data, unsigned int dataSize, char ** output) { + static char * errorMsgs[] = { + "Out of memory!" + }; + // Base64 conversion table from RFC 4648 [0..63] // NOTE: They represent 64 values (6 bits), to encode 3 bytes of data into 4 "sixtets" (6bit characters) - static const char base64EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + // 65th value is the padding symbol. + static const char base64EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - // Compute expected size and padding - int paddedSize = dataSize; - while (paddedSize%3 != 0) paddedSize++; // Padding bytes to round 4*(dataSize/3) to 4 bytes - int estimatedOutputSize = 4*(paddedSize/3); - int padding = paddedSize - dataSize; - - // Adding null terminator to string - estimatedOutputSize += 1; + // Formula lifted from https://cvs.savannah.gnu.org/viewvc/gnulib/gnulib/lib/base64.h?view=markup&content-type=text%2Fvnd.viewcvs-markup&revision=HEAD#l30 + // We add one more to ensure enough space for null terminator. + int allocationSize = ((dataSize + 2) / 3) +1; // Load some memory to store encoded string - char *encodedData = (char *)RL_CALLOC(estimatedOutputSize, 1); - if (encodedData == NULL) return NULL; - - int outputCount = 0; - for (int i = 0; i < dataSize;) - { - unsigned int octetA = 0; - unsigned int octetB = 0; - unsigned int octetC = 0; - unsigned int octetPack = 0; - - octetA = data[i]; // Generates 2 sextets - octetB = ((i + 1) < dataSize)? data[i + 1] : 0; // Generates 3 sextets - octetC = ((i + 2) < dataSize)? data[i + 2] : 0; // Generates 4 sextets - - octetPack = (octetA << 16) | (octetB << 8) | octetC; - - encodedData[outputCount + 0] = (unsigned char)(base64EncodeTable[(octetPack >> 18) & 0x3f]); - encodedData[outputCount + 1] = (unsigned char)(base64EncodeTable[(octetPack >> 12) & 0x3f]); - encodedData[outputCount + 2] = (unsigned char)(base64EncodeTable[(octetPack >> 6) & 0x3f]); - encodedData[outputCount + 3] = (unsigned char)(base64EncodeTable[octetPack & 0x3f]); - outputCount += 4; - i += 3; + char *encodedData = (char *)RL_CALLOC(allocationSize, 1); + if (encodedData == NULL) { + *output = errorMsgs[0]; + return false; } - // Add required padding bytes - for (int p = 0; p < padding; p++) encodedData[outputCount - p - 1] = '='; + *output = encodedData; + + unsigned int index; + int dataLeft = dataSize; + + while (dataLeft) { + index = (unsigned int)(data[0] >> 2 & 0x3f); + *encodedData++ = base64EncodeTable[index]; + + index = (unsigned int)(((data[0] << 4) + + ( --dataLeft ? data[1] >> 4 : 0)) & 0x3f); + *encodedData++ = base64EncodeTable[index]; + + index = 64; + if (dataLeft) { + index = (unsigned int)(((data[1] << 2) + + (--dataLeft ? data[2] >> 6 : 0)) & 0x3f); + } + *encodedData++ = base64EncodeTable[index]; + + index = 64; + if (dataLeft) index = (unsigned int)(data[2] & 0x3f); + *encodedData++ = base64EncodeTable[index]; + + if(dataLeft) dataLeft--; - // Add null terminator to string - encodedData[outputCount] = '\0'; - outputCount++; + if(dataLeft) data += 3; + } - if (outputCount != estimatedOutputSize) TRACELOG(LOG_WARNING, "BASE64: Output size differs from estimation"); + // Our null terminator. + *encodedData = '\0'; - *outputSize = estimatedOutputSize; - return encodedData; + return true; +} + +// Encode data to Base64 string +// NOTE: Returned string includes NULL terminator, considered on outputSize +char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) +{ + *outputSize=0; + char * output = NULL; + bool ok = Base64Encode(data, dataSize, &output); + if (ok) { + *outputSize = (int)strlen(output); + return output; + } + output = NULL; + return output; } // Decode Base64 string (expected NULL terminated)