#include #include #include #include #include "jfile.h" #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif #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 segon arxiu 47 4 4 tamany del segon axiu 51 14 1 tamany de la ruta al segon arxiu 52 "data/adios.txt" 14 la ruta al segon 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 { // Estructures // =============================================================================================================================== // 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 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; }; // Variables // =============================================================================================================================== static std::string resource_filename = ""; // Nom de l'arxiu de recursos static std::string resource_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 config; // Vector amb els valors guardats a l'arxiu de configuració // Funcions // =============================================================================================================================== // 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; } // 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; } // 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 copua 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; } } bool fileExists(const std::string resourcename) { // 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); } } // 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 = (resourcename == toc[count].path); if (!found) { count++; } } return found; } else { return access((resource_folder + resourcename).c_str(), F_OK) == 0; } } // 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 = (resourcename == toc[count].path); if (!found) { count++; } } // Si no trobem el recurs, petem el mame if (!found) { // [TODO] Donar mes informació de quin recurs no havem trobat printf("Arxiu: %s\n", resourcename.c_str()); perror("El recurs no s'ha trobat en l'arxiu de recursos"); exit(1); } // Agafem el tamany del recurs de la taula de continguts 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 { // [TODO] Donar mes informació de quin recurs no havem trobat perror("No s'ha pogut obrir l'arxiu de recursos"); 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) { fseek(f, 0, SEEK_END); 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, const bool zeroTerminated) { // Per si de cas falla la cosa, fiquem el filesize a 0 filesize = 0; // Usem la funció anterior per a obtinde un FILE*, independentment de on pillem els recursos FILE *f = getFilePointer(resourcename, filesize, true); // Si l'arxiu no existeix, tornem nullptr if (!f) return nullptr; // Reservem memòria per al buffer // Si afegim el 0 al final, el tamany del buffer es 1 byte mes gran char *buffer = (char *)malloc(filesize + (zeroTerminated ? 1 : 0)); // llegim el contingut del arxiu i el fiquem en el buffer fread(buffer, filesize, 1, f); // Afegim el 0 al final si toca (i augmentem en 1 la variable filesize que tornem) if (zeroTerminated) buffer[filesize++] = 0; // Tanquem l'arxiu fclose(f); // 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) { #ifdef _WIN32 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; #elif __linux__ struct passwd *pw = getpwuid(getuid()); const char *homedir = pw->pw_dir; config_folder = std::string(homedir) + "/.config/" + foldername; { // Intenta crear ".config", per si no existeix std::string config_base_folder = std::string(homedir) + "/.config"; int ret = mkdir(config_base_folder.c_str(), S_IRWXU); if (ret == -1 && errno != EEXIST) { printf("ERROR CREATING CONFIG BASE FOLDER."); exit(EXIT_FAILURE); } } #endif struct stat st = {0}; if (stat(config_folder.c_str(), &st) == -1) { #ifdef _WIN32 int ret = mkdir(config_folder.c_str()); #else int ret = mkdir(config_folder.c_str(), S_IRWXU); #endif 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 + "/"; } // 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); } } // 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); } } // 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(); } char tmp[255]; void ignoreWhitespace(char **buffer, const bool stopOnLineEnd=false) { if (!stopOnLineEnd) while (**buffer!=0 && **buffer<=32) (*buffer)++; else while (**buffer!='\n' && **buffer!=0 && **buffer<=32) (*buffer)++; } // Llig una cadena de l'arxiu especificat. En cas de no poder, torna nullptr. const char *readString(char **buffer, const bool stopOnLineEnd) { ignoreWhitespace(buffer, stopOnLineEnd); if (**buffer==0) return nullptr; if (stopOnLineEnd && **buffer=='\n') { (*buffer)++; return nullptr; } int i=0; while (**buffer>32 && i<255) { tmp[i++] = *(*buffer)++; } tmp[i]=0; return tmp; } // Llig un enter de l'arxiu especificat. En cas de no poder, torna 0. int readInt(char **buffer) { ignoreWhitespace(buffer); if (**buffer==0) return 0; return atoi(readString(buffer)); } // Escriu una cadena a l'arxiu especificat. void writeString(FILE *f, const char* str) { fprintf(f, "%s ", str); } // Escriu un enter a l'arxiu especificat. void writeInt(FILE *f, const int value) { fprintf(f, "%i ", value); } // Escriu un salt de linea a l'arxiu especificat. void jumpLine(FILE *f) { fprintf(f, "\n"); } }