#include "qcommon.h"
#include <uv.h>
#define PAK0_CHECKSUM 0x40e614e0
typedef struct {
char name[MAX_QPATH];
int filepos, filelen;
} packfile_t;
typedef struct pack_s {
char filename[MAX_OSPATH];
uv_file handle;
int numfiles;
packfile_t *files;
} pack_t;
char fs_gamedir[MAX_OSPATH];
cvar_t *fs_basedir;
cvar_t *fs_cddir;
cvar_t *fs_gamedirvar;
typedef struct filelink_s {
struct filelink_s *next;
char *from;
int fromlength;
char *to;
} filelink_t;
filelink_t *fs_links;
typedef struct searchpath_s {
char filename[MAX_OSPATH];
pack_t *pack; struct searchpath_s *next;
} searchpath_t;
searchpath_t *fs_searchpaths;
searchpath_t *fs_base_searchpaths;
int FS_filelength(FILE *f) {
int pos;
int end;
pos = ftell(f);
fseek(f, 0, SEEK_END);
end = ftell(f);
fseek(f, pos, SEEK_SET);
return end;
}
void FS_CreatePath(const char *path_) {
size_t length = strlen(path_);
char *path = malloc(length + 1);
memcpy(path, path_, length + 1);
for(char *ofs = path + 1; *ofs; ofs++) {
if(*ofs == '/') { *ofs = 0;
uv_fs_t req;
uv_fs_mkdir(global_uv_loop(), &req, path, 0777, NULL);
uv_fs_req_cleanup(&req);
*ofs = '/';
}
}
free(path);
}
void FS_FCloseFile(FILE *f) { fclose(f); }
int Developer_searchpath(int who) {
searchpath_t *search;
for(search = fs_searchpaths; search; search = search->next) {
if(strstr(search->filename, "xatrix"))
return 1;
if(strstr(search->filename, "rogue"))
return 2;
}
return (0);
}
int file_from_pak = 0;
#ifndef NO_ADDONS
int FS_FOpenFile(const char *filename, FILE **file) {
searchpath_t *search;
char netpath[MAX_OSPATH];
pack_t *pak;
int i;
filelink_t *link;
file_from_pak = 0;
for(link = fs_links; link; link = link->next) {
if(!strncmp(filename, link->from, link->fromlength)) {
Com_sprintf(netpath, sizeof(netpath), "%s%s", link->to, filename + link->fromlength);
*file = fopen(netpath, "rb");
if(*file) {
Com_DPrintf("link file: %s\n", netpath);
return FS_filelength(*file);
}
return -1;
}
}
for(search = fs_searchpaths; search; search = search->next) {
if(search->pack) {
pak = search->pack;
for(i = 0; i < pak->numfiles; i++)
if(!Q_strcasecmp(pak->files[i].name, filename)) { file_from_pak = 1;
Com_DPrintf("PackFile: %s : %s\n", pak->filename, filename);
*file = fopen(pak->filename, "rb");
if(!*file)
Com_Error(ERR_FATAL, "Couldn't reopen %s", pak->filename);
fseek(*file, pak->files[i].filepos, SEEK_SET);
return pak->files[i].filelen;
}
} else {
Com_sprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename);
*file = fopen(netpath, "rb");
if(!*file)
continue;
Com_DPrintf("FindFile: %s\n", netpath);
return FS_filelength(*file);
}
}
Com_DPrintf("FindFile: can't find %s\n", filename);
*file = NULL;
return -1;
}
#else
int FS_FOpenFile(char *filename, FILE **file) {
searchpath_t *search;
char netpath[MAX_OSPATH];
pack_t *pak;
int i;
file_from_pak = 0;
if(!strcmp(filename, "config.cfg") || !strncmp(filename, "players/", 8)) {
Com_sprintf(netpath, sizeof(netpath), "%s/%s", FS_Gamedir(), filename);
*file = fopen(netpath, "rb");
if(!*file)
return -1;
Com_DPrintf("FindFile: %s\n", netpath);
return FS_filelength(*file);
}
for(search = fs_searchpaths; search; search = search->next)
if(search->pack)
break;
if(!search) {
*file = NULL;
return -1;
}
pak = search->pack;
for(i = 0; i < pak->numfiles; i++)
if(!Q_strcasecmp(pak->files[i].name, filename)) { file_from_pak = 1;
Com_DPrintf("PackFile: %s : %s\n", pak->filename, filename);
*file = fopen(pak->filename, "rb");
if(!*file)
Com_Error(ERR_FATAL, "Couldn't reopen %s", pak->filename);
fseek(*file, pak->files[i].filepos, SEEK_SET);
return pak->files[i].filelen;
}
Com_DPrintf("FindFile: can't find %s\n", filename);
*file = NULL;
return -1;
}
#endif
void CDAudio_Stop(void);
#define MAX_READ 0x10000
void FS_Read(void *buffer, int len, FILE *f) {
int block, remaining;
int read;
byte *buf;
buf = (byte *)buffer;
remaining = len;
while(remaining) {
block = remaining;
if(block > MAX_READ)
block = MAX_READ;
read = fread(buf, 1, block, f);
if(read == 0) {
Com_Error(ERR_FATAL, "FS_Read: 0 bytes read");
}
if(read == -1)
Com_Error(ERR_FATAL, "FS_Read: -1 bytes read");
remaining -= read;
buf += read;
}
}
int FS_LoadFile(const char *path, void **buffer) {
FILE *h;
byte *buf;
int len;
buf = NULL;
len = FS_FOpenFile(path, &h);
if(!h) {
if(buffer)
*buffer = NULL;
return -1;
}
if(!buffer) {
fclose(h);
return len;
}
buf = Z_Malloc(len);
*buffer = buf;
FS_Read(buf, len, h);
fclose(h);
return len;
}
void FS_FreeFile(void *buffer) { Z_Free(buffer); }
pack_t *FS_LoadPackFile(char *packfile) {
dpackheader_t header;
int i;
packfile_t *newfiles;
int numpackfiles;
pack_t *pack;
FILE *packhandle;
dpackfile_t info[MAX_FILES_IN_PACK];
packhandle = fopen(packfile, "rb");
if(!packhandle)
return NULL;
fread(&header, 1, sizeof(header), packhandle);
if(LittleLong(header.ident) != IDPAKHEADER)
Com_Error(ERR_FATAL, "%s is not a packfile", packfile);
header.dirofs = LittleLong(header.dirofs);
header.dirlen = LittleLong(header.dirlen);
numpackfiles = header.dirlen / sizeof(dpackfile_t);
if(numpackfiles > MAX_FILES_IN_PACK)
Com_Error(ERR_FATAL, "%s has %i files", packfile, numpackfiles);
newfiles = Z_Malloc(numpackfiles * sizeof(packfile_t));
fseek(packhandle, header.dirofs, SEEK_SET);
fread(info, 1, header.dirlen, packhandle);
#ifdef NO_ADDONS
int checksum = Com_BlockChecksum((void *)info, header.dirlen);
if(checksum != PAK0_CHECKSUM)
return NULL;
#endif
for(i = 0; i < numpackfiles; i++) {
strcpy(newfiles[i].name, info[i].name);
newfiles[i].filepos = LittleLong(info[i].filepos);
newfiles[i].filelen = LittleLong(info[i].filelen);
}
pack = Z_Malloc(sizeof(pack_t));
strcpy(pack->filename, packfile);
pack->numfiles = numpackfiles;
pack->files = newfiles;
uv_fs_t req;
uv_fs_open(global_uv_loop(), &req, packfile, O_RDONLY, 0, NULL);
pack->handle = uv_fs_get_result(&req);
uv_fs_req_cleanup(&req);
Com_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
return pack;
}
void FS_AddGameDirectory(char *dir) {
int i;
searchpath_t *search;
pack_t *pak;
char pakfile[MAX_OSPATH];
strcpy(fs_gamedir, dir);
search = Z_Malloc(sizeof(searchpath_t));
strcpy(search->filename, dir);
search->next = fs_searchpaths;
fs_searchpaths = search;
for(i = 0; i < 10; i++) {
Com_sprintf(pakfile, sizeof(pakfile), "%s/pak%i.pak", dir, i);
pak = FS_LoadPackFile(pakfile);
if(!pak)
continue;
search = Z_Malloc(sizeof(searchpath_t));
search->pack = pak;
search->next = fs_searchpaths;
fs_searchpaths = search;
}
}
const char *FS_Gamedir(void) { return fs_gamedir; }
void FS_ExecAutoexec(void) {
char *dir;
char name[MAX_QPATH];
dir = Cvar_VariableString("gamedir");
if(*dir)
Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, dir);
else
Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, BASEDIRNAME);
if(Sys_FindFirst(name, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM))
Cbuf_AddText("exec autoexec.cfg\n");
Sys_FindClose();
}
void FS_SetGamedir(const char *dir) {
searchpath_t *next;
if(strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\") || strstr(dir, ":")) {
Com_Printf("Gamedir should be a single filename, not a path\n");
return;
}
while(fs_searchpaths != fs_base_searchpaths) {
if(fs_searchpaths->pack) {
uv_fs_t req;
uv_fs_close(global_uv_loop(), &req, fs_searchpaths->pack->handle, NULL);
uv_fs_req_cleanup(&req);
Z_Free(fs_searchpaths->pack->files);
Z_Free(fs_searchpaths->pack);
}
next = fs_searchpaths->next;
Z_Free(fs_searchpaths);
fs_searchpaths = next;
}
if(dedicated && !dedicated->value)
Cbuf_AddText("vid_restart\nsnd_restart\n");
Cbuf_AddText("sv_reload_database\n");
Com_sprintf(fs_gamedir, sizeof(fs_gamedir), "%s/%s", fs_basedir->string, dir);
if(!strcmp(dir, BASEDIRNAME) || (*dir == 0)) {
Cvar_FullSet("gamedir", "", CVAR_SERVERINFO | CVAR_NOSET);
Cvar_FullSet("game", "", CVAR_LATCH | CVAR_SERVERINFO);
} else {
Cvar_FullSet("gamedir", dir, CVAR_SERVERINFO | CVAR_NOSET);
if(fs_cddir->string[0])
FS_AddGameDirectory(va("%s/%s", fs_cddir->string, dir));
FS_AddGameDirectory(va("%s/%s", fs_basedir->string, dir));
}
}
void FS_Link_f(void) {
filelink_t *l, **prev;
if(Cmd_Argc() != 3) {
Com_Printf("USAGE: link <from> <to>\n");
return;
}
prev = &fs_links;
for(l = fs_links; l; l = l->next) {
if(!strcmp(l->from, Cmd_Argv(1))) {
Z_Free(l->to);
if(!strlen(Cmd_Argv(2))) { *prev = l->next;
Z_Free(l->from);
Z_Free(l);
return;
}
l->to = CopyString(Cmd_Argv(2));
return;
}
prev = &l->next;
}
l = Z_Malloc(sizeof(*l));
l->next = fs_links;
fs_links = l;
l->from = CopyString(Cmd_Argv(1));
l->fromlength = strlen(l->from);
l->to = CopyString(Cmd_Argv(2));
}
char **FS_ListFiles(char *findname, int *numfiles, unsigned musthave, unsigned canthave) {
char *s;
int nfiles = 0;
char **list = 0;
s = Sys_FindFirst(findname, musthave, canthave);
while(s) {
if(s[strlen(s) - 1] != '.')
nfiles++;
s = Sys_FindNext(musthave, canthave);
}
Sys_FindClose();
if(!nfiles)
return NULL;
nfiles++; *numfiles = nfiles;
list = malloc(sizeof(char *) * nfiles);
memset(list, 0, sizeof(char *) * nfiles);
s = Sys_FindFirst(findname, musthave, canthave);
nfiles = 0;
while(s) {
if(s[strlen(s) - 1] != '.') {
list[nfiles] = strdup(s);
#ifdef _WIN32
strlwr(list[nfiles]);
#endif
nfiles++;
}
s = Sys_FindNext(musthave, canthave);
}
Sys_FindClose();
return list;
}
void FS_Dir_f(void) {
const char *path = NULL;
char findname[1024];
char wildcard[1024] = "*.*";
char **dirnames;
int ndirs;
if(Cmd_Argc() != 1) {
strcpy(wildcard, Cmd_Argv(1));
}
while((path = FS_NextPath(path)) != NULL) {
char *tmp = findname;
Com_sprintf(findname, sizeof(findname), "%s/%s", path, wildcard);
while(*tmp != 0) {
if(*tmp == '\\')
*tmp = '/';
tmp++;
}
Com_Printf("Directory of %s\n", findname);
Com_Printf("----\n");
if((dirnames = FS_ListFiles(findname, &ndirs, 0, 0)) != 0) {
int i;
for(i = 0; i < ndirs - 1; i++) {
if(strrchr(dirnames[i], '/'))
Com_Printf("%s\n", strrchr(dirnames[i], '/') + 1);
else
Com_Printf("%s\n", dirnames[i]);
free(dirnames[i]);
}
free(dirnames);
}
Com_Printf("\n");
};
}
void FS_Path_f(void) {
searchpath_t *s;
filelink_t *l;
Com_Printf("Current search path:\n");
for(s = fs_searchpaths; s; s = s->next) {
if(s == fs_base_searchpaths)
Com_Printf("----------\n");
if(s->pack)
Com_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
else
Com_Printf("%s\n", s->filename);
}
Com_Printf("\nLinks:\n");
for(l = fs_links; l; l = l->next)
Com_Printf("%s : %s\n", l->from, l->to);
}
const char *FS_NextPath(const char *prevpath) {
searchpath_t *s;
const char *prev;
if(!prevpath)
return fs_gamedir;
prev = fs_gamedir;
for(s = fs_searchpaths; s; s = s->next) {
if(s->pack)
continue;
if(prevpath == prev)
return s->filename;
prev = s->filename;
}
return NULL;
}
void FS_InitFilesystem(void) {
Cmd_AddCommand("path", FS_Path_f);
Cmd_AddCommand("link", FS_Link_f);
Cmd_AddCommand("dir", FS_Dir_f);
fs_basedir = Cvar_Get("basedir", ".", CVAR_NOSET);
fs_cddir = Cvar_Get("cddir", "", CVAR_NOSET);
if(fs_cddir->string[0])
FS_AddGameDirectory(va("%s/" BASEDIRNAME, fs_cddir->string));
FS_AddGameDirectory(va("%s/" BASEDIRNAME, fs_basedir->string));
fs_base_searchpaths = fs_searchpaths;
fs_gamedirvar = Cvar_Get("game", "", CVAR_LATCH | CVAR_SERVERINFO);
if(fs_gamedirvar->string[0])
FS_SetGamedir(fs_gamedirvar->string);
}
struct LoadAsyncState {
uv_fs_t req;
const char *path;
void (*error)(void *ud);
void (*done)(const void *, int, void *ud);
void *ud;
uv_file file;
uint64_t offset;
uint64_t size;
void *buffer;
filelink_t *link;
searchpath_t *search;
int pack_file_index;
};
void LoadAsync_continue(struct LoadAsyncState *state);
void LoadAsync_process_close(uv_fs_t *req) {
struct LoadAsyncState *state = (struct LoadAsyncState *)uv_handle_get_data((uv_handle_t *)req);
uv_fs_req_cleanup(req);
Z_Free(state);
}
void LoadAsync_process_read(uv_fs_t *req) {
struct LoadAsyncState *state = (struct LoadAsyncState *)uv_handle_get_data((uv_handle_t *)req);
uv_file result = uv_fs_get_result(req);
uv_fs_req_cleanup(req);
if(result < 0) {
state->error(state->ud);
} else {
state->done(state->buffer, state->size, state->ud);
Z_Free(state->buffer);
state->buffer = NULL;
}
if(state->file) {
uv_fs_close(global_uv_loop(), req, state->file, LoadAsync_process_close);
} else {
Z_Free(state);
}
}
void LoadAsync_process_fstate(uv_fs_t *req) {
struct LoadAsyncState *state = (struct LoadAsyncState *)uv_handle_get_data((uv_handle_t *)req);
uv_file result = uv_fs_get_result(req);
uint64_t size = uv_fs_get_statbuf(req)->st_size;
uv_fs_req_cleanup(req);
if(result < 0) {
LoadAsync_continue(state);
} else {
state->size = size;
state->buffer = Z_Malloc(size);
uv_fs_read(global_uv_loop(), req, state->file, &(uv_buf_t){.base = state->buffer, .len = size}, 1, state->offset,
LoadAsync_process_read);
}
}
void LoadAsync_process_open(uv_fs_t *req) {
struct LoadAsyncState *state = (struct LoadAsyncState *)uv_handle_get_data((uv_handle_t *)req);
uv_file result = uv_fs_get_result(req);
uv_fs_req_cleanup(req);
if(result < 0) {
LoadAsync_continue(state);
} else {
state->file = result;
uv_fs_fstat(global_uv_loop(), req, state->file, LoadAsync_process_fstate);
}
}
void LoadAsync_continue(struct LoadAsyncState *state) {
char temp_path[MAX_OSPATH];
while(state->link) {
filelink_t *link = state->link;
state->link = link->next;
if(!strncmp(state->path, link->from, link->fromlength)) {
Com_sprintf(temp_path, sizeof(temp_path), "%s%s", link->to, state->path + link->fromlength);
uv_fs_open(global_uv_loop(), &state->req, temp_path, O_RDONLY, 0, LoadAsync_process_open);
return;
}
}
while(state->search) {
searchpath_t *search = state->search;
if(search->pack) {
while(state->pack_file_index < search->pack->numfiles) {
packfile_t *pack_file = &search->pack->files[state->pack_file_index++];
if(!Q_strcasecmp(pack_file->name, (char *)state->path)) {
state->size = pack_file->filelen;
state->offset = pack_file->filepos;
state->buffer = Z_Malloc(state->size);
uv_fs_read(global_uv_loop(), &state->req, state->search->pack->handle,
&(uv_buf_t){.base = state->buffer, .len = state->size}, 1, state->offset, LoadAsync_process_read);
return;
}
}
state->pack_file_index = 0;
state->search = search->next;
continue;
} else {
state->search = search->next;
Com_sprintf(temp_path, sizeof(temp_path), "%s/%s", search->filename, state->path);
uv_fs_open(global_uv_loop(), &state->req, temp_path, O_RDONLY, 0, LoadAsync_process_open);
return;
}
}
}
void FS_LoadAsync(const char *path, void (*error)(void *ud), void (*done)(const void *, int, void *ud), void *ud) {
struct LoadAsyncState *state = Z_Malloc(sizeof(*state) + strlen(path) + 1);
memset(state, 0, sizeof(*state));
memcpy(state + 1, path, strlen(path) + 1);
uv_handle_set_data((uv_handle_t *)&state->req, state);
state->path = (const char *)(state + 1);
state->link = fs_links;
state->search = fs_searchpaths;
state->error = error;
state->done = done;
state->ud = ud;
LoadAsync_continue(state);
}