hiscore.c (8308B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2025 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include "hiscore.h" 6 #include "local.h" 7 8 #include <SDL3/SDL.h> 9 #include <assert.h> 10 #include <errno.h> 11 #include <limits.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <sys/stat.h> 16 #include <time.h> 17 18 int 19 hs_add(struct HiScoreEntry** table, const char* name, const int name_len, 20 const unsigned long int score, const char* timestamp) 21 { 22 struct HiScoreEntry* p = NULL; 23 struct HiScoreEntry* q = NULL; 24 25 assert((table != NULL) && (*table != NULL) && (timestamp != NULL)); 26 27 p = *table; 28 while (p < *table + HI_SCORE_MAX) 29 { 30 assert(p != NULL); 31 if (p->score < score) 32 { 33 q = *table + HI_SCORE_MAX - 1; 34 while (q != p) 35 { 36 hs_assign_ss(q - 1, q); 37 q--; 38 } 39 hs_assign_ps(name, name_len, score, timestamp, p); 40 break; 41 } 42 p++; 43 } 44 45 return 0; 46 } 47 48 int 49 hs_add_now(struct HiScoreEntry** table, const char* name, const int name_len, 50 const unsigned long int score) 51 { 52 struct tm* ns = NULL; 53 char sn[TIMESTAMP_MAX]; 54 time_t nt; 55 nt = time(NULL); 56 if (!(ns = localtime(&nt))) 57 { 58 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "localtime: %s", 59 strerror(errno)); 60 return 1; 61 } 62 if (!strftime(sn, TIMESTAMP_MAX, "%Y-%m-%d %H:%M", ns)) 63 { 64 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "strftime failed"); 65 return 1; 66 } 67 return hs_add(table, name, name_len, score, sn); 68 } 69 70 int 71 hs_assign_ps(const char* name, const int name_len, 72 const unsigned long int score, const char* timestamp, 73 struct HiScoreEntry* dest) 74 { 75 char* new_name = NULL; 76 77 assert(dest != NULL); 78 79 if (!timestamp) 80 { 81 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 82 "hs_asssign_ps: timestamp is null!"); 83 return 1; 84 } 85 86 if (!name || !name_len) 87 goto hs_assign_ps_skip_name; 88 89 if (dest->name_len < name_len) 90 { 91 new_name = realloc(dest->name, name_len); 92 if (!new_name) 93 { 94 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "realloc failed"); 95 return 1; 96 } 97 } 98 if (!memccpy(dest->name, name, 0, name_len)) 99 dest->name[name_len] = 0; 100 101 /*SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 102 "hs_assign_ps: dest->name = {%s} [%d]", 103 dest->name, dest->name_len);*/ 104 105 hs_assign_ps_skip_name: 106 dest->name_len = name_len; 107 dest->score = score; 108 if (!memccpy(dest->timestamp, timestamp, 0, TIMESTAMP_MAX)) 109 dest->timestamp[TIMESTAMP_MAX - 1] = 0; 110 return 0; 111 } 112 113 int 114 hs_assign_ss(const struct HiScoreEntry* source, struct HiScoreEntry* dest) 115 { 116 char* new_name = NULL; 117 118 assert((source != NULL) && (dest != NULL)); 119 120 if (!*source->timestamp) 121 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, 122 "hs_asssign_ss: timestamp is empty!"); 123 124 if (!source->name) 125 goto hs_assign_ss_skip_name; 126 127 if (dest->name_len < source->name_len) 128 { 129 new_name = realloc(dest->name, source->name_len); 130 if (!new_name) 131 { 132 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "realloc failed"); 133 return 1; 134 } 135 } 136 if (!memccpy(dest->name, source->name, 0, source->name_len)) 137 dest->name[source->name_len] = 0; 138 139 hs_assign_ss_skip_name: 140 dest->name_len = source->name_len; 141 dest->score = source->score; 142 if (!memccpy(dest->timestamp, source->timestamp, 0, TIMESTAMP_MAX)) 143 dest->timestamp[TIMESTAMP_MAX - 1] = 0; 144 return 0; 145 } 146 147 int 148 hs_init(struct HiScoreEntry** table) 149 { 150 struct HiScoreEntry* p = NULL; 151 int len = sizeof(HI_SCORE_DEFAULT_NAME); 152 153 assert(table != NULL); 154 *table = malloc(HI_SCORE_MAX * sizeof(struct HiScoreEntry)); 155 if (!*table) 156 goto hs_init_malloc_error; 157 for (p = *table; p < *table + HI_SCORE_MAX; p++) 158 { 159 /* Zero out for privacy and security */ 160 p->name = calloc(1, HI_SCORE_NAME_MAX); 161 if (!p->name) 162 goto hs_init_malloc_error; 163 memccpy(p->name, HI_SCORE_DEFAULT_NAME, 0, HI_SCORE_NAME_MAX); 164 p->name_len = len - 1; 165 p->score = 0; 166 memccpy(p->timestamp, DEFAULT_TIMESTAMP, 0, TIMESTAMP_MAX); 167 } 168 169 return 0; 170 171 hs_init_malloc_error: 172 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "malloc failed"); 173 return 1; 174 } 175 176 /* USERDIR not existing is ok, create it in that case */ 177 char* 178 hs_get_hofdir(void) 179 { 180 struct stat st; 181 static char* result = NULL; 182 char* home = NULL; 183 int result_size = PATH_MAX + 1; 184 185 if (!result) 186 result = malloc(result_size); 187 else 188 goto hs_get_hofdir_skip_generating; 189 if (!result) 190 goto hs_get_hofdir_error; 191 192 home = getenv("HOME"); 193 if (!home || !*home) 194 goto hs_get_hofdir_no_home; 195 196 snprintf(result, result_size, "%s/%s", home, USERDIR); 197 198 if (stat(result, &st) == 0) 199 goto hs_get_hofdir_skip_generating; 200 201 if (errno == ENOENT || errno == ENOTDIR) 202 { 203 if (mkdir(result, 204 S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) 205 < 0) 206 goto hs_get_hofdir_error; 207 } 208 else 209 goto hs_get_hofdir_error; 210 211 hs_get_hofdir_skip_generating: 212 return result; 213 214 hs_get_hofdir_no_home: 215 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 216 "hs_get_hofdir: HOME environment variable is unset or empty"); 217 errno = ENOENT; 218 return NULL; 219 220 hs_get_hofdir_error: 221 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_get_hofdir: %s", 222 strerror(errno)); 223 return NULL; 224 } 225 226 /* 227 * File structure: 228 * HI_SCORE_MAX entries, each consisting of 229 * 230 * ,-----------------------------------------------------------. 231 * | name (HI_SCORE_NAME_MAX unsigned chars) | 232 * |-----------------------------------------------------------| 233 * | score 4-byte unsigned long int BE | 234 * |-----------------------------------------------------------| 235 * | timestamp (TIMESTAMP_MAX unsigned chars) | 236 * `-----------------------------------------------------------' 237 */ 238 int 239 hs_load(struct HiScoreEntry** table, const char* hofdir) 240 { 241 FILE* infile = NULL; 242 char* filename = NULL; 243 char name[HI_SCORE_NAME_MAX]; 244 char timestamp[TIMESTAMP_MAX]; 245 int filename_size = PATH_MAX + 1; 246 int name_len, i, ib; 247 unsigned long int score; 248 uint8_t byte; 249 250 if (!hofdir) 251 goto hs_load_error; 252 253 filename = malloc(filename_size); 254 if (!filename) 255 goto hs_load_error; 256 257 snprintf(filename, filename_size, "%s/%s", hofdir, HOFFILE); 258 259 if (!(infile = fopen(filename, "rb"))) 260 { 261 /* ENOENT means we don't have the file yet */ 262 if (errno == ENOENT) 263 goto hs_load_cleanup; 264 265 goto hs_load_error; 266 } 267 268 i = 0; 269 while (!feof(infile) && i < HI_SCORE_MAX) 270 { 271 /* Zero out for privacy and security */ 272 memset(name, 0, HI_SCORE_NAME_MAX); 273 if (fread(name, HI_SCORE_NAME_MAX, 1, infile) < 1) 274 { 275 if (feof(infile)) 276 continue; 277 else 278 { 279 fclose(infile); 280 goto hs_load_error; 281 } 282 } 283 name_len = strlen(name); 284 score = 0; 285 for (ib = 0; ib < 4; ib++) 286 { 287 if (fread(&byte, 1, 1, infile) < 1) 288 { 289 fclose(infile); 290 goto hs_load_error; 291 } 292 score |= SETBYTENOF4(byte, ib); 293 } 294 if (fread(timestamp, TIMESTAMP_MAX, 1, infile) < 1) 295 { 296 if (feof(infile)) 297 continue; 298 else 299 { 300 fclose(infile); 301 goto hs_load_error; 302 } 303 } 304 hs_add(table, name, name_len, score, timestamp); 305 i++; 306 } 307 308 hs_load_cleanup: 309 if (infile && fclose(infile) == EOF) 310 goto hs_load_error; 311 312 free(filename); 313 return 0; 314 315 hs_load_error: 316 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_load: %s", strerror(errno)); 317 return 1; 318 } 319 320 int 321 hs_write(struct HiScoreEntry* table, const char* hofdir) 322 { 323 FILE* outfile = NULL; 324 struct HiScoreEntry* p = NULL; 325 char* filename = NULL; 326 int filename_size = PATH_MAX + 1; 327 int i, ib; 328 uint8_t byte; 329 330 if (!hofdir) 331 goto hs_write_error; 332 333 filename = malloc(filename_size); 334 if (!filename) 335 goto hs_write_error; 336 337 snprintf(filename, filename_size, "%s/%s", hofdir, HOFFILE); 338 339 if (!(outfile = fopen(filename, "wb"))) 340 goto hs_write_error; 341 342 for (p = table; p < table + HI_SCORE_MAX; p++) 343 { 344 for (i = 0; i < HI_SCORE_NAME_MAX; i++) 345 { 346 errno = 0; 347 byte = p->name[i]; 348 if (fwrite(&byte, 1, 1, outfile) < 1) 349 { 350 fclose(outfile); 351 goto hs_write_error; 352 } 353 } 354 errno = 0; 355 for (ib = 0; ib < 4; ib++) 356 { 357 byte = GETBYTENOF4(p->score, ib); 358 if (fwrite(&byte, 1, 1, outfile) < 1) 359 { 360 fclose(outfile); 361 goto hs_write_error; 362 } 363 } 364 for (i = 0; i < TIMESTAMP_MAX; i++) 365 { 366 errno = 0; 367 byte = p->timestamp[i]; 368 if (fwrite(&byte, 1, 1, outfile) < 1) 369 { 370 fclose(outfile); 371 goto hs_write_error; 372 } 373 } 374 } 375 if (fclose(outfile) == EOF) 376 goto hs_write_error; 377 378 free(filename); 379 return 0; 380 381 hs_write_error: 382 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_write: %s", strerror(errno)); 383 return 1; 384 }