- [NEW] Passat a SDL3

- [NEW] Ara usa JailAudio
- [NEW] Usant última versió de Respak
- [NEW] Afegit lagueirtofile
This commit is contained in:
2025-06-29 16:56:28 +02:00
parent d5286d8abe
commit 737e18fd96
22 changed files with 6398 additions and 832 deletions

View File

@@ -15,486 +15,215 @@
#include <pwd.h>
#endif
#define DEFAULT_FILENAME "data.jf2"
#define DEFAULT_FOLDER "data/"
#define CONFIG_FILENAME "config.txt"
#define DEFAULT_FILENAME "data.jf2"
#define DEFAULT_FOLDER "data/"
#define CONFIG_FILENAME "config.txt"
/* FORMAT DEL ARXIU .JF2
4 bytes header capçalera "PK2" (caracter 0 de final de cadena al final) (en realitat passe de ella, pero be...)
4 bytes num_files nombre d'arxius inclosos
4 bytes toc_offset en quina posició de l'arxiu està la taula de continguts
... Ara venen tots els arxius inclosos, uno darrere de l'altre.
... Quan acaben ve la taula de continguts. La taula te tantes entrades com arxius inclosos. Cada entrada te el següent format:
(per cert, toc_offset apunta ací)
4 bytes offset en quina posició del arxiu de recursos comença este arxiu
4 bytes size tamany d'este arxiu
1 byte path_size tamany de la cadena amb la ruta de l'arxiu
path_size bytes path ruta de l'arxiu original. La usem per a trobar el arxiu que ens demanen.
EXEMPLE SIMPLE:
- Imaginem que volem incloure un arxiu "data/hola.txt" amb el contingut "HOLA", i un arxiu "data/adios.txt" amb el contingut "ADIOS":
OFFSET CONTINGUT TAMANY DESCRIPCIÓ
0 "PK2"+0 4 La capçalera
4 2 4 nombre d'arxius
8 21 4 offset a la taula de continguts
--- COMENCEN ELS ARXIUS INCLOSOS ---
12 HOLA 4 el contingut del primer arxiu
16 ADIOS 5 el contingut del segon arxiu
--- COMENÇA LA TAULA DE CONTINGUTS ---
21 12 4 offset al primer arxiu
25 4 4 tamany del primer axiu
29 13 1 tamany de la ruta al primer arxiu
30 "data/hola.txt" 13 la ruta al primer arxiu
43 16 4 offset al primer arxiu
47 4 4 tamany del primer axiu
51 13 1 tamany de la ruta al primer arxiu
52 "data/adios.txt" 14 la ruta al primer arxiu
- Es un exemple raro, perque ocupa mes la ruta al arxiu que l'arxiu en si, pero espere que la idea quede clara!
Al principi se carrega la tabla de continguts en memòria, així el acces als arxius es ràpid.
Y com funciona tot açò? pos per defecte va a intentar llegir tots els arxius de "data.jf2". Si no troba e l'arxiu, automaticament passa a
buscar-ho tot en la carpeta "data" en arxius independents. En principi, si no se tenen requeriments diferents, no fa falta configurar res.
Respecte al tema de l'arxiu de configuració, està montat de forma que se pot escriure i llegir valors asociats a una clau sense calfar-se el cap.
En l'arxiu de configuració els valor se guarden de la forma:
CLAU=VALOR
Cada un en una linea. Si llegim i no existeix, torna cadena buida. Si escrivim i ja exisita, se reemplaça.
Tot son valors de cadena. Si en realitat son números, tindràs que encarregar-te tu de la caonversió de cadena a numero o al reves.
*/
namespace file
struct file_t
{
// Estructures
// ===============================================================================================================================
std::string path;
uint32_t size;
uint32_t offset;
};
// Estructura que representa un arxiu en la tabla de continguts del arxiu de recursos
struct file_t
{
std::string path; // Ruta relativa de l'arxiu
uint32_t size; // Tamany de l'arxiu
uint32_t offset; // Offset a l'arxiu dins de l'arxiu de recursos
};
std::vector<file_t> toc;
std::vector<file_t> toc; // vector que conté la taula de continguts de l'arxiu de recursos
/* El std::map me fa coses rares, vaig a usar un good old std::vector amb una estructura key,value propia i au, que sempre funciona */
struct keyvalue_t {
std::string key, value;
};
/* El std::map me fa coses rares, vaig a usar un good old std::vector amb una estructura key,value propia i au, que sempre funciona */
struct keyvalue_t
{
std::string key, value;
};
char *resource_filename = NULL;
char *resource_folder = NULL;
int file_source = SOURCE_FILE;
char scratch[255];
static std::string config_folder;
std::vector<keyvalue_t> config;
// Variables
// ===============================================================================================================================
static std::string resource_filename = DEFAULT_FILENAME; // Nom de l'arxiu de recursos
static std::string resource_folder = DEFAULT_FOLDER; // Nom de la carpeta de recursos
static int file_source = SOURCE_FILE; // D'on anem a pillar els recursos, arxiu o carpeta
static std::string config_folder; // Nom de la carpeta on guardar la configuració
std::vector<keyvalue_t> config; // Vector amb els valors guardats a l'arxiu de configuració
void file_setresourcefilename(const char *str) {
if (resource_filename != NULL) free(resource_filename);
resource_filename = (char*)malloc(strlen(str)+1);
strcpy(resource_filename, str);
}
// Funcions
// ===============================================================================================================================
void file_setresourcefolder(const char *str) {
if (resource_folder != NULL) free(resource_folder);
resource_folder = (char*)malloc(strlen(str)+1);
strcpy(resource_folder, str);
}
// Estableix el nom de l'arxiu on estàn guardats els recursos (es "data.jf2" per defecte)
void setResourceFilename(const std::string str)
{
resource_filename = str;
void file_setsource(const int src) {
file_source = src%2; // mod 2 so it always is a valid value, 0 (file) or 1 (folder)
if (src==SOURCE_FOLDER && resource_folder==NULL) file_setresourcefolder(DEFAULT_FOLDER);
}
bool file_getdictionary() {
if (resource_filename == NULL) file_setresourcefilename(DEFAULT_FILENAME);
std::ifstream fi (resource_filename, std::ios::binary);
if (!fi.is_open()) return false;
char header[4];
fi.read(header, 4);
uint32_t num_files, toc_offset;
fi.read((char*)&num_files, 4);
fi.read((char*)&toc_offset, 4);
fi.seekg(toc_offset);
for (int i=0; i<num_files; ++i)
{
uint32_t file_offset, file_size;
fi.read( (char*)&file_offset, 4 );
fi.read( (char*)&file_size, 4 );
uint8_t path_size;
fi.read( (char*)&path_size, 1 );
char file_name[path_size+1];
fi.read( file_name, path_size );
file_name[path_size] = 0;
std::string filename = file_name;
toc.push_back({filename, file_size, file_offset});
}
fi.close();
}
char *file_getfilenamewithfolder(const char* filename) {
strcpy(scratch, resource_folder);
strcat(scratch, filename);
return scratch;
}
FILE *file_getfilepointer(const char *resourcename, int& filesize, const bool binary) {
if (file_source==SOURCE_FILE and toc.size()==0) {
if (not file_getdictionary()) file_setsource(SOURCE_FOLDER);
}
FILE *f;
if (file_source==SOURCE_FILE) {
bool found = false;
uint32_t count = 0;
while( !found && count < toc.size() ) {
found = ( std::string(resourcename) == toc[count].path );
if( !found ) count++;
}
if( !found ) {
perror("El recurs no s'ha trobat en l'arxiu de recursos");
exit(1);
}
filesize = toc[count].size;
f = fopen(resource_filename, binary?"rb":"r");
if (not f) {
perror("No s'ha pogut obrir l'arxiu de recursos");
exit(1);
}
fseek(f, toc[count].offset, SEEK_SET);
} else {
f = fopen(file_getfilenamewithfolder(resourcename), binary?"rb":"r");
fseek(f, 0, SEEK_END);
filesize = ftell(f);
fseek(f, 0, SEEK_SET);
}
return f;
}
// Estableix el nom de la carpeta on estàn guardats els recursos (es "data" per defecte)
void setResourceFolder(const std::string str)
{
resource_folder = str;
}
char *file_getfilebuffer(const char *resourcename, int& filesize) {
FILE *f = file_getfilepointer(resourcename, filesize, true);
char* buffer = (char*)malloc(filesize);
fread(buffer, filesize, 1, f);
fclose(f);
return buffer;
}
// Estableix d'on s'han de obtenir els recursos (arxius individuals dins d'una carpeta o arxiu de recursos)
void setSource(const int src)
{
file_source = src % 2; // mod 2 de forma que sempre es un valor vàlid, 0 (arxiu) o 1 (carpeta)
// Si volem que busque en carpeta i encara no haviem especificat una carpeta, usem la per defecte
if (/*src == SOURCE_FOLDER && */resource_folder == "")
{
setResourceFolder(DEFAULT_FOLDER);
}
}
// Carreguem la taula de continguts de l'arxiu de recursos
bool getTableOfContents()
{
// Si encara no haviem especificat un arxiu de recursos, usem el arxiu de recursos per defecte
if (resource_filename == "")
{
setResourceFilename(DEFAULT_FILENAME);
}
// Si l'arxiu de recursos existeix...
std::ifstream fi(resource_filename, std::ios::binary);
if (fi.is_open())
{
// Llegim la capçalera (controlar que siga correcta?)
char header[4];
fi.read(header, 4);
// LLegim el nombre d'arxius i la posició de la taula de continguts
uint32_t num_files, toc_offset;
fi.read((char *)&num_files, 4);
fi.read((char *)&toc_offset, 4);
// Anem a la taula de continguts
fi.seekg(toc_offset);
// Per a cada arxiu inclos en l'arxiu de recursos...
for (unsigned int i = 0; i < num_files; ++i)
{
// Llegim en quina posició està i quant ocupa
uint32_t file_offset, file_size;
fi.read((char *)&file_offset, 4);
fi.read((char *)&file_size, 4);
// Llegim la seua ruta
uint8_t path_size;
fi.read((char *)&path_size, 1);
char file_name[path_size + 1];
fi.read(file_name, path_size);
file_name[path_size] = 0;
std::string filename = file_name;
// Y afegim les dades a la taula de continguts en memòria
toc.push_back({filename, file_size, file_offset});
}
// Tanquem la paradeta i tornem true
fi.close();
return true;
}
else // Si no s'ha pogut llegir el arxiu de recursos, tornem false
{
return false;
}
}
// Obté un "std::ifstream" al arxiu que se li demana, independentment de la font (arxius individual en carpeta, o arxiu de recursos)
std::ifstream getFileStream(const std::string resourcename, int *filesize, const bool binary)
{
// Si tenim configurat agafar els recursos de arxiu, pero encara no tenim la taula de continguts carregada...
if (file_source == SOURCE_FILE and toc.size() == 0)
{
// Si fallem al intentar carregar la taula de continguts de l'arxiu de recursos, canviem a pillar els recursos de carpeta
if (not getTableOfContents())
{
setSource(SOURCE_FOLDER);
}
}
std::ifstream f;
// Si estem pillant els recursos de un arxiu de recursos...
if (file_source == SOURCE_FILE)
{
// Busquem el recurs en la taula de continguts usant la ruta
bool found = false;
uint32_t count = 0;
while (!found && count < toc.size())
{
found = (resource_folder + resourcename == toc[count].path);
if (!found)
{
count++;
}
}
// Si no trobem el recurs, petem el mame
if (!found)
{
printf("ERROR FATAL: No s'ha trobat el recurs '%s' a l'arxiu de recursos '%s'\n", resourcename.c_str(), resource_filename.c_str());
exit(1);
}
// Agafem el tamany del recurs de la taula de continguts
if (filesize) *filesize = toc[count].size;
// obrim l'arxiu de recursos
f.open(resource_filename.c_str(), binary ? std::ios::binary : std::ios::in);
if (!f.is_open()) // En el raruno cas de que a este altures pete al obrir el arxiu de recursos, petem el mame
{
printf("ERROR FATAL: No s'ha pogut obrir l'arxiu de recursos '%s'\n", resource_filename.c_str());
exit(1);
}
// Anem a la posició on està el recurs que volem. Amb açò "f" ja està preparat per a ser tornar.
// Ojo, realment estic tornant un FILE* al arxiu de recursos, pero ja apuntant al moment en que comença el recurs que volem.
// Ho dic perque si fem fseek(f, 0, SEEK_SET) tornarà al principi de l'arxiu de recursos, no del recurs. Tindre-ho en compte.
f.seekg(toc[count].offset);
}
else
{
// Si estem pillant els recursos de carpeta, simplement obrim el arxiu en questió i tornem el FILE* associat.
f.open((resource_folder + resourcename), binary ? std::ios::binary : std::ios::in);
if (f.rdstate() & std::ios_base::failbit)
{
printf("ERROR FATAL: No s'ha pogut obrir l'arxiu '%s/%s'\n", resource_folder.c_str(), resourcename.c_str());
exit(1);
}
f.seekg(0, std::ios_base::end);
if (filesize) *filesize = f.tellg();
f.seekg(0, std::ios_base::beg);
}
// Tornar el punter FILE* al arxiu. OJO! Tenim que tancar-lo quan acabem amb ell
return f;
}
// Obté un "FILE*" al arxiu que se li demana, independentment de la font (arxius individual en carpeta, o arxiu de recursos)
FILE *getFilePointer(const std::string resourcename, int *filesize, const bool binary)
{
// Si tenim configurat agafar els recursos de arxiu, pero encara no tenim la taula de continguts carregada...
if (file_source == SOURCE_FILE and toc.size() == 0)
{
// Si fallem al intentar carregar la taula de continguts de l'arxiu de recursos, canviem a pillar els recursos de carpeta
if (not getTableOfContents())
{
setSource(SOURCE_FOLDER);
}
}
FILE *f;
// Si estem pillant els recursos de un arxiu de recursos...
if (file_source == SOURCE_FILE)
{
// Busquem el recurs en la taula de continguts usant la ruta
bool found = false;
uint32_t count = 0;
while (!found && count < toc.size())
{
found = (resource_folder + resourcename == toc[count].path);
if (!found)
{
count++;
}
}
// Si no trobem el recurs, petem el mame
if (!found)
{
printf("ERROR FATAL: No s'ha trobat el recurs '%s' a l'arxiu de recursos '%s'\n", resourcename.c_str(), resource_filename.c_str());
exit(1);
}
// Agafem el tamany del recurs de la taula de continguts
if (filesize) *filesize = toc[count].size;
// obrim l'arxiu de recursos
f = fopen(resource_filename.c_str(), binary ? "rb" : "r");
if (!f) // En el raruno cas de que a este altures pete al obrir el arxiu de recursos, petem el mame
{
printf("ERROR FATAL: No s'ha pogut obrir l'arxiu de recursos '%s'\n", resource_filename.c_str());
exit(1);
}
// Anem a la posició on està el recurs que volem. Amb açò "f" ja està preparat per a ser tornar.
// Ojo, realment estic tornant un FILE* al arxiu de recursos, pero ja apuntant al moment en que comença el recurs que volem.
// Ho dic perque si fem fseek(f, 0, SEEK_SET) tornarà al principi de l'arxiu de recursos, no del recurs. Tindre-ho en compte.
fseek(f, toc[count].offset, SEEK_SET);
}
else
{
// Si estem pillant els recursos de carpeta, simplement obrim el arxiu en questió i tornem el FILE* associat.
f = fopen((resource_folder + resourcename).c_str(), binary ? "rb" : "r");
if(!f)
{
printf("ERROR FATAL: No s'ha pogut obrir l'arxiu '%s/%s'\n", resource_folder.c_str(), resourcename.c_str());
exit(1);
}
fseek(f, 0, SEEK_END);
if (filesize) *filesize = ftell(f);
fseek(f, 0, SEEK_SET);
}
// Tornar el punter FILE* al arxiu. OJO! Tenim que tancar-lo quan acabem amb ell
return f;
}
// Obté un buffer de memòria en format "char*" del arxiu que se li demana, independentment de la font (arxius individual en carpeta, o arxiu de recursos)
char *getFileBuffer(const std::string resourcename, int *filesize)
{
int size;
// Usem la funció anterior per a obtinde un FILE*, independentment de on pillem els recursos
FILE *f = getFilePointer(resourcename, &size, true);
// Reservem memòria per al buffer
char *buffer = (char *)malloc(size);
// llegim el contingut del arxiu i el fiquem en el buffer
fread(buffer, size, 1, f);
// Tanquem l'arxiu
fclose(f);
if (filesize) *filesize = size;
// Tornem el buffer. OJO! recordar alliberar-lo amb free(buffer) quan acabem amb ell.
return buffer;
}
// Estableix el nom de la carpeta on es guardarà la configuració
// Adaptat del codi que va escriure JailDesigner en el JailDoctor's Dilemma
// Vull revisar-la tranquilament algun dia
void setConfigFolder(const std::string foldername)
{
// Crea la carpeta del sistema donde guardar datos
void file_setconfigfolder(const char *foldername)
{
#ifdef _WIN32
config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
#elif __APPLE__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
#elif __linux__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/." + foldername;
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/." + foldername;
#endif
struct stat st = {0};
if (stat(config_folder.c_str(), &st) == -1)
{
struct stat st = {0};
if (stat(config_folder.c_str(), &st) == -1)
{
#ifdef _WIN32
int ret = mkdir(config_folder.c_str());
int ret = mkdir(config_folder.c_str());
#else
int ret = mkdir(config_folder.c_str(), S_IRWXU);
int ret = mkdir(config_folder.c_str(), S_IRWXU);
#endif
if (ret == -1)
{
printf("ERROR CREATING CONFIG FOLDER.");
exit(EXIT_FAILURE);
}
if (ret == -1)
{
printf("ERROR CREATING CONFIG FOLDER.");
exit(EXIT_FAILURE);
}
}
}
// Obté el nom de la carpeta on es guardarà la configuració
const std::string getConfigFolder()
{
return config_folder + "/";
const char *file_getconfigfolder() {
std::string folder = config_folder + "/";
return folder.c_str();
}
void file_loadconfigvalues() {
config.clear();
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "r");
if (!f) return;
char line[1024];
while (fgets(line, sizeof(line), f)) {
char *value = strchr(line, '=');
if (value) {
*value='\0'; value++;
value[strlen(value)-1] = '\0';
config.push_back({line, value});
}
}
fclose(f);
}
void file_saveconfigvalues() {
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "w");
if (f) {
for (auto pair : config) {
fprintf(f, "%s=%s\n", pair.key.c_str(), pair.value.c_str());
}
fclose(f);
}
}
// Carrega tots els valors guardats a l'arxiu de recursos
void loadConfigValues()
{
// Buidem la taula de claus-valors en memòria
config.clear();
// Obrim l'arxiu de configuració
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "r");
if (f)
{
// Agafem linea a linea
char line[1024];
while (fgets(line, sizeof(line), f))
{
// Separem la clau del valor
char *value = strchr(line, '=');
if (value)
{
*value = '\0';
value++;
value[strlen(value) - 1] = '\0';
// i els afegim a la taula de claus-valors en memòria
config.push_back({line, value});
}
}
// tanquem la paradeta
fclose(f);
const char* file_getconfigvalue(const char *key) {
if (config.empty()) file_loadconfigvalues();
for (auto pair : config) {
if (pair.key == std::string(key)) {
strcpy(scratch, pair.value.c_str());
return scratch;
}
}
return NULL;
}
// Guardem tots els valors en la taula de claus-valors a l'arxiu de configuració
void saveConfigValues()
{
// Obrim l'arxiu de configuració
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "w");
if (f)
{
// Guardem cada parella clau/valor de la taula en memòria en una linea en format "clau=valor\n" en l'arxiu de configuració
for (auto pair : config)
{
fprintf(f, "%s=%s\n", pair.key.c_str(), pair.value.c_str());
}
// tanquem la paradeta
fclose(f);
void file_setconfigvalue(const char* key, const char* value) {
if (config.empty()) file_loadconfigvalues();
for (auto &pair : config) {
if (pair.key == std::string(key)) {
pair.value = value;
file_saveconfigvalues();
return;
}
}
// Obté un valor de l'arxiu de configuració per a la clau donada (o cadena buida si no existeix)
const std::string getConfigValue(const std::string key)
{
// Si la taula de claus-valors esta buida, la carreguem de l'arxiu de configuració
if (config.empty())
{
loadConfigValues();
}
// busquem la clau en la taula
for (auto pair : config)
{
if (pair.key == std::string(key))
{
// Si la trobem, tornem el seu valor
return pair.value;
}
}
// Si no la trobem, tornem cadena buida
return "";
}
// Estableix un valor en l'arxiu de configuració per a la clau donada
void setConfigValue(const std::string key, const std::string value)
{
// Si la taula de claus-valors esta buida, la carreguem de l'arxiu de configuració
if (config.empty())
{
loadConfigValues();
}
// busquem la clau en la taula
for (auto &pair : config)
{
if (pair.key == std::string(key))
{
// Si la trobem, actualitzem el seu valor i guardem els canvis a l'arxiu de configuració
pair.value = value;
saveConfigValues();
return;
}
}
// Si NO la trobem, afegim la nova clau i el seu valor, i guardem els canvis a l'arxiu de configuració
config.push_back({key, value});
saveConfigValues();
}
}
config.push_back({key, value});
file_saveconfigvalues();
return;
}