diff --git a/source-code/data/plugins/components.ini b/source-code/data/plugins/components.ini index 369be77..6c30c94 100644 --- a/source-code/data/plugins/components.ini +++ b/source-code/data/plugins/components.ini @@ -51,3 +51,6 @@ target_inspector = true # saves high scores to plugins/scores.ini score_saver = true + +# adds a pause menu +pause = true diff --git a/source-code/source/plugins/Launcher/framework.h b/source-code/source/plugins/Launcher/framework.h index dce686a..9c13be6 100644 --- a/source-code/source/plugins/Launcher/framework.h +++ b/source-code/source/plugins/Launcher/framework.h @@ -247,6 +247,8 @@ ConfigOptionBase* componentsArray[] = { new BooleanOption(L"target_inspector", COMPONENTS_SECTION, COMPONENTS_FILE, L"Target Inspector", L"Enables hold transfers.", false, true), new BooleanOption(L"score_saver", COMPONENTS_SECTION, COMPONENTS_FILE, L"Score Saver", L"Saves high scores to plugins/scores.ini.", false, true), + + new BooleanOption(L"pause", COMPONENTS_SECTION, COMPONENTS_FILE, L"Pause", L"Adds a pause menu.", false, true), }; bool IsLineInFile(LPCSTR searchLine, LPCWSTR fileName) diff --git a/source-code/source/plugins/ShaderPatch/src/dllmain.cpp b/source-code/source/plugins/ShaderPatch/src/dllmain.cpp index e82d648..c5a37f8 100644 --- a/source-code/source/plugins/ShaderPatch/src/dllmain.cpp +++ b/source-code/source/plugins/ShaderPatch/src/dllmain.cpp @@ -213,7 +213,7 @@ void hookedLoadFromFarcThunk(FArchivedFile** ppFile) if (modifiedStr.length() > 0) { - free(file.data); + //free(file.data); file.data = _strdup(modifiedStr.c_str()); file.dataSize = modifiedStr.size(); printf("[ShaderPatch] Patched %s\n", file.fileName.GetCharBuf()); diff --git a/source-code/source/plugins/TLAC/Components/ComponentsManager.cpp b/source-code/source/plugins/TLAC/Components/ComponentsManager.cpp index a5599c8..c505d9c 100644 --- a/source-code/source/plugins/TLAC/Components/ComponentsManager.cpp +++ b/source-code/source/plugins/TLAC/Components/ComponentsManager.cpp @@ -12,6 +12,7 @@ #include "DebugComponent.h" #include "ScaleComponent.h" #include "ScoreSaver.h" +#include "Pause.h" #include "GameTargets/TargetInspector.h" using ConfigFile = TLAC::FileSystem::ConfigFile; @@ -36,6 +37,7 @@ namespace TLAC::Components new InputEmulator(), new TouchSliderEmulator(), new TouchPanelEmulator(), + new Pause(), // ensure pause is always immediately after input emulators so it can swallow inputs new PlayerDataManager(), new FrameRateManager(), new FastLoader(), @@ -133,6 +135,22 @@ namespace TLAC::Components component->UpdateInput(); } + void ComponentsManager::UpdatePostInput() + { + for (auto& component : components) + { + component->UpdatePostInput(); + } + } + + void ComponentsManager::UpdateDraw2D() + { + for (auto& component : components) + { + component->UpdateDraw2D(); + } + } + void ComponentsManager::OnFocusGain() { for (auto& component : components) diff --git a/source-code/source/plugins/TLAC/Components/ComponentsManager.h b/source-code/source/plugins/TLAC/Components/ComponentsManager.h index 0c20c0e..ce58b37 100644 --- a/source-code/source/plugins/TLAC/Components/ComponentsManager.h +++ b/source-code/source/plugins/TLAC/Components/ComponentsManager.h @@ -27,6 +27,8 @@ namespace TLAC::Components void Initialize(); void Update(); void UpdateInput(); + void UpdatePostInput(); + void UpdateDraw2D(); void OnFocusGain(); void OnFocusLost(); void Dispose(); diff --git a/source-code/source/plugins/TLAC/Components/EmulatorComponent.h b/source-code/source/plugins/TLAC/Components/EmulatorComponent.h index 48b6175..c486283 100644 --- a/source-code/source/plugins/TLAC/Components/EmulatorComponent.h +++ b/source-code/source/plugins/TLAC/Components/EmulatorComponent.h @@ -16,6 +16,8 @@ namespace TLAC::Components virtual void Update() = 0; virtual void UpdateInput() {}; + virtual void UpdatePostInput() {}; + virtual void UpdateDraw2D() {}; virtual void OnFocusGain() {}; virtual void OnFocusLost() {}; diff --git a/source-code/source/plugins/TLAC/Components/Input/InputEmulator.cpp b/source-code/source/plugins/TLAC/Components/Input/InputEmulator.cpp index a26b38a..96af78b 100644 --- a/source-code/source/plugins/TLAC/Components/Input/InputEmulator.cpp +++ b/source-code/source/plugins/TLAC/Components/Input/InputEmulator.cpp @@ -13,6 +13,7 @@ #include "../../Utilities/EnumBitwiseOperations.h" #include "../../FileSystem/ConfigFile.h" #include "../GameState.h" +#include "../Pause.h" const std::string KEY_CONFIG_FILE_NAME = "keyconfig.ini"; @@ -138,6 +139,8 @@ namespace TLAC::Components UpdateSliderLR(); } + SetMetaButtons(); + UpdateHoldState(); heldButtons = GetButtonFromHold(); @@ -236,13 +239,13 @@ namespace TLAC::Components { JvsButtons buttons = JVS_NONE; - if (!(*(GameState*)CURRENT_GAME_STATE_ADDRESS == GS_GAME && *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_GAME_MAIN) && + if ((Pause::pause || !(*(GameState*)CURRENT_GAME_STATE_ADDRESS == GS_GAME && *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_GAME_MAIN)) && buttonTestFunc(MenuLBinding)) { buttons |= JVS_L; return buttons; } - if (!(*(GameState*)CURRENT_GAME_STATE_ADDRESS == GS_GAME && *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_GAME_MAIN) && + if ((Pause::pause || !(*(GameState*)CURRENT_GAME_STATE_ADDRESS == GS_GAME && *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_GAME_MAIN)) && buttonTestFunc(MenuRBinding)) { buttons |= JVS_R; @@ -370,4 +373,26 @@ namespace TLAC::Components else inputState->Down.Buttons &= ~JVS_R; } + + void InputEmulator::SetMetaButtons() + { + // bit 0x6e is used to skip a bunch of screens + if ((inputState->Down.Buttons & (JVS_L | JVS_R)) == 0) // ask sega okay? idk why this is needed + { + if ((inputState->Tapped.Buttons & (JVS_START | JVS_TRIANGLE | JVS_SQUARE | JVS_CROSS | JVS_CIRCLE)) != 0) + inputState->SetBit(0x6e, true, InputBufferType_Tapped); + else + inputState->SetBit(0x6e, false, InputBufferType_Tapped); + + if ((inputState->Down.Buttons & (JVS_START | JVS_TRIANGLE | JVS_SQUARE | JVS_CROSS | JVS_CIRCLE)) != 0) + inputState->SetBit(0x6e, true, InputBufferType_Down); + else + inputState->SetBit(0x6e, false, InputBufferType_Down); + + if ((inputState->Released.Buttons & (JVS_START | JVS_TRIANGLE | JVS_SQUARE | JVS_CROSS | JVS_CIRCLE)) != 0) + inputState->SetBit(0x6e, true, InputBufferType_Released); + else + inputState->SetBit(0x6e, false, InputBufferType_Released); + } + } } \ No newline at end of file diff --git a/source-code/source/plugins/TLAC/Components/Input/InputEmulator.h b/source-code/source/plugins/TLAC/Components/Input/InputEmulator.h index b795293..309dfd9 100644 --- a/source-code/source/plugins/TLAC/Components/Input/InputEmulator.h +++ b/source-code/source/plugins/TLAC/Components/Input/InputEmulator.h @@ -102,5 +102,6 @@ namespace TLAC::Components void UpdateInputBit(uint32_t bit, uint8_t keycode); void UpdateSliderLR(); + void SetMetaButtons(); }; } diff --git a/source-code/source/plugins/TLAC/Components/Pause.cpp b/source-code/source/plugins/TLAC/Components/Pause.cpp new file mode 100644 index 0000000..c897548 --- /dev/null +++ b/source-code/source/plugins/TLAC/Components/Pause.cpp @@ -0,0 +1,616 @@ +#include "Pause.h" +#include "../Constants.h" +#include "GameState.h" +#include "ComponentsManager.h" +#include "../Utilities/Drawing.h" +#include "GL/glut.h" +#include "detours.h" +#include +#include +#include + +namespace TLAC::Components +{ + using TLAC::Utilities::Drawing; + bool Pause::pause = false; + bool Pause::isPaused = false; + bool Pause::giveUp = false; + bool Pause::showUI = true; + int Pause::selResultAet1 = 0; + int Pause::selResultAet2 = 0; + int Pause::selResultAet3 = 0; + int Pause::selResultAet4 = 0; + int Pause::triangleAet = 0; + int Pause::squareAet = 0; + int Pause::crossAet = 0; + int Pause::circleAet = 0; + int Pause::curMenuPos = 0; + int Pause::mainMenuPos = 0; + Pause::menusets Pause::curMenuSet = MENUSET_MAIN; + std::chrono::time_point Pause::menuItemMoveTime; + std::vector Pause::origAetMovOp; + uint8_t* Pause::aetMovPatchAddress = (uint8_t*)0x1401703b3; + std::vector Pause::origFramespeedOp; + uint8_t* Pause::framespeedPatchAddress = (uint8_t*)0x140192D50; + std::vector Pause::streamPlayStates; + bool(*divaGiveUpFunc)(void*) = (bool(*)(void* cls))GIVEUP_FUNC_ADDRESS; + PlayerData* Pause::playerData; + InputState* Pause::inputState; + TouchSliderState* Pause::sliderState; + TouchPanelState* Pause::panelState; + ComponentsManager* Pause::componentsManager; + JvsButtons Pause::filteredButtons; + int Pause::lastTouchType = 0; + std::vector Pause::menu = { + { + "PAUSED", + { + { "RESUME", unpause, false }, + //{ "RESTART", restart, false }, + { "SE VOLUME", sevolmenu, false }, + { "GIVE UP", giveup, false }, + } + }, + { + "SE VOLUME", + { + { "+", sevolplus, true }, + { "XX", mainmenu, false }, + { "-", sevolminus, true }, + } + }, + }; + + 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* manager) + { + inputState = (InputState*)(*(uint64_t*)INPUT_STATE_PTR_ADDRESS); + playerData = (PlayerData*)PLAYER_DATA_ADDRESS; + sliderState = (TouchSliderState*)SLIDER_CTRL_TASK_ADDRESS; + panelState = (TouchPanelState*)TASK_TOUCH_ADDRESS; + componentsManager = manager; + + 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; + } + + // block all buttons from being passed to game + filteredButtons = allButtons; + + setMenuPos(MENUSET_MAIN, 0); + showUI = true; + isPaused = true; + } + + // always exit pause if key is tapped or no longer in game somehow + if (isPauseKeyTapped() || !isInGame()) + { + pause = false; + } + else + { + if (inputState->Tapped.Buttons & JVS_SQUARE) + { + showUI = !showUI; + if (showUI) + { + menuItemMoveTime = std::chrono::high_resolution_clock::now(); // restart animations + } + else + { + // clear these from the screen too + /* + Drawing::destroyAetLayer(selResultAet1); + Drawing::destroyAetLayer(selResultAet2); + Drawing::destroyAetLayer(selResultAet3); + Drawing::destroyAetLayer(selResultAet4); + */ + Drawing::destroyAetLayer(triangleAet); + Drawing::destroyAetLayer(squareAet); + Drawing::destroyAetLayer(crossAet); + Drawing::destroyAetLayer(circleAet); + } + } + + // only process menu events when UI is visible + if (showUI) + { + if (inputState->Tapped.Buttons & JVS_L) + setMenuPos(curMenuSet, curMenuPos - 1); + + if (inputState->Tapped.Buttons & JVS_R) + setMenuPos(curMenuSet, curMenuPos + 1); + + if (inputState->Tapped.Buttons & JVS_CROSS) + { + if (curMenuSet == MENUSET_MAIN) + { + pause = false; + } + else + { + setMenuPos(MENUSET_MAIN, mainMenuPos); + } + } + + if (inputState->Tapped.Buttons & JVS_CIRCLE) + menu[curMenuSet].items[curMenuPos].callback(); + + if (!componentsManager->IsDwGuiActive()) // not sure if this check is necessary, but it doesn't hurt + { + if (panelState->ContactType == 2 && lastTouchType != 2) // down and was not down before + { + for (int i = 0; i < menu[curMenuSet].items.size(); i++) + { + Drawing::Point itemCoords = getMenuItemCoords(curMenuSet, i); + + Drawing::Point touchCoords = { panelState->XPosition, panelState->YPosition }; + touchCoords.x *= 1280.0f / *(int*)RESOLUTION_WIDTH_ADDRESS; // convert to 720p coords + touchCoords.y *= 720.0f / *(int*)RESOLUTION_HEIGHT_ADDRESS; + + if (touchCoords.x >= itemCoords.x - menuItemWidth / 2 && + touchCoords.x <= itemCoords.x + menuItemWidth / 2 && + touchCoords.y >= itemCoords.y - menuItemHeight / 2 && + touchCoords.y <= itemCoords.y + menuItemHeight / 2) + { + setMenuPos(curMenuSet, i); + menu[curMenuSet].items[i].callback(); + break; + } + } + } + } + + if (curMenuSet == 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); + menu[MENUSET_SEVOL].items[1].name = buf; + delete[] buf; + } + } + } + + // no slider while paused + // this is simpler than buttons because slider doesn't trigger on press, it triggers on movement + // (therefore per-button blocking isn't needed) + sliderState->ResetSensors(); + } + 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]; + } + + /* + Drawing::destroyAetLayer(selResultAet1); + Drawing::destroyAetLayer(selResultAet2); + Drawing::destroyAetLayer(selResultAet3); + Drawing::destroyAetLayer(selResultAet4); + */ + Drawing::destroyAetLayer(triangleAet); + Drawing::destroyAetLayer(squareAet); + Drawing::destroyAetLayer(crossAet); + Drawing::destroyAetLayer(circleAet); + + isPaused = false; + } + + // buttons that have been tapped no longer need to be filtered (doing this with down broke retriggering) + filteredButtons = (JvsButtons)(filteredButtons & ~inputState->Tapped.Buttons); + + // only enter pause if in game + if (isInGame()) + { + if (isPauseKeyTapped()) + { + pause = true; + } + } + } + + // swallow filtered button inputs + inputState->Tapped.Buttons = (JvsButtons)(inputState->Tapped.Buttons & ~filteredButtons); + inputState->DoubleTapped.Buttons = (JvsButtons)(inputState->DoubleTapped.Buttons & ~filteredButtons); + inputState->Down.Buttons = (JvsButtons)(inputState->Down.Buttons & ~filteredButtons); + inputState->Released.Buttons = (JvsButtons)(inputState->Released.Buttons & ~filteredButtons); + inputState->IntervalTapped.Buttons = (JvsButtons)(inputState->IntervalTapped.Buttons & ~filteredButtons); + + lastTouchType = panelState->ContactType; + } + + // returns the midpoint of a menu button + Drawing::Point Pause::getMenuItemCoords(menusets set, int pos) + { + const float slant = 35.0f / 199.0f; + + int menuOriginY = menuY - (menuItemTotalHeight * menu[set].items.size() - menuItemPadding) / 2 + menuItemHeight / 2; + if (set != MENUSET_MAIN) + { + menuOriginY += menuItemTotalHeight * 1.2 / 2; + } + int menuOriginX = menuX + (menuY - menuOriginY) * slant; + + Drawing::Point out; + out.x = menuOriginX - (menuItemTotalHeight * pos * slant); + out.y = menuOriginY + menuItemTotalHeight * pos; + + return out; + } + + void Pause::UpdateDraw2D() + { + if (isPaused && showUI) + { + // setup draw objects + Drawing::FontInfo fontInfo(0x11); + Drawing::DrawParams dtParams(&fontInfo); + dtParams.layer = bgLayer; + Drawing::Point aetScale = { 0.65, 0.65 }; + Drawing::Point aetLoc = { (1280 - aetScale.x * 1280) / 2, (720 - aetScale.y * 720) / 2 }; + + // get aspect ratio + float aspect = *(float*)UI_ASPECT_RATIO; + + + // bg rect + float bgWidth = aspect * 720 + 2; // add a couple of pixels to protect against rounding errors + float bgLeft = -(bgWidth - 1280) / 2; // 0,0 is in the corner of a 720p view.. half of the extra over 1280 wide is the horizontal offset to centre the bg + Drawing::RectangleBounds rect; + rect = { bgLeft, 0, bgWidth, 720 }; + dtParams.colour = 0xc0000000; + dtParams.fillColour = dtParams.colour; + Drawing::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 }; + Drawing::fillRectangle(&dtParams, &rect); + rect = { pauseX2, pauseY1, pausePartWidth, pauseHeight }; + Drawing::fillRectangle(&dtParams, &rect); + */ + + + dtParams.layer = contentLayer; + + // bg box + /* + static int selResultFile = 0; + if (selResultFile == 0) + { + //keyWinFile = Drawing::findAetFileId("AET_KEY_WIN_MAIN"); + selResultFile = Drawing::findAetFileId("AET_SEL_RESULT_MAIN"); + //printf("keyWinFile: %d\n", keyWinFile); + } + + Drawing::destroyAetLayer(selResultAet1); + Drawing::destroyAetLayer(selResultAet2); + Drawing::destroyAetLayer(selResultAet3); + Drawing::destroyAetLayer(selResultAet4); + if (selResultFile != -1) + { + //keyWinAet = Drawing::createAetLayer(keyWinFile, dtParams.layer, CREATEAET_20000, "win_in", aetLoc, 0, nullptr, nullptr, 3.7, 3.7, aetScale, 0); + aetScale = { 0.47f, 0.47f }; + aetLoc = { (1280 - aetScale.x * 1280) / 2, (720 - aetScale.y * 720) / 2 + 72 * aetScale.y }; + float animPos = 7.2; + selResultAet1 = Drawing::createAetLayer(selResultFile, dtParams.layer, CREATEAET_20000, "window_in", aetLoc, 0, nullptr, nullptr, animPos, animPos, aetScale, 0); + selResultAet2 = Drawing::createAetLayer(selResultFile, dtParams.layer, CREATEAET_20000, "window_in", aetLoc, 0, nullptr, nullptr, animPos, animPos, aetScale, 0); + selResultAet3 = Drawing::createAetLayer(selResultFile, dtParams.layer, CREATEAET_20000, "window_in", aetLoc, 0, nullptr, nullptr, animPos, animPos, aetScale, 0); + selResultAet4 = Drawing::createAetLayer(selResultFile, dtParams.layer, CREATEAET_20000, "window_in", aetLoc, 0, nullptr, nullptr, animPos, animPos, aetScale, 0); + } + */ + + dtParams.colour = 0xffffff00; + Drawing::drawPolyline(&dtParams, { + { 537, 255 }, + { 794, 255 }, + { 759, 454 }, + { 744, 466 }, + { 487, 466 }, + { 522, 267 }, + { 537, 255 }, + }); + Drawing::drawPolyline(&dtParams, { + { 537 - .7f, 255 - 2 }, + { 794 + 2.38f, 255 - 2 }, + { 759 + 2.1f, 454 + 1.15f }, + { 744 + .7f, 466 + 2 }, + { 487 - 2.38f, 466 + 2 }, + { 522 - 2.1f, 267 - 1.15f }, + { 537 - .7f, 255 - 2 }, + }); + Drawing::drawPolyline(&dtParams, { + { 548, 258 }, + { 768, 258 }, + { 774, 265 }, + { 770, 286 }, + { 764, 286 }, + { 737, 435 }, + { 743, 435 }, + { 739, 456 }, + { 733, 463 }, + { 513, 463 }, + { 507, 456 }, + { 511, 435 }, + { 517, 435 }, + { 544, 286 }, + { 538, 286 }, + { 542, 265 }, + { 548, 258 }, + }); + + + Drawing::Point menuCoords = getMenuItemCoords(curMenuSet, curMenuPos); + // selection cursor + const float selectBoxWidth = menuItemWidth; + const float selectBoxHeight = menuItemHeight; + const float selectBoxThickness = 2; + + float selectBoxX = menuCoords.x - selectBoxWidth / 2; + float selectBoxY = menuCoords.y - menuItemHeight / 2; + + dtParams.colour = 0xc0ffff00; + dtParams.fillColour = 0xc0ffff00; + //rect = { selectBoxX, selectBoxY, selectBoxWidth, selectBoxHeight }; + //Drawing::drawRectangle(&dtParams, rect, selectBoxThickness); + + const float slant = 35.0f / 199.0f; + Drawing::drawPolyline(&dtParams, { + { selectBoxX + (selectBoxHeight - 8) * slant + 8 * 15 / 12, selectBoxY }, + { selectBoxX + selectBoxWidth, selectBoxY }, + { selectBoxX + selectBoxWidth - (selectBoxHeight - 8) * slant, selectBoxY + selectBoxHeight - 8 }, + { selectBoxX + selectBoxWidth - (selectBoxHeight - 8) * slant - 8 * 15 / 12, selectBoxY + selectBoxHeight }, + { selectBoxX, selectBoxY + selectBoxHeight }, + { selectBoxX + (selectBoxHeight - 8) * slant, selectBoxY + 8 }, + { selectBoxX + (selectBoxHeight - 8) * slant + 8 * 15 / 12, selectBoxY }, + }); + Drawing::drawPolyline(&dtParams, { + { selectBoxX + (selectBoxHeight - 8) * slant + 8 * 15 / 12 - .7f, selectBoxY - 2 }, + { selectBoxX + selectBoxWidth + 2.38f, selectBoxY - 2 }, + { selectBoxX + selectBoxWidth - (selectBoxHeight - 8) * slant + 2.1f, selectBoxY + selectBoxHeight - 8 + 1.15f }, + { selectBoxX + selectBoxWidth - (selectBoxHeight - 8) * slant - 8 * 15 / 12 + .7f, selectBoxY + selectBoxHeight + 2 }, + { selectBoxX - 2.38f, selectBoxY + selectBoxHeight + 2 }, + { selectBoxX + (selectBoxHeight - 8) * slant - 2.1f, selectBoxY + 8 - 1.15f }, + { selectBoxX + (selectBoxHeight - 8) * slant + 8 * 15 / 12 - .7f, selectBoxY - 2 }, + }); + + + // menu + fontInfo.setSize(menuTextSize, menuTextSize); + + if (curMenuSet != MENUSET_MAIN) + { + dtParams.textCurrentLoc = { menuX + 90 * slant, menuY - 90 }; + dtParams.lineOriginLoc.y = dtParams.textCurrentLoc.y; + dtParams.colour = 0xffffffff; + Drawing::drawText(&dtParams, (Drawing::drawTextFlags)(Drawing::DRAWTEXT_ALIGN_CENTRE | Drawing::DRAWTEXT_STROKE), menu[curMenuSet].name); + } + + for (int i = 0; i < menu[curMenuSet].items.size(); i++) + { + menuCoords = getMenuItemCoords(curMenuSet, i); + dtParams.textCurrentLoc = { menuCoords.x, menuCoords.y - menuTextSize / 2 }; + dtParams.lineOriginLoc = dtParams.textCurrentLoc; + if (i == curMenuPos) + { + uint8_t alpha = (cosf(getMenuAnimPos() * 6.283185f) * 0.15 + 0.85) * 255; + dtParams.colour = 0x00ffff00 | (alpha << 24); + } + else + { + dtParams.colour = 0xffffffff; + } + + Drawing::drawText(&dtParams, (Drawing::drawTextFlags)(Drawing::DRAWTEXT_ALIGN_CENTRE), menu[curMenuSet].items[i].name); + } + + + // key legend + Drawing::destroyAetLayer(triangleAet); + Drawing::destroyAetLayer(squareAet); + Drawing::destroyAetLayer(crossAet); + Drawing::destroyAetLayer(circleAet); + + float textLeft; + if (aspect > 16.0f / 9.0f) + textLeft = 32; + else + textLeft = (1280 - bgWidth) / 2 + 32; // 0,0 is in the corner of a 720p view.. half of the difference to 1280 wide is the horizontal offset to the window corner + + fontInfo.setSize(18, 18); + dtParams.textCurrentLoc = { textLeft, 720 - 40 }; + dtParams.lineOriginLoc = dtParams.textCurrentLoc; + const float spriteSize = 18; + const float halfSpriteSize = spriteSize / 2; + aetLoc = { 0, dtParams.textCurrentLoc.y + halfSpriteSize }; // the aets are centered on their location, so fudge this a little + aetScale = { spriteSize / 64, spriteSize / 64 }; // just approximated + + dtParams.colour = 0xffffffff; + Drawing::drawTextW(&dtParams, (Drawing::drawTextFlags)(Drawing::DRAWTEXT_ENABLE_XADVANCE), L"↑↓/LR:Move "); + + aetLoc.x = dtParams.textCurrentLoc.x + halfSpriteSize; + squareAet = Drawing::createAetLayer(3, dtParams.layer, Drawing::CREATEAET_20000, "button_shikaku", aetLoc, 0, nullptr, nullptr, 0, 0, aetScale, nullptr); + dtParams.textCurrentLoc.x += spriteSize; + Drawing::drawTextW(&dtParams, (Drawing::drawTextFlags)(Drawing::DRAWTEXT_ENABLE_XADVANCE), L":Hide Menu "); + + aetLoc.x = dtParams.textCurrentLoc.x + halfSpriteSize; + crossAet = Drawing::createAetLayer(3, dtParams.layer, Drawing::CREATEAET_20000, "button_batsu", aetLoc, 0, nullptr, nullptr, 0, 0, aetScale, nullptr); + dtParams.textCurrentLoc.x += spriteSize; + if (curMenuSet == MENUSET_MAIN) + Drawing::drawTextW(&dtParams, (Drawing::drawTextFlags)(Drawing::DRAWTEXT_ENABLE_XADVANCE), L":Close "); + else + Drawing::drawTextW(&dtParams, (Drawing::drawTextFlags)(Drawing::DRAWTEXT_ENABLE_XADVANCE), L":Back "); + + aetLoc.x = dtParams.textCurrentLoc.x + halfSpriteSize; + circleAet = Drawing::createAetLayer(3, dtParams.layer, Drawing::CREATEAET_20000, "button_maru", aetLoc, 0, nullptr, nullptr, 0, 0, aetScale, nullptr); + dtParams.textCurrentLoc.x += spriteSize; + Drawing::drawTextW(&dtParams, (Drawing::drawTextFlags)(Drawing::DRAWTEXT_ENABLE_XADVANCE), L":Select"); + } + } + + void Pause::OnFocusLost() + { + if (isInGame()) + pause = true; + } + + bool Pause::isPauseKeyTapped() + { + return inputState->Tapped.Buttons & JVS_START; + } + + bool Pause::isInGame() + { + return *(GameState*)CURRENT_GAME_STATE_ADDRESS == GS_GAME && *(SubGameState*)CURRENT_GAME_SUB_STATE_ADDRESS == SUB_GAME_MAIN; + } + + 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::setMenuPos(menusets set, int pos) + { + if (set >= 0 && set < menu.size()) + curMenuSet = set; + else + curMenuSet = MENUSET_MAIN; + + if (pos < 0) + pos = menu[curMenuSet].items.size() - 1; + else if (pos >= menu[curMenuSet].items.size()) + pos = 0; + + curMenuPos = pos; + + if (curMenuSet == MENUSET_MAIN) + mainMenuPos = curMenuPos; + + menuItemMoveTime = std::chrono::high_resolution_clock::now(); // restart animations + } + + float Pause::getMenuAnimPos() + { + const int duration = 1500000000; // 1.5s + return (float)((std::chrono::high_resolution_clock::now() - menuItemMoveTime) % std::chrono::nanoseconds(duration)).count() / (float)duration; + } + + void Pause::InjectCode(void* address, const std::vector 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); + } +} diff --git a/source-code/source/plugins/TLAC/Components/Pause.h b/source-code/source/plugins/TLAC/Components/Pause.h new file mode 100644 index 0000000..66afc4f --- /dev/null +++ b/source-code/source/plugins/TLAC/Components/Pause.h @@ -0,0 +1,135 @@ +#pragma once +#include "../Constants.h" +#include "EmulatorComponent.h" +#include "PlayerData.h" +#include "GameState.h" +#include "../Utilities/Drawing.h" +#include "Input/InputState.h" +#include "Input/TouchSliderState.h" +#include "Input/TouchPanelState.h" +#include +#include +#include + +namespace TLAC::Components +{ + class Pause : public EmulatorComponent + { + public: + Pause(); + ~Pause(); + + virtual const char* GetDisplayName() override; + + virtual void Initialize(ComponentsManager*) override; + virtual void Update() override; + virtual void UpdatePostInput() override; + virtual void UpdateDraw2D() override; + virtual void OnFocusLost() override; + + static bool pause; // set pause to change pause state + static bool giveUp; // set give up to end current song + private: + // this is a mess of static so that menuItems can work + static bool isPauseKeyTapped(); + static bool isInGame(); + static std::vector streamPlayStates; + static void InjectCode(void* address, const std::vector data); + + static bool isPaused; // tracks internal state + + static void saveOldPatchOps(); + + static std::vector origAetMovOp; + static uint8_t* aetMovPatchAddress; + + static std::vector origFramespeedOp; + static uint8_t* framespeedPatchAddress; + + static bool hookedGiveUpFunc(void* cls); + + static void setSEVolume(int amount); + + static PlayerData* playerData; + static InputState* inputState; + static TouchSliderState* sliderState; + static TouchPanelState* panelState; + static ComponentsManager* componentsManager; + + static const JvsButtons allButtons = (JvsButtons)(JVS_START | JVS_TRIANGLE | JVS_SQUARE | JVS_CROSS | JVS_CIRCLE | JVS_L | JVS_R); // deliberately only has control panel buttons + static JvsButtons filteredButtons; + static int lastTouchType; + + static const int menuX = 640; + static const int menuY = 360; + static const int menuItemWidth = 150; + static const int menuItemHeight = 36; + static const int menuItemPadding = 12; + static const int menuItemTotalHeight = menuItemHeight + menuItemPadding; + static const int menuTextSize = 24; + + static const uint32_t bgLayer = 0x18; + static const uint32_t contentLayer = 0x19; // same as startup screen + + static bool showUI; + + static int selResultAet1; + static int selResultAet2; + static int selResultAet3; + static int selResultAet4; + static int triangleAet; + static int squareAet; + static int crossAet; + static int circleAet; + + enum menusets + { + MENUSET_MAIN = 0, + MENUSET_SEVOL = 1, + }; + + struct menuItem + { + std::string name; + void(*callback)(); + bool keyRepeat; // unimplemented + + menuItem(std::string _name, void(*_callback)(), bool _keyRepeat) + { + name = _name; + callback = _callback; + keyRepeat = _keyRepeat; + } + }; + + struct menuSet + { + std::string name; + std::vector items; + }; + + static int curMenuPos; + static int mainMenuPos; // syncs to curMenuPos when curMenuSet == MENUSET_MAIN (used for restoring on back) + static menusets curMenuSet; + + static std::chrono::time_point menuItemMoveTime; // used for animation + + static void mainmenu() { setMenuPos(MENUSET_MAIN, mainMenuPos); }; + static void unpause() { pause = false; }; + //static void restart() { *(uint8_t*)0x140d0b512 = 2; ((void(*)(uint64_t))0x140127a30)(0x140d0b510); ((void(*)(uint64_t))0x140674e20)(PV_GAME_BASE_ADDRESS); /* *(uint8_t*)0x140d0b512 = 1; *(uint8_t*)0x140d0b513 = 1; *(int32_t*)0x140d0b534 = 0; ((void(*)(uint64_t))0x140127a30)(0x140d0b510); */ unpause(); } + static void giveup() { giveUp = true; }; + + static void sevolmenu() { setMenuPos(MENUSET_SEVOL, 1); }; + static void sevolplus() { setSEVolume(10); }; + static void sevolminus() { setSEVolume(-10); }; + + // contents are in Pause.cpp because they can't be inline here for a static (const) array/vec + static std::vector menu; + + static void setMenuPos(menusets set, int pos); + + static float getMenuAnimPos(); + + static TLAC::Utilities::Drawing::Point getMenuItemCoords(menusets set, int pos); + }; +} diff --git a/source-code/source/plugins/TLAC/Constants.h b/source-code/source/plugins/TLAC/Constants.h index d5ce191..91c2c89 100644 --- a/source-code/source/plugins/TLAC/Constants.h +++ b/source-code/source/plugins/TLAC/Constants.h @@ -8,6 +8,8 @@ constexpr uint8_t JNE_OPCODE = 0x85; //constexpr uint64_t ENGINE_UPDATE_HOOK_TARGET_ADDRESS = 0x000000014018CC40; constexpr uint64_t ENGINE_UPDATE_INPUT_ADDRESS = 0x000000014018CBB0; +constexpr uint64_t ENGINE_DRAW_2D_ADDRESS = 0x0000000140501F70; +constexpr uint64_t ENGINE_FINISH_DRAW_ADDRESS = 0x0000000140192730; constexpr uint64_t CURRENT_GAME_STATE_ADDRESS = 0x0000000140EDA810; constexpr uint64_t CURRENT_GAME_SUB_STATE_ADDRESS = 0x0000000140EDA82C; @@ -97,6 +99,25 @@ constexpr uint64_t CURRENT_SONG_ID_ADDRESS = 0x0000000140CDD8E0; constexpr uint64_t SELPV_CURRENT_SONG_ADDRESS = 0x000000014CC12438; constexpr uint64_t SONG_CLEAR_COUNTS_ADDRESS = 0x00000001411A95E8; +constexpr uint64_t AUDIO_MAIN_CLASS_ADDRESS = 0x000000014CC61120; + +constexpr uint64_t DSC_TIME_ADDRESS = 0x0000000140D0B518; +constexpr uint64_t DSC_START_FROM_TIME_ADDRESS = 0x0000000140D0B540; +constexpr uint64_t DSC_START_SYS_TIME_ADDRESS = 0x0000000140D0B548; +constexpr uint64_t DSC_TIME_ADDRESS_2 = 0x0000000140D0B520; +constexpr uint64_t DSC_START_FROM_TIME_ADDRESS_2 = 0x0000000140D0B560; +constexpr uint64_t DSC_START_SYS_TIME_ADDRESS_2 = 0x0000000140D0B568; + +constexpr uint64_t DSC_PAUSE_FUNC_ADDRESS = 0x00000001401295C0; +constexpr uint64_t DSC_UNPAUSE_FUNC_ADDRESS = 0x0000000140129590; + +constexpr uint64_t PV_GAME_BASE_ADDRESS = 0x000000014CC95270; +constexpr uint64_t GIVEUP_FUNC_ADDRESS = 0x000000014010EF00; + +constexpr uint64_t AET_DEBUG_ARRAY_POINTER_ADDRESS = 0x0000000140EC5430 + 0x98; +constexpr uint64_t AET_ARRAY_POINTER_ADDRESS = 0x0000000140EC5430 + 0xD8; + + #define XINPUT_A 0x00 #define XINPUT_B 0x01 #define XINPUT_X 0x02 diff --git a/source-code/source/plugins/TLAC/TLAC.vcxproj b/source-code/source/plugins/TLAC/TLAC.vcxproj index 7fa64e0..3fefc80 100644 --- a/source-code/source/plugins/TLAC/TLAC.vcxproj +++ b/source-code/source/plugins/TLAC/TLAC.vcxproj @@ -128,6 +128,7 @@ + @@ -161,6 +162,7 @@ + @@ -181,6 +183,7 @@ + @@ -208,6 +211,7 @@ + diff --git a/source-code/source/plugins/TLAC/TLAC.vcxproj.filters b/source-code/source/plugins/TLAC/TLAC.vcxproj.filters index d95a19b..79143c4 100644 --- a/source-code/source/plugins/TLAC/TLAC.vcxproj.filters +++ b/source-code/source/plugins/TLAC/TLAC.vcxproj.filters @@ -195,6 +195,12 @@ Header Files + + Header Files + + + Header Files + @@ -329,5 +335,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/source-code/source/plugins/TLAC/Utilities/Drawing.cpp b/source-code/source/plugins/TLAC/Utilities/Drawing.cpp new file mode 100644 index 0000000..2df6676 --- /dev/null +++ b/source-code/source/plugins/TLAC/Utilities/Drawing.cpp @@ -0,0 +1,160 @@ +#pragma once +#include +#include +#include +#include "Drawing.h" +#include "../Constants.h" + +namespace TLAC::Utilities +{ +#pragma pack(push, 1) + Drawing::FontInfo*(*Drawing::getFont)(FontInfo* fi, uint32_t id) = (FontInfo*(*)(FontInfo* fi, uint32_t id))0x140196510; + void(*Drawing::fontSize)(FontInfo* fi, float width, float height) = (void(*)(FontInfo* fi, float width, float height))0x140199e60; + + void Drawing::drawText(DrawParams* dtParam, drawTextFlags flags, std::string str) + { + ((void(*)(DrawParams*, uint32_t, const char*, int64_t))0x140198500)(dtParam, flags, str.c_str(), str.length()); + } + void Drawing::drawTextW(DrawParams* dtParam, drawTextFlags flags, std::wstring str) + { + const wchar_t* ptrs[2]; + ptrs[0] = str.c_str(); + ptrs[1] = (wchar_t*)((uint64_t)ptrs[0] + str.length() * 2); + + ((void(*)(DrawParams*, uint32_t, const wchar_t**))0x140198380)(dtParam, flags, &ptrs[0]); + } + + void Drawing::drawTextFormattedW(DrawParams* dtParam, drawTextFlags flags, std::wstring str) + { + const wchar_t* ptrs[2]; + ptrs[0] = str.c_str(); + ptrs[1] = (wchar_t*)((uint64_t)ptrs[0] + str.length() * 2); + + ((void(*)(uint32_t, const wchar_t**, DrawParams*))0x1404c2aa0)(flags, &ptrs[0], dtParam); + } + + void Drawing::drawTextWithSpritesW(DrawParams* dtParam, drawTextFlags flags, std::wstring str) + { + const wchar_t* ptrs[2]; + ptrs[0] = str.c_str(); + ptrs[1] = (wchar_t*)((uint64_t)ptrs[0] + str.length() * 2); + + ((void(*)(uint32_t, const wchar_t**, DrawParams*))0x1404c2cf0)(flags, &ptrs[0], dtParam); + } + + + void(*Drawing::fillRectangle)(DrawParams* dtParam, const RectangleBounds &rect) = (void(*)(DrawParams* dtParam, const RectangleBounds &rect))0x140198d80; + + // draws only a border -- use fillRectangle to fill contained pixels + void Drawing::drawRectangle(DrawParams* dtParam, const RectangleBounds &rect) + { + ((void(*)(DrawParams*, const RectangleBounds&))0x140198320)(dtParam, rect); + } + + // draws only a border -- use fillRectangle to fill contained pixels + void Drawing::drawRectangle(DrawParams* dtParam, const RectangleBounds &rect, float thickness) + { + uint32_t oldFillColour = dtParam->fillColour; + dtParam->fillColour = dtParam->colour; + + // yes this seems pretty dodgy, but sega does it this way so... I guess it's the only way + RectangleBounds tempRect = { rect.x, rect.y, thickness, rect.height }; // left side + fillRectangle(dtParam, tempRect); + + tempRect.x = rect.x + rect.width - thickness; // right side + fillRectangle(dtParam, tempRect); + + tempRect = { rect.x + thickness, rect.y, rect.width - (thickness * 2), thickness }; // top side + fillRectangle(dtParam, tempRect); + + tempRect.y = rect.y + rect.height - thickness; // left side + fillRectangle(dtParam, tempRect); + + dtParam->fillColour = oldFillColour; + } + + + void Drawing::drawLine(DrawParams* dtParam, const Point &p1, const Point &p2) + { + ((void(*)(DrawParams*, const RectangleBounds&))0x140198080)(dtParam, { p1.x, p1.y, p2.x, p2.y }); + } + // draw from the top left corner of rect to the bottom left + void Drawing::drawLine(DrawParams* dtParam, const RectangleBounds &rect) + { + drawLine(dtParam, { rect.x, rect.y }, { rect.x + rect.width, rect.y + rect.height }); + } + + void Drawing::drawPolyline(DrawParams* dtParam, const std::vector points) + { + ((void(*)(DrawParams*, const Point*, uint64_t))0x1401980e0)(dtParam, points.data(), points.size()); + } + + /* + int Drawing::findAetDebugFileId(std::string name) + { + AetDebugFileInfo* aetArray = *(AetDebugFileInfo**)AET_DEBUG_ARRAY_POINTER_ADDRESS; + AetDebugFileInfo* aetArrayEndAddress = *(AetDebugFileInfo**)(AET_DEBUG_ARRAY_POINTER_ADDRESS + 0x08); + + int id = 0; + while (&aetArray[id] < aetArrayEndAddress) + { + if (name == aetArray[id].name2.GetCharBuf()) // dwgui enum uses name2, so this should too I guess + return aetArray[id].gameId; + else + id++; + } + return -1; + } + */ + + // gets a file ID for use with createAetLayer + // returns -1 if the file was not found + // note: names are a little different to in 2dauth test -- it seems like they have "_MAIN" appended + int Drawing::findAetFileId(std::string name) + { + AetFileInfo* aetArray = *(AetFileInfo**)AET_ARRAY_POINTER_ADDRESS; + AetFileInfo* aetArrayEndAddress = *(AetFileInfo**)(AET_ARRAY_POINTER_ADDRESS + 0x08); + + int id = 0; + while (&aetArray[id] < aetArrayEndAddress) + { + if (name == aetArray[id].name.GetCharBuf()) // dwgui enum uses name2, so this should too I guess + return aetArray[id].id1; + else + id++; + } + return -1; + } + + // draw an aet layer (with all settings) + // aetSpeedCallback is actually a pointer to a class or struct with the callback address at offset +0x8 + int Drawing::createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc, int32_t unk2, const char* animation, const char* animation2, float animationInTime, float animationOutTime, const Point &scale, const void* aetSpeedCallback) + { + return ((int(*)(int32_t, uint32_t, createAetFlags, const char*, const Point&, int32_t, const char*, const char*, float, float, const Point&, const void*))0x14013be60)(fileId, drawLayer, flags, name, loc, unk2, animation, animation2, animationInTime, animationOutTime, scale, aetSpeedCallback); + } + // draw an aet layer (with animation timing override) + int Drawing::createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc, float animationInTime, float animationOutTime) + { + return createAetLayer(fileId, drawLayer, flags, name, loc, 0, 0, 0, animationInTime, animationOutTime, *(Point*)0, 0); + } + // draw an aet layer (with scale) + int Drawing::createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc, const Point &scale) + { + return createAetLayer(fileId, drawLayer, flags, name, loc, 0, 0, 0, -1, -1, scale, 0); + } + // draw an aet layer + int Drawing::createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc) + { + return createAetLayer(fileId, drawLayer, flags, name, loc, 0, 0, 0, -1, -1, *(Point*)0, 0); + } + + void Drawing::destroyAetLayer(int &layer) + { + if (layer != 0) + { + ((void(*)(int layer))0x14019d570)(layer); + layer = 0; + } + } +#pragma pack(pop) +} \ No newline at end of file diff --git a/source-code/source/plugins/TLAC/Utilities/Drawing.h b/source-code/source/plugins/TLAC/Utilities/Drawing.h new file mode 100644 index 0000000..4f6b5a5 --- /dev/null +++ b/source-code/source/plugins/TLAC/Utilities/Drawing.h @@ -0,0 +1,269 @@ +#pragma once +#include +#include +#include + +namespace TLAC::Utilities +{ + class Drawing + { + public: +#pragma pack(push, 1) + struct Point + { + float x; + float y; + }; + struct RectangleBounds + { + float x; + float y; + float width; + float height; + }; + + struct RawFont + { + uint32_t sprId; // ? + uint8_t width1; // glyph? + uint8_t height1; // glyph? + uint8_t width2; // advance? + uint8_t height2; // advance? + uint8_t metric08; // or flag? + uint8_t metric09; // or flag? + uint8_t padding0a[0x06]; + float metric08divby09; + uint64_t _0x10; + int64_t dataBegin; + int64_t dataEnd; + int64_t dataCapacityEnd; + uint8_t padding38[0x8]; + }; + + struct RawFontHolderIdk // ...SeemsPrettyPointlessTbh + { + RawFont* rawfont; + uint16_t width1; + uint16_t height1; + uint16_t zero1; // ? + uint16_t zero2; // ? + uint8_t zero3; // some kind of flag, but seems unused + }; + + struct FontInfo + { + uint32_t fontId; + uint8_t padding04[0x4]; + RawFont* rawfont; + uint16_t flag10; // (zero3 != 0 && padding38[0] != 0) ? 2 : (metric08 != metric09 ? 1 : 0) + uint8_t padding12[0x02]; + float width1; + float height1; + float width2; + float height2; + float userSizeWidth; + float userSizeHeight; + float userSizeWidthMultiplier; // userSizeWidth / width1 + float userSizeHeightMultiplier; // userSizeHeight / width2 + float zero1; // ? + float zero2; // ? + + void setSize(float width, float height) + { + ((void(*)(FontInfo* fi, float width, float height))0x140199e60)(this, width, height); + } + + FontInfo(uint32_t id) + { + ((FontInfo*(*)(FontInfo* fi, uint32_t id))0x140196510)(this, id); + } + }; + + static FontInfo*(*getFont)(FontInfo* fi, uint32_t id); + static void(*fontSize)(FontInfo* fi, float width, float height); + + struct DrawParams + { + uint32_t colour; // RGBA byte array?, so set as 0xAABBGGRR + uint32_t fillColour; // ?? RGBA byte array?, so set as 0xAABBGGRR + uint8_t clip; + uint8_t unk09[0x3]; + RectangleBounds clipRect; + uint32_t layer; // 8 seems similar to default but higher + // 0x18 is below 0x19 but still seems to be above any game elements + // 0x19 is startup screen (below dwgui) + // I noticed some use different scaling + uint32_t unk20; + uint32_t unk24; + uint32_t unk28; + Point textCurrentLoc; + Point lineOriginLoc; // must be set for newlines to work as expected + uint8_t padding3c[0x4]; + uint64_t lineLength; // in characters + FontInfo* font; + uint16_t unk50; + + DrawParams(FontInfo* fi) + { + colour = 0xffffffff; + fillColour = 0xff808080; // except it's not? + clip = 0; + clipRect = { 0, 0, 0, 0 }; + layer = 0x7; + unk20 = 0x0; + unk24 = 0xd; + unk28 = 0; + textCurrentLoc = { 0, 0 }; + lineOriginLoc = { 0, 0 }; + lineLength = 0; + font = fi; + unk50 = 0x25a1; + } + + DrawParams() + { + DrawParams((FontInfo*)0x140eda860); + } + }; + + enum drawTextFlags : uint32_t + { + DRAWTEXT_ENABLE_XADVANCE = 1, + DRAWTEXT_ALIGN_RIGHT = 2, + DRAWTEXT_ALIGN_SCREEN_CENTRE = 4, + DRAWTEXT_ALIGN_CENTRE = 8, + DRAWTEXT_ENABLE_CLIP = 0x200, + DRAWTEXT_SCALING_OPTIMISED = 0x400, // ? -- seems to be set if the requested font size doesn't match the original font, and the internal width/height 1 and 2 match + // maybe it's just required for any scaling though + DRAWTEXT_STROKE = 0x10000, + }; + + static void drawText(DrawParams* dtParam, drawTextFlags flags, std::string str); + static void drawTextW(DrawParams* dtParam, drawTextFlags flags, std::wstring str); + static void drawTextFormattedW(DrawParams* dtParam, drawTextFlags flags, std::wstring str); + static void drawTextWithSpritesW(DrawParams* dtParam, drawTextFlags flags, std::wstring str); + + static void(*fillRectangle)(DrawParams* dtParam, const RectangleBounds &rect); + // draws only a border -- use fillRectangle to fill contained pixels + static void drawRectangle(DrawParams* dtParam, const RectangleBounds &rect); + // draws only a border -- use fillRectangle to fill contained pixels + static void drawRectangle(DrawParams* dtParam, const RectangleBounds &rect, float thickness); + + static void drawLine(DrawParams* dtParam, const Point &p1, const Point &p2); + // draw from the top left corner of rect to the bottom left + static void drawLine(DrawParams* dtParam, const RectangleBounds &rect); + + static void drawPolyline(DrawParams* dtParam, const std::vector points); + + + struct MsString { + union { + char* string_ptr; + char string_buf[16]; + }; + uint64_t len; + uint64_t bufsize; + + char* GetCharBuf() + { + if (bufsize > 0xf && string_ptr != nullptr) + return string_ptr; + else + return string_buf; + }; + + void SetCharBuf(char* newcontent) + { + len = strlen(newcontent); + bufsize = len; + if (len > 0xf) + { + string_ptr = _strdup(newcontent); + } + else + { + strcpy_s(string_buf, newcontent); + } + } + }; + + /* struct MsStringW { + union { + wchar_t* string_ptr; + wchar_t string_buf[8]; + }; + uint64_t len; + uint64_t bufsize; + + wchar_t* GetCharBuf() + { + if (bufsize > 0x7 && string_ptr != nullptr) + return string_ptr; + else + return string_buf; + }; + + void SetCharBuf(wchar_t* newcontent) + { + len = wcslen(newcontent); + bufsize = len; + if (len > 0xf) + { + string_ptr = wcsdup(newcontent); + } + else + { + wcscpy_s(string_buf, newcontent); + } + } + }; */ + + struct AetDebugFileInfo + { + MsString name1; + int32_t gameId; + char unk[4]; // seems to be some kind of mode prefix (eg. "GAM_") + MsString name2; // same as name1??? + MsString filename; + int32_t dbId1; // not sure what the db ids are... this one might be a position + int32_t dbId12; + }; + + struct AetFileInfo + { + int32_t id1; + char unk1[4]; // seems to be some kind of mode prefix (eg. "GAM_") + int32_t id2; // same as id1??? + int32_t unk2; + MsString name; + int32_t unk3; + int32_t unk4; // might be related to sprites? + }; + + + // int findAetDebugFileId(std::string name); + + // gets a file ID for use with createAetLayer + // returns -1 if the file was not found + // note: names are a little different to in 2dauth test -- it seems like they have "_MAIN" appended + static int findAetFileId(std::string name); + + enum createAetFlags : uint32_t + { + CREATEAET_20000 = 0x20000, + }; + + // draw an aet layer (with all settings) + // aetSpeedCallback is actually a pointer to a class or struct with the callback address at offset +0x8 + static int createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc, int32_t unk2, const char* animation, const char* animation2, float animationInTime, float animationOutTime, const Point &scale, const void* aetSpeedCallback); + // draw an aet layer (with animation timing override) + static int createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc, float animationInTime, float animationOutTime); + // draw an aet layer (with scale) + static int createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc, const Point &scale); + // draw an aet layer + static int createAetLayer(int32_t fileId, uint32_t drawLayer, createAetFlags flags, const char* name, const Point &loc); + + static void destroyAetLayer(int &layer); +#pragma pack(pop) + }; +} \ No newline at end of file diff --git a/source-code/source/plugins/TLAC/dllmain.cpp b/source-code/source/plugins/TLAC/dllmain.cpp index 26efd73..45979ea 100644 --- a/source-code/source/plugins/TLAC/dllmain.cpp +++ b/source-code/source/plugins/TLAC/dllmain.cpp @@ -19,6 +19,7 @@ #pragma comment(lib, "detours.lib") void(__cdecl* divaEngineUpdate)() = (void(__cdecl*)())0x14018CC40; +void(__cdecl* divaEngineDraw2D)(void* addr) = (void(__cdecl*)(void* addr))ENGINE_DRAW_2D_ADDRESS; LRESULT CALLBACK MessageWindowProcessCallback(HWND, UINT, WPARAM, LPARAM); DWORD WINAPI WindowMessageDispatcher(LPVOID); @@ -101,6 +102,8 @@ namespace TLAC ComponentsManager.UpdateInput(); } + ComponentsManager.UpdatePostInput(); + if ((framework::inputDisable)) { Input::Keyboard::GetInstance()->PollInput(); @@ -124,6 +127,11 @@ namespace TLAC ComponentsManager.OnFocusLost(); } + void UpdateDraw2D() + { + ComponentsManager.UpdateDraw2D(); + } + void InitializeExtraSettings() { const LPCTSTR RESOLUTION_CONFIG_FILE_NAME = _T(".\\config.ini"); @@ -340,6 +348,12 @@ void hookedEngineUpdate() //divaEngineUpdate(); } +void hookedEngineDraw2D(void* addr) +{ + TLAC::UpdateDraw2D(); + divaEngineDraw2D(addr); +} + BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { HWND consoleHandle = GetConsoleWindow(); @@ -361,6 +375,11 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser DetourAttach(&(PVOID&)divaEngineUpdate, hookedEngineUpdate); DetourTransactionCommit(); + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&(PVOID&)divaEngineDraw2D, hookedEngineDraw2D); + DetourTransactionCommit(); + TLAC::InitializeExtraSettings(); TLAC::framework::Module = hModule;