Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
TiledDecoder.cpp
Go to the documentation of this file.
1/*
2 * TiledDecoder.cpp - Base64 and compression implementation
3 */
4
5#include "../include/TiledDecoder.h"
6#include "../../system/system_utils.h"
7#include "../../TiledLevelLoader/third_party/miniz/miniz.h"
8#include <sstream>
9#include <cctype>
10
11namespace Olympe {
12namespace Tiled {
13
14 const std::string TiledDecoder::base64_chars =
15 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
16 "abcdefghijklmnopqrstuvwxyz"
17 "0123456789+/";
18
19 std::vector<uint8_t> TiledDecoder::DecodeBase64(const std::string& encoded)
20 {
21 std::vector<uint8_t> result;
22
23 // Remove whitespace
24 std::string cleaned;
25 cleaned.reserve(encoded.size());
26 for (char c : encoded) {
27 if (!isspace(static_cast<unsigned char>(c))) {
28 cleaned += c;
29 }
30 }
31
32 int in_len = static_cast<int>(cleaned.size());
33 int i = 0;
34 int j = 0;
35 int in_ = 0;
36 unsigned char char_array_4[4], char_array_3[3];
37
38 while (in_len-- && (cleaned[in_] != '=') && IsBase64(cleaned[in_])) {
39 char_array_4[i++] = cleaned[in_]; in_++;
40 if (i == 4) {
41 for (i = 0; i < 4; i++)
42 char_array_4[i] = static_cast<unsigned char>(base64_chars.find(char_array_4[i]));
43
44 char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
45 char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
46 char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
47
48 for (i = 0; (i < 3); i++)
49 result.push_back(char_array_3[i]);
50 i = 0;
51 }
52 }
53
54 if (i) {
55 for (j = i; j < 4; j++)
56 char_array_4[j] = 0;
57
58 for (j = 0; j < 4; j++)
59 char_array_4[j] = static_cast<unsigned char>(base64_chars.find(char_array_4[j]));
60
61 char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
62 char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
63 char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
64
65 for (j = 0; (j < i - 1); j++)
66 result.push_back(char_array_3[j]);
67 }
68
69 return result;
70 }
71
72 std::vector<uint8_t> TiledDecoder::DecompressGzip(const std::vector<uint8_t>& compressed)
73 {
74 if (compressed.empty()) {
75 SYSTEM_LOG << "TiledDecoder: ERROR - Empty input for gzip decompression" << std::endl;
76 return std::vector<uint8_t>();
77 }
78
79 // Estimate output size (assume 10x compression ratio)
80 size_t estimated_size = compressed.size() * 10;
81 std::vector<uint8_t> result(estimated_size);
82
84 int status = mz_uncompress(result.data(), &dest_len, compressed.data(),
85 static_cast<mz_ulong>(compressed.size()));
86
87 if (status == MZ_BUF_ERROR) {
88 // Buffer too small, resize to 50 times compressed size to accommodate higher compression ratios
89 estimated_size = compressed.size() * 50;
90 result.resize(estimated_size);
91 dest_len = static_cast<mz_ulong>(estimated_size);
92 status = mz_uncompress(result.data(), &dest_len, compressed.data(),
93 static_cast<mz_ulong>(compressed.size()));
94 }
95
96 if (status != MZ_OK) {
97 const char* errorStr = "unknown error";
98 switch (status) {
99 case MZ_STREAM_ERROR: errorStr = "stream error (invalid parameters)"; break;
100 case MZ_DATA_ERROR: errorStr = "data error (corrupted or incomplete data)"; break;
101 case MZ_MEM_ERROR: errorStr = "memory error (out of memory)"; break;
102 case MZ_BUF_ERROR: errorStr = "buffer error (output buffer too small)"; break;
103 }
104 SYSTEM_LOG << "TiledDecoder: ERROR - Gzip decompression failed"
105 << "\n Error code: " << status << " (" << errorStr << ")"
106 << "\n Input size: " << compressed.size() << " bytes"
107 << "\n This indicates corrupted, truncated, or invalid gzip data"
108 << std::endl;
109 return std::vector<uint8_t>();
110 }
111
112 result.resize(dest_len);
113 return result;
114 }
115
116 std::vector<uint8_t> TiledDecoder::DecompressZlib(const std::vector<uint8_t>& compressed)
117 {
118 // zlib and gzip use the same decompression in miniz
120 }
121
122 std::vector<uint32_t> TiledDecoder::BytesToTileIds(const std::vector<uint8_t>& bytes)
123 {
124 std::vector<uint32_t> result;
125
126 // ====================================================================
127 // CRITICAL VALIDATION: Verify byte array size is multiple of 4
128 // Each tile ID requires exactly 4 bytes (uint32_t little-endian)
129 // ====================================================================
130 if (bytes.size() % 4 != 0) {
131 SYSTEM_LOG << "TiledDecoder: ERROR - Byte array size (" << bytes.size()
132 << ") is not a multiple of 4"
133 << "\n This indicates corrupted or truncated tile data"
134 << "\n Each tile ID requires exactly 4 bytes"
135 << "\n Missing bytes: " << (4 - (bytes.size() % 4))
136 << std::endl;
137 return result;
138 }
139
140 result.reserve(bytes.size() / 4);
141
142 for (size_t i = 0; i < bytes.size(); i += 4) {
143 // Little-endian conversion
145 static_cast<uint32_t>(bytes[i]) |
146 (static_cast<uint32_t>(bytes[i + 1]) << 8) |
147 (static_cast<uint32_t>(bytes[i + 2]) << 16) |
148 (static_cast<uint32_t>(bytes[i + 3]) << 24);
149 result.push_back(tileId);
150 }
151
152 return result;
153 }
154
155 std::vector<uint32_t> TiledDecoder::ParseCSV(const std::string& csv)
156 {
157 std::vector<uint32_t> result;
158 std::istringstream stream(csv);
159 std::string token;
160 int tokenCount = 0;
161 int errorCount = 0;
162
163 while (std::getline(stream, token, ',')) {
164 ++tokenCount;
165 // Remove whitespace
166 token.erase(0, token.find_first_not_of(" \t\n\r\f\v"));
167 token.erase(token.find_last_not_of(" \t\n\r\f\v") + 1);
168
169 if (!token.empty()) {
170 try {
171 uint32_t value = static_cast<uint32_t>(std::stoul(token));
172 result.push_back(value);
173 } catch (const std::invalid_argument& e) {
174 ++errorCount;
175 SYSTEM_LOG << "TiledDecoder: ERROR - Invalid CSV token at position " << tokenCount
176 << ": '" << token << "' (not a valid number). Error : " << e.what() << std::endl;
177 } catch (const std::out_of_range& e) {
178 ++errorCount;
179 SYSTEM_LOG << "TiledDecoder: ERROR - CSV token out of range at position " << tokenCount
180 << ": '" << token << "' (exceeds uint32_t maximum). Error " << e.what() << std::endl;
181 } catch (...) {
182 ++errorCount;
183 SYSTEM_LOG << "TiledDecoder: ERROR - Failed to parse CSV token at position " << tokenCount
184 << ": '" << token << "'" << std::endl;
185 }
186 }
187 }
188
189 if (errorCount > 0) {
190 SYSTEM_LOG << "TiledDecoder: WARNING - Parsed " << result.size() << " valid tiles from "
191 << tokenCount << " tokens with " << errorCount << " errors" << std::endl;
192 }
193
194 return result;
195 }
196
197 std::vector<uint32_t> TiledDecoder::DecodeTileData(
198 const std::string& data,
199 const std::string& encoding,
200 const std::string& compression)
201 {
202 if (encoding == "csv") {
203 return ParseCSV(data);
204 }
205 else if (encoding == "base64") {
206 // Decode base64
207 std::vector<uint8_t> decoded = DecodeBase64(data);
208 if (decoded.empty()) {
209 SYSTEM_LOG << "TiledDecoder: ERROR - Base64 decode failed"
210 << "\n Input data length: " << data.length() << " characters"
211 << "\n This may indicate corrupted or invalid base64 data"
212 << std::endl;
213 return std::vector<uint32_t>();
214 }
215
216 SYSTEM_LOG << "TiledDecoder: Base64 decoded " << decoded.size() << " bytes" << std::endl;
217
218 // Decompress if needed
219 if (compression == "gzip") {
220 size_t compressedSize = decoded.size();
222 if (decoded.empty()) {
223 SYSTEM_LOG << "TiledDecoder: ERROR - Gzip decompression failed"
224 << "\n Compressed size: " << compressedSize << " bytes"
225 << "\n This may indicate corrupted compression data"
226 << std::endl;
227 return std::vector<uint32_t>();
228 }
229 SYSTEM_LOG << "TiledDecoder: Gzip decompressed from " << compressedSize
230 << " to " << decoded.size() << " bytes" << std::endl;
231 }
232 else if (compression == "zlib") {
233 size_t compressedSize = decoded.size();
235 if (decoded.empty()) {
236 SYSTEM_LOG << "TiledDecoder: ERROR - Zlib decompression failed"
237 << "\n Compressed size: " << compressedSize << " bytes"
238 << "\n This may indicate corrupted compression data"
239 << std::endl;
240 return std::vector<uint32_t>();
241 }
242 SYSTEM_LOG << "TiledDecoder: Zlib decompressed from " << compressedSize
243 << " to " << decoded.size() << " bytes" << std::endl;
244 }
245
246 // Convert to tile IDs
247 return BytesToTileIds(decoded);
248 }
249 else {
250 SYSTEM_LOG << "TiledDecoder: ERROR - Unsupported encoding: '" << encoding << "'"
251 << "\n Supported encodings: 'csv', 'base64'"
252 << std::endl;
253 return std::vector<uint32_t>();
254 }
255 }
256
257} // namespace Tiled
258} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
static std::vector< uint8_t > DecodeBase64(const std::string &encoded)
static const std::string base64_chars
static bool IsBase64(unsigned char c)
static std::vector< uint32_t > DecodeTileData(const std::string &data, const std::string &encoding, const std::string &compression)
static std::vector< uint8_t > DecompressGzip(const std::vector< uint8_t > &compressed)
static std::vector< uint32_t > ParseCSV(const std::string &csv)
static std::vector< uint32_t > BytesToTileIds(const std::vector< uint8_t > &bytes)
static std::vector< uint8_t > DecompressZlib(const std::vector< uint8_t > &compressed)
#define SYSTEM_LOG