Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp (revision 2618) +++ ps/trunk/source/main.cpp (revision 2619) @@ -1,1553 +1,1662 @@ #include "precompiled.h" #ifdef SCED # include "ui/StdAfx.h" # undef ERROR #endif // SCED #include #include #include #include #include #include "lib.h" #include "lib/sdl.h" #include "lib/ogl.h" #include "lib/detect.h" #include "lib/timer.h" #include "lib/input.h" #include "lib/res/res.h" #include "lib/res/sound/snd.h" #include "lib/res/graphics/tex.h" #include "lib/res/graphics/cursor.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/Loader.h" #include "ps/Font.h" #include "ps/CConsole.h" #include "ps/Game.h" #include "ps/Interact.h" #include "ps/Hotkey.h" #include "ps/ConfigDB.h" #include "ps/CLogger.h" #include "ps/i18n.h" #include "ps/Overlay.h" #include "ps/StringConvert.h" #include "graphics/MapReader.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "graphics/ObjectManager.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/UnitManager.h" #include "graphics/MaterialManager.h" #include "graphics/MeshManager.h" #include "renderer/Renderer.h" #include "simulation/BaseEntityCollection.h" #include "simulation/Entity.h" #include "simulation/EntityHandles.h" #include "simulation/EntityManager.h" #include "simulation/PathfindEngine.h" #include "simulation/Scheduler.h" #include "simulation/Projectile.h" #include "scripting/ScriptingHost.h" #include "scripting/GameEvents.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" #include "scripting/JSInterface_Camera.h" #include "scripting/JSInterface_Selection.h" #include "scripting/JSInterface_Console.h" #include "scripting/JSCollection.h" #include "scripting/DOMEvent.h" #ifndef NO_GUI # include "gui/scripting/JSInterface_IGUIObject.h" # include "gui/scripting/JSInterface_GUITypes.h" # include "gui/GUI.h" #endif #include "sound/CMusicPlayer.h" #include "sound/JSI_Sound.h" #include "Network/SessionManager.h" #include "Network/Server.h" #include "Network/Client.h" #define LOG_CATEGORY "main" extern int conInputHandler(const SDL_Event* ev); // Globals bool keys[SDLK_LAST]; bool mouseButtons[5]; int g_mouse_x=50, g_mouse_y=50; int g_xres, g_yres; int g_bpp; int g_freq; bool g_active = true; // flag to disable extended GL extensions until fix found - specifically, crashes // using VBOs on laptop Radeon cards static bool g_NoGLVBO=false; // flag to switch on shadows static bool g_Shadows=false; // flag to switch off pbuffers static bool g_NoPBuffer=true; // flag to switch on fixed frame timing (RC: I'm using this for profiling purposes) static bool g_FixedFrameTiming=false; static bool g_VSync = false; static float g_LodBias = 0.0f; static bool g_Quickstart=false; extern CLightEnv g_LightEnv; static bool g_EntGraph = false; static float g_Gamma = 1.0f; extern int game_view_handler(const SDL_Event* ev); static CMusicPlayer MusicPlayer; CStr g_CursorName = "test"; CStr g_ActiveProfile = "default"; extern size_t frameCount; static bool quit = false; // break out of main loop -#ifdef ATLAS -static bool g_Atlas = true; // allows optional startup in Atlas vs non-Atlas (game) modes -#endif - const wchar_t* HardcodedErrorString(int err) { #define E(sym) case sym: return L ## #sym; switch(err) { E(ERR_NO_MEM) E(ERR_FILE_NOT_FOUND) E(ERR_INVALID_HANDLE) E(ERR_INVALID_PARAM) E(ERR_EOF) E(ERR_PATH_NOT_FOUND) E(ERR_PATH_LENGTH) default: return 0; } } const wchar_t* ErrorString(int err) { // language file not available (yet) return HardcodedErrorString(err); // TODO: load from language file } ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); -void Testing (void) + +//---------------------------------------------------------------------------- +// Atlas (map editor) integration +//---------------------------------------------------------------------------- + +static void* const ATLAS_SO_UNAVAILABLE = (void*)-1; +static void* atlas_so_handle; + + +// free reference to Atlas UI SO (avoids resource leak report) +static void ATLAS_Shutdown() +{ + // (avoid dlclose warnings) + if(atlas_so_handle != 0 && atlas_so_handle != ATLAS_SO_UNAVAILABLE) + dlclose(atlas_so_handle); +} + + +// return true if the Atlas UI shared object is available; +// used to disable the main menu editor button if not. +// note: this actually loads the SO, but that isn't expected to be slow. +// call ATLAS_Shutdown at exit to avoid leaking it. +static bool ATLAS_IsAvailable() +{ + // first time: try to open Atlas UI shared object + // postcondition: atlas_so_handle valid or == ATLAS_SO_UNAVAILABLE. + if(atlas_so_handle == 0) + { + // since this SO exports a C++ interface, it is critical that + // compiler options are the same between app and SO; therefore, + // we need to go with the debug version in debug builds. + // note: on Windows, the extension is replaced with .dll by dlopen. +#ifndef NDEBUG + const char* so_name = "AtlasUI_d.so"; +#else + const char* so_name = "AtlasUI.so"; +#endif + // we don't care when relocations take place because this SO contains + // very few symbols, so RTLD_LAZY or RTLD_NOW aren't needed. + const int flags = RTLD_LOCAL; + atlas_so_handle = dlopen(so_name, flags); + // open failed (mostly likely SO not found) + if(!atlas_so_handle) + atlas_so_handle = ATLAS_SO_UNAVAILABLE; + } + + return atlas_so_handle != ATLAS_SO_UNAVAILABLE; +} + + +static bool atlas_is_running; + +// if Atlas is running, some parts of the GUI need not be loaded +// (reduces startup time). +static bool ATLAS_IsRunning() +{ + return atlas_is_running; +} + + +enum AtlasRunFlags +{ + // used by ATLAS_RunIfOnCmdLine; makes any error output go through + // DISPLAY_ERROR rather than a GUI dialog box (because GUI init was + // skipped to reduce load time). + ATLAS_NO_GUI = 1 +}; + +// starts the Atlas UI. +static void ATLAS_Run(int argc, char* argv[], int flags = 0) +{ + // first check if we can run at all + if(!ATLAS_IsAvailable()) + { + if(flags & ATLAS_NO_GUI) + DISPLAY_ERROR(L"The Atlas UI was not successfully loaded and therefore cannot be started as requested."); + else + DISPLAY_ERROR(L"The Atlas UI was not successfully loaded and therefore cannot be started as requested.");// TODO: implement GUI error message + return; + } + + void(*pStartWindow)(int argc, char* argv[]); + *(void**)&pStartWindow = dlsym(atlas_so_handle, "_StartWindow"); + pStartWindow(argc, argv); + + atlas_is_running = true; +} + + +// starts the Atlas UI if an "-editor" switch is found on the command line. +// this is the alternative to starting the main menu and clicking on +// the editor button; it is much faster because it's called during early +// init and therefore skips GUI setup. +// notes: +// - GUI init still runs, but some GUI setup will be skipped since +// ATLAS_IsRunning() will return true. +// - could be merged into CFG_ParseCommandLineArgs, because that appears +// to be called early enough. it's not really worth it because this +// code is quite simple and we thus avoid startup order dependency. +static void ATLAS_RunIfOnCmdLine(int argc, char* argv[]) { - g_Console->InsertMessage(L"Testing Function Registration"); + for(int i = 1; i < argc; i++) // skip program name argument + { + if(!strcmp(argv[i], "-editor")) + { + // don't bother removing this param (unnecessary) + + ATLAS_Run(argc, argv, ATLAS_NO_GUI); + break; + } + } } +//---------------------------------------------------------------------------- +// GUI integration +//---------------------------------------------------------------------------- + + +static void GUI_Init() +{ +#ifndef NO_GUI + {TIMER(ps_gui_init); + g_GUI.Initialize();} + + {TIMER(ps_gui_setup_xml); + g_GUI.LoadXMLFile("gui/test/setup.xml");} + {TIMER(ps_gui_styles_xml); + g_GUI.LoadXMLFile("gui/test/styles.xml");} + {TIMER(ps_gui_sprite1_xml); + g_GUI.LoadXMLFile("gui/test/sprite1.xml");} + + // Atlas is running, we won't need these GUI pages (for now! + // what if Atlas switches to in-game mode?!) + // TODO: temporary hack until revised GUI structure is completed. + if(ATLAS_IsRunning()) + return; + + {TIMER(ps_gui_1); + g_GUI.LoadXMLFile("gui/test/1_init.xml");} + {TIMER(ps_gui_2); + g_GUI.LoadXMLFile("gui/test/2_mainmenu.xml");} + {TIMER(ps_gui_3); + g_GUI.LoadXMLFile("gui/test/3_loading.xml");} + {TIMER(ps_gui_4); + g_GUI.LoadXMLFile("gui/test/4_session.xml");} + {TIMER(ps_gui_6); + g_GUI.LoadXMLFile("gui/test/6_subwindows.xml");} + {TIMER(ps_gui_6_1); + g_GUI.LoadXMLFile("gui/test/6_1_manual.xml");} + {TIMER(ps_gui_6_2); + g_GUI.LoadXMLFile("gui/test/6_2_jukebox.xml");} + {TIMER(ps_gui_7); + g_GUI.LoadXMLFile("gui/test/7_atlas.xml");} + {TIMER(ps_gui_9); + g_GUI.LoadXMLFile("gui/test/9_global.xml");} +#endif +} + + +static void GUI_Shutdown() +{ +#ifndef NO_GUI + g_GUI.Destroy(); + delete &g_GUI; +#endif +} +// display progress / description in loading screen +static void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) +{ +#ifndef NO_GUI + CStrW i18n_description = I18n::translate(pending_task); + JSString* js_desc = StringConvert::wstring_to_jsstring(g_ScriptingHost.getContext(), i18n_description); + g_ScriptingHost.SetGlobal("g_Progress", INT_TO_JSVAL(percent)); + g_ScriptingHost.SetGlobal("g_LoadDescription", STRING_TO_JSVAL(js_desc)); + g_GUI.SendEventToAll("progress"); +#endif +} //---------------------------------------------------------------------------- // config and profile //---------------------------------------------------------------------------- static void CFG_LoadProfile( CStr profile ) { CStr base = CStr( "profiles/" ) + profile; g_ConfigDB.SetConfigFile(CFG_USER, true, base + "/settings/user.cfg"); g_ConfigDB.Reload(CFG_USER); int max_history_lines = 50; CFG_GET_USER_VAL("console.history.size", Int, max_history_lines); g_Console->UseHistoryFile(base+"/settings/history", max_history_lines); } // Fill in the globals from the config files. static void CFG_LoadGlobals() { CFG_GET_SYS_VAL("profile", String, g_ActiveProfile); // Now load the profile before trying to retrieve the values of the rest of these. CFG_LoadProfile( g_ActiveProfile ); CFG_GET_USER_VAL("xres", Int, g_xres); CFG_GET_USER_VAL("yres", Int, g_yres); CFG_GET_USER_VAL("vsync", Bool, g_VSync); CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO); CFG_GET_USER_VAL("shadows", Bool, g_Shadows); CFG_GET_USER_VAL("lodbias", Float, g_LodBias); float gain = -1.0f; CFG_GET_USER_VAL("sound.mastergain", Float, gain); if(gain > 0.0f) WARN_ERR(snd_set_master_gain(gain)); LOG(NORMAL, LOG_CATEGORY, "g_x/yres is %dx%d", g_xres, g_yres); LOG(NORMAL, LOG_CATEGORY, "Active profile is %s", g_ActiveProfile.c_str()); } static void CFG_ParseCommandLineArgs(int argc, char* argv[]) { for(int i = 1; i < argc; i++) { // this arg isn't an option; skip if(argv[i][0] != '-') continue; char* name = argv[i]+1; // no leading '-' // switch first letter of option name switch(argv[i][1]) { case 'c': if(strcmp(name, "conf") == 0) { if(argc-i >= 1) // at least one arg left { i++; char* arg = argv[i]; char* equ = strchr(arg, '='); if(equ) { *equ = 0; g_ConfigDB.CreateValue(CFG_COMMAND, arg) ->m_String = (equ+1); } } } break; case 'e': g_EntGraph = true; break; case 'f': if(strncmp(name, "fixedframe", 10) == 0) g_FixedFrameTiming=true; break; case 'g': if(strncmp(name, "g=", 2) == 0) { g_Gamma = (float)atof(argv[i] + 3); if(g_Gamma == 0.0f) g_Gamma = 1.0f; } break; case 'l': if(strncmp(name, "listfiles", 9) == 0) vfs_enable_file_listing(true); break; case 'n': if(strncmp(name, "novbo", 5) == 0) g_ConfigDB.CreateValue(CFG_COMMAND, "novbo")->m_String="true"; else if(strncmp(name, "nopbuffer", 9) == 0) g_NoPBuffer = true; break; case 'q': if(strncmp(name, "quickstart", 10) == 0) g_Quickstart = true; break; case 's': if(strncmp(name, "shadows", 7) == 0) g_ConfigDB.CreateValue(CFG_COMMAND, "shadows")->m_String="true"; break; case 'v': g_ConfigDB.CreateValue(CFG_COMMAND, "vsync")->m_String="true"; break; case 'x': if(strncmp(name, "xres=", 6) == 0) g_ConfigDB.CreateValue(CFG_COMMAND, "xres")->m_String=argv[i]+6; break; case 'y': if(strncmp(name, "yres=", 6) == 0) g_ConfigDB.CreateValue(CFG_COMMAND, "yres")->m_String=argv[i]+6; break; case 'p': if(strncmp(name, "profile=", 8) == 0 ) g_ConfigDB.CreateValue(CFG_COMMAND, "profile")->m_String = argv[i]+9; break; } // switch } } static void CFG_Init(int argc, char* argv[]) { debug_printf("CFG_Init &argc=%p &argv=%p\n", &argc, &argv); TIMER(CFG_Init); MICROLOG(L"init config"); new CConfigDB; g_ConfigDB.SetConfigFile(CFG_SYSTEM, false, "config/system.cfg"); g_ConfigDB.Reload(CFG_SYSTEM); g_ConfigDB.SetConfigFile(CFG_MOD, true, "config/mod.cfg"); // No point in reloading mod.cfg here - we haven't mounted mods yet CFG_ParseCommandLineArgs(argc, argv); // Collect information from system.cfg, the profile file, // and any command-line overrides to fill in the globals. CFG_LoadGlobals(); } //---------------------------------------------------------------------------- static std::string SplitExts(const char *exts) { std::string str = exts; std::string ret = ""; size_t idx = str.find_first_of(" "); while(idx != std::string::npos) { if(idx >= str.length() - 1) { ret += str; break; } ret += str.substr(0, idx); ret += "\n"; str = str.substr(idx + 1); idx = str.find_first_of(" "); } return ret; } static void WriteSystemInfo() { TIMER(write_sys_info); get_gfx_info(); // get_cpu_info already called during init - see call site get_snd_info(); get_mem_status(); struct utsname un; uname(&un); FILE* f = fopen("../logs/system_info.txt", "w"); if(!f) return; // .. OS fprintf(f, "OS : %s %s (%s)\n", un.sysname, un.release, un.version); // .. CPU fprintf(f, "CPU : %s, %s", un.machine, cpu_type); if(cpus > 1) fprintf(f, " (x%d)", cpus); if(cpu_freq != 0.0f) { if(cpu_freq < 1e9) fprintf(f, ", %.2f MHz\n", cpu_freq*1e-6); else fprintf(f, ", %.2f GHz\n", cpu_freq*1e-9); } else fprintf(f, "\n"); // .. memory fprintf(f, "Memory : %lu MiB; %lu MiB free\n", tot_mem/MiB, avl_mem/MiB); // .. graphics fprintf(f, "Graphics Card : %s\n", gfx_card); fprintf(f, "OpenGL Drivers : %s; %s\n", glGetString(GL_VERSION), gfx_drv_ver); fprintf(f, "Video Mode : %dx%d:%d@%d\n", g_xres, g_yres, g_bpp, g_freq); // .. sound fprintf(f, "Sound Card : %s\n", snd_card); fprintf(f, "Sound Drivers : %s\n", snd_drv_ver); // // .. network name / ips // // note: can't use un.nodename because it is for an // "implementation-defined communications network". char hostname[128] = "(unknown)"; (void)gethostname(hostname, sizeof(hostname)-1); // -1 makes sure it's 0-terminated. if the function fails, // we display "(unknown)" and will skip IP output below. fprintf(f, "Network Name : %s", hostname); { hostent* host = gethostbyname(hostname); if(!host) goto no_ip; struct in_addr** ips = (struct in_addr**)host->h_addr_list; if(!ips) goto no_ip; // output all IPs (> 1 if using VMware or dual ethernet) fprintf(f, " ("); for(uint i = 0; i < 256 && ips[i]; i++) // safety { // separate entries but avoid trailing comma if(i != 0) fprintf(f, ", "); fprintf(f, "%s", inet_ntoa(*ips[i])); } fprintf(f, ")"); } no_ip: fprintf(f, "\n"); // .. OpenGL extensions (write them last, since it's a lot of text) const char* exts = oglExtList(); if (!exts) exts = "{unknown}"; fprintf(f, "\nOpenGL Extensions: \n%s\n", SplitExts(exts).c_str()); fclose(f); f = 0; } static int SetVideoMode(int w, int h, int bpp, bool fullscreen) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); ulong flags = SDL_OPENGL; if(fullscreen) flags |= SDL_FULLSCREEN; if(!SDL_SetVideoMode(w, h, bpp, flags)) return -1; glViewport(0, 0, w, h); #ifndef NO_GUI g_GUI.UpdateResolution(); #endif oglInit(); // required after each mode change if(SDL_SetGamma(g_Gamma, g_Gamma, g_Gamma) < 0) debug_warn("SDL_SetGamma failed"); return 0; } // identifies the file format that is to be written // (case-insensitive). examples: "bmp", "png", "jpg". // BMP is good for quick output at the expense of large files. static void WriteScreenshot(const char* extension = "png") { // determine next screenshot number. // // current approach: increment number until that file doesn't yet exist. // this is fairly slow, but it's typically only done once, since the last // number is cached. binary search shouldn't be necessary. // // known bug: after program restart, holes in the number series are // filled first. example: add 1st and 2nd; [exit] delete 1st; [restart] // add 3rd -> it gets number 1, not 3. // could fix via enumerating all files, but it's not worth it ATM. char fn[VFS_MAX_PATH]; const char* file_format_string = "screenshots/screenshot%04d.%s"; // %04d -> always 4 digits, so sorting by filename works correctly. static int next_num = 1; do sprintf(fn, file_format_string, next_num++, extension); while(vfs_exists(fn)); const int w = g_xres, h = g_yres; const int bpp = 24; GLenum fmt = GL_RGB; int flags = TEX_BOTTOM_UP; // we want writing BMP to be as fast as possible, // so read data from OpenGL in BMP format to obviate conversion. if(!stricmp(extension, "bmp")) { fmt = GL_BGR; flags |= TEX_BGR; } const size_t size = w * h * bpp; void* img = mem_alloc(size); glReadPixels(0, 0, w, h, fmt, GL_UNSIGNED_BYTE, img); if(tex_write(fn, w, h, bpp, flags, img) < 0) debug_warn("WriteScreenshot: tex_write failed"); mem_free(img); } // HACK: Let code from other files (i.e. the scripting system) quit void kill_mainloop() { quit = true; } -static int handler(const SDL_Event* ev) +static int MainInputHandler(const SDL_Event* ev) { int c; switch(ev->type) { case SDL_ACTIVEEVENT: g_active = ev->active.gain != 0; break; case SDL_MOUSEMOTION: g_mouse_x = ev->motion.x; g_mouse_y = ev->motion.y; break; case SDL_KEYDOWN: c = ev->key.keysym.sym; keys[c] = true; break; case SDL_HOTKEYDOWN: switch( ev->user.code ) { case HOTKEY_EXIT: quit = true; break; case HOTKEY_SCREENSHOT: WriteScreenshot(); break; case HOTKEY_PLAYMUSIC: { // MusicPlayer.open("audio/music/germanic peace 3.ogg"); // MusicPlayer.play(); Handle hs = snd_open( //"audio/music/germanic peace 3.ogg" "audio/voice/hellenes/soldier/Attack-ZeusSaviourandVictory.ogg" ); snd_set_pos(hs, 0,0,0, true); snd_play(hs); } break; default: return( EV_PASS ); } return( EV_HANDLED ); case SDL_KEYUP: c = ev->key.keysym.sym; keys[c] = false; break; case SDL_MOUSEBUTTONDOWN: c = ev->button.button; if( c < 5 ) mouseButtons[c] = true; else debug_warn("SDL mouse button defs changed; fix mouseButton array def"); break; case SDL_MOUSEBUTTONUP: c = ev->button.button; if( c < 5 ) mouseButtons[c] = false; else debug_warn("SDL mouse button defs changed; fix mouseButton array def"); break; } return EV_PASS; } void EndGame() { if (g_NetServer) { delete g_NetServer; g_NetServer=NULL; } else if (g_NetClient) { delete g_NetClient; g_NetClient=NULL; } delete g_Game; g_Game=NULL; } ///////////////////////////////////////////////////////////////////////////////////////////// // RenderNoCull: render absolutely everything to a blank frame to force renderer // to load required assets -void RenderNoCull() +static void RenderNoCull() { g_Renderer.BeginFrame(); if (g_Game) g_Game->GetView()->RenderNoCull(); g_Renderer.FlushFrame(); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); g_Renderer.EndFrame(); } static void Render() { MICROLOG(L"begin frame"); oglCheck(); #ifndef NO_GUI // HACK: because colour-parsing requires the GUI CStr skystring = "61 193 255"; CFG_GET_USER_VAL("skycolor", String, skystring); CColor skycol; GUI::ParseString(skystring, skycol); g_Renderer.SetClearColor(skycol.Int()); #endif // start new frame g_Renderer.BeginFrame(); oglCheck(); if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->Render(); oglCheck(); MICROLOG(L"flush frame"); PROFILE_START( "flush frame" ); g_Renderer.FlushFrame(); PROFILE_END( "flush frame" ); glPushAttrib( GL_ENABLE_BIT ); glDisable( GL_LIGHTING ); glDisable( GL_TEXTURE_2D ); glDisable( GL_DEPTH_TEST ); if( g_EntGraph ) { PROFILE( "render entity overlays" ); glColor3f( 1.0f, 0.0f, 1.0f ); MICROLOG(L"render entities"); g_EntityManager.renderAll(); // <-- collision outlines, pathing routes } PROFILE_START( "render selection" ); g_Mouseover.renderSelectionOutlines(); g_Selection.renderSelectionOutlines(); PROFILE_END( "render selection" ); glPopAttrib(); } else { PROFILE_START( "flush frame" ); g_Renderer.FlushFrame(); PROFILE_END( "flush frame" ); } oglCheck(); PROFILE_START( "render fonts" ); MICROLOG(L"render fonts"); // overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); PROFILE_END( "render fonts" ); oglCheck(); #ifndef NO_GUI // Temp GUI message GeeTODO glLoadIdentity(); MICROLOG(L"render GUI"); PROFILE_START( "render gui" ); g_GUI.Draw(); PROFILE_END( "render gui" ); #endif oglCheck(); // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Added -- glEnable(GL_TEXTURE_2D); // -- GL oglCheck(); { PROFILE( "render console" ); glLoadIdentity(); MICROLOG(L"render console"); CFont font("console"); font.Bind(); g_Console->Render(); } oglCheck(); // Profile information PROFILE_START( "render profiling" ); RenderProfile(); PROFILE_END( "render profiling" ); oglCheck(); if (g_Game && g_Game->IsGameStarted()) { PROFILE( "render selection overlays" ); g_Mouseover.renderOverlays(); g_Selection.renderOverlays(); } oglCheck(); // Draw the cursor (or set the Windows cursor, on Windows) cursor_draw(g_CursorName, g_mouse_x, g_mouse_y); // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); MICROLOG(L"end frame"); g_Renderer.EndFrame(); oglCheck(); } static void InitScripting() { TIMER(InitScripting); // Create the scripting host. This needs to be done before the GUI is created. new ScriptingHost; // It would be nice for onLoad code to be able to access the setTimeout() calls. new CScheduler; // Register the JavaScript interfaces with the runtime CEntity::ScriptingInit(); CBaseEntity::ScriptingInit(); JSI_Sound::ScriptingInit(); CProfileNode::ScriptingInit(); #ifndef NO_GUI JSI_IGUIObject::init(); JSI_GUITypes::init(); #endif JSI_Vector3D::init(); EntityCollection::Init( "EntityCollection" ); SColour::ScriptingInit(); CPlayer::ScriptingInit(); PlayerCollection::Init( "PlayerCollection" ); CDamageType::ScriptingInit(); CJSComplexPropertyAccessor::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor is already being compiled for T = CEntity. CScriptEvent::ScriptingInit(); CJSProgressTimer::ScriptingInit(); CProjectile::ScriptingInit(); g_ScriptingHost.DefineConstant( "ORDER_NONE", -1 ); g_ScriptingHost.DefineConstant( "ORDER_GOTO", CEntityOrder::ORDER_GOTO ); g_ScriptingHost.DefineConstant( "ORDER_PATROL", CEntityOrder::ORDER_PATROL ); g_ScriptingHost.DefineConstant( "ORDER_ATTACK", CEntityOrder::ORDER_ATTACK_MELEE ); g_ScriptingHost.DefineConstant( "ORDER_GATHER", CEntityOrder::ORDER_GATHER ); #define REG_JS_CONSTANT(_name) g_ScriptingHost.DefineConstant(#_name, _name) REG_JS_CONSTANT(SDL_BUTTON_LEFT); REG_JS_CONSTANT(SDL_BUTTON_MIDDLE); REG_JS_CONSTANT(SDL_BUTTON_RIGHT); REG_JS_CONSTANT(SDL_BUTTON_WHEELUP); REG_JS_CONSTANT(SDL_BUTTON_WHEELDOWN); #undef REG_JS_CONSTANT CNetMessage::ScriptingInit(); JSI_Camera::init(); JSI_Console::init(); new CGameEvents; } static void InitVfs(const char* argv0) { TIMER(InitVfs); // set current directory to "$game_dir/data". // this is necessary because it is otherwise unknown, // especially if run from a shortcut / symlink. // // "../data" is relative to the executable (in "$game_dir/system"). // // rationale for data/ being root: untrusted scripts must not be // allowed to overwrite critical game (or worse, OS) files. // the VFS prevents any accesses to files above this directory. int err = file_rel_chdir(argv0, "../data"); if(err < 0) throw err; { vfs_init(); vfs_mount("", "mods/official", VFS_MOUNT_RECURSIVE|VFS_MOUNT_ARCHIVES|VFS_MOUNT_WATCH); vfs_mount("screenshots/", "screenshots"); vfs_mount("profiles/", "profiles", VFS_MOUNT_RECURSIVE); } extern void vfs_dump_stats(); vfs_dump_stats(); // don't try vfs_display yet: SDL_Init hasn't yet redirected stdout } static void InitPs() { // console { TIMER(ps_console); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font("console"); g_Console->m_iFontHeight = font.GetLineSpacing(); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 9; } // language and hotkeys { TIMER(ps_lang_hotkeys); std::string lang = "english"; CFG_GET_SYS_VAL("language", String, lang); I18n::LoadLanguage(lang.c_str()); loadHotkeys(); } -#ifndef NO_GUI - { - // GUI uses VFS, so this must come after VFS init. - {TIMER(ps_gui_init); - g_GUI.Initialize();} - - {TIMER(ps_gui_setup_xml); - g_GUI.LoadXMLFile("gui/test/setup.xml");} - {TIMER(ps_gui_styles_xml); - g_GUI.LoadXMLFile("gui/test/styles.xml");} - {TIMER(ps_gui_sprite1_xml); - g_GUI.LoadXMLFile("gui/test/sprite1.xml");} - } + // GUI uses VFS, so this must come after VFS init. + GUI_Init(); +} - // Temporary hack until revised GUI structure is completed. -#ifdef ATLAS - if (! g_Atlas) -#endif - { -// TIMER(ps_gui_hack) - {TIMER(ps_gui_1); - g_GUI.LoadXMLFile("gui/test/1_init.xml");} - {TIMER(ps_gui_2); - g_GUI.LoadXMLFile("gui/test/2_mainmenu.xml");} - {TIMER(ps_gui_3); - g_GUI.LoadXMLFile("gui/test/3_loading.xml");} - {TIMER(ps_gui_4); - g_GUI.LoadXMLFile("gui/test/4_session.xml");} - {TIMER(ps_gui_6); - g_GUI.LoadXMLFile("gui/test/6_subwindows.xml");} - {TIMER(ps_gui_6_1); - g_GUI.LoadXMLFile("gui/test/6_1_manual.xml");} - {TIMER(ps_gui_6_2); - g_GUI.LoadXMLFile("gui/test/6_2_jukebox.xml");} - {TIMER(ps_gui_7); - g_GUI.LoadXMLFile("gui/test/7_atlas.xml");} - {TIMER(ps_gui_9); - g_GUI.LoadXMLFile("gui/test/9_global.xml");} - } - -// { -// TIMER(ps_gui_hello_xml); -// g_GUI.LoadXMLFile("gui/test/hello.xml"); -// } +static void InitInput() +{ + // register input handlers + // This stack is constructed so the first added, will be the last + // one called. This is important, because each of the handlers + // has the potential to block events to go further down + // in the chain. I.e. the last one in the list added, is the + // only handler that can block all messages before they are + // processed. + in_add_handler(game_view_handler); + + in_add_handler(interactInputHandler); + + in_add_handler(conInputHandler); + + in_add_handler(profilehandler); + + in_add_handler(hotkeyInputHandler); + + // gui_handler needs to be after (i.e. called before!) the hotkey handler + // so that input boxes can be typed in without setting off hotkeys. +#ifndef NO_GUI + in_add_handler(gui_handler); #endif + + // must be after gui_handler. Should mayhap even be last. + in_add_handler(MainInputHandler); } -static void psShutdown() + +static void ShutdownPs() { -#ifndef NO_GUI - g_GUI.Destroy(); - delete &g_GUI; -#endif + GUI_Shutdown(); delete g_Console; // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(0, g_mouse_x, g_mouse_y); // close down Xerces if it was loaded CXeromyces::Terminate(); MusicPlayer.release(); // Unload the real language (since it depends on the scripting engine, // which is going to be killed later) and use the English fallback messages I18n::LoadLanguage(NULL); } static void InitRenderer() { TIMER(InitRenderer); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_NOPBUFFER,g_NoPBuffer); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff new CTextureManager; // create the material manager new CMaterialManager; new CMeshManager; // create actor related stuff new CSkeletonAnimManager; new CObjectManager; new CUnitManager; MICROLOG(L"init renderer"); g_Renderer.Open(g_xres,g_yres,g_bpp); // Setup default lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. g_LightEnv.m_SunColor=RGBColor(1,1,1); g_LightEnv.SetRotation(DEGTORAD(270)); g_LightEnv.SetElevation(DEGTORAD(45)); g_LightEnv.m_TerrainAmbientColor=RGBColor(0,0,0); g_LightEnv.m_UnitsAmbientColor=RGBColor(0.4f,0.4f,0.4f); g_Renderer.SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; g_Renderer.SetViewport(vp); } static void InitSDL() { MICROLOG(L"init sdl"); if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOG(ERROR, LOG_CATEGORY, "SDL library initialization failed: %s", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); } static int ProgressiveLoad() { wchar_t description[100]; int progress_percent; int ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent); switch(ret) { // no load active => no-op (skip code below) case 0: return 0; // current task didn't complete. we only care about this insofar as the // load process is therefore not yet finished. case ERR_TIMED_OUT: break; // just finished loading case LDR_ALL_FINISHED: g_Game->ReallyStartGame(); wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting.."); // LDR_ProgressiveLoad returns L""; set to valid text to // avoid problems in converting to JSString break; // error! default: CHECK_ERR(ret); // can't do this above due to legit ERR_TIMED_OUT break; } -#ifndef NO_GUI - // display progress / description in loading screen - CStrW i18n_description = I18n::translate(description); - JSString* js_desc = StringConvert::wstring_to_jsstring(g_ScriptingHost.getContext(), i18n_description); - g_ScriptingHost.SetGlobal("g_Progress", INT_TO_JSVAL(progress_percent)); - g_ScriptingHost.SetGlobal("g_LoadDescription", STRING_TO_JSVAL(js_desc)); - g_GUI.SendEventToAll("progress"); -#endif - + GUI_DisplayLoadProgress(progress_percent, description); return 0; } -u64 PREVTSC; - static void Shutdown() { MICROLOG(L"Shutdown"); - psShutdown(); // Must delete g_GUI before g_ScriptingHost + ATLAS_Shutdown(); + + ShutdownPs(); // Must delete g_GUI before g_ScriptingHost if (g_Game) EndGame(); delete &g_Scheduler; delete &g_SessionManager; delete &g_Mouseover; delete &g_Selection; delete &g_Pathfinder; // Managed by CWorld // delete &g_EntityManager; delete &g_GameAttributes; delete &g_JSGameEvents; delete &g_EntityTemplateCollection; delete &g_ScriptingHost; // destroy actor related stuff delete &g_UnitMan; delete &g_ObjMan; delete &g_SkelAnimMan; delete &g_MaterialManager; delete &g_MeshManager; // destroy terrain related stuff delete &g_TexMan; // destroy renderer delete &g_Renderer; delete &g_ConfigDB; // Shut down the network loop CSocketBase::Shutdown(); // Really shut down the i18n system. Any future calls // to translate() will crash. I18n::Shutdown(); snd_shutdown(); vfs_shutdown(); h_mgr_shutdown(); mem_shutdown(); debug_shutdown(); delete &g_Logger; delete &g_Profiler; } // workaround for VC7 EBP-trashing bug, which confuses the stack trace code. #if MSC_VERSION -#pragma optimize("", off) +# pragma optimize("", off) #endif static void Init(int argc, char* argv[], bool setup_gfx = true) { debug_printf("INIT &argc=%p &argv=%p\n", &argc, &argv); MICROLOG(L"Init"); debug_set_thread_name("main"); // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(187); // no longer set 24 bit (float) precision by default: for // very long game uptimes (> 1 day; e.g. dedicated server), // we need full precision when calculating the time. // if there's a spot where we want to speed up divides|sqrts, // we can temporarily change precision there. // _control87(_PC_24, _MCW_PC); // detects CPU clock frequency and capabilities, which are prerequisites // for using the TSC as a timer (desirable due to its high resolution). // do this before lengthy init so we can time those accurately. get_cpu_info(); // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. MICROLOG(L"init vfs"); const char* argv0 = argc? argv[0] : NULL; // ScEd doesn't have a main(argc, argv), and so it has no argv. In that // case, just pass NULL to InitVfs, which will work out the current // directory for itself. InitVfs(argv0); // This must come after VFS init, which sets the current directory // (required for finding our output log files). new CLogger; // Call LoadLanguage(NULL) to initialise the I18n system, but // without loading an actual language file - translate() will // just show the English key text, which is better than crashing // from a null pointer when attempting to translate e.g. error messages. // Real languages can only be loaded when the scripting system has // been initialised. // // this uses LOG and must therefore come after CLogger init. MICROLOG(L"init i18n"); I18n::LoadLanguage(NULL); + // should be done before the bulk of GUI init because it prevents + // most loads from happening (since ATLAS_IsRunning will return true). + ATLAS_RunIfOnCmdLine(argc, argv); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); if(setup_gfx) InitSDL(); // preferred video mode = current desktop settings // (command line params may override these) get_cur_vmode(&g_xres, &g_yres, &g_bpp, &g_freq); new CProfileManager; // before any script code MICROLOG(L"init scripting"); InitScripting(); // before GUI // g_ConfigDB, command line args, globals CFG_Init(argc, argv); // GUI is notified in SetVideoMode, so this must come before that. #ifndef NO_GUI new CGUI; #endif bool windowed = false; CFG_GET_SYS_VAL("windowed", Bool, windowed); if (setup_gfx) { MICROLOG(L"set vmode"); if(SetVideoMode(g_xres, g_yres, 32, !windowed) < 0) { LOG(ERROR, LOG_CATEGORY, "Could not set %dx%d graphics mode: %s", g_xres, g_yres, SDL_GetError()); throw PSERROR_System_VmodeFailed(); } SDL_WM_SetCaption("0 A.D.", "0 A.D."); } oglCheck(); if(!g_Quickstart) { WriteSystemInfo(); vfs_display(); } else { // speed up startup by disabling all sound // (OpenAL init will be skipped). // must be called before first snd_open. snd_disable(true); } // (must come after SetVideoMode, since it calls oglInit) const char* missing = oglHaveExtensions(0, "GL_ARB_multitexture", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", 0); if(missing) { wchar_t buf[500]; const wchar_t* fmt = L"The %hs extension doesn't appear to be available on your computer. " L"The game may still work, though - you are welcome to try at your own risk. " L"If not or it doesn't look right, upgrade your graphics card."; swprintf(buf, ARRAY_SIZE(buf), fmt, missing); DISPLAY_ERROR(buf); // TODO: i18n } // enable/disable VSync // note: "GL_EXT_SWAP_CONTROL" is "historical" according to dox. #if OS_WIN if(oglHaveExtension("WGL_EXT_swap_control")) wglSwapIntervalEXT(g_VSync? 1 : 0); #endif MICROLOG(L"init ps"); InitPs(); oglCheck(); InitRenderer(); TIMER(init_after_InitRenderer); // This needs to be done after the renderer has loaded all its actors... new CBaseEntityCollection; // CEntityManager is managed by CWorld //new CEntityManager; new CPathfindEngine; new CSelectedEntities; new CMouseoverEntities; new CSessionManager; new CGameAttributes; // Register a few Game/Network JS globals g_ScriptingHost.SetGlobal("g_GameAttributes", OBJECT_TO_JSVAL(g_GameAttributes.GetScript())); // Check for heap corruption after every allocation. Very, very slowly. // (And it highlights the allocation just after the one you care about, // so you need to run it again and tell it to break on the one before.) // debug_heap_enable(DEBUG_HEAP_ALL); - // register input handlers - { - // This stack is constructed so the first added, will be the last - // one called. This is important, because each of the handlers - // has the potential to block events to go further down - // in the chain. I.e. the last one in the list added, is the - // only handler that can block all messages before they are - // processed. - in_add_handler(game_view_handler); - - in_add_handler(interactInputHandler); - - in_add_handler(conInputHandler); - - in_add_handler(profilehandler); - - in_add_handler(hotkeyInputHandler); - - // I don't know how much this screws up, but the gui_handler needs - // to be after the hotkey, so that input boxes can be typed in - // without setting off hotkeys. -#ifndef NO_GUI - in_add_handler(gui_handler); -#endif - - in_add_handler(handler); // must be after gui_handler. Should mayhap even be last. - } + InitInput(); oglCheck(); #ifndef NO_GUI g_GUI.SendEventToAll("load"); #endif if (setup_gfx) { MICROLOG(L"render blank"); // render everything to a blank frame to force renderer to load everything RenderNoCull(); } if (g_FixedFrameTiming) { CCamera &g_Camera=*g_Game->GetView()->GetCamera(); #if 0 // TOPDOWN g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(90)); g_Camera.m_Orientation.SetIdentity(); g_Camera.m_Orientation.RotateX(DEGTORAD(90)); g_Camera.m_Orientation.Translate(CELL_SIZE*250*0.5, 250, CELL_SIZE*250*0.5); #else // std view g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(20)); g_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); g_Camera.m_Orientation.RotateY(DEGTORAD(-45)); g_Camera.m_Orientation.Translate(350, 350, -275); #endif g_Camera.UpdateFrustum(); } - - g_Console->RegisterFunc(Testing, L"Testing"); } #if MSC_VERSION -#pragma optimize("", on) // restore; see above. +# pragma optimize("", on) // restore; see above. #endif static void Frame() { MICROLOG(L"Frame"); oglCheck(); PROFILE_START( "update music" ); MusicPlayer.update(); PROFILE_END( "update music" ); static double last_time; const double time = get_time(); const float TimeSinceLastFrame = (float)(time-last_time); last_time = time; ONCE(return); // first call: set last_time and return debug_assert(TimeSinceLastFrame >= 0.0f); PROFILE_START( "reload changed files" ); MICROLOG(L"reload files"); vfs_reload_changed_files(); PROFILE_END( "reload changed files" ); oglCheck(); PROFILE_START( "progressive load" ); ProgressiveLoad(); PROFILE_END( "progressive load" ); PROFILE_START( "input" ); MICROLOG(L"input"); in_get_events(); g_SessionManager.Poll(); PROFILE_END( "input" ); oglCheck(); PROFILE_START( "gui tick" ); #ifndef NO_GUI g_GUI.TickObjects(); #endif PROFILE_END( "gui tick" ); oglCheck(); PROFILE_START( "game logic" ); if (g_Game && g_Game->IsGameStarted()) { PROFILE_START( "simulation update" ); g_Game->Update(TimeSinceLastFrame); PROFILE_END( "simulation update" ); if (!g_FixedFrameTiming) { PROFILE( "camera update" ); g_Game->GetView()->Update(float(TimeSinceLastFrame)); } PROFILE_START( "selection and interaction ui" ); // TODO Where does GameView end and other things begin? g_Mouseover.update( TimeSinceLastFrame ); g_Selection.update(); PROFILE_END( "selection and interaction ui" ); PROFILE_START( "sound update" ); CCamera* camera = g_Game->GetView()->GetCamera(); CMatrix3D& orientation = camera->m_Orientation; float* pos = &orientation._data[12]; float* dir = &orientation._data[8]; float* up = &orientation._data[4]; if(snd_update(pos, dir, up) < 0) debug_printf("snd_update failed\n"); PROFILE_END( "sound update" ); } else { // CSimulation would do this with the proper turn length if we were in // a game. This is basically just to keep script timers running. uint ms_elapsed = (uint)(TimeSinceLastFrame*1000); g_Scheduler.update(ms_elapsed); if(snd_update(0, 0, 0) < 0) debug_printf("snd_update (pos=0 version) failed\n"); } PROFILE_END( "game logic" ); PROFILE_START( "update console" ); g_Console->Update(TimeSinceLastFrame); PROFILE_END( "update console" ); // ugly, but necessary. these are one-shot events, have to be reset. // Spoof mousebuttonup events for the hotkey system SDL_Event spoof; spoof.type = SDL_MOUSEBUTTONUP; spoof.button.button = SDL_BUTTON_WHEELUP; if( mouseButtons[SDL_BUTTON_WHEELUP] ) hotkeyInputHandler( &spoof ); spoof.button.button = SDL_BUTTON_WHEELDOWN; if( mouseButtons[SDL_BUTTON_WHEELDOWN] ) hotkeyInputHandler( &spoof ); mouseButtons[SDL_BUTTON_WHEELUP] = false; mouseButtons[SDL_BUTTON_WHEELDOWN] = false; oglCheck(); PROFILE_START( "render" ); if(g_active) { MICROLOG(L"render"); Render(); MICROLOG(L"finished render"); PROFILE_START( "swap buffers" ); SDL_GL_SwapBuffers(); PROFILE_END( "swap buffers" ); } // inactive; relinquish CPU for a little while // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored else SDL_Delay(10); PROFILE_END( "render" ); g_Profiler.Frame(); calc_fps(); if (g_FixedFrameTiming && frameCount==100) quit=true; } #ifndef SCED int main(int argc, char* argv[]) { debug_printf("MAIN &argc=%p &argv=%p\n", &argc, &argv); -#ifdef ATLAS - if (g_Atlas) - { - extern void BeginAtlas(int, char**); - BeginAtlas(argc, argv); - } - else -#endif - { - Init(argc, argv, true); + Init(argc, argv, true); - // Optionally, do some simple tests to ensure things aren't broken - // extern void PerformTests(); - // PerformTests(); + // Optionally, do some simple tests to ensure things aren't broken + // extern void PerformTests(); + // PerformTests(); - while(!quit) - Frame(); + while(!quit) + Frame(); - Shutdown(); - } + Shutdown(); exit(0); } // Public functions for Atlas to use: // TODO: Make this far less hacky void Init_(int argc, char** argv, bool setup_gfx) { g_Quickstart = true; Init(argc, argv, setup_gfx); } void Shutdown_() { Shutdown(); } void Render_() { Render(); } #else // SCED: void ScEd_Init() { g_Quickstart = true; Init(0, NULL, false); } void ScEd_Shutdown() { Shutdown(); } #endif // SCED Index: ps/trunk/source/lib/sysdep/win/wposix.h =================================================================== --- ps/trunk/source/lib/sysdep/win/wposix.h (revision 2618) +++ ps/trunk/source/lib/sysdep/win/wposix.h (revision 2619) @@ -1,395 +1,412 @@ // misc. POSIX routines for Win32 // // Copyright (c) 2003 Jan Wassenberg // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // Contact info: // Jan.Wassenberg@stud.uni-karlsruhe.de // http://www.stud.uni-karlsruhe.de/~urkt/ // note: try to avoid redefining CRT functions - if building against the // DLL CRT, the declarations will be incompatible. adding _CRTIMP to the decl // is a last resort (e.g. if the regular CRT headers would conflict). #ifndef __WPOSIX_H__ #define __WPOSIX_H__ // split out of this module. #include "wposix_types.h" #include "waio.h" #include "wsock.h" #include "wtime.h" #include "wpthread.h" #ifdef __cplusplus extern "C" { #endif // we define some CRT functions (e.g. access), because they're otherwise // only brought in by win-specific headers (here, ). // define correctly for static or DLL CRT in case the original header // is included, to avoid conflict warnings. #ifndef _CRTIMP # ifdef _DLL # define _CRTIMP __declspec(dllimport) # else # define _CRTIMP # endif #endif // // // #define PATH_MAX 255 // Win32 MAX_PATH is 260 #if OS_WIN # ifndef SIZE_MAX // VC2005 already defines this in limits.h # define SIZE_MAX 0xffffffff # endif #else # define SIZE_MAX 0xffffffffffffffff #endif // // // #include // not defined there: #define EINPROGRESS 100000 #define ETIMEDOUT (60) // matches NetworkInternal.h def /* enum { EINPROGRESS = 1000, // Operation in progress. ENOMEM // Not enough space. EACCES, // Permission denied. EADDRINUSE, // Address in use. EADDRNOTAVAIL, // Address not available. EAGAIN, // Resource unavailable, try again (may be the same value as EWOULDBLOCK]). EALREADY, // Connection already in progress. EBADF, // Bad file descriptor. ECANCELED, // Operation canceled. ECONNABORTED, // Connection aborted. ECONNREFUSED, // Connection refused. ECONNRESET, // Connection reset. EDOM, // Mathematics argument out of domain of function. EEXIST, // File exists. EFAULT, // Bad address. EHOSTUNREACH, // Host is unreachable. EINTR, // Interrupted function. EINVAL, // Invalid argument. EISCONN, // Socket is connected. EISDIR, // Is a directory. ENAMETOOLONG, // Filename too long. ENETDOWN, // Network is down. ENETRESET, // Connection aborted by network. ENETUNREACH, // Network unreachable. ENOENT, // No such file or directory. ENOEXEC, // Executable file format error. ENOSPC, // No space left on device. ENOSYS, // Function not supported. ENOTCONN, // The socket is not connected. ENOTDIR, // Not a directory. ENOTEMPTY, // Directory not empty. ENOTSOCK, // Not a socket. ENOTSUP, // Not supported. EOVERFLOW, // Value too large to be stored in data type. EPERM, // Operation not permitted. EPIPE, // Broken pipe. EPROTO, // Protocol error. ERANGE, // Result too large. ETIMEDOUT, // Connection timed out. EWOULDBLOCK // Operation would block (may be the same value as EAGAIN]). }; */ // // sys/stat.h // // already defined by MinGW #if MSC_VERSION typedef unsigned int mode_t; #endif // VC libc includes stat, but it's quite slow. // we implement our own, but use the CRT struct definition. // rename the VC function definition to avoid conflict. /* #define stat vc_stat // // Extra hack for VC++ 2005, since it defines inline stat/fstat // functions inside stat.h (which get confused by the // macro-renaming of "stat") # if MSC_VERSION >= 1400 # define RC_INVOKED // stat.h only includes stat.inl if "!defined(RC_INVOKED) && !defined(__midl)" # include # undef RC_INVOKED # else # include # endif #undef stat */ # include extern int mkdir(const char*, mode_t); // currently only sets st_mode (file or dir) and st_size. //extern int stat(const char*, struct stat*); #define S_IRWXO 0xffff #define S_IRWXU 0xffff #define S_IRWXG 0xffff // stat.h _S_* values are wrong! disassembly shows _S_IWRITE is 0x80, // instead of 0x100. define christmas-tree value to be safe. #define S_ISDIR(m) (m & S_IFDIR) #define S_ISREG(m) (m & S_IFREG) // // dirent.h // typedef void DIR; struct dirent { // note: SUSv3 describes this as a "char array" but of unspecified size. // since that precludes using sizeof(), we may as well declare as a // pointer to avoid copying in the implementation. char* d_name; }; extern DIR* opendir(const char* name); extern struct dirent* readdir(DIR*); extern int closedir(DIR*); // return status for the file returned by the last successful // readdir call from the given directory stream. // currently sets st_size, st_mode, and st_mtime; the rest are zeroed. // non-portable, but considerably faster than stat(). used by file_enum. extern int readdir_stat_np(DIR*, struct stat*); // // // // mmap prot flags #define PROT_NONE 0x00 #define PROT_READ 0x01 #define PROT_WRITE 0x02 #define PROT_EXEC 0x04 // mmap flags #define MAP_SHARED 0x01 // writes change the underlying file #define MAP_PRIVATE 0x02 // writes do not affect the file (copy-on-write) #define MAP_FIXED 0x04 // .. non-portable #define MAP_ANONYMOUS 0x10 #define MAP_NORESERVE 0x20 // note: we need a means of only "reserving" virtual address ranges // for the fixed-address expandable array mechanism. the non-portable // MAP_NORESERVE flag says that no space in the page file need be reserved. // the caller can still try to access the entire mapping, but might get // SIGBUS if there isn't enough room to commit a page. Linux currently // doesn't commit mmap-ed regions anyway, but we specify this flag to // make sure of that in the future. #define MAP_FAILED 0 extern void* mmap(void* start, size_t len, int prot, int flags, int fd, off_t offset); extern int munmap(void* start, size_t len); extern int mprotect(void* addr, size_t len, int prot); // // // // values from MS _open - do not change! #define O_RDONLY 0x0000 // open for reading only #define O_WRONLY 0x0001 // open for writing only #define O_RDWR 0x0002 // open for reading and writing #define O_APPEND 0x0008 // writes done at eof #define O_CREAT 0x0100 // create and open file #define O_TRUNC 0x0200 // open and truncate #define O_EXCL 0x0400 // open only if file doesn't already exist // .. Win32-only (not specified by POSIX) #define O_TEXT_NP 0x4000 // file mode is text (translated) #define O_BINARY_NP 0x8000 // file mode is binary (untranslated) // .. wposix.cpp only (bit values not used by MS _open) #define O_NO_AIO_NP 0x20000 // wposix-specific: do not open a separate AIO-capable handle. // this is used for small files where AIO overhead isn't worth it, // thus speeding up loading and reducing resource usage. // .. not supported by Win32 (bit values not used by MS _open) #define O_NONBLOCK 0x1000000 // redefinition error here => io.h is getting included somewhere. // we implement this function, so the io.h definition conflicts if // compiling against the DLL CRT. either rename the io.h def // (as with vc_stat), or don't include io.h. extern int open(const char* fn, int mode, ...); // // // // values from MS _access() implementation. do not change. #define F_OK 0 #define R_OK 4 #define W_OK 2 #define X_OK 0 // MS implementation doesn't support this distinction. // hence, the file is reported executable if it exists. extern int read (int fd, void* buf, size_t nbytes); // thunk extern int write(int fd, void* buf, size_t nbytes); // thunk extern _CRTIMP off_t lseek(int fd, off_t ofs, int whence); // redefinition error here => io.h is getting included somewhere. // we implement this function, so the io.h definition conflicts if // compiling against the DLL CRT. either rename the io.h def // (as with vc_stat), or don't include io.h. extern int close(int); extern _CRTIMP int access(const char*, int); extern int chdir(const char*); #undef getcwd extern char* getcwd(char*, size_t); // user tests if available via #ifdef; can't use enum. #define _SC_PAGESIZE 1 #define _SC_PAGE_SIZE 1 #define _SC_PHYS_PAGES 2 #define _SC_AVPHYS_PAGES 3 extern long sysconf(int name); // // // extern char* realpath(const char*, char*); // // // #define TCSANOW 0 struct termios { long c_lflag; }; #define ICANON 2 // do not change - correspond to ENABLE_LINE_INPUT / ENABLE_ECHO_INPUT #define ECHO 4 extern int tcgetattr(int fd, struct termios* termios_p); extern int tcsetattr(int fd, int optional_actions, const struct termios* termios_p); // // // struct pollfd { int fd; short int events, revents; }; #define POLLIN 1 extern int poll(struct pollfd[], int, int); // +// +// + +// these have no meaning for the Windows GetProcAddress implementation, +// so they are ignored but provided for completeness. +#define RTLD_LAZY 0x01 +#define RTLD_NOW 0x02 +#define RTLD_GLOBAL 0x04 // semantics are unsupported, so complain if set. +#define RTLD_LOCAL 0x08 + +extern int dlclose(void* handle); +extern char* dlerror(void); +extern void* dlopen(const char* so_name, int flags); +extern void* dlsym(void* handle, const char* sym_name); + + +// // // struct utsname { char sysname[9]; // Name of this implementation of the operating system. char nodename[16]; // Name of this node within an implementation-defined // communications network. // Win9x requires this minimum buffer size. char release[9]; // Current release level of this implementation. char version[16]; // Current version level of this release. char machine[9]; // Name of the hardware type on which the system is running. }; extern int uname(struct utsname*); // // serial port IOCTL // // use with TIOCMBIS #define TIOCM_RTS 1 // use with TIOCMGET or TIOCMIWAIT #define TIOCM_CD 0x80 // MS_RLSD_ON #define TIOCM_CTS 0x10 // MS_CTS_ON enum { TIOCMBIS, // set control line TIOCMGET, // get line state TIOCMIWAIT // wait for status change }; extern int ioctl(int fd, int op, int* data); #ifndef _WINSOCKAPI_ #define FIONREAD 0 #endif extern void _get_console(void); extern void _hide_console(void); #ifdef __cplusplus } #endif #endif // #ifndef __WPOSIX_H__ Index: ps/trunk/source/lib/sysdep/win/wposix.cpp =================================================================== --- ps/trunk/source/lib/sysdep/win/wposix.cpp (revision 2618) +++ ps/trunk/source/lib/sysdep/win/wposix.cpp (revision 2619) @@ -1,898 +1,959 @@ // misc. POSIX routines for Win32 // // Copyright (c) 2004-2005 Jan Wassenberg // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // Contact info: // Jan.Wassenberg@stud.uni-karlsruhe.de // http://www.stud.uni-karlsruhe.de/~urkt/ #include "precompiled.h" #include #include #include "lib.h" #include "posix.h" #include "win_internal.h" #include "sysdep/cpu.h" // cast intptr_t to HANDLE; centralized for easier changing, e.g. avoiding // warnings. i = -1 converts to INVALID_HANDLE_VALUE (same value). static HANDLE HANDLE_from_intptr(intptr_t i) { return (HANDLE)((char*)0 + i); } ////////////////////////////////////////////////////////////////////////////// // // file // ////////////////////////////////////////////////////////////////////////////// int open(const char* fn, int oflag, ...) { const bool is_com_port = strncmp(fn, "/dev/tty", 8) == 0; // also used later, before aio_reopen // translate "/dev/tty%d" to "COM%d" if(is_com_port) { char port[] = "COM1"; const char digit = fn[8]+1; // PCs only support COM1..COM4. if(!('1' <= digit && digit <= '4')) return -1; port[3] = digit; fn = port; } mode_t mode = 0; if(oflag & O_CREAT) { va_list args; va_start(args, oflag); mode = va_arg(args, mode_t); va_end(args); } WIN_SAVE_LAST_ERROR; // CreateFile int fd = _open(fn, oflag, mode); WIN_RESTORE_LAST_ERROR; // cases when we don't want to open a second AIO-capable handle: // .. stdin/stdout/stderr if(fd <= 2) goto no_aio; // .. COM port - we don't currently need AIO access for those, and // aio_reopen's CreateFile would fail with "access denied". if(is_com_port) goto no_aio; // .. caller is requesting we skip it (see file_open) if(oflag & O_NO_AIO_NP) goto no_aio; // none of the above apply; now re-open the file. // note: this is possible because _open defaults to DENY_NONE sharing. WARN_ERR(aio_reopen(fd, fn, oflag)); no_aio: // CRT doesn't like more than 255 files open. // warn now, so that we notice why so many are open. #ifndef NDEBUG if(fd > 256) debug_warn("wposix: too many files open (CRT limitation)"); #endif return fd; } int close(int fd) { debug_assert(3 <= fd && fd < 256); // note: there's no good way to notify us that wasn't opened for // AIO, so we could skip aio_close. storing a bit in the fd is evil and // a fd -> info map is redundant (waio already has one). // therefore, we require aio_close to fail gracefully. WARN_ERR(aio_close(fd)); return _close(fd); } // we don't want to #define read to _read, since that's a fairly common // identifier. therefore, translate from MS CRT names via thunk functions. // efficiency is less important, and the overhead could be optimized away. int read(int fd, void* buf, size_t nbytes) { return _read(fd, buf, nbytes); } int write(int fd, void* buf, size_t nbytes) { return _write(fd, buf, nbytes); } int ioctl(int fd, int op, int* data) { const HANDLE h = HANDLE_from_intptr(_get_osfhandle(fd)); switch(op) { case TIOCMGET: /* TIOCM_* mapped directly to MS_*_ON */ GetCommModemStatus(h, (DWORD*)data); break; case TIOCMBIS: /* only RTS supported */ if(*data & TIOCM_RTS) EscapeCommFunction(h, SETRTS); else EscapeCommFunction(h, CLRRTS); break; case TIOCMIWAIT: static DWORD mask; DWORD new_mask = 0; if(*data & TIOCM_CD) new_mask |= EV_RLSD; if(*data & TIOCM_CTS) new_mask |= EV_CTS; if(new_mask != mask) SetCommMask(h, mask = new_mask); WaitCommEvent(h, &mask, 0); break; } return 0; } // // determine file system type on the current drive - // needed to work around incorrect FAT time translation. // static enum Filesystem { FS_INVALID, // detect_filesystem() not yet called FS_FAT, // FAT12, FAT16, or FAT32 FS_NTFS, // (most common) FS_UNKNOWN // newer FS we don't know about } filesystem; // rationale: the previous method of checking every path was way too slow // (taking ~800ms total during init). instead, we only determine the FS once. // this is quite a bit easier than intercepting chdir() calls and/or // caching FS type per drive letter, but not foolproof. // // if some data files are on a different volume that is set up as FAT, // the workaround below won't be triggered (=> timestamps may be off by // 1 hour when DST is in effect). oh well, that is not a supported. // // the common case (everything is on a single NTFS volume) is more important // and must run without penalty. // called from the first filetime_to_time_t() call, not win.cpp init; // this means we can rely on the current directory having been set to // the app's directory (and therefore its appendant volume - see above). static void detect_filesystem() { char root_path[MAX_PATH] = "c:\\"; // default in case GCD fails DWORD gcd_ret = GetCurrentDirectory(sizeof(root_path), root_path); debug_assert(gcd_ret != 0); // if this fails, no problem - we have the default from above. root_path[3] = '\0'; // cut off after "c:\" char fs_name[32] = {0}; BOOL ret = GetVolumeInformation(root_path, 0,0,0,0,0, fs_name, sizeof(fs_name)); fs_name[ARRAY_SIZE(fs_name)-1] = '\0'; debug_assert(ret != 0); // if this fails, no problem - we really only care if fs is FAT, // and will assume that's not the case (since fs_name != "FAT"). filesystem = FS_UNKNOWN; if(!strncmp(fs_name, "FAT", 3)) // e.g. FAT32 filesystem = FS_FAT; else if(!strcmp(fs_name, "NTFS")) filesystem = FS_NTFS; } // from wtime extern time_t local_filetime_to_time_t(FILETIME* ft); extern time_t utc_filetime_to_time_t(FILETIME* ft); // convert Windows FILETIME to POSIX time_t (seconds-since-1970 UTC); // used by stat and readdir_stat_np for st_mtime. // // works around a documented Windows bug in converting FAT file times // (correct results are desired since VFS mount logic considers // files 'equal' if their mtime and size are the same). static time_t filetime_to_time_t(FILETIME* ft) { ONCE(detect_filesystem()); // the FAT file system stores local file times, while // NTFS records UTC. Windows does convert automatically, // but uses the current DST settings. (boo!) // we go back to local time, and convert properly. if(filesystem == FS_FAT) { FILETIME local_ft; FileTimeToLocalFileTime(ft, &local_ft); return local_filetime_to_time_t(&local_ft); } return utc_filetime_to_time_t(ft); } /* // currently only sets st_mode (file or dir) and st_size. int stat(const char* fn, struct stat* s) { memset(s, 0, sizeof(struct stat)); WIN32_FILE_ATTRIBUTE_DATA fad; if(!GetFileAttributesEx(fn, GetFileExInfoStandard, &fad)) return -1; s->st_mtime = filetime_to_time_t(fad.ftLastAccessTime) // dir if(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) s->st_mode = S_IFDIR; else { s->st_mode = S_IFREG; s->st_size = (off_t)((((u64)fad.nFileSizeHigh) << 32) | fad.nFileSizeLow); } return 0; } */ ////////////////////////////////////////////////////////////////////////////// // // dir // ////////////////////////////////////////////////////////////////////////////// #undef getcwd char* getcwd(char* buf, size_t buf_size) { return _getcwd(buf, buf_size); } char* realpath(const char* fn, char* path) { if(!GetFullPathName(fn, PATH_MAX, path, 0)) return 0; return path; } int mkdir(const char* path, mode_t) { return CreateDirectory(path, 0)? 0 : -1; } // opendir/readdir/closedir // // note: we avoid opening directories or returning entries that have // hidden or system attributes set. this is to prevent returning something // like "\System Volume Information", which raises an error upon opening. // 0-initialized by wdir_alloc for safety; this is required for // num_entries_scanned. struct WDIR { HANDLE hFind; // the dirent returned by readdir. // note: having only one global instance is not possible because // multiple independent opendir/readdir sequences must be supported. struct dirent ent; WIN32_FIND_DATA fd; // since opendir calls FindFirstFile, we need a means of telling the // first call to readdir that we already have a file. // that's the case iff this is == 0; we use a counter rather than a // flag because that allows keeping statistics. int num_entries_scanned; }; // suballocator - satisfies most requests with a reusable static instance. // this avoids hundreds of alloc/free which would fragment the heap. // to guarantee thread-safety, we fall back to malloc if the instance is // already in use. (it's important to avoid suprises since this is such a // low-level routine). static WDIR global_wdir; static uintptr_t global_wdir_is_in_use; // zero-initializes the WDIR (code below relies on this) static inline WDIR* wdir_alloc() { WDIR* d; // successfully reserved the global instance if(CAS(&global_wdir_is_in_use, 0, 1)) { d = &global_wdir; memset(d, 0, sizeof(*d)); } else d = (WDIR*)calloc(1, sizeof(WDIR)); return d; } static inline void wdir_free(WDIR* d) { if(d == &global_wdir) global_wdir_is_in_use = 0; else free(d); } static const DWORD hs = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM; // make sure path exists and is a normal (according to attributes) directory. static bool is_normal_dir(const char* path) { const DWORD fa = GetFileAttributes(path); // .. path not found if(fa == INVALID_FILE_ATTRIBUTES) return false; // .. not a directory if((fa & FILE_ATTRIBUTE_DIRECTORY) == 0) return false; // .. hidden or system attribute(s) set if((fa & hs) != 0) return false; return true; } DIR* opendir(const char* path) { if(!is_normal_dir(path)) { errno = ENOENT; goto fail; } { WDIR* d = wdir_alloc(); if(!d) { errno = ENOMEM; goto fail; } // build search path for FindFirstFile. note: "path\\dir" only returns // information about that directory; trailing slashes aren't allowed. // for dir entries to be returned, we have to append "\\*". char search_path[PATH_MAX]; snprintf(search_path, ARRAY_SIZE(search_path), "%s\\*", path); // note: we could store search_path and defer FindFirstFile until // readdir. this way is a bit more complex but required for // correctness (we must return a valid DIR iff is valid). d->hFind = FindFirstFileA(search_path, &d->fd); if(d->hFind == INVALID_HANDLE_VALUE) { // actual failure (not just an empty directory) if(GetLastError() != ERROR_NO_MORE_FILES) { // unfortunately there's no way around this; we need to allocate // d before FindFirstFile because it uses d->fd. wdir_free(d); errno = 0; // unknown goto fail; } } // success return d; } fail: debug_warn("opendir failed"); return 0; } struct dirent* readdir(DIR* d_) { WDIR* const d = (WDIR*)d_; // avoid polluting the last error. DWORD prev_err = GetLastError(); // first call - skip FindNextFile (see opendir). if(d->num_entries_scanned == 0) { // this directory is empty. if(d->hFind == INVALID_HANDLE_VALUE) return 0; goto already_have_file; } // until end of directory or a valid entry was found: for(;;) { if(!FindNextFileA(d->hFind, &d->fd)) goto fail; already_have_file: d->num_entries_scanned++; // not a hidden or system entry -> it's valid. if((d->fd.dwFileAttributes & hs) == 0) break; } // this entry has passed all checks; return information about it. // (note: d_name is a pointer; see struct dirent definition) d->ent.d_name = d->fd.cFileName; return &d->ent; fail: // FindNextFile failed; determine why and bail. // .. legit, end of dir reached. don't pollute last error code. if(GetLastError() == ERROR_NO_MORE_FILES) SetLastError(prev_err); else debug_warn("readdir: FindNextFile failed"); return 0; } // return status for the dirent returned by the last successful // readdir call from the given directory stream. // currently sets st_size, st_mode, and st_mtime; the rest are zeroed. // non-portable, but considerably faster than stat(). used by file_enum. int readdir_stat_np(DIR* d_, struct stat* s) { WDIR* d = (WDIR*)d_; memset(s, 0, sizeof(struct stat)); s->st_size = (off_t)u64_from_u32(d->fd.nFileSizeHigh, d->fd.nFileSizeLow); s->st_mode = (d->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)? S_IFDIR : S_IFREG; s->st_mtime = filetime_to_time_t(&d->fd.ftLastWriteTime); return 0; } int closedir(DIR* d_) { WDIR* const d = (WDIR*)d_; FindClose(d->hFind); wdir_free(d); return 0; } ////////////////////////////////////////////////////////////////////////////// // // terminal // ////////////////////////////////////////////////////////////////////////////// static HANDLE std_h[2] = { (HANDLE)((char*)0 + 3), (HANDLE)((char*)0 + 7) }; void _get_console() { AllocConsole(); } void _hide_console() { FreeConsole(); } int tcgetattr(int fd, struct termios* termios_p) { if(fd >= 2) return -1; HANDLE h = std_h[fd]; DWORD mode; GetConsoleMode(h, &mode); termios_p->c_lflag = mode & (ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT); return 0; } int tcsetattr(int fd, int /* optional_actions */, const struct termios* termios_p) { if(fd >= 2) return -1; HANDLE h = std_h[fd]; SetConsoleMode(h, (DWORD)termios_p->c_lflag); FlushConsoleInputBuffer(h); return 0; } int poll(struct pollfd /* fds */[], int /* nfds */, int /* timeout */) { return -1; } ////////////////////////////////////////////////////////////////////////////// // // memory mapping // ////////////////////////////////////////////////////////////////////////////// // convert POSIX PROT_* flags to their Win32 PAGE_* enumeration equivalents. // used by mprotect. static DWORD win32_prot(int prot) { // this covers all 8 combinations of read|write|exec // (note that "none" means all flags are 0). switch(prot & (PROT_READ|PROT_WRITE|PROT_EXEC)) { case PROT_NONE: return PAGE_NOACCESS; case PROT_READ: return PAGE_READONLY; case PROT_WRITE: // not supported by Win32; POSIX allows us to also grant read access. return PAGE_READWRITE; case PROT_EXEC: return PAGE_EXECUTE; case PROT_READ|PROT_WRITE: return PAGE_READWRITE; case PROT_READ|PROT_EXEC: return PAGE_EXECUTE_READ; case PROT_WRITE|PROT_EXEC: // not supported by Win32; POSIX allows us to also grant read access. return PAGE_EXECUTE_READWRITE; case PROT_READ|PROT_WRITE|PROT_EXEC: return PAGE_EXECUTE_READWRITE; default: UNREACHABLE; } } int mprotect(void* addr, size_t len, int prot) { const DWORD flNewProtect = win32_prot(prot); DWORD flOldProtect; // required by VirtualProtect BOOL ok = VirtualProtect(addr, len, flNewProtect, &flOldProtect); return ok? 0 : -1; } // called when flags & MAP_ANONYMOUS static int map_mem(void* start, size_t len, int prot, int flags, int fd, void** pp) { // sanity checks. we don't care about these but enforce them to // ensure callers are compatible with mmap. // .. MAP_ANONYMOUS is documented to require this. debug_assert(fd == -1); // .. if MAP_SHARED, writes are to change "the underlying [mapped] // object", but there is none here (we're backed by the page file). debug_assert(flags & MAP_PRIVATE); // see explanation at MAP_NORESERVE definition. DWORD flAllocationType = (flags & MAP_NORESERVE)? MEM_RESERVE : MEM_COMMIT; DWORD flProtect = win32_prot(prot); void* p = VirtualAlloc(start, len, flAllocationType, flProtect); if(!p) return ERR_NO_MEM; *pp = p; return 0; } // given mmap prot and flags, output protection/access values for use with // CreateFileMapping / MapViewOfFile. they only support read-only, // read/write and copy-on-write, so we dumb it down to that and later // set the correct (and more restrictive) permission via mprotect. static int map_file_access(int prot, int flags, DWORD& flProtect, DWORD& dwAccess) { // assume read-only; other cases handled below. flProtect = PAGE_READONLY; dwAccess = FILE_MAP_READ; if(prot & PROT_WRITE) { // determine write behavior: (whether they change the underlying file) switch(flags & (MAP_SHARED|MAP_PRIVATE)) { // .. changes are written to file. case MAP_SHARED: flProtect = PAGE_READWRITE; dwAccess = FILE_MAP_WRITE; // read and write break; // .. copy-on-write mapping; writes do not affect the file. case MAP_PRIVATE: flProtect = PAGE_WRITECOPY; dwAccess = FILE_MAP_COPY; break; // .. either none or both of the flags are set. the latter is // definitely illegal according to POSIX and some man pages // say exactly one must be set, so abort. default: return ERR_INVALID_PARAM; } } return 0; } static int map_file(void* start, size_t len, int prot, int flags, int fd, off_t ofs, void** pp) { debug_assert(fd != -1); // handled by mmap_mem WIN_SAVE_LAST_ERROR; HANDLE hFile = HANDLE_from_intptr(_get_osfhandle(fd)); if(hFile == INVALID_HANDLE_VALUE) return ERR_INVALID_PARAM; // MapViewOfFileEx will fail if the "suggested" base address is // nonzero but cannot be honored, so wipe out unless MAP_FIXED. if(!(flags & MAP_FIXED)) start = 0; // choose protection and access rights for CreateFileMapping / // MapViewOfFile. these are weaker than what PROT_* allows and // are augmented below by subsequently mprotect-ing. DWORD flProtect; DWORD dwAccess; RETURN_ERR(map_file_access(prot, flags, flProtect, dwAccess)); // enough foreplay; now actually map. const HANDLE hMap = CreateFileMapping(hFile, 0, flProtect, 0, 0, (LPCSTR)0); // .. create failed; bail now to avoid overwriting the last error value. if(!hMap) return ERR_NO_MEM; const DWORD ofs_hi = u64_hi(ofs), ofs_lo = u64_lo(ofs); void* p = MapViewOfFileEx(hMap, dwAccess, ofs_hi, ofs_lo, (SIZE_T)len, start); // .. make sure we got the requested address if MAP_FIXED was passed. debug_assert(!(flags & MAP_FIXED) || (p == start)); // .. free the mapping object now, so that we don't have to hold on to its // handle until munmap(). it's not actually released yet due to the // reference held by MapViewOfFileEx (if it succeeded). CloseHandle(hMap); // .. map failed; bail now to avoid "restoring" the last error value. if(!p) return ERR_NO_MEM; // slap on correct (more restrictive) permissions. WARN_ERR(mprotect(p, len, prot)); WIN_RESTORE_LAST_ERROR; *pp = p; return 0; } void* mmap(void* start, size_t len, int prot, int flags, int fd, off_t ofs) { void* p; int err; if(flags & MAP_ANONYMOUS) err = map_mem(start, len, prot, flags, fd, &p); else err = map_file(start, len, prot, flags, fd, ofs, &p); if(err < 0) { WARN_ERR(err); return MAP_FAILED; } return p; } int munmap(void* start, size_t UNUSED(len)) { // UnmapViewOfFile checks if start was returned by MapViewOfFile*; // if not, it will fail. BOOL ok = UnmapViewOfFile(start); if(!ok) // VirtualFree requires dwSize to be 0 (entire region is released). ok = VirtualFree(start, 0, MEM_RELEASE); // both failed if(!ok) { debug_warn("munmap failed"); return -1; } return 0; } +//----------------------------------------------------------------------------- +// DLL +//----------------------------------------------------------------------------- + +static HMODULE HMODULE_from_void(void* handle) +{ + return (HMODULE)handle; +} + +static void* void_from_HMODULE(HMODULE hModule) +{ + return (void*)hModule; +} + + +int dlclose(void* handle) +{ + BOOL ok = FreeLibrary(HMODULE_from_void(handle)); + if(!ok) + debug_warn("dlclose failed"); + return ok? 0 : -1; +} + + +char* dlerror(void) +{ + return 0; +} + + +void* dlopen(const char* so_name, int flags) +{ + if(flags & RTLD_GLOBAL) + debug_warn("dlopen: unsupported flag(s)"); + + // if present, strip .so extension; add .dll extension + char dll_name[MAX_PATH]; + strcpy_s(dll_name, ARRAY_SIZE(dll_name)-4, so_name); + char* ext = strrchr(dll_name, '.'); + if(!ext) + ext = dll_name + strlen(dll_name); + strcpy(ext, ".dll"); // safe + + HMODULE hModule = LoadLibrary(dll_name); + if(!hModule) + debug_warn("dlopen failed"); + return void_from_HMODULE(hModule); +} + + +void* dlsym(void* handle, const char* sym_name) +{ + HMODULE hModule = HMODULE_from_void(handle); + void* sym = GetProcAddress(hModule, sym_name); + if(!sym) + debug_warn("dlsym failed"); + return sym; +} + + +//----------------------------------------------------------------------------- int uname(struct utsname* un) { static OSVERSIONINFO vi; vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&vi); // OS implementation name const char* family = "??"; int ver = (vi.dwMajorVersion << 8) | vi.dwMinorVersion; if(vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) family = (ver == 0x045a)? "ME" : "9x"; if(vi.dwPlatformId == VER_PLATFORM_WIN32_NT) { if(ver == 0x0500) family = "2k"; else if(ver == 0x0501) family = "XP"; else family = "NT"; } sprintf(un->sysname, "Win%s", family); // release info const char* vs = vi.szCSDVersion; int sp; if(sscanf(vs, "Service Pack %d", &sp) == 1) sprintf(un->release, "SP %d", sp); else { const char* release = ""; if(vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { if(!strcmp(vs, " C")) release = "OSR2"; else if(!strcmp(vs, " A")) release = "SE"; } strcpy(un->release, release); // safe } // version sprintf(un->version, "%lu.%02lu.%lu", vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber & 0xffff); // node name DWORD buf_size = sizeof(un->nodename); DWORD last_err = GetLastError(); BOOL ok = GetComputerName(un->nodename, &buf_size); // GetComputerName sets last error even on success - suppress. if(ok) SetLastError(last_err); else debug_warn("GetComputerName failed"); // hardware type static SYSTEM_INFO si; GetSystemInfo(&si); if(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) strcpy(un->machine, "AMD64"); // safe else strcpy(un->machine, "IA-32"); // safe return 0; } long sysconf(int name) { // used by _SC_*_PAGES static DWORD page_size; static BOOL (WINAPI *pGlobalMemoryStatusEx)(MEMORYSTATUSEX*); ONCE( { // get page size // (used by _SC_PAGESIZE and _SC_*_PAGES) SYSTEM_INFO si; GetSystemInfo(&si); // can't fail => page_size always > 0. page_size = si.dwPageSize; // import GlobalMemoryStatusEx - it's not defined by the VC6 PSDK. // used by _SC_*_PAGES if available (provides better results). const HMODULE hKernel32Dll = LoadLibrary("kernel32.dll"); *(void**)&pGlobalMemoryStatusEx = GetProcAddress(hKernel32Dll, "GlobalMemoryStatusEx"); FreeLibrary(hKernel32Dll); // make sure the reference is released so BoundsChecker // doesn't complain. it won't actually be unloaded anyway - // there is at least one other reference. } ); switch(name) { case _SC_PAGESIZE: return page_size; case _SC_PHYS_PAGES: case _SC_AVPHYS_PAGES: { u64 total_phys_mem; u64 avail_phys_mem; // first try GlobalMemoryStatus - cannot fail. // override its results if GlobalMemoryStatusEx is available. MEMORYSTATUS ms; GlobalMemoryStatus(&ms); // can't fail. total_phys_mem = ms.dwTotalPhys; avail_phys_mem = ms.dwAvailPhys; // newer API is available: use it to report correct results // (no overflow or wraparound) on systems with > 4 GB of memory. MEMORYSTATUSEX mse = { sizeof(mse) }; if(pGlobalMemoryStatusEx && pGlobalMemoryStatusEx(&mse)) { total_phys_mem = mse.ullTotalPhys; avail_phys_mem = mse.ullAvailPhys; } // else: not an error, since this isn't available before Win2k / XP. // we have results from GlobalMemoryStatus anyway. if(name == _SC_PHYS_PAGES) return (long)(round_up((uintptr_t)total_phys_mem, 2*MiB) / page_size); // Richter, "Programming Applications for Windows": // reported value doesn't include non-paged pool reserved // during boot; it's not considered available to kernel. // it's 528 KiB on my 512 MiB machine (WinXP and Win2k). else return (long)(avail_phys_mem / page_size); } default: return -1; } }