diff --git a/source/gui/GUIutil.h b/source/gui/GUIutil.h index 7f80304ff3..c40c6ccba2 100755 --- a/source/gui/GUIutil.h +++ b/source/gui/GUIutil.h @@ -300,7 +300,7 @@ public: // Copy from memory tOutput = *(T*)mem; - delete [] mem; + free(mem); // TODO Gee: Undefined type - maybe report in log return true; diff --git a/source/lib/adts.cpp b/source/lib/adts.cpp index abece67375..ae1c5a627c 100755 --- a/source/lib/adts.cpp +++ b/source/lib/adts.cpp @@ -27,85 +27,3 @@ private: // const size_t num_pages = (max_items + items_per_page-1) / items_per_page; T* pages[(max_items + (sizeof(T) / PAGE_SIZE)-1) / (sizeof(T) / PAGE_SIZE)]; }; - - - - -// -// cache implementation -// - -// mark l as the most recently used line. -void Cache::reference(List_iterator l) -{ - lru_list.splice(lru_list.begin(), lru_list, l); - idx[l->tag] = l; -} - - -// return the line identified by tag, or 0 if not in cache. -Cache::Line* Cache::find_line(u64 tag) -{ - Map::const_iterator i = idx.find(tag); - // not found - if(i == idx.end()) - return 0; - - // index points us to list entry - List_iterator l = i->second; - reference(l); - return &*l; -} - - -// return the pointer associated with the line identified by tag, -// or 0 if not in cache. -void* Cache::get(u64 tag) -{ - Line* l = find_line(tag); - return l? l->p : 0; -} - - -// add tag to cache, and associate p with it. -// return 0 on success, or -1 if already in cache. -int Cache::add(u64 tag, void* p) -{ - if(get(tag)) - { - assert(0 && "add: tag already in cache!"); - return -1; - } - - // add directly to front of LRU list - lru_list.push_front(Line(tag, p)); - idx[tag] = lru_list.begin(); - return 0; -} - - -// find least recently used entry that isn't locked; -// change its tag, and return its associated pointer. -void* Cache::replace_lru_with(u64 new_tag) -{ - // scan in least->most used order for first non-locked entry - List_iterator l = lru_list.end(); - while(l != lru_list.begin()) - { - --l; - if(!l->locked) - goto have_entry; - } - - // all are locked and cannot be displaced. - // caller should add() enough lines so that this never happens. - assert(0 && "replace_lru_with not possible - all lines are locked"); - return 0; - -have_entry: - - idx.erase(l->tag); - l->tag = new_tag; - reference(l); - return l->p; -} diff --git a/source/lib/adts.h b/source/lib/adts.h index 508ef96772..6cf0d45867 100755 --- a/source/lib/adts.h +++ b/source/lib/adts.h @@ -1,8 +1,49 @@ +#ifndef ADTS_H__ +#define ADTS_H__ + + #include "lib.h" #include #include +#include + + + +static struct BIT_BUF +{ + ulong buf; + ulong cur; /* bit to be appended (toggled by add()) */ + ulong len; /* |buf| [bits] */ + + void reset() + { + buf = 0; + cur = 0; + len = 0; + } + + /* toggle current bit if desired, and add to buffer (new bit is LSB) */ + void add(ulong toggle) + { + cur ^= toggle; + buf <<= 1; + buf |= cur; + len++; + } + + /* extract LS n bits */ + uint extract(ulong n) + { + ulong i = buf & ((1ul << n) - 1); + buf >>= n; + + return i; + } +} +bit_buf; + template struct RingBuf @@ -33,22 +74,24 @@ template struct RingBuf class const_iterator { public: - const_iterator() : data(0), pos(0) {} - const_iterator(const T* _data, size_t _pos) : data(_data), pos(_pos) {} + const_iterator() : data(0), pos(0) + {} + const_iterator(const T* _data, size_t _pos) : data(_data), pos(_pos) + {} const T& operator[](int idx) const - { return data[(pos+idx) % n]; } + { return data[(pos+idx) % n]; } const T& operator*() const - { return data[pos]; } + { return data[pos]; } const T* operator->() const - { return &**this; } + { return &**this; } const_iterator& operator++() // pre - { pos = (pos+1) % n; return (*this); } + { pos = (pos+1) % n; return (*this); } const_iterator operator++(int) // post - { const_iterator tmp = *this; ++*this; return tmp; } + { const_iterator tmp = *this; ++*this; return tmp; } bool operator==(const const_iterator& rhs) const - { return pos == rhs.pos && data == rhs.data; } + { return pos == rhs.pos && data == rhs.data; } bool operator!=(const const_iterator& rhs) const - { return !(*this == rhs); } + { return !(*this == rhs); } protected: const T* data; size_t pos; @@ -67,80 +110,283 @@ template struct RingBuf // cache // -class Cache + +// owns a pool of resources (Entry-s), associated with a 64 bit id. +// typical use: add all available resources to the cache via grow(); +// assign() ids to the resources, and update the resource data if necessary; +// retrieve() the resource, given id. +template class Cache { public: - // return the pointer associated with the line identified by tag, - // or 0 if not in cache. - void* get(u64 tag); - - // add tag to cache, and associate p with it. - // return 0 on success, or -1 if already in cache. - int add(u64 tag, void* p); - - // find least recently used entry that isn't locked; - // change its tag, and return its associated pointer. - void* replace_lru_with(u64 new_tag); - - int get_ctx(u64 tag, uintptr_t& ctx) + // 'give' Entry to the cache. + int grow(Entry& e) { - Line* l = find_line(tag); - if(!l) - return -1; - ctx = l->ctx; + // add to front of LRU list, but not index + // (since we don't have an id yet) + lru_list.push_front(Line(0, e)); return 0; } - int set_ctx(u64 tag, uintptr_t ctx) + + // find the least-recently used line; associate id with it, + // and return its Entry. fails (returns 0) if id is already + // associated, or all lines are locked. + Entry* assign(u64 id) { - Line* l = find_line(tag); + if(find_line(id)) + { + assert(0 && "assign: id already in cache!"); + return 0; + } + + // scan in least->most used order for first non-locked entry + List_iterator l = lru_list.end(); + while(l != lru_list.begin()) + { + --l; + if(l->refs == 0) + goto have_line; + } + + // all are locked and cannot be displaced. + // caller should grow() enough lines so that this never happens. + assert(0 && "assign: all lines locked - grow() more lines"); + return 0; + + have_line: + + // update mapping (index) + idx.erase(id); + idx[id] = l; + + l->id = id; + return &l->ent; + } + + + // find line identified by id; return its entry or 0 if not in cache. + Entry* retrieve(u64 id) + { + // invalid: id 0 denotes not-yet-associated lines + if(id == 0) + { + assert(0 && "retrieve: id 0 not allowed"); + return 0; + } + Line* l = find_line(id); + return l? &l->ent : 0; + } + + + // add/release a reference to a line, to protect it against + // displacement via associate(). we verify refs >= 0. + int lock(u64 id, bool locked) + { + Line* l = find_line(id); if(!l) return -1; - l->ctx = ctx; + + if(locked) + l->refs++; + else + { + assert(l->refs > 0); + l->refs--; + } return 0; } - int lock(u64 tag, bool locked) - { - Line* l = find_line(tag); - if(!l) - return -1; - l->locked = locked; - return 0; - } private: // implementation: // cache lines are stored in a list, most recently used in front. - // a map finds the list entry containing a given tag in log-time. + // a map finds the list entry containing a given id in log-time. struct Line { - u64 tag; - void* p; - bool locked; // protect from displacement - uintptr_t ctx; + u64 id; + Entry ent; + int refs; // protect from displacement if > 0 - Line(u64 _tag, void* _p) + Line(u64 _tag, Entry& _ent) { - tag = _tag; - p = _p; - locked = false; - ctx = 0; + id = 0; + ent = _ent; + refs = 0; } }; typedef std::list List; - typedef List::iterator List_iterator; + typedef typename List::iterator List_iterator; List lru_list; typedef std::map Map; Map idx; - // return the line identified by tag, or 0 if not in cache. - Line* find_line(u64 tag); + // return the line identified by id, or 0 if not in cache. + // mark it as the most recently used line. + Line* find_line(u64 id) + { + Map::const_iterator i = idx.find(id); + // not found + if(i == idx.end()) + return 0; - // mark l as the most recently used line. - void reference(List_iterator l); + // index points us to list entry + List_iterator l = i->second; + + // mark l as the most recently used line. + lru_list.splice(lru_list.begin(), lru_list, l); + idx[l->id] = l; + + return &*l; + } }; + + +// from VFS, not currently needed + +/* +template class StringMap +{ +public: + + T* add(const char* fn, T& t) + { + const FnHash fn_hash = fnv_hash(fn); + + t.name = fn; + + std::pair item = std::make_pair(fn_hash, t); + std::pair res; + res = map.insert(item); + + if(!res.second) + { + assert(0 && "add: already in container"); + return 0; + } + + // return address of user data (T) inserted into container. + return &((res.first)->second); + } + + T* find(const char* fn) + { + const FnHash fn_hash = fnv_hash(fn); + MapIt it = map.find(fn_hash); + // O(log(size)) + if(it == map.end()) + return 0; + return &it->second; + } + + size_t size() const + { + return map.size(); + } + + void clear() + { + map.clear(); + } + + +private: + typedef std::map Map; + typedef typename Map::iterator MapIt; + Map map; + + +public: + + class iterator + { + public: + iterator() + {} + iterator(typename StringMap::MapIt _it) + { it = _it; } + T& operator*() const + { return it->second; } + T* operator->() const + { return &**this; } + iterator& operator++() // pre + { ++it; return (*this); } + bool operator==(const iterator& rhs) const + { return it == rhs.it; } + bool operator!=(const iterator& rhs) const + { return !(*this == rhs); } + protected: + typename StringMap::MapIt it; + }; + + iterator begin() + { return iterator(map.begin()); } + + iterator end() + { return iterator(map.end()); } + +}; + + + +template class PriMap +{ +public: + + int add(Key key, uint pri, Data& data) + { + Item item = std::make_pair(pri, data); + MapEntry ent = std::make_pair(key, item); + std::pair ret; + ret = map.insert(ent); + // already in map + if(!ret.second) + { + MapIt it = ret.first; + Item item = it->second; + const uint old_pri = item.first; + Data& old_data = item.second; + + // new data is of higher priority; replace older data + if(old_pri <= pri) + { + old_data = data; + return 0; + } + // new data is of lower priority; don't add + else + return 1; + } + + return 0; + } + + Data* find(Key key) + { + MapIt it = map.find(key); + if(it == map.end()) + return 0; + + return &it->second.second; + } + + void clear() + { + map.clear(); + } + +private: + typedef std::pair Item; + typedef std::pair MapEntry; + typedef std::map Map; + typedef typename Map::iterator MapIt; + Map map; +}; +*/ + + + +#endif // #ifndef ADTS_H__ diff --git a/source/lib/config.h b/source/lib/config.h index 91d899d684..c3f734ed70 100755 --- a/source/lib/config.h +++ b/source/lib/config.h @@ -24,3 +24,13 @@ #undef HAVE_X +#undef CONFIG_DISABLE_EXCEPTIONS + +// TODO: where does this belong? +#ifdef CONFIG_DISABLE_EXCEPTIONS +# ifdef _WIN32 +# define _HAS_EXCEPTIONS 0 +# else +# define STL_NO_EXCEPTIONS +# endif +#endif \ No newline at end of file diff --git a/source/lib/detect.cpp b/source/lib/detect.cpp index a94264a789..219b0f7e2f 100755 --- a/source/lib/detect.cpp +++ b/source/lib/detect.cpp @@ -30,23 +30,11 @@ # include "sysdep/ia32.h" #endif #include "timer.h" -#include "ogl.h" -#include "sdl.h" -// HACK extern int win_get_gfx_card(); extern int win_get_gfx_drv(); extern int win_get_cpu_info(); -/* -// useful for choosing a video mode. not called by detect(). -void get_cur_resolution(int& xres, int& yres) -{ - // guess - xres = 1024; yres = 768; -} -*/ - // // memory // diff --git a/source/lib/detect.h b/source/lib/detect.h index 9e5f842c85..8e736fd363 100755 --- a/source/lib/detect.h +++ b/source/lib/detect.h @@ -27,8 +27,13 @@ extern "C" { // useful for choosing a video mode. not called by detect(). -// if we fail, don't change the outputs (assumed initialized to defaults) -extern void get_cur_resolution(int& xres, int& yres); +// if we fail, outputs are unchanged (assumed initialized to defaults) +extern int get_cur_resolution(int& xres, int& yres); + +// useful for determining aspect ratio. not called by detect(). +// if we fail, outputs are unchanged (assumed initialized to defaults) +extern int get_monitor_size(int& width_cm, int& height_cm); + extern char gfx_card[64]; // default: "unknown" diff --git a/source/lib/lib.cpp b/source/lib/lib.cpp index 2060382623..50ee7518d4 100755 --- a/source/lib/lib.cpp +++ b/source/lib/lib.cpp @@ -97,6 +97,11 @@ int atexit2(void* func, uintptr_t arg, CallConvention cc) } +int atexit2(void* func) +{ + return atexit2(func, 0, CC_CDECL_0); +} + // call from main as early as possible. diff --git a/source/lib/lib.h b/source/lib/lib.h index c3d9d150d0..a958778c50 100755 --- a/source/lib/lib.h +++ b/source/lib/lib.h @@ -32,14 +32,11 @@ #endif -extern void log_out(char* fmt, ...); - extern void log_out2(char* fmt, ...); - extern void log_out3(char* fmt, ...); - extern "C" int _heapchk(); +#define STMT(STMT_code__) do { STMT_code__; } while(0) // must not be used before main entered! (i.e. not from NLS constructors / functions) #define ONCE(ONCE_code__)\ -{\ +STMT(\ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;\ static bool ONCE_done__ = false;\ if(pthread_mutex_trylock(&(mutex)) == 0 && !ONCE_done__)\ @@ -47,7 +44,15 @@ extern void log_out(char* fmt, ...); ONCE_done__ = true;\ ONCE_code__;\ }\ -} +) + + +#define CHECK_ERR(func)\ +STMT(\ + int err = (int)(func);\ + if(err < 0)\ + return err;\ +) enum LibError @@ -57,9 +62,9 @@ enum LibError ERR_EOF = -1002, // attempted to read beyond EOF ERR_INVALID_PARAM = -1003, ERR_FILE_NOT_FOUND = -1004, + ERR_PATH_NOT_FOUND = -1005, - -ERR_x + ERR_LAST }; @@ -83,8 +88,9 @@ ERR_x // generate a symbol containing the line number of the macro invocation. // used to give a unique name (per file) to types made by cassert. -// we can't prepend __FILE__ to make it globally unique - -// the filename may be enclosed in quotes. +// we can't prepend __FILE__ to make it globally unique - the filename +// may be enclosed in quotes. need the 2 macro expansions to make sure +// __LINE__ is expanded correctly. #define MAKE_UID2__(l) LINE_ ## l #define MAKE_UID1__(l) MAKE_UID2__(l) #define UID__ MAKE_UID1__(__LINE__) @@ -94,14 +100,14 @@ ERR_x #define cassert(expr) struct UID__ { int CASSERT_FAILURE: (expr); }; // less helpful error message, but redefinition doesn't trigger warnings. -#define cassert2(expr) extern char CASSERT_FAILURE[expr]; +#define cassert2(expr) extern char CASSERT_FAILURE[1][(expr)]; // note: alternative method in C++: specialize a struct only for true; -// sizeof will raise 'incomplete type' errors if instantiated with false. +// using it will raise 'incomplete type' errors if instantiated with false. + -#define STMT(STMT_code__) do { STMT_code__; } while(0) // converts 4 character string to u32 for easy comparison @@ -121,8 +127,8 @@ ERR_x -const size_t KB = 1 << 10; -const size_t MB = 1 << 20; +const size_t KB = 1ul << 10; +const size_t MB = 1ul << 20; #ifdef _WIN32 @@ -132,17 +138,16 @@ const size_t MB = 1 << 20; #endif - #define BIT(n) (1ul << (n)) // call from main as early as possible. -void lib_init(); +extern void lib_init(); -enum CallConvention // number of parameters +enum CallConvention // number of parameters and convention { CC_CDECL_0, CC_CDECL_1, @@ -151,20 +156,18 @@ enum CallConvention // number of parameters CC_STDCALL_1, #endif - CC_UNUSED // no trailing comma if !_WIN32 + CC_UNUSED // get rid of trailing comma when !_WIN32 }; -#ifdef _WIN32 -#define CC_DEFAULT CC_STDCALL_1 -#else -#define CC_DEFAULT CC_CDECL_1 -#endif // more powerful atexit: registers an exit handler, with 0 or 1 parameters. // callable before libc initialized, and frees up the real atexit table. // stdcall convention is provided on Windows to call APIs (e.g. WSACleanup). // for these to be called at exit, lib_main must be invoked after _cinit. -extern int atexit2(void* func, uintptr_t arg, CallConvention cc = CC_DEFAULT); +extern int atexit2(void* func, uintptr_t arg, CallConvention cc = CC_CDECL_1); + +// no parameters, cdecl (CC_CDECL_0) +extern int atexit2(void* func); #include "posix.h" diff --git a/source/lib/misc.cpp b/source/lib/misc.cpp index befbf359c8..71b845dc52 100755 --- a/source/lib/misc.cpp +++ b/source/lib/misc.cpp @@ -49,10 +49,12 @@ u32 fnv_hash(const void* buf, const size_t len) else { size_t bytes_left = len; - while(bytes_left--) + while(bytes_left != 0) { h ^= *p++; h *= 0x01000193; + + bytes_left--; } } @@ -62,37 +64,6 @@ u32 fnv_hash(const void* buf, const size_t len) -u16 bswap16(u16 x) -{ - return (u16)(((x & 0xff) << 8) | (x >> 8)); -} - - -u32 bswap32(u32 x) -{ -#ifdef _M_IX86 - -__asm -{ - mov eax, [x] - bswap eax - mov [x], eax -} - -#else - - u32 t = x; - - for(int i = 0; i < 4; i++) - { - x <<= 8; - x |= t & 0xff; - } - -#endif - return x; -} - void bswap32(const u8* data, int cnt) { @@ -164,7 +135,7 @@ int ilog2(const int n) } -static int ilog2(const float x) +int ilog2(const float x) { u32 i = (u32&)x; u32 exp = (i >> 23) & 0xff; @@ -180,31 +151,17 @@ uintptr_t round_up(uintptr_t val, uintptr_t multiple) } -//u16 addusw(u16 x, u16 y) -//{ -// u32 t = x; -// return (u16)MIN(t+y, 0xffff); -//} -// -// -//u16 subusw(u16 x, u16 y) -//{ -// long t = x; -// return MAX(t-y, 0); -//} - - u16 addusw(u16 x, u16 y) { -u32 t = x; -return (u16)MIN(t+y, 0xffff); + u32 t = x; + return (u16)MIN(t+y, 0xffff); } u16 subusw(u16 x, u16 y) { -long t = x; -return (u16)(MAX(t-y, 0)); + long t = x; + return (u16)(MAX(t-y, 0)); } @@ -243,7 +200,7 @@ void base32(const int len, const u8* in, u8* out) *out++ = tbl[c]; } } - +/* #ifndef _WIN32 char *_itoa(int value, char *out, int radix) @@ -292,3 +249,5 @@ char *_ltoa(long val, char *out, int radix) } #endif +*/ + diff --git a/source/lib/misc.h b/source/lib/misc.h index 1a9ab00e64..565a6c1732 100755 --- a/source/lib/misc.h +++ b/source/lib/misc.h @@ -33,6 +33,9 @@ // otherwise, hash bytes of buf. extern u32 fnv_hash(const void* buf, const size_t len = 0); +// hash (currently FNV) of a filename +typedef u32 FnHash; + #ifndef min inline int min(int a, int b) diff --git a/source/lib/ogl.h b/source/lib/ogl.h index d8e9569494..7c84e41e88 100755 --- a/source/lib/ogl.h +++ b/source/lib/ogl.h @@ -10,6 +10,7 @@ extern "C" { // #ifdef _WIN32 + #ifndef WINGDIAPI #define WINGDIAPI __declspec(dllimport) #endif @@ -19,15 +20,17 @@ extern "C" { #ifndef APIENTRY #define APIENTRY __stdcall #endif + typedef unsigned short wchar_t; // for glu.h + #endif // #ifndef _WIN32 #ifdef __APPLE__ -#include -#include +# include +# include #else -#include -#include +# include +# include #endif @@ -46,9 +49,9 @@ typedef unsigned short wchar_t; // for glu.h #undef GL_GLEXT_PROTOTYPES #ifdef __APPLE__ -#include +# include #else -#include +# include #endif #define GL_TEXTURE_IMAGE_SIZE_ARB 0x86A0 @@ -59,9 +62,9 @@ typedef unsigned short wchar_t; // for glu.h // #ifdef _WIN32 -#define CALL_CONV __stdcall +# define CALL_CONV __stdcall #else -#define CALL_CONV +# define CALL_CONV #endif #define FUNC(ret, name, params) extern ret (CALL_CONV *name) params; diff --git a/source/lib/res/file.cpp b/source/lib/res/file.cpp index 4ef47ad327..aa1d5f3b22 100755 --- a/source/lib/res/file.cpp +++ b/source/lib/res/file.cpp @@ -57,9 +57,210 @@ const size_t BLOCK_SIZE = 1ul << BLOCK_SIZE_LOG2; // * requests for part of a block are usually followed by another. + + +// return the native equivalent of the given portable path +// (i.e. convert all '/' to the platform's directory separator) +// makes sure length < PATH_MAX. +static int mk_native_path(const char* const path, char* const n_path) +{ +/* + // if there's a platform with multiple-character DIR_SEP, + // scan through the path and add space for each separator found. + const size_t len = strlen(p_path); + + n_path = (const char*)malloc(len * sizeof(char)); + if(!n_path) + return ERR_NO_MEM; +*/ + + const char* portable = path; + char* native = (char*)n_path; + + size_t len = 0; + + for(;;) + { + len++; + if(len >= PATH_MAX) + return -1; + + char c = *portable++; + + if(c == '/') + c = DIR_SEP; + + *native++ = c; + + if(c == '\0') + break; + } + + return 0; +} + + +// rationale for data dir being root: +// xxxxxxxx yyyyyyyyy zzzzzzzzzz sandbox; vfs users (untrusted scripts) can't overwrite critical game files +int file_set_root_dir(const char* argv0, const char* root_dir) +{ + // security check: only allow attempting to set root dir once + // (prevents malicious scripts from overwriting important files + // above the intended VFS root) + static bool already_attempted; + if(already_attempted) + { + assert(0 && "vfs_set_root called more than once"); + return -1; + } + already_attempted = true; + + + if(access(argv0, X_OK) < 0) + return -errno; + + char path[PATH_MAX+1]; + path[PATH_MAX] = '\0'; + if(!realpath(argv0, path)) + return -errno; + + // remove executable name + char* fn = strrchr(path, DIR_SEP); + if(!fn) + return -1; + *fn = 0; + + // path is now the absolute path to the executable. + + if(chdir(path) < 0) + return -errno; + + char* native_root = path; // reuse path[] (no longer needed) + CHECK_ERR(mk_native_path(root_dir, native_root)); + + if(chdir(native_root) < 0) + return -errno; + + return 0; +} + + + + + + +// need to store entries returned by readdir so they can be sorted. +struct DirEnt +{ + std::string name; + uint flags; + ssize_t size; + + DirEnt(const char* _name, uint _flags, ssize_t _size) + : name(_name), flags(_flags), size(_size) {} +}; + +typedef std::vector DirEnts; +typedef DirEnts::iterator DirEntsIt; + +static bool dirent_less(DirEnt& d1, DirEnt& d2) + { return d1.name.compare(d2.name) < 0; } + +int file_enum_dirents(const char* dir, DirEntCB cb, uintptr_t user) +{ + char n_path[PATH_MAX+1]; + n_path[PATH_MAX] = '\0'; + // will append filename to this, hence "path". + // 0-terminate simplifies filename strncpy below. + CHECK_ERR(mk_native_path(dir, n_path)); + + // all entries are enumerated (adding to list), sorted, then passed to cb + DirEnts dirents; + + int stat_err = 0; + int cb_err = 0; + int ret; + + DIR* os_dir = opendir(n_path); + if(!os_dir) + return -1; + + // will append file names here + const size_t n_path_len = strlen(n_path); + char* fn_start = n_path + n_path_len; + *fn_start++ = DIR_SEP; + + struct dirent* ent; + while((ent = readdir(os_dir))) + { + const char* fn = ent->d_name; + + strncpy(fn_start, fn, PATH_MAX-n_path_len); + // stat needs the relative path. this is easier than changing + // directory every time, and should be fast enough. + + struct stat s; + ret = stat(n_path, &s); + if(ret < 0) + { + if(stat_err == 0) + stat_err = ret; + continue; + } + + uint flags = 0; + ssize_t size = s.st_size; + + // dir + if(s.st_mode & S_IFDIR) + { + // skip . and .. + if(fn[0] == '.' && (fn[1] == '\0' || (fn[1] == '.' && fn[2] == '\0'))) + continue; + + flags |= LOC_DIR; + size = -1; + } + // skip if neither dir nor file + else if(!(s.st_mode & S_IFREG)) + continue; + + dirents.push_back(DirEnt(fn, flags, size)); + } + closedir(os_dir); + + std::sort(dirents.begin(), dirents.end(), dirent_less); + + for(DirEntsIt it = dirents.begin(); it != dirents.end(); ++it) + { + const char* name_c = it->name.c_str(); + const uint flags = it->flags; + const ssize_t size = it->size; + ret = cb(name_c, flags, size, user); + if(ret < 0) + if(cb_err == 0) + cb_err = ret; + } + + if(cb_err < 0) + return cb_err; + return stat_err; +} + + +int file_stat(const char* path, struct stat* s) +{ + char n_path[PATH_MAX+1]; + CHECK_ERR(mk_native_path(path, n_path)); + + return stat(n_path, s); +} + + /////////////////////////////////////////////////////////////////////////////// // // file open/close +// stores information about file (e.g. size) in File struct // /////////////////////////////////////////////////////////////////////////////// @@ -93,7 +294,7 @@ static const u32 FILE_MAGIC = FOURCC('F','I','L','E'); #endif -static int file_validate(uint line, File* f) +static int file_validate(const uint line, File* const f) { const char* msg = ""; int err = -1; @@ -135,10 +336,13 @@ do\ while(0); -int file_open(const char* path, int flags, File* f) +int file_open(const char* p_fn, int flags, File* f) { memset(f, 0, sizeof(File)); + char n_fn[PATH_MAX]; + CHECK_ERR(mk_native_path(p_fn, n_fn)); + if(!f) goto invalid_f; // jump to CHECK_FILE post-check, which will handle this. @@ -153,15 +357,16 @@ int file_open(const char* path, int flags, File* f) else { struct stat s; - int err = stat(path, &s); - if(err < 0) - return err; + if(stat(n_fn, &s) < 0) + return -1; + if(!(s.st_mode & S_IFREG)) + return -1; size = s.st_size; } - int fd = open(path, mode); + int fd = open(n_fn, mode); if(fd < 0) - return 1; + return -1; #ifdef PARANOIA f->magic = FILE_MAGIC; @@ -169,7 +374,7 @@ int file_open(const char* path, int flags, File* f) f->flags = flags; f->size = size; - f->fn_hash = fnv_hash(path); + f->fn_hash = fnv_hash(n_fn); // copy filename insteaD? f->mapping = 0; f->fd = fd; } @@ -197,6 +402,73 @@ int file_close(File* f) } +/////////////////////////////////////////////////////////////////////////////// +// +// low level IO +// thin wrapper over aio; no alignment or caching +// +/////////////////////////////////////////////////////////////////////////////// + + +struct ll_cb +{ + aiocb cb; +}; + + +int ll_start_io(File* f, size_t ofs, size_t size, void* p, ll_cb* lcb) +{ + CHECK_FILE(f) + + if(size == 0) + { + assert(0 && "ll_start_io: size = 0 - why?"); + return ERR_INVALID_PARAM; + } + if(ofs >= f->size) + { + assert(0 && "ll_start_io: ofs beyond f->size"); + return -1; + } + + size_t bytes_left = f->size - ofs; // > 0 + int op = (f->flags & FILE_WRITE)? LIO_WRITE : LIO_READ; + + // don't read beyond EOF + if(size > bytes_left) // avoid min() - it wants int + size = bytes_left; + + aiocb* cb = &lcb->cb; + + // send off async read/write request + cb->aio_lio_opcode = op; + cb->aio_buf = p; + cb->aio_fildes = f->fd; + cb->aio_offset = (off_t)ofs; + cb->aio_nbytes = size; + return lio_listio(LIO_NOWAIT, &cb, 1, (struct sigevent*)0); + // this just issues the I/O - doesn't wait until complete. +} + + +// as a convenience, return a pointer to the transfer buffer +// (rather than expose the ll_cb internals) +ssize_t ll_wait_io(ll_cb* lcb, void*& p) +{ + aiocb* cb = &lcb->cb; + + // wait for transfer to complete. + while(aio_error(cb) == EINPROGRESS) + aio_suspend(&cb, 1, NULL); + + p = cb->aio_buf; + + // return how much was actually transferred, + // or -1 if the transfer failed. + return aio_return(cb); +} + + /////////////////////////////////////////////////////////////////////////////// // // block cache @@ -204,22 +476,25 @@ int file_close(File* f) /////////////////////////////////////////////////////////////////////////////// -static Cache c; +static Cache c; + // don't need a Block struct as cache data - + // it stores its associated ID, and does refcounting + // with lock(). -// create a tag for use with the Cache that uniquely identifies +// create an id for use with the Cache that uniquely identifies // the block from the file containing . -static u64 make_tag(u32 fn_hash, size_t ofs) +static u64 block_make_id(const u32 fn_hash, const size_t ofs) { - // tag format: filename hash | block number - // 63 32 31 0 + // id format: filename hash | block number + // 63 32 31 0 // // we assume the hash (currently: FNV) is unique for all filenames. // chance of a collision is tiny, and a build tool will ensure // filenames in the VFS archives are safe. // // block_num will always fit in 32 bits (assuming maximum file size - // = 2^32 * BLOCK_SIZE = 2^48 - enough); we check this, but don't + // = 2^32 * BLOCK_SIZE = 2^48 -- plenty); we check this, but don't // include a workaround. we could return 0, and the caller would have // to allocate their own buffer, but don't bother. @@ -227,60 +502,61 @@ static u64 make_tag(u32 fn_hash, size_t ofs) size_t block_num = ofs / BLOCK_SIZE; assert(block_num <= 0xffffffff); - u64 tag = fn_hash; // careful, don't shift a u32 32 bits left - tag <<= 32; - tag |= block_num; - return tag; + u64 id = fn_hash; // careful, don't shift a u32 32 bits left + id <<= 32; + id |= block_num; + return id; } // -static void* block_alloc(u64 tag) +static void* block_alloc(const u64 id) { - void* p; - - // initialize pool, if not done already. - static size_t cache_size; - static size_t cache_pos = 0; - static void* cache = 0; - if(!cache) + // initialize cache, if not done already. + static bool cache_initialized; + if(!cache_initialized) { - cache_size = 16 * BLOCK_SIZE; get_mem_status(); - // TODO: adjust cache_size - cache = mem_alloc(cache_size, BLOCK_SIZE); - if(!cache) + // TODO: calculate size + size_t num_blocks = 16; + + // evil: waste some mem (up to one block) to make sure the first block + // isn't at the start of the allocation, so that users can't + // mem_free() it. do this by manually aligning the pool. + // + // allocator will free the whole thing at exit. + void* pool = mem_alloc((num_blocks+1) * BLOCK_SIZE); + if(!pool) return 0; - } - // we have free blocks - add to cache - if(cache_pos < cache_size) - { - p = (char*)cache + cache_pos; - cache_pos += BLOCK_SIZE; + uintptr_t start = round_up((uintptr_t)pool + 1, BLOCK_SIZE); + // +1 => if already block-aligned, add a whole block! - if(c.add(tag, p) < 0) + // add all blocks to cache + void* p = (void*)start; + for(size_t i = 0; i < num_blocks; i++) { - assert(0 && "block_alloc: Cache::add failed!"); - return 0; + if(c.grow(p) < 0) + assert(0 && "block_alloc: Cache::grow failed!"); + // currently can't fail. + p = (char*)p + BLOCK_SIZE; } - } - // all of our pool's blocks are in the cache. - // displace the LRU entry. if not possible (all are locked), fail. - else - { - p = c.replace_lru_with(tag); - if(!p) - return 0; + + cache_initialized = true; } - if(c.lock(tag, true) < 0) + void** entry = c.assign(id); + if(!entry) + return 0; + void* block = *entry; + + if(c.lock(id, true) < 0) assert(0 && "block_alloc: Cache::lock failed!"); // can't happen: only cause is tag not found, but we successfully // added it above. if it did fail, that'd be bad: we leak the block, // and/or the buffer may be displaced while in use. hence, assert. - return p; + return block; } @@ -290,11 +566,20 @@ static void* block_alloc(u64 tag) // instead, add a copy on write call, if necessary. - -static int block_retrieve(u64 tag, void*& p) +static int block_retrieve(const u64 id, void*& p) { - p = c.get(tag); - return p? 0 : -1; + void** entry = c.retrieve(id); + if(entry) + { + p = *entry; + c.lock(id, true); // add reference + return 0; + } + else + { + p = 0; + return -1; + } // important note: // for the purposes of starting the IO, we can regard blocks whose read @@ -303,35 +588,29 @@ static int block_retrieve(u64 tag, void*& p) // // don't want to require IOs to be completed in order of issue: // that'd mean only 1 caller can read from file at a time. - // would obviate associating tag with IO, but is overly restrictive. + // would obviate associating id with IO, but is overly restrictive. } -static int block_discard(u64 tag) +static int block_discard(const u64 id) { - return c.lock(tag, false); + return c.lock(id, false); } -void file_free_buf(void *p) -{ -} - - -// remove from cache? -int discard_buf(void* p) -{ - return 0; -} - -int free_buf(void* p) +int file_free_buf(void*& p) { uintptr_t _p = (uintptr_t)p; void* actual_p = (void*)(_p - (_p % BLOCK_SIZE)); // round down return mem_free(actual_p); + + // remove from cache? + + // check if in use? } + /////////////////////////////////////////////////////////////////////////////// // // async I/O @@ -339,27 +618,25 @@ int free_buf(void* p) /////////////////////////////////////////////////////////////////////////////// -enum -{ - CACHED = 1, -}; - struct IO { - struct aiocb* cb; - // struct aiocb is too big to store here. - // IOs are reused, so we don't allocate a - // new aiocb every file_start_io. + u64 block_id; + // transferring via cache (=> BLOCK_SIZE aligned) iff != 0 - u64 tag; + void* block; + // valid because cache line is locked + // (rug can't be pulled out from under us) + + ll_cb* cb; + // this is too big to store here. IOs are reused, + // so we don't allocate a new cb every file_start_io. void* user_p; size_t user_ofs; size_t user_size; - uint flags; - - void* block; + int cached : 1; + int pending : 1; }; H_TYPE_DEFINE(IO) @@ -371,8 +648,8 @@ H_TYPE_DEFINE(IO) static void IO_init(IO* io, va_list args) { - size_t size = round_up(sizeof(struct aiocb), 16); - io->cb = (struct aiocb*)mem_alloc(size, 16, MEM_ZERO); + size_t size = round_up(sizeof(struct ll_cb), 16); + io->cb = (ll_cb*)mem_alloc(size, 16, MEM_ZERO); } static void IO_dtor(IO* io) @@ -390,9 +667,7 @@ static void IO_dtor(IO* io) // aio_result, which would terminate the read. static int IO_reload(IO* io, const char* fn) { - if(!io->cb) - return -1; - return 0; + return io->cb? 0 : ERR_NO_MEM; } @@ -401,7 +676,7 @@ static int IO_reload(IO* io, const char* fn) // extra layer on top of h_alloc, so we can reuse IOs // -// (avoids allocating the aiocb every IO => less fragmentation) +// (avoids allocating the cb every IO => less heap fragmentation) // // don't worry about reassigning IOs to their associated file - // they don't need to be reloaded, since the VFS refuses reload @@ -413,7 +688,7 @@ typedef std::vector IOList; static IOList free_ios; // list of all IOs allocated. -// used to find active IO, given tag (see below). +// used to find active IO, given id (see below). // also used to free all IOs before the handle manager // cleans up at exit, so they aren't seen as resource leaks. @@ -436,8 +711,8 @@ static void io_cleanup(void) static Handle io_alloc() { - ONCE(atexit(io_cleanup)) - + ONCE(atexit(io_cleanup)); +/* // grab from freelist if(!free_ios.empty()) { @@ -448,13 +723,15 @@ static Handle io_alloc() // we don't check if the freelist contains valid handles. // that "can't happen", and if it does, it'll be caught // by the handle dereference in file_start_io. - // - // note that no one else can actually free an IO - - // that would require its handle type, which is private to - // this module. the free_io call just adds it to the freelist; - // all allocated IOs are destroyed by the handle manager at exit. - } + // + // no one else can actually free an IO - that would require + // its handle type, which is private to this module. + // the free_io call just adds it to the freelist; + // all allocated IOs are destroyed in io_cleanup at exit. + return h; + } +*/ // allocate a new IO Handle h = h_alloc(H_IO, 0); // .. it's valid - store in list. @@ -466,24 +743,41 @@ static Handle io_alloc() static int io_free(Handle hio) { - // mark it unused, and incidentally make sure hio is valid - // before adding to freelist. H_DEREF(hio, IO, io); - io->tag = 0; + + if(io->pending) + { + assert(0 && "io_free: IO pending"); + return -1; + } + + // clear the other IO fields, just to be sure. + // (but don't memset the whole thing - that'd trash the cb pointer!) + io->block_id = 0; + io->block = 0; + io->cached = 0; + io->user_ofs = 0; + io->user_size = 0; + io->user_p = 0; + memset(io->cb, 0, sizeof(ll_cb)); + + // TODO: complain if buffer not yet freed? + + // we know hio is valid, since we successfully dereferenced above. free_ios.push_back(hio); return 0; } -// need to find IO, given tag, to make sure a block +// need to find IO, given id, to make sure a block // that is marked cached has actually been read. // it is expected that there only be a few allocated IOs, // so it's ok to search this list every cache hit. // adding to the cache data structure would be messier. -struct FindTag : public std::binary_function +struct FindBlock : public std::binary_function { - bool operator()(Handle hio, u64 tag) const + bool operator()(Handle hio, u64 block_id) const { // can't use H_DEREF - we return bool IO* io = (IO*)h_user_data(hio, H_IO); @@ -492,14 +786,14 @@ struct FindTag : public std::binary_function assert(0 && "invalid handle in all_ios list!"); return false; } - return io->tag == tag; + return io->block_id == block_id; } }; -static Handle io_find_tag(u64 tag) +static Handle io_find(u64 block_id) { IOList::const_iterator it; - it = std::find_if(all_ios.begin(), all_ios.end(), std::bind2nd(FindTag(), tag)); + it = std::find_if(all_ios.begin(), all_ios.end(), std::bind2nd(FindBlock(), block_id)); // not found if(it == all_ios.end()) return 0; @@ -513,7 +807,7 @@ static Handle io_find_tag(u64 tag) // rationale for extra alignment / copy layer, even though aio takes care of it: -// aio would read pad to its minimum read alignment, copy over, and be done; +// aio would pad to its minimum read alignment, copy over, and be done; // in our case, if something is unaligned, a request for the remainder of the // block is likely to follow, so we want to cache the whole block. @@ -546,43 +840,47 @@ Handle file_start_io(File* f, size_t user_ofs, size_t user_size, void* user_p) user_size = bytes_left; - const u64 tag = make_tag(f->fn_hash, user_ofs); + u64 block_id = block_make_id(f->fn_hash, user_ofs); // allocate IO slot Handle hio = io_alloc(); H_DEREF(hio, IO, io); - struct aiocb* cb = io->cb; - - io->tag = tag; - io->user_p = user_p; - io->user_ofs = user_ofs; + io->block_id = block_id; + io->user_p = user_p; + io->user_ofs = user_ofs; io->user_size = user_size; - // notes: io->flags and io->block are already zeroed; - // cb holds the actual IO request (aligned offset and size). + // notes: io->cached, io->pending and io->block are already zeroed; + // cb will receive the actual IO request (aligned offset and size). - // if already cached, we're done - if(block_retrieve(tag, io->block) == 0) - { - io->flags = CACHED; - return hio; - } +debug_out("file_start_io hio=%I64x ofs=%d size=%d\n", hio, user_ofs, user_size); // aio already safely handles unaligned buffers or offsets. // when reading zip files, we don't want to repeat a read - // if a block containing end of one file and start of the next + // if a block contains the end of one file and start of the next // (speed concern). + // therefore, we align and round up to whole blocks. // // note: cache even if this is the last block before EOF: // a zip archive may contain one last file in the block. // if not, no loss - the buffer will be LRU, and reused. - size_t ofs = user_ofs; size_t padding = ofs % BLOCK_SIZE; ofs -= padding; size_t size = round_up(padding + user_size, BLOCK_SIZE); + + + // if already cached, we're done + if(size == BLOCK_SIZE && block_retrieve(block_id, io->block) == 0) + { + debug_out("file_start_io: cached! block # = %d\n", block_id & 0xffffffff); + io->cached = 1; + return hio; + } + + void* buf = 0; void* our_buf = 0; @@ -591,10 +889,13 @@ Handle file_start_io(File* f, size_t user_ofs, size_t user_size, void* user_p) else { if(size == BLOCK_SIZE) - our_buf = io->block = block_alloc(tag); + our_buf = io->block = block_alloc(block_id); // transferring more than one block - doesn't go through cache! else + { our_buf = mem_alloc(size, BLOCK_SIZE); + block_id = 0; + } if(!our_buf) { err = ERR_NO_MEM; @@ -604,20 +905,14 @@ Handle file_start_io(File* f, size_t user_ofs, size_t user_size, void* user_p) buf = our_buf; } - // send off async read/write request - cb->aio_lio_opcode = op; - cb->aio_buf = buf; - cb->aio_fildes = f->fd; - cb->aio_offset = (off_t)ofs; - cb->aio_nbytes = size; - err = lio_listio(LIO_NOWAIT, &cb, 1, (struct sigevent*)0); - // return as soon as I/O is queued + err = ll_start_io(f, ofs, size, buf, io->cb); if(err < 0) { fail: file_discard_io(hio); - file_free_buf(our_buf); + if(size != BLOCK_SIZE) + file_free_buf(our_buf); return err; } @@ -627,41 +922,35 @@ fail: int file_wait_io(const Handle hio, void*& p, size_t& size) { +debug_out("file_wait_io: hio=%I64x\n", hio); + int ret = 0; p = 0; size = 0; H_DEREF(hio, IO, io); - struct aiocb* cb = io->cb; + ll_cb* cb = io->cb; size = io->user_size; - ssize_t bytes_transferred; // block's tag is in cache. need to check if its read is still pending. - if(io->flags & CACHED) + if(io->cached) { - Handle cache_hio = io_find_tag(io->tag); + Handle cache_hio = io_find(io->block_id); // was already finished - don't wait if(cache_hio <= 0) goto skip_wait; - // not finished yet; wait for it below, as with uncached reads. - else - { - H_DEREF(cache_hio, IO, cache_io); - // can't fail, since io_find_tag has to dereference each handle. - cb = cache_io->cb; - } + + // not finished yet; will wait for it below, as with uncached reads. + H_DEREF(cache_hio, IO, cache_io); + // can't fail, since io_find has to dereference each handle. + cb = cache_io->cb; } - // wait for transfer to complete - { - while(aio_error(cb) == EINPROGRESS) - aio_suspend(&cb, 1, NULL); + void* transfer_buf; + ssize_t bytes_transferred = ll_wait_io(cb, transfer_buf); - bytes_transferred = aio_return(cb); - ret = bytes_transferred? 0 : -1; - } skip_wait: if(io->block) @@ -679,9 +968,9 @@ skip_wait: else p = src; } - // read directly into target buffer + // we had read directly into target buffer else - p = (void *)cb->aio_buf; // cb->aio_buf is volatile, p is not + p = transfer_buf; return ret; } @@ -690,7 +979,7 @@ skip_wait: int file_discard_io(Handle& hio) { H_DEREF(hio, IO, io); - block_discard(io->tag); + block_discard(io->block_id); io_free(hio); return 0; } @@ -702,7 +991,7 @@ int file_discard_io(Handle& hio) // *p != 0: *p is the source/destination address for the transfer. // (FILE_MEM_READONLY?) // *p == 0: allocate a buffer, read into it, and return it in *p. -// when no longer needed, it must be freed via file_discard_buf. +// when no longer needed, it must be freed via file_free_buf. // p == 0: read raw_size bytes from file, starting at offset raw_ofs, // into temp buffers; each block read is passed to cb, which is // expected to write actual_size bytes total to its output buffer @@ -714,6 +1003,8 @@ int file_discard_io(Handle& hio) ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** const p, const FILE_IO_CB cb, const uintptr_t ctx) // optional { +debug_out("file_io fd=%d size=%d ofs=%d\n", f->fd, raw_size, raw_ofs); + CHECK_FILE(f) const bool is_write = (f->flags == FILE_WRITE); @@ -743,7 +1034,9 @@ ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** con const size_t misalign = raw_ofs % BLOCK_SIZE; // actual transfer start offset - const size_t start_ofs = raw_ofs - misalign; // BLOCK_SIZE-aligned + // not aligned! aio takes care of initial unalignment; + // next read will be aligned, because we read up to the next block. + const size_t start_ofs = raw_ofs; void* buf = 0; // I/O source or sink; assume temp buffer @@ -805,18 +1098,32 @@ ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** con // const int MAX_IOS = 2; - Handle ios[MAX_IOS] = { 0, 0 }; + Handle ios[MAX_IOS] = { 0 }; + +if(ios[0] || ios[1])abort(); + int head = 0; int tail = 0; int pending_ios = 0; bool all_issued = false; - size_t raw_cnt = 0; // amount of raw data transferred so far - size_t issue_cnt = 0; // sum of I/O transfer requests + // (useful, raw data: possibly compressed, but doesn't count padding) + size_t raw_transferred_cnt = 0; + size_t issue_cnt = 0; + + // if callback, what it reports; otherwise, = raw_transferred_cnt + // this is what we'll return + size_t actual_transferred_cnt = 0; ssize_t err = +1; // loop terminates if <= 0 + +static int seq; +seq++; +if(seq == 4) +seq=4; + for(;;) { // queue not full, data remaining to transfer, and no error: @@ -824,11 +1131,11 @@ ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** con if(pending_ios < MAX_IOS && !all_issued && err > 0) { // calculate issue_size: - // want to transfer up to the next block boundary. + // at most, transfer up to the next block boundary. size_t issue_ofs = start_ofs + issue_cnt; const size_t left_in_block = BLOCK_SIZE - (issue_ofs % BLOCK_SIZE); - const size_t left_in_file = raw_size - issue_cnt; - size_t issue_size = MIN(left_in_block, left_in_file); + const size_t total_left = raw_size - issue_cnt; + size_t issue_size = MIN(left_in_block, total_left); // assume temp buffer allocated by file_start_io void* data = 0; @@ -866,7 +1173,7 @@ ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** con //// if size comes out short, we must be at EOF - raw_cnt += size; + raw_transferred_cnt += size; if(cb && !(err <= 0)) { @@ -876,7 +1183,12 @@ ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** con // pending transfers to complete. if(ret <= 0) err = ret; + else + actual_transferred_cnt += ret; } + // no callback to process data: raw = actual + else + actual_transferred_cnt += size; file_discard_io(hio); // zeroes array entry } @@ -899,9 +1211,9 @@ ssize_t file_io(File* const f, const size_t raw_ofs, size_t raw_size, void** con return err; } - assert(issue_cnt >= raw_cnt && raw_cnt == raw_size); + assert(issue_cnt == raw_transferred_cnt && raw_transferred_cnt == raw_size); - return (ssize_t)raw_cnt; + return (ssize_t)actual_transferred_cnt; } diff --git a/source/lib/res/file.h b/source/lib/res/file.h index 5a5345bb35..d55bde0045 100755 --- a/source/lib/res/file.h +++ b/source/lib/res/file.h @@ -53,12 +53,26 @@ enum // do not cache any part of the file // (e.g. if caching on a higher level) FILE_NO_CACHE = 4 - - }; -extern int file_open(const char* path, int flags, File* f); +// keep in sync with zip.cpp and vfs.cpp *_CB_FLAGS! +enum FILE_CB_FLAGS +{ + // location + LOC_DIR = BIT(0), +}; + +extern int file_set_root_dir(const char* argv0, const char* root); + + +typedef int(*DirEntCB)(const char* name, uint flags, ssize_t size, uintptr_t user); + +extern int file_enum_dirents(const char* dir, DirEntCB cb, uintptr_t user); + +extern int file_stat(const char* path, struct stat*); + +extern int file_open(const char* fn, int flags, File* f); extern int file_close(File* f); extern int file_map(File* f, void*& p, size_t& size); diff --git a/source/lib/res/h_mgr.cpp b/source/lib/res/h_mgr.cpp index 28bc254d76..5ec58b1fca 100755 --- a/source/lib/res/h_mgr.cpp +++ b/source/lib/res/h_mgr.cpp @@ -40,7 +40,7 @@ // why fixed size control blocks, instead of just allocating dynamically? // it is expected that resources be created and freed often. this way is // much nicer to the memory manager. defining control blocks larger than -// the alloted space is caught by h_alloc (made possible by the vtbl builder +// the allotted space is caught by h_alloc (made possible by the vtbl builder // storing control block size). it is also efficient to have all CBs in an // more or less contiguous array (see below). // @@ -69,19 +69,20 @@ const uint TAG_SHIFT = 0; const u32 TAG_MASK = 0xffffffff; // safer than (1 << 32) - 1 -// - index (0-based) points to control block in our array. +// - index (0-based) of control block in our array. // (field width determines maximum currently open handles) #define IDX_BITS 16 const uint IDX_SHIFT = 32; -const i32 IDX_MASK = (1l << IDX_BITS) - 1; +const u32 IDX_MASK = (1l << IDX_BITS) - 1; +// make sure both fields fit within a Handle variable cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT); // return the handle's index field (always non-negative). // no error checking! -static inline i32 h_idx(const Handle h) -{ return (i32)((h >> IDX_SHIFT) & IDX_MASK); } +static inline u32 h_idx(const Handle h) +{ return (u32)((h >> IDX_SHIFT) & IDX_MASK); } // return the handle's tag field. // no error checking! @@ -89,7 +90,7 @@ static inline u32 h_tag(const Handle h) { return (u32)((h >> TAG_SHIFT) & TAG_MASK); } // build a handle from index and tag -static inline Handle handle(const i32 idx, const i32 tag) +static inline Handle handle(const u32 idx, const u32 tag) { assert(idx <= IDX_MASK && tag <= TAG_MASK && "handle: idx or tag too big"); // somewhat clunky, but be careful with the shift: @@ -104,28 +105,38 @@ static inline Handle handle(const i32 idx, const i32 tag) // internal per-resource-instance data // + // determines maximum number of references to a resource. -// a handle's idx field isn't stored in its HDATA entry (not needed); -// to save space, this should take its place, i.e. it should fit in IDX_BITS. -static const uint REF_BITS = 12; +static const uint REF_BITS = 8; static const u32 REF_MAX = 1ul << REF_BITS; +static const uint TYPE_BITS = 8; + +// a handle's idx field isn't stored in its HDATA entry (not needed); +// to save space, these should take its place, i.e. they should fit in IDX_BITS. +// if not, ignore + comment out this assertion. +cassert(REF_BITS + TYPE_BITS <= IDX_BITS); + + // chosen so that all current resource structs are covered, // and so sizeof(HDATA) is a power of 2 (for more efficient array access // and array page usage). static const size_t HDATA_USER_SIZE = 48; +static const size_t HDATA_MAX_PATH = 64; + // 64 bytes struct HDATA { uintptr_t key; u32 tag : TAG_BITS; u32 refs : REF_BITS; + u32 type_idx : TYPE_BITS; H_Type type; - const char* fn; - u8 user[HDATA_USER_SIZE]; + + char fn[HDATA_MAX_PATH]; }; @@ -190,8 +201,8 @@ static HDATA* h_data_any_type(const Handle h) i32 idx = h_idx(h); // this function is only called for existing handles. - // they'd also fail the tag check below, but bail here - // to avoid needlessly allocating that entry's page. + // they'd also fail the tag check below, but bail out here + // already to avoid needlessly allocating that entry's page. if(idx > last_in_use) return 0; HDATA* hd = h_data(idx); @@ -282,7 +293,7 @@ static int alloc_idx(i32& idx, HDATA*& hd) assert(!"alloc_idx: too many open handles (increase IDX_BITS)"); return -1; } - idx = last_in_use+1; // incrementing idx would start it at 1 + idx = last_in_use+1; // just incrementing idx would start it at 1 hd = h_data(idx); if(!hd) return ERR_NO_MEM; @@ -325,7 +336,7 @@ int h_free(Handle& h, H_Type type) if(!hd) return ERR_INVALID_HANDLE; - // have valid refcount (don't decrement if alread 0) + // have valid refcount (don't decrement if already 0) if(hd->refs > 0) { hd->refs--; @@ -356,7 +367,7 @@ int h_free(Handle& h, H_Type type) // any further params are passed to type's init routine Handle h_alloc(H_Type type, const char* fn, uint flags, ...) { - ONCE(atexit(cleanup)) + ONCE(atexit(cleanup)); Handle err; diff --git a/source/lib/res/mem.cpp b/source/lib/res/mem.cpp index 2a88b16dd1..f03660f9ef 100755 --- a/source/lib/res/mem.cpp +++ b/source/lib/res/mem.cpp @@ -83,15 +83,35 @@ static void* pool_alloc(const size_t size, const uint align, uintptr_t& ctx, MEM ////////////////////////////////////////////////////////////////////////////// +static bool has_shutdown = false; + typedef std::map PtrToH; +static PtrToH* _ptr_to_h; + + +static void ptr_to_h_shutdown() +{ + has_shutdown = true; + delete _ptr_to_h; +} + // undefined NLSO init order fix -static PtrToH& _ptr_to_h() +static PtrToH& get_ptr_to_h() { - static PtrToH ptr_to_h_; - return ptr_to_h_; + if(!_ptr_to_h) + { + if(has_shutdown) + assert("mem.cpp: ptr -> handle lookup used after module shutdown"); + // crash + burn + + _ptr_to_h = new PtrToH; + + atexit2(ptr_to_h_shutdown); + } + return *_ptr_to_h; } -#define ptr_to_h _ptr_to_h() +#define ptr_to_h get_ptr_to_h() // not needed by other modules - mem_get_size and mem_assign is enough. @@ -268,11 +288,15 @@ void* mem_alloc(size_t size, const uint align, uint flags, Handle* phm) } -void* mem_get_ptr(Handle hm, size_t* size) +void* mem_get_ptr(Handle hm, size_t* size /* = 0 */) { Mem* m = H_USER_DATA(hm, Mem); if(!m) + { + if(size) + *size = 0; return 0; + } assert((!m->p || m->size) && "mem_get_ptr: mem corrupted (p valid =/=> size > 0)"); diff --git a/source/lib/res/res.h b/source/lib/res/res.h index f5325e9acf..e5209c7a33 100755 --- a/source/lib/res/res.h +++ b/source/lib/res/res.h @@ -2,4 +2,6 @@ #include "res/vfs.h" #include "res/tex.h" #include "res/mem.h" -#include "res/font.h" \ No newline at end of file +#include "res/font.h" +#include "res/file.h" +#include "res/zip.h" \ No newline at end of file diff --git a/source/lib/res/vfs.cpp b/source/lib/res/vfs.cpp index 3397699fc9..9045b970ba 100755 --- a/source/lib/res/vfs.cpp +++ b/source/lib/res/vfs.cpp @@ -28,10 +28,13 @@ #include "misc.h" #include "vfs.h" #include "mem.h" +#include "adts.h" #include #include #include +#include +#include #include // currently not thread safe, but that will most likely change @@ -65,9 +68,671 @@ // - archives (these remain open. allowing reload would mean we'd have to keep // track of all files from an archive, and reload them all. another hassle. // anyway, if files are to be changed in-game, then change the plain-file -// version, that's what they're for). +// version - that's what they're for). + + +/////////////////////////////////////////////////////////////////////////////// +// +// path +// +/////////////////////////////////////////////////////////////////////////////// + +// path types: +// p_* : portable +// v_* : VFS +// f_* : no path at all, filename only + + +static int path_append(char* dst, const char* path, const char* path2) +{ + const size_t path_len = strlen(path); + const size_t path2_len = strlen(path2); + + if(path_len+path2_len+1 > PATH_MAX) + return -1; + + char* p = dst; + + strcpy(p, path); + p += path_len; + if(path_len > 0 && p[-1] != '/') + *p++ = '/'; + strcpy(p, path2); + return 0; +} + + +static int path_validate(const uint line, const char* const path) +{ + size_t path_len = 0; + + const char* msg = 0; // error occurred <==> != 0 + int err = -1; // pass error code to caller + + // disallow absolute path + if(path[0] == '/') + { + msg = "absolute path"; + goto fail; + } + + // scan each char in path string; count length. + for(;;) + { + const int c = path[path_len++]; + + // whole path is too long + if(path_len >= VFS_MAX_PATH) + { + msg = "path too long"; + goto fail; + } + + // disallow ".." to prevent going above the VFS root dir + static bool last_was_dot; + if(c == '.') + { + if(last_was_dot) + { + msg = "contains \"..\""; + goto fail; + } + last_was_dot = true; + } + else + last_was_dot = false; + + // disallow OS-specific dir separators + if(c == '\\' || c == ':') + { + msg = "contains OS-specific dir separator (e.g. '\\', ':')"; + goto fail; + } + + // end of string, all is well. + if(c == '\0') + goto ok; + } + + // failed somewhere - err is the error code, + // or -1 if not set specifically above. +fail: + debug_out("path_validate at line %d failed: %s", err); + assert(0 && "path_validate failed"); + return err; + +ok: + return 0; +} + + +#define CHECK_PATH(p_path) CHECK_ERR(path_validate(__LINE__, p_path)) + + +/////////////////////////////////////////////////////////////////////////////// +// +// file location +// +/////////////////////////////////////////////////////////////////////////////// + + +// the VFS stores the location (archive or directory) of each file; +// this allows multiple search paths without having to check each one +// when opening a file (slow). +// +// one Loc is allocated for each archive or directory mounted, and all +// real subdirectories; all files in an archive share the same location. +// therefore, files only /point/ to a (possibly shared) Loc. +// if a file's location changes (e.g. after mounting a higher-priority +// directory), the VFS entry will point to the new Loc; the priority +// of both locations is unchanged. +// +// allocate via loc_create, passing the location. do not free! +// we keep track of all Locs allocated; they are freed at exit, +// and by loc_free_all (useful when rebuilding the VFS). +// this is much easier and safer than walking the VFS tree and +// freeing every location we find. + +struct Loc; +typedef std::vector Locs; + +// not many instances and allocated via new => +// don't worry about struct size / alignment. +struct Loc +{ + Handle ha; + + std::string path; + + uint pri; + + + Loc(Handle _ha, const char* _path, uint _pri) + : ha(_ha), path(_path), pri(_pri) + { + ONCE(atexit2(loc_free_all)); + + // add to list for later deletion + locs.push_back(this); + } + + friend int loc_free_all(); + +private: + static Locs locs; +}; + +Locs Loc::locs; + + +static inline void loc_free(Loc* const loc) + { delete loc; } + +static int loc_free_all() +{ + // we don't expect many Locs to be added (one per loose-file dir + // or archive), so don't worry about reallocating memory. + // return value is also irrelevant - can't fail as currently implemented. + + Locs& locs = Loc::locs; + std::for_each(locs.begin(), locs.end(), loc_free); + // could use smart ptr (boost or loki) instead, but this'll do + + locs.clear(); + return 0; +} + + +// wrapper on top of new + ctor to emphasize that +// the caller must not free the Loc pointer. +// (if they do, VFS entries point to freed memory => disaster) +static Loc* loc_create(const Handle ha, const char* const p_path, const uint pri) +{ + return new Loc(ha, p_path, pri); +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// "file system" (tree structure; stores location of each file) +// +/////////////////////////////////////////////////////////////////////////////// + + +struct VDir; + +typedef std::map SubDirs; +typedef SubDirs::iterator SubDirIt; + +typedef std::map Files; +typedef Files::iterator FileIt; + // note: priority is accessed by following the Loc pointer. + // keeping a copy in the map would lead to better cache coherency, + // but it's a bit more clumsy (map filename to struct {pri, Loc*}). + // revisit if file lookup open is too slow (unlikely). + +struct VDir +{ + std::string v_name; + + void* watch; + + + int file_add(const char* const fn, const uint pri, Loc* const loc) + { + std::string _fn(fn); + + typedef std::pair Ent; + Ent ent = std::make_pair(_fn, loc); + std::pair ret; + ret = files.insert(ent); + // file already in dir + if(!ret.second) + { + FileIt it = ret.first; + Loc*& old_loc = it->second; + + // new Loc is of higher priority; replace pointer + if(old_loc->pri <= loc->pri) + { + old_loc = loc; + return 0; + } + // new Loc is of lower priority; keep old pointer + else + return 1; + } + + return 0; + } + + Loc* file_find(const char* fn) + { + std::string _fn(fn); + FileIt it = files.find(_fn); + if(it == files.end()) + return 0; + return it->second; + } + + VDir* subdir_add(const char* fn, VDir& _dir) + { + std::string _fn(fn); + _dir.v_name = _fn; + + std::pair item = std::make_pair(_fn, _dir); + std::pair res; + res = subdirs.insert(item); + // already in container + if(!res.second) + assert(0 && "already in subdir"); + + SubDirIt it = res.first; + return &it->second; + } + + VDir* subdir_find(const char* fn) + { + std::string _fn(fn); + SubDirIt it = subdirs.find(_fn); + if(it == subdirs.end()) + return 0; + return &it->second; + } + + void clear_tree() + { + SubDirIt it; + for(it = subdirs.begin(); it != subdirs.end(); ++it) + { + VDir& dir = it->second; + dir.clear_tree(); + } + + files.clear(); + subdirs.clear(); + } + + SubDirs subdirs; // can't make private; needed for iterator + +private: + + Files files; +}; + + +VDir vfs_root; + + +enum LookupFlags +{ + PF_DEFAULT, + LF_CREATE_MISSING_COMPONENTS = 1 +}; + +static int tree_lookup(const char* vfs_path, Loc** loc = 0, VDir** dir = 0, LookupFlags flags = PF_DEFAULT) +{ + CHECK_PATH(vfs_path); + + // copy into (writeable) buffer so we can 'tokenize' path components + // by replacing '/' with '\0'. + // note: CHECK_PATH does length checking + char buf[VFS_MAX_PATH]; + strcpy(buf, vfs_path); + const char* cur_component = buf; + + const bool create_missing_components = flags & LF_CREATE_MISSING_COMPONENTS; + + VDir* cur_dir = &vfs_root; + + for(;;) + { + char* slash = strchr(cur_component, '/'); + // we have followed all path components. + // cur_component is the filename or "" + if(!slash) + { + // caller wants pointer to file location returned + if(loc) + { + const char* fn = cur_component; + *loc = cur_dir->file_find(fn); + + // user wanted its loc, but it's not found - fail + if(!*loc) + return ERR_FILE_NOT_FOUND; + } + // caller wants pointer to this dir returned + if(dir) + *dir = cur_dir; + return 0; + } + // cur_component is a subdirectory name; change to it + else + { + const char* subdir_name = cur_component; + *slash = 0; + + VDir* subdir = cur_dir->subdir_find(subdir_name); + if(!subdir) + { + if(create_missing_components) + { + VDir _dir; + subdir = cur_dir->subdir_add(subdir_name, _dir); + } + else + return ERR_PATH_NOT_FOUND; + } + + cur_dir = subdir; + cur_component = slash+1; + } + } +} + + + + + + + + + + + + + + + +typedef std::vector Archives; +typedef Archives::iterator ArchiveIt; + + + + + + + + + + + +struct FileCBParams +{ + VDir* dir; + Loc* loc; + + // somewhat of a hack. which archives are mounted into the VFS is stored + // in an Archives list in the Mount struct; they don't have anything to + // do with a VFS dir. we want to enumerate the archives in a dir via the + // normal populate(), though, so have to pass this to its callback. + Archives* archives; +}; + + +// called for each OS dir ent. +// add each file and directory to the VFS dir. +// +// note: +// we don't mount archives here for performance reasons. +// that means archives in subdirectories of mount points aren't added! +// rationale: can't determine if file is an archive via extension - +// they might be called .pk3 or whatnot. for every file in the tree, we'd have +// to try to open it as an archive - not good. +// this restriction also simplifies the code a bit, but if it's a problem, +// just generate a list of archives here and mount them from the caller. +static int file_cb(const char* fn, uint flags, ssize_t size, uintptr_t user) +{ + FileCBParams* params = (FileCBParams*)user; + VDir* cur_dir = params->dir; + Loc* cur_loc = params->loc; + Archives* archives = params->archives; + + // directory + if(flags & LOC_DIR) + { + VDir _dir; + cur_dir->subdir_add(fn, _dir); + } + // file + else + { + // only add to list; don't enumerate its files yet for easier debugging + // (we see which files are in a dir / archives) + // also somewhat faster, due to better locality. + // + // don't check filename extension - archives won't necessarily + // be called .zip (example: Quake III .pk3). + // just try to open the file. + if(archives) + { + const Handle ha = zip_archive_open(fn); + if(ha > 0) + archives->push_back(ha); + } + + cur_dir->file_add(fn, cur_loc->pri, cur_loc); + } + + return 0; +} + + + +static int vdir_add_from_archive(VDir* dir, const Handle ha, const uint pri, Archives* archives) +{ + // all files in the archive share this location + Loc* loc = loc_create(ha, 0, pri-1); + + // add all files in archive to the VFS dir + FileCBParams params = { dir, loc, archives }; + CHECK_ERR(zip_enum_files(ha, file_cb, (uintptr_t)¶ms)); + + return 0; +} + + + +static int addR(VDir* vdir, const char* path, Loc* loc, const uint pri, Archives* archives) +{ + // add watch + if(!vdir->watch) + vdir->watch = 0; + + // add files and subdirs to dir; gather list of all archives + FileCBParams params = { vdir, loc, archives }; + file_enum_dirents(path, file_cb, (uintptr_t)¶ms); + + // loc will now be used for files in Zip archives + loc->pri--; +// loc->path = 0; + + for(SubDirIt it = vdir->subdirs.begin(); it != vdir->subdirs.end(); ++it) + { + VDir* subdir = &it->second; + + char v_subdir_path[PATH_MAX]; + const char* v_subdir_name_c = subdir->v_name.c_str(); + CHECK_ERR(path_append(v_subdir_path, path, v_subdir_name_c)); + + addR(subdir, v_subdir_path, loc, pri, archives); + } + + // for each archive: + // (already sorted due to file_enum_dirents) +/* + { + for(ArchiveIt it = archives->begin(); it != archives->end(); ++it) + { + const Handle ha = *it; + CHECK_ERR(vdir_add_from_archive(vdir, ha, pri, archives)); + } + } +*/ + return 0; +} + + +// parent param not reference to allow passing 0 root node? +static int vdir_add_from_dir(VDir* vdir, const char* path, const uint pri, Archives* archives) +{ + // all loose files in the new dir and its subdirs share this location + Loc* loc = loc_create(0, path, pri+1); + + return addR(vdir, path, loc, pri, archives); +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// mount archives and directories into the VFS +// +/////////////////////////////////////////////////////////////////////////////// + + +// need a list of all mounted dirs or archives so we can vfs_reload at +// any time. it's also nice, but not necessary, to unmount at exit +// (so resources aren't reported as leaked). + +struct Mount +{ + std::string vfs_path; + std::string name; // of OS dir or archive being mounted + uint pri; + + Archives archives; + + Mount(const char* _vfs_path, const char* _name, uint _pri) + : vfs_path(_vfs_path), name(_name), pri(_pri), archives() {} +}; + +typedef std::list Mounts; +typedef Mounts::iterator MountIt; + +static Mounts mounts; + + + +// actually mount the specified entry (either Zip archive or dir). +// split out of vfs_mount because we need to mount without changing the +// mount list, when invalidating (reloading) the VFS. +static int remount(Mount& m) +{ + const char* vfs_path = m.vfs_path.c_str(); + const char* name = m.name.c_str(); + const uint pri = m.pri; + + CHECK_PATH(name); + + VDir* vdir; + CHECK_ERR(tree_lookup(vfs_path, 0, &vdir, LF_CREATE_MISSING_COMPONENTS)); + + int err; + + // add files and subdirectories to the VFS dir + err = vdir_add_from_dir(vdir, name, pri, &m.archives); + // success +// if(err == 0) + return 0; + + const Handle ha = zip_archive_open(name); + err = vdir_add_from_archive(vdir, ha, pri, &m.archives); + if(err == 0) + return 0; + + return ERR_PATH_NOT_FOUND; +} + + +static int unmount(Mount& m) +{ + std::for_each(m.archives.begin(), m.archives.end(), zip_archive_close); + return 0; +} + + +static void unmount_all(void) +{ + std::for_each(mounts.begin(), mounts.end(), unmount); +} + + +int vfs_mount(const char* const vfs_path, const char* const name, const uint pri) +{ + ONCE(atexit(unmount_all)); + + // make sure it's not already mounted, i.e. in mounts + { + for(MountIt it = mounts.begin(); it != mounts.end(); ++it) + if(it->name == name) + { + assert(0 && "vfs_mount: already mounted"); + return -1; + } + } + + mounts.push_back(Mount(vfs_path, name, pri)); + + // actually mount the entry + MountIt it = mounts.end(); + Mount& m = *(--it); + return remount(m); +} + + +// eventually returns the first error that occurred; does not abort. +int vfs_rebuild() +{ + int err = 0; + + vfs_root.clear_tree(); + + loc_free_all(); + + // need error return. manual loop is easier than functor + for_each. + for(MountIt it = mounts.begin(); it != mounts.end(); ++it) + { + int ret = remount(*it); + if(err == 0) + err = ret; + } + + return err; +} + + +int vfs_unmount(const char* name) +{ + for(MountIt it = mounts.begin(); it != mounts.end(); ++it) + // found the corresponding entry + if(it->name == name) + { + int unmount_err = unmount(*it); + + mounts.erase(it); + + int rebuild_err = vfs_rebuild(); + + return (unmount_err < 0)? unmount_err : rebuild_err; + } + + return ERR_PATH_NOT_FOUND; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// +// +/////////////////////////////////////////////////////////////////////////////// + +// OLD // rationale for n-archives per PATH entry: // We need to be able to unmount specific paths (e.g. when switching mods). // Don't want to remount everything (slow), or specify a mod tag when mounting @@ -75,277 +740,37 @@ // the game keeps track of what path(s) it mounted for a mod, // and unmounts those when needed. -struct PATH + +int vfs_realpath(const char* fn, char* full_path) { - struct PATH* next; + Loc* loc; + CHECK_ERR(tree_lookup(fn, &loc)); - // "" if root, otherwise path from root, including DIR_SEP. - // points to space at end of this struct. - char* dir; - - size_t num_archives; - Handle archives[1]; - - // space allocated here for archive Handles + dir string -}; -static PATH* path_list; - - - - - -int vfs_set_root(const char* argv0, const char* root) -{ - if(access(argv0, X_OK) < 0) - return errno; - - char path[PATH_MAX+1]; - path[PATH_MAX] = 0; - if(!realpath(argv0, path)) - return errno; - - // remove executable name - char* fn = strrchr(path, DIR_SEP); - if(!fn) - return -1; - *fn = 0; - - chdir(path); - chdir(root); - - return vfs_mount("."); -} - - -int vfs_mount(const char* path) -{ - size_t i; - - const size_t path_len = strlen(path); - if(path_len > VFS_MAX_PATH) + if(loc->ha <= 0) { - assert(!"vfs_mount_dir: path name is longer than VFS_MAX_PATH"); - return -1; + strncpy(full_path, loc->path.c_str(), PATH_MAX); } - - // security check: path must not contain "..", - // so that it can't access the FS above the VFS root. - if(strstr(path, "..")) - { - assert(0 && "vfs_mount: .. in path string"); - return -1; - } - - // enumerate all archives in - std::vector archives; - DIR* dir = opendir(path); - struct dirent* ent; - while((ent = readdir(dir))) - { - const char* fn = ent->d_name; - struct stat s; - if(stat(fn, &s) < 0) - continue; - // regular file - if(s.st_mode & S_IFREG) - { - char* ext = strrchr(fn, '.'); - // it's a Zip file - add to list - if(ext && !strcmp(ext, ".zip")) - archives.push_back(fn); - } - } - closedir(dir); - - // number of Zip files we'll try to open; - // final # archives may be less, if opening fails on some of them - // note: this many are allocated for the PATH entry, - // but only successfully opened archives are added - const size_t num_zip_files = archives.size(); - - // alloc search path entry (add to front) - const size_t archives_size = num_zip_files*sizeof(Handle); - const size_t dir_size = path_len+1+1; // DIR_SEP and '\0' appended - const size_t tot_size = sizeof(PATH)+archives_size+dir_size; - const size_t entry_size = round_up((long)(tot_size), 32); - PATH* entry = (PATH*)mem_alloc(entry_size, 32); - if(!entry) - return ERR_NO_MEM; - entry->next = path_list; - path_list = entry; - - // copy over path string, and convert '/' to platform specific DIR_SEP. - entry->dir = (char*)&entry->archives[0] + archives_size; - char* d = entry->dir; - for(i = 0; i < path_len; i++) - { - int c = path[i]; - if(c == '/' || c == '\\' || c == ':') - { - assert(c == '/' && "vfs_mount: path string contains platform specific dir separator; use '/'"); - c = DIR_SEP; - } - *d++ = c; - } - - // add trailing DIR_SEP, so we can just append filename when opening. - // exception: if mounting the current directory "." (i.e. the VFS root), - // make it "" - guaranteed to be portable and possibly faster. - if(!strcmp(path, ".")) - entry->dir[0] = '\0'; else { - d[0] = DIR_SEP; - d[1] = '\0'; + const char* archive_fn = h_filename(loc->ha); + if(!archive_fn) + return -1; + strncpy(full_path, archive_fn, PATH_MAX); } - // add archives in alphabetical order - std::sort(archives.begin(), archives.end()); - Handle* p = entry->archives; - for(i = 0; i < num_zip_files; i++) - { - const Handle h = zip_archive_open(archives[i].c_str()); - if(h > 0) - *p++ = h; - } - entry->num_archives = p - entry->archives; // actually valid archives - return 0; } -int vfs_umount(const char* path) -{ - PATH** prev = &path_list; - PATH* entry = path_list; - while(entry) - { - // found - if(!strcmp(entry->dir, path)) - { - // close all archives - for(size_t i = 0; i < entry->num_archives; i++) - zip_archive_close(entry->archives[i]); - - // remove from list - *prev = entry->next; - mem_free(entry); - - return 0; - } - - prev = &entry->next; - entry = entry->next; - } - - // not found - return -1; -} - - -typedef int (*VFS_PATH_CB)(const char* full_rel_path, Handle ha, uintptr_t ctx); - -// call cb, passing the ctx argument, for each mounted path or archive. -// if it returns an error (< 0) other than ERR_FILE_NOT_FOUND or succeeds -// (returns 0), return that value; otherwise, continue calling. -// if it never succeeded, fail with ERR_FILE_NOT_FOUND. -// rationale: we want to fail with the correct error value if something -// actually goes wrong (e.g. file locked). callbacks can abort the sequence -// by returning some error value. -static int vfs_foreach_path(VFS_PATH_CB cb, const char* fn, uintptr_t ctx) -{ - char full_rel_path[PATH_MAX+1]; full_rel_path[PATH_MAX] = 0; - - int err; - - for(PATH* entry = path_list; entry; entry = entry->next) - { - // dir (already includes DIR_SEP) - snprintf(full_rel_path, PATH_MAX, "%s%s", entry->dir, fn); - err = cb(full_rel_path, 0, ctx); - if(err <= 0 && err != ERR_FILE_NOT_FOUND) - return err; - - // archive - for(size_t i = 0; i < entry->num_archives; i++) - { - err = cb(fn, entry->archives[i], ctx); - if(err <= 0 && err != ERR_FILE_NOT_FOUND) - return err; - } - } - - // if we get here, the function always failed with ERR_FILE_NOT_FOUND or - // requested the next path. - return ERR_FILE_NOT_FOUND; -} - - - -static int realpath_cb(const char* path, Handle ha, uintptr_t ctx) -{ - char* full_path = (char*)ctx; - struct stat s; - int err; - - if(!path && !ha) - { - assert(0 && "realpath_cb: called with invalid path and archive handle"); - return 1; - } - - if(ha) - { - err = zip_stat(ha, path, &s); - if(!err) - { - const char* fn = h_filename(ha); - if(!fn) - { - assert(0 && "realpath_cb: h_filename 0, despite successful zip_stat"); - return 1; - } - - strncpy(full_path, fn, PATH_MAX); - return 0; - } - } - else - { - err = stat(path, &s); - if(!err) - { - strncpy(full_path, path, PATH_MAX); - return 0; - } - } - - // failed *stat above - return error code. - return err; -} - -int vfs_realpath(const char* fn, char* full_path) -{ - return vfs_foreach_path(realpath_cb, fn, (uintptr_t)full_path); -} - - -static int stat_cb(const char* path, Handle ha, uintptr_t ctx) -{ - struct stat* s = (struct stat*)ctx; - - if(!ha) - return stat(path, s); - else - return zip_stat(ha, path, s); - - assert(0 && "stat_cb: called with invalid path and archive handle"); - return 1; -} - int vfs_stat(const char* fn, struct stat* s) { - return vfs_foreach_path(stat_cb, fn, (uintptr_t)s); + Loc* loc; + CHECK_ERR(tree_lookup(fn, &loc)); + + if(loc->ha <= 0) + return stat(loc->path.c_str(), s); + else + return zip_stat(loc->ha, fn, s); } @@ -377,7 +802,8 @@ struct VFile ZFile zf; }; - // be aware when adding fields that we're already pushing the size limit. + // be aware when adding fields that we're already pushing the size limit + // (especially in PARANOIA builds, which add a member!) }; H_TYPE_DEFINE(VFile) @@ -429,44 +855,6 @@ static void VFile_dtor(VFile* vf) } -// note: can't use the same callback: must check all paths -// for a plain file version before looking in archives. - - -// called for each mounted path or archive. -static int file_open_cb(const char* path, Handle ha, uintptr_t ctx) -{ - VFile* vf = (VFile*)ctx; - - // not a normal file - ask for next path. - if(ha != 0) - return 1; - - return file_open(path, vf_flags(vf), &vf->f); -} - - -// called for each mounted path or archive. -static int zip_open_cb(const char* path, Handle ha, uintptr_t ctx) -{ - VFile* vf = (VFile*)ctx; - - // file not in archive - ask for next path. - if(ha == 0) - return 1; - - int err = zip_open(ha, path, &vf->zf); - if(!err) - { - vf_flags(vf) |= VF_ZIP; - return 0; // found it - done - } - - // failed to open - return err; -} - - static int VFile_reload(VFile* vf, const char* fn) { @@ -479,31 +867,39 @@ static int VFile_reload(VFile* vf, const char* fn) int err = -1; - // careful! this code is a bit tricky. sorry :P - // only allow plain-file lookup before looking in Zip archives if - // opening for writing (we don't support writing to archive), or - // on dev builds. rationale for disabling in final builds: much faster, - // and a bit more secure (we can protect archives from modification more - // easily than the individual files). + Loc* loc; + CHECK_ERR(tree_lookup(fn, &loc)); - // dev build: always allow plain-file lookup. - // final: only allow if opening for writing. - // - // note: flags have been set by init already -#ifdef FINAL - if(flags & VFS_WRITE) -#endif - err = vfs_foreach_path(file_open_cb, fn, (uintptr_t)vf); + if(loc->ha <= 0) + { + const char* path; + char buf[PATH_MAX+1]; + // (it's in the VFS root dir) + if(loc->path[0] == '\0') + path = fn; + else + { + const char* loc_path = loc->path.c_str(); + CHECK_ERR(path_append(buf, loc_path, fn)); + path = buf; + } - // load from Zip iff we didn't successfully open the plain file, - // and we're not opening for writing. - if(err < 0 && !(flags & VFS_WRITE)) - err = vfs_foreach_path(zip_open_cb, fn, (uintptr_t)vf); + CHECK_ERR(file_open(path, vf_flags(vf), &vf->f)); + } + else + { + if(flags & VFS_WRITE) + { + assert(0 && "requesting write access to file in archive"); + return -1; + } + + CHECK_ERR(zip_open(loc->ha, fn, &vf->zf)); + + flags |= VF_ZIP; + } - // failed - return error code - if(err < 0) - return err; // success flags |= VF_OPEN; @@ -513,39 +909,24 @@ static int VFile_reload(VFile* vf, const char* fn) Handle vfs_open(const char* fn, int flags /* = 0 */) { - // security check: path must not include ".." - // (checking start of string isn't enough) - if(strstr(fn, "..")) - { - assert(0 && "vfs_open: .. in path string"); - return 0; - } - - return h_alloc(H_VFile, fn, 0, flags); + Handle h = h_alloc(H_VFile, fn, 0, flags); // pass file flags to init + +debug_out("vfs_open fn=%s %I64x\n", fn, h); +return h; } inline int vfs_close(Handle& h) { +debug_out("vfs_close %I64x\n", h); return h_free(h, H_VFile); } - -// we return a ZFile handle for files in an archive, instead of making the -// handle a member of File, so we don't open 2 handles per file. there's too -// much internal Zip state to store here - we want to keep it encapsulated. -// note: since handle types are private to each module, each function has to -// give the Zip version a crack at its handle, and only continue if it fails. -// when adding file types, their state should go in File, or else the -// 'is this our handle' checks would get unwieldy. - - - - ssize_t vfs_io(Handle hf, size_t ofs, size_t size, void*& p) { +debug_out("vfs_io ofs=%d size=%d\n", ofs, size); H_DEREF(hf, VFile, vf); // (vfs_open makes sure it's not opened for writing if zip) @@ -553,7 +934,7 @@ ssize_t vfs_io(Handle hf, size_t ofs, size_t size, void*& p) return zip_read(&vf->zf, ofs, size, p); // normal file: - // let file_io alloc the buffer if the caller didn't, + // let file_io alloc the buffer if the caller didn't (i.e. p = 0), // because it knows about alignment / padding requirements return file_io(&vf->f, ofs, size, &p); } @@ -561,6 +942,7 @@ ssize_t vfs_io(Handle hf, size_t ofs, size_t size, void*& p) Handle vfs_load(const char* fn, void*& p, size_t& size) { +debug_out("vfs_load fn=%s\n", fn); p = 0; // vfs_io needs initial 0 value size = 0; // in case open or deref fails @@ -625,9 +1007,7 @@ Handle vfs_map(const char* fn, int flags, void*& p, size_t& size) { Handle hf = vfs_open(fn, flags); H_DEREF(hf, VFile, vf); - int err = file_map(&vf->f, p, size); - if(err < 0) - return err; + CHECK_ERR(file_map(&vf->f, p, size)); MEM_DTOR dtor = 0; uintptr_t ctx = 0; return mem_assign(p, size, 0, dtor, ctx); diff --git a/source/lib/res/vfs.h b/source/lib/res/vfs.h index 3dbe29942f..713f8b5522 100755 --- a/source/lib/res/vfs.h +++ b/source/lib/res/vfs.h @@ -23,13 +23,12 @@ #include "h_mgr.h" #include "posix.h" -#define VFS_MAX_PATH 63 +#define VFS_MAX_PATH 256 // includes trailing '\0' -extern int vfs_set_root(const char* argv0, const char* root); -extern int vfs_mount(const char* path); -extern int vfs_umount(const char* path); +extern int vfs_mount(const char* vfs_path, const char* name, uint pri); +extern int vfs_umount(const char* name); -extern int vfs_stat(const char* fn, struct stat *buffer); +extern int vfs_stat(const char* fn, struct stat*); extern int vfs_realpath(const char* fn, char* realpath); extern Handle vfs_load(const char* fn, void*& p, size_t& size); @@ -39,6 +38,8 @@ extern int vfs_close(Handle& h); extern Handle vfs_map(Handle hf, int flags, void*& p, size_t& size); + + // // async read interface // diff --git a/source/lib/res/zip.cpp b/source/lib/res/zip.cpp index da4e2b183f..e7f4b3dcb7 100755 --- a/source/lib/res/zip.cpp +++ b/source/lib/res/zip.cpp @@ -20,26 +20,559 @@ #include #include -#include "zip.h" -#include "file.h" #include "lib.h" -#include "misc.h" -#include "h_mgr.h" -#include "mem.h" -#include "vfs.h" +#include "res.h" #include #ifdef _MSC_VER #pragma comment(lib, "zlib.lib") #endif +#include + +/////////////////////////////////////////////////////////////////////////////// // -// low-level in-memory inflate routines on top of ZLib +// Zip-specific code +// passes list of files in archive to lookup +// +/////////////////////////////////////////////////////////////////////////////// + + +// convenience container for location / size of file in archive. +struct ZFileLoc +{ + size_t ofs; + size_t csize; // = 0 if not compressed + size_t ucsize; + + // why csize? + // file I/O may be N-buffered, so it's good to know when the raw data + // stops (or else we potentially overshoot by N-1 blocks), + // but not critical, since Zip files are compressed individually. + // (if we read too much, it's ignored by inflate). + // + // we also need a way to check if file compressed (e.g. to fail mmap + // requests if the file is compressed). packing a bit in ofs or + // ucsize is error prone and ugly (1 bit less won't hurt though). + // any other way will mess up the nice 2^n byte size anyway, so + // might as well store csize. +}; + + +// find end of central dir record in file (loaded or mapped). +static int zip_find_ecdr(const void* const file, const size_t size, const u8*& ecdr_) +{ + const char ecdr_id[] = "PK\5\6"; // signature + const size_t ECDR_SIZE = 22; + + if(size < ECDR_SIZE) + { + assert(0 && "zip_find_ecdr: size is way too small"); + return -1; + } + const u8* ecdr = (const u8*)file + size - ECDR_SIZE; + + // early out: check expected case (ECDR at EOF; no file comment) + if(*(u32*)ecdr == *(u32*)&ecdr_id) + goto found_ecdr; + + + // scan the last 66000 bytes of file for ecdr_id signature + // (zip comment <= 65535 bytes, sizeof(ECDR) = 22, add some for safety) + // if the zip file is < 66000 bytes, scan the whole file. + + size_t bytes_left = MIN(66000, size); + ecdr = (const u8*)file + size - bytes_left; + + while(bytes_left >= 4) + { + if(*(u32*)ecdr == *(u32*)&ecdr_id) + goto found_ecdr; + + // check next 4 bytes (non aligned!!) + ecdr++; + bytes_left--; + } + + // reached EOF and still haven't found the ECDR identifier. + ecdr_ = 0; + return -1; + +found_ecdr: + ecdr_ = ecdr; + return 0; +} + + +// make sure the LFH fields match those passed (from the CDFH). +// only used in PARANOIA builds - costs time when opening archives. +static int zip_verify_lfh(const void* const file, const size_t lfh_ofs, const size_t file_ofs) +{ + const char lfh_id[] = "PK\3\4"; // signature + const size_t LFH_SIZE = 30; + + assert(lfh_ofs < file_ofs); // header comes before file + + const u8* lfh = (const u8*)file + lfh_ofs; + + if(*(u32*)lfh != *(u32*)lfh_id) + { + assert(0 && "LFH corrupt! (signature doesn't match)"); + return -1; + } + + const u16 lfh_fn_len = read_le16(lfh+26); + const u16 lfh_e_len = read_le16(lfh+28); + + const size_t lfh_file_ofs = lfh_ofs + LFH_SIZE + lfh_fn_len + lfh_e_len; + + if(file_ofs != lfh_file_ofs) + { + assert(0 && "warning: CDFH and LFH data differ! normal builds will"\ + "return incorrect file offsets. check Zip file!"); + return -1; + } + + return 0; +} + + +// extract information from the current Central Directory File Header; +// advance cdfh to point to the next; return -1 on unrecoverable error, +// 0 on success (<==> output fields are valid), > 0 if file is invalid. +static int zip_read_cdfh(const u8*& cdfh, const char*& fn, size_t& fn_len, ZFileLoc* const loc) +{ + const char cdfh_id[] = "PK\1\2"; // signature + const size_t CDFH_SIZE = 46; + const size_t LFH_SIZE = 30; + + if(*(u32*)cdfh != *(u32*)cdfh_id) + { + assert(0 && "CDFH corrupt! (signature doesn't match)"); + goto skip_file; + } + + const u8 method = cdfh[10]; + const u32 csize_ = read_le32(cdfh+20); + const u32 ucsize_ = read_le32(cdfh+24); + const u16 fn_len_ = read_le16(cdfh+28); + const u16 e_len = read_le16(cdfh+30); + const u16 c_len = read_le16(cdfh+32); + const u32 lfh_ofs = read_le32(cdfh+42); + + const char* fn_ = (const char*)cdfh+CDFH_SIZE; + // not 0-terminated! + + // compression method: neither deflated nor stored + if(method & ~8) + { + assert(0 && "warning: unknown compression method"); + goto skip_file; + } + + fn = fn_; + fn_len = fn_len_; + + loc->ofs = lfh_ofs + LFH_SIZE + fn_len_ + e_len; + loc->csize = csize_; + loc->ucsize = ucsize_; + + // performance issue: want to avoid seeking between LFHs and central dir. + // would be safer to calculate file offset from the LFH, since its + // filename / extra data fields may differ WRT the CDFH version. + // we don't bother checking for this in normal builds: if they were + // to be different, we'd notice: headers of files would end up corrupted. +#ifdef PARANOIA + if(!zip_verify_lfh(file, lfh_ofs, file_ofs)) + goto skip_file; +#endif + + cdfh += CDFH_SIZE + fn_len + e_len + c_len; + return 0; + +// file was invalid somehow; try to seek forward to the next CDFH +skip_file: + // scan for next CDFH (look for signature) + for(int i = 0; i < 256; i++) + { + if(*(u32*)cdfh == *(u32*)cdfh_id) + goto found_next_cdfh; + cdfh++; + } + + // next CDFH not found. caller must abort + return -1; + +// file was skipped, but we have the next CDFH +found_next_cdfh: + return 1; +} + + +// fn (filename) is not necessarily 0-terminated! +// loc is only valid during the callback! must be copied or saved. +typedef int(*ZipCdfhCB)(const uintptr_t user, const i32 idx, const char* fn, const size_t fn_len, const ZFileLoc* const loc); + +// go through central directory of the Zip file (loaded or mapped into memory); +// call back for each file. +// +// HACK: call back with negative index the first time; its abs. value is +// the number of files in the archive. lookup needs to know this so it can +// allocate memory. having lookup_init call zip_get_num_files and then +// zip_enum_files would require passing around a ZipInfo struct, +// or searching for the ECDR twice - both ways aren't nice. +static int zip_enum_files(const void* const file, const size_t size, const ZipCdfhCB cb, const uintptr_t user) +{ + int err; + ZFileLoc loc; + + // find End of Central Directory Record + const u8* ecdr; + err = zip_find_ecdr(file, size, ecdr); + if(err < 0) + return err; + + // call back with number of files in archive + const i32 num_files = read_le16(ecdr+10); + // .. callback expects -num_files < 0. + // if it's 0, the callback would treat it as an index => crash. + if(!num_files) + return -1; + err = cb(user, -num_files, 0, 0, 0); + if(err < 0) + return err; + + const u32 cd_ofs = read_le32(ecdr+16); + const u8* cdfh = (const u8*)file + cd_ofs; + // pointer is advanced in zip_read_cdfh + + for(i32 idx = 0; idx < num_files; idx++) + { + const char* fn; + size_t fn_len; + err = zip_read_cdfh(cdfh, fn, fn_len, &loc); + // .. non-recoverable error reading dir + if(err < 0) + return err; + // .. file was skipped (e.g. invalid compression format) + // call back with 0 params; don't skip the file, so that + // all indices are valid. + if(err > 0) + cb(user, idx, 0, 0, 0); + // .. file valid. + else + cb(user, idx, fn, fn_len, &loc); + } + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// file lookup +// per archive: find file information (e.g. location, size), given filename. +// +/////////////////////////////////////////////////////////////////////////////// + + +// file lookup: store array of files in archive (ZEnt); lookup via linear +// search for hash of filename. +// optimization: store index of last file opened; check the one after +// that first, and only then search the whole array. this is a big +// win if files are opened sequentially (they should be so ordered +// in the archive anyway, to reduce seeks) // +// rationale: added index because exposing index is bad (say we change lookup data struct) +// much cleaner to only export handle -uintptr_t zip_init_ctx() +// don't bother making a tree structure: first, it's a bit of work +// (Zip files store paths as part of the file name - there's no extra +// directory information); second, the VFS file location DB stores +// handle and file index per file, making its lookup constant-time. + +struct ZEnt +{ + const char* fn; // currently allocated individually + + ZFileLoc loc; +}; + + +typedef std::map LookupIdx; +typedef LookupIdx::iterator LookupIdxIt; + +// per-archive information for mapping filename -> file info +struct LookupInfo +{ + ZEnt* ents; + FnHash* fn_hashes; + // split out of ZEnt for more efficient search + // (=> ZEnt is power-of-2, back-to-back fn_hashes) + // + // currently both share one memory allocation; only mem_free() ents! + + i32 num_files; + + i32 next_file; + // for last-file-opened optimization. + // we store index of next file instead of the last one opened + // to avoid trouble on first call (don't want last == -1). + + // don't know size of std::map, and this is used in a control block + // allocate dynamically to save size. + LookupIdx* idx; +}; + + +// add file to the lookup data structure. +// callback for zip_enum_files, in order (0 <= idx < num_files). +// +// fn (filename) is not necessarily 0-terminated! +// loc is only valid during the callback! must be copied or saved. +static int lookup_add_file_cb(const uintptr_t user, const i32 idx, const char* const fn, const size_t fn_len, const ZFileLoc* const loc) +{ + LookupInfo* li = (LookupInfo*)user; + + // HACK: on first call, idx is negative and tells us how many + // files are in the archive (so we can allocate memory). + // see zip_enum_files for why it's done this way. + if(idx < 0) + { + const i32 num_files = -idx; + + // both arrays in one allocation (more efficient) + const size_t ents_size = (num_files * sizeof(ZEnt)); + const size_t array_size = ents_size + (num_files * sizeof(FnHash)); + void* p = mem_alloc(array_size, 4*KB); + if(!p) + return ERR_NO_MEM; + + li->num_files = num_files; + li->ents = (ZEnt*)p; + li->fn_hashes = (FnHash*)((char*)p + ents_size); + return 0; + } + + ZEnt* ent = li->ents + idx; + + FnHash fn_hash = fnv_hash(fn); + + (*li->idx)[fn_hash] = idx; + li->fn_hashes[idx] = fn_hash; + + // valid file - write out its info. + if(loc) + { + ent->fn = (const char*)malloc(fn_len+1); + if(!ent->fn) + return ERR_NO_MEM; + strcpy((char*)ent->fn, fn); + + ent->loc = *loc; + } + // invalid file / error reading its dir entry: zero its file info. + // (don't skip it to make sure all indices are valid). + else + memset(ent, 0, sizeof(ZEnt)); + + return 0; +} + + +// initialize lookup data structure for Zip archive +static int lookup_init(LookupInfo* const li, const void* const file, const size_t size) +{ + // all other fields are initialized in lookup_add_file_cb + li->next_file = 0; + + li->idx = new LookupIdx; + + return zip_enum_files(file, size, lookup_add_file_cb, (uintptr_t)li); +} + + +// free lookup data structure. no use-after-free checking. +static int lookup_free(LookupInfo* const li) +{ + for(i32 i = 0; i < li->num_files; i++) + { + free((void*)li->ents[i].fn); + li->ents[i].fn = 0; + } + + li->num_files = 0; + + delete li->idx; + + return mem_free(li->ents); +} + + +// return key of file for use in lookup_get_file_info. +static int lookup_file(LookupInfo* const li, const char* const fn, i32& idx) +{ + const FnHash fn_hash = fnv_hash(fn); + const FnHash* fn_hashes = li->fn_hashes; + + const i32 num_files = li->num_files; + i32 i = li->next_file; + + // .. next_file marker is at the end of the array, or + // its entry isn't what we're looking for: consult index + if(i >= num_files || fn_hashes[i] != fn_hash) + { + LookupIdxIt it = li->idx->find(fn_hash); + // not found + if(it == li->idx->end()) + return ERR_FILE_NOT_FOUND; + + i = it->second; + } + + li->next_file = i+1; + idx = i; + return 0; +} + + +// return file information, given file key (from lookup_file). +static int lookup_get_file_info(LookupInfo* const li, const i32 idx, const char*& fn, ZFileLoc* const loc) +{ + if(idx < 0 || idx >= li->num_files-1) + { + assert(0 && "lookup_get_file_info: index out of bounds"); + return -1; + } + + const ZEnt* const ent = &li->ents[idx]; + fn = ent->fn; + *loc = ent->loc; + return 0; +} + + +typedef ZipFileCB LookupFileCB; + +static int lookup_enum_files(LookupInfo* const li, LookupFileCB cb, uintptr_t user) +{ + int err; + + const ZEnt* ent = li->ents; + for(i32 i = 0; i < li->num_files; i++, ent++) + { + err = cb(ent->fn, LOC_ZIP, (ssize_t)ent->loc.ucsize, user); + if(err < 0) + return 0; + } + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// container with handle for archive info +// owns archive file and its lookup mechanism. +// +/////////////////////////////////////////////////////////////////////////////// + + +struct ZArchive +{ + File f; + + LookupInfo li; + + // hack: on first open, file is invalid (fn_hash isn't set), + // and file validate in file_close fails. + // workaround: only close if open. + bool is_open; +}; + +H_TYPE_DEFINE(ZArchive) + + +static void ZArchive_init(ZArchive* za, va_list args) +{ +} + + +static void ZArchive_dtor(ZArchive* za) +{ + if(za->is_open) + { + file_close(&za->f); + lookup_free(&za->li); + + za->is_open = false; + } +} + + +static int ZArchive_reload(ZArchive* za, const char* fn) +{ + int err; + + err = file_open(fn, 0, &za->f); + if(err < 0) + return err; + + void* file; + size_t size; + err = file_map(&za->f, file, size); + if(err < 0) + goto exit_close; + + err = lookup_init(&za->li, file, size); + if(err < 0) + goto exit_unmap_close; + + za->is_open = true; + return 0; + +exit_unmap_close: + file_unmap(&za->f); +exit_close: + file_close(&za->f); + return err; +} + + +// open and return a handle to the zip archive indicated by +Handle zip_archive_open(const char* const fn) +{ + return h_alloc(H_ZArchive, fn); +} + + +// close the archive and set ha to 0 +int zip_archive_close(Handle& ha) +{ + return h_free(ha, H_ZArchive); +} + + +// would be nice to pass along a key (allowing for O(1) lookup in archive), +// but then the callback is no longer compatible to file / vfs enum files. +int zip_enum_files(const Handle ha, const ZipFileCB cb, const uintptr_t user) +{ + H_DEREF(ha, ZArchive, za); + + return lookup_enum_files(&za->li, cb, user); +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// in-memory inflate routines (zlib wrapper) +// +/////////////////////////////////////////////////////////////////////////////// + + +uintptr_t inf_init_ctx() { // allocate ZLib stream z_stream* stream = (z_stream*)mem_alloc(round_up(sizeof(z_stream), 32), 32, MEM_ZERO); @@ -51,7 +584,7 @@ uintptr_t zip_init_ctx() } -int zip_start_read(uintptr_t ctx, void* out, size_t out_size) +int inf_start_read(uintptr_t ctx, void* out, size_t out_size) { if(!ctx) return ERR_INVALID_PARAM; @@ -62,13 +595,13 @@ int zip_start_read(uintptr_t ctx, void* out, size_t out_size) assert(0 && "zip_start_read: ctx already in use!"); return -1; } - stream->next_out = (Bytef*)out; + stream->next_out = (Byte*)out; stream->avail_out = (uInt)out_size; return 0; } -ssize_t zip_inflate(uintptr_t ctx, void* in, size_t in_size) +ssize_t inf_inflate(uintptr_t ctx, void* in, size_t in_size) { if(!ctx) return ERR_INVALID_PARAM; @@ -76,6 +609,9 @@ ssize_t zip_inflate(uintptr_t ctx, void* in, size_t in_size) size_t prev_avail_out = stream->avail_out; + stream->avail_in = (uInt)in_size; + stream->next_in = (Byte*)in; + int err = inflate(stream, Z_SYNC_FLUSH); // check+return how much actual data was read @@ -92,7 +628,7 @@ ssize_t zip_inflate(uintptr_t ctx, void* in, size_t in_size) } -int zip_finish_read(uintptr_t ctx) +int inf_finish_read(uintptr_t ctx) { if(!ctx) return ERR_INVALID_PARAM; @@ -112,7 +648,7 @@ int zip_finish_read(uintptr_t ctx) } -int zip_free_ctx(uintptr_t ctx) +int inf_free_ctx(uintptr_t ctx) { if(!ctx) return ERR_INVALID_PARAM; @@ -126,212 +662,12 @@ int zip_free_ctx(uintptr_t ctx) } -// -// Zip archive -// - - -static const char ecdr_id[] = "PK\5\6"; // End of Central Directory Header identifier -static const char cdfh_id[] = "PK\1\2"; // Central File Header identifier -static const char lfh_id[] = "PK\3\4"; // Local File Header identifier - -struct ZEnt -{ - size_t ofs; - size_t csize; // 0 if not compressed - size_t ucsize; - - // why csize? - // file I/O may be N-buffered, so it's good to know when the raw data - // stops (or else we potentially overshoot by N-1 blocks), - // but not critical, since Zip files are stored individually. - // - // we also need a way to check if file compressed (e.g. to fail mmap - // requests if the file is compressed). packing a bit in ofs or - // ucsize is error prone and ugly (1 bit less won't hurt though). - // any other way will mess up the nice 8 byte size anyway, so might - // as well store csize. - // - // don't worry too much about non-power-of-two size, will probably - // change to global FS tree instead of linear lookup later anyway. -}; - -struct ZArchive -{ - File f; - - // file lookup - u16 num_files; - u16 last_file; // index of last file we found (speed up lookups of sequential files) - u32* fn_hashs; // split for more efficient search - ZEnt* ents; -}; - -H_TYPE_DEFINE(ZArchive) - - -static void ZArchive_init(ZArchive* za, va_list args) -{ -} - - -static void ZArchive_dtor(ZArchive* za) -{ - file_close(&za->f); - mem_free(za->fn_hashs); // both fn_hashs[] and files[] -} - - -static int ZArchive_reload(ZArchive* za, const char* fn) -{ - const u8* ecdr; // declare here to avoid goto scope problems - - int err = file_open(fn, 0, &za->f); - if(err < 0) - return err; - - void* p; - size_t size; - err = file_map(&za->f, p, size); - if(err < 0) - return err; - -{ - // find end of central dir record - // by scanning last 66000 bytes of file for ecdr_id magic - // (zip comment <= 65535 bytes, sizeof(ECDR) = 22, add some for safety) - // if the zip file is < 66000 bytes, scan the whole file - - size_t bytes_left = 66000; // min(66k, size) - avoid stupid warning - if(bytes_left > size) - bytes_left = size; - - ecdr = (const u8*)p + size - 22; - if(*(u32*)ecdr == *(u32*)&ecdr_id) - goto found_ecdr; - - ecdr = (const u8*)p + size - bytes_left; - - while(bytes_left-3 > 0) - { - if(*(u32*)ecdr == *(u32*)&ecdr_id) - goto found_ecdr; - - // check next 4 bytes (non aligned!!) - ecdr++; - bytes_left--; - } - - // reached EOF and still haven't found the ECDR identifier -} - -fail: - file_unmap(&za->f); - file_close(&za->f); - return -1; - -found_ecdr: -{ - // read ECDR - const u16 num_files = read_le16(ecdr+10); - const u32 cd_ofs = read_le32(ecdr+16); - - // memory for fn_hash and Ent arrays - void* file_list_mem = mem_alloc(num_files * (sizeof(u32) + sizeof(ZEnt)), 4*KB); - if(!file_list_mem) - goto fail; - u32* fn_hashs = (u32*)file_list_mem; - ZEnt* ents = (ZEnt*)((u8*)file_list_mem + num_files*sizeof(u32)); - - // cache file list for faster lookups - // currently linear search, comparing filename hash. - // TODO: if too slow, use hash table. - const u8* cdfh = (const u8*)p+cd_ofs; - u32* hs = fn_hashs; - ZEnt* ent = ents; - u16 i; - for(i = 0; i < num_files; i++) - { - // read CDFH - if(*(u32*)cdfh != *(u32*)cdfh_id) - continue; - const u32 csize = read_le32(cdfh+20); - const u32 ucsize = read_le32(cdfh+24); - const u16 fn_len = read_le16(cdfh+28); - const u16 e_len = read_le16(cdfh+30); - const u16 c_len = read_le16(cdfh+32); - const u32 lfh_ofs = read_le32(cdfh+42); - const u8 method = cdfh[10]; - - if(method & ~8) // neither deflated nor stored - continue; - - // read LFH - const u8* const lfh = (const u8*)p + lfh_ofs; - if(*(u32*)lfh != *(u32*)lfh_id) - continue; - const u16 lfh_fn_len = read_le16(lfh+26); - const u16 lfh_e_len = read_le16(lfh+28); - const char* lfh_fn = (const char*)lfh+30; - - *hs++ = fnv_hash(lfh_fn); - ent->ofs = lfh_ofs + 30 + lfh_fn_len + lfh_e_len; - ent->csize = csize; - ent->ucsize = ucsize; - ent++; - - (uintptr_t&)cdfh += 46 + fn_len + e_len + c_len; - } - - za->num_files = i; - za->last_file = 0; - za->fn_hashs = fn_hashs; - za->ents = ents; -} // scope - - return 0; -} - - -// open and return a handle to the zip archive indicated by -Handle zip_archive_open(const char* const fn) -{ - return h_alloc(H_ZArchive, fn); -} - -// close the archive and set ha to 0 -int zip_archive_close(Handle& ha) -{ - return h_free(ha, H_ZArchive); -} - - +/////////////////////////////////////////////////////////////////////////////// // // file from Zip archive +// on top of inflate and lookup // - - -static int lookup(Handle ha, const char* fn, const ZEnt*& ent) -{ - H_DEREF(ha, ZArchive, za); - - // find its File descriptor - const u32 fn_hash = fnv_hash(fn); - u16 i = za->last_file+1; - if(i >= za->num_files || za->fn_hashs[i] != fn_hash) - { - for(i = 0; i < za->num_files; i++) - if(za->fn_hashs[i] == fn_hash) - break; - if(i == za->num_files) - return ERR_FILE_NOT_FOUND; - - za->last_file = i; - } - - ent = &za->ents[i]; - return 0; -} +/////////////////////////////////////////////////////////////////////////////// // marker for ZFile struct, to make sure it's valid @@ -381,30 +717,37 @@ do\ while(0); - -int zip_open(const Handle ha, const char* fn, ZFile* zf) +int zip_open_idx(const Handle ha, const i32 idx, ZFile* zf) { memset(zf, 0, sizeof(ZFile)); if(!zf) goto invalid_zf; - // jump to CHECK_ZFILE post-check, which will handle this. + // jump to CHECK_ZFILE post-check, which will handle this. { - const ZEnt* ze; - int err = lookup(ha, fn, ze); + H_DEREF(ha, ZArchive, za); + LookupInfo* li = (LookupInfo*)&za->li; + + const char* fn; + ZFileLoc loc; + // don't want ZFile to contain a ZFileLoc struct - + // its ucsize member must be 'loose' for compatibility with File. + // => need to copy ZFileLoc fields into ZFile. + int err = lookup_get_file_info(li, idx, fn, &loc); if(err < 0) return err; #ifdef PARANOIA - zf->magic = ZFILE_MAGIC; + zf->magic = ZFILE_MAGIC; #endif - zf->ofs = ze->ofs; - zf->csize = ze->csize; - zf->ucsize = ze->ucsize; + zf->ucsize = loc.ucsize; + zf->ofs = loc.ofs; + zf->csize = loc.csize; + zf->ha = ha; - zf->read_ctx = zip_init_ctx(); + zf->read_ctx = inf_init_ctx(); } invalid_zf: @@ -414,28 +757,51 @@ invalid_zf: } +int zip_open(const Handle ha, const char* fn, ZFile* zf) +{ + H_DEREF(ha, ZArchive, za); + LookupInfo* li = (LookupInfo*)&za->li; + + i32 idx; + int err = lookup_file(li, fn, idx); + if(err < 0) + return err; + + return zip_open_idx(ha, idx, zf); +} + + int zip_close(ZFile* zf) { CHECK_ZFILE(zf) - // remaining fields don't need to be freed/cleared - return zip_free_ctx(zf->read_ctx); + // remaining ZFile fields don't need to be freed/cleared + return inf_free_ctx(zf->read_ctx); } // return file information for in archive int zip_stat(Handle ha, const char* fn, struct stat* s) { - const ZEnt* ze; - int err = lookup(ha, fn, ze); + H_DEREF(ha, ZArchive, za); + LookupInfo* li = &za->li; + + i32 idx; + int err = lookup_file(li, fn, idx); if(err < 0) return err; - s->st_size = (off_t)ze->ucsize; + const char* fn2; // unused + ZFileLoc loc; + lookup_get_file_info(li, idx, fn2, &loc); + // can't fail - returned valid index above + + s->st_size = (off_t)loc.ucsize; return 0; } + // convenience function, allows implementation change in ZFile. // note that size == ucsize isn't foolproof, and adding a flag to // ofs or size is ugly and error-prone. @@ -454,19 +820,11 @@ ssize_t zip_read(ZFile* zf, size_t raw_ofs, size_t size, void*& p) ssize_t err = -1; ssize_t raw_bytes_read; - + ZArchive* za = H_USER_DATA(zf->ha, ZArchive); if(!za) return ERR_INVALID_HANDLE; - void* our_buf = 0; // buffer we allocated (if necessary) - if(!p) - { - p = our_buf = mem_alloc(size); - if(!p) - return ERR_NO_MEM; - } - const size_t ofs = zf->ofs + raw_ofs; // not compressed - just pass it on to file_io @@ -484,10 +842,18 @@ ssize_t zip_read(ZFile* zf, size_t raw_ofs, size_t size, void*& p) if(raw_ofs != zf->last_raw_ofs) { assert(0 && "zip_read: compressed read offset is non-continuous"); - goto fail; + return -1; } - err = (ssize_t)zip_start_read(zf->read_ctx, p, size); + void* our_buf = 0; // buffer we allocated (if necessary) + if(!p) + { + p = our_buf = mem_alloc(size); + if(!p) + return ERR_NO_MEM; + } + + err = (ssize_t)inf_start_read(zf->read_ctx, p, size); if(err < 0) { fail: @@ -504,9 +870,9 @@ fail: // zip_inflate, until all compressed data has been read, or it indicates // the desired output amount has been reached. const size_t raw_size = zf->csize; - raw_bytes_read = file_io(&za->f, ofs, raw_size, (void**)0, zip_inflate, zf->read_ctx); + raw_bytes_read = file_io(&za->f, ofs, raw_size, (void**)0, inf_inflate, zf->read_ctx); - err = zip_finish_read(zf->read_ctx); + err = inf_finish_read(zf->read_ctx); if(err < 0) goto fail; diff --git a/source/lib/res/zip.h b/source/lib/res/zip.h index aa893ca373..ec44fb9792 100755 --- a/source/lib/res/zip.h +++ b/source/lib/res/zip.h @@ -25,18 +25,18 @@ // -// low-level in-memory inflate routines +// in-memory inflate routines (zlib wrapper) // -extern uintptr_t zip_init_ctx(); +extern uintptr_t inf_init_ctx(); -extern int zip_start_read(uintptr_t ctx, void* out, size_t out_size); +extern int inf_start_read(uintptr_t ctx, void* out, size_t out_size); -extern ssize_t zip_inflate(uintptr_t ctx, void* in, size_t in_size); +extern ssize_t inf_inflate(uintptr_t ctx, void* in, size_t in_size); -extern int zip_finish_read(uintptr_t ctx); +extern int inf_finish_read(uintptr_t ctx); -extern int zip_free_ctx(uintptr_t ctx); +extern int inf_free_ctx(uintptr_t ctx); // @@ -50,6 +50,15 @@ extern Handle zip_archive_open(const char* fn); extern int zip_archive_close(Handle& ha); +// keep in sync with file.cpp and vfs.cpp *_CB_FLAGS +enum ZIP_CB_FLAGS +{ + LOC_ZIP = BIT(1) +}; + +typedef int(*ZipFileCB)(const char* const fn, const uint flags, const ssize_t size, const uintptr_t user); +extern int zip_enum_files(const Handle ha, const ZipFileCB cb, const uintptr_t user); + // // file // diff --git a/source/lib/sdl.h b/source/lib/sdl.h index 67c2f274f8..21b785fec3 100755 --- a/source/lib/sdl.h +++ b/source/lib/sdl.h @@ -1,6 +1,6 @@ #if defined(_WIN32) && !defined(NO_WSDL) -#include "sysdep/win/wsdl.h" +# include "sysdep/win/wsdl.h" #else -#include -#include -#endif \ No newline at end of file +# include +# include +#endif diff --git a/source/lib/sysdep/ia32.cpp b/source/lib/sysdep/ia32.cpp index 5084abde4a..c08cb4168e 100755 --- a/source/lib/sysdep/ia32.cpp +++ b/source/lib/sysdep/ia32.cpp @@ -293,7 +293,7 @@ again: } std::sort(samples.begin(), samples.end()); - double median = samples[num_samples/2]; +// double median = samples[num_samples/2]; // median filter (remove upper and lower 25% and average the rest) double sum = 0.0; diff --git a/source/lib/sysdep/sysdep.cpp b/source/lib/sysdep/sysdep.cpp index dc4eda3a2b..30b86f8bb9 100755 --- a/source/lib/sysdep/sysdep.cpp +++ b/source/lib/sysdep/sysdep.cpp @@ -10,18 +10,26 @@ #ifndef _WIN32 // portable output routines (win.cpp overrides these) -void display_msg(const wchar_t* caption, const wchar_t* msg) + +void display_msg(const char* caption, const char* msg) +{ + fprintf(stderr, "%s: %s\n", caption, msg); +} + +void wdisplay_msg(const wchar_t* caption, const wchar_t* msg) { fwprintf(stderr, L"%ws: %ws\n", caption, msg); } + void debug_out(const char* fmt, ...) { va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); + fflush(stdout); } diff --git a/source/lib/sysdep/sysdep.h b/source/lib/sysdep/sysdep.h index 46434ade39..b20981ed87 100755 --- a/source/lib/sysdep/sysdep.h +++ b/source/lib/sysdep/sysdep.h @@ -9,7 +9,8 @@ extern "C" { #endif -extern void display_msg(const wchar_t* caption, const wchar_t* msg); +extern void display_msg(const char* caption, const char* msg); +extern void wdisplay_msg(const wchar_t* caption, const wchar_t* msg); extern void debug_out(const char* fmt, ...); extern void check_heap(); diff --git a/source/lib/sysdep/win/hrt.cpp b/source/lib/sysdep/win/hrt.cpp index 4dcc545994..14a084c3d6 100755 --- a/source/lib/sysdep/win/hrt.cpp +++ b/source/lib/sysdep/win/hrt.cpp @@ -28,21 +28,24 @@ #include "detect.h" #include "win_internal.h" -#include // not included by win due to WIN32_LEAN_AND_MEAN +#include + // not included by win_internal due to its WIN32_LEAN_AND_MEAN define #ifdef _MSC_VER #pragma comment(lib, "winmm.lib") #endif -// ticks per second; average of last few values measured in calibrate +// ticks per second; average of last few values measured in calibrate() static double hrt_freq = -1.0; -// used to start the hrt tick values near 0 +// used to rebase the hrt tick values to 0 static i64 hrt_origin = 0; static HRTImpl hrt_impl = HRT_NONE; -static HRTOverride overrides[3] = { HRT_DEFAULT, HRT_DEFAULT, HRT_DEFAULT }; +static HRTOverride overrides[HRT_NUM_IMPLS]; // HRTImpl enums as index + // HACK: no init needed - static data is zeroed (= HRT_DEFAULT) +cassert(HRT_DEFAULT == 0); static i64 hrt_nominal_freq = -1; @@ -160,10 +163,14 @@ static void choose_impl() // // TGT // - hrt_impl = HRT_TGT; - hrt_nominal_freq = 1000; - return; + if(1) + { + hrt_impl = HRT_TGT; + hrt_nominal_freq = 1000; + return; + } + // no warning here - doesn't inspire confidence in VC dead code removal. assert(0 && "hrt_choose_impl: no safe timer found!"); hrt_impl = HRT_NONE; hrt_nominal_freq = -1; @@ -174,6 +181,7 @@ static void choose_impl() // return ticks since first call. lock must be held. // // split to allow calling from reset_impl_lk without recursive locking. +// (not a problem, but avoids a BoundsChecker warning) static i64 ticks_lk() { i64 t; @@ -231,8 +239,8 @@ static i64 ticks_lk() // choose a HRT implementation. lock must be held. // -// don't want to saddle timer with the problem of initializing us -// on first call - it wouldn't otherwise need to be thread-safe. +// don't want to saddle timer module with the problem of initializing +// us on first call - it wouldn't otherwise need to be thread-safe. static void reset_impl_lk() { HRTImpl old_impl = hrt_impl; @@ -414,14 +422,13 @@ lock(); hrt_cal_time = hrt_cur; ms_cal_time = ms_cur; - // - // when we wake up, we don't know if timer has been updated yet. - // they may be off by 1 tick - try to compensate. - // + // we're called from a WinMM event, so the timer has just been updated. + // no need to determine tick / compensate. - double dt = ms_ds; // actual elapsed time since last calibration - double hrt_err = ms_ds - hrt_ds; - double hrt_abs_err = fabs(hrt_err), hrt_rel_err = hrt_abs_err / ms_ds; +// double dt = ms_ds; // actual elapsed time since last calibration +// double hrt_err = ms_ds - hrt_ds; +// double hrt_abs_err = fabs(hrt_err); +// double hrt_rel_err = hrt_abs_err / ms_ds; double hrt_est_freq = hrt_ds / ms_ds; // only add to buffer if within 10% of nominal @@ -457,6 +464,12 @@ static UINT mm_event; // keep calibrate() portable, don't need args anyway static void CALLBACK trampoline(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) { + UNUSED(uTimerID); + UNUSED(uMsg); + UNUSED(dwUser); + UNUSED(dw1); + UNUSED(dw2); + calibrate(); } @@ -475,11 +488,11 @@ static void init_calibration_thread() GetSystemTimeAdjustment(&adj, &incr, &adj_disabled); DWORD res = adj / 10000; mm_event = timeSetEvent(1000, res, trampoline, 0, TIME_PERIODIC); - atexit2(timeKillEvent, mm_event); + atexit2(timeKillEvent, mm_event, CC_STDCALL_1); #else - // TODO: port thread. no big deal, and the timer works without. + // TODO: port thread. it's no big deal, but the timer should work without. #endif } diff --git a/source/lib/sysdep/win/hrt.h b/source/lib/sysdep/win/hrt.h index 05bd52dd10..78db0ead7c 100755 --- a/source/lib/sysdep/win/hrt.h +++ b/source/lib/sysdep/win/hrt.h @@ -46,7 +46,9 @@ enum HRTOverride { // allow use of this implementation if available, // and we can work around its problems - HRT_DEFAULT, + // + // HACK: give it value 0 for easier static data initialization + HRT_DEFAULT = 0, // override our 'safe to use' recommendation // set by hrt_override_impl (via command line arg or console function) diff --git a/source/lib/sysdep/win/waio.cpp b/source/lib/sysdep/win/waio.cpp index 275ee020e3..2400a5838c 100755 --- a/source/lib/sysdep/win/waio.cpp +++ b/source/lib/sysdep/win/waio.cpp @@ -40,41 +40,33 @@ // // note: current Windows lowio handle limit is 2k -class AioHandles -{ -public: - AioHandles() : hs(0), size(0) {} - ~AioHandles(); +static HANDLE* aio_hs; + // array; expanded when needed in aio_h_set - HANDLE get(int fd); - int set(int fd, HANDLE h); - -private: - HANDLE* hs; - uint size; -}; +static int aio_hs_size; -AioHandles::~AioHandles() +static void aio_h_cleanup() { win_lock(WAIO_CS); - for(int i = 0; (unsigned)i < size; i++) - if(hs[i] != INVALID_HANDLE_VALUE) + for(int i = 0; i < aio_hs_size; i++) + if(aio_hs[i] != INVALID_HANDLE_VALUE) { - CloseHandle(hs[i]); - hs[i] = INVALID_HANDLE_VALUE; + CloseHandle(aio_hs[i]); + aio_hs[i] = INVALID_HANDLE_VALUE; } - free(hs); - hs = 0; - size = 0; + free(aio_hs); + aio_hs = 0; + + aio_hs_size = 0; win_unlock(WAIO_CS); } -bool is_valid_file_handle(HANDLE h) +static bool is_valid_file_handle(HANDLE h) { SetLastError(0); bool valid = (GetFileSize(h, 0) != INVALID_FILE_SIZE); @@ -82,20 +74,24 @@ bool is_valid_file_handle(HANDLE h) return valid; } + // get async capable handle to file -HANDLE AioHandles::get(int fd) +HANDLE aio_h_get(int fd) { HANDLE h = INVALID_HANDLE_VALUE; win_lock(WAIO_CS); - if((unsigned)fd < size) - h = hs[fd]; + if(0 <= fd && fd < aio_hs_size) + h = aio_hs[fd]; else + { assert(0); + h = INVALID_HANDLE_VALUE; + } if(!is_valid_file_handle(h)) - return INVALID_HANDLE_VALUE; + h = INVALID_HANDLE_VALUE; win_unlock(WAIO_CS); @@ -103,29 +99,34 @@ HANDLE AioHandles::get(int fd) } -int AioHandles::set(int fd, HANDLE h) +int aio_h_set(int fd, HANDLE h) { win_lock(WAIO_CS); + WIN_ONCE(atexit2(aio_h_cleanup)) + + if(fd < 0) + goto fail; + // grow hs array to at least fd+1 entries - if((unsigned)fd >= size) + if(fd >= aio_hs_size) { uint size2 = (uint)round_up(fd+8, 8); - HANDLE* hs2 = (HANDLE*)realloc(hs, size2*sizeof(HANDLE)); + HANDLE* hs2 = (HANDLE*)realloc(aio_hs, size2*sizeof(HANDLE)); if(!hs2) goto fail; - for(uint i = size; i < size2; i++) + for(uint i = aio_hs_size; i < size2; i++) hs2[i] = INVALID_HANDLE_VALUE; - hs = hs2; - size = size2; + aio_hs = hs2; + aio_hs_size = size2; } if(h == INVALID_HANDLE_VALUE) ; else { - if(hs[fd] != INVALID_HANDLE_VALUE) + if(aio_hs[fd] != INVALID_HANDLE_VALUE) { assert("AioHandles::set: handle already set!"); goto fail; @@ -137,14 +138,14 @@ int AioHandles::set(int fd, HANDLE h) } } - hs[fd] = h; + aio_hs[fd] = h; win_unlock(WAIO_CS); return 0; fail: win_unlock(WAIO_CS); - assert(0 && "AioHandles::set failed"); + assert(0 && "aio_h_set failed"); return -1; } @@ -171,37 +172,20 @@ struct Req size_t buf_size; }; -class Reqs -{ -public: - Reqs(); - ~Reqs(); - Req* find(const aiocb* cb); - Req* alloc(const aiocb* cb); -private: - enum { MAX_REQS = 8 }; - Req reqs[MAX_REQS]; -}; +// TODO: explain links between Req and cb -Reqs::Reqs() -{ - for(int i = 0; i < MAX_REQS; i++) - { - memset(&reqs[i], 0, sizeof(Req)); - reqs[i].ovl.hEvent = CreateEvent(0,1,0,0); // manual reset - } -} +const int MAX_REQS = 64; +static Req reqs[MAX_REQS]; -Reqs::~Reqs() +void req_cleanup(void) { Req* r = reqs; + for(int i = 0; i < MAX_REQS; i++, r++) { - r->cb = 0; - HANDLE& h = r->ovl.hEvent; if(h != INVALID_HANDLE_VALUE) { @@ -209,44 +193,62 @@ Reqs::~Reqs() h = INVALID_HANDLE_VALUE; } - free(r->buf); + ::free(r->buf); r->buf = 0; } } -// find request slot currently in use by cb -// cb = 0 => search for empty slot -Req* Reqs::find(const aiocb* cb) +void req_init() { + atexit(req_cleanup); + + for(int i = 0; i < MAX_REQS; i++) + reqs[i].ovl.hEvent = CreateEvent(0,1,0,0); // manual reset +} + + +Req* req_alloc(aiocb* cb) +{ + ONCE(req_init()); + Req* r = reqs; - for(int i = 0; i < MAX_REQS; i++, r++) - if(r->cb == cb) - return r; + if(r->cb == 0) + { + r->cb = cb; + cb->req_ = r; + +debug_out("req_alloc cb=%p r=%p\n", cb, r); + return r; + } + - assert(0 && "Reqs::find failed"); return 0; } -Req* Reqs::alloc(const aiocb* cb) +Req* req_find(const aiocb* cb) { - win_lock(WAIO_CS); +debug_out("req_find cb=%p r=%p\n", cb, cb->req_); - // find free request slot - Req* r = find(0); - if(!r) + return (Req*)cb->req_; +} + + +int req_free(Req* r) +{ +debug_out("req_free cb=%p r=%p\n", r->cb, r); + + if(r->cb == 0) { - win_unlock(WAIO_CS); - assert(0 && "Reqs::alloc: no request slot available!"); - return 0; + assert(0); + return -1; } - r->cb = (aiocb*)cb; - win_unlock(WAIO_CS); - - return r; + r->cb->req_ = 0; + r->cb = 0; + return 0; } @@ -256,30 +258,15 @@ Req* Reqs::alloc(const aiocb* cb) // ////////////////////////////////////////////////////////////////////////////// -static AioHandles* aio_hs; -static Reqs* reqs; - // Win32 functions require sector aligned transfers. // max of all drives' size is checked in init(). static size_t sector_size = 4096; // minimum: one page -static void cleanup(void) -{ - delete aio_hs; - delete reqs; -} - - // caller ensures this is not re-entered! static void init() { - reqs = new Reqs; - aio_hs = new AioHandles; - - atexit(cleanup); - // Win32 requires transfers to be sector aligned. // find maximum of all drive's sector sizes, then use that. // (it's good to know this up-front, and checking every open() is slow). @@ -291,7 +278,7 @@ static void init() if(!(drives & (1ul << drive))) continue; - drive_str[0] = 'A'+drive; + drive_str[0] = (char)('A'+drive); DWORD spc, nfc, tnc; // don't need these DWORD sector_size2; @@ -331,7 +318,7 @@ int aio_assign_handle(uintptr_t handle) return fd; } - return aio_hs->set(fd, (HANDLE)handle); + return aio_h_set(fd, (HANDLE)handle); } @@ -364,20 +351,21 @@ WIN_ONCE(init()); // TODO: need to do this elsewhere in case other routines call return -1; } - if(aio_hs->set(fd, h) < 0) + if(aio_h_set(fd, h) < 0) { assert(0 && "aio_open failed"); CloseHandle(h); return -1; } +debug_out("aio_open fn=%s fd=%d\n", fn, fd); return 0; } int aio_close(int fd) { - HANDLE h = aio_hs->get(fd); + HANDLE h = aio_h_get(fd); if(h == INVALID_HANDLE_VALUE) // out of bounds or already closed { assert(0 && "aio_close failed"); @@ -387,7 +375,9 @@ int aio_close(int fd) SetLastError(0); if(!CloseHandle(h)) assert(0); - aio_hs->set(fd, INVALID_HANDLE_VALUE); + aio_h_set(fd, INVALID_HANDLE_VALUE); + +debug_out("aio_close fd=%d\n", fd); return 0; } @@ -401,6 +391,8 @@ int aio_close(int fd) // cb->aio_offset must be 0. static int aio_rw(struct aiocb* cb) { +debug_out("aio_rw cb=%p\n", cb); + if(!cb) { assert(0); @@ -412,17 +404,24 @@ static int aio_rw(struct aiocb* cb) return 0; } - HANDLE h = aio_hs->get(cb->aio_fildes); + HANDLE h = aio_h_get(cb->aio_fildes); if(h == INVALID_HANDLE_VALUE) { assert(0 && "aio_rw: associated handle is invalid"); return -EINVAL; } - Req* r = reqs->alloc(cb); + if(cb->req_) + { + // SUSv3 says this has undefined results; we fail the attempt. + assert(0 && "aio_rw: aiocb is already in use"); + return -1; + } + + Req* r = req_alloc(cb); if(!r) { - assert(0); + assert(0 && "aio_rw: cannot allocate a Req (too many concurrent IOs)"); return -1; } @@ -472,22 +471,17 @@ static int aio_rw(struct aiocb* cb) r->ovl.Internal = r->ovl.InternalHigh = 0; -//#if _MSC_VER >= 1300 -// r->ovl.Pointer = (void*)ofs; -//#else -// r->ovl.Offset = ofs; -//#endif + // a bit tricky: this should work even if size_t grows to 64 bits. + // + // we don't use OVERLAPPED.Pointer because it's not defined in + // previous platform sdk versions, and i can't figure out how + // determine the sdk version installed. can't just check for the + // vc6/vc7 compiler - vc6 with the old sdk may have been upgraded + // to the vc7.1 compiler. + // + // this assumes little endian, but we're windows-specific here anyway. + *(size_t*)&r->ovl.Offset = ofs; -// a bit tricky: this should work even if size_t grows to 64 bits. -// -// we don't use OVERLAPPED.Pointer because it's not defined in -// previous platform sdk versions, and i can't figure out how -// determine the sdk version installed. can't just check for the -// vc6/vc7 compiler - vc6 with the old sdk may have been upgraded -// to the vc7.1 compiler. -// -// this assumes little endian, but we're windows-specific here anyway. -*(size_t*)&r->ovl.Offset = ofs; assert(cb->aio_buf != 0); @@ -498,9 +492,14 @@ ResetEvent(r->ovl.hEvent); BOOL ok = (cb->aio_lio_opcode == LIO_READ)? ReadFile(h, buf, size32, 0, &r->ovl) : WriteFile(h, buf, size32, 0, &r->ovl); - if(ok || GetLastError() == ERROR_IO_PENDING) - return 0; - return -1; + if(GetLastError() == ERROR_IO_PENDING) + { + // clear annoying error + SetLastError(0); + ok = true; + } + + return ok? 0 : -1; } @@ -544,7 +543,8 @@ int lio_listio(int mode, struct aiocb* const cbs[], int n, struct sigevent* se) // return status of transfer int aio_error(const struct aiocb* cb) { - Req* const r = reqs->find(cb); +debug_out("aio_error cb=%p\n", cb); + Req* const r = req_find(cb); if(!r) return -1; @@ -565,7 +565,8 @@ int aio_error(const struct aiocb* cb) // get bytes transferred. call exactly once for each op. ssize_t aio_return(struct aiocb* cb) { - Req* const r = reqs->find(cb); +debug_out("aio_return cb=%p\n", cb); + Req* const r = req_find(cb); if(!r) return -1; @@ -576,8 +577,7 @@ ssize_t aio_return(struct aiocb* cb) if(r->pad || _buf % sector_size) memcpy(cb->aio_buf, (u8*)r->buf + r->pad, cb->aio_nbytes); - // free this request slot - r->cb = 0; + req_free(r); return (ssize_t)cb->aio_nbytes; } @@ -587,7 +587,7 @@ int aio_cancel(int fd, struct aiocb* cb) { UNUSED(cb) - const HANDLE h = aio_hs->get(fd); + const HANDLE h = aio_h_get(fd); if(h == INVALID_HANDLE_VALUE) return -1; @@ -608,6 +608,8 @@ int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* t { int i; +debug_out("aio_suspend cb=%p\n", cbs[0]); + if(n <= 0 || n > MAXIMUM_WAIT_OBJECTS) return -1; @@ -620,7 +622,7 @@ int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* t if(!cbs[i]) continue; - Req* r = reqs->find(cbs[i]); + Req* r = req_find(cbs[i]); if(r) { if(r->ovl.Internal == STATUS_PENDING) diff --git a/source/lib/sysdep/win/waio.h b/source/lib/sysdep/win/waio.h index 4d03e8d799..14fc2e8dad 100755 --- a/source/lib/sysdep/win/waio.h +++ b/source/lib/sysdep/win/waio.h @@ -26,13 +26,16 @@ struct aiocb { - int aio_fildes; // File descriptor. - off_t aio_offset; // File offset. - void* aio_buf; // Location of buffer. - size_t aio_nbytes; // Length of transfer. - int aio_reqprio; // Request priority offset. - struct sigevent aio_sigevent; // Signal number and value. + int aio_fildes; // File descriptor. + off_t aio_offset; // File offset. + void* aio_buf; // Location of buffer. + size_t aio_nbytes; // Length of transfer. + int aio_reqprio; // Request priority offset. + struct sigevent aio_sigevent; // Signal number and value. int aio_lio_opcode; // Operation to be performed. + + // internal + void* req_; // != 0 <==> cb in use }; enum diff --git a/source/lib/sysdep/win/wdetect.cpp b/source/lib/sysdep/win/wdetect.cpp index 2db0730ae6..fdcf7e687c 100755 --- a/source/lib/sysdep/win/wdetect.cpp +++ b/source/lib/sysdep/win/wdetect.cpp @@ -20,6 +20,7 @@ #include #include "detect.h" +#include "lib.h" #include "win_internal.h" @@ -30,42 +31,166 @@ #endif +// EnumDisplayDevices (used in get_monitor_size and win_get_gfx_card) +// is not available on Win95 or NT. try to import it manually here. +// note: FreeLibrary at exit avoids BoundsChecker resource leak warnings. +static BOOL (WINAPI *pEnumDisplayDevicesA)(void*, DWORD, void*, DWORD); +static int import_EnumDisplayDevices() +{ + if(!pEnumDisplayDevicesA) + { + static HMODULE hUser32Dll = LoadLibrary("user32.dll"); + *(void**)&pEnumDisplayDevicesA = GetProcAddress(hUser32Dll, "EnumDisplayDevicesA"); + ONCE(atexit2(FreeLibrary, (uintptr_t)hUser32Dll, CC_STDCALL_1)); + } + + return pEnumDisplayDevicesA? 0 : -1; +} + + // useful for choosing a video mode. not called by detect(). -// if we fail, don't change the outputs (assumed initialized to defaults) -void get_cur_resolution(int& xres, int& yres) +// if we fail, outputs are unchanged (assumed initialized to defaults) +int get_cur_resolution(int& xres, int& yres) { DEVMODEA dm; memset(&dm, 0, sizeof(dm)); dm.dmSize = sizeof(dm); // dm.dmDriverExtra already set to 0 by memset - if(EnumDisplaySettingsA(0, ENUM_CURRENT_SETTINGS, &dm)) + if(!EnumDisplaySettingsA(0, ENUM_CURRENT_SETTINGS, &dm)) + return -1; + + xres = dm.dmPelsWidth; + yres = dm.dmPelsHeight; + return 0; +} + + +// useful for determining aspect ratio. not called by detect(). +// if we fail, outputs are unchanged (assumed initialized to defaults) +int get_monitor_size(int& width_cm, int& height_cm) +{ + DISPLAY_DEVICE adapter = { sizeof(DISPLAY_DEVICE) }; + DISPLAY_DEVICE monitor = { sizeof(DISPLAY_DEVICE) }; + // need to be distinct (EnumDisplayDevices requirement) + + LONG err; + char key_name[256]; + DWORD key_name_len; + DWORD key_type; + + bool found = false; + + + // make sure EnumDisplayDevices is available (as pEnumDisplayDevicesA) + CHECK_ERR(import_EnumDisplayDevices()); + + HKEY hkDisplay; + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Enum\\Display", 0, KEY_READ, &hkDisplay) != 0) + return -1; + + // we only look at the first monitor of the first display adapter + // attached to the desktop, assumed to be the primary monitor. + + // for each display adapter + for(int adapter_idx = 0; !found; adapter_idx++) { - xres = dm.dmPelsWidth; - yres = dm.dmPelsHeight; + // get display adapter + if(!pEnumDisplayDevicesA(0, adapter_idx, &adapter, 0)) + break; + if(!(adapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) + continue; + + // get its associated monitor; + // will search for its DeviceID in the registry + if(!pEnumDisplayDevicesA(adapter.DeviceName, 0, &monitor, 0)) + continue; + + // for each class in registry + for(int class_idx = 0; !found; class_idx++) + { + // open next key + HKEY hkClass; + key_name_len = sizeof(key_name); + if(RegEnumKeyEx(hkDisplay, class_idx, key_name, &key_name_len, 0, 0, 0, 0) != 0) + break; + if(RegOpenKeyEx(hkDisplay, key_name, 0, KEY_READ, &hkClass) != 0) + break; + + // for each device in registry + for(int dev_idx = 0; !found; dev_idx++) + { + // open next key + HKEY hkDev; + key_name_len = sizeof(key_name); + if(RegEnumKeyEx(hkClass, dev_idx, key_name, &key_name_len, 0, 0, 0, 0) != 0) + break; + if(RegOpenKeyEx(hkClass, key_name, 0, KEY_READ, &hkDev) != 0) + break; + + // build dev_id: (%s\\%s, HardwareID, Driver) + // example: "Monitor\NEC6604\{4D36E96E-E325-11CE-BFC1-08002BE10318}\0001" + // will compare this against monitor.DeviceID + char dev_id[256]; + DWORD dev_id_len = sizeof(dev_id); + err = RegQueryValueEx(hkDev, "HardwareID", 0, &key_type, (BYTE*)dev_id, &dev_id_len); + if(err != 0 || (key_type != REG_MULTI_SZ && key_type != REG_SZ)) + goto skip_dev; + char* p = (char*)dev_id + strlen((const char*)dev_id); + *p++ = '\\'; + dev_id_len = sizeof(dev_id) - dev_id_len; + err = RegQueryValueEx(hkDev, "Driver", 0, &key_type, (BYTE*)p, &dev_id_len); + if(err != 0 || (key_type != REG_MULTI_SZ && key_type != REG_SZ)) + goto skip_dev; + + // this (hkDev) is not the monitor you're looking for.. + if(strcmp(monitor.DeviceID, (const char*)dev_id) != 0) + goto skip_dev; + + HKEY hkDevParams; + if(RegOpenKeyEx(hkDev, "Device Parameters", 0, KEY_READ, &hkDevParams) != 0) + goto skip_dev; + + // read EDID + BYTE edid[256]; + DWORD edid_len = sizeof(edid); + if(RegQueryValueEx(hkDevParams, "EDID", 0, &key_type, edid, &edid_len) == 0) + { + width_cm = edid[21]; + height_cm = edid[22]; + found = true; + // break out of all loops; all keys will be closed + } + + RegCloseKey(hkDevParams); + + skip_dev: + RegCloseKey(hkDev); + } + + RegCloseKey(hkClass); + } } + + RegCloseKey(hkDisplay); + + return found? 0 : -1; } int win_get_gfx_card() { - // EnumDisplayDevices is not available on Win95 or NT - HMODULE hUser32Dll = LoadLibrary("user32.dll"); - int (WINAPI *pEnumDisplayDevicesA)(void*, DWORD, void*, DWORD); - *(void**)&pEnumDisplayDevicesA = GetProcAddress(hUser32Dll, "EnumDisplayDevicesA"); - if(pEnumDisplayDevicesA) - { - DISPLAY_DEVICEA dev; - dev.cb = sizeof(dev); - if(pEnumDisplayDevicesA(0, 0, &dev, 0)) - { - strcpy(gfx_card, (const char*)dev.DeviceString); - return 0; - } - } - FreeLibrary(hUser32Dll); + // make sure EnumDisplayDevices is available (as pEnumDisplayDevicesA) + if(import_EnumDisplayDevices() < 0) + return -1; - return -1; + DISPLAY_DEVICEA dev; + dev.cb = sizeof(dev); + if(!pEnumDisplayDevicesA(0, 0, &dev, 0)) + return -1; + + strncpy(gfx_card, (const char*)dev.DeviceString, sizeof(gfx_card)-1); + return 0; } @@ -120,10 +245,26 @@ int win_get_gfx_drv() int win_get_cpu_info() { + // get number of CPUs (can't fail) SYSTEM_INFO si; GetSystemInfo(&si); cpus = si.dwNumberOfProcessors; + // read CPU frequency from registry + HKEY hKey; + const char* key = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &hKey) == 0) + { + DWORD freq_mhz; + DWORD size = sizeof(freq_mhz); + if(RegQueryValueEx(hKey, "~MHz", 0, 0, (LPBYTE)&freq_mhz, &size) == 0) + cpu_freq = freq_mhz * 1e6; + + RegCloseKey(hKey); + } + + // determine whether system is a laptop. + // (if SpeedStep detect below fails, guess SpeedStep <==> laptop) HW_PROFILE_INFO hi; GetCurrentHwProfile(&hi); bool is_laptop = !(hi.dwDockInfo & DOCKINFO_DOCKED) ^ diff --git a/source/lib/sysdep/win/win.cpp b/source/lib/sysdep/win/win.cpp index f4bf3b5d64..dec02da767 100755 --- a/source/lib/sysdep/win/win.cpp +++ b/source/lib/sysdep/win/win.cpp @@ -23,6 +23,7 @@ #include #include // malloc debug +#include // @@ -37,62 +38,60 @@ void check_heap() } -void display_msg(const wchar_t* caption, const wchar_t* msg) +void display_msg(const char* caption, const char* msg) +{ + MessageBoxA(0, msg, caption, MB_ICONEXCLAMATION); +} + +void wdisplay_msg(const wchar_t* caption, const wchar_t* msg) { MessageBoxW(0, msg, caption, MB_ICONEXCLAMATION); } -// fixing Win32 _vsnprintf to return # characters that would be written, -// as required by C99, looks difficult and unnecessary. if any other code -// needs that, generalize the following code into vasprintf. + +// need to shoehorn printf-style variable params into +// the OutputDebugString call. +// - don't want to split into multiple calls - would add newlines to output. +// - fixing Win32 _vsnprintf to return # characters that would be written, +// as required by C99, looks difficult and unnecessary. if any other code +// needs that, implement GNU vasprintf. +// - fixed size buffers aren't nice, but much simpler than vasprintf-style +// allocate+expand_until_it_fits. these calls are for quick debug output, +// not loads of data, anyway. + + +static const int MAX_CNT = 512; + // max output size of 1 call of (w)debug_out (including \0) + + void debug_out(const char* fmt, ...) { - size_t size = 256; - void* buf = 0; + char buf[MAX_CNT]; + buf[MAX_CNT-1] = '\0'; - for(;;) - { - void* buf2 = realloc(buf, size); - // out of mem - free old buf and quit - if(!buf2) - goto fail; - buf = buf2; - // don't assign directly from realloc - if it fails, - // we'd leak memory. + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, MAX_CNT-1, fmt, ap); + va_end(ap); - { - va_list args; - va_start(args, fmt); - // have to re-create every time. va_copy isn't portable. - - int ret = vsnprintf((char*)buf, size, fmt, args); - - // success - if(ret > -1 && ret < (int)size) - break; - // return value was required buffer size (without trailing '\0') - if(ret > (int)size) - size = ret+1; - // -1: increase buffer size - else - size *= 2; - - va_end(args); - } - - // prevent infinite loop - if(size > 64*KB) - goto fail; - } - - OutputDebugString((const char*)buf); - -fail: - free(buf); + OutputDebugString(buf); +} + + +void wdebug_out(const wchar_t* fmt, ...) +{ + wchar_t buf[MAX_CNT]; + buf[MAX_CNT-1] = L'\0'; + + va_list ap; + va_start(ap, fmt); + vsnwprintf(buf, MAX_CNT-1, fmt, ap); + va_end(ap); + + OutputDebugStringW(buf); } - @@ -182,4 +181,4 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { pre_main_init(); return main(__argc, __argv); -} \ No newline at end of file +} diff --git a/source/lib/sysdep/win/win.h b/source/lib/sysdep/win/win.h index daf9502b9a..3eb4554d41 100755 --- a/source/lib/sysdep/win/win.h +++ b/source/lib/sysdep/win/win.h @@ -7,6 +7,7 @@ #define snprintf _snprintf #define swprintf _snwprintf #define vsnprintf _vsnprintf +#define vsnwprintf _vsnwprintf #include // wchar_t @@ -18,4 +19,4 @@ #define WINAPI __stdcall #define WINAPIV __cdecl -#endif // #ifndef __WIN_H__ \ No newline at end of file +#endif // #ifndef __WIN_H__ diff --git a/source/lib/sysdep/win/win_internal.h b/source/lib/sysdep/win/win_internal.h index f381031ebf..994a027a70 100755 --- a/source/lib/sysdep/win/win_internal.h +++ b/source/lib/sysdep/win/win_internal.h @@ -22,6 +22,7 @@ #define _WIN32_WINNT 0x0500 + #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ //#define NOVIRTUALKEYCODES // VK_* //#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* @@ -269,4 +270,4 @@ extern void win_unlock(uint idx); } -#endif // #ifndef WIN_INTERNAL_H \ No newline at end of file +#endif // #ifndef WIN_INTERNAL_H diff --git a/source/lib/sysdep/win/wposix.cpp b/source/lib/sysdep/win/wposix.cpp index a864c8ee12..f2fff6b754 100755 --- a/source/lib/sysdep/win/wposix.cpp +++ b/source/lib/sysdep/win/wposix.cpp @@ -43,8 +43,10 @@ extern int aio_close(int); int open(const char* fn, int mode, ...) { + bool is_com_port = strncmp(fn, "/dev/tty", 8) == 0; + // /dev/tty? => COM? - if(!strncmp(fn, "/dev/tty", 8)) + if(is_com_port) { static char port[] = "COM "; port[3] = (char)(fn[8]+1); @@ -55,7 +57,12 @@ int open(const char* fn, int mode, ...) // open it for async I/O as well (_open defaults to deny_none sharing) if(fd > 2) - aio_open(fn, mode, fd); + { + // .. unless it's a COM port. don't currently need aio access for those; + // also, aio_open's CreateFile reports access denied when trying to open. + if(!is_com_port) + aio_open(fn, mode, fd); + } return fd; } @@ -331,8 +338,8 @@ int pthread_mutex_timedlock(pthread_mutex_t* m, const struct timespec* abs_timeo { struct timespec cur_ts; clock_gettime(CLOCK_REALTIME, &cur_ts); - ms_timeout = (cur_ts.tv_sec - abs_timeout->tv_sec ) * 1000 + - (cur_ts.tv_nsec - abs_timeout->tv_nsec) / 1000000; + ms_timeout = DWORD((cur_ts.tv_sec - abs_timeout->tv_sec ) * 1000 + + (cur_ts.tv_nsec - abs_timeout->tv_nsec) / 1000000); } return WaitForSingleObject(*m, ms_timeout) == WAIT_OBJECT_0? 0 : -1; @@ -498,6 +505,8 @@ int nanosleep(const struct timespec* rqtp, struct timespec* /* rmtp */) int gettimeofday(struct timeval* tv, void* tzp) { + UNUSED(tzp); + #ifndef NDEBUG if(!tv) { diff --git a/source/lib/sysdep/win/wposix.h b/source/lib/sysdep/win/wposix.h index 8b55ef00e7..90d6f2d300 100755 --- a/source/lib/sysdep/win/wposix.h +++ b/source/lib/sysdep/win/wposix.h @@ -34,6 +34,13 @@ extern "C" { #define IMP(ret, name, param) extern "C" __declspec(dllimport) ret __stdcall name param; +// for functions actually implemented in the CRT +#ifdef _DLL +#define _CRTIMP __declspec(dllimport) +#else +#define _CRTIMP +#endif + // @@ -189,10 +196,13 @@ extern int open(const char* fn, int mode, ...); // // +// values from MS _access() implementation. do not change. #define F_OK 0 -#define R_OK 1 +#define R_OK 4 #define W_OK 2 -#define X_OK 4 +#define X_OK 0 + // MS implementation doesn't support this distinction. + // hence, the file is reported executable if it exists. #define read _read #define write _write diff --git a/source/lib/sysdep/win/wsdl.cpp b/source/lib/sysdep/win/wsdl.cpp index 9c3bc624b0..e7312adbec 100755 --- a/source/lib/sysdep/win/wsdl.cpp +++ b/source/lib/sysdep/win/wsdl.cpp @@ -235,7 +235,7 @@ inline SDLKey vkmap(int vk) { static SDLKey VK_SDLKMap[256]; /* VK_SDLKMap[vk] == SDLK */ - ONCE( init_vkmap(VK_SDLKMap); ) + ONCE( init_vkmap(VK_SDLKMap); ); assert(vk >= 0 && vk < 256); @@ -306,16 +306,17 @@ return_char: char_buf[num_chars]=0; translated_keysym=vkmap(vk); //wprintf(L"ToUnicode: Translated %02x to [%s], %d chars, SDLK %02x. Extended flag %d, scancode %d\n", vk, char_buf, num_chars, translated_keysym, msg.lParam & 0x01000000, scancode); - fflush(stdout); + //fflush(stdout); goto return_char; } else if (num_chars == -1) { // Dead Key: Don't produce an event for this one //printf("ToUnicode: Dead Key %02x [%c] [%c] SDLK %02x\n", vk, vk, char_buf[0], vkmap(vk)); - fflush(stdout); + //fflush(stdout); num_chars = 0; break; + // leave the switch statement; get next message. } // num_chars == 0: No translation: Just produce a plain KEYDOWN event @@ -327,7 +328,7 @@ return_char: ev->key.keysym.unicode = 0; //printf("ToUnicode: No translation for %02x, extended flag %d, scancode %d, SDLK %02x [%c]\n", vk, msg.lParam & 0x01000000, scancode, ev->key.keysym.sym, ev->key.keysym.sym); - fflush(stdout); + //fflush(stdout); return 1; } @@ -338,7 +339,7 @@ return_char: // TODO Modifier statekeeping ev->type = SDL_KEYUP; - ev->key.keysym.sym = vkmap(msg.wParam); + ev->key.keysym.sym = vkmap((int)msg.wParam); ev->key.keysym.unicode = 0; return 1; @@ -360,7 +361,7 @@ return_char: sdl_btn = SDL_BUTTON_LEFT + btn/3; // assumes L,R,M if(sdl_btn != -1) { - ev->type = SDL_MOUSEBUTTONDOWN + btn%3; + ev->type = (u8)(SDL_MOUSEBUTTONDOWN + btn%3); ev->button.button = (u8)sdl_btn; ev->button.x = (u16)(msg.lParam & 0xffff); ev->button.y = (u16)((msg.lParam >> 16) & 0xffff); @@ -514,9 +515,11 @@ void SDL_Quit() } -void SDL_WM_SetCaption(const char *title, const char *icon) +void SDL_WM_SetCaption(const char* title, const char* icon) { SetWindowText(hWnd, title); + + UNUSED(icon); // TODO: implement } diff --git a/source/lib/sysdep/x.cpp b/source/lib/sysdep/x.cpp index af264fbac7..4a80ded860 100755 --- a/source/lib/sysdep/x.cpp +++ b/source/lib/sysdep/x.cpp @@ -21,17 +21,27 @@ #include "detect.h" // useful for choosing a video mode. not called by detect(). -// if we fail, don't change the outputs (assumed initialized to defaults) -void get_cur_resolution(int& xres, int& yres) +// if we fail, outputs are unchanged (assumed initialized to defaults) +int get_cur_resolution(int& xres, int& yres) { Display* disp = XOpenDisplay(0); if(!disp) - return; + return -1; int screen = XDefaultScreen(disp); xres = XDisplayWidth (disp, screen); yres = XDisplayHeight(disp, screen); XCloseDisplay(disp); + return 0; } + +// useful for determining aspect ratio. not called by detect(). +// if we fail, outputs are unchanged (assumed initialized to defaults) +int get_monitor_size(int& width_cm, int& height_cm) +{ + return -1; +} + + #endif // #ifdef HAVE_X diff --git a/source/main.cpp b/source/main.cpp index df2bbe8e39..04bf2db3ad 100755 --- a/source/main.cpp +++ b/source/main.cpp @@ -82,7 +82,7 @@ static void display_startup_error(const wchar_t* msg) const wchar_t* caption = L"0ad startup problem"; write_sys_info(); - display_msg(caption, msg); + wdisplay_msg(caption, msg); exit(1); } @@ -214,8 +214,63 @@ static void do_tick() { } + + + int main(int argc, char* argv[]) { + +chdir("\\games\\bf\\ScreenShots"); +int a,b; +char fn[100]; +__asm cpuid __asm rdtsc __asm mov a, eax +int i=0; + +/* +int guess=4; +for(;;) +{ +sprintf(fn, "ScreenShot%d.jpg", i); +if(access(fn, F_OK) < 0) +{ + int lo = guess/2; + int hi = guess; + for(;;) + { + int test = (lo+hi)/2; + sprintf(fn, "ScreenShot%d.jpg", i); + if(access(fn, F_OK) < 0) + ; + } +} +else + guess *= 2; +break; + +} +*/ + + +/* +DIR* d = opendir("."); +while(readdir(d)) +i++; +closedir(d); +*/ + +/* +for(i = 0; i < 100; i++) +{ +sprintf(fn, "ScreenShot%d.jpg", i); +if(access(fn, F_OK) < 0) +break; +} +*/ + + +__asm cpuid __asm rdtsc __asm mov b, eax +int c = b-a; + lib_init(); // set 24 bit (float) FPU precision for faster divides / sqrts @@ -270,8 +325,8 @@ glEnable (GL_DEPTH_TEST); new CConfig; - vfs_set_root(argv[0], "data"); - vfs_mount("mods/official"); + file_set_root_dir(argv[0], "../data"); + vfs_mount("", "mods/official/", 0); #ifndef NO_GUI // GUI uses VFS, so this must come after VFS init. @@ -281,14 +336,11 @@ glEnable (GL_DEPTH_TEST); g_GUI.LoadXMLFile("gui/sprite1.xml"); #endif + // tex = tex_load("0adlogo2.bmp"); // tex_upload(tex); font = font_load("verdana.fnt"); - - - - terr_init(); diff --git a/source/ps/Network/NetMessage.cpp b/source/ps/Network/NetMessage.cpp index 5eaf1f8054..5ad46f1366 100755 --- a/source/ps/Network/NetMessage.cpp +++ b/source/ps/Network/NetMessage.cpp @@ -35,7 +35,7 @@ CNetMessage *CNetMessage::DeserializeMessage(ENetMessageType type, u8 *buffer, u { g_DeserializerMap.insert(std::make_pair(pReg->m_Type, pReg->m_pDeserializer)); } - ) + ); } printf("DeserializeMessage: Finding for MT %d\n", type); diff --git a/source/terrain/Particle.cpp b/source/terrain/Particle.cpp index b11c05fcdc..df06b5c35b 100755 --- a/source/terrain/Particle.cpp +++ b/source/terrain/Particle.cpp @@ -25,7 +25,7 @@ ==================================================================*/ #include "Particle.h" -#include "time.h" +#include "timer.h" #include "ogl.h" #include diff --git a/source/terrain/ParticleEmitter.cpp b/source/terrain/ParticleEmitter.cpp index 4a57c95169..2706d3b4a7 100755 --- a/source/terrain/ParticleEmitter.cpp +++ b/source/terrain/ParticleEmitter.cpp @@ -27,7 +27,7 @@ ==================================================================*/ #include "ParticleEmitter.h" -#include "time.h" +#include "timer.h" #include "ogl.h" #include diff --git a/source/terrain/Sprite.cpp b/source/terrain/Sprite.cpp index a59967cd8f..975d48ad0a 100755 --- a/source/terrain/Sprite.cpp +++ b/source/terrain/Sprite.cpp @@ -26,7 +26,7 @@ #include "Sprite.h" #include "ogl.h" -#include "tex.h" +#include "res/tex.h" CSprite::CSprite() : m_texture(NULL) diff --git a/source/terrain/terrainMain.cpp b/source/terrain/terrainMain.cpp index 7bbbad541a..61f4144eb5 100755 --- a/source/terrain/terrainMain.cpp +++ b/source/terrain/terrainMain.cpp @@ -19,7 +19,7 @@ void InitScene (); void InitResources (); void RenderScene (); -extern bool keys[256]; +extern bool keys[512]; // SDL also defines non-ascii keys; 512 should be enough CMatrix3D g_WorldMat; @@ -309,6 +309,7 @@ for (uint ii=0;ii