linije

Клон игре Color lines
git clone https://git.sr.ht/~strahinja/linije
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

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 }