484 lines
15 KiB
C++
484 lines
15 KiB
C++
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include "jfile.h"
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifndef _WIN32
|
|
#include <pwd.h>
|
|
#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 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
|
|
{
|
|
// 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<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;
|
|
};
|
|
|
|
// 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<keyvalue_t> 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;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
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");
|
|
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)
|
|
{
|
|
// Usem la funció anterior per a obtinde un FILE*, independentment de on pillem els recursos
|
|
FILE *f = getFilePointer(resourcename, filesize, true);
|
|
|
|
// 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) + "/." + foldername;
|
|
#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(const 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(const char **buffer, const bool stopOnLineEnd=false)
|
|
{
|
|
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++);
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
// Llig un enter de l'arxiu especificat. En cas de no poder, torna 0.
|
|
int readInt(const 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");
|
|
}
|
|
|
|
} |