////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// 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.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// game.cpp
// Project: Postal
//
// This module deals with the high-level aspects of setting up and running the
// game.
//
// History:
//		11/19/96 MJR	Started.
//		01/15/96 MJR	Added GamePath()
//
//		01/28/97	JMI	Added calls to MenuTrans API (a PalTran).
//
//		01/30/97	MJR	Fixed bug in CorrectifyPath() that accidentally truncated
//							the passed-in string.
//
//		01/31/97	JMI	Now uses FullPath() to get full path for filespecs.
//
//		01/31/97	JMI	Added g_resmgrGame for specific to actual game data.
//
//		01/31/97	JMI	Number of title loops done on start up is now gotten from
//							INI file.
//
//		02/02/97	JMI	Now calls StartTitle() and EndTitle() after launching an
//							edit session, a user game, or a demo game.
//
//		02/02/97	JMI	Now, before exiting, makes sure all samples are done
//							playing.
//
//		02/03/97	JMI	Changed DoMenu() called to new DoMenuInput() and
//							DoMenuOutput() calls.
//
//		02/03/97	JMI	Now sets the base path for g_resmgrGame, Shell, & Menus.
//
//		02/04/97	JMI	Added functions to get and set the gamma level.
//
//		02/04/97	JMI	Changed LoadDib() call to Load() (which now supports
//							loading of DIBs).
//
//		02/13/97	JMI	Removed game level alpha XRay stuff (now setup in CHood).
//
//		02/18/97	JMI	Now opens SampleMaster's SAK file.
//
//		03/06/97	JMI	Made PalTranOn() and PalTranOff() extern instead of inline.
//
//		03/17/97	JMI	Made PalTranOff() abortable via a keypress.  Put in the
//							shape of the code for aborting via a key in PalTranOn() but
//							there is currently no interface to MenuTrans to allow this
//							type of abort (or jump).
//
//		03/28/97	JMI	Un#if 0'd the SavePrefs() call.
//
//		03/28/97	JMI	Now stores and resets the current dir.
//
//		04/11/97	JMI	Added Game_Menu_Demo() externally callable function to
//							start the demo.
//
//		04/14/97	JMI	Now specifies which image for StartTitle() to start with.
//
//		04/14/97	JMI	Missed a spot in the previous change.
//
//		04/16/97 MJR	Added simple test of file paths.  Also changed it so that
//							preferences are always saved, even if the game encounters
//							an error beforehand.
//
//							Fixed the simple test of file paths to actual use the
//							file path!!!
//
//		04/17/97 MJR	Put the test for the CD path back in.
//
//		04/22/97	JMI	No longer uses chdir().
//
//		04/22/97	JMI	Now sets g_GameSettings.m_sServer and
//							g_GameSettings.m_sClient to 0 before running the demo.
//
//		04/24/97	JMI	Currently, aborting the PalTranOff() is a bad idea, so
//							we don't allow it (until we support EndMenuTrans(TRUE) ).
//
//		05/06/97	JMI	Now uses a Gamma multiplier to define a line instead of
//							the exponent defining the curve.
//
//		05/07/97	JMI	Now uses a Gamma exponent and uses the 'Gamma Val' as a
//							constant multiplier:
//							MappedColorVal	= (colorVal ^ GAMMA_EXPONENT) * GammaVal
//
//		05/21/97	JMI	Added a resource manager for resources that are not SAKed.
//
//		05/23/97	JMI	Added a CloseSaks() which closes and purges the SAKs.
//							This solves the problem with the ASSERTions about the logic
//							vars (the RResmgr's were declared at file scope as were the
//							CLogTabVars so it was just a matter of order of destruction
//							that dictated whether the ASSERTions occurred).
//
//		06/03/97	JMI	Commented out error message for read-only prefs file.
//
//		06/03/97	JMI	Now uses g_GameSettings members for demo timeouts instead
//							of the DEMO_TIMEOUT macro.
//
//		06/04/97	JMI	Added conditionally compiled message instead of editor
//							when editor is requested from the menu.
//							Also, now StartTitle() calls within GameLoop() request the
//							last title page.
//
//		06/04/97	JMI	Added AbortAllSamples() call when exitting.
//
//		06/04/97	JMI	Added MUST_BE_ON_CD, EDITOR_DISABLED, and CHECK_FOR_COOKIE
//							conditional compilation macros and added a check for a
//							specific U32 in the COOKIE file.
//
//		06/12/97 MJR	Reworked the callbacks so that the game-specific code now
//							resides in this module rather than the menu module.
//							Reworked the entire core loop so it passes all the
//							required parameters to Play() instead of using globals.
//							Moved all the demo and network setup stuff out of play.cpp
//							and into here.
//
//		06/15/97 MJR	More changes in terms of how Play() is called and what
//							this module does to get ready for calling it.
//							Also changed calls to rspDoSystem() into calls to Update().
//
//							Added required calls to DeleteInputDemoData().
//
//		06/15/97 MJR	Made use of new-and-improved input demo interface.
//							(But demo mode is still not working right...had to check
//							it in though to go along with changes in other files).
//
//		06/16/97 MJR	Fixed stupid error that caused demo record not to work.
//
//		06/16/97	JMI	Now passes destination buffer in StartMenu() call.
//
//		06/16/97	JMI	Added g_fontSmall.
//
//					MJR	Added make-a-demo-movie stuff for debugging demo mode
//							problems.
//
//					JMI	Now, based on INI setting g_GameSettings.m_sTrickySystemQuit,
//							we en/disable system quit status flags.
//
//					JMI	Changed "res/fonts/SmallSmash.fnt" to "res/fonts/SmSmash.fnt"
//
//		06/17/97 MJR	Modified CorrectifyBasePath() so it will change any
//							relative paths into absolute paths.  This, in turn, fixed
//							a problem whereby the loading of demo files failed because
//							it assumed the name of the demo file would begin with the
//							HD path.  This assumption normally worked, but failed when
//							the HD path was a relative path.
//
//		06/18/97 MJR	Added SeedRandom() and GetRandom().
//
//					MJR	Fixed bug in CorrectifyBasePath().  Now handles empty
//							paths (it ignores them).
//
//		06/24/97	JMI	Added SynchLog() debug mechanism.  See function comment for
//							functionality details.  See macros in game.h for usage via
//							macro(s).
//
//		06/24/97	JMI	Undefined CHECK_FOR_COOKIE.
//
//					JMI	Changed usage of strcmp to rspStricmp in GetRandom() and
//							SynchLog().
//
//		06/30/97 MJR	Changed client and server objects to use new/delete instead
//							of having them live on the stack.  They are very large,
//							and caused local (stack) data to exceed 32k on the mac.
//
//		07/03/97	JMI	Converted calls to rspOpen/SaveBox() to new parm
//							conventions.
//
//		07/03/97	JMI	Added Game_ControlsMenu() and actions for editting input
//							settings for keys and mouse.
//
//		07/05/97 MJR	Changed to RSP_BLACK_INDEX instead of 0.
//
//		07/06/97	JMI	Changed ACTION_EDIT_MOUSE_SETTINGS and
//							ACTION_EDIT_KEY_SETTINGS to ACTION_EDIT_INPUT_SETTINGS.
//							Also, now only one function is called no matter which type
//							of input settings are to be editted, EditInputSettings().
//
//		07/11/97 BRH	Finished up the expiration date checking for the
//							Registry.  Still need to add something for the Mac
//							version.
//
//		07/11/97 BRH	Fixed typos causing the latest date not to be written to
//							the registry.
//
//		07/13/97	JMI	Now sets the appropriate base path or opens the approriate
//							SAK dependent on the current audio mode.
//
//		07/13/97	JMI	Added Game_StartChallengeGame() and an action for the
//							various challenge game types.
//
//		07/14/97 BRH	Changed calls to Play() to pass the challenge mode flag.
//							Fixed value in Get Registry in case of an error on
//							decrypt.
//
//		07/16/97 BRH	Made changes to the Mac expiration date code to normalize
//							the time to 1970 like the rest of the machines so that
//							the dates can be stored in 1 format.
//
//		07/16/97	JMI	Now uses real time when pausing on the titel screens (in-
//							stead of 'Load Loops').
//
//		07/17/97 BRH	Finished debugging the Mac Prefs file expiration date code.
//
//					MJR	Fixed sample rate problem on the mac.
//
//					BRH	Put in the real expiration date and a comment for the
//							real release date, and updated the current release date
//							to today so that we can still test it.
//
//		07/18/97 BRH	Added game load and save functions so that the player's
//							game can be saved and loaded.  Also added a global
//							stockpile object used to transfer loaded/saved info to/from
//							the CDude's stockpile.
//
//		07/19/97 MJR	Fixed bug where demo mode would start if user cancelled out
//							of a dialog after spending a long time there.
//							Also changed so demo mode will not start if app is in BG.
//
//		07/23/97 BRH	Changed calls to Title functions to use the new array of
//							title screen durations.
//
//		07/23/97 BRH	Changed expiration date display for PC back to asctime(gmtime())
//							since ctime didn't work correctly.
//
//		07/26/97	JMI	Added g_fontPostal to replace g_fontSmall.  Got rid of
//							g_fontSmall.
//
//		07/28/97	JMI	Now displays a different message for non-marketing
//							releases.
//
//		08/01/97 BRH	Cycles through 3 demos rather than just 1. Also added
//							open dialog for playing demos so that the user can choose
//							which demo to play.
//
//		08/02/97	JMI	Now initializes the default RGuiItem font to g_fontBig in-
//							stead of relying on everybody who uses it to invidually set
//							it.
//
//		08/05/97	JMI	Added Game_AudioOptionsChoice().
//
//		08/06/97 MJR	Added socket startup/shutdown.
//
//		08/08/97 MJR	Background and foreground callbacks are now installed here
//							so they are in effect for the entire app.
//
//		08/08/97	JMI	Now passes parameter to start musak in title to
//							StartTitle().
//
//		08/12/97	JMI	m_szDemoFile was FullPath()'ed twice so the demo wouldn't
//							load.  Fixed.
//
//		08/12/97	JMI	Added SubPathOpenBox() (see proto for details) and
//							FullPathCustom().
//
//		08/13/97 MJR	We now pass difficulty level to Play().
//
//		08/13/97	JMI	No longer remembers the previous demo file you recorded as
//							a patch to either improper usage or problem in
//							SubPathOpenBox().
//
//		08/14/97	JMI	Added prealm parameter to Game_Save/LoadPlayersGame() so
//							they can query/modify realm settings/flags.
//							Also, changed questionable access on m_action in
//							Game_SavePlayersGame() and Game_LoadPlayersGame() which
//							were casting &m_action to a (short*) even though ACTION is
//							a 32 bit value.  May not have worked on the Mac although it
//							would for values less than 32767 and greater than 0 on the
//							little endian PC.
//							Also, added an ACTION_LOAD_GAME so that could be part of
//							the main loop with the intent of making the loaded
//							difficulty a local variable but this turned out to be error
//							prone with keeping this value up to date with the proper
//							value (either the INI difficulty or the loaded difficulty)
//							and it was annoying to determine which one to use on each
//							Play() call so I made a function that returns the
//							appropriate value (but only once...ick).  If you feel like
//							it try it the other way, but be aware that on any iteration
//							g_GameSettings.m_sDifficulty may change via a menu.
//							Also, had to add a flag so one action can lead to another.
//							The main loop used to reset the action after every action
//							making it impossible for one action to lead to another.
//
//		08/15/97	JRD	Added a function to control brightness/contrast in place
//							of gamma.  This was because the gamma function could not
//							recreate a "normal palette."
//
//		08/15/97 JRD	Attempted to hook both brightness and contrast changes to
//							the old gamma slider with a crude algorithm.
//
//		08/18/97	JMI	Now allows ALT-F4 to drop you out of the title sequence.
//
//		08/18/97	JMI	In both debug and release modes, we were setting the do
//							system mode to sleep.  But we'd only restore it in release
//							mode which basically left us in sleep mode in debug mode.
//							Now restores it to TOLERATEOS in debug mode.
//
//		08/19/97 BRH	Uses the game settings m_sNumAvailableDemos to control the
//							demo loop rather than the set macro NUM_DEMO_FILES
//
//					MJR	Added support for new MP flags.
//
//		08/20/97	JMI	Modified Game_LoadPlayersGame() to receive the ptr to the
//							action to fill with the Player's saved action so the
//							function would not have to use the global m_action.
//							Also, Moved Game_LoadPlayersGame() proto into game.cpp b/c
//							it now takes an ACTION* but ACTION is defined in game.cpp
//							and since no one currently calls this function externally,
//							what the hell.
//
//		08/21/97 BRH	Changed the game sak to be loaded from the GAME_PATH_GAME
//							directory, getting it ready for the installation process.
//							Also changed the demos to HD path to make it easier for
//							people to load their own demos.  Changed the sound res
//							manager path to the GAME_PATH_SOUND for installation.
//
//		08/21/97	JMI	Changed call to Update() to UpdateSystem() and occurrences
//							of rspUpdateDisplay() to UpdateDisplay().
//
//		08/22/97	JMI	Changed occurrences of UpdateDisplay() back to
//							rspUpdateDisplay().  Now that we are using the lock
//							functions correctly, we don't need them.
//							Also, removed rspLockBuffer() and rspUnlockBuffer() that
//							were used to encapsulate the entire app in a lock.
//
//		08/22/97 BRH	Changed the cookie position for use with a new smaller
//							res.sak file since we now have Direct X and more sound,
//							we needed more space on the CD.
//
//		08/23/97	JMI	Now gets callbacks from the initialization and killing of
//							the main menu and stops any playing title/main-menu musak
//							when the main menu is killed.
//
//		08/24/97	JMI	Added a timeout to the abortion of playing samples just in
//							case there's a bug or a sound driver problem (no need to
//							to taunt infinite loopage).
//
//		08/25/97	JMI	Now sets the sound quality based on the current mode.
//							Also, now, if there's no sak dir and no sak for the current
//							audio mode, complains.
//
//		08/26/97 BRH	Merged in changes from a branched game.cpp which has the
//							special cases for the ending demo level of the game.
//							Added a function GameEndingSequence which is called
//							when the global flag that indicates the player has won is
//							set.  It plays the demo file and then calls the
//							Title_EndingSequence.
//
//		08/26/97	JMI	Now any menu can be restore in the GameCore() loop instead
//							of just the main menu by setting pmenuStart to point at the
//							menu to be next started.
//
//		08/30/97 BRH	Fixed the base path for the logic table resource manager
//							to load from the CD rather than the HD path.
//
//		09/02/97	JMI	Added a new option to the demo actions which plays one of
//							the factory preset demos.
//							Also, in the case that there were no factory preset demos
//							and the auto play demo kicked in, it would ASSERT that
//							the filename was "".
//
//		09/02/97	JMI	Now more deluxe control for regarding the starting of the
//							next menu after an action in GameCore().
//
//		09/03/97 BRH	Changed the g_resmgrRes logic table manager to load
//							from the HD path rather than the CD path, so that it
//							is easier to modify, add or change logics, especially
//							if we have to patch them in the future.
//
//		09/04/97	JMI	Now Game_ControlsMenu() only sets a new action if one is
//							not already set.  This keeps the controls menu from setting
//							the action when it is launched from Play.cpp instead of
//							here.
//
//		09/05/97 PPL	In CorrectifyBasePath, made sure we allocate the buffer for the
//							full path instead of letting getcwd allocate it for us.  It's
//							not that we won't let it -- it's just that the Mac implementation
//							will not do it automatically as the PC implementation would.
//
//		09/05/97 PPL	Per Bill's directions, we will use the path from the CD for
//							the challenge level.
//
//		09/05/97 MJR	A few changes to use the new network stuff.
//
//		09/06/97 MJR	Now inits and kills the network problem GUI.
//
//		09/10/97 BRH	Had to change the res.sak in order to fit the MPlayer
//							and Heat setups on the CD, so I also had to change the
//							cookie.
//
//		09/10/97 MJR	Now properly converts paths from system to rspix format
//							on the start single player and start challenge level
//							dialogs.  This bug only showed up on the Mac.
//
//		09/11/97 BRH	Added different cookie and file position for
//							COMP_USA_VERSION only.
//
//		09/11/97	JMI	Removed old code for PLAY_SPECIFIC_LEVEL_ONLY macro which
//							no longer exists.
//
//		09/12/97 MJR	Now more code doesn't compile when the editor is disabled,
//							in an attempt to make it harder to hack.
//
//		09/18/97	JMI	Added expiration message for
//							EXPIRATION_MSG_POSTAL_LAUNCH_WEEKEND compile condition.
//
//					JMI	Changed 'will expire on' to 'expires' so it sounds better
//							both past/future/etc tense wise and the 'on' didn't work
//							well with the format "Mon Sep 22 23:59:00" which contains
//							no 'at'.
//
//		09/24/97 BRH	For LOCALE != US version, took out the school yard demo
//							from the ending sequence.
//
//		09/29/97	JMI	Now will try to load any samples SAK in the case that there
//							is no audio mode, the specified mode's samples SAK is not
//							present, and there's no 'NoSak' dir specification.
//							If absolutely no samples SAK is found, a message is
//							displayed that does not mention whether or not your
//							hardware supports the specified mode.
//
//		10/01/97 BRH	Changed res.sak location so it would fit on the disk.
//
//		10/08/97	JMI	Made the synchronization logs opened and closed via one
//							set of two functions to make it easier to use them in
//							different scenarios.
//							Also, the logs should now only use the filename (excluding
//							the path) to make it possible for Mac vs. PC logs to be
//							compared.
//
//		10/10/97	JMI	Updated Game_ControlsMenu() to handle joystick option
//							when ALLOW_JOYSTICK is defined.
//
//		10/14/97	JMI	Made SynchLog()'s expr parameter a double instead of an int
//							for more accuracy.
//
//		10/15/97	JMI	Changed the %g used to fprintf() SynchLog() expr values
//							to %0.8f b/c the Mac and PC formats differed slightly.  The
//							Mac one often contains as many as four zeroes after the
//							last significant decimal, sometimes less, and most of the
//							time none.  The PC never seems to put trailing zeroes.
//
//		10/16/97	JMI	Now mac demo basename is mdefault.dmo (instead of
//							default.dmo) since the game plays slightly differently on
//							the Mac than on the PC.
//
//		10/22/97	JMI	In the call to Play() for recording demos, FullPathHD() was
//							being called on the passed in path but now that
//							CRealm::Open() tries all the deluxe path stuff we don't
//							need that (and cannot use it b/c Open() does a conversion
//							to system path format as does FullPathHD() ).
//
//		11/20/97	JMI	Added cooperative flag to Play() calls.
//
//		12/01/97 BRH	Added flags for Add On levels to play calls, and
//							added ACTION_PLAY_ADDON as one of the options.
//
//		12/04/97 BRH	Added Single player menu option to play add on levels
//							or original levels.
//
//		01/05/98	JMI	Now uses RMix::GetMode() to get sound quality.  Also, now
//							uses the source bits per sample (instead of the device
//							setting) to determine which sample resources to use.
//							Note that the quality is still set based on the device bits
//							per sample though so that the volumes could be tuned
//							accordingly in SampleMaster's ms_asQualityCategoryAdjustors.
//
//		01/21/98	JMI	Now the foreground and background callbacks use a value,
//							INVALID_CURSOR_SHOW_LEVEL, to flag whether or not they're
//							in the background so they know which callbacks to ignore
//							and which one's to use.  I think that before (under DirectX)
//							we were getting more than one Backcall per Forecall and/or
//							vice-versa.
//
//		03/05/98 BRH	Now attempts to identify the PostalSD disc when the game
//							starts to clear up any confusion about which CD must be
//							in the drive to play the Add on Pack.  The Add on Pack
//							requires the original PostalCD in the drive, and it reads
//							the original res.sak file from that disc.  The PostalSD
//							disc also has a res.sak file on it, but its not the one
//							we want, and if someone tried to play the game with the
//							PostalSD disc in the drive rather than the PostalCD, then
//							the copy protection would flag it as invalid.  So an
//							additional check is now made and if the PostalSD disc
//							is in the drive, it will display a message to tell them
//							to put the PostalCD in the drive.
//
//		06/04/98 BRH	Removed the demo timeout check from the SPAWN build since
//							the spawn version doesn't have any demo files available
//							it shouldn't try to run them when the user is idle.  It
//							was attempting to do so, and when it can't find a demo
//							file, a message box pops up which would cause much
//							confusion to the user.
//
//		07/08/98	JMI	Removed extra Close() in checking for add on CD.  Also,
//							moved close that was in incorrect spot in check for add on
//							CD.  These were only annoying in Debug mode when it is
//							flagged.
//
//		09/27/99	JMI	Changed to allow ending sequence only in any locale
//							satisfying the CompilerOptions macro VIOLENT_LOCALE.
//
//		10/07/99	JMI	Conditional remove Add On start menu option when in
//							SUPER_POSTAL target.
//
//		10/11/99	JMI	Changed the audio sak name to be RATEjBITS instead of
//							RATE_BITS to differentiate it from Orig & SD Postal sound
//							saks.
//
//		02/04/00 MJR	Added dialog box to prompt for original CD if
//							PROMPT_FOR_ORIGINAL_CD is defined.
//
//					MJR	Changed so that it now checks the HD first and the VD
//							second when trying to load the shell sak.  This
//							was done to fix problems with the Japan Add On.
//
//		03/30/00 MJR	Changed to new macro for controlling the test for the
//							original Postal CD to differentiate it from the test for
//							a CD-ROM drive.
//
//							Added AUDIO_SAK_SEPARATOR_CHAR to make it easy to control
//							the separater character in audio sak filenames.
//
//		04/01/00 MJR	Fixed bug with AUDIO_SAK_SEPARATOR_CHAR.
//							Changed so in debug mode, it continues even if not on CD.
//
//		04/03/00 MJR	Modified MUST_BE_ON_CD code so that the test is bypassed if
//							the CD path is to our development server.  This allows
//							for easier testing.
//
//		06/24/01 MJR	Got rid of CompUSA cookie variation.  Also cleaned up a
//							few other macros.
//
////////////////////////////////////////////////////////////////////////////////

#include "RSPiX.h"

#include <time.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif

#include "WishPiX/Menu/menu.h"
#include "WishPiX/Prefs/prefs.h"
#include "WishPiX/ResourceManager/resmgr.h"

#include "main.h"
#include "menus.h"
#include "game.h"
#include "localize.h"
#include "play.h"
#include "title.h"
#include "credits.h"
#include "scene.h"
#include "update.h"
#include "MenuTrans.h"
#include "SampleMaster.h"
#include "input.h"
#include "encrypt.h"
#include "credits.h"

#include "CompileOptions.h"
#include "pvr.h"
#include "aica.h"
#include "maple.h"

////////////////////////////////////////////////////////////////////////////////
// Macros/types/etc.
////////////////////////////////////////////////////////////////////////////////

// Total units required for loading during title sequence (estimated)
#define TITLE_LOAD_UNITS 5000

// TEMPORARY!  Determines how long the fake title sequence load will take.
// Remove this when there's real stuff being loaded.
#define TEMP_TITLE_LOOPS 500
#define TEMP_TITLE_TIME_PER_LOOP 10 // 100 looks normal, 10 goes extremely quickly

// Font stuff
#define BIG_FONT_FILE "res/fonts/system1.fnt"
#define POSTAL_FONT_FILE "res/fonts/smash.fnt"
#define FONT_HEIGHT 12		// "Best" for ComicB.
#define FONT_FORE_INDEX 255 // White or black.
#define FONT_BACK_INDEX 0	// Transparent.

#define NORMAL_PAL_TRAN_TIME 750

#define TEMP_ALPHA_EFFECT "2dEffects/alpha00.bmp"

#define SHELL_SAK_FILENAME "res/shell/shell.sak"
#define GAME_SAK_FILENAME "res/game/game.sak"
#define SAMPLES_SAK_SUBDIR "res/game/"
#define CUTSCENE_SAK_FILENAME_FRMT "res/cutscene/cutscene%02d.sak"

// Since the game plays slightly differently on the PC than on the Mac,
// we'll have two different sets of demos.
#define DEFAULT_DEMO_PREFIX "res/demos/default"
#define DEFAULT_DEMO_SUFFIX ".dmo"
#define DEMO_OPEN_TITLE "Choose a Demo File to Play Back"
#define DEMO_LEVEL_DIR "res/demos/."
#define ENDING_DEMO_NAME "res/levels/single/index.rdx" // Fake name for school.dmo

#define DEMO_DIR "res/demos/."
#define DEMO_EXT ".dmo"

#define LEVEL_DIR "res/levels/."

#if WITH_STEAMWORKS
extern bool EnableSteamCloud;
#define SAVEGAME_DIR (EnableSteamCloud ? "steamcloud" : "savegame")
#else
#define SAVEGAME_DIR ("savegame")
#endif

#define CHECK_FOR_ASSETS_FILENAME "res/res.sak"
#define CHECK_FOR_POSTALSD_FILENAME "res/hoods/ezmart.sak"
#define COOKIE_VALUE 0x9504cc39		   //0xb5cf76dd
#define COOKIE_FILE_POSITION 289546856 //323290320
#define COOKIE_XOR_MASK 0x6afb39e5	   //0x66666666

// Exponent used to define gamma curve.
#define GAMMA_EXPONENT 1.50

// The directories for each type of challenge levels.
#define TIMED_CHALLENGE_LEVEL_DIR "res/levels/gauntlet/timed/."
#define CHECKPOINT_CHALLENGE_LEVEL_DIR "res/levels/gauntlet/checkpt/."
#define GOAL_CHALLENGE_LEVEL_DIR "res/levels/gauntlet/goal/."
#define FLAG_CHALLENGE_LEVEL_DIR "res/levels/gauntlet/capflag/."

// The titles for the open dialog for each type of challenge levels.
#ifdef MOBILE
#define TIMED_CHALLENGE_OPEN_TITLE "Timed Challenge"
#define CHECKPOINT_CHALLENGE_OPEN_TITLE "Checkpoint Challenge"
#define GOAL_CHALLENGE_OPEN_TITLE "Goal Challenge"
#define FLAG_CHALLENGE_OPEN_TITLE "Capture the Flag Challenge"
#else
#define TIMED_CHALLENGE_OPEN_TITLE "Choose Timed Challenge"
#define CHECKPOINT_CHALLENGE_OPEN_TITLE "Choose Checkpoint Challenge"
#define GOAL_CHALLENGE_OPEN_TITLE "Choose Goal Challenge"
#define FLAG_CHALLENGE_OPEN_TITLE "Choose Capture the Flag Challenge"
#endif

#define INVALID_DIFFICULTY -7

#define TIME_OUT_FOR_ABORT_SOUNDS 3000 // In ms.

// Get a random number plus/minus the specified value
#define RandomPlusMinus(x) ((MyRandom() % (((x) + 1) * 2)) - (x))

// Determines the number of elements in the passed array at compile time.
#define NUM_ELEMENTS(a) (sizeof(a) / sizeof(a[0]))

// We use this value to flag the fact that we haven't hidden the cursor.
#define INVALID_CURSOR_SHOW_LEVEL ((short)0x8000)

// Width of progress bar
#define PROGRESS_BAR_WIDTH 310
#define PROGRESS_BAR_HEIGHT 8

// Random values for creating spread of pixels (it does +/- these values!!!!)
#define PROGRESS_BAR_RANDOM_X 7
#define PROGRESS_BAR_RANDOM_Y 2

// Number of pixels to plot each time
#define PROGRESS_BAR_DENSITY 10

// Various action types
typedef enum
{
	ACTION_NOTHING,
	ACTION_PLAY_SINGLE,
	ACTION_PLAY_BROWSE,
	ACTION_PLAY_HOST,
	ACTION_PLAY_CONNECT,
	ACTION_PLAY_CHALLENGE,
	ACTION_EDITOR,
	ACTION_DEMO_PLAYBACK,
	ACTION_DEMO_RECORD,
	ACTION_EDIT_INPUT_SETTINGS,
	ACTION_POSTAL_ORGAN,
	ACTION_LOAD_GAME,
	ACTION_PLAY_ADDON,
	ACTION_PLAY_ADDON2,
	ACTION_PLAY_ALL,
	ACTION_LOAD_COOP_GAME,
	ACTION_PAL_50,
	ACTION_PAL_60,
	ACTION_LANGUAGE_SET	
} ACTION;

////////////////////////////////////////////////////////////////////////////////
// Variables/data
////////////////////////////////////////////////////////////////////////////////

// Global savegame state
volatile GameSave g_PlaceHolderSave = 
{
	// Game
	int8_t(0),														// cHudMode
	int8_t(1),														// cRumbleStrength
	int8_t(PLAYER_1),												// sActiveCoopPlayers
	int8_t(0),														// sPlayerColorIndex;
	{int8_t(4), int8_t(4)},											// sDifficulty[2]
	{int16_t(ACTION_NOTHING), int16_t(ACTION_NOTHING)},				// sAction[2]
	{int16_t(0), int16_t(0)},										// sRealmNum[2]	
	uint32_t(1114113), 												// ulUnlockedLevels - first levels of original, SD and Plus	
	// Stockpile
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		//  sStockPileHitPoints[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		//  sStockPileNumGrenades[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		//  sStockPileNumFireBombs[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		//  sStockPileNumMissiles[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileNumNapalms[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileNumBullets[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileNumShells[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileNumFuel[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileNumMines[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileNumHeatseekers[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileMachineGun[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileMissileLauncher[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileShotGun[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileSprayCannon[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileFlameThrower[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileNapalmLauncher[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileDeathWadLauncher[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileDoubleBarrel[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileKevlarLayers[5]
	{int16_t(0), int16_t(0), int16_t(0), int16_t(0), int16_t(0)},		// 	sStockPileBackpack[5]
	// Audio
	int8_t(0),			// sAudioLanguage
	int8_t(8),			// sPainFrequency
	{
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
		int16_t(SampleMaster::UserDefaultVolume),
	},		// sCategoryVolume[10]
	// Control Sets
	{int8_t(0), int8_t(0), int8_t(0), int8_t(0)},
	// High scores
	{
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)},
	{uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0),uint32_t(0)}
	}
};

// Global game settings
CGameSettings g_GameSettings;
int32_t g_HighScores[23][8];

// Cookie flag
int32_t g_lCookieMonster;

// Global big font
RFont g_fontBig;

// Global Postal font.
RFont g_fontPostal;

// todo??
CAicaSoundPack *g_AicaSounds = nullptr;
bool CAicaSoundPack::m_bStoreAllInAICA = false;

PVR::cRenderMgr *g_pvrRenderer;
RResMgr *PVR::cTextureFactory::pActiveResMgr = nullptr;
MAPLE::cMapleVmu* MAPLE::cMapleVmu::m_pActiveVmu = nullptr;
bool MAPLE::cMapleJump::m_bRumbleOff = false;

char *MAPLE::cMapleVmu::m_pszSaveDescL = "I regret nothing!";
char *MAPLE::cMapleVmu::m_pszSaveDescS = "POSTAL Savefile";
char *MAPLE::cMapleVmu::m_pszSaveAppName = SAVE_FILE_NAME;
    
volatile GameSave g_SaveGameState[5];
volatile int8_t g_sSaveGameSlot = 0;
volatile int8_t g_cPlayerCount = 1;
volatile int8_t g_sActiveMenuController = 0;
volatile int8_t g_cCoopPlayerColours[4] = {0};
volatile int8_t g_cLastPlayerToDie = 0;
volatile int8_t g_sActivePlayers = PLAYER_1;
volatile int8_t g_sPlayerScorePos = -1;
volatile int8_t g_sPlayerFlags[4] = {PLAYER_1, PLAYER_2, PLAYER_3, PLAYER_4};
volatile bool g_bHighScoreUpdated = false;
volatile bool g_bSaveHighScore = false;
volatile bool g_bAllCoopPlayersDead = true;
volatile bool g_bLimitMenuInput = false;
volatile bool g_bResetCamera = false;
volatile const uint8_t g_pVmuImgData[192] = {
    0x08, 0x01, 0x03, 0xF0, 0x80, 0x08, 0x08, 0x02, 0x04, 0x08, 0x40, 0x08,
    0x04, 0x04, 0x08, 0x04, 0x20, 0x10, 0x03, 0x04, 0x10, 0x02, 0x10, 0xE0,
    0x00, 0xF8, 0x20, 0x01, 0x1F, 0x00, 0x00, 0x08, 0x2C, 0x01, 0x10, 0x00,
    0x00, 0x08, 0x33, 0x81, 0x10, 0x00, 0x00, 0x08, 0x52, 0x71, 0x10, 0x00,
    0x00, 0x08, 0x52, 0x0D, 0x10, 0x00, 0x00, 0x04, 0x4C, 0x83, 0x20, 0x00,
    0x00, 0x04, 0x5F, 0x03, 0x20, 0x00, 0x00, 0x04, 0x80, 0x03, 0x20, 0x00,
    0x00, 0x03, 0x80, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x8C, 0x04, 0x80, 0x00,
    0x00, 0x00, 0x90, 0x05, 0xC0, 0x00, 0x00, 0x00, 0x90, 0x05, 0x40, 0x00,
    0x00, 0x00, 0xEB, 0xC5, 0x40, 0x00, 0x00, 0x01, 0xF7, 0xE5, 0x40, 0x00,
    0x00, 0x01, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x01, 0xF7, 0xE4, 0xC0, 0x00,
    0x00, 0x00, 0x80, 0x04, 0x40, 0x00, 0x00, 0x00, 0x80, 0x04, 0x40, 0x00,
    0x00, 0x00, 0x80, 0x04, 0x40, 0x00, 0x00, 0x00, 0x80, 0x04, 0x40, 0x00,
    0x00, 0x01, 0xC6, 0x04, 0x40, 0x00, 0x00, 0x01, 0x39, 0x08, 0x80, 0x00,
    0x00, 0x01, 0x00, 0xF0, 0x80, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00,
    0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0C, 0x00, 0x00,
    0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

volatile const uint16_t g_pSaveIconPalData[16] = {
    0xF000, 0xF100, 0xF200, 0xF200,
    0xF300, 0xF400, 0xF300, 0xF400,
    0xF500, 0xF600, 0xF311, 0xF700,
    0xF610, 0xF810, 0xFA10, 0xFB41
};

volatile const uint8_t g_pSaveIconImgData[512] = {
	0x13, 0x11, 0x11, 0x23, 0x24, 0x43, 0x31, 0x14, 0x42, 0x33, 0x23, 0x13,
	0x33, 0x31, 0x11, 0x34, 0x31, 0x11, 0x23, 0x12, 0x24, 0x21, 0x11, 0x14,
	0x44, 0x23, 0x11, 0x01, 0x32, 0x33, 0x10, 0x11, 0x33, 0x10, 0x33, 0x33,
	0x31, 0x11, 0x13, 0x44, 0x54, 0x22, 0x21, 0x00, 0x14, 0x22, 0x21, 0x11,
	0x23, 0x11, 0x11, 0x13, 0x33, 0x11, 0x27, 0x88, 0x84, 0x48, 0x97, 0x22,
	0x02, 0x42, 0x21, 0x11, 0x31, 0x13, 0x11, 0x11, 0x67, 0x88, 0x89, 0xDD,
	0xDB, 0xDB, 0xDB, 0x98, 0x73, 0x42, 0x21, 0x31, 0x31, 0x31, 0x11, 0x11,
	0x7B, 0xBB, 0xDD, 0xEF, 0xFF, 0xEE, 0xEE, 0xD9, 0x83, 0x22, 0x31, 0x13,
	0x11, 0x13, 0x11, 0x44, 0x8D, 0xBB, 0xEE, 0xEE, 0xFF, 0xFF, 0xEE, 0xB8,
	0x41, 0x13, 0x21, 0x11, 0x11, 0x12, 0x11, 0x24, 0x5B, 0xDE, 0xEE, 0xEF,
	0xFF, 0xFF, 0xFE, 0xD8, 0x84, 0x00, 0x13, 0x11, 0x13, 0x13, 0x12, 0x14,
	0x5B, 0x7D, 0xDB, 0xDE, 0xFF, 0xFF, 0xEE, 0xEB, 0x97, 0x40, 0x11, 0x13,
	0x11, 0x11, 0x11, 0x33, 0x64, 0x01, 0x9B, 0xDB, 0xDD, 0xED, 0xB8, 0x98,
	0x54, 0x21, 0x13, 0x31, 0x32, 0x23, 0x11, 0x11, 0x64, 0x54, 0x25, 0x89,
	0xBB, 0xB8, 0x85, 0x54, 0x32, 0x41, 0x23, 0x33, 0x32, 0x32, 0x31, 0x11,
	0x33, 0x63, 0xA4, 0xA4, 0x77, 0x23, 0x42, 0x54, 0x43, 0x21, 0x33, 0x33,
	0x12, 0x13, 0x10, 0x00, 0x00, 0xA3, 0xAA, 0x44, 0x41, 0x00, 0x13, 0x11,
	0x33, 0x11, 0x23, 0x32, 0x13, 0x11, 0x10, 0x10, 0x00, 0x13, 0x31, 0x12,
	0x31, 0x00, 0x10, 0x01, 0x01, 0x01, 0x31, 0x14, 0x33, 0x32, 0x31, 0x10,
	0x11, 0x11, 0x11, 0x45, 0x54, 0x11, 0x10, 0x11, 0x11, 0x11, 0x31, 0x13,
	0x23, 0x11, 0x23, 0x22, 0x62, 0x44, 0x54, 0x8D, 0xB8, 0x88, 0x82, 0x32,
	0x13, 0x11, 0x13, 0x13, 0x22, 0x33, 0x22, 0x26, 0x64, 0x57, 0x88, 0xBE,
	0xBB, 0xBB, 0xBB, 0xB4, 0x21, 0x13, 0x13, 0x22, 0x32, 0x22, 0x32, 0xA4,
	0x58, 0x77, 0x78, 0x9E, 0xEE, 0x8D, 0xDB, 0xB8, 0x44, 0x42, 0x11, 0x22,
	0x32, 0x22, 0x22, 0x46, 0x58, 0x77, 0x78, 0x9D, 0xEE, 0x9B, 0xDD, 0xB8,
	0x41, 0x33, 0x12, 0x23, 0x11, 0x33, 0x31, 0x24, 0x57, 0x77, 0x55, 0xBB,
	0xBB, 0xBB, 0xBB, 0x97, 0x41, 0x11, 0x33, 0x23, 0x11, 0x32, 0x21, 0x24,
	0x44, 0x88, 0x88, 0xDB, 0xBB, 0xB8, 0x89, 0x55, 0x31, 0x14, 0x32, 0x31,
	0x31, 0x12, 0x11, 0x24, 0x44, 0x79, 0x9B, 0xDB, 0xBB, 0xB9, 0x99, 0x54,
	0x32, 0x12, 0x33, 0x32, 0x63, 0x13, 0x11, 0x32, 0x44, 0x58, 0x78, 0x20,
	0x11, 0x88, 0x89, 0x52, 0x22, 0x11, 0x31, 0x22, 0x21, 0x11, 0x11, 0x13,
	0x45, 0x45, 0x45, 0x45, 0x44, 0x77, 0x89, 0x54, 0x22, 0x13, 0x12, 0x22,
	0x31, 0x31, 0x10, 0x11, 0x25, 0x45, 0x78, 0x98, 0x89, 0xCB, 0x98, 0x44,
	0x23, 0x13, 0x22, 0x62, 0x11, 0x31, 0x13, 0x10, 0x14, 0x77, 0x78, 0x98,
	0xBD, 0xBB, 0xB5, 0x21, 0x11, 0x31, 0x32, 0xA2, 0x33, 0x31, 0x11, 0x11,
	0x1A, 0x5C, 0x8C, 0xC9, 0xBB, 0xBB, 0xB7, 0x31, 0x11, 0x32, 0x11, 0x11,
	0x44, 0x63, 0x10, 0x01, 0x1A, 0x5C, 0xCC, 0x75, 0x5B, 0xBB, 0xB7, 0x43,
	0x11, 0x11, 0x01, 0x11, 0x55, 0x54, 0x31, 0x11, 0x11, 0x14, 0x57, 0x66,
	0x44, 0xBB, 0x97, 0x23, 0x33, 0x31, 0x10, 0x11, 0x57, 0x85, 0x46, 0x31,
	0x11, 0x11, 0x26, 0x43, 0x22, 0x24, 0x42, 0x11, 0x10, 0x01, 0x10, 0x01,
	0x47, 0x54, 0x63, 0x11, 0x1A, 0x11, 0x13, 0x23, 0x21, 0x36, 0x43, 0x11,
	0x00, 0x01, 0x00, 0x11, 0x58, 0x54, 0x31, 0x13, 0x31, 0x11, 0x13, 0x13,
	0x33, 0x35, 0x41, 0x10, 0x01, 0x11, 0x11, 0x11
};

// Resource manager for game resources.  These are resources used by the actual
// game, like things that a CThing loads that is not level specific.  For example,
// CBall loads, of course, foot.bmp, which would be loaded through this ResMgr.
// Note:  This resmgr should not be used for things like info on ordering, menu
// resources, g_fontBig, or anything not specific to the real game.
// Note:  Realm specifc data, such as alpha effects, etc., should be loaded
// through prealm->m_resmgr.
RResMgr g_resmgrGame;

// Resource manager for shell resources.  Do not use this to load things like
// the main dudes' sprites.
RResMgr g_resmgrShell;

// Resource manager for non-SAK resources.
RResMgr g_resmgrRes;

// Time and Date values
int32_t g_lRegTime;
int32_t g_lRegValue;
int32_t g_lExpTime;
int32_t g_lExpValue;
int32_t g_lReleaseTime;

// Stockpile used to transfer loaded/saved data to/from the CDude's stockpile
CStockPile g_stockpile[4];

bool g_bTransferStockpile;
int16_t g_sRealmNumToSave;

// Flag for special end of game demo sequence which is set in Play() when
// it has been determined that the player has won.
bool g_bLastLevelDemo = false;

// The secret cookie value used to determine if the humongous file exists
static U32 ms_u32Cookie = COOKIE_VALUE;

// These variables are generally controlled via the menu system
static ACTION m_action;
static int32_t m_lDemoBaseTime;
static int32_t m_lDemoTimeOut;
static char m_szRealmFile[RSP_MAX_PATH + 1];
static char m_szDemoFile[RSP_MAX_PATH + 1];
static int16_t m_sRealmNum;
static bool m_bJustOneRealm;

// Cursor show level before we went to the background
static int16_t ms_sForegroundCursorShowLevel = INVALID_CURSOR_SHOW_LEVEL;

// Used by random number stuff
static int32_t m_lRandom = 1;
static RFile *m_pfileRandom = 0;

// Used by if-logging schtuff.
static int32_t ms_lSynchLogSeq = 0;

static RFile ms_fileSynchLog;

// true to create synchronization logs, false to compare to them.
static bool m_bWriteLogs = false;

static int8_t ms_sLoadedDifficulty = INVALID_DIFFICULTY;

static SampleMaster::SoundInstance ms_siMusak = 0;

////////////////////////////////////////////////////////////////////////////////
// Function prototypes
////////////////////////////////////////////////////////////////////////////////

static int16_t GameCore(void); // Returns 0 on success.

static void ResetDemoTimer(void);

static int16_t LoadStaticTextures(void);

static int16_t OpenSaks(void); // Returns 0 on success.

static void CloseSaks(void);

static int16_t LoadAssets(void);

static int16_t UnloadAssets(void);

static void GameSetRegistry(void);

static void GameGetRegistry(void);

static void GameEndingSequence(void);

static int16_t GetRealmToRecord(
	char *pszRealmFile,
	int16_t sMaxFileLen);

static int16_t GetDemoFile(
	char *pszDemoFile,
	int16_t sMaxFileLen);

// Callback gets called when OS is about to switch app into the background
static void BackgroundCall(void);

// Callback gets called when OS is about to switch app into the foreground
static void ForegroundCall(void);

// Returns difficulty for games.
// Note that this function is only valid once after a difficulty adjustment
// and then it goes back to the default (g_GameSettings value).
static int8_t GetGameDifficulty(void); // Returns cached game difficulty.

// Opens the synchronization log with the specified access flags if in a
// TRACENASSERT mode and synchronization logging is enabled.
// Also, opens the random log, if it is enabled via
// g_GameSettings.m_szDebugMovie or something.
static void OpenSynchLogs( // Returns nothing.
	bool bWriteLogs);	   // In:  true to create log, false to compare.

// Closes the synchronization logs, if open.
static void CloseSynchLogs(void); // Returns nothing.

////////////////////////////////////////////////////////////////////////////////
//
// Local random stuff
//
////////////////////////////////////////////////////////////////////////////////
static int ms_lRandom;

inline void MySeedRandom(int32_t seed)
{
	ms_lRandom = seed;
}

inline int32_t MyRandom(void)
{
	return (((ms_lRandom = ms_lRandom * 214013L + 2531011L) >> 16) & 0x7fff);
}

////////////////////////////////////////////////////////////////////////////////
//
// Load a previously saved game
//
////////////////////////////////////////////////////////////////////////////////

extern int16_t Game_LoadPlayersGame( // Returns SUCCESS if loaded saved game file
	char *pszSaveName,				 // In:  Name of the saved game file to open
	int8_t *psDifficulty,			 // Out: Saved game realm difficulty.
	ACTION *paction);				 // Out: Saved game action.

extern int16_t Game_LoadPlayersGame(
	MAPLE::cMapleVmu* pMemoryCard,	// Returns SUCCESS if all goes well
	int8_t cSlotNumber,
	int8_t *psDifficulty, // Out: Saved game realm difficulty.
	ACTION *paction);

#if 1 //PLATFORM_UNIX
#include <sys/stat.h>
// todo?
static void EnumExistingSaveGames(Menu *menu)
{
	char gamename[RSP_MAX_PATH];
	int i = 0;
	int Max = (sizeof(menu->ami) / sizeof(menu->ami[0])) - 1;
	if (Max > MAX_SAVE_SLOTS)
		Max = MAX_SAVE_SLOTS;

#if MOBILE
	snprintf(gamename, sizeof(gamename), "%s/auto.gme", SAVEGAME_DIR);

	const char *fname = FindCorrectFile(gamename, "w");
	struct stat statbuf;

	const char *str = "unused";
	char timebuf[32];
	menu->ami[0].sEnabled = (stat(fname, &statbuf) != -1);
	if (menu->ami[0].sEnabled)
	{
		struct tm *tm;
		if ((tm = localtime((const time_t *)&statbuf.st_mtime)) == NULL)
			str = "unknown";
		else
		{
			strftime(timebuf, sizeof(timebuf), "%m/%d/%y %H:%M", tm);
			str = timebuf;
		}
	}

	snprintf(gamename, sizeof(gamename), "Auto - [%s]", str);
	menu->ami[0].pszText = strdup(gamename);

	for (i = 0; i < Max; i++)
	{
		snprintf(gamename, sizeof(gamename), "%s/%d.gme", SAVEGAME_DIR, i);

		const char *fname = FindCorrectFile(gamename, "w");
		struct stat statbuf;

		const char *str = "unused";
		char timebuf[32];
		menu->ami[i + 1].sEnabled = (stat(fname, &statbuf) != -1);

		if (menu->ami[i + 1].sEnabled)
		{
			struct tm *tm;
			if ((tm = localtime((const time_t *)&statbuf.st_mtime)) == NULL)
				str = "unknown";
			else
			{
				strftime(timebuf, sizeof(timebuf), "%m/%d/%y %H:%M", tm);
				str = timebuf;
			}
		}

		snprintf(gamename, sizeof(gamename), "%d - [%s]", i, str);
		menu->ami[i + 1].pszText = strdup(gamename);
	}
#else
	for (i = 0; i < Max; i++)
	{
		snprintf(gamename, sizeof(gamename), "%s/%d.gme", SAVEGAME_DIR, i);
		const char *fname = FindCorrectFile(gamename, "w");
		struct stat statbuf;

		const char *str = "unused";
		char timebuf[32];
		menu->ami[i].sEnabled = (stat(fname, &statbuf) != -1);
		if (menu->ami[i].sEnabled)
		{
			struct tm *tm;
			if ((tm = localtime(&statbuf.st_mtime)) == NULL)
				str = "unknown";
			else
			{
				strftime(timebuf, sizeof(timebuf), "%m/%d/%y %H:%M", tm);
				str = timebuf;
			}
		}
		snprintf(gamename, sizeof(gamename), "%s/%d.gme [%s]", SAVEGAME_DIR, i, str);

		menu->ami[i].pszText = strdup(gamename);
	}

#endif
}
#endif

////////////////////////////////////////////////////////////////////////////////
//
// Do the high-level startup stuff, run the game, and then cleanup afterwards.
//
// It is assumed that the system/RSPiX environment are setup properly before
// this function is called.
//
////////////////////////////////////////////////////////////////////////////////
extern void TheGame(void)
{
	int16_t sResult = 0;

	ms_u32Cookie = ~ms_u32Cookie;
	g_lRegValue = 0;
	g_lExpValue = 0;
	g_lRegTime = 0;

	g_lReleaseTime = RELEASE_DATE;

// To make it more confusing for someone debugging through the code,
// the flag for the cookie check will be this large number SAFE_DATE.
// Once the game determines elsewhere that the cookie is correct, it will
// in some way modify the flag so that it is not SAFE_DATE, then later
// the check will be for anything other than SAFE_DATE
#ifdef CHECK_FOR_COOKIE
	g_lCookieMonster = SAFE_DATE;
#else
	g_lCookieMonster = SAFE_DATE - RELEASE_DATE;
#endif

	g_bTransferStockpile = false;

	// Setup the PVR renderer
	g_pvrRenderer = new PVR::cRenderMgr();
	
	// Setup the aica stuff
	g_AicaSounds = new CAicaSoundPack();

	// Read all settings from preference file
	// todo: inline settings
	sResult = CSettings::LoadPrefs(g_pszPrefFileName);

	// Init extended controller input settings
	g_InputSettings[CONTROL_SCHEME_STANDARD_ALT].LoadInputSet(CInputSettings::Set2);
	g_InputSettings[CONTROL_SCHEME_WINGMAN].LoadInputSet(CInputSettings::Set3);
	g_InputSettings[CONTROL_SCHEME_REVERSE].LoadInputSet(CInputSettings::Set4);
	g_InputSettings[CONTROL_SCHEME_TWINSTICK].LoadInputSet(CInputSettings::Set5);

	// Set VMU save file image data
	rspGetJoy(0)->m_pMemCards[0]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));
	rspGetJoy(0)->m_pMemCards[1]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));
	rspGetJoy(1)->m_pMemCards[0]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));
	rspGetJoy(1)->m_pMemCards[1]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));
	rspGetJoy(2)->m_pMemCards[0]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));
	rspGetJoy(2)->m_pMemCards[1]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));
	rspGetJoy(3)->m_pMemCards[0]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));
	rspGetJoy(3)->m_pMemCards[1]->SetSaveIcon((uint8_t*)(g_pSaveIconImgData), (uint16_t*)(g_pSaveIconPalData));

	// Set VMU LCD image data
	rspGetJoy(0)->m_pMemCards[0]->SetLCDImage((uint8_t*)(g_pVmuImgData));
	rspGetJoy(0)->m_pMemCards[1]->SetLCDImage((uint8_t*)(g_pVmuImgData));
	rspGetJoy(1)->m_pMemCards[0]->SetLCDImage((uint8_t*)(g_pVmuImgData));
	rspGetJoy(1)->m_pMemCards[1]->SetLCDImage((uint8_t*)(g_pVmuImgData));
	rspGetJoy(2)->m_pMemCards[0]->SetLCDImage((uint8_t*)(g_pVmuImgData));
	rspGetJoy(2)->m_pMemCards[1]->SetLCDImage((uint8_t*)(g_pVmuImgData));
	rspGetJoy(3)->m_pMemCards[0]->SetLCDImage((uint8_t*)(g_pVmuImgData));
	rspGetJoy(3)->m_pMemCards[1]->SetLCDImage((uint8_t*)(g_pVmuImgData));

	RFile file;

	if (sResult == 0)
	{
		// Try loading special file that exists solely for the purpose of checking for
		// valid paths in the prefs file.  We do this for each of the paths.

		if (sResult == 0)
		{
			if (file.Open(FullPathCD(CHECK_FOR_ASSETS_FILENAME), "r", RFile::LittleEndian) == 0)
			{
				file.Close();
			}

			else
			{
				sResult = -1;
				TRACE("Game(): Can't find assets based on HD path specified in prefs!\n");
			}
		}
		if (sResult == 0)
		{
			if (file.Open(FullPathSound(CHECK_FOR_ASSETS_FILENAME), "r", RFile::LittleEndian) == 0)
				file.Close();
			else
			{
				sResult = -1;
				TRACE("Game(): Can't find assets based on Sound path specified in prefs!\n");
			}
		}
		if (sResult == 0)
		{
			if (file.Open(FullPathGame(CHECK_FOR_ASSETS_FILENAME), "r", RFile::LittleEndian) == 0)
				file.Close();
			else
			{
				sResult = -1;
				TRACE("Game(): Can't find assets based on Game path specified in prefs!\n");
			}
		}

		if (sResult == 0)
		{
			if (file.Open(FullPathHoods(CHECK_FOR_ASSETS_FILENAME), "r", RFile::LittleEndian) == 0)
				file.Close();
			else
			{
				sResult = -1;
				TRACE("Game(): Can't find assets based on Hoods path specified in prefs!\n");
			}
		}

		// Check for special file, COOKIE, and size.
		if (sResult == 0)
		{
			//todo: should beu sing this
			ms_u32Cookie ^= COOKIE_XOR_MASK;

			if (file.Open(FullPathCD(CHECK_FOR_ASSETS_FILENAME), "rb", RFile::LittleEndian) == 0)
			{
				file.Close();
			}
			else
			{
				sResult = -1;
				TRACE("Game(): Can't find assets based on CD path specified in prefs!\n");
			}

			// If any problems . . .
			if (sResult != 0)
			{
			}
		}

		if (sResult == 0)
		{

			// Set the gamma level to value indicated by settings.
			SetGammaLevel(g_GameSettings.m_sGammaVal);

			// Open SAKs or setup equivalent paths.
			sResult = OpenSaks();

			// Load font.
			if (g_fontBig.Load(FullPath(GAME_PATH_VD, BIG_FONT_FILE)) != 0)
			{
				TRACE("GameLoadAssets(): Error loading font: %s !\n", FullPath(GAME_PATH_VD, BIG_FONT_FILE));
				sResult = -1;
			}

			// Load font, the smaller.
			if (g_fontPostal.Load(FullPath(GAME_PATH_VD, POSTAL_FONT_FILE)) != 0)
			{
				TRACE("GameLoadAssets(): Error loading font: %s !\n", FullPath(GAME_PATH_VD, POSTAL_FONT_FILE));
				sResult = -1;
			}

			PVR::cTexture *pTexture;

			PVR::cTextureFactory::SetResMgr(&g_resmgrGame);
			LoadStaticTextures();

			PVR::cTextureFactory::SetResMgr(&g_resmgrShell);
									
			g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SPLASH_FIRST, PVR::cTextureFactory::FromKmg("title/rating.bmp", 3, 2));
			g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SPLASH_SECOND, PVR::cTextureFactory::FromKmg("title/logo.bmp", 3, 2));
			//g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SPLASH_KOS, PVR::cTextureFactory::FromKmg("title/kallistios.bmp", 3, 2));
			g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SPLASH_CHINCHILLA, PVR::cTextureFactory::FromKmg("title/chinchillaretro.bmp", 3, 2));
			g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SPLASH_WAVE, PVR::cTextureFactory::FromKmg("title/wave.bmp", 3, 2));
			g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SPLASH_THIRD, PVR::cTextureFactory::FromKmg("title/postal.bmp", 3, 2));

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	bool bMenuActive = false;
	RInputEvent ie;	
	// Clear any events that might be in the queue	
	ClearMapleInputState(0);
	ClearMapleInputState(1);
	ClearMapleInputState(2);
	ClearMapleInputState(3);

	// Get the registry value
	GameGetRegistry();

	char *sakName;
	if (false && g_GameSettings.m_sAudioLanguage == JAPANESE_AUDIO)
	{
		sakName = "/cd/res/sound/shell-j.sak";
	}
	else
	{
		sakName = "/cd/res/sound/shell.sak";
	}

	if (!strstr(g_resmgrSamples.GetBasePath(), sakName))
	{
		g_resmgrSamples.SetBasePath(sakName);
		g_resmgrSamples.Purge();
		g_resmgrSamples.CloseSak();
		g_resmgrSamples.OpenSak(sakName);

		// todo?
		// Reload relevant menu sounds
		CacheSample(g_smidGeneralBeep);
		CacheSample(g_smidMenuItemSelect);
		CacheSample(g_smidMusicTester);
		CacheSample(g_smidAmbientTester);
		CacheSample(g_smidTitleMusak);
		CacheSample(g_smidEndingAudio);
		CacheSample(g_smidCreditsMusak);
		CacheSample(g_smidShotgun);
		CacheSample(g_smidRocketExplode);
		CacheSample(g_smidLoadedWeapon);
		CacheSample(g_smidEmptyWeapon);
		CacheSample(g_smidSteveScumbag);
		CacheSample(g_smidMikeAhh);
		CacheSample(g_smidWrithing2);
		CacheSample(g_smidDemonBleed);
	}

	if (rspIs50Hz())
	{
		while (sResult == 0)
		{					
			if (bMenuActive)
			{			
				// As always...
				UpdateSystem();
				g_pvrRenderer->FlushBatchedDrawQueues();
			}
		
			// Get next input event
			ie.type = RInputEvent::None;
			rspGetNextInputEvent(&ie);
		
			// If menu isn't active, get it going
			if (!bMenuActive)
			{	
				sResult = StartMenu(&menuVideo, &g_resmgrShell);
				if (sResult == 0)
				{
					bMenuActive = true;			
				}
				else
				{				
					break;
				}
			}	
			// Let menu system do its thing		
			DoMenuInput(&ie);
			g_pvrRenderer->FlushBatchedDrawQueues();
			DoMenuOutput();

			if (m_action != ACTION_NOTHING)
			{
				switch (m_action)
				{				
					case ACTION_PAL_50:
						sResult = 1;
						break;
					case ACTION_PAL_60:
						sResult = 1;
						rspSetVideoMode(8, 640, 480, 640, 480, 1, 0);
						break;
				}
			}			
		}

		// If the menu is active, end it
		if (bMenuActive)
		{
			StopMenu();
			m_action = ACTION_NOTHING;
			bMenuActive = false;
			sResult = 0;
		}
	}


	while (sResult == 0)
		{					
			if (bMenuActive)
			{			
				// As always...
				UpdateSystem();
				g_pvrRenderer->FlushBatchedDrawQueues();
			}
		
			// Get next input event
			ie.type = RInputEvent::None;
			rspGetNextInputEvent(&ie);
		
			// If menu isn't active, get it going
			if (!bMenuActive)
			{	
				sResult = StartMenu(&menuLanguage, &g_resmgrShell);
				if (sResult == 0)
				{
					bMenuActive = true;			
				}
				else
				{				
					break;
				}
			}	
			// Let menu system do its thing		
			DoMenuInput(&ie);
			g_pvrRenderer->FlushBatchedDrawQueues();
			DoMenuOutput();

			if (m_action == ACTION_LANGUAGE_SET)
			{
				sResult = 1;						
			}			
		}

		// If the menu is active, end it
		if (bMenuActive)
		{
			StopMenu();
			m_action = ACTION_NOTHING;
			bMenuActive = false;
			sResult = 0;
		}



			if (sResult == 0)
			{
				// Start title, passing the "total units" for its progress meter
				sResult = StartTitle(1, true, &ms_siMusak);

				if (sResult == 0)
				{
					// Load assets that we want to keep around at all times
					sResult = LoadAssets();
					// End title (regardless of previous result)
					EndTitle();

					if (sResult == 0)
					{

						// Set the font most GUIs will use (the menu system uses its own RPrint).
						// Note that the size does not matter, we just want to set the font ptr.
						RGuiItem::ms_print.SetFont(15, &g_fontBig);

						// Do the core game stuff
						sResult = GameCore();

						// If there weren't any errors, wrap things up
						if (!sResult)
						{
							// Do ending credits
							if (rspGetQuitStatus() > 1)
							{
								rspSetQuitStatus(0);
								Credits();
							}
						}
					}
					
					// Unload assets loaded earlier
					UnloadAssets();
				}
				else
				{
					TRACE("Game(): Error returned by StartTitle()!\n");
				}
			}
			else
			{
				TRACE("Game(): Error returned by OpenSaks().\n");
			}
	
			// Abort all samples.
			AbortAllSamples();

			// We should never need a timeout but I don't want to risk a Muppets
			// scenario where a shitty sound driver causes us to think a sound is always
			// playing.
			// Wait for all samples to finish.
			int32_t lTimeOutTime = rspGetMilliseconds() + TIME_OUT_FOR_ABORT_SOUNDS;
			// Wait for them to stop.
			while (IsSamplePlaying() == true && rspGetMilliseconds() < lTimeOutTime)
			{
				// Always do periodic updates.
				// Crucial to sound completing.
				UpdateSystem();
			}

			// Close SAKs and/or create SAKs??
			CloseSaks();
		}	
	}
	else
	{
		TRACE("Game(): Error returned by ReadGamePrefs()!\n");
	}
}

static int16_t LoadStaticTextures()
{
	PVR::cTextureFactory::SetResMgr(&g_resmgrGame);	
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_PARTICLES_AAN, PVR::cTextureFactory::FromKmg("vqtextures/animations/particles.aan", 1, 1, 256, 256, true));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SMOKE_AAN, PVR::cTextureFactory::FromKmg("vqtextures/animations/smoke.aan", 2, 1, true));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_EXPLODE_AAN, PVR::cTextureFactory::FromKmg("vqtextures/animations/explo.aan", 2, 1, true));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_GEXPLODE_AAN, PVR::cTextureFactory::FromKmg("vqtextures/animations/gexplo.aan", 2, 1, true));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_FIRE_AAN, PVR::cTextureFactory::FromKmg("vqtextures/animations/fire.aan", 2, 1, true));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_TOPBAR, PVR::cTextureFactory::FromKmg("vqtextures/ui/topbar", 1, 1, 256, 256));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_CONTROLLER, PVR::cTextureFactory::FromKmg("vqtextures/ui/controller", 2, 2, 512, 512, true));
		
	// Mine sprites - todo?? BMP?
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MINE_TIMED, PVR::cTextureFactory::FromRimage("2d/side/timemine.img"));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MINE_PROXIMITY, PVR::cTextureFactory::FromRimage("2d/side/proxmine.img"));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MINE_BETTY, PVR::cTextureFactory::FromRimage("2d/side/bettymine.img"));

	// Shadow sprite - todo: do i need bmp?
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SHADOW, PVR::cTextureFactory::FromRimage("2d/side/shadow.img"));	
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_SMALL_SHADOW, PVR::cTextureFactory::FromRimage("2d/side/smallshadow.img"));
	
	// Menu background texture.
	RImage *pImg;
	rspGetResource(&g_resmgrShell, "Menu/menu_md.bmp", &pImg);
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MENU_BG, PVR::cTextureFactory::FromRimage(pImg, PVR::cRenderMgr::MenuPalette));

	// Menu palette.
	if (pImg->m_pPalette != NULL)
	{
		g_pvrRenderer->m_pPaletteMenu = pImg->DetachPalette();
		int16_t sEntrySize = g_pvrRenderer->m_pPaletteMenu->m_sPalEntrySize;
		int16_t sStartIndex = 0;

		// Set palette.
		rspSetPaletteEntries(
			0,
			230,
			g_pvrRenderer->m_pPaletteMenu->Red(sStartIndex),
			g_pvrRenderer->m_pPaletteMenu->Green(sStartIndex),
			g_pvrRenderer->m_pPaletteMenu->Blue(sStartIndex),
			sEntrySize);

		// Update hardware.
		rspUpdatePalette(PVR::cRenderMgr::DefaultPalette);
		rspUpdatePalette(PVR::cRenderMgr::MenuPalette);
		rspUpdatePalette(PVR::cRenderMgr::HudPalette);

		pvr_set_pal_entry(RSP_WHITE_INDEX, PACK_ARGB8888(0, 0, 0, 0));
		pvr_set_pal_entry(RSP_WHITE_INDEX + 256, PACK_ARGB8888(0, 0, 0, 0));
	}

	rspReleaseAndPurgeResource(&g_resmgrShell, &pImg);

	// Progress bar texture.		
	static int16_t asWavyY[] = {0, 1, 2, 0, -2, -1, 1, 0, -1, 0, 1, 2, 1, 0, 1, 0, -1, -2, -1, -2, 0, 1, 2, 0, -2, -1, 0, 0};	
	int16_t sBaseY = asWavyY[(int16_t)(1 * (NUM_ELEMENTS(asWavyY) - 1))];
	pImg = new RImage();
	pImg->CreateImage(PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT, RImage::BMP8);
	rspRect(RSP_WHITE_INDEX, pImg, 0, 0, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT);
	
	// Draw a few pixels at a time to get a more solid look					
	for (int16_t sBaseX = 0; sBaseX <= PROGRESS_BAR_WIDTH; sBaseX++)
	{
		for (int16_t i = 0; i < PROGRESS_BAR_DENSITY; i++)
		{
			// Choose random location nearby the specified location
			int16_t sX = sBaseX + RandomPlusMinus(PROGRESS_BAR_RANDOM_X);
			int16_t sY = sBaseY + RandomPlusMinus(PROGRESS_BAR_RANDOM_Y);
			uint8_t ucBloodColour = rspMatchColorRGB(
				128, 0, 0, 
				1, 254, 
				g_pvrRenderer->m_pPaletteMenu->Red(), 
				g_pvrRenderer->m_pPaletteMenu->Green(), 
				g_pvrRenderer->m_pPaletteMenu->Blue(), sizeof(U8));

			// Draw apixel at this location
			rspPlot(ucBloodColour, pImg, sX, sY);
		}
	}
	
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_PROGRESS_BAR, PVR::cTextureFactory::FromRimage(pImg));
	delete pImg;

	// UI widget textures.
	PVR::cTextureFactory::SetResMgr(&g_resmgrShell);
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MENU_BULLET, PVR::cTextureFactory::FromRimage("Menu/bullet.bmp", PVR::cRenderMgr::MenuPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MENU_BUTTON, PVR::cTextureFactory::FromRimage("Menu/button.bmp", PVR::cRenderMgr::MenuPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MENU_BUTTON_L, PVR::cTextureFactory::FromRimage("Menu/buttonleft.bmp", PVR::cRenderMgr::MenuPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MENU_BUTTON_R, PVR::cTextureFactory::FromRimage("Menu/buttonright.bmp", PVR::cRenderMgr::MenuPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MENU_BAR, PVR::cTextureFactory::FromRimage("Menu/bar.bmp", PVR::cRenderMgr::MenuPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_MENU_TEXT_BG, PVR::cTextureFactory::FromRimage("Menu/textbg.bmp", PVR::cRenderMgr::MenuPalette));

	// Set toolbar/HUD palette.
	g_pvrRenderer->m_pPaletteHud = new RPal;
	g_pvrRenderer->m_pPaletteHud->Load("/cd/res/vqtextures/hoods/afb/afb.bmp/pal.pal");
	int16_t sEntrySize = g_pvrRenderer->m_pPaletteHud->m_sPalEntrySize;
	int16_t sStartIndex = 0;
	rspSetPaletteEntries(
		0,
		256,
		g_pvrRenderer->m_pPaletteHud->Red(sStartIndex),
		g_pvrRenderer->m_pPaletteHud->Green(sStartIndex),
		g_pvrRenderer->m_pPaletteHud->Blue(sStartIndex),
		sEntrySize);

	// Toolbar BG & target sprites
	PVR::cTextureFactory::SetResMgr(&g_resmgrGame);
	//g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_TOPBAR, PVR::cTextureFactory::FromRimage("2d/side/topbar.bmp", PVR::cRenderMgr::HudPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_TOOLBAR, PVR::cTextureFactory::FromRimage("2d/side/toolbar.bmp", PVR::cRenderMgr::HudPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_TARGET_FIRST, PVR::cTextureFactory::FromRimage("2d/side/target1.bmp", PVR::cRenderMgr::HudPalette));
	//g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_TARGET_SECOND, PVR::cTextureFactory::FromRimage("2d/side/target2.bmp", PVR::cRenderMgr::HudPalette));
	g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_TARGET_THIRD, PVR::cTextureFactory::FromRimage("2d/side/target3.bmp", PVR::cRenderMgr::HudPalette));

	// Update hardware.
	rspUpdatePalette(PVR::cRenderMgr::HudPalette);
	pvr_set_pal_entry(RSP_WHITE_INDEX + 256 * PVR::cRenderMgr::HudPalette, PACK_ARGB8888(0, 0, 0, 0));
}

////////////////////////////////////////////////////////////////////////////////
//
// Do the core game stuff (display menu, play a game, run the demo, etc.)
//
////////////////////////////////////////////////////////////////////////////////
static int16_t GameCore(void) // Returns 0 on success.
{
	int16_t sResult = 0;
	uint16_t usDemoCount = 0;
	bool bMPath = false,
		 bMPathServer = false;

	// Clear end of game flag - play will set the flag if the player wins the game
	g_bLastLevelDemo = false;

	// Init demo mode times
	m_lDemoBaseTime = rspGetMilliseconds();
	m_lDemoTimeOut = g_GameSettings.m_lInitialDemoTimeOut;

	// Clear flags that are updated by the menu system callbacks (in this module)
	m_action = ACTION_NOTHING;
	m_sRealmNum = 0;
	m_szRealmFile[0] = 0;
	m_bJustOneRealm = false;

	// Flag indicates whether or not menu is active
	bool bMenuActive = false;
	ACTION actionNext = ACTION_NOTHING; // Initialized for safety.

	Menu *pmenuStart = NULL; // Next menu to start if not NULL.
	bool bPalTran = true;	 // true to PalTranOn() before next menu.
	bool bTitleImage = true; // true to display title image before next menu.
	bool bTitleMusak = true; // true to play title musak during next menu. Requires title image.

	//////////////////////////////////////
	//////////////////////////////////////

	// Draw previously loaded title image.
	g_pvrRenderer->FlushBatchedDrawQueues();
	
	// Keep looping until user quits or error occurs.  Maybe even play
	// a game or two along the way.  If there's no user input for a
	// certain amount of time, automatically run the self-playing demo.
	RInputEvent ie;
	memset(&ie, '\0', sizeof(ie)); // fix valgrind complaining... --ryan.

	while (sResult == 0)
	{
		// Clear the end of game flag each time just to be safe, it only needs
		// to be set within the last iteration
		g_bLastLevelDemo = false;

		char *sakName;
		if ( false && g_GameSettings.m_sAudioLanguage == JAPANESE_AUDIO)
		{
			sakName = "/cd/res/sound/shell-j.sak";
		}
		else
		{
			sakName = "/cd/res/sound/shell.sak";
		}

		if (!strstr(g_resmgrSamples.GetBasePath(), sakName))
		{
			g_GameSettings.m_bLimitedSampleTesters = false;
			g_resmgrSamples.SetBasePath(sakName);
			g_resmgrSamples.Purge();
			g_resmgrSamples.CloseSak();
			g_resmgrSamples.OpenSak(sakName);

			// todo?
			// Reload relevant menu sounds
			CacheSample(g_smidGeneralBeep);
			CacheSample(g_smidMenuItemSelect);
			CacheSample(g_smidMusicTester);
			CacheSample(g_smidAmbientTester);
			CacheSample(g_smidTitleMusak);
			CacheSample(g_smidEndingAudio);
			CacheSample(g_smidCreditsMusak);
			CacheSample(g_smidShotgun);
			CacheSample(g_smidRocketExplode);
			CacheSample(g_smidLoadedWeapon);
			CacheSample(g_smidEmptyWeapon);
			CacheSample(g_smidSteveScumbag);
			CacheSample(g_smidMikeAhh);
			CacheSample(g_smidWrithing2);
			CacheSample(g_smidDemonBleed);
		}

		// todo ???
		if (g_pvrRenderer->m_szDrawMode == PVR::cRenderMgr::Batched && bMenuActive)
		{
			g_pvrRenderer->DrawQuad(
				g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_SPLASH_THIRD),
				0, 0, 640, 480,
				PVR::cRenderMgr::LayerBg);

			// As always...
			UpdateSystem();

			g_pvrRenderer->FlushBatchedDrawQueues();
		}

		// Get next input event
		ie.type = RInputEvent::None;
		rspGetNextInputEvent(&ie);

		// If there's any key or button input, or the app is in BG mode (per the OS),
		// reset the demo timer
		if ((ie.type != RInputEvent::None) || rspIsBackground())
			ResetDemoTimer();

		// Does user want to exit app?  Do this BEFORE checking whether menu is active
		// so we won't bother activating the menu only to find out we're going to exit.
		if (rspGetQuitStatus())
			break;

		// If menu isn't active, get it going
		if (!bMenuActive)
		{
			// If paltran requested . . .
			if (bPalTran)
				PalTranOn();
				
			if (MAPLE::cMapleVmu::GetCount() == 0)
			{
				sResult = StartMenu(pmenuStart ? pmenuStart : &menuNoMemoryCard, &g_resmgrShell);
			}
			else if (MAPLE::cMapleVmu::m_pActiveVmu == nullptr && g_sSaveGameSlot >= 0)
			{				
				sResult = StartMenu(pmenuStart ? pmenuStart : &menuPickMemoryCard, &g_resmgrShell);
			}
			else
			{
				sResult = StartMenu(pmenuStart ? pmenuStart : &menuMain, &g_resmgrShell);
			}
						
			if (sResult == 0)
			{
				bMenuActive = true;
				// Restore defaults.
				pmenuStart = NULL;
				bPalTran = true;
				bTitleImage = true;
				bTitleMusak = true;
			}
			else
			{
				TRACE("GameLoop(): Error returned by StartMenu()!\n");
				break;
			}
		}
	
		// Let menu system do its thing		
		DoMenuInput(&ie);

		g_pvrRenderer->FlushBatchedDrawQueues();
		g_pvrRenderer->DrawQuad(
			g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_SPLASH_THIRD),
			0, 0, 640, 480,
			PVR::cRenderMgr::LayerBg, 1.0f);

		float r = g_pvrRenderer->m_fGlobalRScale;
		float g = g_pvrRenderer->m_fGlobalGScale;
		float b = g_pvrRenderer->m_fGlobalBScale;
		g_pvrRenderer->m_fGlobalRScale = 1.0f;
		g_pvrRenderer->m_fGlobalGScale = 1.0f;
		g_pvrRenderer->m_fGlobalBScale = 1.0f;
		
		DoMenuOutput();

		g_pvrRenderer->m_fGlobalRScale = r;
		g_pvrRenderer->m_fGlobalGScale = g;
		g_pvrRenderer->m_fGlobalBScale = b;
		// Reset next action.
		actionNext = ACTION_NOTHING;

		// Any action to handle?
		if (m_action != ACTION_NOTHING)
		{
			// Handle the action
			switch (m_action)
			{
			//------------------------------------------------------------------------------
			// Play single player game
			//------------------------------------------------------------------------------
			case ACTION_PLAY_SINGLE:

				// End menu
				g_pvrRenderer->FlushBatchedDrawQueues();
				StopMenu();

				PalTranOff();
				bMenuActive = false;

				// Clear the game winning flag - play will set it before it returns
				// if the player has won the game
				g_bLastLevelDemo = false;
			//g_bLastLevelDemo = true;
//m_sRealmNum = 15;
				//if(false)
				Play(				
					INPUT_MODE_LIVE,	 // Input mode
					m_sRealmNum,		 // Realm number OR -1 to use realm file
					m_szRealmFile,		 // Realm file
					m_bJustOneRealm,	 // Whether to play just one realm or not
					false,				 // Not challenge mode
					0,					 // Not new single player Add on levels
					GetGameDifficulty(), // Difficulty level
					false,				 // Rejunenate (MP only)
					0,					 // Time limit (MP only)
					0,					 // Kill limit (MP only)
					0,					 // Use cooperative levels (MP only)
					(g_sActivePlayers > PLAYER_1) ? 1 : 0,	 // Use cooperative mode (MP only)
					0,					 // Frame time (MP only)
					NULL);				 // Demo mode file

				// If the player won the game, show them the last level demo
				// and then the ending cutscenes.
				if (g_bLastLevelDemo)
					GameEndingSequence();

				// clear the end of game flag just to be safe
				g_bLastLevelDemo = false;
				break;

			//------------------------------------------------------------------------------
			// Join a multiplayer game
			//------------------------------------------------------------------------------
			case ACTION_PLAY_BROWSE:
			case ACTION_PLAY_CONNECT:
			break;

			//------------------------------------------------------------------------------
			// Host a multiplayer game
			//------------------------------------------------------------------------------
			case ACTION_PLAY_HOST:
			break;

			//------------------------------------------------------------------------------
			// Play challenge game
			//------------------------------------------------------------------------------
			case ACTION_PLAY_CHALLENGE:
				// Remember menu to go back to.
				// pmenuStart = GetCurrentMenu();
				g_pvrRenderer->FlushBatchedDrawQueues();
				// End the menu.
				StopMenu();
				bMenuActive = false;
				// Turn off paltran but remember to restore.
				PalTranOff();
				bPalTran = true;
				// Remember to show title, but no musak.
				bTitleImage = true;
				bTitleMusak = false;

				// Note that m_sRealmNum, m_szRealmFile, and m_bJustOneRealm are
				// set via the callback, Game_StartChallengeGame().
				// ***ADD FLAG(S) TO THIS CALL INDICATING THIS IS A CHALLENGE GAME***
				Play(				
					INPUT_MODE_LIVE,	 // Input mode
					m_sRealmNum,		 // Realm number OR -1 to use realm file
					m_szRealmFile,		 // Realm file
					m_bJustOneRealm,	 // Whether to play just one realm or not
					true,				 // Play challenge levels
					false,				 // Not new single player Add On levels
					GetGameDifficulty(), // Difficulty level
					false,				 // Rejunenate (MP only)
					0,					 // Time limit (MP only)
					0,					 // Kill limit (MP only)
					0,					 // Cooperative (MP only)
					(g_sActivePlayers > PLAYER_1) ? 1 : 0,	 // Use cooperative mode (MP only)
					0,					 // Frame time (MP only)
					NULL);				 // Demo mode file

#ifdef MOBILE
				AndroidSetScreenMode(TOUCH_SCREEN_MENU);
#endif
				break;

			//------------------------------------------------------------------------------
			// Play Add On levels
			//------------------------------------------------------------------------------
			case ACTION_PLAY_ADDON:
#ifndef SPAWN
				// Remember menu to go back to.
				//pmenuStart = GetCurrentMenu();
				// End the menu.
				g_pvrRenderer->FlushBatchedDrawQueues();
				StopMenu();
				bMenuActive = false;

				// Clear the game winning flag - play will set it before it returns
				// if the player has won the game
				g_bLastLevelDemo = false;
				//g_bLastLevelDemo = true;

				// Turn off paltran but remember to restore.
				PalTranOff();
				bPalTran = true;
				// Remember to show title, but no musak.
				bTitleImage = true;
				bTitleMusak = false;

				// Note that m_sRealmNum, m_szRealmFile, and m_bJustOneRealm are
				// set via the callback, Game_StartChallengeGame().
				// ***ADD FLAG(S) TO THIS CALL INDICATING THIS IS A CHALLENGE GAME***
				Play(			
					INPUT_MODE_LIVE,	 // Input mode
					m_sRealmNum,		 // Realm number OR -1 to use realm file
					m_szRealmFile,		 // Realm file
					m_bJustOneRealm,	 // Whether to play just one realm or not
					false,				 // Don't play challenge levels
					1,					 // Play new single player Add on levels
					GetGameDifficulty(), // Difficulty level
					false,				 // Rejunenate (MP only)
					0,					 // Time limit (MP only)
					0,					 // Kill limit (MP only)
					0,					 // Cooperative (MP only)
					(g_sActivePlayers > PLAYER_1) ? 1 : 0,	 // Use cooperative mode (MP only)
					0,					 // Frame time (MP only)
					NULL);				 // Demo mode file
#ifdef MOBILE
				AndroidSetScreenMode(TOUCH_SCREEN_MENU);
#endif
#endif // SPAWN
				break;
#if TARGET == POSTAL_2015
#ifndef SPAWN
			case ACTION_PLAY_ADDON2:
				// Remember menu to go back to.
				//pmenuStart = GetCurrentMenu();
				// End the menu.
				g_pvrRenderer->FlushBatchedDrawQueues();
				StopMenu();
				bMenuActive = false;
				// Turn off paltran but remember to restore.
				PalTranOff();
				bPalTran = true;
				// Remember to show title, but no musak.
				bTitleImage = true;
				bTitleMusak = false;

				// Note that m_sRealmNum, m_szRealmFile, and m_bJustOneRealm are
				// set via the callback, Game_StartChallengeGame().
				// ***ADD FLAG(S) TO THIS CALL INDICATING THIS IS A CHALLENGE GAME***
				Play(	
					INPUT_MODE_LIVE,	 // Input mode
					m_sRealmNum,		 // Realm number OR -1 to use realm file
					m_szRealmFile,		 // Realm file
					m_bJustOneRealm,	 // Whether to play just one realm or not
					false,				 // Don't play challenge levels
					2,					 // Play new single player Japan Add on levels
					GetGameDifficulty(), // Difficulty level
					false,				 // Rejunenate (MP only)
					0,					 // Time limit (MP only)
					0,					 // Kill limit (MP only)
					0,					 // Cooperative (MP only)
					(g_sActivePlayers > PLAYER_1) ? 1 : 0,	 // Use cooperative mode (MP only)
					0,					 // Frame time (MP only)
					NULL);				 // Demo mode file
#ifdef MOBILE
				AndroidSetScreenMode(TOUCH_SCREEN_MENU);
#endif
				break;
			case ACTION_PLAY_ALL:
				// Remember menu to go back to.
				//	pmenuStart = GetCurrentMenu();
				g_pvrRenderer->FlushBatchedDrawQueues();
				// End the menu.
				StopMenu();
				bMenuActive = false;
				// Turn off paltran but remember to restore.
				PalTranOff();
				bPalTran = true;
				// Remember to show title, but no musak.
				bTitleImage = true;
				bTitleMusak = false;

				// Note that m_sRealmNum, m_szRealmFile, and m_bJustOneRealm are
				// set via the callback, Game_StartChallengeGame().
				// ***ADD FLAG(S) TO THIS CALL INDICATING THIS IS A CHALLENGE GAME***
				Play(				
					INPUT_MODE_LIVE,	 // Input mode
					m_sRealmNum,		 // Realm number OR -1 to use realm file
					m_szRealmFile,		 // Realm file
					m_bJustOneRealm,	 // Whether to play just one realm or not
					false,				 // Don't play challenge levels
					3,					 // Play all other levels, though
					GetGameDifficulty(), // Difficulty level
					false,				 // Rejunenate (MP only)
					0,					 // Time limit (MP only)
					0,					 // Kill limit (MP only)
					0,					 // Cooperative (MP only)
					(g_sActivePlayers > PLAYER_1) ? 1 : 0,	 // Use cooperative mode (MP only)
					0,					 // Frame time (MP only)
					NULL);				 // Demo mode file
#ifdef MOBILE
				AndroidSetScreenMode(TOUCH_SCREEN_MENU);
#endif
				// If the player won the game, show them the last level demo
				// and then the ending cutscenes.
				if (g_bLastLevelDemo)
					GameEndingSequence();

				// clear the end of game flag just to be safe
				g_bLastLevelDemo = false;
				break;
#endif // SPAWN
#endif // POSTAL_2015

			//------------------------------------------------------------------------------
			// Playback demo
			//------------------------------------------------------------------------------
			case ACTION_DEMO_PLAYBACK:
			{
				// Prepare all settings for demo mode
				CSettings::PreDemo();

				// If demo debug movie file is specified in prefs, open it now.  The RFile*
				// is does double-duty as a flag, where non-zero means movie mode is enabled.
				RFile *pfileDemoDebugMovie = 0;
#if 0
					if (strlen(g_GameSettings.m_szDemoDebugMovie) > 0)
						{
						pfileDemoDebugMovie = new RFile;
						if (pfileDemoDebugMovie->Open(g_GameSettings.m_szDemoDebugMovie, "rb", RFile::LittleEndian) != 0)
							{
							delete pfileDemoDebugMovie;
							pfileDemoDebugMovie = 0;
							}
						}
#else
				// Compare to synchronization logs, if enabled.
				OpenSynchLogs(false);
#endif

				if (InputDemoInit() == 0)
				{
					// If no specific filename has been set for the demo, then load one of
					// the default demos.
					if (m_szDemoFile[0] == '\0')
					{
						// If there are default demos . . .
						if (g_GameSettings.m_sNumAvailableDemos > 0)
						{
							sprintf(m_szDemoFile, "%s%d%s", FullPathCD(DEFAULT_DEMO_PREFIX), usDemoCount % MAX((int16_t)1, g_GameSettings.m_sNumAvailableDemos), DEFAULT_DEMO_SUFFIX);
						}
					}

					// If there is now a demo filename . . .
					if (m_szDemoFile[0] != '\0')
					{
						RFile fileDemo;
						usDemoCount++;
						if (fileDemo.Open(m_szDemoFile, "rb", RFile::LittleEndian) == 0)
						{
							// Read name of realm file
							char szRealmFile[RSP_MAX_PATH];
							fileDemo.Read(szRealmFile);
							// Read whether it's a full path.
							int16_t sRealmFileIsFullPath;
							fileDemo.Read(&sRealmFileIsFullPath);
							if (!fileDemo.Error())
							{
								// Load input demo data (must be BEFORE setting playback mode)
								if (InputDemoLoad(&fileDemo) == 0)
								{
									// End menu (now that we know there were no errors)
									StopMenu();
									PalTranOff();
									bMenuActive = false;

									Play(										
										INPUT_MODE_PLAYBACK,  // Input mode
										-1,					  // Always use specific realm file
										szRealmFile,		  // Realm file to be played
										false,				  // Don't play just one realm
										false,				  // Not challenge mode
										0,					  // Not new single player Add On levels
										GetGameDifficulty(),  // Difficulty level
										false,				  // Rejunenate (MP only)
										0,					  // Time limit (MP only)
										0,					  // Kill limit (MP only)
										0,					  // Cooperative (MP only)
										0,					  // Use cooperative mode (MP only)
										0,					  // Frame time (MP only)
										pfileDemoDebugMovie); // Demo mode file
								}
								else
								{
									TRACE("GameCore(): Couldn't load demo data!\n");									
								}
							}
							else
							{
								TRACE("GameCore(): Couldn't load realm name!\n");								
							}
							fileDemo.Close();
						}
						else
						{
							TRACE("GameCore(): Couldn't open demo file: '%s'\n", m_szDemoFile);							
						}
					}
					else
					{
						TRACE("GameCore(): No demo filename.\n");
					}

					// Reset demo file name for next time.
					m_szDemoFile[0] = 0;
					InputDemoKill();
				}

#if 0
					if (pfileDemoDebugMovie)
						{
						// File may have been closed by Play() if an error occurred
						if (pfileDemoDebugMovie->IsOpen())
							pfileDemoDebugMovie->Close();
						delete pfileDemoDebugMovie;
						pfileDemoDebugMovie = 0;
						}
#else
				CloseSynchLogs();
#endif

#ifdef MOBILE
				AndroidSetScreenMode(TOUCH_SCREEN_MENU);
#endif
				// Restore settings to what they were before demo mode
				CSettings::PostDemo();
			}
			break;
		
			//------------------------------------------------------------------------------
			// Postal organ.
			//------------------------------------------------------------------------------
			case ACTION_POSTAL_ORGAN:
				// Launch Postal Organ.
				PlayWithMyOrgan();
				break;

// todo?
			//------------------------------------------------------------------------------
			// Load previously saved user game.
			//------------------------------------------------------------------------------
			case ACTION_LOAD_GAME:
			{
				// This function will open the saved game file and set the correct game mode
					// and settings.  Note that this modifies the m_action (that's how we get
					// out this state...this confused me for a while but it seems like a good
					// way to choose the appropriate original action).
				Game_LoadPlayersGame(MAPLE::cMapleVmu::m_pActiveVmu, g_sSaveGameSlot, &ms_sLoadedDifficulty, &actionNext);				
				m_bJustOneRealm = false;

				break;
			}
			break;

			case ACTION_LOAD_COOP_GAME:
			{				
				Game_LoadPlayersGame(MAPLE::cMapleVmu::m_pActiveVmu, g_sSaveGameSlot, &ms_sLoadedDifficulty, &actionNext);				
				m_bJustOneRealm = false;
				break;
			}
			break;

			//------------------------------------------------------------------------------
			// Oooops
			//------------------------------------------------------------------------------
			default:
				TRACE("GameCore(): Unrecognized action: %ld!\n", (int32_t)m_action);
				break;
			}

			// If this action lead to another action . . .
			if (actionNext != ACTION_NOTHING)
			{
				// Set current action to the next action
				m_action = actionNext;
			}
			else
			{
				// Reset action
				m_action = ACTION_NOTHING;
			}

			// If menu was taken away, restore the screen behind it
			if (!bMenuActive)
			{
				// If title requested . . .
				if (bTitleImage)
				{
					// Only use musak, if specified.
					StartTitle(0, bTitleMusak, &ms_siMusak);
					EndTitle();
				}
			}

			// Reset demo timer
			ResetDemoTimer();
		}
	}

	// Set the registry value before exiting
	GameSetRegistry();

	// If the menu is active, end it
	if (bMenuActive)
	{
		StopMenu();
		PalTranOff();
		bMenuActive = false;
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
//
// Reset temo timer
//
////////////////////////////////////////////////////////////////////////////////
static void ResetDemoTimer(void)
{
	// Reset base time
	m_lDemoBaseTime = rspGetMilliseconds();

	// Change timeout to use persistant timeout
	m_lDemoTimeOut = g_GameSettings.m_lPersistentDemoTimeOut;
}

////////////////////////////////////////////////////////////////////////////////
//
// Get realm to be recorded
//
////////////////////////////////////////////////////////////////////////////////
static int16_t GetRealmToRecord( // Returns 0 on success, negative on error, 1 if
								 // not subpathable (i.e., returned path is full path).
	char *pszRealmFile,
	int16_t sMaxFileLen)
{
	int16_t sResult = 0;

	// Static so dialog will "remember" the previously-used name
	static char szFile[RSP_MAX_PATH] = "";

	// If not yet used, start out in appropriate directory
	//	if (szFile[0] == '\0')
	strcpy(szFile, FullPathCD(LEVEL_DIR));

	// Display open dialog to let user choose a file
	sResult = SubPathOpenBox(FullPathCD(""), "Choose Realm To Record", szFile, szFile, sizeof(szFile), "rlm");
	if (sResult >= 0)
	{
		// Convert path to RSPiX path.
		char *pszFullPath = rspPathFromSystem(szFile);
		// Check if result will fit into specified buffer
		if (strlen(pszFullPath) < sMaxFileLen)
		{
			strcpy(pszRealmFile, pszFullPath);
		}
		else
		{
			sResult = -1;
			TRACE("GetRealmToRecord(): File name too long to return in specified buffer!\n");
		}
	}
	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
//
// Get a subpath relative to the specified game path.
//
////////////////////////////////////////////////////////////////////////////////
extern int16_t SubPathOpenBox(	// Returns 0 on success, negative on error, 1 if
								// not subpathable (i.e., returned path is full path).
	char *pszFullPath,			// In:  Full path to be relative to (system format).
	char *pszBoxTitle,			// In:  Title of box.
	char *pszDefFileName,		// In:  Default filename (system format).
	char *pszChosenFileName,	// Out: User's choice (system format).
	int16_t sStrSize,			// In:  Amount of memory pointed to by pszChosenFileName.
	char *pszFilter /*= NULL*/) // In:  If not NULL, '.' delimited extension based filename
								//	filter specification.  Ex: ".cpp.h.exe.lib" or "cpp.h.exe.lib"
								// Note: Cannot use '.' in filter.  Preceding '.' ignored.
{
	int16_t sResult;

	char szBasePath[RSP_MAX_PATH];
	int32_t lBasePathLen = strlen(pszFullPath);
	if (lBasePathLen < sizeof(szBasePath))
	{
		strcpy(szBasePath, pszFullPath);

		// Get index to last character
		int16_t sLastIndex = lBasePathLen;
		if (sLastIndex > 0)
			sLastIndex--;

#ifdef WIN32
		// If base path doesn't end with a slash, add one
		if (szBasePath[sLastIndex] != RSP_SYSTEM_PATH_SEPARATOR)
		{
			if ((sLastIndex + 2) < RSP_MAX_PATH)
			{
				szBasePath[sLastIndex + 1] = RSP_SYSTEM_PATH_SEPARATOR;
				szBasePath[sLastIndex + 2] = 0;
			}
			else
			{
				sResult = -1;
				TRACE("SubPathOpenBox(): Path would've exceed max length with separator tacked on!\n");
			}
		}
#else
		// If base path ends with a colon, get rid of it
		if (szBasePath[sLastIndex] == RSP_SYSTEM_PATH_SEPARATOR)
			szBasePath[sLastIndex] = 0;
#endif

		char szChosenFileName[RSP_MAX_PATH];

		// Display open dialog to let user choose a file
		//sResult = rspOpenBox(pszBoxTitle, pszDefFileName, szChosenFileName, sizeof(szChosenFileName), pszFilter);
		if (sResult == 0)
		{
			// Attempt to remove path from the specified name
			int32_t lFullPathLen = strlen(szBasePath);
			if (rspStrnicmp(szChosenFileName, szBasePath, lFullPathLen) == 0)
			{
				// Copy sub path to destination.
				strcpy(pszChosenFileName, szChosenFileName + lFullPathLen);
			}
			else
			{
				// Not subpathable.
				sResult = 1;
				// Return fullpath.
				// Copy full path to destination.
				strcpy(pszChosenFileName, szChosenFileName);
			}
		}
	}
	else
	{
		sResult = -2;
		TRACE("SubPathOpenBox(): pszFullPath string too long.\n");
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
//
// Get name of demo file
//
////////////////////////////////////////////////////////////////////////////////
static int16_t GetDemoFile(
	char *pszDemoFile,
	int16_t sMaxFileLen)
{
	int16_t sResult = 0;

	// Static so dialog will "remember" the previously-used name
	static char szFile[RSP_MAX_PATH] = "";

	// If not yet used, start out in appropriate directory
	if (szFile[0] == '\0')
		strcpy(szFile, FullPathVD(DEMO_DIR));

	// Display save dialog to let user choose a file
	//TODO?
	sResult = 0; // rspSaveBox(g_pszSaveDemoTitle, szFile, szFile, sizeof(szFile), DEMO_EXT);
	if (sResult == 0)
	{
		// Check if result will fit into specified buffer
		if (strlen(szFile) < sMaxFileLen)
		{
			strcpy(pszDemoFile, szFile);
		}
		else
		{
			sResult = -1;
			TRACE("GetDemoFile(): File name too long to return in specified buffer!\n");
		}
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
//
// Open SAKs or set equivalent base paths.
//
////////////////////////////////////////////////////////////////////////////////
static int16_t OpenSaks(void)
{
	int16_t sResult = 0; // Assume success.

	// Set base paths.
	g_resmgrShell.SetBasePath(g_GameSettings.m_szNoSakDir);
	g_resmgrGame.SetBasePath(g_GameSettings.m_szNoSakDir);
	g_resmgrRes.SetBasePath(FullPath(GAME_PATH_CD, ""));

	// Attempt to load the Game SAK . . .
	if (g_resmgrGame.OpenSak(FullPath(GAME_PATH_GAME, GAME_SAK_FILENAME)) == 0)
	{
	}

	// Attempt to load the Shell SAK . . .
	if ((g_resmgrShell.OpenSak(FullPath(GAME_PATH_HD, SHELL_SAK_FILENAME)) == 0) ||
		(g_resmgrShell.OpenSak(FullPath(GAME_PATH_VD, SHELL_SAK_FILENAME)) == 0))
	{
	}

	////////////////////////////////////////////////////////////////////////////
	// The Samples res directory and SAK filename are based on the current
	// audio mode.
	////////////////////////////////////////////////////////////////////////////

	// Get the current audio mode, if any.
	/*int16_t sInSoundMode;
	int32_t lSamplesPerSec;
	int32_t lDevBitsPerSample;
	int32_t lSrcBitsPerSample;
	int32_t lMixBitsPerSample;	
	lSamplesPerSec = 22050;
	lSrcBitsPerSample = 16;*/
		
	char *sakName;
	if (false && g_GameSettings.m_sAudioLanguage == JAPANESE_AUDIO)
	{
		sakName = "/cd/res/sound/shell-j.sak";
	}
	else
	{
		sakName = "/cd/res/sound/shell.sak";
	}

	// Attempt to load the Sample SAK . . .
	if (g_resmgrSamples.OpenSak(sakName) == 0)
	{
		// todo??
		// Cache shell sounds
		CacheSample(g_smidGeneralBeep);
		CacheSample(g_smidMenuItemSelect);
		CacheSample(g_smidMusicTester);
		CacheSample(g_smidAmbientTester);
		CacheSample(g_smidTitleMusak);
		CacheSample(g_smidEndingAudio);
		CacheSample(g_smidCreditsMusak);
		CacheSample(g_smidShotgun);
		CacheSample(g_smidRocketExplode);
		CacheSample(g_smidLoadedWeapon);
		CacheSample(g_smidEmptyWeapon);
		CacheSample(g_smidSteveScumbag);
		CacheSample(g_smidMikeAhh);
		CacheSample(g_smidWrithing2);
		CacheSample(g_smidDemonBleed);
		g_resmgrSamples.SetBasePath(sakName);
	}

	// Set the appropriate quality.		
	g_GameSettings.m_eCurSoundQuality = (SampleMaster::SoundQuality)(1);

	// Set volumes based on quality's category adjustor.
	int16_t i;
	for (i = 0; i < SampleMaster::MAX_NUM_SOUND_CATEGORIES; i++)
	{
		SetCategoryVolume((SampleMaster::SoundCategory)i, g_GameSettings.m_asCategoryVolumes[i]);
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
//
// Close SAKs and/or Purge() them.
//
////////////////////////////////////////////////////////////////////////////////
static void CloseSaks(void)
{
	g_resmgrShell.Purge();
	g_resmgrShell.CloseSak();

	g_resmgrGame.Purge();
	g_resmgrGame.CloseSak();

	g_resmgrSamples.Purge();
	g_resmgrSamples.CloseSak();

	g_resmgrRes.Purge();
	g_resmgrRes.CloseSak();
}

////////////////////////////////////////////////////////////////////////////////
//
// Load game data that we want around the entire game.
//
// I'm not entirely against just keeping all the game data global since that's
// the way it's always been done.  On the other hand, I kind of like the idea
// of using a similar mechanism for loading game data as I did for doing the
// game settings.  It allows various modules to automatically be included in
// the loading process, and keeps the data local to the module.  Back on the
// first hand, we'll probably be grouping data into large, wad-type files, so
// somehow it seems less compelling to try to keep the data separated within
// the app.
//
////////////////////////////////////////////////////////////////////////////////
static int16_t LoadAssets(void)
{

	int16_t i;
	int32_t lTotalTime = 0;

	for (i = 0; i < TitleGetNumTitles(); i++)
		lTotalTime += g_GameSettings.m_alTitleDurations[i];

	// Fake lots of loading with a simple timing loop
	int32_t lTime;
	int32_t lLastTime = rspGetMilliseconds();
	int32_t lEndTime = lLastTime + lTotalTime;
	int loop = 0;

	do
	{
		lTime = rspGetMilliseconds();

		UpdateSystem();

		DoTitle(lTime - lLastTime);

		lLastTime = lTime;

	} while (lTime < lEndTime && rspGetQuitStatus() == FALSE);

	// If we get this far, return success
	return 0;
}

////////////////////////////////////////////////////////////////////////////////
//
// Unload game data that was loaded by GameLoadAssets().
//
////////////////////////////////////////////////////////////////////////////////
static int16_t UnloadAssets(void)
{
	return 0;
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Start Single Player Game" menu
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_StartSinglePlayerGame(
	int16_t sMenuItem)
{		
	playthroughMS = 0;

// If its a spawn version, then don't allow them to play single player
// games, and to make it harder, we will take out some of the code for
// single player games so its harder to hack back in.
#ifndef SPAWN

	switch (sMenuItem)
	{
	// "ORIGINAL LEVELS"
	case 0:
		m_action = ACTION_PLAY_SINGLE;
		m_sRealmNum = 0;
		m_szRealmFile[0] = 0;
		m_bJustOneRealm = false;
		break;
	// "ADD-ON LEVELS"
	case 1:
		m_action = ACTION_PLAY_ADDON;
		m_sRealmNum = 0;
		m_szRealmFile[0] = 0;
		m_bJustOneRealm = false;
		break;
	case 2:
		m_action = ACTION_PLAY_ADDON2;
		m_sRealmNum = 0;
		m_szRealmFile[0] = 0;
		m_bJustOneRealm = false;
		break;
	case 3:
		m_action = ACTION_PLAY_ALL;
		m_sRealmNum = 0;
		m_szRealmFile[0] = 0;
		m_bJustOneRealm = false;
		break;
	case 4:
		m_action = ACTION_LOAD_GAME;
		break;
	case 5:
		m_action = ACTION_LOAD_COOP_GAME;
		break;
	case 7:
		Game_StartChallengeGame(0);
		break;
	}

	// The main game loop resets the demo timer whenever it notices any user input.
	// However, when the user is in a dialog or message box, the OS handles all the
	// user input, and the main game loop won't know anything about what's going on
	// in there.  If the user spends a long time in there, the demo timer will
	// expire.  We don't want that to happen, so we manually reset the demo timer
	// here in recognition of the fact that some kind of user input obviously occurred.
	ResetDemoTimer();
#endif // SPAWN
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the Level Select
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_StartLevelOnce(
	int16_t sMenuItem)
{	
	playthroughMS = 0;

// If its a spawn version, then don't allow them to play single player
// games, and to make it harder, we will take out some of the code for
// single player games so its harder to hack back in.
#ifndef SPAWN

	m_action = ACTION_PLAY_ALL;
	m_sRealmNum = sMenuItem;
	m_szRealmFile[0] = 0;
	m_bJustOneRealm = true;

	// The main game loop resets the demo timer whenever it notices any user input.
	// However, when the user is in a dialog or message box, the OS handles all the
	// user input, and the main game loop won't know anything about what's going on
	// in there.  If the user spends a long time in there, the demo timer will
	// expire.  We don't want that to happen, so we manually reset the demo timer
	// here in recognition of the fact that some kind of user input obviously occurred.
	ResetDemoTimer();
#endif // SPAWN
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Start MultiPlayer Game" menu
//
////////////////////////////////////////////////////////////////////////////////
extern bool Game_StartMultiPlayerGame(
	int16_t sMenuItem)
{
	bool bAccept = true;

	// The main game loop resets the demo timer whenever it notices any user input.
	// However, when the user is in a dialog or message box, the OS handles all the
	// user input, and the main game loop won't know anything about what's going on
	// in there.  If the user spends a long time in there, the demo timer will
	// expire.  We don't want that to happen, so we manually reset the demo timer
	// here in recognition of the fact that some kind of user input obviously occurred.
	ResetDemoTimer();

	return bAccept;
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Join MultiPlayer Game" menu
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_JoinMultiPlayerGame(
	int16_t sMenuItem)
{
	switch (sMenuItem)
	{
	// "BROWSE"
	case 0:
		m_action = ACTION_PLAY_BROWSE;
		break;

	// "CONNECT"
	case 1:
		m_action = ACTION_PLAY_CONNECT;
		break;
	}

	// The main game loop resets the demo timer whenever it notices any user input.
	// However, when the user is in a dialog or message box, the OS handles all the
	// user input, and the main game loop won't know anything about what's going on
	// in there.  If the user spends a long time in there, the demo timer will
	// expire.  We don't want that to happen, so we manually reset the demo timer
	// here in recognition of the fact that some kind of user input obviously occurred.
	ResetDemoTimer();
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Host MultiPlayer Game" menu
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_HostMultiPlayerGame(
	int16_t sMenuItem)
{
	switch (sMenuItem)
	{
	// "HOST"
	case 0:
		m_action = ACTION_PLAY_HOST;
		break;
	}

	// The main game loop resets the demo timer whenever it notices any user input.
	// However, when the user is in a dialog or message box, the OS handles all the
	// user input, and the main game loop won't know anything about what's going on
	// in there.  If the user spends a long time in there, the demo timer will
	// expire.  We don't want that to happen, so we manually reset the demo timer
	// here in recognition of the fact that some kind of user input obviously occurred.
	ResetDemoTimer();
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Start Demo Game" menu
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_StartDemoGame(
	int16_t sMenuItem)
{
	char *pszDemoFile = NULL;
	char szLevelDir[RSP_MAX_PATH] = "";
	char szTitle[256] = "";
	TRACE("sMenuItem = %d\n", sMenuItem);
	switch (sMenuItem)
	{
#if TARGET == POSTAL_2015
	case 0:
		sprintf(m_szDemoFile, "%s/default0.dmo", FullPathCD(DEMO_LEVEL_DIR));
		m_action = ACTION_DEMO_PLAYBACK;
		break;
	case 1:
		sprintf(m_szDemoFile, "%s/default1.dmo", FullPathCD(DEMO_LEVEL_DIR));
		m_action = ACTION_DEMO_PLAYBACK;
		break;
	case 2:
		sprintf(m_szDemoFile, "%s/default2.dmo", FullPathCD(DEMO_LEVEL_DIR));
		m_action = ACTION_DEMO_PLAYBACK;
		break;
#else
	// Browse for and Playback demo
	case 0:
	{
		// Get the filename of the demo to load.
		static char szFile[RSP_MAX_PATH] = "";
		sprintf(szLevelDir, "%s", DEMO_LEVEL_DIR);
		pszDemoFile = szFile;
		// If not yet used, start out in appropriate directory
		if (pszDemoFile[0] == '\0')
			strcpy(pszDemoFile, FullPathCD(szLevelDir));

		// Display open dialog to let user choose a realm file
		sprintf(szTitle, "%s", DEMO_OPEN_TITLE);
		/*if (rspOpenBox(szTitle, pszDemoFile, m_szDemoFile, sizeof(m_szDemoFile), ".dmo") == 0)
			{
				m_action = ACTION_DEMO_PLAYBACK;
			}*/
	}
	break;

	// Play auto demo.
	case 1:
		// Clear demo filename.  This signifies that we should play one of
		// the auto demos.
		m_szDemoFile[0] = '\0';
		m_action = ACTION_DEMO_PLAYBACK;
		break;

	// Record demo
	case 2:
		m_action = ACTION_DEMO_RECORD;
#endif
	}

	// The main game loop resets the demo timer whenever it notices any user input.
	// However, when the user is in a dialog or message box, the OS handles all the
	// user input, and the main game loop won't know anything about what's going on
	// in there.  If the user spends a long time in there, the demo timer will
	// expire.  We don't want that to happen, so we manually reset the demo timer
	// here in recognition of the fact that some kind of user input obviously occurred.
	ResetDemoTimer();
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Buy" option on the Main Menu
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_Buy(void)
{
	ResetDemoTimer();
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Editor" option on the Main Menu
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_StartEditor(void)
{

}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Controls" menu.
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_ControlsMenu(
	int16_t sMenuItem)
{
	// Only do this if we're not currently in an action . . .
	if (m_action == ACTION_NOTHING)
	{
		switch (sMenuItem)
		{
		// Edit keyboard settings.
		case 0:
			m_action = ACTION_EDIT_INPUT_SETTINGS;
			break;

		// Edit mouse settings.
		case 1:
			m_action = ACTION_EDIT_INPUT_SETTINGS;
			break;

		// Edit joystick settings.
		case 2:
#if defined(ALLOW_JOYSTICK)
			m_action = ACTION_EDIT_INPUT_SETTINGS;
#endif // defined(ALLOW_JOYSTICK)
			break;
		}

		// The main game loop resets the demo timer whenever it notices any user input.
		// However, when the user is in a dialog or message box, the OS handles all the
		// user input, and the main game loop won't know anything about what's going on
		// in there.  If the user spends a long time in there, the demo timer will
		// expire.  We don't want that to happen, so we manually reset the demo timer
		// here in recognition of the fact that some kind of user input obviously occurred.
		ResetDemoTimer();
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for "Audio Options" menu.
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_AudioOptionsChoice( // Returns nothing.
	int16_t sMenuItem)				 // In:  Chosen item.
{	
	switch (sMenuItem)
	{
	/*case 1:
		m_action = ACTION_POSTAL_ORGAN;
		break;*/
	case 1:
		// Reload the audio SAK for the correct language.
		// I was expecting to have to find a better place to put
		// this, so that it doesn't reload every single time the
		// option is changed, but it actually seems to be very quick
		// and even works mid-game... Sort of. It's still a bad idea
		// to do that, so I've greyed out the option on the pause
		// screen.
		//	g_resmgrSamples.Purge();
		//	g_resmgrSamples.CloseSak();
		//	OpenSaks();
		// todo: switch language
		break;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the "Start Challenge" menu.
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_StartChallengeGame( // Returns nothing.
	int16_t sChallengeNum)			 //In: Path to realm to play.
{
	char *pszRealmFile = NULL;
	char szLevelDir[RSP_MAX_PATH] = "";
	char szTitle[256] = "";

	if (sChallengeNum == 0)
	{
		// Run the Gauntlet.
		m_action = ACTION_PLAY_CHALLENGE;
		m_sRealmNum = 0;
		m_szRealmFile[0] = 0;
		m_bJustOneRealm = false;
	}
	else
	{
		// Convert path from system format to rspix format so it matches the
		// way we normally call Play(), which is with a rspix path.
		// MASSIVE BUG IN rspPathFromSystem() on the Mac -- it doesn't allow
		// the src and dst to be the same, even though the doc says it can!!!!
		// Workaround is to use temporary buffer.
		/*strcpy(m_szRealmFile, sChallengePath);
		char szTmp[RSP_MAX_PATH];
		strcpy(szTmp, m_szRealmFile);
		rspPathFromSystem(szTmp, m_szRealmFile);*/

		//Use RealNum to load the right Realm. So we get all the information (i.e. Background-Image and Text).
		m_action = ACTION_PLAY_CHALLENGE;
		m_sRealmNum = (sChallengeNum - 1); //Values in INI are 1-23, but RealmNum is zero-based
		m_bJustOneRealm = true;
	}

	// The main game loop resets the demo timer whenever it notices any user input.
	// However, when the user is in a dialog or message box, the OS handles all the
	// user input, and the main game loop won't know anything about what's going on
	// in there.  If the user spends a long time in there, the demo timer will
	// expire.  We don't want that to happen, so we manually reset the demo timer
	// here in recognition of the fact that some kind of user input obviously occurred.
	ResetDemoTimer();
}

////////////////////////////////////////////////////////////////////////////////
//
// Callback for the Main Menu init/kill.
//
////////////////////////////////////////////////////////////////////////////////
extern void Game_InitMainMenu( // Returns nothing.
	int16_t sInit)			   // In:  TRUE, if initializing; FALSE, if killing.
{
	// If initializing the menu . . .
	if (sInit)
	{
	}
	// Otherwise, killing the menu.
	else
	{
		// Abort the title/main-menu musak.
		AbortSample(ms_siMusak);
		// No need to reset ms_siMusak.
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Game_SavePlayersGame - Save the realm, game type, difficulty, and stockpile
//
////////////////////////////////////////////////////////////////////////////////
extern int16_t Game_SavePlayersGame(
				MAPLE::cMapleVmu* pMemoryCard,	// Returns SUCCESS if all goes well
				int8_t cSlotNumber,
				char* pszSaveName,		// In:  Name of the save file
				int8_t sDifficulty,
				int16_t sSaveFlags)
{
	int8_t offset = (g_sActivePlayers > PLAYER_1) ? 1 : 0;
	int8_t numPlayers = 0;
	
	for (int8_t index = 0; index < MAX_DUDES; index++)
	{
		if (g_sActivePlayers & g_sPlayerFlags[index])
		{
			numPlayers++;
		}
	}

	// Game
	if (sSaveFlags & FLAG_SAVE_ALL)
	{
		g_SaveGameState[cSlotNumber].sDifficulty[offset] = sDifficulty;
		g_SaveGameState[cSlotNumber].sAction[offset] = int16_t(m_action);
		g_SaveGameState[cSlotNumber].sActiveCoopPlayers = g_sActivePlayers;
		g_SaveGameState[cSlotNumber].sRealmNum[offset] = g_sRealmNumToSave;
		g_SaveGameState[cSlotNumber].ulUnlockedLevels = g_GameSettings.m_ulUnlockedLevels;		

		for (int8_t playerId = 0; playerId < numPlayers; playerId++)
		{
			g_SaveGameState[cSlotNumber].sStockPileHitPoints[playerId + offset] 		= g_stockpile[playerId].m_sHitPoints;
			g_SaveGameState[cSlotNumber].sStockPileNumGrenades[playerId + offset] 		= g_stockpile[playerId].m_sNumGrenades;
			g_SaveGameState[cSlotNumber].sStockPileNumFireBombs[playerId + offset] 		= g_stockpile[playerId].m_sNumFireBombs;
			g_SaveGameState[cSlotNumber].sStockPileNumMissiles[playerId + offset] 		= g_stockpile[playerId].m_sNumMissiles;
			g_SaveGameState[cSlotNumber].sStockPileNumNapalms[playerId + offset] 		= g_stockpile[playerId].m_sNumNapalms;
			g_SaveGameState[cSlotNumber].sStockPileNumBullets[playerId + offset] 		= g_stockpile[playerId].m_sNumBullets;
			g_SaveGameState[cSlotNumber].sStockPileNumShells[playerId + offset] 		= g_stockpile[playerId].m_sNumShells;
			g_SaveGameState[cSlotNumber].sStockPileNumFuel[playerId + offset] 			= g_stockpile[playerId].m_sNumFuel;
			g_SaveGameState[cSlotNumber].sStockPileNumMines[playerId + offset] 			= g_stockpile[playerId].m_sNumMines;
			g_SaveGameState[cSlotNumber].sStockPileNumHeatseekers[playerId + offset]	= g_stockpile[playerId].m_sNumHeatseekers;		
			g_SaveGameState[cSlotNumber].sStockPileMachineGun[playerId + offset] 		= g_stockpile[playerId].m_sMachineGun;
			g_SaveGameState[cSlotNumber].sStockPileMissileLauncher[playerId + offset] 	= g_stockpile[playerId].m_sMissileLauncher;
			g_SaveGameState[cSlotNumber].sStockPileShotGun[playerId + offset] 			= g_stockpile[playerId].m_sShotGun;
			g_SaveGameState[cSlotNumber].sStockPileSprayCannon[playerId + offset] 		= g_stockpile[playerId].m_sSprayCannon;
			g_SaveGameState[cSlotNumber].sStockPileFlameThrower[playerId + offset] 		= g_stockpile[playerId].m_sFlameThrower;
			g_SaveGameState[cSlotNumber].sStockPileNapalmLauncher[playerId + offset] 	= g_stockpile[playerId].m_sNapalmLauncher;
			g_SaveGameState[cSlotNumber].sStockPileDeathWadLauncher[playerId + offset] 	= g_stockpile[playerId].m_sDeathWadLauncher;
			g_SaveGameState[cSlotNumber].sStockPileDoubleBarrel[playerId + offset] 		= g_stockpile[playerId].m_sDoubleBarrel;
			g_SaveGameState[cSlotNumber].sStockPileKevlarLayers[playerId + offset] 		= g_stockpile[playerId].m_sKevlarLayers;
			g_SaveGameState[cSlotNumber].sStockPileBackpack[playerId + offset] 			= g_stockpile[playerId].m_sBackpack;
		}		
	}
	
	if (sSaveFlags & FLAG_SAVE_ALL || sSaveFlags & FLAG_SAVE_UNLOCKED_LEVELS)
	{
		g_SaveGameState[cSlotNumber].ulUnlockedLevels = g_GameSettings.m_ulUnlockedLevels;
	}

	// Prefs
	if (sSaveFlags & FLAG_SAVE_ALL || sSaveFlags & FLAG_SAVE_PREFS)
	{
		g_SaveGameState[cSlotNumber].cHudMode = g_GameSettings.m_cHudMode;
		g_SaveGameState[cSlotNumber].cRumbleStrength = g_GameSettings.m_cRumbleStrength;
		g_SaveGameState[cSlotNumber].sPlayerColorIndex = g_GameSettings.m_sPlayerColorIndex;			
		g_SaveGameState[cSlotNumber].sAudioLanguage = g_GameSettings.m_sAudioLanguage;
		g_SaveGameState[cSlotNumber].sPainFrequency = g_GameSettings.m_sPainFrequency;
		g_SaveGameState[cSlotNumber].sCategoryVolume[0] = g_GameSettings.m_asCategoryVolumes[0];
		g_SaveGameState[cSlotNumber].sCategoryVolume[1] = g_GameSettings.m_asCategoryVolumes[1];
		g_SaveGameState[cSlotNumber].sCategoryVolume[2] = g_GameSettings.m_asCategoryVolumes[2];
		g_SaveGameState[cSlotNumber].sCategoryVolume[3] = g_GameSettings.m_asCategoryVolumes[3];
		g_SaveGameState[cSlotNumber].sCategoryVolume[4] = g_GameSettings.m_asCategoryVolumes[4];
		g_SaveGameState[cSlotNumber].sCategoryVolume[5] = g_GameSettings.m_asCategoryVolumes[5];
		g_SaveGameState[cSlotNumber].sCategoryVolume[6] = g_GameSettings.m_asCategoryVolumes[6];
		g_SaveGameState[cSlotNumber].sCategoryVolume[7] = g_GameSettings.m_asCategoryVolumes[7];
		g_SaveGameState[cSlotNumber].sCategoryVolume[8] = g_GameSettings.m_asCategoryVolumes[8];
		g_SaveGameState[cSlotNumber].sCategoryVolume[9] = g_GameSettings.m_asCategoryVolumes[9];
		g_SaveGameState[cSlotNumber].sControllerSet[0] = g_GameSettings.m_sControllerSet[0];
		g_SaveGameState[cSlotNumber].sControllerSet[1] = g_GameSettings.m_sControllerSet[1];
		g_SaveGameState[cSlotNumber].sControllerSet[2] = g_GameSettings.m_sControllerSet[2];
		g_SaveGameState[cSlotNumber].sControllerSet[3] = g_GameSettings.m_sControllerSet[3];
	}
		
	// todo?
	bool bWriteError = (MAPLE::cMapleVmu::m_pActiveVmu->WriteData(MAPLE::cMapleVmu::m_pszSaveAppName, (uint8_t*)(&g_SaveGameState), int16_t(sizeof(GameSave) * MAX_SAVE_SLOTS)) == false);
	return 0;
}


extern int16_t Game_SavePlayersPrefs(
				MAPLE::cMapleVmu* pMemoryCard,	// Returns SUCCESS if all goes well
				int8_t cSlotNumber,
				char* pszSaveName		// In:  Name of the save file
			)
{	
	g_SaveGameState[cSlotNumber].cHudMode = g_GameSettings.m_cHudMode;
	g_SaveGameState[cSlotNumber].cRumbleStrength = g_GameSettings.m_cRumbleStrength;
	g_SaveGameState[cSlotNumber].sPlayerColorIndex = g_GameSettings.m_sPlayerColorIndex;		
	g_SaveGameState[cSlotNumber].sAudioLanguage = g_GameSettings.m_sAudioLanguage;
	g_SaveGameState[cSlotNumber].sPainFrequency = g_GameSettings.m_sPainFrequency;
	g_SaveGameState[cSlotNumber].sCategoryVolume[0] = g_GameSettings.m_asCategoryVolumes[0];
	g_SaveGameState[cSlotNumber].sCategoryVolume[1] = g_GameSettings.m_asCategoryVolumes[1];
	g_SaveGameState[cSlotNumber].sCategoryVolume[2] = g_GameSettings.m_asCategoryVolumes[2];
	g_SaveGameState[cSlotNumber].sCategoryVolume[3] = g_GameSettings.m_asCategoryVolumes[3];
	g_SaveGameState[cSlotNumber].sCategoryVolume[4] = g_GameSettings.m_asCategoryVolumes[4];
	g_SaveGameState[cSlotNumber].sCategoryVolume[5] = g_GameSettings.m_asCategoryVolumes[5];
	g_SaveGameState[cSlotNumber].sCategoryVolume[6] = g_GameSettings.m_asCategoryVolumes[6];
	g_SaveGameState[cSlotNumber].sCategoryVolume[7] = g_GameSettings.m_asCategoryVolumes[7];
	g_SaveGameState[cSlotNumber].sCategoryVolume[8] = g_GameSettings.m_asCategoryVolumes[8];
	g_SaveGameState[cSlotNumber].sCategoryVolume[9] = g_GameSettings.m_asCategoryVolumes[9];
	g_SaveGameState[cSlotNumber].sControllerSet[0] = g_GameSettings.m_sControllerSet[0];
	g_SaveGameState[cSlotNumber].sControllerSet[1] = g_GameSettings.m_sControllerSet[1];
	g_SaveGameState[cSlotNumber].sControllerSet[2] = g_GameSettings.m_sControllerSet[2];
	g_SaveGameState[cSlotNumber].sControllerSet[3] = g_GameSettings.m_sControllerSet[3];

	// todo - error?
	bool bWriteError = (MAPLE::cMapleVmu::m_pActiveVmu->WriteData(MAPLE::cMapleVmu::m_pszSaveAppName, (uint8_t*)(&g_SaveGameState), int16_t(sizeof(GameSave) * MAX_SAVE_SLOTS)) == false);
	return 0;
}


extern int16_t Game_SavePlayerHighScores(
				MAPLE::cMapleVmu* pMemoryCard,	// Returns SUCCESS if all goes well
				int8_t cSlotNumber,
				char* pszSaveName		// In:  Name of the save file
			)
{		
	// Challenge high-scores
	for (int16_t sChallenge = 0; sChallenge < 23; sChallenge++)
	{			
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][0] = g_HighScores[sChallenge][0];
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][1] = g_HighScores[sChallenge][1];
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][2] = g_HighScores[sChallenge][2];
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][3] = g_HighScores[sChallenge][3];
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][4] = g_HighScores[sChallenge][4];
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][5] = g_HighScores[sChallenge][5];
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][6] = g_HighScores[sChallenge][6];
		g_SaveGameState[cSlotNumber].lHighScores[sChallenge][7] = g_HighScores[sChallenge][7];
	}	

	// todo - error?
	bool bWriteError = (MAPLE::cMapleVmu::m_pActiveVmu->WriteData(MAPLE::cMapleVmu::m_pszSaveAppName, (uint8_t*)(&g_SaveGameState), int16_t(sizeof(GameSave) * MAX_SAVE_SLOTS)) == false);
	return 0;
}


// todo:
extern int16_t Game_LoadPlayersSaveSlot(int8_t cSlotNumber)
{							
	g_GameSettings.m_cHudMode = g_SaveGameState[cSlotNumber].cHudMode;

	switch (g_GameSettings.m_cHudMode)
	{
		case 0:						
			g_GameSettings.m_sViewH = 400;
			g_GameSettings.m_sFilmY = 40;
			break;
		case 1:
			g_GameSettings.m_sViewH = 480;			
			g_GameSettings.m_sFilmY = 0;
			break;
	}

	g_pvrRenderer->SetClip(0, g_GameSettings.m_sFilmY, 640, g_GameSettings.m_sViewH);

	g_GameSettings.m_cRumbleStrength = g_SaveGameState[cSlotNumber].cRumbleStrength;
	g_GameSettings.m_ulUnlockedLevels = g_SaveGameState[cSlotNumber].ulUnlockedLevels;
	g_GameSettings.m_sPlayerColorIndex = g_SaveGameState[cSlotNumber].sPlayerColorIndex;
	g_GameSettings.m_sAudioLanguage = g_SaveGameState[cSlotNumber].sAudioLanguage;
	g_GameSettings.m_sPainFrequency = g_SaveGameState[cSlotNumber].sPainFrequency;	
	g_GameSettings.m_asCategoryVolumes[0] = g_SaveGameState[cSlotNumber].sCategoryVolume[0];
	g_GameSettings.m_asCategoryVolumes[1] = g_SaveGameState[cSlotNumber].sCategoryVolume[1];
	g_GameSettings.m_asCategoryVolumes[2] = g_SaveGameState[cSlotNumber].sCategoryVolume[2];
	g_GameSettings.m_asCategoryVolumes[3] = g_SaveGameState[cSlotNumber].sCategoryVolume[3];
	g_GameSettings.m_asCategoryVolumes[4] = g_SaveGameState[cSlotNumber].sCategoryVolume[4];
	g_GameSettings.m_asCategoryVolumes[5] = g_SaveGameState[cSlotNumber].sCategoryVolume[5];
	g_GameSettings.m_asCategoryVolumes[6] = g_SaveGameState[cSlotNumber].sCategoryVolume[6];
	g_GameSettings.m_asCategoryVolumes[7] = g_SaveGameState[cSlotNumber].sCategoryVolume[7];
	g_GameSettings.m_asCategoryVolumes[8] = g_SaveGameState[cSlotNumber].sCategoryVolume[8];
	g_GameSettings.m_asCategoryVolumes[9] = g_SaveGameState[cSlotNumber].sCategoryVolume[9];
	for (int8_t index = 0; index < 4; index++)
	{
		MAPLE::cMapleController *pController;
		pController = rspGetJoy(index);
		if (pController->IsExtended())
		{		
			pController->m_cControlSchemeId = CONTROL_SCHEME_TWINSTICK;			
		}
		else
		{			
			pController->m_cControlSchemeId = g_GameSettings.m_sControllerSet[index] = g_SaveGameState[cSlotNumber].sControllerSet[index];			
		}
	}

	// Challenge high-scores
	for (int16_t sChallenge = 0; sChallenge < 23; sChallenge++)
	{			
		g_HighScores[sChallenge][0] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][0];
		g_HighScores[sChallenge][1] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][1];
		g_HighScores[sChallenge][2] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][2];
		g_HighScores[sChallenge][3] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][3];
		g_HighScores[sChallenge][4] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][4];
		g_HighScores[sChallenge][5] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][5];
		g_HighScores[sChallenge][6] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][6];
		g_HighScores[sChallenge][7] = g_SaveGameState[cSlotNumber].lHighScores[sChallenge][7];
	}	
}

////////////////////////////////////////////////////////////////////////////////
//
// Game_LoadPlayersGame - Load a previously saved game, restore the settings
//
////////////////////////////////////////////////////////////////////////////////
// todo??
extern int16_t Game_LoadPlayersGame(
	char *pszSaveName,	   // In:  Name of the saved game file to open
	int8_t *psDifficulty, // Out: Saved game realm difficulty.
	ACTION *paction)	   // Out: Saved game action.
{
	RFile rf;
	int16_t sResult = 0;
	return sResult;
}

// todo
extern int16_t Game_LoadPlayersGame(
	MAPLE::cMapleVmu* pMemoryCard,	// Returns SUCCESS if all goes well
	int8_t cSlotNumber,
	int8_t *psDifficulty, // Out: Saved game realm difficulty.
	ACTION *paction)
{		
	int8_t offset = (g_sActivePlayers > PLAYER_1) ? 1 : 0;
	int8_t numPlayers = 0;

	for (int8_t index = 0; index < MAX_DUDES; index++)
	{
		if (g_sActivePlayers & g_sPlayerFlags[index])
		{
			numPlayers++;
		}
	}

	// Game
	*psDifficulty = g_SaveGameState[cSlotNumber].sDifficulty[offset];
	*paction = (ACTION)g_SaveGameState[cSlotNumber].sAction[offset];
	m_sRealmNum = g_SaveGameState[cSlotNumber].sRealmNum[offset];
	
	// todo: needed here?
	g_GameSettings.m_ulUnlockedLevels = g_SaveGameState[cSlotNumber].ulUnlockedLevels;
	g_GameSettings.m_sPlayerColorIndex = g_SaveGameState[cSlotNumber].sPlayerColorIndex;

	// Stockpile
	for (int8_t playerId = 0; playerId < numPlayers; playerId++)
	{
		if (g_SaveGameState[cSlotNumber].sActiveCoopPlayers & g_sPlayerFlags[playerId])
		{
			g_stockpile[playerId].m_sHitPoints 			= g_SaveGameState[cSlotNumber].sStockPileHitPoints[playerId + offset];
			g_stockpile[playerId].m_sNumGrenades		= g_SaveGameState[cSlotNumber].sStockPileNumGrenades[playerId + offset];
			g_stockpile[playerId].m_sNumFireBombs 		= g_SaveGameState[cSlotNumber].sStockPileNumFireBombs[playerId + offset];
			g_stockpile[playerId].m_sNumMissiles 		= g_SaveGameState[cSlotNumber].sStockPileNumMissiles[playerId + offset];
			g_stockpile[playerId].m_sNumNapalms 		= g_SaveGameState[cSlotNumber].sStockPileNumNapalms[playerId + offset];
			g_stockpile[playerId].m_sNumBullets 		= g_SaveGameState[cSlotNumber].sStockPileNumBullets[playerId + offset];
			g_stockpile[playerId].m_sNumShells 			= g_SaveGameState[cSlotNumber].sStockPileNumShells[playerId + offset];
			g_stockpile[playerId].m_sNumFuel 			= g_SaveGameState[cSlotNumber].sStockPileNumFuel[playerId + offset];
			g_stockpile[playerId].m_sNumMines 			= g_SaveGameState[cSlotNumber].sStockPileNumMines[playerId + offset];
			g_stockpile[playerId].m_sNumHeatseekers 	= g_SaveGameState[cSlotNumber].sStockPileNumHeatseekers[playerId + offset];
			g_stockpile[playerId].m_sMachineGun 		= g_SaveGameState[cSlotNumber].sStockPileMachineGun[playerId + offset];
			g_stockpile[playerId].m_sMissileLauncher 	= g_SaveGameState[cSlotNumber].sStockPileMissileLauncher[playerId + offset];
			g_stockpile[playerId].m_sShotGun 			= g_SaveGameState[cSlotNumber].sStockPileShotGun[playerId + offset];
			g_stockpile[playerId].m_sSprayCannon 		= g_SaveGameState[cSlotNumber].sStockPileSprayCannon[playerId + offset];
			g_stockpile[playerId].m_sFlameThrower 		= g_SaveGameState[cSlotNumber].sStockPileFlameThrower[playerId + offset];
			g_stockpile[playerId].m_sNapalmLauncher 	= g_SaveGameState[cSlotNumber].sStockPileNapalmLauncher[playerId + offset];
			g_stockpile[playerId].m_sDeathWadLauncher 	= g_SaveGameState[cSlotNumber].sStockPileDeathWadLauncher[playerId + offset];
			g_stockpile[playerId].m_sDoubleBarrel 		= g_SaveGameState[cSlotNumber].sStockPileDoubleBarrel[playerId + offset];
			g_stockpile[playerId].m_sKevlarLayers 		= g_SaveGameState[cSlotNumber].sStockPileKevlarLayers[playerId + offset];
			g_stockpile[playerId].m_sBackpack 			= g_SaveGameState[cSlotNumber].sStockPileBackpack[playerId + offset];
		}
	}
									
	g_bTransferStockpile = true;
}

// todo:
extern void Game_InitSaveFile()
{		
	for (int index = 0; index < MAX_SAVE_SLOTS; index++)
	{			
		g_SaveGameState[index].cHudMode 		= rspIsVGA() ? 0 : 1;
		g_SaveGameState[index].cRumbleStrength 	= g_PlaceHolderSave.cRumbleStrength;
		g_SaveGameState[index].sDifficulty[0] 	= g_PlaceHolderSave.sDifficulty[0];
		g_SaveGameState[index].sDifficulty[1] 	= g_PlaceHolderSave.sDifficulty[1];
		g_SaveGameState[index].sAction[0] 		= g_PlaceHolderSave.sAction[0];
		g_SaveGameState[index].sAction[1] 		= g_PlaceHolderSave.sAction[1];
		g_SaveGameState[index].sRealmNum[0] 	= g_PlaceHolderSave.sRealmNum[0];				
		g_SaveGameState[index].sRealmNum[1] 	= g_PlaceHolderSave.sRealmNum[1];

		g_SaveGameState[index].sActiveCoopPlayers = g_PlaceHolderSave.sActiveCoopPlayers;
		g_SaveGameState[index].ulUnlockedLevels	 = g_PlaceHolderSave.ulUnlockedLevels;
		g_SaveGameState[index].sPlayerColorIndex = g_PlaceHolderSave.sPlayerColorIndex;

		// Stockpiles
		for (int inner = 0; inner < 5; inner++)
		{						
			g_SaveGameState[index].sStockPileHitPoints[inner] 			= g_PlaceHolderSave.sStockPileHitPoints[inner];
			g_SaveGameState[index].sStockPileNumGrenades[inner] 		= g_PlaceHolderSave.sStockPileNumGrenades[inner];
			g_SaveGameState[index].sStockPileNumFireBombs[inner] 		=  g_PlaceHolderSave.sStockPileNumFireBombs[inner];
			g_SaveGameState[index].sStockPileNumMissiles[inner] 		= g_PlaceHolderSave.sStockPileNumMissiles[inner];
			g_SaveGameState[index].sStockPileNumNapalms[inner] 			= g_PlaceHolderSave.sStockPileNumNapalms[inner];
			g_SaveGameState[index].sStockPileNumBullets[inner] 			= g_PlaceHolderSave.sStockPileNumBullets[inner];
			g_SaveGameState[index].sStockPileNumShells[inner] 			= g_PlaceHolderSave.sStockPileNumShells[inner];
			g_SaveGameState[index].sStockPileNumFuel[inner] 			= g_PlaceHolderSave.sStockPileNumFuel[inner];
			g_SaveGameState[index].sStockPileNumMines[inner] 			= g_PlaceHolderSave.sStockPileNumMines[inner];
			g_SaveGameState[index].sStockPileNumHeatseekers[inner] 		= g_PlaceHolderSave.sStockPileNumHeatseekers[inner];
			g_SaveGameState[index].sStockPileMachineGun[inner] 			= g_PlaceHolderSave.sStockPileMachineGun[inner];
			g_SaveGameState[index].sStockPileMissileLauncher[inner] 	= g_PlaceHolderSave.sStockPileMissileLauncher[inner];
			g_SaveGameState[index].sStockPileShotGun[inner] 			= g_PlaceHolderSave.sStockPileShotGun[inner];
			g_SaveGameState[index].sStockPileSprayCannon[inner] 		= g_PlaceHolderSave.sStockPileSprayCannon[inner];
			g_SaveGameState[index].sStockPileFlameThrower[inner] 		= g_PlaceHolderSave.sStockPileFlameThrower[inner];
			g_SaveGameState[index].sStockPileNapalmLauncher[inner] 		= g_PlaceHolderSave.sStockPileNapalmLauncher[inner];
			g_SaveGameState[index].sStockPileDeathWadLauncher[inner] 	= g_PlaceHolderSave.sStockPileDeathWadLauncher[inner];
			g_SaveGameState[index].sStockPileDoubleBarrel[inner] 		= g_PlaceHolderSave.sStockPileDoubleBarrel[inner];
			g_SaveGameState[index].sStockPileKevlarLayers[inner] 		= g_PlaceHolderSave.sStockPileKevlarLayers[inner];
			g_SaveGameState[index].sStockPileBackpack[inner] 			= g_PlaceHolderSave.sStockPileBackpack[inner];
		}
				
		g_SaveGameState[index].sPainFrequency 	= g_PlaceHolderSave.sPainFrequency;
		g_SaveGameState[index].sAudioLanguage 	= g_PlaceHolderSave.sAudioLanguage;
		g_SaveGameState[index].sCategoryVolume[0] = g_PlaceHolderSave.sCategoryVolume[0];
		g_SaveGameState[index].sCategoryVolume[1] = g_PlaceHolderSave.sCategoryVolume[1];
		g_SaveGameState[index].sCategoryVolume[2] = g_PlaceHolderSave.sCategoryVolume[2];
		g_SaveGameState[index].sCategoryVolume[3] = g_PlaceHolderSave.sCategoryVolume[3];
		g_SaveGameState[index].sCategoryVolume[4] = g_PlaceHolderSave.sCategoryVolume[4];
		g_SaveGameState[index].sCategoryVolume[5] = g_PlaceHolderSave.sCategoryVolume[5];
		g_SaveGameState[index].sCategoryVolume[6] = g_PlaceHolderSave.sCategoryVolume[6];
		g_SaveGameState[index].sCategoryVolume[7] = g_PlaceHolderSave.sCategoryVolume[7];
		g_SaveGameState[index].sCategoryVolume[8] = g_PlaceHolderSave.sCategoryVolume[8];
		g_SaveGameState[index].sCategoryVolume[9] = g_PlaceHolderSave.sCategoryVolume[9];
			
		g_SaveGameState[index].sControllerSet[0] = g_PlaceHolderSave.sControllerSet[0];	
		g_SaveGameState[index].sControllerSet[1] = g_PlaceHolderSave.sControllerSet[1];
		g_SaveGameState[index].sControllerSet[2] = g_PlaceHolderSave.sControllerSet[2];
		g_SaveGameState[index].sControllerSet[3] = g_PlaceHolderSave.sControllerSet[3];

		// Challenge high-scores
		for (int16_t sChallenge = 0; sChallenge < 23; sChallenge++)
		{			
			g_SaveGameState[index].lHighScores[sChallenge][0] =	g_PlaceHolderSave.lHighScores[sChallenge][0];
			g_SaveGameState[index].lHighScores[sChallenge][1] =	g_PlaceHolderSave.lHighScores[sChallenge][1];
			g_SaveGameState[index].lHighScores[sChallenge][2] =	g_PlaceHolderSave.lHighScores[sChallenge][2];
			g_SaveGameState[index].lHighScores[sChallenge][3] =	g_PlaceHolderSave.lHighScores[sChallenge][3];
			g_SaveGameState[index].lHighScores[sChallenge][4] =	g_PlaceHolderSave.lHighScores[sChallenge][4];
			g_SaveGameState[index].lHighScores[sChallenge][5] =	g_PlaceHolderSave.lHighScores[sChallenge][5];
			g_SaveGameState[index].lHighScores[sChallenge][6] =	g_PlaceHolderSave.lHighScores[sChallenge][6];
			g_SaveGameState[index].lHighScores[sChallenge][7] =	g_PlaceHolderSave.lHighScores[sChallenge][7];		
		}
	}

	// todo
	MAPLE::cMapleVmu::m_pActiveVmu->WriteData(MAPLE::cMapleVmu::m_pszSaveAppName, (uint8_t*)(&g_SaveGameState), int16_t(sizeof(GameSave) * MAX_SAVE_SLOTS));
}


// todo?
extern bool Game_ReadGlobalSaveStates()
{		
	return MAPLE::cMapleVmu::m_pActiveVmu->ReadData(MAPLE::cMapleVmu::m_pszSaveAppName, (uint8_t*)(&g_SaveGameState), int16_t(sizeof(GameSave) * MAX_SAVE_SLOTS));
}


////////////////////////////////////////////////////////////////////////////////
//
// GameEndingSequence - Start a demo of the last level
//
////////////////////////////////////////////////////////////////////////////////

void GameEndingSequence(void)
{
	// Prepare all settings for demo mode
	CSettings::PreDemo();

	g_sActivePlayers = PLAYER_1;
	g_cPlayerCount = 1;

#if VIOLENT_LOCALE

	// If demo debug movie file is specified in prefs, open it now.  The RFile*
	// is does double-duty as a flag, where non-zero means movie mode is enabled.
	RFile *pfileDemoDebugMovie = 0;
	m_pfileRandom = 0;
	if (strlen(g_GameSettings.m_szDemoDebugMovie) > 0)
	{
		m_pfileRandom = new RFile;
		if (m_pfileRandom->Open(g_GameSettings.m_szDemoDebugMovie, "rb", RFile::LittleEndian) != 0)
		{
			delete m_pfileRandom;
			m_pfileRandom = 0;
		}
	}

	if (InputDemoInit() == 0)
	{
		// This is the special end of game demo, so set up the demo name
		sprintf(m_szDemoFile, "%s", FullPathCD(ENDING_DEMO_NAME));

		RFile fileDemo;
		if (fileDemo.Open(m_szDemoFile, "rb", RFile::LittleEndian) == 0)
		{
			// Read name of realm file
			char szRealmFile[RSP_MAX_PATH];
			fileDemo.Read(szRealmFile);
			// Read whether it's a full path.
			int16_t sRealmFileIsFullPath;
			fileDemo.Read(&sRealmFileIsFullPath);
			if (!fileDemo.Error())
			{
				// Load input demo data (must be BEFORE setting playback mode)
				if (InputDemoLoad(&fileDemo) == 0)
				{
					// End menu (now that we know there were no errors)
					//					StopMenu();
					//					PalTranOff();
					//					bMenuActive = false;
					Play(						
						INPUT_MODE_PLAYBACK,  // Input mode
						-1,					  // Always use specific realm file
						szRealmFile,		  // Realm file to be played
						false,				  // Don't play just one realm
						false,				  // Not challenge mode
						0,					  // Not new single player Add On levels
						GetGameDifficulty(),  // Difficulty level
						false,				  // Rejunenate (MP only)
						0,					  // Time limit (MP only)
						0,					  // Kill limit (MP only)
						0,					  // Cooperative (MP only)
						0,					  // Use cooperative mode (MP only)
						0,					  // Frame time (MP only)
						pfileDemoDebugMovie); // Demo mode file						
				}
				else
				{
					TRACE("GameCore(): Couldn't load demo data!\n");					
				}
			}
			else
			{
				TRACE("GameCore(): Couldn't load realm name!\n");
			}
			fileDemo.Close();
		}
		else
		{
			TRACE("GameCore(): Couldn't open demo file: '%s'\n", m_szDemoFile);			
		}
		// Reset demo file name for next time.
		m_szDemoFile[0] = 0;
		InputDemoKill();
	}

	if (m_pfileRandom)
	{
		// File may have been closed by Play() if an error occurred
		if (m_pfileRandom->IsOpen())
			m_pfileRandom->Close();
		delete m_pfileRandom;
		m_pfileRandom = 0;
	}

#endif // VIOLENT_LOCALE

	// Restore settings to what they were before demo mode
	CSettings::PostDemo();

	SetInputMode(INPUT_MODE_LIVE); // take it out of demo mode so achievements can unlock.

	// Do the ending title sequence.
	Title_GameEndSequence();

	// Do special credits:
	Credits(&g_smidFinalSceneCredits);	
		
	g_pvrRenderer->m_fGlobalRScale = 1.0f;
	g_pvrRenderer->m_fGlobalGScale = 0.0f;
	g_pvrRenderer->m_fGlobalBScale = 0.0f;
	// Restore the main menu screen background.
	g_pvrRenderer->DrawQuad(
		g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_SPLASH_THIRD),
		0, 0, 640, 480,
		PVR::cRenderMgr::LayerBg, 1.0f);
}

////////////////////////////////////////////////////////////////////////////////
// Returns a ptr to just the portion of the file path that specifies the file
// name (excluding the path).
////////////////////////////////////////////////////////////////////////////////
static char *GetFileNameFromPath( // Returns file name.
	char *pszFullPath)			  // In:  File's full path.
{
	// Scan back for the separator or the beginning.
	char *pszIndex = pszFullPath + (strlen(pszFullPath) - 1);

	while (pszIndex >= pszFullPath && *pszIndex != RSP_SYSTEM_PATH_SEPARATOR)
	{
		pszIndex--;
	}

	return (pszIndex + 1);
}

////////////////////////////////////////////////////////////////////////////////
// Opens the synchronization log with the specified access flags if in a
// TRACENASSERT mode and synchronization logging is enabled.
// Also, opens the random log, if it is enabled via
// g_GameSettings.m_szDebugMovie or something.
////////////////////////////////////////////////////////////////////////////////
static void OpenSynchLogs( // Returns nothing.
	bool bWriteLogs)	   // In:  true to create log, false to compare.
{
	m_pfileRandom = 0;
	if (strlen(g_GameSettings.m_szDemoDebugMovie) > 0)
	{
		m_pfileRandom = new RFile;
		if (m_pfileRandom->Open(
				g_GameSettings.m_szDemoDebugMovie,
				bWriteLogs ? "wb" : "rb",
				RFile::LittleEndian) != 0)
		{
			delete m_pfileRandom;
			m_pfileRandom = 0;
		}
	}

	m_bWriteLogs = bWriteLogs;
}

////////////////////////////////////////////////////////////////////////////////
// Closes the synchronization logs, if open.
////////////////////////////////////////////////////////////////////////////////
static void CloseSynchLogs(void) // Returns nothing.
{
	if (m_pfileRandom)
	{
		// File may have been closed by Play() if an error occurred
		if (m_pfileRandom->IsOpen())
			m_pfileRandom->Close();
		delete m_pfileRandom;
		m_pfileRandom = 0;
	}

#if defined(_DEBUG) || defined(TRACENASSERT)
	if (ms_fileSynchLog.IsOpen())
	{
		ms_fileSynchLog.Close();
	}
#endif // defined(_DEBUG) || defined(TRACENASSERT)
}

////////////////////////////////////////////////////////////////////////////////
// Synchronization logger -- Call this function to log an expression and a user
// value in the synch log.  When active (if g_GameSettings.m_szSynchLog is
// a valid path and filename), if recording a demo, these calls are logged to
// a file including the calling file and line number.  When active, if playing
// back a demo, these calls are compared to those stored in the log and, if
// a discrepancy occurs, a modal dialog box will pop up with the pertinent info
// followed by an ASSERT(0) for easy debugging.
////////////////////////////////////////////////////////////////////////////////
extern int SynchLog( // Result of expr.
	double expr,	 // In:  Expression to evaluate.
	char *pszFile,	 // In:  Calling file.
	int32_t lLine,	 // In:  Calling line.
	char *pszExpr,	 // In:  Original C++ source expression.
	U32 u32User)	 // In:  A user value that is intended to be consistent.
{
#if defined(_DEBUG) || defined(TRACENASSERT)
	if (ms_fileSynchLog.IsOpen())
	{
		if (m_bWriteLogs)
		{
			fprintf(
				ms_fileSynchLog.m_fs,
				"[Seq: %ld] %s : %ld  <$%s$> == %0.8f; User == %lu\n",
				ms_lSynchLogSeq++,
				GetFileNameFromPath(pszFile),
				lLine,
				pszExpr,
				expr,
				u32User);
		}
		else
		{
			char szFileIn[RSP_MAX_PATH];
			char szExprIn[1024];
			int32_t lLineIn;
			int32_t lSeqIn;
			double exprIn;
			U32 u32UserIn;

			if (fscanf(
					ms_fileSynchLog.m_fs,
					"[Seq: %ld] %s : %ld  <$%1024[^$]$> == %g; User == %lu\n",
					&lSeqIn,
					szFileIn,
					&lLineIn,
					szExprIn,
					&exprIn,
					&u32UserIn) == 6)
			{
				// Verify . . .
				if ((rspStricmp(szFileIn, GetFileNameFromPath(pszFile)) != 0) || (lLineIn != lLine) || (exprIn != expr) || (u32UserIn != u32User))
				{
					char szOut[2048];
					sprintf(
						szOut,
						"'If' sequence (%ld) mismatch!\n\n"
						"   Was <<%s>> at %s(%ld) which got %g; User == %lu\n\n"
						"   Now <<%s>> at %s(%ld) which got %g; User == %lu",
						ms_lSynchLogSeq,
						szExprIn,
						szFileIn,
						lLineIn,
						exprIn,
						u32UserIn,
						pszExpr,
						GetFileNameFromPath(pszFile),
						lLine,
						expr,
						u32User);

					TRACE("%s\n\n", szOut);

					/*	rspMsgBox(
							RSP_MB_ICN_INFO | RSP_MB_BUT_OK,
							"Postal",
							szOut);*/

					// Make this easy to debug
					ASSERT(0);
				}

				ms_lSynchLogSeq++;
			}
			else
			{
				TRACE("Synch(): Error reading log file.\n");
			}
		}
	}
#else
	rspMsgBox(
		RSP_MB_ICN_STOP | RSP_MB_BUT_OK,
		"Postal",
		"Synchronization logging is disabled for release mode for safety.\n");
#endif // defined(_DEBUG) || defined(TRACENASSERT)

	return expr;
}

////////////////////////////////////////////////////////////////////////////////
// GameGetRegistry
////////////////////////////////////////////////////////////////////////////////

static void GameGetRegistry(void)
{
#ifdef CHECK_EXPIRATION_DATE
	char szTime[40];
	char szTimeEncrypt[40];
	time_t lTime;
	DWORD dwTimeLength;
	DWORD dwSize = 255;
	char szData[256];
	char szName[256];
	int16_t sEncryptedKeyLength = 36;

	uint8_t szKey[40];

	// This is the encoded path name of the registry key where the value is stored
	szKey[0] = 0x07;
	szKey[1] = 0x29;
	szKey[2] = 0xbd;
	szKey[3] = 0x3a;
	szKey[4] = 0xba;
	szKey[5] = 0x22;
	szKey[6] = 0xbe;
	szKey[7] = 0x36;
	szKey[8] = 0xf5;
	szKey[9] = 0x22;
	szKey[10] = 0xfb;
	szKey[11] = 0x24;
	szKey[12] = 0xd0;
	szKey[13] = 0x3a;
	szKey[14] = 0xd4;
	szKey[15] = 0x71;
	szKey[16] = 0xcb;
	szKey[17] = 0x3e;
	szKey[18] = 0xd5;
	szKey[19] = 0x2a;
	szKey[20] = 0xec;
	szKey[21] = 0x29;
	szKey[22] = 0x89;
	szKey[23] = 0x30;
	szKey[24] = 0x8f;
	szKey[25] = 0x78;
	szKey[26] = 0x83;
	szKey[27] = 0x66;
	szKey[28] = 0xb4;
	szKey[29] = 0x40;
	szKey[30] = 0x86;
	szKey[31] = 0x66;
	szKey[32] = 0x8f;
	szKey[33] = 0x71;
	szKey[34] = 0x9b;
	szKey[35] = 0xfe;
	szKey[36] = 0x00;

#ifdef WIN32

	DWORD dwDisposition;
	DWORD dwType;
	DWORD dwNameSize = 255;
	HKEY hkResult;
	int32_t lError;
	int16_t sEncryptedValueLength = 9;

	uint8_t szIn[10];

	// This is the encoded name of the registry value itendifier
	szIn[0] = 0x07;
	szIn[1] = 0x29;
	szIn[2] = 0xa8;
	szIn[3] = 0x0f;
	szIn[4] = 0xa7;
	szIn[5] = 0x0c;
	szIn[6] = 0xa8;
	szIn[7] = 0x0e;
	szIn[8] = 0xfd;
	szIn[9] = 0x00;

	// Get the current time and convert it to a string so it can be encoded
	time(&lTime);
	sprintf(szTime, "%ld", lTime);

	// Decrypte the registry key path so the key can be opened
	Decrypt((char *)szKey, szName, sEncryptedKeyLength);
	szName[sEncryptedKeyLength - 2] = 0;
	lError = RegCreateKeyEx(HKEY_LOCAL_MACHINE, szName, 0,
							"", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
							&hkResult, &dwDisposition);
	// Destroy the source and result.
	memset(szName, 0xeb, sEncryptedKeyLength);

	if (lError == ERROR_SUCCESS && lTime > g_lReleaseTime)
	{
		if (dwDisposition == REG_CREATED_NEW_KEY)
		// Write the initial value
		{
			// Make sure current value is after release value
			dwTimeLength = Encrypt(szTime, szTimeEncrypt, strlen(szTime));
			Decrypt((char *)szIn, szName, sEncryptedValueLength);
			szName[sEncryptedValueLength - 2] = 0;
			RegSetValueEx(hkResult, szName, 0, REG_BINARY, (uint8_t *)szTimeEncrypt, dwTimeLength);
			memset(szName, 0xeb, sEncryptedValueLength);
			g_lRegTime = lTime;
		}
		else
		{
			lError = RegEnumValue(hkResult, 0, szName, &dwNameSize, 0, &dwType, (uint8_t *)szData, &dwSize);
			if (lError != ERROR_SUCCESS)
				g_lRegTime = EXPIRATION_DATE;
			else
			{
				if (Decrypt(szData, szTime, dwSize) == 0)
				{
					szTime[dwSize] = 0;
					g_lRegTime = atol(szTime);
				}
				else
				{
					g_lRegTime = EXPIRATION_DATE;
				}
			}
		}
		RegCloseKey(hkResult);
	}
	else
	// If there was an error opening the registry key, then it will trace the error
	// message here.
	{
		LPVOID lpMsgBuf;

		FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
			(LPTSTR)&lpMsgBuf,
			0,
			NULL);

		// Display the string.
		TRACE((char *)lpMsgBuf);

		// Free the buffer.
		LocalFree(lpMsgBuf);

		// This failed either because it couldn't write, or because
		// the date has been set back before the release date, so
		// force this to fail
		g_lRegTime = EXPIRATION_DATE;
	}

#else // WIN32
	// Do mac version here.
	char szPath[512];
	time(&lTime);
	lTime -= ((365 * 70UL) + 17) * 24 * 60 * 60; // time_fudge 1900->1970

	// Open a prefs file and read the value, or write the value
	// if it is the first time.
	RFile rfPref;
	// If the file does not exist, then write the current date to the file
	// as long as it is after the release date.
	macGetSpecialPath(MacPreferencesFolderType, true, szPath, 254);
	Decrypt((char *)szKey, szName, sEncryptedKeyLength);
	sEncryptedKeyLength = MAX(0, sEncryptedKeyLength - 2);
	szName[sEncryptedKeyLength] = 0;
	strcat(szPath, szName + 19);
	if (rfPref.Open(szPath, "rb", RFile::LittleEndian) != SUCCESS)
	{
		if (rfPref.Open(szPath, "wb", RFile::LittleEndian) != SUCCESS)
		{
			// If the file cannot be created, then hose the program
			g_lRegTime = EXPIRATION_DATE;
		}
		else
		{
			// If the date is set back before the release of the beta, then hose the
			// program
			g_lRegTime = lTime;
			if (lTime < g_lReleaseTime)
				g_lRegTime = lTime = EXPIRATION_DATE;
			dwTimeLength = Encrypt((char *)&lTime, szTimeEncrypt, 4);
			rfPref.Write(&dwTimeLength);
			rfPref.Write(szTime, dwTimeLength);
			rfPref.Close();
		}
	}
	// If the prefs file is here, then open it and read the last date
	else
	{
		rfPref.Read(&dwSize);
		rfPref.Read(szData, dwSize);
		rfPref.Close();

		if (Decrypt(szData, (char *)&lTime, dwSize) == 0)
		{
			g_lRegTime = lTime;
		}
		else
		{
			g_lRegTime = EXPIRATION_DATE;
		}
	}

#endif // WIN32
	if (g_lRegTime < RELEASE_DATE)
		g_lRegTime = EXPIRATION_DATE;

#else  // CHECK_EXPIRATION_DATE
	g_lRegTime = 0;
#endif // CHECK_EXPIRATION_DATE
}

////////////////////////////////////////////////////////////////////////////////
// GameSetRegistry
////////////////////////////////////////////////////////////////////////////////

static void GameSetRegistry(void)
{
#ifdef CHECK_EXPIRATION_DATE
	char szTimeEncrypt[40];
	time_t lTime;
	DWORD dwTimeLength;
	char szName[256];
	int16_t sEncryptedKeyLength = 36;
	uint8_t szKey[40];

	szKey[0] = 0x00;
	szKey[1] = 0x3e;
	szKey[2] = 0xde;
	szKey[3] = 0x28;
	szKey[4] = 0xda;
	szKey[5] = 0x46;
	szKey[6] = 0xdd;
	szKey[7] = 0x57;
	szKey[8] = 0xc2;
	szKey[9] = 0x34;
	szKey[10] = 0xc4;
	szKey[11] = 0x32;
	szKey[12] = 0xe8;
	szKey[13] = 0x3f;
	szKey[14] = 0xf8;
	szKey[15] = 0x2c;
	szKey[16] = 0xe8;
	szKey[17] = 0x3e;
	szKey[18] = 0xe8;
	szKey[19] = 0x2a;
	szKey[20] = 0xdc;
	szKey[21] = 0x03;
	szKey[22] = 0xff;
	szKey[23] = 0x14;
	szKey[24] = 0xfb;
	szKey[25] = 0x18;
	szKey[26] = 0xfe;
	szKey[27] = 0x1f;
	szKey[28] = 0xc9;
	szKey[29] = 0x35;
	szKey[30] = 0xe0;
	szKey[31] = 0x02;
	szKey[32] = 0xea;
	szKey[33] = 0x09;
	szKey[34] = 0xf1;
	szKey[35] = 0xf5;
	szKey[36] = 0x00;

#ifdef WIN32

	char szTime[40];
	DWORD dwDisposition;
	DWORD dwSize = 255;
	DWORD dwNameSize = 255;
	HKEY hkResult;
	int32_t lError;
	int16_t sEncryptedValueLength = 9;

	uint8_t szIn[10];

	szIn[0] = 0x07;
	szIn[1] = 0x29;
	szIn[2] = 0xa8;
	szIn[3] = 0x0f;
	szIn[4] = 0xa7;
	szIn[5] = 0x0c;
	szIn[6] = 0xa8;
	szIn[7] = 0x0e;
	szIn[8] = 0xfd;
	szIn[9] = 0x00;

	time(&lTime);
	lTime = MAX(lTime, g_lRegTime);
	sprintf(szTime, "%ld", lTime);

	Decrypt((char *)szKey, szName, sEncryptedKeyLength);
	szName[sEncryptedKeyLength - 2] = 0;
	lError = RegCreateKeyEx(HKEY_LOCAL_MACHINE, szName, 0,
							"", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
							&hkResult, &dwDisposition);
	memset(szName, 0xea, sEncryptedKeyLength);

	if (lError == ERROR_SUCCESS)
	{
		// Make sure current value is after release value
		dwTimeLength = Encrypt(szTime, szTimeEncrypt, strlen(szTime));
		Decrypt((char *)szIn, szName, sEncryptedValueLength);
		szName[sEncryptedValueLength - 2] = 0;
		RegSetValueEx(hkResult, szName, 0, REG_BINARY, (uint8_t *)szTimeEncrypt, dwTimeLength);
		memset(szIn, 0xee, sEncryptedValueLength);
		RegCloseKey(hkResult);
	}
	else
	{
		LPVOID lpMsgBuf;

		FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
			(LPTSTR)&lpMsgBuf,
			0,
			NULL);

		// Display the string.
		TRACE((char *)lpMsgBuf);

		// Free the buffer.
		LocalFree(lpMsgBuf);
	}
#else // WIN32
	// Do the Mac version here
	char szPath[512];
	RFile rfPref;
	time(&lTime);
	lTime -= ((365 * 70UL) + 17) * 24 * 60 * 60; // time_fudge 1900->1970

	// Make sure the time is after the last registered time.
	lTime = MAX(lTime, (time_t)g_lRegTime);
	dwTimeLength = Encrypt((char *)&lTime, szTimeEncrypt, 4);
	macGetSpecialPath(MacPreferencesFolderType, true, szPath, 254);
	Decrypt((char *)szKey, szName, sEncryptedKeyLength);
	sEncryptedKeyLength = MAX(0, sEncryptedKeyLength - 2);
	szName[sEncryptedKeyLength] = 0;
	strcat(szPath, szName + 19);

	if (rfPref.Open(szPath, "wb", RFile::LittleEndian) == SUCCESS)
	{
		rfPref.Write(&dwTimeLength);
		rfPref.Write(szTimeEncrypt, dwTimeLength);
		rfPref.Close();
	}
	// Open the prefs file and write the value here.

#endif // WIN32

#endif // CHECK_EXPIRATION_DATE
}

////////////////////////////////////////////////////////////////////////////////
//
// Seed the random number generator
//
////////////////////////////////////////////////////////////////////////////////
extern void SeedRand(
	int32_t lSeed)
{
	m_lRandom = lSeed;
}

////////////////////////////////////////////////////////////////////////////////
//
// Get a random number
//
////////////////////////////////////////////////////////////////////////////////
#if defined(_DEBUG) || defined(TRACENASSERT)
extern int32_t GetRandomDebug(char *FILE_MACRO, int32_t LINE_MACRO)
{
	// Get next random number
	int32_t lNewVal = (((m_lRandom = m_lRandom * 214013L + 2531011L) >> 16) & 0x7fff);

	if (m_pfileRandom)
	{
		if (m_bWriteLogs)
		{
			fprintf(
				m_pfileRandom->m_fs,
				"%s : %ld rand = %ld\n",
				GetFileNameFromPath(FILE_MACRO),
				LINE_MACRO,
				lNewVal);
			//				m_pfileRandom->Write(lNewVal);
			//				m_pfileRandom->Write(LINE_MACRO);
			//				m_pfileRandom->Write(FILE_MACRO);
		}
		else
		{
			int32_t lSavedVal;
			int32_t lSavedLine;
			char szSavedFile[1024];
			fscanf(
				m_pfileRandom->m_fs,
				"%s : %ld rand = %ld\n",
				szSavedFile,
				&lSavedLine,
				&lSavedVal);
			//				m_pfileRandom->Read(&lSavedVal);
			//				m_pfileRandom->Read(&lSavedLine);
			//				m_pfileRandom->Read(szSavedFile);

			if ((lSavedVal != lNewVal) || (lSavedLine != LINE_MACRO) || (rspStricmp(szSavedFile, GetFileNameFromPath(FILE_MACRO)) != 0))
			{
				/*rspMsgBox(
						RSP_MB_ICN_INFO | RSP_MB_BUT_OK,
						"Postal",
						"Random number sequence mismatch!\n\n"
						"   Was %s(%ld) which got %ld\n\n"
						"   Now %s(%ld) which got %ld",
						szSavedFile,
						(int32_t)lSavedLine,
						(int32_t)lSavedVal,
						GetFileNameFromPath(FILE_MACRO),
						LINE_MACRO,
						(int32_t)lNewVal);*/

				// Make this easy to debug
				ASSERT(0);
			}
		}
	}
	return lNewVal;
}
#else
extern int32_t GetRandom(void)
{
	// Get next random number
	return (((m_lRandom = m_lRandom * 214013L + 2531011L) >> 16) & 0x7fff);
}
#endif // defined(_DEBUG) || defined(TRACENASSERT).

////////////////////////////////////////////////////////////////////////////////
//
// Define a 0 to take the place of the stdlib version.  We don't want
// anyone calling that version!
//
////////////////////////////////////////////////////////////////////////////////
// It seems that this only works in debug mode.  In release, the linker
// generates a fatal "multiple definitions of 0" error.  For now, only
// do this in debug mode.
#ifdef _DEBUG
#ifndef PLATFORM_UNIX
extern int rand(void)
{
	/*rspMsgBox(
		RSP_MB_ICN_INFO | RSP_MB_BUT_OK,
		"Postal",
		"The stdlib version of 0 was called.  It is forbidden within this application.");*/
	return 0;
}
#endif
#endif

////////////////////////////////////////////////////////////////////////////////
//
// Do palette transition so menu can be displayed on top of existing background.
// NOTE: There must be a matching PalTranOff() for each PalTranOn()!!!
//
////////////////////////////////////////////////////////////////////////////////
extern void PalTranOn(
	int32_t lTime /* = -1 */) // In:  How long transition should take (or -1 for default)
{
	if (lTime == -1)
		lTime = NORMAL_PAL_TRAN_TIME;

	StartMenuTrans(lTime);

	// Do until done or keypress.
	if (g_pvrRenderer->m_szDrawMode == PVR::cRenderMgr::Batched)
	{		
		while (DoPreMenuTrans() == false)
		{
			g_pvrRenderer->DrawQuad(
				g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_SPLASH_THIRD),
				0, 0, 640, 480,
				PVR::cRenderMgr::LayerBg,
				1.0f);

			UpdateSystem();

			g_pvrRenderer->FlushBatchedDrawQueues();
		}
	}	
}

////////////////////////////////////////////////////////////////////////////////
//
// Undo the palette transition to restore the original background colors.
//
////////////////////////////////////////////////////////////////////////////////
extern void PalTranOff(void)
{

	if (g_pvrRenderer->m_szDrawMode == PVR::cRenderMgr::Batched)
	{
		// todo ???
		// Do until done
		while (DoPostMenuTrans() == false)
		{
			g_pvrRenderer->DrawQuad(
				g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_SPLASH_THIRD),
				0, 0, 640, 480,
				PVR::cRenderMgr::LayerBg);

			// As always...
			UpdateSystem();

			g_pvrRenderer->FlushBatchedDrawQueues();
		}
	}

	// Let paltran finish/clean up.
	EndMenuTrans(true);
}

////////////////////////////////////////////////////////////////////////////////
//
// Set gamma/brighten-effect value.  Note that there is no value that results
//	in identity.  See Brightness / contrast below:
// RIGHT NOW THIS FUNCTION IS BEING USED TO CALL BRIGHTNESS CONTRAST:
//
////////////////////////////////////////////////////////////////////////////////
extern void SetGammaLevel( // Returns nothing.
	int16_t sBase)		   // New brighten value.
{
	// For the time being, use this to control the other function:

	// Pick a contrast based on the chosen brightness.
	double dBrightness = double(sBase) / 128.0 - 1.0;

	// Allow a larger range:
	dBrightness *= 2.0;

	// This was no clip
	//double dContrast = MIN(1.0 - dBrightness, dBrightness + 1.0);
	// Let's link contrast with brightness and clip:
	double dContrast = (dBrightness + 1.0); // needs to be a value of 1 to be neutral

	// With this scheme, contrast can never be more than +1.0 or less than 0.0
	SetBrightnessContrast(dBrightness, dContrast);
	// Update settings.
	g_GameSettings.m_sGammaVal = sBase;

	return; // don't set gamma for now

	U8 au8RedMap[256];
	U8 au8GreenMap[256];
	U8 au8BlueMap[256];

	int16_t i;
	int16_t sClipVal;
	for (i = 0;
		 i < 256;
		 i++)
	{
		sClipVal = MAX((int16_t)0, MIN(int16_t(pow((double)i / 100.0, GAMMA_EXPONENT) * sBase), (int16_t)255));
		au8RedMap[i] = (U8)sClipVal;
		au8GreenMap[i] = (U8)sClipVal;
		au8BlueMap[i] = (U8)sClipVal;
	}

	// Update map.
	rspSetPaletteMaps(
		0,
		256,
		au8RedMap,
		au8GreenMap,
		au8BlueMap,
		sizeof(U8));

	// Update hardware through new map.
	rspUpdatePalette();

	// Update settings.
	g_GameSettings.m_sGammaVal = sBase;
}

////////////////////////////////////////////////////////////////////////////////
//
//	Set Brightness and Contrast.  Zero (neutral) values yield and identity
// curve.  Valid input is from -1 to 1.
//
////////////////////////////////////////////////////////////////////////////////
extern void SetBrightnessContrast(
	double dBrightness, // -1.0 = dim, 0.0 = normal, 1.0 = bright
	double dContrast	// -1.0 = low contrast, 0.0 = normal, 1.0 = high
)
{
	U8 au8RedMap[256];
	U8 au8GreenMap[256];
	U8 au8BlueMap[256];

	// I will scale the ranges to within reasonable limits:
	ASSERT((dBrightness >= -1.0) || (dBrightness <= 1.0));
	//ASSERT( (dContrast >= -1.0) || (dContrast <= 1.0));

	//dContrast = dContrast + 1.0; // this IS the tangent value (0-2)

	int16_t i;
	double dScale = 1.0 / 128.0;
	for (i = 0; i < 256; i++)
	{
		double dX = dScale * i - 1.0;
		int16_t sLev = int16_t(128.0 + 128.0 * ((((((1.0 - dContrast) * dX) - dBrightness) * dX) + dContrast) * dX + dBrightness));

		if (sLev < 0)
			sLev = 0;
		if (sLev > 255)
			sLev = 255;

		au8RedMap[i] = (U8)sLev;
		au8GreenMap[i] = (U8)sLev;
		au8BlueMap[i] = (U8)sLev;
	}

	// Update map.
	rspSetPaletteMaps(
		0,
		256,
		au8RedMap,
		au8GreenMap,
		au8BlueMap,
		sizeof(U8));

	// Update hardware through new map.
	rspUpdatePalette();
	//----------------------------------
	// set postal levels?
}

////////////////////////////////////////////////////////////////////////////////
//
// Get gamma/brighten-effect value from palette map (not from settings).
//
////////////////////////////////////////////////////////////////////////////////
extern int16_t GetGammaLevel(void) // Returns current brighten value.
{
	return g_GameSettings.m_sGammaVal;
}

////////////////////////////////////////////////////////////////////////////////
//
// Create a full path out of a partial path.
//
// The partial path must be in "RSPiX neutral" format, which is pretty much
// like a partial Windows path, except with slashes instead of backslashes.
//
// BEWARE: The return value is a pointer to a static string, which means its
// contents are changed every time this function is called!  If you're just
// going to use the string and then be done with it, this should work fine, but
// if you need the string to stick around a while, you should probably do a
// strcpy() into your own string buffer.
//
// There are several variations - pick the one you like best.
//
////////////////////////////////////////////////////////////////////////////////

// This string is much larger than it needs to be in case the string
// size changes as a result of the conversion to system format, in which
// case our tests would miss it until it's too late.  A massive overrun
// will still cause a problem, but that would never happen :)
static char m_acFullPath[RSP_MAX_PATH + RSP_MAX_PATH];

extern char *FullPath(	  // Returns full path in system format
	int16_t sPathType,	  // In:  PATH_CD, PATH_HD, or PATH_VD
	char *pszPartialPath) // In:  Partial path in RSPiX format
{
	// Start with the specified base path (copy the string from the game settings)
	if (sPathType == GAME_PATH_CD)
		return FullPathCD(pszPartialPath);
	else if (sPathType == GAME_PATH_HD)
		return FullPathCD(pszPartialPath);
	//return FullPathHD(pszPartialPath);
	else if (sPathType == GAME_PATH_VD)
		return FullPathVD(pszPartialPath);
	else if (sPathType == GAME_PATH_SOUND)
		return FullPathSound(pszPartialPath);
	else if (sPathType == GAME_PATH_GAME)
		return FullPathGame(pszPartialPath);
	else if (sPathType == GAME_PATH_HOODS)
		return FullPathHoods(pszPartialPath);
	else
	{
		TRACE("FullPath(): Unknown path type: %d -- I predict an ASSERT() will occur soon...\n", (int16_t)sPathType);
		ASSERT(1);

		// In case they want to ignore the assert, just return a pointer to an empty string
		m_acFullPath[0] = 0;
		return m_acFullPath;
	}
}

extern char *FullPathCD(  // Returns full path in system format
	char *pszPartialPath) // In:  Partial path in RSPiX format
{
	// Start with proper base path
	ASSERT(strlen(g_GameSettings.m_pszCDPath) < RSP_MAX_PATH);

	strcpy(m_acFullPath, g_GameSettings.m_pszCDPath);
	// Make sure partial path isn't too long.  It is possible that the conversion
	// to the system format will change its length slightly, but it shouldn't be
	// enough to make a real difference to this test.
	ASSERT(strlen(pszPartialPath) < RSP_MAX_PATH);

	// Check if the combination of the partial and base path will be too long.
	ASSERT((strlen(pszPartialPath) + strlen(m_acFullPath)) < RSP_MAX_PATH);

	// Convert specified partial path from rspix to system format, putting the
	// result immediately following the base path
	rspPathToSystem(pszPartialPath, m_acFullPath + strlen(m_acFullPath));

	// Make sure result isn't too long (our buffer can handle it, but it's still a problem)
	ASSERT(strlen(m_acFullPath) < RSP_MAX_PATH);

	// todo: clean up
	// TODO: Make less bodgy
	//	const char* name = FindCorrectFile(pszFileName, pszFlags);
	uint8_t length = strlen(m_acFullPath);
	char *pch = strstr(m_acFullPath, "/cd//cd/");

	if (pch != NULL)
	{
		char filen[100];
		strncpy(filen, pch + 8, length - 7);
		sprintf(m_acFullPath, "/cd/%s", filen);
	}

	return m_acFullPath;
}

// todo: remove
/*extern char* FullPathHD(								// Returns full path in system format
	const char* pszPartialPath)								// In:  Partial path in RSPiX format
	{
	// Start with proper base path
	ASSERT(strlen(g_GameSettings.m_pszHDPath) < RSP_MAX_PATH);
	strcpy(m_acFullPath, g_GameSettings.m_pszHDPath);
	// Make sure partial path isn't too long.  It is possible that the conversion
	// to the system format will change its length slightly, but it shouldn't be
	// enough to make a real difference to this test.
	ASSERT(strlen(pszPartialPath) < RSP_MAX_PATH);

	// Check if the combination of the partial and base path will be too long.
	ASSERT((strlen(pszPartialPath) + strlen(m_acFullPath)) < RSP_MAX_PATH);

	// Convert specified partial path from rspix to system format, putting the
	// result immediately following the base path
	rspPathToSystem(pszPartialPath, m_acFullPath + strlen(m_acFullPath));

	// Make sure result isn't too long (our buffer can handle it, but it's still a problem)
	ASSERT(strlen(m_acFullPath) < RSP_MAX_PATH);

	// Return pointer to full path
	return m_acFullPath;
	}*/

extern char *FullPathVD(  // Returns full path in system format
	char *pszPartialPath) // In:  Partial path in RSPiX format
{
	// Start with proper base path
	ASSERT(strlen(g_GameSettings.m_pszVDPath) < RSP_MAX_PATH);
	strcpy(m_acFullPath, g_GameSettings.m_pszVDPath);

	// Make sure partial path isn't too long.  It is possible that the conversion
	// to the system format will change its length slightly, but it shouldn't be
	// enough to make a real difference to this test.
	ASSERT(strlen(pszPartialPath) < RSP_MAX_PATH);

	// Check if the combination of the partial and base path will be too long.
	ASSERT((strlen(pszPartialPath) + strlen(m_acFullPath)) < RSP_MAX_PATH);

	// Convert specified partial path from rspix to system format, putting the
	// result immediately following the base path
	rspPathToSystem(pszPartialPath, m_acFullPath + strlen(m_acFullPath));

	// Make sure result isn't too long (our buffer can handle it, but it's still a problem)
	ASSERT(strlen(m_acFullPath) < RSP_MAX_PATH);

	// Return pointer to full path
	return m_acFullPath;
}

extern char *FullPathSound( // Returns full path in system format
	char *pszPartialPath)	// In:  Partial path in RSPiX format
{
	// Start with proper base path
	ASSERT(strlen(g_GameSettings.m_pszSoundPath) < RSP_MAX_PATH);
	strcpy(m_acFullPath, g_GameSettings.m_pszSoundPath);
	// Make sure partial path isn't too long.  It is possible that the conversion
	// to the system format will change its length slightly, but it shouldn't be
	// enough to make a real difference to this test.
	ASSERT(strlen(pszPartialPath) < RSP_MAX_PATH);

	// Check if the combination of the partial and base path will be too long.
	ASSERT((strlen(pszPartialPath) + strlen(m_acFullPath)) < RSP_MAX_PATH);

	// Convert specified partial path from rspix to system format, putting the
	// result immediately following the base path
	rspPathToSystem(pszPartialPath, m_acFullPath + strlen(m_acFullPath));

	// Make sure result isn't too long (our buffer can handle it, but it's still a problem)
	ASSERT(strlen(m_acFullPath) < RSP_MAX_PATH);

	// Return pointer to full path
	return m_acFullPath;
}

extern char *FullPathGame( // Returns full path in system format
	char *pszPartialPath)  // In:  Partial path in RSPiX format
{
	// Start with proper base path
	ASSERT(strlen(g_GameSettings.m_pszGamePath) < RSP_MAX_PATH);
	strcpy(m_acFullPath, g_GameSettings.m_pszGamePath);
	// Make sure partial path isn't too long.  It is possible that the conversion
	// to the system format will change its length slightly, but it shouldn't be
	// enough to make a real difference to this test.
	ASSERT(strlen(pszPartialPath) < RSP_MAX_PATH);

	// Check if the combination of the partial and base path will be too long.
	ASSERT((strlen(pszPartialPath) + strlen(m_acFullPath)) < RSP_MAX_PATH);

	// Convert specified partial path from rspix to system format, putting the
	// result immediately following the base path
	rspPathToSystem(pszPartialPath, m_acFullPath + strlen(m_acFullPath));

	// Make sure result isn't too long (our buffer can handle it, but it's still a problem)
	ASSERT(strlen(m_acFullPath) < RSP_MAX_PATH);

	// Return pointer to full path
	return m_acFullPath;
}

extern char *FullPathHoods( // Returns full path in system format
	char *pszPartialPath)	// In:  Partial path in RSPiX format
{
	// Start with proper base path
	ASSERT(strlen(g_GameSettings.m_pszHoodsPath) < RSP_MAX_PATH);
	strcpy(m_acFullPath, g_GameSettings.m_pszHoodsPath);
	// Make sure partial path isn't too long.  It is possible that the conversion
	// to the system format will change its length slightly, but it shouldn't be
	// enough to make a real difference to this test.
	ASSERT(strlen(pszPartialPath) < RSP_MAX_PATH);

	// Check if the combination of the partial and base path will be too long.
	ASSERT((strlen(pszPartialPath) + strlen(m_acFullPath)) < RSP_MAX_PATH);

	// Convert specified partial path from rspix to system format, putting the
	// result immediately following the base path
	rspPathToSystem(pszPartialPath, m_acFullPath + strlen(m_acFullPath));

	// Make sure result isn't too long (our buffer can handle it, but it's still a problem)
	ASSERT(strlen(m_acFullPath) < RSP_MAX_PATH);

	// Return pointer to full path
	return m_acFullPath;
}

extern char *FullPathCustom( // Returns full path in system format
	char *pszFullPath,		 // In:  Full path in in RSPiX format.
	char *pszPartialPath)	 // In:  Partial path in RSPiX format.
{
	char *pszFullSystemPath = rspPathToSystem(pszFullPath);
	// Start with proper base path
	ASSERT(strlen(pszFullSystemPath) < RSP_MAX_PATH);
	strcpy(m_acFullPath, pszFullSystemPath);

	// Make sure partial path isn't too long.  It is possible that the conversion
	// to the system format will change its length slightly, but it shouldn't be
	// enough to make a real difference to this test.
	ASSERT(strlen(pszPartialPath) < RSP_MAX_PATH);

	// Check if the combination of the partial and base path will be too long.
	ASSERT((strlen(pszPartialPath) + strlen(m_acFullPath)) < RSP_MAX_PATH);

	// Convert specified partial path from rspix to system format, putting the
	// result immediately following the base path
	rspPathToSystem(pszPartialPath, m_acFullPath + strlen(m_acFullPath));

	// Make sure result isn't too long (our buffer can handle it, but it's still a problem)
	ASSERT(strlen(m_acFullPath) < RSP_MAX_PATH);

	// Return pointer to full path
	return m_acFullPath;
}

////////////////////////////////////////////////////////////////////////////////
//
// Correctify the specified base path.
//
// First, it ensures that the path is absolute (not relative).  Then, it ensures
// the path ends properly, either with or without the system-specific separator
// character, depending on which system we're running on.
//
////////////////////////////////////////////////////////////////////////////////
int16_t CorrectifyBasePath( // Returns 0 if successfull, non-zero otherwise
	char *pszBasePath,		// I/O: Base path to be corrected
	int16_t sMaxPathLen)	// In:  Maximum length of base path
{
	int16_t sResult = 0;

	// Make sure they aren't passing an empty string, which should be be left alone
	if (strlen(pszBasePath) > 0)
	{

		// Make sure they aren't passing a string that's already too large!
		if ((strlen(pszBasePath) + 1) <= sMaxPathLen)
		{

			//------------------------------------------------------------------------------
			// Convert path from relative to absolute.  We do this by setting the current
			// directory to the specified path and then asking for the current directory,
			// which is always returned as an absolute path.  If the path is already
			// absolute to begin with, it *should* be unchanged as a result.
			//------------------------------------------------------------------------------

			// Get original directory (let it allocate buffer of at LEAST specified size, more if needed)

			// 09/05/97, PPL
			// Apparently, the getcwd function works completely (well, maybe not completely) different
			// on the MAC.  We need to go ahead and allocate our own buffer and free it later because
			// if a NULL is passed in, a NULL is returned.  This should not have any adverse effects
			// on the PC version.
			//char* pszOrigDir = getcwd(NULL, RSP_MAX_PATH);
			/*char* pszOrigDir = (char*)malloc(RSP_MAX_PATH);
			
			if (pszOrigDir != NULL)
				{
				// Let's go ahead and get the current working directory here, once we're sure that
				// the string to store it has been properly allocated.
				pszOrigDir = getcwd(pszOrigDir, RSP_MAX_PATH);

				// Change to specified directory, which may be relative or absolute
				if (chdir(pszBasePath) == 0)
					{
					// Get directory, which is always returned as absolute
					if (getcwd(pszBasePath, sMaxPathLen) == NULL)
						{
						sResult = -1;
						TRACE("CorrectifyBasePath(): Couldn't get current directory (was set to '%s')!\n", pszBasePath);
						}
					}
				else
					{
					sResult = -1;
					TRACE("CorrectifyBasePath(): Couldn't change to specified directory: '%s'!\n", pszBasePath);
					}

				// Restore original directory
				if (chdir(pszOrigDir) != 0)
					{
					sResult = -1;
					TRACE("CorrectifyBasePath(): Couldn't restore original directory: '%s'!\n", pszOrigDir);
					}

				// Free buffer that was allocated by getcwd()
				free(pszOrigDir);
				pszOrigDir = 0;
				}*/

			//------------------------------------------------------------------------------
			// Ensure that path ends properly, either with or without the system-specific
			// separator character, depending on which system we're on.
			//------------------------------------------------------------------------------
			if (sResult == 0)
			{
				// Get index to last character
				int16_t sLastIndex = strlen(pszBasePath);
				if (sLastIndex > 0)
					sLastIndex--;

#if 1 //def WIN32 \
	// If base path doesn't end with a slash, add one
				if (pszBasePath[sLastIndex] != RSP_SYSTEM_PATH_SEPARATOR)
				{
					if ((sLastIndex + 2) < RSP_MAX_PATH)
					{
						pszBasePath[sLastIndex + 1] = RSP_SYSTEM_PATH_SEPARATOR;
						pszBasePath[sLastIndex + 2] = 0;
					}
					else
					{
						sResult = -1;
						TRACE("CorrectifyBasePath(): Path would've exceed max length with separator tacked on!\n");
					}
				}
#else
				// If base path ends with a colon, get rid of it
				if (pszBasePath[sLastIndex] == RSP_SYSTEM_PATH_SEPARATOR)
					pszBasePath[sLastIndex] = 0;
#endif
			}
		}
		else
		{
			sResult = -1;
			TRACE("CorrectifyBasePath(): Specified path is already longer than the specified maximum length!\n");
		}
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
//
// Notification that we are about to be in the background.
//
////////////////////////////////////////////////////////////////////////////////
static void BackgroundCall(void)
{
	// TRACE("Background\n");
// todo: remove
	// Return CPU to OS.
	rspSetDoSystemMode(RSP_DOSYSTEM_SLEEP);

	// If cursor show level not already stored . . .
	/*if (ms_sForegroundCursorShowLevel == INVALID_CURSOR_SHOW_LEVEL)
		{
		// Store current cursor show level and then make cursor visible		
		}*/
}

////////////////////////////////////////////////////////////////////////////////
//
// Notification that we are about to be in the foreground.
//
////////////////////////////////////////////////////////////////////////////////
static void ForegroundCall(void)
{
	// TRACE("Foreground\n");
// todo: remove
#if defined(_DEBUG)
	// Wake CPU.
	rspSetDoSystemMode(RSP_DOSYSTEM_TOLERATEOS);
#else
	// Return CPU to us.
	rspSetDoSystemMode(RSP_DOSYSTEM_HOGCPU);
#endif
}

////////////////////////////////////////////////////////////////////////////////
// Returns difficulty for games.
// Note that this function is only valid once after a difficulty adjustment
// and then it goes back to the default (g_GameSettings value).
////////////////////////////////////////////////////////////////////////////////
static int8_t GetGameDifficulty(void) // Returns cached game difficulty.
{
	int8_t sDifficulty = g_GameSettings.m_sDifficulty;

	// If there is a cached difficulty . . .
	if (ms_sLoadedDifficulty != INVALID_DIFFICULTY)
	{
		// Return cached difficulty.
		sDifficulty = ms_sLoadedDifficulty;

		// Clear cache.
		ms_sLoadedDifficulty = INVALID_DIFFICULTY;
	}

	return sDifficulty;
}

////////////////////////////////////////////////////////////////////////////////
// Returns Title of a given Challenge-ID
////////////////////////////////////////////////////////////////////////////////
extern char *GetChallengeTitle(int16_t challengeID)
{
	char title[256];
	char fileName[256];
	Play_GetRealmInfo(false, false, true, 0, challengeID - 1, 0, fileName, 256, title, 256);
	return title;
}

extern void SetPal60(bool bMode)
{
	if (bMode)
	{
		m_action = ACTION_PAL_60;
	}
	else
	{
		m_action = ACTION_PAL_50;
	}	
}

extern void SetLanguage(int8_t cLanguage)
{
	m_action = ACTION_LANGUAGE_SET;
	g_GameSettings.m_cActiveLanguage = cLanguage;		
}

////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////
