////===========================================================================// //// File: rp4lbe4.cc // //// Project: MUNGA Brick: Red Planet LBE Application // //// Contents: // ////---------------------------------------------------------------------------// //// Date Who Modification // //// -------- --- ---------------------------------------------------------- // //// // ////---------------------------------------------------------------------------// //// Copyright (C) 1994, Virtual World Entertainment, Inc. // //// All Rights reserved worldwide // //// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // ////===========================================================================// // #define WIN32_LEAN_AND_MEAN #define _WIN32_WINNT 0x0500 #define WINVER 0x0500 #include #include "rpl4.h" #pragma hdrstop #include "rpl4pb.h" #include "rpl4fe.h" #include "rpl4console.h" #include "rpl4lobby.h" #include "..\munga_l4\l4steamtransport.h" #include "..\munga_l4\l4splr.h" #include "rpl4ver.h" #include "..\munga\resver.h" #include "..\munga\resource.h" // added for game status drawing support #include "..\munga\player.h" #include "..\munga\mission.h" #include "..\munga_l4\l4ctrl.h" // end add #include #include #include #include #define SPOOL_SIZE 0x600000 //#define SCREEN_WIDTH 1024 //#define SCREEN_HEIGHT 768 Byte version[3] = {MAJOR_DATA_VERSION, RELEASE_VERSION, MINOR_DATA_VERSION}; char* names[] = {"InitializingState", "WaitingForEgg", "LoadingMission", "WaitingForLaunch", "LaunchingMission", "RunningMission", "EndingMission", "StoppingMission", "SuspendingMission", "ResumingMission", "AbortingMission", "CreatingMission", "ApplicationStateCount"}; //########################################################################## //################## RP4L4Application Globals ######################## //########################################################################## extern const char* const ProgName; const char* const ProgName = "rpl4"; Application *rpl4App; HWND hWnd; // // Unhandled-exception minidump: rpl4crash.dmp lands beside the exe so // a crash on a test machine hands back a stack. dbghelp loads lazily - // no link-time dependency. // static LONG WINAPI RPL4CrashDumpFilter(EXCEPTION_POINTERS *exception) { HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); if (dbghelp != NULL) { typedef BOOL (WINAPI *MiniDumpWriteDumpFn)( HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION, PMINIDUMP_CALLBACK_INFORMATION); MiniDumpWriteDumpFn write_dump = (MiniDumpWriteDumpFn) GetProcAddress(dbghelp, "MiniDumpWriteDump"); if (write_dump != NULL) { HANDLE file = CreateFileA("rpl4crash.dmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (file != INVALID_HANDLE_VALUE) { MINIDUMP_EXCEPTION_INFORMATION dump_info; dump_info.ThreadId = GetCurrentThreadId(); dump_info.ExceptionPointers = exception; dump_info.ClientPointers = FALSE; write_dump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal, &dump_info, NULL, NULL); CloseHandle(file); } } } return EXCEPTION_CONTINUE_SEARCH; } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE: DestroyWindow(hWnd); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; case WM_SYSCOMMAND: // Alt is a flight control (reverse thrust) - releasing it must // not pop the window menu and steal keyboard focus if ((wParam & 0xFFF0) == SC_KEYMENU) { return 0; } break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { char filename[50]; strcpy_s(filename, 50, "rpl4.log"); std::ofstream logfile; logfile.open(filename); std::cout.rdbuf(logfile.rdbuf()); SetUnhandledExceptionFilter(RPL4CrashDumpFilter); // load up our environment variables //controls if(getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=KEYBOARD"); if(getenv("DPLARG") == NULL) putenv("DPLARG=1"); if(getenv("L4DPLCFG") == NULL) putenv("L4DPLCFG=RPDPL.INI"); if(getenv("MULTISAMPLE") == NULL) putenv("MULTISAMPLE=0"); if(getenv("TARGETFPS") == NULL) putenv("TARGETFPS=60"); if(getenv("MAXPARTICLES") == NULL) putenv("MAXPARTICLES=8192"); FILE *file; char line[1024]; if (fopen_s(&file, "environ.ini", "r") == 0) { while (!feof(file)) { if (fgets(line, sizeof(line), file)) { for (int i = strlen(line); i >= 0; i--) if (line[i] == '\n' || line[i] == '\r') line[i] = 0; // the file is self-documenting: skip comments, blanks, // and anything that is not KEY=VALUE char *setting = line; while (*setting == ' ' || *setting == '\t') ++setting; if (*setting == '\0' || *setting == '#' || *setting == ';' || strchr(setting, '=') == NULL) continue; putenv(setting); } } fclose(file); } DEBUG_STREAM << "Red Planet 4.12.2" << std::endl << std::flush; DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush; #ifdef RP412_STEAM // // RP412STEAM=1 (environ.ini or a Steam launch) swaps the wire to // ISteamNetworkingSockets. Failure logs the reason and the game // carries on over TCP - dev boxes without Steam are unaffected. // { const char *steam_switch = getenv("RP412STEAM"); if (steam_switch != NULL && atoi(steam_switch) != 0) { SteamNetTransport_Install(); } } #endif // GetProcessAffinityMask(GetCurrentProcess(), DWORD_PTR res = SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)1); if (res == 0) { DWORD err = GetLastError(); DEBUG_STREAM << "Could not set thread affinity. Err Code = " << err << std::endl << std::flush; } // //---------------------------------------------------------------------- // Parse command line for an egg notation file name //---------------------------------------------------------------------- // int argc; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); Logical run_application = RPL4Application::ParseCommandLine(argc, argv); if (!run_application) { return 1; } else { Start_Registering(); } // //------------------------------------------------------------------------- // Create our window. This is only temporary because this will move after // the graphics are moved over. //------------------------------------------------------------------------- // WNDCLASS wc; if (!hPrevInstance) { wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION); wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = L"MainWndClass"; if (!RegisterClass(&wc)) return FALSE; } DWORD wsStyle = WS_OVERLAPPED | WS_SYSMENU; if (L4Application::GetFullscreen()) wsStyle = WS_POPUP; hWnd = CreateWindowEx(0, L"MainWndClass", L"RPL4", WS_OVERLAPPEDWINDOW, 0, 0, L4Application::GetScreenWidth(), L4Application::GetScreenHeight(), (HWND)NULL, (HMENU)NULL, hInstance, (LPVOID)NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nShowCmd); #if !_DEBUG // Arcade pods have no mouse - but desktop/windowed play needs the // cursor for the on-screen cockpit buttons, so hide it only when // running fullscreen. if (L4Application::GetFullscreen()) { ShowCursor(FALSE); } #endif // //------------------------------------------------------------------------- // Front-end mode: started without an egg, a network, or mission review. // The race loop below keeps everything in ONE process - setup screen, // mission, teardown, and back to the setup screen - with the local // console marshaling each race. (-egg / -net / -mr run a single pass, // exactly as before.) //------------------------------------------------------------------------- // Logical front_end_mode = L4Application::GetMissionReviewMode() == 0 && L4Application::GetNetworkCommonFlatAddress() == 0 && !L4Application::GetEggNotationFileName(); // no egg on the command line int last_launch_mode = FELaunchSingle; for (;;) { if (front_end_mode) { if (!IsWindow(hWnd)) { break; // the cockpit window is gone (closed mid-race) } // drain any WM_QUIT left over from the previous mission's // teardown so it cannot instantly close the setup screen MSG leftover; while (PeekMessage(&leftover, NULL, WM_QUIT, WM_QUIT, PM_REMOVE)) { } // lobby races: the owner publishes the score sheet, members // pull it - the same results screen shows on every machine RPL4Lobby_PushRaceResults(); RPL4Lobby_PullRaceResults(); // last race's final scores first (no-op on first boot) if (!RPL4FrontEnd_ShowResults(hInstance, hWnd)) { break; } static char frontend_egg[MAX_PATH]; if (!RPL4FrontEnd_Run(hInstance, hWnd, frontend_egg, sizeof(frontend_egg))) { break; // player closed the menu without launching } last_launch_mode = RPL4FrontEnd_LastLaunchMode(); Exit_Code = 0; if (last_launch_mode == FELaunchMember) { // // Lobby member: enter the race as a network pod. The // owner's console connects over the wire, feeds the egg, // and marshals us - no local egg, no local console. If // the owner leaves mid-race the mission ends (a race // with no console never stops on its own). // L4Application::SetNetworkCommonFlatAddress(1501); gConsoleLossEndsMission = True; } else { gConsoleLossEndsMission = False; L4Application::SetEggNotationFileName(frontend_egg); // Front-end games are marshaled by the in-process console: // it ends the race when the selected time expires. // (Hand-fed -egg runs stay unmarshaled - the developer // shortcut.) // // RP412HOSTPODS turns the launch into a hosted network // race: this pod switches to network mode (it meshes like // any pod) and the console also marshals the listed member // pods over the wire. The Steam lobby primes the same path. const char *host_pods = getenv("RP412HOSTPODS"); if (host_pods != NULL && host_pods[0] != '\0') { int host_port = 1501; const char *port_override = getenv("RP412HOSTPORT"); if (port_override != NULL && atoi(port_override) > 0) { host_port = atoi(port_override); } L4Application::SetNetworkCommonFlatAddress(host_port); if (!RPL4LocalConsole_InstallNetworkRace( RPL4FrontEnd_LastMissionSeconds(), frontend_egg, host_pods, RPL4FrontEnd_LastPilotNames())) { DEBUG_STREAM << "Network race setup failed - back to the menu\n" << std::flush; L4Application::SetNetworkCommonFlatAddress(0); continue; } } else { L4Application::SetNetworkCommonFlatAddress(0); RPL4LocalConsole_Install(RPL4FrontEnd_LastMissionSeconds()); } } } // //------------------------------------------------------------------------- // Open the resource file. The brace will help correct the scoping problem // of destruction the resource file //------------------------------------------------------------------------- // { StreamableResourceFile resources("rpl4.res", version); Check(&resources); // //---------------------------------------------- // Create and initialize the application manager //---------------------------------------------- // ApplicationManager* app_manager; Application *new_app; int review_mode = RPL4Application::GetMissionReviewMode(); if (review_mode > 0) { MissionReviewApplicationManager *spool_mgr = new MissionReviewApplicationManager(hInstance, hWnd, atoi(getenv("TARGETFPS")), (review_mode == 2) ? 1 : 2, SPOOL_SIZE); app_manager = spool_mgr; Register_Object(app_manager); // //------------------------------------------------------------------- // Create and initialize the playback application if there is a spool // file //------------------------------------------------------------------- // #if 1 CString spoolFileName = L4Application::GetSpoolFileName(); if (!spoolFileName) spoolFileName = "last.spl"; SpoolFile *spool = spool_mgr->GetStoredSpoolFile(spoolFileName); if (spool) { new_app = new RPL4PlaybackApplication(hInstance, hWnd, &resources, spool); } else { new_app = new RPL4IdleApplication(hInstance, hWnd, &resources); } Register_Object(new_app); app_manager->StartApplication(new_app); #else new_app = new RPL4PlaybackApplication(hInstance, hWnd, &resources, NULL); Register_Object(new_app); app_manager->StartApplication(new_app); #endif // //------------------------------------ // Initialize the spooling application //------------------------------------ // if (review_mode == 1) { new_app = new L4SpoolingApplication(hInstance, hWnd, &resources, RPL4); Register_Object(new_app); app_manager->StartApplication(new_app); } } else { app_manager = new ApplicationManager(hInstance, hWnd, atoi(getenv("TARGETFPS"))); Register_Object(app_manager); // //-------------------------------------- // Create and initialize the application //-------------------------------------- // Application *new_app = new RPL4Application(hInstance, hWnd, &resources); rpl4App = new_app; Register_Object(new_app); SetCursorPos(1680 / 2, 1050 / 2); app_manager->StartApplication(new_app); } // //-------------------------- // Run missions till we done //-------------------------- // app_manager->RunMissions(); rpl4App = NULL; Unregister_Object(app_manager); delete app_manager; } // //------------------------------------------------------------------------- // Single-binary race loop: if the local console ended this mission // (or we raced as a lobby member under the owner's console), go // back to the setup screen / lobby room for the next one; anything // else (user quit, -egg / -net / -mr single pass) falls out. //------------------------------------------------------------------------- // if (!front_end_mode || (last_launch_mode != FELaunchMember && !RPL4LocalConsole_MissionCompleted())) { break; } } #if !_DEBUG // symmetric with the fullscreen-only hide at startup if (L4Application::GetFullscreen()) { ShowCursor(TRUE); } #endif Stop_Registering(); return Exit_Code; }