/* Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #if defined (__linux__) || defined (__APPLE__) #include #endif #ifdef _WIN32 #include #endif #include "pakstuff.h" #include "unzip.h" #include "str.h" #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif int m_nPAKIndex; FILE* pakfile[16]; struct PACKDirectory pakdir; PACKDirPtr pakdirptr = &pakdir; UInt16 dirsize; bool pakopen = false; int f_type; DIRECTORY *paktextures = NULL; UInt32 PakColormapOffset; UInt32 PakColormapSize; DIRECTORY *dirhead = NULL; bool g_bPK3 = false; char g_strBasePaths[16][1024]; int g_numBasePaths = 0; struct PK3FileInfo { unzFile m_zFile; char *m_pName; unz_s m_zInfo; long m_lSize; ~PK3FileInfo() { delete []m_pName; } bool operator ==(const PK3FileInfo& rhs) const { return strcmp(m_pName, rhs.m_pName) == 0; } }; #define __PATHSEPERATOR '/' //#define LOG_PAKFAIL #ifdef LOG_PAKFAIL #if defined (__linux__) || defined (__APPLE__) #include #include #endif #include class LogFile { public: FILE *m_pFile; LogFile(const char* pName) { #if defined (__linux__) || defined (__APPLE__) // leo: use ~/.q3a/radiant/paklog instead of /tmp/paklog.txt char *home = NULL; home = getenv("HOME"); if ( home == NULL ) { uid_t id = getuid(); struct passwd *pwd; setpwent(); while( (pwd = getpwent()) != NULL ) if( pwd->pw_uid == id ) { home = pwd->pw_dir; break; } endpwent(); } if (home != NULL) { char path[PATH_MAX]; strcpy (path, home); if (path[strlen(path)-1] != '/') strcat (path, "/"); strcat (path, ".q3a/radiant/paklog"); m_pFile = fopen(path, "w"); } else #endif m_pFile = fopen(pName, "w"); } ~LogFile() { if (m_pFile) { fclose(m_pFile); } } void Log(const char *pFormat, ...) { if (m_pFile == NULL) return; va_list arg_ptr; va_start(arg_ptr, pFormat); fprintf(m_pFile, pFormat, arg_ptr); va_end(arg_ptr); } }; LogFile g_LogFile("/tmp/paklog.txt"); #endif template class StrPtr : public Str { protected: T* m_pPtr; StrPtr() { m_pPtr = NULL; } StrPtr(const char *pStr, T *p) : Str(pStr) { m_pPtr = p; } T* Ptr() { return m_pPtr; } T& Ref() { return *m_pPtr; } }; // PtrList // a list of ptrs // template class PtrList { protected: T *m_pPtr; PtrList *m_pNext; public: PtrList() { m_pNext = NULL; m_pPtr = NULL; } PtrList(T *ip) { m_pNext = NULL; m_pPtr = ip; } virtual ~PtrList() { delete m_pPtr; } PtrList* Next() { return m_pNext; } void Add(T *ip) { PtrList *pl = this; while (pl && pl->m_pNext) { pl = pl->Next(); } pl->m_pNext = new PtrList(ip); } void Remove() { PtrList *p = m_pNext; if (p) { while (p->m_pNext != this && p->m_pNext != NULL) { p = p->m_pNext; } if (p->m_pNext == this) { p->m_pNext = m_pNext; } } } virtual PtrList* Find(T *ip) { PtrList *p = m_pNext; while (p) { if (*p->m_pPtr == *ip) { return p; } p = p->m_pNext; } return NULL; } // remove vp from the list void Remove(T *ip) { PtrList *p = Find(ip); if (p) { p->Remove(); } } T* Ptr() { return m_pPtr; } T& Ref() { return *m_pPtr; } void RemoveAll() { PtrList *p = m_pNext; while (p) { PtrList *p2 = p; p = p->m_pNext; delete p2; } } }; typedef PtrList ZFileList; typedef PtrList StrList; typedef PtrList PK3List; StrList g_PK3TexturePaths; PK3List g_PK3Files; ZFileList g_zFiles; #define WORK_LEN 1024 #define TEXTURE_PATH "textures" #define PATH_SEPERATORS "/\\:\0" /* char* __StrDup(char* pStr) { if (pStr == NULL) pStr = ""; return strcpy(new char[strlen(pStr)+1], pStr); } char* __StrDup(const char* pStr) { if (pStr == NULL) pStr = ""; return strcpy(new char[strlen(pStr)+1], pStr); } */ #define MEM_BLOCKSIZE 4096 void* __qblockmalloc(size_t nSize) { void *b; // round up to threshold int nAllocSize = nSize % MEM_BLOCKSIZE; if ( nAllocSize > 0) { nSize += MEM_BLOCKSIZE - nAllocSize; } b = malloc(nSize + 1); memset (b, 0, nSize); return b; } void* __qmalloc (size_t nSize) { void *b; b = malloc(nSize + 1); memset (b, 0, nSize); return b; } /* ==================== Extract file parts ==================== */ void __ExtractFilePath (const char *path, char *dest) { const char *src; src = path + strlen(path) - 1; // // back up until a \ or the start // while (src != path && *(src-1) != __PATHSEPERATOR) src--; memcpy (dest, path, src-path); dest[src-path] = 0; } void __ExtractFileName (const char *path, char *dest) { const char *src; src = path + strlen(path) - 1; // // back up until a \ or the start // while (src != path && *(src-1) != '/' && *(src-1) != '\\' ) src--; while (*src) { *dest++ = *src++; } *dest = 0; } void __ExtractFileBase (const char *path, char *dest) { const char *src; src = path + strlen(path) - 1; // // back up until a \ or the start // while (src != path && *(src-1) != '/' && *(src-1) != '\\' ) src--; while (*src && *src != '.') { *dest++ = *src++; } *dest = 0; } void __ExtractFileExtension (const char *path, char *dest) { const char *src; src = path + strlen(path) - 1; // // back up until a . or the start // while (src != path && *(src-1) != '.') src--; if (src == path) { *dest = 0; // no extension return; } strcpy (dest,src); } void __ConvertDOSToUnixName( char *dst, const char *src ) { while ( *src ) { if ( *src == '\\' ) *dst = '/'; else *dst = *src; dst++; src++; } *dst = 0; } static void AddSlash(Str& str) { int nLen = str.GetLength(); if (nLen > 0) { if (str[nLen-1] != '\\' && str[nLen-1] != '/') str += '\\'; } } static void FindReplace(Str& strContents, const char* pTag, const char* pValue) { if (strcmp(pTag, pValue) == 0) return; for (int nPos = strContents.Find(pTag); nPos >= 0; nPos = strContents.Find(pTag)) { int nRightLen = strContents.GetLength() - strlen(pTag) - nPos; Str strLeft(strContents.Left(nPos)); Str strRight(strContents.Right(nRightLen)); strLeft += pValue; strLeft += strRight; strContents = strLeft; } } void ClearFileList(FILELIST **list) { FILELIST *temp; while(*list) { temp = *list; *list = (*list)->next; free(temp); } } void ClearDirList(DIRLIST **list) { DIRLIST *temp; while(*list) { temp = *list; *list = (*list)->next; free(temp); } } DIRECTORY *FindPakDir(DIRECTORY *dir, char *name) { DIRECTORY *currentPtr; for(currentPtr = dir; currentPtr; currentPtr = currentPtr->next) { if(!stricmp(name, currentPtr->name)) { return currentPtr; } } return NULL; } // LoadPK3FileList // --------------- // // This gets passed a file mask which we want to remove as // we are only interested in the directory name and any given // extension. Only handles explicit filenames or *.something // bool LoadPK3FileList(FILELIST **filelist, const char *pattern) { char cSearch[WORK_LEN]; __ConvertDOSToUnixName( cSearch, pattern ); char cPath[WORK_LEN]; char cExt[WORK_LEN]; char cFile[WORK_LEN]; char cWork[WORK_LEN]; __ExtractFilePath(pattern, cPath); __ExtractFileName(pattern, cFile); __ExtractFileExtension(pattern, cExt); const char *pCompare = (strnicmp(cFile, "*.", 2) == 0) ? cExt : cFile; PK3List *p = g_PK3Files.Next(); while (p != NULL) { // qualify the path PK3FileInfo *pKey = p->Ptr(); if (strstr(pKey->m_pName, cPath) && strstr(pKey->m_pName, pCompare)) { __ExtractFileName(pKey->m_pName, cWork); AddToFileListAlphabetized(filelist, cWork, 0, 0, false); } p = p->Next(); } return (*filelist) != NULL; } bool GetPackFileList(FILELIST **filelist, char *pattern) { char *str1, *str2; int i; DIRECTORY *dummy = paktextures; FILELIST *temp; if (!pakopen) return false; if (g_bPK3) { return LoadPK3FileList(filelist, pattern); } str1 = pattern; for(i = 0; pattern[i] != '\0'; i++) { if(pattern[i] == '\\') pattern[i] = '/'; } while(strchr(str1, '/')) { str2 = strchr(str1, '/'); *str2++ = '\0'; dummy = FindPakDir(dummy, str1); if(!dummy) return false; str1 = str2; } for(temp = dummy->files; temp; temp=temp->next) { AddToFileListAlphabetized(filelist, temp->filename, temp->offset, 0, false); } return true; } bool GetPackTextureDirs(DIRLIST **dirlist) { UInt16 i; char buf[57]; if (!pakopen) return 1; if (g_bPK3) { StrList *pl = g_PK3TexturePaths.Next(); while (pl != NULL) { AddToDirListAlphabetized(dirlist, pl->Ref(), 0); pl = pl->Next(); } return true; } for (i = 0; i < dirsize; i++) { if(!strnicmp(pakdirptr[i].name, "textures", 8)) { strncpy(buf, &(pakdirptr[i].name[9]), 46); if(strchr(buf, '\\')) *strchr(buf, '\\') = '\0'; else if(strchr(buf, '/')) *strchr(buf, '/') = '\0'; else buf[56] = '\0'; if(strchr(buf, '.')) continue; AddToDirListAlphabetized(dirlist, buf, 0); } } return true; } bool AddToDirListAlphabetized(DIRLIST **list, char *dirname, int from) { DIRLIST *currentPtr, *previousPtr, *newPtr; strlwr(dirname); for(currentPtr = *list; currentPtr; currentPtr = currentPtr->next) { if(!stricmp(dirname, currentPtr->dirname)) { return false; } } previousPtr = NULL; currentPtr = *list; if((newPtr = (DIRLIST *)__qmalloc(sizeof(DIRLIST))) == NULL) return false; strcpy(newPtr->dirname, dirname); newPtr->from = from; while(currentPtr != NULL && stricmp(dirname, currentPtr->dirname) > 0) { previousPtr = currentPtr; currentPtr = currentPtr->next; } //End while if(previousPtr == NULL) { newPtr->next = *list; *list = newPtr; } //End if else { previousPtr->next = newPtr; newPtr->next = currentPtr; } //End else return true; } bool AddToFileListAlphabetized(FILELIST **list, char *filename, UInt32 offset, UInt32 size, bool dirs) { FILELIST *currentPtr, *previousPtr, *newPtr; for(currentPtr = *list; currentPtr; currentPtr = currentPtr->next) { if(!stricmp(filename, currentPtr->filename)) { return false; } } previousPtr = NULL; currentPtr = *list; if((newPtr = (FILELIST *)__qmalloc(sizeof(FILELIST))) == NULL) return false; strcpy(newPtr->filename, filename); newPtr->offset = offset; newPtr->size = size; while(currentPtr != NULL && stricmp(filename, currentPtr->filename) > 0) { previousPtr = currentPtr; currentPtr = currentPtr->next; } //End while if(previousPtr == NULL) { newPtr->next = *list; *list = newPtr; } //End if else { previousPtr->next = newPtr; newPtr->next = currentPtr; } //End else return true; } int PakLoadAnyFile(const char *filename, void **bufferptr) { char cWork[WORK_LEN]; if (g_bPK3) { // leo: hack to be able to use pak files from multiple directories for (int i = 0; i < g_numBasePaths; i++) { PK3FileInfo *pInfo; Str strKey; // need to lookup the file without the base/texture path on it Str strBase(g_strBasePaths[i]); AddSlash(strBase); __ConvertDOSToUnixName(cWork, strBase); Str strFile(filename); __ConvertDOSToUnixName(strFile, strFile); strFile.MakeLower(); strlwr(cWork); FindReplace(strFile, cWork, ""); PK3FileInfo infoFind; infoFind.m_pName = __StrDup(strFile.GetBuffer()); PK3List *pList = g_PK3Files.Find(&infoFind); if (pList) { pInfo = pList->Ptr(); memcpy(pInfo->m_zFile, &pInfo->m_zInfo, sizeof(unz_s)); if (unzOpenCurrentFile(pInfo->m_zFile) == UNZ_OK) { void *buffer = __qblockmalloc(pInfo->m_lSize+1); int n = unzReadCurrentFile(pInfo->m_zFile , buffer, pInfo->m_lSize); *bufferptr = buffer; unzCloseCurrentFile(pInfo->m_zFile); return n; } } } #ifdef LOG_PAKFAIL sprintf(cWork, "PAK failed on %s\n", filename); g_LogFile.Log(cWork); #endif return -1; } for (int i = 0; i < dirsize; i++) { if(!stricmp(filename, pakdirptr[i].name)) { if (fseek(pakfile[m_nPAKIndex], pakdirptr[i].offset, SEEK_SET) >= 0) { void *buffer = __qmalloc (pakdirptr[i].size+1); ((char *)buffer)[pakdirptr[i].size] = 0; if (fread(buffer, 1, pakdirptr[i].size, pakfile[m_nPAKIndex]) == pakdirptr[i].size) { *bufferptr = buffer; return pakdirptr[i].size; } } } } #ifdef LOG_PAKFAIL sprintf(cWork, "PAK failed on %s\n", filename); g_LogFile.Log(cWork); #endif return -1; } DIRECTORY *AddPakDir(DIRECTORY **dir, char *name) { DIRECTORY *currentPtr, *previousPtr, *newPtr; for(currentPtr = *dir; currentPtr; currentPtr = currentPtr->next) { if(!stricmp(name, currentPtr->name)) { return currentPtr; } } previousPtr = NULL; currentPtr = *dir; if((newPtr = (DIRECTORY *)__qmalloc(sizeof(DIRECTORY))) == NULL) return NULL; strcpy(newPtr->name, name); newPtr->files = NULL; while(currentPtr != NULL && stricmp(name, currentPtr->name) > 0) { previousPtr = currentPtr; currentPtr = currentPtr->next; } if(previousPtr == NULL) { newPtr->next = *dir; *dir = newPtr; } else { previousPtr->next = newPtr; newPtr->next = currentPtr; } return newPtr; } // OpenPK3 // ------- // Opens a PK3 ( or zip ) file and creates a list of filenames // and zip info structures // bool OpenPK3(const char *filename) { char cFilename[WORK_LEN]; char cName[WORK_LEN]; char cWork[WORK_LEN]; unz_file_info zInfo; unzFile *zFile = new unzFile(unzOpen(filename)); g_zFiles.Add(zFile); if (zFile != NULL) { int nStatus = unzGoToFirstFile(*zFile); while (nStatus == UNZ_OK) { cFilename[0] = '\0'; unzGetCurrentFileInfo(*zFile, &zInfo, cFilename, WORK_LEN, NULL, 0, NULL, 0); strlwr(cFilename); __ConvertDOSToUnixName( cWork, cFilename); if (strstr(cWork, ".") != NULL) { PK3FileInfo *pInfo = new PK3FileInfo(); pInfo->m_pName = __StrDup(cWork); memcpy(&pInfo->m_zInfo, (unz_s*)*zFile, sizeof(unz_s)); pInfo->m_lSize = zInfo.uncompressed_size; pInfo->m_zFile = *zFile; g_PK3Files.Add(pInfo); } char *p = strstr(cFilename, TEXTURE_PATH); if (p != NULL) { // FIXME: path differences per os ? // catch solo directory entry if (strlen(p) > strlen(TEXTURE_PATH) + 1) { // skip textures + path seperator p += strlen(TEXTURE_PATH) + 1; int nEnd = strcspn(p, PATH_SEPERATORS); strncpy(cName, p, nEnd); cName[nEnd] = '\0'; bool bFound = false; StrList *pl = g_PK3TexturePaths.Next(); while (pl != NULL) { if (strcmpi(pl->Ref(), cName) == 0) { // already have this, continue bFound = true; break; } pl = pl->Next(); } if (!bFound) { g_PK3TexturePaths.Add(new Str(cName)); } } } nStatus = unzGoToNextFile(*zFile); } } return (zFile != NULL); } void closePK3(unzFile zf) { unzClose(zf); } void OpenPakFile(const char *filename) { if(!pakopen) paktextures = NULL; pakopen = g_bPK3 = OpenPK3(filename); } void ClearPaKDir(DIRECTORY **dir) { DIRECTORY *d1 = *dir, *d2; while(d1) { ClearFileList(&(d1->files)); d2 = d1; d1 = d1->next; free(d2); } } void CleanUpPakDirs() { ClearPaKDir(&paktextures); paktextures = NULL; dirhead = NULL; g_PK3TexturePaths.RemoveAll(); g_PK3Files.RemoveAll(); } void ClosePakFile(void) { if(pakopen) { if (g_bPK3) { ZFileList *p = g_zFiles.Next(); while (p != NULL) { unzFile uz = p->Ref(); closePK3(uz); p = p->Next(); } } else { fclose(pakfile[m_nPAKIndex]); } } pakopen = false; CleanUpPakDirs(); } void WINAPI InitPakFile(const char * pBasePath, const char *pName) { if (g_numBasePaths == 0) { m_nPAKIndex = 0; pakopen = false; paktextures = NULL; } strcpy(g_strBasePaths[g_numBasePaths], pBasePath); g_numBasePaths++; if (pName == NULL) { //++timo FIXME: use some kind of compatibility lib here! #if defined (__linux__) || defined (__APPLE__) char cWork[WORK_LEN]; struct dirent *dirlist; DIR *dir; dir = opendir (pBasePath); if (dir != NULL) { while ((dirlist = readdir (dir)) != NULL) { if (strstr (dirlist->d_name, ".pk3") == NULL) continue; sprintf(cWork, "%s/%s", pBasePath, dirlist->d_name); OpenPakFile(cWork); } closedir (dir); } #endif #ifdef _WIN32 char cWork[WORK_LEN]; Str strPath(pBasePath); AddSlash(strPath); strPath += "*.pk3"; bool bGo = true; struct _finddata_t fileinfo; int handle = _findfirst (strPath, &fileinfo); if (handle != -1) { do { sprintf(cWork, "%s/%s", pBasePath, fileinfo.name); OpenPakFile(cWork); } while (_findnext( handle, &fileinfo ) != -1); _findclose (handle); } #endif } else { OpenPakFile(pName); } }