2019-04-04 00:57:53 -07:00
|
|
|
#include <PrimeAPI.h>
|
2021-05-12 21:59:43 +03:00
|
|
|
|
|
|
|
|
#include <prime/CArchitectureQueue.hpp>
|
|
|
|
|
#include <prime/CMainFlow.hpp>
|
|
|
|
|
#include <prime/CGameArea.hpp>
|
|
|
|
|
#include <prime/CGameState.hpp>
|
|
|
|
|
#include <prime/CPlayerState.hpp>
|
|
|
|
|
#include <prime/CStateManager.hpp>
|
|
|
|
|
#include <prime/CWorldState.hpp>
|
|
|
|
|
#include <prime/CPlayer.hpp>
|
|
|
|
|
#include <prime/CWorld.hpp>
|
|
|
|
|
#include <dvd.h>
|
|
|
|
|
#include <os.h>
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
void *memcpy(void *dest, const void *src, size_t count);
|
|
|
|
|
|
|
|
|
|
__attribute__((visibility("default"))) extern void __rel_prolog();
|
|
|
|
|
}
|
2019-04-04 00:57:53 -07:00
|
|
|
|
2019-04-07 11:33:42 -07:00
|
|
|
// IMPORTANT NOTE: Most of the values, enums & structs declared here
|
|
|
|
|
// are mirrored in Prime World Editor NDolphinIntegration.h.
|
|
|
|
|
|
2019-04-04 00:57:53 -07:00
|
|
|
// Debug config file magic
|
|
|
|
|
const uint32 gkDebugConfigMagic = 0x00BADB01;
|
|
|
|
|
|
|
|
|
|
// Current quickplay version
|
2019-04-07 11:33:42 -07:00
|
|
|
// This should match EQuickplayVersion::Current in Prime World Editor
|
2019-04-08 01:11:06 -07:00
|
|
|
const uint32 gkQuickplayVersion = 2;
|
2019-04-04 00:57:53 -07:00
|
|
|
|
|
|
|
|
// Feature mask enum
|
|
|
|
|
enum EQuickplayFeature
|
|
|
|
|
{
|
|
|
|
|
/** On boot, automatically load the area specified by WorldID and AreaID */
|
|
|
|
|
kQF_JumpToArea = 0x00000001,
|
|
|
|
|
/** Spawn the player in the location specified by SpawnTransform */
|
|
|
|
|
kQF_SetSpawnPosition = 0x00000002,
|
2021-05-12 21:59:43 +03:00
|
|
|
/** Give the player all items on spawn */
|
|
|
|
|
kQF_GiveAllItems = 0x00000004,
|
2019-04-04 00:57:53 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Contains debug parameters for quickplay from the editor
|
|
|
|
|
// This is a mix of user-selected options and context from the current editor state
|
|
|
|
|
struct SQuickplayParms
|
|
|
|
|
{
|
2021-05-12 21:59:43 +03:00
|
|
|
uint32 Magic;
|
|
|
|
|
uint32 Version;
|
|
|
|
|
uint32 FeatureFlags;
|
|
|
|
|
uint32 BootWorldAssetID;
|
|
|
|
|
uint32 BootAreaAssetID;
|
|
|
|
|
uint32 __PADDING; // Explicit align to 64 bits
|
|
|
|
|
uint64 BootAreaLayerFlags;
|
|
|
|
|
CTransform4f SpawnTransform;
|
2019-04-04 00:57:53 -07:00
|
|
|
};
|
|
|
|
|
SQuickplayParms gQuickplayParms;
|
|
|
|
|
|
2021-05-12 21:59:43 +03:00
|
|
|
constexpr const size_t QUICKPLAY_BUFFER_SIZE = ((sizeof(SQuickplayParms) + 31) & ~31);
|
2019-04-04 00:57:53 -07:00
|
|
|
|
|
|
|
|
// Forward decls
|
|
|
|
|
void LoadDebugParamsFromDisc();
|
|
|
|
|
|
|
|
|
|
// Module init
|
2021-05-12 21:59:43 +03:00
|
|
|
__attribute__((visibility("default"))) void __rel_prolog()
|
2019-04-04 00:57:53 -07:00
|
|
|
{
|
2021-05-12 21:59:43 +03:00
|
|
|
MODULE_INIT;
|
|
|
|
|
OSReport("Quickplay module loaded\n");
|
|
|
|
|
LoadDebugParamsFromDisc();
|
2019-04-04 00:57:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This callback workaround is needed because the game doesn't have any synchronous
|
|
|
|
|
// DVD reading functions linked into the DOL, so we have to use the async one
|
|
|
|
|
volatile s32 gDvdBytesRead = -1;
|
|
|
|
|
|
|
|
|
|
void DvdLoadFinishedCallback(s32 Result, DVDFileInfo* pFileInfo)
|
|
|
|
|
{
|
2021-05-12 21:59:43 +03:00
|
|
|
gDvdBytesRead = Result;
|
2019-04-04 00:57:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadDebugParamsFromDisc()
|
|
|
|
|
{
|
2021-05-12 21:59:43 +03:00
|
|
|
DVDFileInfo File;
|
|
|
|
|
const char* pkFailReason = NULL;
|
|
|
|
|
|
|
|
|
|
// Debug config is stored in the "dbgconfig" file in the filesystem root
|
|
|
|
|
if (DVDOpen("dbgconfig", &File))
|
|
|
|
|
{
|
|
|
|
|
int RawLength = DVDGetLength(&File);
|
|
|
|
|
int Length = OSRoundUp32B(RawLength);
|
|
|
|
|
|
|
|
|
|
if (RawLength >= sizeof(SQuickplayParms))
|
|
|
|
|
{
|
|
|
|
|
// The DVD read buffer must be aligned to 32 bytes.
|
|
|
|
|
// Since we can't control the alignment of stack variables, supply
|
|
|
|
|
// an extra 32 bytes to the array so we can ensure proper alignment.
|
|
|
|
|
int Length = OSRoundUp32B(DVDGetLength(&File));
|
|
|
|
|
int AllocSize = Length + 32;
|
|
|
|
|
uint8 Buffer[QUICKPLAY_BUFFER_SIZE + 32];
|
|
|
|
|
void* pAlignedBuffer = (void*) OSRoundUp32B(&Buffer[0]);
|
2019-04-04 00:57:53 -07:00
|
|
|
|
2021-05-12 21:59:43 +03:00
|
|
|
if (DVDReadAsyncPrio(&File, pAlignedBuffer, Length, 0, DvdLoadFinishedCallback, 0))
|
|
|
|
|
{
|
|
|
|
|
while (gDvdBytesRead < 0) {}
|
|
|
|
|
|
|
|
|
|
// DVD load complete - parse data
|
|
|
|
|
SQuickplayParms* pParms = static_cast<SQuickplayParms*>(pAlignedBuffer);
|
|
|
|
|
|
|
|
|
|
if (gDvdBytesRead < sizeof(SQuickplayParms))
|
|
|
|
|
{
|
|
|
|
|
pkFailReason = "Failed to read enough data from dbgconfig file.";
|
|
|
|
|
}
|
|
|
|
|
else if (pParms->Magic != gkDebugConfigMagic)
|
|
|
|
|
{
|
|
|
|
|
pkFailReason = "Invalid dbgconfig magic.";
|
|
|
|
|
}
|
|
|
|
|
else if (pParms->Version != gkQuickplayVersion)
|
|
|
|
|
{
|
|
|
|
|
pkFailReason = "Invalid quickplay version.";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
OSReport("Quickplay parameters loaded successfully!\n");
|
|
|
|
|
memcpy(&gQuickplayParms, pParms, sizeof(SQuickplayParms));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pkFailReason = "Failed to read dbgconfig file.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pkFailReason = "dbgconfig file is too small.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DVDClose(&File);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pkFailReason = "Failed to open dbgconfig file.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pkFailReason != NULL)
|
|
|
|
|
{
|
|
|
|
|
OSReport("%s Quickplay debug features will not be enabled.\n", pkFailReason);
|
|
|
|
|
gQuickplayParms.FeatureFlags = 0;
|
|
|
|
|
}
|
2019-04-04 00:57:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hooks
|
|
|
|
|
void Hook_CMainFlow_AdvanceGameState(CMainFlow* pMainFlow, CArchitectureQueue& Queue)
|
|
|
|
|
{
|
2021-05-12 21:59:43 +03:00
|
|
|
// Hook into CMainFlow::AdvanceGameState(). When this function is called with
|
|
|
|
|
// the game state set to PreFrontEnd, that indicates that engine initialization
|
|
|
|
|
// is complete and the game is proceeding to the main menu. We hook in here to
|
|
|
|
|
// bypass the main menu and boot directly into the game.
|
|
|
|
|
static bool sHasDoneInitialBoot = false;
|
|
|
|
|
|
|
|
|
|
// Make sure the patch does not run twice if the player quits out to main menu
|
|
|
|
|
if (!sHasDoneInitialBoot &&
|
|
|
|
|
(gQuickplayParms.FeatureFlags & kQF_JumpToArea) &&
|
|
|
|
|
pMainFlow->GetGameState() == EClientFlowStates::PreFrontEnd)
|
|
|
|
|
{
|
|
|
|
|
sHasDoneInitialBoot = true;
|
|
|
|
|
g_GameState->SetCurrentWorldId( gQuickplayParms.BootWorldAssetID );
|
|
|
|
|
g_GameState->CurrentWorldState().SetDesiredAreaAssetId( gQuickplayParms.BootAreaAssetID );
|
|
|
|
|
pMainFlow->SetGameState(EClientFlowStates::Game, Queue);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pMainFlow->AdvanceGameState(Queue);
|
|
|
|
|
}
|
2019-04-04 00:57:53 -07:00
|
|
|
}
|
|
|
|
|
|
2019-04-07 11:33:42 -07:00
|
|
|
void Hook_CStateManager_InitializeState(CStateManager& StateMgr, uint WorldAssetId, TAreaId AreaId, uint AreaAssetId)
|
2019-04-04 00:57:53 -07:00
|
|
|
{
|
2021-05-12 21:59:43 +03:00
|
|
|
// This function runs when a world is being initialized for gameplay.
|
|
|
|
|
static bool sDoneFirstInit = false;
|
2019-04-07 11:33:42 -07:00
|
|
|
|
2021-05-12 21:59:43 +03:00
|
|
|
// Allow the original function to run first before we execute custom logic
|
|
|
|
|
StateMgr.InitializeState(WorldAssetId, AreaId, AreaAssetId);
|
|
|
|
|
CStateManager::EInitPhase Phase = StateMgr.GetInitPhase();
|
|
|
|
|
|
|
|
|
|
if (!sDoneFirstInit && Phase == CStateManager::EInitPhase::Done)
|
|
|
|
|
{
|
|
|
|
|
sDoneFirstInit = true;
|
|
|
|
|
|
|
|
|
|
// Spawn the player in the location specified by SpawnTransform.
|
|
|
|
|
// This feature doesn't make much sense without JumpToArea, so we require that flag to be on too.
|
|
|
|
|
if ( (gQuickplayParms.FeatureFlags & kQF_JumpToArea) &&
|
|
|
|
|
(gQuickplayParms.FeatureFlags & kQF_SetSpawnPosition) )
|
|
|
|
|
{
|
|
|
|
|
StateMgr.GetPlayer()->Teleport(gQuickplayParms.SpawnTransform, StateMgr, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fill out all inventory values to capacity.
|
|
|
|
|
if (gQuickplayParms.FeatureFlags & kQF_GiveAllItems)
|
|
|
|
|
{
|
|
|
|
|
CPlayerState* pPlayerState = StateMgr.GetPlayerState();
|
|
|
|
|
size_t lastIndex = static_cast<size_t>(CPlayerState::EItemType::Max);
|
|
|
|
|
for (size_t itemIdx = 0; itemIdx < lastIndex; itemIdx++)
|
|
|
|
|
{
|
|
|
|
|
auto item = static_cast<CPlayerState::EItemType>(itemIdx);
|
|
|
|
|
#if PRIME > 1
|
|
|
|
|
if (gkPowerUpShouldPersist[itemIdx] == 0)
|
|
|
|
|
continue;
|
|
|
|
|
#endif
|
|
|
|
|
uint32 Max = gkPowerUpMaxValues[itemIdx];
|
|
|
|
|
pPlayerState->ReInitializePowerUp(item, Max);
|
|
|
|
|
pPlayerState->IncrPickUp(item, Max);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-08 01:11:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Hook_CGameArea_StartStreamIn(CGameArea* pArea, CStateManager& StateMgr)
|
|
|
|
|
{
|
2021-05-12 21:59:43 +03:00
|
|
|
static bool sFirstLoad = false;
|
|
|
|
|
|
|
|
|
|
// Hook into the first time StartStreamIn is called to make sure that
|
|
|
|
|
// all layer flags we want enabled are set.
|
|
|
|
|
// This feature also requires JumpToArea enabled.
|
|
|
|
|
if (!sFirstLoad &&
|
|
|
|
|
(gQuickplayParms.FeatureFlags & kQF_JumpToArea))
|
|
|
|
|
{
|
|
|
|
|
sFirstLoad = true;
|
|
|
|
|
|
|
|
|
|
CWorld* world = StateMgr.GetWorld();
|
|
|
|
|
TAreaId areaId = world->GetAreaId(gQuickplayParms.BootAreaAssetID);
|
|
|
|
|
|
|
|
|
|
auto& layerState = g_GameState->CurrentWorldState().layerState;
|
|
|
|
|
layerState->areaLayers[areaId.id].m_layerBits = gQuickplayParms.BootAreaLayerFlags;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pArea->StartStreamIn(StateMgr);
|
2019-04-04 00:57:53 -07:00
|
|
|
}
|