Per the user: the game never reads the pilot keypad, so the numpad becomes the flight cluster - 8/2/4/6 stick, 7/9 pedals, 0 trigger - with Shift/Ctrl as throttle up/down and Alt as reverse thrust. That frees the entire letter board: W/A/S/D/Q/E return to their printed MFD bank positions and B goes back to being the gap key, so the default profile now carries the complete unmodified vRIO bank layout. The keypad addresses stay bindable (arcade key events) but ship unbound. Alt as a held flight control meant every release popped the window menu and stole focus - WndProc now eats SC_KEYMENU. Shift/Ctrl/Alt key-name aliases added to the parser alongside the .NET names. Profile parses clean (59 key buttons, 8 key axes); single-player cycle and key-bomb tests green (Alt+Q abort unaffected). Machines with an existing bindings.txt keep their old map - delete the file to take the new defaults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
482 lines
15 KiB
C++
482 lines
15 KiB
C++
////===========================================================================//
|
|
//// 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 <tchar.h>
|
|
#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 <DbgHelp.h>
|
|
#include <strsafe.h>
|
|
#include <direct.h>
|
|
#include <shellapi.h>
|
|
|
|
#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;
|
|
putenv(line);
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
DEBUG_STREAM << "Red Planet v4.10" << 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;
|
|
}
|