#include "quakedef.h" #include "fs.h" #include #include #include #include #include #include #include #include #include #include #include extern PPB_FileIO *ppb_fileio; extern PPB_FileRef *ppb_fileref; extern PPB_FileSystem *ppb_filesystem; extern PPB_Core *ppb_core; extern PPB_URLLoader *urlloader; extern PPB_URLRequestInfo *urlrequestinfo; extern PPB_URLResponseInfo *urlresponseinfo; extern PPB_Var *ppb_var_interface; extern PP_Instance pp_instance; #define GOOGLE_DONT_KNOW_HOW_TO_CREATE_USABLE_APIS #ifdef GOOGLE_DONT_KNOW_HOW_TO_CREATE_USABLE_APIS //the pepper api is flawed. deeply flawed. //1: api calls (including various gl calls) may only be made on the main thread). //2: the main thread may not use non-asyncronous calls. //the recommendation to get around this is to run everything on a worker thread, but to make calls to the main thread to do the actual call, then signal the worker thread to wake up again in the main thread's callback. //which is impractical when you have 80+ different sorts of performance-dependant gl calls. //I can't easily put things requiring file access on another thread, if only because it would make alias/exec console commands non-synchronous //to get around this, I instead make my own memory-only 'filesystem', populating it at startup with downloads. I *really* hope your browser/server are set to enable file caching. //at some point I'll periodically write the data back to a persistant store/reload it at startup so saved games/configs can work #define FSPPAPI_OpenTemp FS_OpenTemp #define VFSPPAPI_Open VFSOS_Open typedef struct mfchunk_s { struct mfchunk_s *prev; struct mfchunk_s *next; unsigned long startpos; unsigned int len; char data[64]; } mfchunk_t; typedef struct mfile_s { /*chunks can be trimmed only when there's no refs*/ char name[MAX_QPATH]; int refs; int unlinked:1; unsigned long length; mfchunk_t *chunkhead; mfchunk_t *chunktail; struct mfile_s *prev; struct mfile_s *next; } mfile_t; mfile_t *mfiles; typedef struct { vfsfile_t funcs; mfile_t *file; unsigned long offset; mfchunk_t *cchunk; } vfsmfile_t; typedef struct { PP_Resource dl; char buffer[65536]; char *fname; vfsfile_t *out; } dlfile_t; static int activedls; static int availdlslots = 2; void readfinished(void* user_data, int32_t result) { dlfile_t *f = user_data; /*if there was a prior request that didn't finish yet...*/ struct PP_CompletionCallback ccb = {readfinished, f, PP_COMPLETIONCALLBACK_FLAG_OPTIONAL}; // Sys_Printf("lastresult: %i\n", result); if (result == PP_OK) { Sys_Printf("%s completed\n", f->fname); if (f->out) VFS_CLOSE(f->out); //ppb_core->ReleaseResource(f->dl); activedls--; availdlslots++; free(f); return; } for (; result > 0; result = urlloader->ReadResponseBody(f->dl, f->buffer, sizeof(f->buffer), ccb)) { if (!f->out) { Sys_Printf("Downloading %s\n", f->fname); f->out = VFSOS_Open(f->fname, "wb"); } // Sys_Printf("write: %i\n", result); VFS_WRITE(f->out, f->buffer, result); } // Sys_Printf("result: %i\n", result); if (result != PP_OK_COMPLETIONPENDING) { Sys_Printf("file %s failed or something\n", f->fname); if (f->out) VFS_CLOSE(f->out); ppb_core->ReleaseResource(f->dl); activedls--; availdlslots++; free(f); } } void dlstarted(void* user_data, int32_t result) { dlfile_t *f = user_data; struct PP_CompletionCallback ccb = {readfinished, f, PP_COMPLETIONCALLBACK_FLAG_OPTIONAL}; readfinished(user_data, urlloader->ReadResponseBody(f->dl, f->buffer, sizeof(f->buffer), ccb)); } qboolean FSPPAPI_Init(int *fileid) { dlfile_t *dlf; PP_Resource dlri; static char *dlnames[] = { "id1/pak0.pak", // "id1/gfx/conback.tga", // "id1/gfx/conchars.tga", "id1/pak1.pak", // "id1/bigass1.dem", // "id1/overkill.qwd", "id1/pak2.pak", "id1/pak3.pak", "id1/pak4.pak", NULL }; if (availdlslots) { if (!dlnames[*fileid]) { if (!activedls) return true; /*engine has all the content it needs*/ return false; /*still downloading something, don't let it continue just yet*/ } dlf = malloc(sizeof(*dlf)); if (!dlf) return false; dlf->out = NULL; activedls++; availdlslots--; dlf->fname = dlnames[*fileid]; *fileid+=1; dlf->dl = urlloader->Create(pp_instance); dlri = urlrequestinfo->Create(pp_instance); urlrequestinfo->SetProperty(dlri, PP_URLREQUESTPROPERTY_URL, ppb_var_interface->VarFromUtf8(dlf->fname, strlen(dlf->fname))); struct PP_CompletionCallback ccb = {dlstarted, dlf, PP_COMPLETIONCALLBACK_FLAG_NONE}; urlloader->Open(dlf->dl, dlri, ccb); ppb_core->ReleaseResource(dlri); } return false; } static int preparechunk(vfsmfile_t *f, int bytes, void **data) { int sz; mfchunk_t *cnk; if (!bytes) { *data = 0; return 0; } if (!f->cchunk) cnk = f->file->chunkhead; else { cnk = f->cchunk; //rewind through the chunks while (cnk->startpos > f->offset) cnk = cnk->prev; } //find the chunk that contains our start offset while (!cnk || cnk->startpos+cnk->len <= f->offset) { if (!cnk) { sz = (bytes + sizeof(*cnk) - sizeof(cnk->data) + 4095) & ~4095; if (sz < 65536) sz = 65536; cnk = malloc(sz); memset(cnk, 0xcc, sz); if (!cnk) { *data = 0; return 0; } cnk->len = (sz + sizeof(cnk->data) - sizeof(*cnk)); cnk->next = NULL; if (f->file->chunktail) { cnk->prev = f->file->chunktail; cnk->prev->next = cnk; cnk->startpos = cnk->prev->startpos + cnk->prev->len; } else { f->file->chunkhead = cnk; cnk->prev = NULL; cnk->startpos = 0; } // Sys_Printf("Allocated chunk %p: %u-%u\n", cnk, cnk->startpos, cnk->startpos + cnk->len); f->file->chunktail = cnk; } else cnk = cnk->next; } // Sys_Printf("Returning offset %p, %i\n", cnk, (f->offset - cnk->startpos)); // Sys_Printf("%u %u\n", f->offset, cnk->startpos); *data = cnk->data + (f->offset - cnk->startpos); f->cchunk = cnk; sz = cnk->startpos + cnk->len - f->offset; if (sz > bytes) { // Sys_Printf("Returning len %u\n", bytes); return bytes; } // Sys_Printf("Returning len %u\n", sz); return sz; } static int VFSMEM_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) { int total = 0; int chunklen; void *chunkdat; vfsmfile_t *f = (vfsmfile_t*)file; if (bytestoread > f->file->length - f->offset) bytestoread = f->file->length - f->offset; while ((chunklen = preparechunk(f, bytestoread, &chunkdat)) > 0) { // Sys_Printf("Read %i at %u\n", chunklen, f->offset); memcpy(buffer, chunkdat, chunklen); buffer = (char*)buffer + chunklen; bytestoread -= chunklen; total += chunklen; f->offset += chunklen; } // Sys_Printf("%s", (char*)buffer-total); return total; } static int VFSMEM_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestoread) { int total = 0; int chunklen; void *chunkdat; vfsmfile_t *f = (vfsmfile_t*)file; while ((chunklen = preparechunk(f, bytestoread, &chunkdat)) > 0) { // Sys_Printf("Write %i at %u\n", chunklen, f->offset); memcpy(chunkdat, buffer, chunklen); buffer = (char*)buffer + chunklen; bytestoread -= chunklen; total += chunklen; f->offset += chunklen; } if (f->file->length < f->offset) f->file->length = f->offset; // Sys_Printf("written: %i, file is now at %i\n", total, f->offset); return total; } static qboolean VFSMEM_Seek (struct vfsfile_s *file, unsigned long pos) { vfsmfile_t *f = (vfsmfile_t*)file; f->offset = pos; return true; } static unsigned long VFSMEM_Tell (struct vfsfile_s *file) { vfsmfile_t *f = (vfsmfile_t*)file; return f->offset; } static unsigned long VFSMEM_GetSize (struct vfsfile_s *file) { vfsmfile_t *f = (vfsmfile_t*)file; return f->file->length; } static void VFSMEM_Close(vfsfile_t *file) { vfsmfile_t *f = (vfsmfile_t*)file; f->file->refs -= 1; if (!f->file->refs) { if (f->file->unlinked) { mfchunk_t *cnk; while (f->file->chunkhead) { cnk = f->file->chunkhead->next; free(f->file->chunkhead); f->file->chunkhead = cnk; } free(f->file); } } free(f); } static void VFSMEM_Flush(struct vfsfile_s *file) { // vfsmfile_t *f = (vfsmfile_t*)file; } vfsfile_t *FSPPAPI_OpenTemp(void) { /*create a file which is already unlinked*/ mfile_t *f; vfsmfile_t *r; f = malloc(sizeof(*f)); if (!f) return NULL; strcpy(f->name, "some temp file"); f->refs = 0; f->unlinked = true; f->length = 0; f->next = NULL; f->prev = NULL; f->chunkhead = NULL; f->chunktail = NULL; r = malloc(sizeof(*r)); if (!r) return NULL; r->file = f; r->offset = 0; r->cchunk = NULL; f->refs++; r->funcs.ReadBytes = VFSMEM_ReadBytes; r->funcs.WriteBytes = VFSMEM_WriteBytes; r->funcs.Seek = VFSMEM_Seek; r->funcs.Tell = VFSMEM_Tell; r->funcs.GetLen = VFSMEM_GetSize; r->funcs.Close = VFSMEM_Close; r->funcs.Flush = VFSMEM_Flush; return &r->funcs; } vfsfile_t *VFSPPAPI_Open(const char *osname, const char *mode) { mfile_t *f; vfsmfile_t *r; if (strlen(osname) >= sizeof(f->name)) return NULL; for (f = mfiles; f; f = f->next) { if (!strcmp(f->name, osname)) break; } if (!f && (*mode == 'w' || *mode == 'a')) { f = malloc(sizeof(*f)); if (f) { strcpy(f->name, osname); f->refs = 0; f->unlinked = false; f->length = 0; f->next = mfiles; f->prev = NULL; if (mfiles) mfiles->prev = f; mfiles = f; f->chunkhead = NULL; f->chunktail = NULL; } } if (!f) return NULL; r = malloc(sizeof(*r)); if (!r) return NULL; r->file = f; r->offset = 0; r->cchunk = NULL; f->refs++; r->funcs.ReadBytes = VFSMEM_ReadBytes; r->funcs.WriteBytes = VFSMEM_WriteBytes; r->funcs.Seek = VFSMEM_Seek; r->funcs.Tell = VFSMEM_Tell; r->funcs.GetLen = VFSMEM_GetSize; r->funcs.Close = VFSMEM_Close; r->funcs.Flush = VFSMEM_Flush; return &r->funcs; } static vfsfile_t *FSPPAPI_OpenVFS(void *handle, flocation_t *loc, const char *mode) { char diskname[MAX_OSPATH]; //path is already cleaned, as anything that gets a valid loc needs cleaning up first. snprintf(diskname, sizeof(diskname), "%s/%s", (char*)handle, loc->rawname); return VFSPPAPI_Open(diskname, mode); } static void FSPPAPI_PrintPath(void *handle) { Con_Printf("%s\n", (char*)handle); } static void FSPPAPI_ClosePath(void *handle) { Z_Free(handle); } static int FSPPAPI_RebuildFSHash(const char *filename, int filesize, void *data) { if (filename[strlen(filename)-1] == '/') { //this is actually a directory char childpath[256]; Q_snprintfz(childpath, sizeof(childpath), "%s*", filename); Sys_EnumerateFiles((char*)data, childpath, FSPPAPI_RebuildFSHash, data); return true; } if (!Hash_GetInsensative(&filesystemhash, filename)) { bucket_t *bucket = (bucket_t*)BZ_Malloc(sizeof(bucket_t) + strlen(filename)+1); strcpy((char *)(bucket+1), filename); //#ifdef _WIN32 // Q_strlwr((char *)(bucket+1)); //#endif Hash_AddInsensative(&filesystemhash, (char *)(bucket+1), data, bucket); fs_hash_files++; } else fs_hash_dups++; return true; } static void FSPPAPI_BuildHash(void *handle) { Sys_EnumerateFiles(handle, "*", FSPPAPI_RebuildFSHash, handle); } /* void debugfs(void) { static qboolean firstrun = true; int len; FILE *f; vfsfile_t *v; char *buf; if (!firstrun) return; firstrun = false; f = fopen("C:/Games/Quake/fte_trunk/pub/id1/pak0.pak", "rb"); fseek(f, 0, SEEK_END); len = ftell(f); fseek(f, 0, SEEK_SET); buf = malloc(len); fread(buf, 1, len, f); fclose(f); v = VFSPPAPI_Open("C:\\Games\\Quake/id1/pak0.pak", "wb"); VFS_WRITE(v, buf, len); free(buf); VFS_CLOSE(v); } */ static qboolean FSPPAPI_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult) { int len; char netpath[MAX_OSPATH]; if (hashedresult && (void *)hashedresult != handle) return false; /* if (!static_registered) { // if not a registered version, don't ever go beyond base if ( strchr (filename, '/') || strchr (filename,'\\')) continue; } */ // check a file in the directory tree snprintf (netpath, sizeof(netpath)-1, "%s/%s",(char*)handle, filename); { vfsfile_t *f = VFSPPAPI_Open(netpath, "rb"); if (!f) return false; len = VFS_GETLEN(f); VFS_CLOSE(f); } if (loc) { loc->len = len; loc->offset = 0; loc->index = 0; Q_strncpyz(loc->rawname, filename, sizeof(loc->rawname)); } return true; } static void FSPPAPI_ReadFile(void *handle, flocation_t *loc, char *buffer) { vfsfile_t *f; size_t result; f = VFSPPAPI_Open(loc->rawname, "rb"); if (!f) //err... return; VFS_SEEK(f, loc->offset); result = VFS_READ(f, buffer, loc->len); if (result != loc->len) Con_Printf("FSPPAPI_ReadFile() fread: Filename: %s, expected %i, result was %u\n",loc->rawname,loc->len,(unsigned int)result); VFS_CLOSE(f); } static int FSPPAPI_EnumerateFiles (void *handle, const char *match, int (*func)(const char *, int, void *), void *parm) { return Sys_EnumerateFiles(handle, match, func, parm); } searchpathfuncs_t osfilefuncs = { FSPPAPI_PrintPath, FSPPAPI_ClosePath, FSPPAPI_BuildHash, FSPPAPI_FLocate, FSPPAPI_ReadFile, FSPPAPI_EnumerateFiles, NULL, NULL, FSPPAPI_OpenVFS }; #else #define FSPPAPI_OpenTemp FS_OpenTemp #define VFSPPAPI_Open VFSOS_Open extern PPB_FileIO *ppb_fileio; extern PPB_FileRef *ppb_fileref; extern PPB_FileSystem *ppb_filesystem; extern PPB_Core *ppb_core; extern PP_Instance pp_instance; static PP_Resource mainfilesystem; struct PP_CompletionCallback nullccb; void FSPPAPI_Init(void) { mainfilesystem = ppb_filesystem->Create(pp_instance, PP_FILESYSTEMTYPE_LOCALPERSISTENT); ppb_filesystem->Open(mainfilesystem, 100*1024*1024, nullccb); } typedef struct { vfsfile_t funcs; PP_Resource handle; int64_t offset; } vfsppapifile_t; static int VFSPPAPI_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) { int res; vfsppapifile_t *intfile = (vfsppapifile_t*)file; res = ppb_fileio->Read(intfile->handle, intfile->offset, buffer, bytestoread, nullccb); if (res > 0) intfile->offset += res; return res; } static int VFSPPAPI_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestoread) { int res; vfsppapifile_t *intfile = (vfsppapifile_t*)file; res = ppb_fileio->Write(intfile->handle, intfile->offset, buffer, bytestoread, nullccb); if (res > 0) intfile->offset += res; return res; } static qboolean VFSPPAPI_Seek (struct vfsfile_s *file, unsigned long pos) { vfsppapifile_t *intfile = (vfsppapifile_t*)file; intfile->offset = pos; return true; } static unsigned long VFSPPAPI_Tell (struct vfsfile_s *file) { vfsppapifile_t *intfile = (vfsppapifile_t*)file; return intfile->offset; } static void VFSPPAPI_Flush(struct vfsfile_s *file) { vfsppapifile_t *intfile = (vfsppapifile_t*)file; ppb_fileio->Flush(intfile->handle, nullccb); } static unsigned long VFSPPAPI_GetSize (struct vfsfile_s *file) { vfsppapifile_t *intfile = (vfsppapifile_t*)file; struct PP_FileInfo fileinfo; fileinfo.size = 0; ppb_fileio->Query(intfile->handle, &fileinfo, nullccb); return fileinfo.size; } static void VFSPPAPI_Close(vfsfile_t *file) { vfsppapifile_t *intfile = (vfsppapifile_t*)file; ppb_fileio->Close(intfile->handle); ppb_core->ReleaseResource(intfile->handle); Z_Free(file); } #ifdef _WIN32 static void VFSPPAPI_CloseTemp(vfsfile_t *file) { vfsppapifile_t *intfile = (vfsppapifile_t*)file; char *fname = (char*)(intfile+1); ppb_fileio->Close(intfile->handle); ppb_core->ReleaseResource(intfile->handle); /*FIXME: add the remove somewhere*/ // _unlink(fname); Z_Free(file); } #endif vfsfile_t *FSPPAPI_OpenTemp(void) { return NULL; #if 0 FILE *f; vfsppapifile_t *file; f = tmpfile(); if (!f) return NULL; file = Z_Malloc(sizeof(vfsppapifile_t)); file->funcs.Close = VFSPPAPI_Close; file->funcs.ReadBytes = VFSPPAPI_ReadBytes; file->funcs.WriteBytes = VFSPPAPI_WriteBytes; file->funcs.Seek = VFSPPAPI_Seek; file->funcs.Tell = VFSPPAPI_Tell; file->funcs.GetLen = VFSPPAPI_GetSize; file->funcs.Flush = VFSPPAPI_Flush; file->handle = f; return (vfsfile_t*)file; #endif } vfsfile_t *VFSPPAPI_Open(const char *osname, const char *mode) { int e; PP_Resource f; PP_Resource fsf; vfsppapifile_t *file; qboolean read = !!strchr(mode, 'r'); qboolean write = !!strchr(mode, 'w'); qboolean append = !!strchr(mode, 'a'); int newmode = 0; if (read) newmode |= PP_FILEOPENFLAG_READ; if (write) newmode |= PP_FILEOPENFLAG_WRITE|PP_FILEOPENFLAG_TRUNCATE|PP_FILEOPENFLAG_CREATE; if (append) newmode |= PP_FILEOPENFLAG_WRITE|PP_FILEOPENFLAG_CREATE; /*should we support w+ or r+ */ fsf = ppb_fileref->Create(mainfilesystem, osname); f = ppb_fileio->Create(pp_instance); e = ppb_fileio->Open(f, fsf, newmode, nullccb); ppb_core->ReleaseResource(fsf); if (e != PP_OK) { Con_Printf("unable to open %s. error %i\n", osname, e); return NULL; } file = Z_Malloc(sizeof(vfsppapifile_t)); file->funcs.ReadBytes = strchr(mode, 'r')?VFSPPAPI_ReadBytes:NULL; file->funcs.WriteBytes = (strchr(mode, 'w')||strchr(mode, 'a'))?VFSPPAPI_WriteBytes:NULL; file->funcs.Seek = VFSPPAPI_Seek; file->funcs.Tell = VFSPPAPI_Tell; file->funcs.GetLen = VFSPPAPI_GetSize; file->funcs.Close = VFSPPAPI_Close; file->funcs.Flush = VFSPPAPI_Flush; file->handle = f; if (append) file->offset = VFSPPAPI_GetSize((vfsfile_t*)file); else file->offset = 0; return (vfsfile_t*)file; } static vfsfile_t *FSPPAPI_OpenVFS(void *handle, flocation_t *loc, const char *mode) { char diskname[MAX_OSPATH]; //path is already cleaned, as anything that gets a valid loc needs cleaning up first. snprintf(diskname, sizeof(diskname), "%s/%s", (char*)handle, loc->rawname); return VFSPPAPI_Open(diskname, mode); } static void FSPPAPI_PrintPath(void *handle) { Con_Printf("%s\n", (char*)handle); } static void FSPPAPI_ClosePath(void *handle) { Z_Free(handle); } static int FSPPAPI_RebuildFSHash(const char *filename, int filesize, void *data) { if (filename[strlen(filename)-1] == '/') { //this is actually a directory char childpath[256]; Q_snprintfz(childpath, sizeof(childpath), "%s*", filename); Sys_EnumerateFiles((char*)data, childpath, FSPPAPI_RebuildFSHash, data); return true; } if (!Hash_GetInsensative(&filesystemhash, filename)) { bucket_t *bucket = (bucket_t*)BZ_Malloc(sizeof(bucket_t) + strlen(filename)+1); strcpy((char *)(bucket+1), filename); //#ifdef _WIN32 // Q_strlwr((char *)(bucket+1)); //#endif Hash_AddInsensative(&filesystemhash, (char *)(bucket+1), data, bucket); fs_hash_files++; } else fs_hash_dups++; return true; } static void FSPPAPI_BuildHash(void *handle) { Sys_EnumerateFiles(handle, "*", FSPPAPI_RebuildFSHash, handle); } static qboolean FSPPAPI_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult) { int len; char netpath[MAX_OSPATH]; Con_Printf("Locate %s\n", filename); if (hashedresult && (void *)hashedresult != handle) return false; /* if (!static_registered) { // if not a registered version, don't ever go beyond base if ( strchr (filename, '/') || strchr (filename,'\\')) continue; } */ // check a file in the directory tree snprintf (netpath, sizeof(netpath)-1, "%s/%s",(char*)handle, filename); { vfsfile_t *f = VFSPPAPI_Open(netpath, "rb"); if (!f) return false; len = VFS_GETLEN(f); VFS_CLOSE(f); } if (loc) { loc->len = len; loc->offset = 0; loc->index = 0; Q_strncpyz(loc->rawname, filename, sizeof(loc->rawname)); } return true; } static void FSPPAPI_ReadFile(void *handle, flocation_t *loc, char *buffer) { vfsfile_t *f; size_t result; f = VFSPPAPI_Open(loc->rawname, "rb"); if (!f) //err... return; VFS_SEEK(f, loc->offset); result = VFS_READ(f, buffer, loc->len); if (result != loc->len) Con_Printf("FSPPAPI_ReadFile() fread: Filename: %s, expected %i, result was %u\n",loc->rawname,loc->len,(unsigned int)result); VFS_CLOSE(f); } static int FSPPAPI_EnumerateFiles (void *handle, const char *match, int (*func)(const char *, int, void *), void *parm) { return Sys_EnumerateFiles(handle, match, func, parm); } searchpathfuncs_t osfilefuncs = { FSPPAPI_PrintPath, FSPPAPI_ClosePath, FSPPAPI_BuildHash, FSPPAPI_FLocate, FSPPAPI_ReadFile, FSPPAPI_EnumerateFiles, NULL, NULL, FSPPAPI_OpenVFS }; #endif