Files
arounders/source/jfile.cpp

425 lines
13 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
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
{
// [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);
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)
{
#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();
}
}