Files
PDModdingCommunity_PD-Loader/source-code/source/plugins/TLAC/Components/Pause.cpp
T
2019-10-21 14:10:40 +11:00

374 lines
11 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Pause.h"
#include "../Constants.h"
#include "GameState.h"
#include "Drawing.h"
#include "GL/glut.h"
#include "detours.h"
#include <windows.h>
#include <vector>
#include <chrono>
namespace TLAC::Components
{
bool Pause::pause = false;
bool Pause::isPaused = false;
bool Pause::giveUp = false;
bool Pause::showUI = true;
unsigned int Pause::menuPos = 0;
unsigned int Pause::mainMenuPos = 0;
unsigned int Pause::menuSet = MENUSET_MAIN;
std::chrono::time_point<std::chrono::high_resolution_clock> Pause::menuItemSelectTime;
std::vector<uint8_t> Pause::origAetMovOp = {};
std::vector<uint8_t> Pause::origFramespeedOp = {};
std::vector<bool> Pause::streamPlayStates;
bool(*divaGiveUpFunc)(void*) = (bool(*)(void* cls))GIVEUP_FUNC_ADDRESS;
InputState* Pause::inputState;
PlayerData* Pause::playerData;
Pause::Pause()
{
}
Pause::~Pause()
{
}
const char* Pause::GetDisplayName()
{
return "pause";
}
void Pause::saveOldPatchOps()
{
origAetMovOp.resize(8);
memcpy(origAetMovOp.data(), aetMovPatchAddress, 8);
origFramespeedOp.resize(4);
memcpy(origFramespeedOp.data(), framespeedPatchAddress, 4);
}
void Pause::Initialize(ComponentsManager*)
{
inputState = (InputState*)(*(uint64_t*)INPUT_STATE_PTR_ADDRESS);
playerData = (PlayerData*)PLAYER_DATA_ADDRESS;
saveOldPatchOps();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)divaGiveUpFunc, hookedGiveUpFunc);
DetourTransactionCommit();
}
void Pause::Update()
{
}
void Pause::UpdatePostInput()
{
if (pause)
{
// enter pause mode on state transition
if (!isPaused)
{
((void(*)())DSC_PAUSE_FUNC_ADDRESS)();
saveOldPatchOps();
InjectCode(aetMovPatchAddress, { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 });
InjectCode(framespeedPatchAddress, { 0x0f, 0x57, 0xc0, 0xc3 }); // XORPS XMM0,XMM0; RET
uint64_t audioMixerAddr = *(uint64_t*)(AUDIO_MAIN_CLASS_ADDRESS + 0x70);
uint64_t audioStreamsAddress = *(uint64_t*)(audioMixerAddr + 0x18);;
int nAudioStreams = *(uint64_t*)(audioMixerAddr + 0x20);
for (int i = 0; i < nAudioStreams; i++)
{
uint32_t* playstate = (uint32_t*)(audioStreamsAddress + i * 0x50 + 0x18);
if (i < streamPlayStates.size())
streamPlayStates[i] = *playstate;
else
streamPlayStates.push_back(*playstate);
*playstate = 0;
}
menuPos = 0;
menuSet = MENUSET_MAIN;
showUI = true;
menuItemSelectTime = std::chrono::high_resolution_clock::now();
isPaused = true;
}
// always exit pause if key is tapped or no longer in game somehow
if (isPauseKeyTapped() || *(GameState*)CURRENT_GAME_STATE_ADDRESS != GS_GAME || *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS != SUB_GAME_MAIN)
{
pause = false;
}
else
{
if (inputState->Tapped.Buttons & JVS_SQUARE)
showUI = !showUI;
// only process menu events when UI is visible
if (showUI)
{
if (inputState->Tapped.Buttons & JVS_L)
{
menuPos -= 1;
menuItemSelectTime = std::chrono::high_resolution_clock::now();
}
if (inputState->Tapped.Buttons & JVS_R)
{
menuPos += 1;
menuItemSelectTime = std::chrono::high_resolution_clock::now();
}
if (inputState->Tapped.Buttons & JVS_TRIANGLE)
{
if (menuSet != MENUSET_MAIN)
{
menuSet = MENUSET_MAIN;
menuPos = mainMenuPos;
menuItemSelectTime = std::chrono::high_resolution_clock::now();
}
}
menuPos %= menuItems[menuSet].size();
if (menuSet == MENUSET_MAIN)
mainMenuPos = menuPos;
// use released when unpausing from X so it doesn't trigger input
if (inputState->Released.Buttons & JVS_CROSS)
pause = false;
// use released because this can trigger an unpause too
if (inputState->Released.Buttons & JVS_CIRCLE)
menuItems[menuSet][menuPos].second();
if (menuSet == MENUSET_SEVOL)
{
const char volformat[] = "%d";
size_t size = snprintf(nullptr, 0, volformat, playerData->act_vol) + 1;
char* buf = new char[size];
snprintf(buf, size, volformat, playerData->act_vol);
menuItems[MENUSET_SEVOL][1].first = buf;
delete[] buf;
}
}
// swallow all button inputs if paused
inputState->Tapped.Buttons = JVS_NONE;
inputState->DoubleTapped.Buttons = JVS_NONE;
inputState->Down.Buttons = JVS_NONE;
inputState->Released.Buttons = JVS_NONE;
inputState->IntervalTapped.Buttons = JVS_NONE;
// todo: slider
}
}
else
{
// exit pause mode on state transition
if (isPaused)
{
((void(*)())DSC_UNPAUSE_FUNC_ADDRESS)();
InjectCode(aetMovPatchAddress, origAetMovOp);
InjectCode(framespeedPatchAddress, origFramespeedOp);
uint64_t audioMixerAddr = *(uint64_t*)(AUDIO_MAIN_CLASS_ADDRESS + 0x70);
uint64_t audioStreamsAddress = *(uint64_t*)(audioMixerAddr + 0x18);;
int nAudioStreams = *(uint64_t*)(audioMixerAddr + 0x20);
for (int i = 0; i < nAudioStreams; i++)
{
uint32_t* playstate = (uint32_t*)(audioStreamsAddress + i * 0x50 + 0x18);
if (i < streamPlayStates.size())
*playstate = streamPlayStates[i];
}
isPaused = false;
}
// only enter pause if in game
if (*(GameState*)CURRENT_GAME_STATE_ADDRESS == GS_GAME && *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_GAME_MAIN)
{
if (isPauseKeyTapped())
{
pause = true;
}
}
}
}
void Pause::UpdateDrawSprites()
{
if (isPaused && showUI)
{
// setup draw objects
FontInfo fontInfo(0x11);
DrawParams dtParams(&fontInfo);
dtParams.layer = 0x19; // same as startup screen
// bg rect
RectangleBounds rect;
rect = { 0, 0, 1280, 720 };
dtParams.colour = 0xa0000000;
dtParams.fillColour = 0xa0000000;
fillRectangle(&dtParams, &rect);
// pause icon
/*
const int pauseWidth = 80;
const int pauseHeight = 110;
const int pauseGap = 20;
const int pausePosX = 32;
const int pausePosY = 32;
const int pausePartWidth = (pauseWidth - pauseGap) / 2;
const int pauseX1 = pausePosX;
const int pauseX2 = pausePosX + pausePartWidth + pauseGap;
const int pauseY1 = pausePosY;
dtParams.colour = 0x80ffffff;
dtParams.fillColour = 0x80ffffff;
rect = { pauseX1, pauseY1, pausePartWidth, pauseHeight };
fillRectangle(&dtParams, &rect);
rect = { pauseX2, pauseY1, pausePartWidth, pauseHeight };
fillRectangle(&dtParams, &rect);
*/
// selection cursor
int selectBoxOrigin = menuY - menuItemHeight * (menuItems[menuSet].size() / 2.0) - (menuItemHeight - menuTextSize) / 2;
int selectBoxPos = selectBoxOrigin + menuItemHeight * menuPos;
const float selectBoxWidth = 200;
const float selectBoxHeight = menuItemHeight;
const float selectBoxThickness = 2;
const float selectBoxX1 = menuX - selectBoxWidth / 2;
const float selectBoxX2 = selectBoxX1 + selectBoxWidth - selectBoxThickness;
const float selectBoxY1 = selectBoxPos;
const float selectBoxY2 = selectBoxY1 + selectBoxThickness;
const float selectBoxY3 = selectBoxY1 + selectBoxHeight - selectBoxThickness;
const float selectBoxSideHeight = selectBoxHeight - 2 * selectBoxThickness;
// dirty hack using fillRectangle to get thickness (idk how to set it or it isn't possible to with drawRectangle)
dtParams.colour = 0xc0ffff00;
dtParams.fillColour = 0xc0ffff00;
rect = { selectBoxX1, selectBoxY1, selectBoxWidth, selectBoxThickness }; // top
fillRectangle(&dtParams, &rect);
rect = { selectBoxX1, selectBoxY3, selectBoxWidth, selectBoxThickness }; // bottom
fillRectangle(&dtParams, &rect);
rect = { selectBoxX1, selectBoxY2, selectBoxThickness, selectBoxSideHeight };
fillRectangle(&dtParams, &rect);
rect = { selectBoxX2, selectBoxY2, selectBoxThickness, selectBoxSideHeight };
fillRectangle(&dtParams, &rect);
// menu
fontInfo.setSize(menuTextSize, menuTextSize);
dtParams.xBegin = menuX;
dtParams.yBegin = menuY - menuItemHeight * (menuItems[menuSet].size() / 2.0);
for (int i = 0; i < menuItems[menuSet].size(); i++)
{
std::pair<std::string, void(*)()> &item = menuItems[menuSet][i];
if (i == menuPos)
{
const int duration = 1500000000; // 1.5s
std::chrono::nanoseconds cyclePos = (std::chrono::high_resolution_clock::now() - menuItemSelectTime) % std::chrono::nanoseconds(duration);
uint8_t alpha = (cosf(cyclePos.count() * 6.283185f / duration) * 0.15 + 0.85) * 255;
dtParams.colour = 0x00ffff00 | (alpha << 24);
}
else
{
dtParams.colour = 0xffffffff;
}
dtParams.xCurrent = dtParams.xBegin;
dtParams.yCurrent = dtParams.yBegin;
drawText(&dtParams, (drawTextFlags)(DRAWTEXT_ALIGN_CENTRE), item.first);
dtParams.yBegin += menuItemHeight;
}
// key legend
fontInfo.setSize(18, 18);
dtParams.xBegin = 32;
dtParams.yBegin = 720 - 40;
dtParams.xCurrent = dtParams.xBegin;
dtParams.yCurrent = dtParams.yBegin;
dtParams.colour = 0xffffffff;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L"L/R:Move ");
if (menuSet != MENUSET_MAIN)
{
dtParams.colour = 0xff40ff40;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L"△");
dtParams.colour = 0xffffffff;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L":Back ");
}
dtParams.colour = 0xffc0c0ff;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L"□");
dtParams.colour = 0xffffffff;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L":Hide Menu ");
dtParams.colour = 0xffff8020;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L"×");
dtParams.colour = 0xffffffff;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L":Close ");
dtParams.colour = 0xff4040ff;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L"○");
dtParams.colour = 0xffffffff;
drawTextW(&dtParams, (drawTextFlags)(DRAWTEXT_ENABLE_LAYOUT), L":Select");
}
}
bool Pause::isPauseKeyTapped()
{
return inputState->Tapped.Buttons & JVS_START;
}
bool Pause::hookedGiveUpFunc(void* cls)
{
if (giveUp)
{
giveUp = false;
pause = false;
return true;
}
else
{
if (divaGiveUpFunc(cls))
{
pause = false;
return true;
}
}
return false;
}
void Pause::setSEVolume(int amount)
{
playerData->act_vol += amount;
if (playerData->act_vol < 0) playerData->act_vol = 0;
if (playerData->act_vol > 100) playerData->act_vol = 100;
playerData->act_slide_vol = playerData->act_vol;
}
void Pause::InjectCode(void* address, const std::vector<uint8_t> data)
{
const size_t byteCount = data.size() * sizeof(uint8_t);
DWORD oldProtect;
VirtualProtect(address, byteCount, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(address, data.data(), byteCount);
VirtualProtect(address, byteCount, oldProtect, nullptr);
}
}