///////////////////////////////////////// // // OpenLieroX // // Auxiliary Software class library // // based on the work of JasonB // enhanced by Dark Charlie and Albert Zeyer // // code under LGPL // ///////////////////////////////////////// // File finding routines // Created 30/9/01 // By Jason Boettcher #ifdef _MSC_VER #pragma warning(disable: 4786) // WARNING: identifier XXX was truncated to 255 characters in the debug info #pragma warning(disable: 4503) // WARNING: decorated name length exceeded, name was truncated #endif #include #include "FindFile.h" #include "StringUtils.h" #include "Debug.h" #include "ConfigHandler.h" #include "sdl/CSettings.h" #ifdef WIN32 # ifndef _WIN32_IE // Some functions that are required are unavailable if this is not defined: // TODO: which functions? # define _WIN32_IE 0x0400 // Because of Dev-cpp # endif # include #else // WIN32 // not defined for older GCC versions; we check only for >=4.3 anyway # ifndef __GNUC_PREREQ # define __GNUC_PREREQ(maj, min) (0) # endif // include hash_set support # if !defined(STLPORT) # if defined(__GNUC__) && __GNUC_PREREQ(4,3) # include # define hash_set std::tr1::unordered_set # else # include using __gnu_cxx::hash_set; # endif # else // STLPORT # include using std::hash_set; # endif // for getpwduid # include // for realpath # include # include #endif void InitSearchPaths() { // have to set to find the config at some of the default places InitBaseSearchPaths(); int i = 1; while(true) { std::string value; if(!ReadString(CONFIGFILENAME, "FileHandling", "SearchPath" + itoa(i), value, "")) break; AddToFileList(&tSearchPaths, value); i++; } // add the basesearchpaths to the searchpathlist as they should be saved in the end for(searchpathlist::const_iterator p1 = basesearchpaths.begin(); p1 != basesearchpaths.end(); i++,p1++) { AddToFileList(&tSearchPaths, *p1); } // print the searchpaths, this may be very usefull for the user notes << "I have now the following searchpaths (in this order):\n"; for(searchpathlist::const_iterator p2 = tSearchPaths.begin(); p2 != tSearchPaths.end(); p2++) { std::string path = *p2; ReplaceFileVariables(path); notes << " " << path << "\n"; } notes << " And that's all." << endl; } searchpathlist tSearchPaths; bool IsFileAvailable(const std::string& f, bool absolute) { std::string abs_f; if(absolute) { abs_f = f; } else if((abs_f = GetFullFileName(f)) == "") return false; // remove trailing slashes // don't remove them on WIN, if it is a drive-letter while(abs_f.size() > 0 && (abs_f[abs_f.size()-1] == '\\' || abs_f[abs_f.size()-1] == '/')) { #ifdef WIN32 if(abs_f.size() > 2 && abs_f[abs_f.size()-2] == ':') break; #endif abs_f.erase(abs_f.size()-1); } abs_f = Utf8ToSystemNative(abs_f); // HINT: this should also work on WIN32, as we have _stat here struct stat s; if(stat(abs_f.c_str(), &s) != 0 || !S_ISREG(s.st_mode)) { // it's not stat-able or not a reg file return false; } // it's stat-able and a file return true; } ////////////////////// // Replaces backward slashes with forward slashes (windows only) // Used when comparing two paths static void ReplaceSlashes(std::string& path) { #ifdef WIN32 for (std::string::iterator it = path.begin(); it != path.end(); it++) if (*it == '\\') *it = '/'; #endif } bool EqualPaths(const std::string& path1, const std::string& path2) { std::string p1 = path1; std::string p2 = path2; ReplaceSlashes(p1); ReplaceSlashes(p2); if (*p1.rbegin() != '/') p1 += '/'; if (*p2.rbegin() != '/') p2 += '/'; return stringcaseequal(p1, p2); } /* Drives */ //////////////////// // drive_list GetDrives() { static drive_list list; list.clear(); #ifdef WIN32 static char drives[34]; int len = GetLogicalDriveStrings(sizeof(drives),drives); // Get the list of drives drive_t tmp; if (len) { for (register int i=0; i 0 && (abs_f[abs_f.size()-1] == '\\' || abs_f[abs_f.size()-1] == '/')) { #ifdef WIN32 if(abs_f.size() > 2 && abs_f[abs_f.size()-2] == ':') break; #endif abs_f.erase(abs_f.size()-1); } // HINT: this should also work on WIN32, as we have _stat here struct stat s; #ifdef WIN32 // uses UTF16 return (wstat(Utf8ToUtf16(abs_f).c_str(), &s) == 0); // ...==0, if successfull #else // other systems return (stat(abs_f.c_str(), &s) == 0); // ...==0, if successfull #endif } // used by unix-GetExactFileName // HINT: it only reads the first char of the seperators // it returns the start of the subdir (the pos _after_ the sep.) // HINT: it returns position in bytes, not in characters size_t GetNextName(const std::string& fullname, const char** seperators, std::string& nextname) { std::string::const_iterator pos; size_t p = 0; unsigned short i; for(pos = fullname.begin(); pos != fullname.end(); pos++, p++) { for(i = 0; seperators[i] != NULL; i++) if(*pos == seperators[i][0]) { nextname = fullname.substr(0, p); return p + 1; } } nextname = fullname; return 0; } // get ending filename of a path size_t GetLastName(const std::string& fullname, const char** seperators) { std::string::const_reverse_iterator pos; size_t p = fullname.size()-1; unsigned short i; for(pos = fullname.rbegin(); pos != fullname.rend(); pos++, p--) { for(i = 0; seperators[i] != NULL; i++) if(*pos == seperators[i][0]) { return p; } } // indicates that there is no more sep return (size_t)(-1); } struct strcasecomparer { bool operator()(const std::string& str1, const std::string& str2) const { return stringcaseequal(str1, str2); } }; typedef hash_set exactfilenamecache_t; struct ExactFilenameCache { exactfilenamecache_t cache; Mutex mutex; } exactfilenamecache; bool is_searchname_in_exactfilenamecache( const std::string& searchname, std::string& exactname ) { Mutex::ScopedLock lock(exactfilenamecache.mutex); exactfilenamecache_t::iterator it = exactfilenamecache.cache.find(searchname); if(it != exactfilenamecache.cache.end()) { exactname = *it; return true; } else return false; } void add_searchname_to_exactfilenamecache(const std::string& exactname) { Mutex::ScopedLock lock(exactfilenamecache.mutex); exactfilenamecache.cache.insert(exactname); } // used by unix-GetExactFileName // does a case insensitive search for searchname in dir // sets filename to the first search result // returns true, if any file found bool CaseInsFindFile(const std::string& dir, const std::string& searchname, std::string& filename) { if(searchname == "") { filename = ""; return true; } // Check first if searchname perhaps exists with exactly this name. // This check is also needed in the case if we cannot read dir (-r) but we can access files (+x) in it. if(IsPathStatable((dir == "") ? searchname : (dir + "/" + searchname))) { filename = searchname; return true; } DIR* dirhandle = opendir((dir == "") ? "." : dir.c_str()); if(dirhandle == NULL) return false; dirent* direntry; while((direntry = readdir(dirhandle))) { if(strcasecmp(direntry->d_name, searchname.c_str()) == 0) { filename = direntry->d_name; closedir(dirhandle); #ifdef DEBUG // HINT: activate this warning temporarly when you want to fix some filenames //if(filename != searchname) // cerr << "filename case mismatch: " << searchname << " <-> " << filename << endl; #endif return true; } add_searchname_to_exactfilenamecache((dir == "") ? direntry->d_name : (dir + "/" + direntry->d_name)); } closedir(dirhandle); return false; } // does case insensitive search for file bool GetExactFileName(const std::string& abs_searchname, std::string& filename) { const char* seps[] = {"\\", "/", (char*)NULL}; if(abs_searchname.size() == 0) { filename = ""; return false; } std::string sname = abs_searchname; ReplaceFileVariables(sname); std::string nextname = ""; std::string nextexactname = ""; size_t pos; bool first_iter = true; // this is used in the bottom loop // search in cache // sname[0..pos-1] is left rest, excluding the / pos = sname.size(); std::string rest; while(true) { rest = sname.substr(0,pos); if(is_searchname_in_exactfilenamecache(rest, filename)) { if(IsPathStatable(filename)) { if(pos == sname.size()) // do we got the whole filename? return true; // filename is the correct one here sname.erase(0,pos+1); first_iter = false; // prevents the following loop from not adding a "/" to filename break; } } pos = GetLastName(rest, seps); if(pos == (size_t)(-1)) { first_iter = false; if(rest == "." || rest == "..") { filename = rest; sname.erase(0,rest.size()+1); break; } filename = "."; break; } if(pos == 0) { filename = "/"; sname.erase(0,1); break; } } // search the filesystem for the name // sname contains the rest of the path // filename contains the start (including a "/" if necces.) // if first_iter is set to true, don't add leading "/" while(true) { pos = GetNextName(sname, seps, nextname); // pos>0 => found a sep (pos is right behind the sep) // pos==0 => none found if(pos > 0) sname.erase(0,pos); if(nextname == "") { // simply ignore this case // (we accept sth like /usr///share/) if(pos == 0) break; continue; } else if(!CaseInsFindFile( filename, // dir nextname, // ~name nextexactname // resulted name )) { // we doesn't get any result // just add rest to it if(!first_iter) filename += "/"; filename += nextname; if(pos > 0) filename += "/" + sname; return false; // error (not found) } if(!first_iter) filename += "/"; filename += nextexactname; if(nextexactname != "") add_searchname_to_exactfilenamecache(filename); if(pos == 0) break; first_iter = false; } // we got here after the full path was resolved successfully return true; } #endif // not WIN32 searchpathlist basesearchpaths; void InitBaseSearchPaths() { basesearchpaths.clear(); #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) AddToFileList(&basesearchpaths, "${HOME}/Library/Application Support/Commander Genius"); AddToFileList(&basesearchpaths, "."); AddToFileList(&basesearchpaths, "${BIN}/data"); AddToFileList(&basesearchpaths, SYSTEM_DATA_DIR"/commandergenius"); #elif defined(__APPLE__) AddToFileList(&basesearchpaths, "${HOME}/Library/Application Support/Commander Genius"); AddToFileList(&basesearchpaths, "."); AddToFileList(&basesearchpaths, "${BIN}/../Resources/data"); AddToFileList(&basesearchpaths, SYSTEM_DATA_DIR"/commandergenius"); #elif defined(WIN32) AddToFileList(&basesearchpaths, "${HOME}/Commander Genius"); AddToFileList(&basesearchpaths, "."); AddToFileList(&basesearchpaths, "${BIN}"); #else // all other systems (Linux, *BSD, OS/2, ...) AddToFileList(&basesearchpaths, "${HOME}/.CommanderGenius"); AddToFileList(&basesearchpaths, "."); AddToFileList(&basesearchpaths, SYSTEM_DATA_DIR"/commandergenius"); // no use of ${SYSTEM_DATA}, because it is uncommon and could cause confusion to the user #endif } void CreateRecDir(const std::string& abs_filename, bool last_is_dir) { std::string tmp; std::string::const_iterator f = abs_filename.begin(); for(tmp = ""; f != abs_filename.end(); f++) { if(*f == '\\' || *f == '/') mkdir(tmp.c_str(), 0777); tmp += *f; } if(last_is_dir) mkdir(tmp.c_str(), 0777); } std::string GetFirstSearchPath() { if(tSearchPaths.size() > 0) return tSearchPaths.front(); else if(basesearchpaths.size() > 0) return basesearchpaths.front(); else return GetHomeDir(); } size_t FileSize(const std::string& path) { FILE *fp = fopen(path.c_str(), "rb"); if (!fp) { fp = OpenGameFile(path, "rb"); if (!fp) return 0; } fseek(fp, 0, SEEK_END); size_t size = ftell(fp); fclose(fp); return size; } #define uchar unsigned char // Checks if the given path is absolute bool IsAbsolutePath(const std::string& path) { #ifdef WIN32 // The path must start with a drive letter if (path.size() < 2) return false; return (isalpha((uchar)path[0]) && path[1] == ':'); #else // Must start with a slash if (!path.size()) return false; return path[0] == '/'; #endif } static std::string specialSearchPathForTheme = ""; void initSpecialSearchPathForTheme() { /*if(tLXOptions->sTheme != "") { specialSearchPathForTheme = GetFullFileName("themes/" + tLXOptions->sTheme); } else */ specialSearchPathForTheme = ""; } const std::string* getSpecialSearchPathForTheme() { if(specialSearchPathForTheme == "") return NULL; else return &specialSearchPathForTheme; } class CheckSearchpathForFile { public: const std::string& filename; std::string* result; std::string* searchpath; CheckSearchpathForFile(const std::string& f, std::string* r, std::string* s) : filename(f), result(r), searchpath(s) {} bool operator() (const std::string& spath) { std::string tmp = spath + filename; if(GetExactFileName(tmp, *result)) { // we got here, if the file exists if(searchpath) *searchpath = spath; return false; // stop checking next searchpaths } // go to the next searchpath return true; } }; std::string GetFullFileName(const std::string& path, std::string* searchpath) { if(searchpath) *searchpath = ""; if(path == "") return GetFirstSearchPath(); // Check if we have an absolute path if(IsAbsolutePath(path)) { std::string tmp; GetExactFileName(path, tmp); return tmp; } std::string fname; CheckSearchpathForFile checker(path, &fname, searchpath); ForEachSearchpath(checker); return fname; } std::string GetWriteFullFileName(const std::string& path, bool create_nes_dirs) { std::string tmp; std::string fname; // get the dir, where we should write into if(tSearchPaths.size() == 0 && basesearchpaths.size() == 0) { errors << "we want to write somewhere, but don't know where => we are writing to your temp-dir now..." << endl; tmp = GetTempDir() + "/" + path; } else { GetExactFileName(GetFirstSearchPath(), tmp); CreateRecDir(tmp); if(!CanWriteToDir(tmp)) { errors << "we cannot write to " << tmp << " => we are writing to your temp-dir now..." << endl; tmp = GetTempDir(); } tmp += "/"; tmp += path; } GetExactFileName(tmp, fname); if(create_nes_dirs) CreateRecDir(fname, false); return tmp; } FILE* OpenAbsFile(const std::string& path, const char *mode) { std::string exactfn; if(!GetExactFileName(path, exactfn)) return NULL; return fopen(exactfn.c_str(), mode); } FILE *OpenGameFile(const std::string& path, const char *mode) { if(path.size() == 0) return NULL; std::string fullfn = GetFullFileName(path); bool write_mode = strchr(mode, 'w') != 0; bool append_mode = strchr(mode, 'a') != 0; if(write_mode || append_mode) { std::string writefullname = GetWriteFullFileName(path, true); if(append_mode && fullfn != "") { // check, if we should copy the file if(IsFileAvailable(fullfn, true)) { // we found the file // GetWriteFullFileName ensures an exact filename, // so no case insensitive check is needed here if(fullfn != writefullname) { // it is not the file, we would write to, so copy it to the wanted destination if(!FileCopy(fullfn, writefullname)) { errors << "problems while copying, so I cannot open this file in append-mode somewhere else" << endl; return NULL; } } } } //errors << "opening file for writing (mode %s): %s\n", mode, writefullname); return fopen(Utf8ToSystemNative(writefullname).c_str(), mode); } if(fullfn.size() != 0) { return fopen(Utf8ToSystemNative(fullfn).c_str(), mode); } return NULL; } bool OpenGameFileR(std::ifstream& f, const std::string& path, std::ios_base::openmode mode) { if(path.size() == 0) return false; std::string fullfn = GetFullFileName(path); if(fullfn.size() != 0) { try { f.open(Utf8ToSystemNative(fullfn).c_str(), mode); return f.is_open(); } catch(...) {} return false; } return false; } bool OpenGameFileW(std::ofstream& f, const std::string& path, std::ios_base::openmode mode) { if(path.size() == 0) return false; std::string fullfn = GetWriteFullFileName(path, true); if(fullfn.size() != 0) { try { f.open(Utf8ToSystemNative(fullfn).c_str(), mode); return f.is_open(); } catch(...) {} return false; } return false; } void AddToFileList(searchpathlist* l, const std::string& f) { if(!FileListIncludesExact(l, f)) l->push_back(f); } void removeEndingSlashes(std::string& s) { while(s.size() > 0 && (*s.rbegin() == '\\' || *s.rbegin() == '/')) s.erase(s.size() - 1); } ///////////////// // Returns true, if the list contains the path bool FileListIncludesExact(const searchpathlist* l, const std::string& f) { std::string tmp1 = f; removeEndingSlashes(tmp1); ReplaceFileVariables(tmp1); replace(tmp1,"\\","/"); // Go through the list, checking each item for(searchpathlist::const_iterator i = l->begin(); i != l->end(); i++) { std::string tmp2 = *i; removeEndingSlashes(tmp2); ReplaceFileVariables(tmp2); replace(tmp2,"\\","/"); if(stringcaseequal(tmp1, tmp2)) return true; } return false; } std::string GetHomeDir() { #ifndef WIN32 char* home = getenv("HOME"); if(home == NULL || home[0] == '\0') { passwd* userinfo = getpwuid(getuid()); if(userinfo) return userinfo->pw_dir; return ""; // both failed, very strange system... } return home; #else static std::string result = ""; if (result.size() == 0) { // Only do this once char tmp[1024]; if (!SHGetSpecialFolderPath(NULL, tmp, CSIDL_PERSONAL,FALSE)) { // TODO: get dynamicaly another possible path // the following is only a workaround! return "C:\\OpenLieroX"; } fix_markend(tmp); result = SystemNativeToUtf8(tmp); } return result; #endif } std::string GetSystemDataDir() { #ifndef WIN32 return SYSTEM_DATA_DIR; #else // windows don't have such dir, don't it? // or should we return windows/system32 (which is not exactly intended here)? return ""; #endif } std::string binary_dir; // given by argv[0], set by main() std::string GetBinaryDir() { return binary_dir; } std::string GetTempDir() { #ifndef WIN32 return "/tmp"; // year, it's so simple :) #else static char buf[1024] = ""; if(buf[0] == '\0') { // only do this once GetTempPath(sizeof(buf), buf); fix_markend(buf); } return SystemNativeToUtf8(buf); #endif } void ReplaceFileVariables(std::string& filename) { if(filename.compare(0,2,"~/")==0 || filename.compare(0,2,"~\\")==0 || filename == "~") { filename.erase(0,1); filename.insert(0,GetHomeDir()); } replace(filename, "${HOME}", GetHomeDir()); replace(filename, "${SYSTEM_DATA}", GetSystemDataDir()); replace(filename, "${BIN}", GetBinaryDir()); } // WARNING: not multithreading aware // HINT: uses absolute paths // returns true, if successfull bool FileCopy(const std::string& src, const std::string& dest) { static char tmp[2048]; notes << "FileCopy: " << src << " -> " << dest << endl; FILE* src_f = fopen(Utf8ToSystemNative(src).c_str(), "rb"); if(!src_f) { errors << "FileCopy: cannot open source" << endl; return false; } FILE* dest_f = fopen(Utf8ToSystemNative(dest).c_str(), "wb"); if(!dest_f) { fclose(src_f); errors << "FileCopy: cannot open destination" << endl; return false; } bool success = true; unsigned short count = 0; notes << "FileCopy: |" << flush; size_t len = 0; while((len = fread(tmp, 1, sizeof(tmp), src_f)) > 0) { if(count == 0) notes << "." << flush; count++; count %= 20; if(len != fwrite(tmp, 1, len, dest_f)) { errors << "FileCopy: problem while writing" << endl; success = false; break; } if(len != sizeof(tmp)) break; } notes << endl; if(success) { success = feof(src_f) != 0; if(!success) errors << "FileCopy: problem while reading" << endl; } fclose(src_f); fclose(dest_f); if(success) notes << "FileCopy: success :)" << endl; return success; } bool CanWriteToDir(const std::string& dir) { // TODO: we have to make this a lot better! std::string fname = dir + "/.some_stupid_temp_file"; FILE* fp = fopen(Utf8ToSystemNative(fname).c_str(), "w"); if(fp) { fclose(fp); remove(Utf8ToSystemNative(fname).c_str()); return true; } return false; } std::string GetAbsolutePath(const std::string& path) { #ifdef WIN32 std::string exactpath; if (!GetExactFileName(path, exactpath)) exactpath = path; char buf[2048]; int len = GetFullPathName(Utf8ToSystemNative(exactpath).c_str(), sizeof(buf), buf, NULL); fix_markend(buf); if (len) return SystemNativeToUtf8(buf); else // Failed return path; #else std::string exactpath; if(GetExactFileName(path, exactpath)) { char buf[PATH_MAX]; if(realpath(exactpath.c_str(), buf) != NULL) { fix_markend(buf); return buf; } else return exactpath; } else return path; #endif } bool PathListIncludes(const std::list& pathlist, const std::string& path) { std::string abs_path; abs_path = GetAbsolutePath(path); // Go through the list, checking each item for(std::list::const_iterator i = pathlist.begin(); i != pathlist.end(); i++) { if(EqualPaths(abs_path, GetAbsolutePath(*i))) { return true; } } return false; } /////////////////////// // Returns the file contents as a string std::string GetFileContents(const std::string& path, bool absolute) { FILE *fp = NULL; if (absolute) fp = fopen(/*Utf8ToSystemNative(path)*/path.c_str(), "rb"); else fp = OpenGameFile(path, "rb"); if (!fp) return ""; fseek(fp, 0, SEEK_END); size_t size = ftell(fp); fseek(fp, 0, SEEK_SET); if (!size) { fclose(fp); return ""; } char *buf = new char[size]; size = fread(buf, 1, size, fp); if (!size) { delete[] buf; fclose(fp); return ""; } std::string result; result.append(buf, size); delete[] buf; fclose(fp); return result; } //////////////// // Extract the directory part from a path std::string ExtractDirectory(const std::string& path) { if (path.size() == 0) return ""; size_t pos = findLastPathSep(path); if (pos == std::string::npos) return path; else return path.substr(0, pos); } std::string GetScriptInterpreterCommandForFile(const std::string& filename) { FILE* f = OpenGameFile(filename, "r"); if(f) { std::string line = ReadUntil(f); if(line.size() > 2 && line[0] == '#' && line[1] == '!') { std::string cmd = line.substr(2); TrimSpaces(cmd); fclose(f); return cmd; } fclose(f); return ""; } return ""; } /////////////////// // Merges two parts of a path into one, for example JoinPath("./test/", "/file.fil") gives "./test/file.fil" std::string JoinPaths(const std::string& path1, const std::string& path2) { if (path1.size() == 0) return path2; if (path2.size() == 0) return path1; std::string result = path1; if (*path1.rbegin() == '/' || *path1.rbegin() == '\\') { if (*path2.begin() == '/' || *path2.begin() == '\\') { result.erase(result.size() - 1); result += path2; return result; } else { result += path2; return result; } } else { if (*path2.begin() == '/' || *path2.begin() == '\\') { result += path2; return result; } else { result += '/'; result += path2; return result; } } } ////////////////////////////// // Creating SDL_RWops structure from a file pointer // We cannot use SDL's function for this under WIN32 because it doesn't allow // passing file pointers to a dll #include // These are taken from SDL_rwops.c #ifdef WIN32 static int stdio_seek(SDL_RWops *context, int offset, int whence) { if ( fseek(context->hidden.stdio.fp, offset, whence) == 0 ) { return(ftell(context->hidden.stdio.fp)); } else { SDL_Error(SDL_EFSEEK); return(-1); } } static int stdio_read(SDL_RWops *context, void *ptr, int size, int maxnum) { size_t nread; nread = fread(ptr, size, maxnum, context->hidden.stdio.fp); if ( nread == 0 && ferror(context->hidden.stdio.fp) ) { SDL_Error(SDL_EFREAD); } return (int)(nread); } static int stdio_write(SDL_RWops *context, const void *ptr, int size, int num) { size_t nwrote; nwrote = fwrite(ptr, size, num, context->hidden.stdio.fp); if ( nwrote == 0 && ferror(context->hidden.stdio.fp) ) { SDL_Error(SDL_EFWRITE); } return (int)(nwrote); } static int stdio_close(SDL_RWops *context) { if ( context ) { if ( context->hidden.stdio.autoclose ) { /* WARNING: Check the return value here! */ fclose(context->hidden.stdio.fp); } free(context); } return(0); } #endif //////////////// // Creates SDL_RWops from a file pointer SDL_RWops *RWopsFromFP(FILE *fp, bool autoclose) { #ifdef WIN32 // Taken from SDL code SDL_RWops *rwops = SDL_AllocRW(); if ( rwops != NULL ) { rwops->seek = stdio_seek; rwops->read = stdio_read; rwops->write = stdio_write; rwops->close = stdio_close; rwops->hidden.stdio.fp = fp; rwops->hidden.stdio.autoclose = (int)autoclose; } return(rwops); #else return SDL_RWFromFP(fp, (SDL_bool)autoclose); #endif }