Comencem a ficar el codi nou, ara va en la carpeta "source"
This commit is contained in:
422
source/jfile.cpp
Normal file
422
source/jfile.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
#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)
|
||||
{
|
||||
// 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
|
||||
char *buffer = (char *)malloc(filesize);
|
||||
|
||||
// llegim el contingut del arxiu i el fiquem en el buffer
|
||||
fread(buffer, filesize, 1, f);
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user