/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include #include #include "linereader.h" class CStorage : public IStorage { public: enum { MAX_PATHS = 16, MAX_PATH_LENGTH = 512 }; char m_aaStoragePaths[MAX_PATHS][MAX_PATH_LENGTH]; int m_NumPaths; char m_aDatadir[MAX_PATH_LENGTH]; char m_aUserdir[MAX_PATH_LENGTH]; char m_aCurrentdir[MAX_PATH_LENGTH]; char m_aBinarydir[MAX_PATH_LENGTH]; CStorage() { mem_zero(m_aaStoragePaths, sizeof(m_aaStoragePaths)); m_NumPaths = 0; m_aDatadir[0] = 0; m_aUserdir[0] = 0; } int Init(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments) { // get userdir fs_storage_path(pApplicationName, m_aUserdir, sizeof(m_aUserdir)); // get datadir FindDatadir(ppArguments[0]); // get currentdir if(!fs_getcwd(m_aCurrentdir, sizeof(m_aCurrentdir))) m_aCurrentdir[0] = 0; // load paths from storage.cfg LoadPaths(ppArguments[0]); if(!m_NumPaths) { dbg_msg("storage", "using standard paths"); AddDefaultPaths(); } // add save directories if(StorageType != STORAGETYPE_BASIC && m_NumPaths && (!m_aaStoragePaths[TYPE_SAVE][0] || !fs_makedir(m_aaStoragePaths[TYPE_SAVE]))) { char aPath[MAX_PATH_LENGTH]; if(StorageType == STORAGETYPE_CLIENT) { fs_makedir(GetPath(TYPE_SAVE, "screenshots", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "screenshots/auto", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "screenshots/auto/stats", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "maps", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "downloadedmaps", aPath, sizeof(aPath))); } fs_makedir(GetPath(TYPE_SAVE, "dumps", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "demos", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "demos/auto", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "demos/auto/race", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "editor", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "ghosts", aPath, sizeof(aPath))); fs_makedir(GetPath(TYPE_SAVE, "teehistorian", aPath, sizeof(aPath))); } return m_NumPaths ? 0 : 1; } void LoadPaths(const char *pArgv0) { // check current directory IOHANDLE File = io_open("storage.cfg", IOFLAG_READ); if(!File) { // check usable path in argv[0] unsigned int Pos = ~0U; for(unsigned i = 0; pArgv0[i]; i++) if(pArgv0[i] == '/' || pArgv0[i] == '\\') Pos = i; if(Pos < MAX_PATH_LENGTH) { char aBuffer[MAX_PATH_LENGTH]; str_copy(aBuffer, pArgv0, Pos+1); str_append(aBuffer, "/storage.cfg", sizeof(aBuffer)); File = io_open(aBuffer, IOFLAG_READ); } if(Pos >= MAX_PATH_LENGTH || !File) { dbg_msg("storage", "couldn't open storage.cfg"); return; } } char *pLine; CLineReader LineReader; LineReader.Init(File); while((pLine = LineReader.Get())) { const char *pLineWithoutPrefix = str_startswith(pLine, "add_path "); if(pLineWithoutPrefix) { AddPath(pLineWithoutPrefix); } } io_close(File); if(!m_NumPaths) dbg_msg("storage", "no paths found in storage.cfg"); } void AddDefaultPaths() { AddPath("$USERDIR"); AddPath("$DATADIR"); AddPath("$CURRENTDIR"); } void AddPath(const char *pPath) { if(m_NumPaths >= MAX_PATHS || !pPath[0]) return; if(!str_comp(pPath, "$USERDIR")) { if(m_aUserdir[0]) { str_copy(m_aaStoragePaths[m_NumPaths++], m_aUserdir, MAX_PATH_LENGTH); dbg_msg("storage", "added path '$USERDIR' ('%s')", m_aUserdir); } } else if(!str_comp(pPath, "$DATADIR")) { if(m_aDatadir[0]) { str_copy(m_aaStoragePaths[m_NumPaths++], m_aDatadir, MAX_PATH_LENGTH); dbg_msg("storage", "added path '$DATADIR' ('%s')", m_aDatadir); } } else if(!str_comp(pPath, "$CURRENTDIR")) { m_aaStoragePaths[m_NumPaths++][0] = 0; dbg_msg("storage", "added path '$CURRENTDIR' ('%s')", m_aCurrentdir); } else { if(fs_is_dir(pPath)) { str_copy(m_aaStoragePaths[m_NumPaths++], pPath, MAX_PATH_LENGTH); dbg_msg("storage", "added path '%s'", pPath); } } } void FindDatadir(const char *pArgv0) { // 1) use data-dir in PWD if present if(fs_is_dir("data/mapres")) { str_copy(m_aDatadir, "data", sizeof(m_aDatadir)); str_copy(m_aBinarydir, "", sizeof(m_aBinarydir)); return; } #if defined(DATA_DIR) // 2) use compiled-in data-dir if present if(fs_is_dir(DATA_DIR "/mapres")) { str_copy(m_aDatadir, DATA_DIR, sizeof(m_aDatadir)); #if defined(BINARY_DIR) str_copy(m_aBinarydir, BINARY_DIR, sizeof(m_aBinarydir)); #else str_copy(m_aBinarydir, DATA_DIR "/..", sizeof(m_aBinarydir)); #endif return; } #endif // 3) check for usable path in argv[0] { unsigned int Pos = ~0U; for(unsigned i = 0; pArgv0[i]; i++) if(pArgv0[i] == '/' || pArgv0[i] == '\\') Pos = i; if(Pos < MAX_PATH_LENGTH) { char aBuf[MAX_PATH_LENGTH]; str_copy(m_aBinarydir, pArgv0, Pos+1); str_format(aBuf, sizeof(aBuf), "%s/data/mapres", m_aBinarydir); if(fs_is_dir(aBuf)) { str_format(m_aDatadir, sizeof(m_aDatadir), "%s/data", m_aBinarydir); return; } else m_aBinarydir[0] = 0; } } #if defined(CONF_FAMILY_UNIX) // 4) check for all default locations { const char *aDirs[] = { "/usr/share/ddnet", "/usr/share/games/ddnet", "/usr/local/share/ddnet", "/usr/local/share/games/ddnet", "/usr/pkg/share/ddnet", "/usr/pkg/share/games/ddnet", "/opt/ddnet" }; const int DirsCount = sizeof(aDirs) / sizeof(aDirs[0]); int i; for (i = 0; i < DirsCount; i++) { char aBuf[128]; str_format(aBuf, sizeof(aBuf), "%s/data/mapres", aDirs[i]); if(fs_is_dir(aBuf)) { str_copy(m_aBinarydir, aDirs[i], sizeof(m_aDatadir)); str_format(m_aDatadir, sizeof(m_aDatadir), "%s/data", aDirs[i]); return; } } } #endif // no data-dir found dbg_msg("storage", "warning no data directory found"); } virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_INFO_CALLBACK pfnCallback, void *pUser) { char aBuffer[MAX_PATH_LENGTH]; if(Type == TYPE_ALL) { // list all available directories for(int i = 0; i < m_NumPaths; ++i) fs_listdir_info(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); } else if(Type >= 0 && Type < m_NumPaths) { // list wanted directory fs_listdir_info(GetPath(Type, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, Type, pUser); } } virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) { char aBuffer[MAX_PATH_LENGTH]; if(Type == TYPE_ALL) { // list all available directories for(int i = 0; i < m_NumPaths; ++i) fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); } else if(Type >= 0 && Type < m_NumPaths) { // list wanted directory fs_listdir(GetPath(Type, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, Type, pUser); } } virtual const char *GetPath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize) { str_format(pBuffer, BufferSize, "%s%s%s", m_aaStoragePaths[Type], !m_aaStoragePaths[Type][0] ? "" : "/", pDir); return pBuffer; } virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = 0, int BufferSize = 0) { char aBuffer[MAX_PATH_LENGTH]; if(!pBuffer) { pBuffer = aBuffer; BufferSize = sizeof(aBuffer); } if(Type == TYPE_ABSOLUTE) { return io_open(pFilename, Flags); } if(str_startswith(pFilename, "mapres/../skins/")) { pFilename = pFilename + 10; // just start from skins/ } if(pFilename[0] == '/' || pFilename[0] == '\\' || str_find(pFilename, "../") != NULL || str_find(pFilename, "..\\") != NULL #ifdef CONF_FAMILY_WINDOWS || (pFilename[0] && pFilename[1] == ':') #endif ) { // don't escape base directory } else if(Flags&IOFLAG_WRITE) { return io_open(GetPath(TYPE_SAVE, pFilename, pBuffer, BufferSize), Flags); } else { IOHANDLE Handle = 0; if(Type <= TYPE_ALL) { // check all available directories for(int i = 0; i < m_NumPaths; ++i) { Handle = io_open(GetPath(i, pFilename, pBuffer, BufferSize), Flags); if(Handle) return Handle; } } else if(Type >= 0 && Type < m_NumPaths) { // check wanted directory Handle = io_open(GetPath(Type, pFilename, pBuffer, BufferSize), Flags); if(Handle) return Handle; } } pBuffer[0] = 0; return 0; } struct CFindCBData { CStorage *pStorage; const char *pFilename; const char *pPath; char *pBuffer; int BufferSize; }; static int FindFileCallback(const char *pName, int IsDir, int Type, void *pUser) { CFindCBData Data = *static_cast(pUser); if(IsDir) { if(pName[0] == '.') return 0; // search within the folder char aBuf[MAX_PATH_LENGTH]; char aPath[MAX_PATH_LENGTH]; str_format(aPath, sizeof(aPath), "%s/%s", Data.pPath, pName); Data.pPath = aPath; fs_listdir(Data.pStorage->GetPath(Type, aPath, aBuf, sizeof(aBuf)), FindFileCallback, Type, &Data); if(Data.pBuffer[0]) return 1; } else if(!str_comp(pName, Data.pFilename)) { // found the file = end str_format(Data.pBuffer, Data.BufferSize, "%s/%s", Data.pPath, Data.pFilename); return 1; } return 0; } virtual bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) { if(BufferSize < 1) return false; pBuffer[0] = 0; char aBuf[MAX_PATH_LENGTH]; CFindCBData Data; Data.pStorage = this; Data.pFilename = pFilename; Data.pPath = pPath; Data.pBuffer = pBuffer; Data.BufferSize = BufferSize; if(Type == TYPE_ALL) { // search within all available directories for(int i = 0; i < m_NumPaths; ++i) { fs_listdir(GetPath(i, pPath, aBuf, sizeof(aBuf)), FindFileCallback, i, &Data); if(pBuffer[0]) return true; } } else if(Type >= 0 && Type < m_NumPaths) { // search within wanted directory fs_listdir(GetPath(Type, pPath, aBuf, sizeof(aBuf)), FindFileCallback, Type, &Data); } return pBuffer[0] != 0; } virtual bool RemoveFile(const char *pFilename, int Type) { if(Type < 0 || Type >= m_NumPaths) return false; char aBuffer[MAX_PATH_LENGTH]; return !fs_remove(GetPath(Type, pFilename, aBuffer, sizeof(aBuffer))); } virtual bool RemoveBinaryFile(const char *pFilename) { char aBuffer[MAX_PATH_LENGTH]; return !fs_remove(GetBinaryPath(pFilename, aBuffer, sizeof(aBuffer))); } virtual bool RenameFile(const char *pOldFilename, const char *pNewFilename, int Type) { if(Type < 0 || Type >= m_NumPaths) return false; char aOldBuffer[MAX_PATH_LENGTH]; char aNewBuffer[MAX_PATH_LENGTH]; return !fs_rename(GetPath(Type, pOldFilename, aOldBuffer, sizeof(aOldBuffer)), GetPath(Type, pNewFilename, aNewBuffer, sizeof (aNewBuffer))); } virtual bool RenameBinaryFile(const char *pOldFilename, const char *pNewFilename) { char aOldBuffer[MAX_PATH_LENGTH]; char aNewBuffer[MAX_PATH_LENGTH]; GetBinaryPath(pOldFilename, aOldBuffer, sizeof(aOldBuffer)); GetBinaryPath(pNewFilename, aNewBuffer, sizeof(aNewBuffer)); if(fs_makedir_rec_for(aNewBuffer) < 0) dbg_msg("storage", "cannot create folder for: %s", aNewBuffer); return !fs_rename(aOldBuffer, aNewBuffer); } virtual bool CreateFolder(const char *pFoldername, int Type) { if(Type < 0 || Type >= m_NumPaths) return false; char aBuffer[MAX_PATH_LENGTH]; return !fs_makedir(GetPath(Type, pFoldername, aBuffer, sizeof(aBuffer))); } virtual void GetCompletePath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize) { if(Type < 0 || Type >= m_NumPaths) { if(BufferSize > 0) pBuffer[0] = 0; return; } GetPath(Type, pDir, pBuffer, BufferSize); } virtual const char* GetBinaryPath(const char *pDir, char *pBuffer, unsigned BufferSize) { str_format(pBuffer, BufferSize, "%s%s%s", m_aBinarydir, !m_aBinarydir[0] ? "" : "/", pDir); return pBuffer; } static IStorage *Create(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments) { CStorage *p = new CStorage(); if(p && p->Init(pApplicationName, StorageType, NumArgs, ppArguments)) { dbg_msg("storage", "initialisation failed"); delete p; p = 0; } return p; } }; void IStorage::StripPathAndExtension(const char *pFilename, char *pBuffer, int BufferSize) { const char *pFilenameEnd = pFilename + str_length(pFilename); const char *pExtractedName = pFilename; const char *pEnd = pFilenameEnd; for(const char *pIter = pFilename; *pIter; pIter++) { if(*pIter == '/' || *pIter == '\\') { pExtractedName = pIter + 1; pEnd = pFilenameEnd; } else if(*pIter == '.') { pEnd = pIter; } } int Length = min(BufferSize, (int)(pEnd - pExtractedName + 1)); str_copy(pBuffer, pExtractedName, Length); } IStorage *CreateStorage(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments) { return CStorage::Create(pApplicationName, StorageType, NumArgs, ppArguments); } IStorage *CreateLocalStorage() { CStorage *pStorage = new CStorage(); if(pStorage) { if(!fs_getcwd(pStorage->m_aCurrentdir, sizeof(pStorage->m_aCurrentdir))) { delete pStorage; return NULL; } pStorage->AddPath("$CURRENTDIR"); } return pStorage; }