mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-04-26 20:31:41 +00:00
Merge branch 'options-menu-and-installer' into options-menu
# Conflicts: # UnleashedRecomp/CMakeLists.txt # UnleashedRecomp/gpu/video.cpp # UnleashedRecomp/gpu/video.h # UnleashedRecomp/locale/locale.h # UnleashedRecomp/ui/message_window.cpp
This commit is contained in:
commit
2bd80f29b1
21 changed files with 1827 additions and 187 deletions
|
|
@ -111,6 +111,7 @@ set(SWA_PATCHES_CXX_SOURCES
|
|||
set(SWA_UI_CXX_SOURCES
|
||||
"ui/achievement_menu.cpp"
|
||||
"ui/achievement_overlay.cpp"
|
||||
"ui/installer_wizard.cpp"
|
||||
"ui/button_guide.cpp"
|
||||
"ui/message_window.cpp"
|
||||
"ui/options_menu.cpp"
|
||||
|
|
@ -199,6 +200,7 @@ find_package(unofficial-concurrentqueue REQUIRED)
|
|||
find_package(imgui CONFIG REQUIRED)
|
||||
find_package(magic_enum CONFIG REQUIRED)
|
||||
find_package(unofficial-tiny-aes-c CONFIG REQUIRED)
|
||||
find_package(nfd CONFIG REQUIRED)
|
||||
find_path(MINIAUDIO_INCLUDE_DIRS "miniaudio.h")
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
|
||||
|
|
@ -237,6 +239,7 @@ target_link_libraries(UnleashedRecomp PRIVATE
|
|||
imgui::imgui
|
||||
magic_enum::magic_enum
|
||||
unofficial::tiny-aes-c::tiny-aes-c
|
||||
nfd::nfd
|
||||
)
|
||||
|
||||
target_include_directories(UnleashedRecomp PRIVATE
|
||||
|
|
@ -318,6 +321,8 @@ generate_aggregate_header(
|
|||
set(RESOURCES_SOURCE_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources")
|
||||
set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res")
|
||||
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/general_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/general_window.dds" ARRAY_NAME "g_general_window" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/left_mouse_button.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/left_mouse_button.dds" ARRAY_NAME "g_left_mouse_button" COMPRESSION_TYPE "zstd")
|
||||
|
|
@ -327,5 +332,14 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/co
|
|||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/start_back.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/start_back.dds" ARRAY_NAME "g_start_back" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/arrow_circle.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/arrow_circle.dds" ARRAY_NAME "g_arrow_circle" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_002.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_002.dds" ARRAY_NAME "g_install_002" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_003.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_003.dds" ARRAY_NAME "g_install_003" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_004.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_004.dds" ARRAY_NAME "g_install_004" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_005.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_005.dds" ARRAY_NAME "g_install_005" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_006.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_006.dds" ARRAY_NAME "g_install_006" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds" ARRAY_NAME "g_install_007" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds" ARRAY_NAME "g_install_008" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/miles_electric_icon.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/miles_electric_icon.dds" ARRAY_NAME "g_miles_electric_icon" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd")
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
#include <ui/window.h>
|
||||
#include <patches/audio_patches.h>
|
||||
|
||||
bool g_isGameLoaded = false;
|
||||
double g_deltaTime;
|
||||
|
||||
// CApplication::Update
|
||||
PPC_FUNC_IMPL(__imp__sub_822C1130);
|
||||
PPC_FUNC(sub_822C1130)
|
||||
{
|
||||
g_isGameLoaded = true;
|
||||
g_deltaTime = ctx.f1.f64;
|
||||
|
||||
SDL_PumpEvents();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
extern bool g_isGameLoaded;
|
||||
extern double g_deltaTime;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@
|
|||
#include <kernel/heap.h>
|
||||
#include <kernel/memory.h>
|
||||
#include <cpu/guest_stack_var.h>
|
||||
#include <ui/installer_wizard.h>
|
||||
#include <ui/window.h>
|
||||
#include <api/boost/smart_ptr/shared_ptr.h>
|
||||
|
||||
SWA_API void Game_PlaySound(const char* pName)
|
||||
{
|
||||
// TODO: use own sound player.
|
||||
if (InstallerWizard::s_isVisible)
|
||||
return;
|
||||
|
||||
guest_stack_var<boost::anonymous_shared_ptr> soundPlayer;
|
||||
GuestToHostFunction<void>(sub_82B4DF50, soundPlayer.get(), ((be<uint32_t>*)g_memory.Translate(0x83367900))->get(), 7, 0, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
enum class ImGuiCallback : int32_t
|
||||
{
|
||||
SetGradient = -1,
|
||||
SetShaderModifier = -2
|
||||
SetShaderModifier = -2,
|
||||
SetOrigin = -3,
|
||||
SetScale = -4,
|
||||
};
|
||||
|
||||
union ImGuiCallbackData
|
||||
|
|
@ -27,6 +29,16 @@ union ImGuiCallbackData
|
|||
{
|
||||
uint32_t shaderModifier;
|
||||
} setShaderModifier;
|
||||
|
||||
struct
|
||||
{
|
||||
float origin[2];
|
||||
} setOrigin;
|
||||
|
||||
struct
|
||||
{
|
||||
float scale[2];
|
||||
} setScale;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ struct PushConstants
|
|||
uint ShaderModifier;
|
||||
uint Texture2DDescriptorIndex;
|
||||
float2 InverseDisplaySize;
|
||||
float2 Origin;
|
||||
float2 Scale;
|
||||
};
|
||||
|
||||
Texture2D<float4> g_Texture2DDescriptorHeap[] : register(t0, space0);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
void main(in float2 position : POSITION, in float2 uv : TEXCOORD, in float4 color : COLOR, out Interpolators interpolators)
|
||||
{
|
||||
interpolators.Position = float4(position * g_PushConstants.InverseDisplaySize * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
float2 correctedPosition = g_PushConstants.Origin + (position - g_PushConstants.Origin) * g_PushConstants.Scale;
|
||||
interpolators.Position = float4(correctedPosition * g_PushConstants.InverseDisplaySize * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
interpolators.UV = uv;
|
||||
interpolators.Color = color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <ui/button_guide.h>
|
||||
#include <ui/message_window.h>
|
||||
#include <ui/options_menu.h>
|
||||
#include <ui/installer_wizard.h>
|
||||
|
||||
#include "imgui_snapshot.h"
|
||||
#include "imgui_common.h"
|
||||
|
|
@ -1060,6 +1061,8 @@ struct ImGuiPushConstants
|
|||
uint32_t shaderModifier{};
|
||||
uint32_t texture2DDescriptorIndex{};
|
||||
ImVec2 inverseDisplaySize{};
|
||||
ImVec2 origin{ 0.0f, 0.0f };
|
||||
ImVec2 scale{ 1.0f, 1.0f };
|
||||
};
|
||||
|
||||
static void CreateImGuiBackend()
|
||||
|
|
@ -1082,6 +1085,7 @@ static void CreateImGuiBackend()
|
|||
ButtonGuide::Init();
|
||||
MessageWindow::Init();
|
||||
OptionsMenu::Init();
|
||||
InstallerWizard::Init();
|
||||
|
||||
ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
|
||||
|
||||
|
|
@ -1223,7 +1227,9 @@ static void CreateImGuiBackend()
|
|||
#endif
|
||||
}
|
||||
|
||||
static void CreateHostDevice()
|
||||
static void BeginCommandList();
|
||||
|
||||
void Video::CreateHostDevice()
|
||||
{
|
||||
for (uint32_t i = 0; i < 16; i++)
|
||||
g_inputSlots[i].index = i;
|
||||
|
|
@ -1413,20 +1419,22 @@ static void CreateHostDevice()
|
|||
desc.renderTargetCount = 1;
|
||||
g_gammaCorrectionPipeline = g_device->createGraphicsPipeline(desc);
|
||||
|
||||
g_xdbfTextureCache = std::unordered_map<uint16_t, GuestTexture*>();
|
||||
g_backBuffer = g_userHeap.AllocPhysical<GuestSurface>(ResourceType::RenderTarget);
|
||||
g_backBuffer->width = 1280;
|
||||
g_backBuffer->height = 720;
|
||||
g_backBuffer->format = BACKBUFFER_FORMAT;
|
||||
g_backBuffer->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(1, 1, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));
|
||||
|
||||
for (auto& achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH))
|
||||
{
|
||||
// huh?
|
||||
if (!achievement.pImageBuffer || !achievement.ImageBufferSize)
|
||||
continue;
|
||||
BeginCommandList();
|
||||
|
||||
g_xdbfTextureCache[achievement.ID] =
|
||||
LoadTexture((uint8_t*)achievement.pImageBuffer, achievement.ImageBufferSize).release();
|
||||
}
|
||||
RenderTextureBarrier blankTextureBarriers[TEXTURE_DESCRIPTOR_NULL_COUNT];
|
||||
for (size_t i = 0; i < TEXTURE_DESCRIPTOR_NULL_COUNT; i++)
|
||||
blankTextureBarriers[i] = RenderTextureBarrier(g_blankTextures[i].get(), RenderTextureLayout::SHADER_READ);
|
||||
|
||||
g_commandLists[g_frame]->barriers(RenderBarrierStage::NONE, blankTextureBarriers, std::size(blankTextureBarriers));
|
||||
}
|
||||
|
||||
static void WaitForGPU()
|
||||
void Video::WaitForGPU()
|
||||
{
|
||||
if (g_vulkan)
|
||||
{
|
||||
|
|
@ -1447,12 +1455,11 @@ static void WaitForGPU()
|
|||
}
|
||||
}
|
||||
|
||||
static bool g_pendingRenderThread;
|
||||
static std::atomic<bool> g_pendingRenderThread;
|
||||
|
||||
static void WaitForRenderThread()
|
||||
{
|
||||
while (g_pendingRenderThread)
|
||||
Sleep(0);
|
||||
g_pendingRenderThread.wait(true);
|
||||
}
|
||||
|
||||
static void BeginCommandList()
|
||||
|
|
@ -1469,7 +1476,7 @@ static void BeginCommandList()
|
|||
|
||||
if (!g_swapChainValid)
|
||||
{
|
||||
WaitForGPU();
|
||||
Video::WaitForGPU();
|
||||
g_backBuffer->framebuffers.clear();
|
||||
g_swapChainValid = g_swapChain->resize();
|
||||
g_needsResize = g_swapChainValid;
|
||||
|
|
@ -1493,7 +1500,7 @@ static void BeginCommandList()
|
|||
if (g_intermediaryBackBufferTextureDescriptorIndex == NULL)
|
||||
g_intermediaryBackBufferTextureDescriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
|
||||
WaitForGPU(); // Fine to wait for GPU, this'll only happen during resize.
|
||||
Video::WaitForGPU(); // Fine to wait for GPU, this'll only happen during resize.
|
||||
|
||||
g_intermediaryBackBufferTexture = g_device->createTexture(RenderTextureDesc::Texture2D(width, height, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));
|
||||
g_textureDescriptorSet->setTexture(g_intermediaryBackBufferTextureDescriptorIndex, g_intermediaryBackBufferTexture.get(), RenderTextureLayout::SHADER_READ);
|
||||
|
|
@ -1540,21 +1547,17 @@ static void BeginCommandList()
|
|||
|
||||
static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, be<uint32_t>* a6)
|
||||
{
|
||||
CreateHostDevice();
|
||||
g_xdbfTextureCache = std::unordered_map<uint16_t, GuestTexture *>();
|
||||
|
||||
g_backBuffer = g_userHeap.AllocPhysical<GuestSurface>(ResourceType::RenderTarget);
|
||||
g_backBuffer->width = 1280;
|
||||
g_backBuffer->height = 720;
|
||||
g_backBuffer->format = BACKBUFFER_FORMAT;
|
||||
g_backBuffer->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(1, 1, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));
|
||||
for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH))
|
||||
{
|
||||
// huh?
|
||||
if (!achievement.pImageBuffer || !achievement.ImageBufferSize)
|
||||
continue;
|
||||
|
||||
BeginCommandList();
|
||||
|
||||
RenderTextureBarrier blankTextureBarriers[TEXTURE_DESCRIPTOR_NULL_COUNT];
|
||||
for (size_t i = 0; i < TEXTURE_DESCRIPTOR_NULL_COUNT; i++)
|
||||
blankTextureBarriers[i] = RenderTextureBarrier(g_blankTextures[i].get(), RenderTextureLayout::SHADER_READ);
|
||||
|
||||
g_commandLists[g_frame]->barriers(RenderBarrierStage::NONE, blankTextureBarriers, std::size(blankTextureBarriers));
|
||||
g_xdbfTextureCache[achievement.ID] =
|
||||
LoadTexture((uint8_t *)achievement.pImageBuffer, achievement.ImageBufferSize).release();
|
||||
}
|
||||
|
||||
auto device = g_userHeap.AllocPhysical<GuestDevice>();
|
||||
memset(device, 0, sizeof(*device));
|
||||
|
|
@ -1788,6 +1791,7 @@ static void DrawImGui()
|
|||
AchievementMenu::Draw();
|
||||
OptionsMenu::Draw();
|
||||
AchievementOverlay::Draw();
|
||||
InstallerWizard::Draw();
|
||||
MessageWindow::Draw();
|
||||
ButtonGuide::Draw();
|
||||
|
||||
|
|
@ -1804,8 +1808,17 @@ static void DrawImGui()
|
|||
}
|
||||
}
|
||||
|
||||
static void SetFramebuffer(GuestSurface *renderTarget, GuestSurface *depthStencil, bool settingForClear);
|
||||
static void FlushViewport();
|
||||
|
||||
static void ProcDrawImGui(const RenderCommand& cmd)
|
||||
{
|
||||
// Make sure the backbuffer is the current target.
|
||||
AddBarrier(g_backBuffer, RenderTextureLayout::COLOR_WRITE);
|
||||
FlushBarriers();
|
||||
SetFramebuffer(g_backBuffer, nullptr, false);
|
||||
FlushViewport();
|
||||
|
||||
auto& commandList = g_commandLists[g_frame];
|
||||
|
||||
commandList->setGraphicsPipelineLayout(g_imPipelineLayout.get());
|
||||
|
|
@ -1849,6 +1862,12 @@ static void ProcDrawImGui(const RenderCommand& cmd)
|
|||
case ImGuiCallback::SetShaderModifier:
|
||||
commandList->setGraphicsPushConstants(0, &callbackData->setShaderModifier, offsetof(ImGuiPushConstants, shaderModifier), sizeof(callbackData->setShaderModifier));
|
||||
break;
|
||||
case ImGuiCallback::SetOrigin:
|
||||
commandList->setGraphicsPushConstants(0, &callbackData->setOrigin, offsetof(ImGuiPushConstants, origin), sizeof(callbackData->setOrigin));
|
||||
break;
|
||||
case ImGuiCallback::SetScale:
|
||||
commandList->setGraphicsPushConstants(0, &callbackData->setScale, offsetof(ImGuiPushConstants, scale), sizeof(callbackData->setScale));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -1879,21 +1898,21 @@ static void ProcDrawImGui(const RenderCommand& cmd)
|
|||
}
|
||||
}
|
||||
|
||||
static bool g_precompiledPipelineStateCache = false;
|
||||
static bool g_shouldPrecompilePipelines = false;
|
||||
|
||||
static void Present()
|
||||
void Video::HostPresent()
|
||||
{
|
||||
DrawImGui();
|
||||
WaitForRenderThread();
|
||||
DrawImGui();
|
||||
|
||||
g_pendingRenderThread = true;
|
||||
g_pendingRenderThread.store(true);
|
||||
|
||||
RenderCommand cmd;
|
||||
cmd.type = RenderCommandType::Present;
|
||||
g_renderQueue.enqueue(cmd);
|
||||
|
||||
// All the shaders are available at this point. We can precompile embedded PSOs then.
|
||||
if (!g_precompiledPipelineStateCache)
|
||||
if (g_shouldPrecompilePipelines)
|
||||
{
|
||||
// This is all the model consumer thread needs to see.
|
||||
++g_compilingDataCount;
|
||||
|
|
@ -1901,10 +1920,20 @@ static void Present()
|
|||
if ((++g_pendingDataCount) == 1)
|
||||
g_pendingDataCount.notify_all();
|
||||
|
||||
g_precompiledPipelineStateCache = true;
|
||||
g_shouldPrecompilePipelines = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Video::StartPipelinePrecompilation()
|
||||
{
|
||||
g_shouldPrecompilePipelines = true;
|
||||
}
|
||||
|
||||
static void GuestPresent()
|
||||
{
|
||||
Video::HostPresent();
|
||||
}
|
||||
|
||||
static void SetRootDescriptor(const UploadAllocation& allocation, size_t index)
|
||||
{
|
||||
auto& commandList = g_commandLists[g_frame];
|
||||
|
|
@ -1950,11 +1979,11 @@ static void ProcPresent(const RenderCommand& cmd)
|
|||
constants.gammaB = 1.0f / std::clamp(constants.gammaB + offset, 0.1f, 4.0f);
|
||||
constants.textureDescriptorIndex = g_intermediaryBackBufferTextureDescriptorIndex;
|
||||
|
||||
auto& framebuffer = g_backBuffer->framebuffers[swapChainTexture];
|
||||
auto &framebuffer = g_backBuffer->framebuffers[swapChainTexture];
|
||||
if (!framebuffer)
|
||||
{
|
||||
RenderFramebufferDesc desc;
|
||||
desc.colorAttachments = const_cast<const RenderTexture**>(&swapChainTexture);
|
||||
desc.colorAttachments = const_cast<const RenderTexture **>(&swapChainTexture);
|
||||
desc.colorAttachmentsCount = 1;
|
||||
framebuffer = g_device->createFramebuffer(desc);
|
||||
}
|
||||
|
|
@ -1965,7 +1994,7 @@ static void ProcPresent(const RenderCommand& cmd)
|
|||
RenderTextureBarrier(swapChainTexture, RenderTextureLayout::COLOR_WRITE)
|
||||
};
|
||||
|
||||
auto& commandList = g_commandLists[g_frame];
|
||||
auto &commandList = g_commandLists[g_frame];
|
||||
commandList->barriers(RenderBarrierStage::GRAPHICS, srcBarriers, std::size(srcBarriers));
|
||||
commandList->setGraphicsPipelineLayout(g_pipelineLayout.get());
|
||||
commandList->setPipeline(g_gammaCorrectionPipeline.get());
|
||||
|
|
@ -1984,14 +2013,14 @@ static void ProcPresent(const RenderCommand& cmd)
|
|||
}
|
||||
}
|
||||
|
||||
auto& commandList = g_commandLists[g_frame];
|
||||
auto &commandList = g_commandLists[g_frame];
|
||||
commandList->end();
|
||||
|
||||
if (g_swapChainValid)
|
||||
{
|
||||
const RenderCommandList* commandLists[] = { commandList.get() };
|
||||
RenderCommandSemaphore* waitSemaphores[] = { g_acquireSemaphores[g_frame].get() };
|
||||
RenderCommandSemaphore* signalSemaphores[] = { g_renderSemaphores[g_frame].get() };
|
||||
const RenderCommandList *commandLists[] = { commandList.get() };
|
||||
RenderCommandSemaphore *waitSemaphores[] = { g_acquireSemaphores[g_frame].get() };
|
||||
RenderCommandSemaphore *signalSemaphores[] = { g_renderSemaphores[g_frame].get() };
|
||||
|
||||
g_queue->executeCommandLists(
|
||||
commandLists, std::size(commandLists),
|
||||
|
|
@ -2025,7 +2054,8 @@ static void ProcPresent(const RenderCommand& cmd)
|
|||
|
||||
BeginCommandList();
|
||||
|
||||
g_pendingRenderThread = false;
|
||||
g_pendingRenderThread.store(false);
|
||||
g_pendingRenderThread.notify_all();
|
||||
}
|
||||
|
||||
static GuestSurface* GetBackBuffer()
|
||||
|
|
@ -4171,10 +4201,10 @@ static RenderFormat ConvertDXGIFormat(ddspp::DXGIFormat format)
|
|||
}
|
||||
}
|
||||
|
||||
static bool LoadTexture(GuestTexture& texture, uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)
|
||||
static bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)
|
||||
{
|
||||
ddspp::Descriptor ddsDesc;
|
||||
if (ddspp::decode_header(data, ddsDesc) != ddspp::Error)
|
||||
if (ddspp::decode_header((unsigned char *)(data), ddsDesc) != ddspp::Error)
|
||||
{
|
||||
RenderTextureDesc desc;
|
||||
desc.dimension = ConvertTextureDimension(ddsDesc.type);
|
||||
|
|
@ -4243,7 +4273,7 @@ static bool LoadTexture(GuestTexture& texture, uint8_t* data, size_t dataSize, R
|
|||
|
||||
for (auto& slice : slices)
|
||||
{
|
||||
uint8_t* srcData = data + ddsDesc.headerSize + slice.srcOffset;
|
||||
const uint8_t* srcData = data + ddsDesc.headerSize + slice.srcOffset;
|
||||
uint8_t* dstData = mappedMemory + slice.dstOffset;
|
||||
|
||||
if (slice.srcRowPitch == slice.dstRowPitch)
|
||||
|
|
@ -4336,7 +4366,7 @@ static bool LoadTexture(GuestTexture& texture, uint8_t* data, size_t dataSize, R
|
|||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<GuestTexture> LoadTexture(uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)
|
||||
std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)
|
||||
{
|
||||
GuestTexture texture(ResourceType::Texture);
|
||||
|
||||
|
|
@ -5583,7 +5613,7 @@ GUEST_FUNCTION_HOOK(sub_82BE96F0, GetSurfaceDesc);
|
|||
GUEST_FUNCTION_HOOK(sub_82BE04B0, GetVertexDeclaration);
|
||||
GUEST_FUNCTION_HOOK(sub_82BE0530, HashVertexDeclaration);
|
||||
|
||||
GUEST_FUNCTION_HOOK(sub_82BDA8C0, Present);
|
||||
GUEST_FUNCTION_HOOK(sub_82BDA8C0, GuestPresent);
|
||||
GUEST_FUNCTION_HOOK(sub_82BDD330, GetBackBuffer);
|
||||
|
||||
GUEST_FUNCTION_HOOK(sub_82BE9498, CreateTexture);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@
|
|||
|
||||
using namespace plume;
|
||||
|
||||
struct Video
|
||||
{
|
||||
static void CreateHostDevice();
|
||||
static void HostPresent();
|
||||
static void StartPipelinePrecompilation();
|
||||
static void WaitForGPU();
|
||||
};
|
||||
|
||||
struct GuestSamplerState
|
||||
{
|
||||
be<uint32_t> data[6];
|
||||
|
|
@ -379,6 +387,6 @@ enum GuestTextureAddress
|
|||
D3DTADDRESS_BORDER = 6
|
||||
};
|
||||
|
||||
extern std::unique_ptr<GuestTexture> LoadTexture(uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping = RenderComponentMapping());
|
||||
extern std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping = RenderComponentMapping());
|
||||
|
||||
extern void VideoConfigValueChangedCallback(class IConfigDef* config);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ static std::unique_ptr<VirtualFileSystem> createFileSystemFromPath(const std::fi
|
|||
}
|
||||
}
|
||||
|
||||
static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<void(uint32_t)> &progressCallback) {
|
||||
static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<void()> &progressCallback) {
|
||||
const std::string filename(pair.first);
|
||||
const uint32_t hashCount = pair.second;
|
||||
if (!sourceVfs.exists(filename))
|
||||
|
|
@ -136,7 +136,8 @@ static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFi
|
|||
return false;
|
||||
}
|
||||
|
||||
progressCallback(++journal.progressCounter);
|
||||
journal.progressCounter += fileData.size();
|
||||
progressCallback();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -200,7 +201,46 @@ bool Installer::checkGameInstall(const std::filesystem::path &baseDirectory)
|
|||
return std::filesystem::exists(baseDirectory / GameDirectory / GameExecutableFile);
|
||||
}
|
||||
|
||||
bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void(uint32_t)> &progressCallback)
|
||||
bool Installer::checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc)
|
||||
{
|
||||
switch (dlc)
|
||||
{
|
||||
case DLC::Spagonia:
|
||||
return std::filesystem::exists(baseDirectory / SpagoniaDirectory / DLCValidationFile);
|
||||
case DLC::Chunnan:
|
||||
return std::filesystem::exists(baseDirectory / ChunnanDirectory / DLCValidationFile);
|
||||
case DLC::Mazuri:
|
||||
return std::filesystem::exists(baseDirectory / MazuriDirectory / DLCValidationFile);
|
||||
case DLC::Holoska:
|
||||
return std::filesystem::exists(baseDirectory / HoloskaDirectory / DLCValidationFile);
|
||||
case DLC::ApotosShamar:
|
||||
return std::filesystem::exists(baseDirectory / ApotosShamarDirectory / DLCValidationFile);
|
||||
case DLC::EmpireCityAdabat:
|
||||
return std::filesystem::exists(baseDirectory / EmpireCityAdabatDirectory / DLCValidationFile);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize)
|
||||
{
|
||||
for (FilePair pair : filePairs)
|
||||
{
|
||||
const std::string filename(pair.first);
|
||||
if (!sourceVfs.exists(filename))
|
||||
{
|
||||
journal.lastResult = Journal::Result::FileMissing;
|
||||
journal.lastErrorMessage = std::format("File {} does not exist in the file system.", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
totalSize += sourceVfs.getSize(filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback)
|
||||
{
|
||||
if (!std::filesystem::exists(targetDirectory) && !std::filesystem::create_directories(targetDirectory))
|
||||
{
|
||||
|
|
@ -265,45 +305,49 @@ bool Installer::parseContent(const std::filesystem::path &sourcePath, std::uniqu
|
|||
}
|
||||
}
|
||||
|
||||
bool Installer::install(const Input &input, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<void(uint32_t)> &progressCallback)
|
||||
constexpr uint32_t PatcherContribution = 512 * 1024 * 1024;
|
||||
|
||||
bool Installer::parseSources(const Input &input, Journal &journal, Sources &sources)
|
||||
{
|
||||
journal = Journal();
|
||||
sources = Sources();
|
||||
|
||||
// Parse the contents of the base game.
|
||||
std::unique_ptr<VirtualFileSystem> gameSource;
|
||||
if (!input.gameSource.empty())
|
||||
{
|
||||
if (!parseContent(input.gameSource, gameSource, journal))
|
||||
if (!parseContent(input.gameSource, sources.game, journal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
journal.progressTotal += GameFilesSize;
|
||||
if (!computeTotalSize({ GameFiles, GameFilesSize }, GameHashes, *sources.game, journal, sources.totalSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the contents of Update.
|
||||
std::unique_ptr<VirtualFileSystem> updateSource;
|
||||
if (!input.updateSource.empty())
|
||||
{
|
||||
if (!parseContent(input.updateSource, updateSource, journal))
|
||||
// Add an arbitrary progress size for the patching process.
|
||||
journal.progressTotal += PatcherContribution;
|
||||
|
||||
if (!parseContent(input.updateSource, sources.update, journal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
journal.progressTotal += UpdateFilesSize;
|
||||
if (!computeTotalSize({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *sources.update, journal, sources.totalSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the contents of the DLC Packs.
|
||||
struct DLCSource {
|
||||
std::unique_ptr<VirtualFileSystem> sourceVfs;
|
||||
std::span<const FilePair> filePairs;
|
||||
const uint64_t *fileHashes = nullptr;
|
||||
std::string targetSubDirectory;
|
||||
};
|
||||
|
||||
std::vector<DLCSource> dlcSources;
|
||||
for (const auto &path : input.dlcSources)
|
||||
{
|
||||
dlcSources.emplace_back();
|
||||
DLCSource &dlcSource = dlcSources.back();
|
||||
sources.dlc.emplace_back();
|
||||
DLCSource &dlcSource = sources.dlc.back();
|
||||
if (!parseContent(path, dlcSource.sourceVfs, journal))
|
||||
{
|
||||
return false;
|
||||
|
|
@ -346,17 +390,51 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
|||
return false;
|
||||
}
|
||||
|
||||
journal.progressTotal += dlcSource.filePairs.size();
|
||||
if (!computeTotalSize(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, journal, sources.totalSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Install the base game.
|
||||
if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *gameSource, targetDirectory / GameDirectory, GameExecutableFile, input.skipHashChecks, journal, progressCallback))
|
||||
// Add the total size in bytes as the journal progress.
|
||||
journal.progressTotal += sources.totalSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Installer::install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback)
|
||||
{
|
||||
// Install files in reverse order of importance. In case of a process crash or power outage, this will increase the likelihood of the installation
|
||||
// missing critical files required for the game to run. These files are used as the way to detect if the game is installed.
|
||||
|
||||
// Install the DLC.
|
||||
if (!sources.dlc.empty())
|
||||
{
|
||||
journal.createdDirectories.insert(targetDirectory / DLCDirectory);
|
||||
}
|
||||
|
||||
for (const DLCSource &dlcSource : sources.dlc)
|
||||
{
|
||||
if (!copyFiles(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, targetDirectory / dlcSource.targetSubDirectory, DLCValidationFile, skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If no game or update was specified, we're finished. This means the user was only installing the DLC.
|
||||
if ((sources.game == nullptr) && (sources.update == nullptr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Install the update.
|
||||
if (!copyFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *sources.update, targetDirectory / UpdateDirectory, UpdateExecutablePatchFile, skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the update.
|
||||
if (!copyFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *updateSource, targetDirectory / UpdateDirectory, UpdateExecutablePatchFile, input.skipHashChecks, journal, progressCallback))
|
||||
// Install the base game.
|
||||
if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *sources.game, targetDirectory / GameDirectory, GameExecutableFile, skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -374,6 +452,10 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
|||
return false;
|
||||
}
|
||||
|
||||
// Update the progress with the artificial amount attributed to the patching.
|
||||
journal.progressCounter += PatcherContribution;
|
||||
progressCallback();
|
||||
|
||||
// Replace the executable by renaming and deleting in a safe way.
|
||||
std::error_code ec;
|
||||
std::filesystem::path oldXexPath = targetDirectory / GameDirectory / (GameExecutableFile + OldExtension);
|
||||
|
|
@ -396,20 +478,6 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
|||
|
||||
std::filesystem::remove(oldXexPath);
|
||||
|
||||
// Install the DLC.
|
||||
if (!dlcSources.empty())
|
||||
{
|
||||
journal.createdDirectories.insert(targetDirectory / DLCDirectory);
|
||||
}
|
||||
|
||||
for (const DLCSource &dlcSource : dlcSources)
|
||||
{
|
||||
if (!copyFiles(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, targetDirectory / dlcSource.targetSubDirectory, DLCValidationFile, input.skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ enum class DLC {
|
|||
Mazuri,
|
||||
Holoska,
|
||||
ApotosShamar,
|
||||
EmpireCityAdabat
|
||||
EmpireCityAdabat,
|
||||
Count = EmpireCityAdabat
|
||||
};
|
||||
|
||||
struct Journal
|
||||
|
|
@ -35,8 +36,8 @@ struct Journal
|
|||
UnknownDLCType
|
||||
};
|
||||
|
||||
uint32_t progressCounter = 0;
|
||||
uint32_t progressTotal = 0;
|
||||
uint64_t progressCounter = 0;
|
||||
uint64_t progressTotal = 0;
|
||||
std::list<std::filesystem::path> createdFiles;
|
||||
std::set<std::filesystem::path> createdDirectories;
|
||||
Result lastResult = Result::Success;
|
||||
|
|
@ -53,13 +54,30 @@ struct Installer
|
|||
std::filesystem::path gameSource;
|
||||
std::filesystem::path updateSource;
|
||||
std::list<std::filesystem::path> dlcSources;
|
||||
bool skipHashChecks = false;
|
||||
};
|
||||
|
||||
struct DLCSource {
|
||||
std::unique_ptr<VirtualFileSystem> sourceVfs;
|
||||
std::span<const FilePair> filePairs;
|
||||
const uint64_t *fileHashes = nullptr;
|
||||
std::string targetSubDirectory;
|
||||
};
|
||||
|
||||
struct Sources
|
||||
{
|
||||
std::unique_ptr<VirtualFileSystem> game;
|
||||
std::unique_ptr<VirtualFileSystem> update;
|
||||
std::vector<DLCSource> dlc;
|
||||
uint64_t totalSize = 0;
|
||||
};
|
||||
|
||||
static bool checkGameInstall(const std::filesystem::path &baseDirectory);
|
||||
static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void(uint32_t)> &progressCallback);
|
||||
static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc);
|
||||
static bool computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize);
|
||||
static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback);
|
||||
static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal);
|
||||
static bool install(const Input &input, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<void(uint32_t)> &progressCallback);
|
||||
static bool parseSources(const Input &input, Journal &journal, Sources &sources);
|
||||
static bool install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback);
|
||||
static void rollback(Journal &journal);
|
||||
|
||||
// Convenience method for checking if the specified file contains the game. This should be used when the user selects the file.
|
||||
|
|
|
|||
|
|
@ -84,6 +84,148 @@ inline static std::unordered_map<std::string, std::unordered_map<ELanguage, std:
|
|||
{ ELanguage::English, "Achievement Unlocked!" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Header_Installer",
|
||||
{
|
||||
{ ELanguage::English, "INSTALLER" },
|
||||
{ ELanguage::Spanish, "INSTALADOR" },
|
||||
},
|
||||
},
|
||||
{
|
||||
"Installer_Header_Installing",
|
||||
{
|
||||
{ ELanguage::English, "INSTALLING" },
|
||||
{ ELanguage::Spanish, "INSTALANDO" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_SelectLanguage",
|
||||
{
|
||||
{ ELanguage::English, "Please select a language." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_Introduction",
|
||||
{
|
||||
{ ELanguage::English, "Welcome to Unleashed Recompiled!\n\nYou'll need an Xbox 360 copy\nof Sonic Unleashed in order to proceed with the installation." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_SelectGameAndUpdate",
|
||||
{
|
||||
{ ELanguage::English, "Add the files for the game and its title update. You can use digital dumps or pre-extracted folders containing the necessary files." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_SelectDLC",
|
||||
{
|
||||
{ ELanguage::English, "Add the files for the DLC. You can use digital dumps or pre-extracted folders containing the necessary files." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_CheckSpace",
|
||||
{
|
||||
{ ELanguage::English, "The content will be installed to the program's folder. Please confirm you have enough free space.\n\n" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_Installing",
|
||||
{
|
||||
{ ELanguage::English, "Please wait while the content is being installed..." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_InstallSucceeded",
|
||||
{
|
||||
{ ELanguage::English, "Installation complete.\n\nThis project is brought to you by:\n\n" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_InstallFailed",
|
||||
{
|
||||
{ ELanguage::English, "Installation failed.\n\nError:\n\n" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_Game",
|
||||
{
|
||||
{ ELanguage::English, "GAME" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_Update",
|
||||
{
|
||||
{ ELanguage::English, "UPDATE" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_RequiredSpace",
|
||||
{
|
||||
{ ELanguage::English, "Required space:" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_AvailableSpace",
|
||||
{
|
||||
{ ELanguage::English, "Available space:" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_Next",
|
||||
{
|
||||
{ ELanguage::English, "NEXT" },
|
||||
{ ELanguage::Spanish, "SIGUIENTE" },
|
||||
{ ELanguage::German, "WEITER" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_Skip",
|
||||
{
|
||||
{ ELanguage::English, "SKIP" },
|
||||
{ ELanguage::Spanish, "SALTAR" },
|
||||
{ ELanguage::German, "ÜBERSPRINGEN" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_AddFiles",
|
||||
{
|
||||
{ ELanguage::English, "ADD FILES" },
|
||||
{ ELanguage::Spanish, "AÑADIR ARCHIVOS" },
|
||||
{ ELanguage::German, "DATEIEN HINZUFÜGEN" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_AddFolder",
|
||||
{
|
||||
{ ELanguage::English, "ADD FOLDER" },
|
||||
{ ELanguage::Spanish, "AÑADIR CARPETA" },
|
||||
{ ELanguage::German, "ORDNER HINZUFÜGEN" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_InvalidFilesList",
|
||||
{
|
||||
{ ELanguage::English, "The following selected files are invalid:" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_InvalidFiles",
|
||||
{
|
||||
{ ELanguage::English, "Some of the files that have\nbeen provided are not valid.\n\nPlease make sure all the\nspecified files are correct\nand try again." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_IncompatibleGameData",
|
||||
{
|
||||
{ ELanguage::English, "The specified game and\nupdate file are incompatible.\n\nPlease ensure the files are\nfor the same version and\nregion and try again." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_DLCWarning",
|
||||
{
|
||||
{ ELanguage::English, "It is highly recommended\nthat you install all of the\nDLC, as it includes high\nquality lighting textures\nfor the base game.\n\nAre you sure you want to\nskip this step?" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Common_Next",
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
#include <user/config.h>
|
||||
#include <user/paths.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <install/installer.h>
|
||||
#include <ui/installer_wizard.h>
|
||||
|
||||
#define GAME_XEX_PATH "game:\\default.xex"
|
||||
|
||||
|
|
@ -27,8 +29,7 @@ CodeCache g_codeCache;
|
|||
XDBFWrapper g_xdbfWrapper;
|
||||
std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;
|
||||
|
||||
// Name inspired from nt's entry point
|
||||
void KiSystemStartup()
|
||||
void HostStartup()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
|
@ -39,7 +40,11 @@ void KiSystemStartup()
|
|||
g_codeCache.Init();
|
||||
|
||||
g_memory.Alloc(XMAIOBegin, 0xFFFF, MEM_COMMIT);
|
||||
}
|
||||
|
||||
// Name inspired from nt's entry point
|
||||
void KiSystemStartup()
|
||||
{
|
||||
const auto gameContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Game");
|
||||
const auto updateContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Update");
|
||||
XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : ".");
|
||||
|
|
@ -136,15 +141,39 @@ uint32_t LdrLoadModule(const char* path)
|
|||
return entry;
|
||||
}
|
||||
|
||||
int main()
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
bool forceInstaller = false;
|
||||
bool forceDLCInstaller = false;
|
||||
for (uint32_t i = 1; i < argc; i++)
|
||||
{
|
||||
forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0);
|
||||
forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0);
|
||||
}
|
||||
|
||||
Config::Load();
|
||||
|
||||
HostStartup();
|
||||
|
||||
Video::CreateHostDevice();
|
||||
|
||||
bool isGameInstalled = Installer::checkGameInstall(".");
|
||||
if (forceInstaller || forceDLCInstaller || !isGameInstalled)
|
||||
{
|
||||
if (!InstallerWizard::Run(isGameInstalled && forceDLCInstaller))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
AchievementData::Load();
|
||||
|
||||
KiSystemStartup();
|
||||
|
||||
uint32_t entry = LdrLoadModule(FileSystem::TransformPath(GAME_XEX_PATH));
|
||||
|
||||
Video::StartPipelinePrecompilation();
|
||||
|
||||
GuestThread::Start(entry);
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,20 @@ static void SetShaderModifier(uint32_t shaderModifier)
|
|||
callbackData->setShaderModifier.shaderModifier = shaderModifier;
|
||||
}
|
||||
|
||||
static void SetOrigin(ImVec2 origin)
|
||||
{
|
||||
auto callbackData = AddCallback(ImGuiCallback::SetOrigin);
|
||||
callbackData->setOrigin.origin[0] = origin.x;
|
||||
callbackData->setOrigin.origin[1] = origin.y;
|
||||
}
|
||||
|
||||
static void SetScale(ImVec2 scale)
|
||||
{
|
||||
auto callbackData = AddCallback(ImGuiCallback::SetScale);
|
||||
callbackData->setScale.scale[0] = scale.x;
|
||||
callbackData->setScale.scale[1] = scale.y;
|
||||
}
|
||||
|
||||
// Aspect ratio aware.
|
||||
static float Scale(float size)
|
||||
{
|
||||
|
|
@ -221,6 +235,32 @@ static float CalcWidestTextSize(const ImFont* font, float fontSize, std::span<st
|
|||
return result;
|
||||
}
|
||||
|
||||
static std::string Truncate(const std::string& input, size_t maxLength, bool useEllipsis = true, bool usePrefixEllipsis = false)
|
||||
{
|
||||
const std::string ellipsis = "...";
|
||||
|
||||
if (input.length() > maxLength)
|
||||
{
|
||||
if (useEllipsis && maxLength > ellipsis.length())
|
||||
{
|
||||
if (usePrefixEllipsis)
|
||||
{
|
||||
return ellipsis + input.substr(0, maxLength - ellipsis.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.substr(0, maxLength - ellipsis.length()) + ellipsis;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.substr(0, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static std::vector<std::string> Split(const char* str, char delimiter)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
|
@ -274,11 +314,29 @@ static void DrawCentredParagraph(const ImFont* font, float fontSize, const ImVec
|
|||
auto paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines);
|
||||
auto offsetY = 0.0f;
|
||||
|
||||
for (auto& str : lines)
|
||||
auto hasList = std::strstr(text, "- ");
|
||||
auto isList = false;
|
||||
auto listOffsetX = 0.0f;
|
||||
|
||||
for (int i = 0; i < lines.size(); i++)
|
||||
{
|
||||
auto& str = lines[i];
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
drawMethod(str.c_str(), ImVec2(/* X */ centre.x - textSize.x / 2, /* Y */ centre.y - paragraphSize.y / 2 + offsetY));
|
||||
if (hasList)
|
||||
{
|
||||
if (!isList && str.starts_with("- ") && lines.size() > i + 1 && lines[i + 1].starts_with("- "))
|
||||
{
|
||||
isList = true;
|
||||
listOffsetX = centre.x - textSize.x / 2;
|
||||
}
|
||||
else if (isList && !str.starts_with("- "))
|
||||
{
|
||||
isList = false;
|
||||
}
|
||||
}
|
||||
|
||||
drawMethod(str.c_str(), ImVec2(/* X */ isList ? listOffsetX : centre.x - textSize.x / 2, /* Y */ centre.y - paragraphSize.y / 2 + offsetY));
|
||||
|
||||
offsetY += textSize.y + Scale(lineMargin);
|
||||
}
|
||||
|
|
|
|||
1230
UnleashedRecomp/ui/installer_wizard.cpp
Normal file
1230
UnleashedRecomp/ui/installer_wizard.cpp
Normal file
File diff suppressed because it is too large
Load diff
13
UnleashedRecomp/ui/installer_wizard.h
Normal file
13
UnleashedRecomp/ui/installer_wizard.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <api/SWA.h>
|
||||
|
||||
struct InstallerWizard
|
||||
{
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Shutdown();
|
||||
static bool Run(bool skipGame);
|
||||
};
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
#include "imgui_utils.h"
|
||||
#include <api/SWA.h>
|
||||
#include <gpu/video.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
#include <res/images/common/general_window.dds.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/common/select_fade.dds.h>
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
|
|
@ -30,6 +32,7 @@ static double g_controlsAppearTime;
|
|||
static ImFont* g_fntSeurat;
|
||||
|
||||
static std::unique_ptr<GuestTexture> g_upSelectionCursor;
|
||||
static std::unique_ptr<GuestTexture> g_upWindow;
|
||||
|
||||
std::string g_text;
|
||||
int g_result;
|
||||
|
|
@ -61,8 +64,6 @@ bool DrawContainer(float appearTime, ImVec2 centre, ImVec2 max, bool isForegroun
|
|||
_max.y = Hermite(centre.y, _max.y, containerMotion);
|
||||
}
|
||||
|
||||
auto vertices = GetPauseContainerVertices(_min, _max);
|
||||
|
||||
// Transparency fade animation.
|
||||
auto colourMotion = g_isClosing
|
||||
? ComputeMotion(appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END)
|
||||
|
|
@ -78,44 +79,7 @@ bool DrawContainer(float appearTime, ImVec2 centre, ImVec2 max, bool isForegroun
|
|||
if (isForeground)
|
||||
drawList->AddRectFilled({ 0.0f, 0.0f }, ImGui::GetIO().DisplaySize, IM_COL32(0, 0, 0, 223 * (g_foregroundCount ? 1 : alpha)));
|
||||
|
||||
auto colShadow = IM_COL32(0, 0, 0, 156 * alpha);
|
||||
auto colGradientTop = IM_COL32(197, 194, 197, 200 * alpha);
|
||||
auto colGradientBottom = IM_COL32(115, 113, 115, 236 * alpha);
|
||||
|
||||
// Draw vertices with gradient.
|
||||
SetGradient(_min, _max, colGradientTop, colGradientBottom);
|
||||
drawList->AddConvexPolyFilled(vertices.data(), vertices.size(), IM_COL32(255, 255, 255, 255 * alpha));
|
||||
ResetGradient();
|
||||
|
||||
// Draw outline.
|
||||
drawList->AddPolyline
|
||||
(
|
||||
vertices.data(),
|
||||
vertices.size(),
|
||||
IM_COL32(247, 247, 247, 255 * alpha),
|
||||
true,
|
||||
Scale(2.5f)
|
||||
);
|
||||
|
||||
// Offset vertices to draw 3D effect lines.
|
||||
for (int i = 0; i < vertices.size(); i++)
|
||||
{
|
||||
vertices[i].x -= Scale(0.4f);
|
||||
vertices[i].y -= Scale(0.2f);
|
||||
}
|
||||
|
||||
auto colLineTop = IM_COL32(165, 170, 165, 230 * alpha);
|
||||
auto colLineBottom = IM_COL32(190, 190, 190, 230 * alpha);
|
||||
auto lineThickness = Scale(1.0f);
|
||||
|
||||
// Top left corner bottom to top left corner top.
|
||||
drawList->AddLine(vertices[0], vertices[1], colLineTop, lineThickness * 0.5f);
|
||||
|
||||
// Top left corner bottom to bottom left.
|
||||
drawList->AddRectFilledMultiColor({ vertices[0].x - 0.2f, vertices[0].y }, { vertices[6].x + lineThickness - 0.2f, vertices[6].y }, colLineTop, colLineTop, colLineBottom, colLineBottom);
|
||||
|
||||
// Top left corner top to top right.
|
||||
drawList->AddLine(vertices[1], vertices[2], colLineTop, lineThickness);
|
||||
DrawPauseContainer(g_upWindow, _min, _max, alpha);
|
||||
|
||||
drawList->PushClipRect(_min, _max);
|
||||
|
||||
|
|
@ -179,6 +143,7 @@ void MessageWindow::Init()
|
|||
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||
|
||||
g_upSelectionCursor = LoadTexture(decompressZstd(g_select_fade, g_select_fade_uncompressed_size).get(), g_select_fade_uncompressed_size);
|
||||
g_upWindow = LoadTexture(decompressZstd(g_general_window, g_general_window_uncompressed_size).get(), g_general_window_uncompressed_size);
|
||||
}
|
||||
|
||||
void MessageWindow::Draw()
|
||||
|
|
@ -186,7 +151,7 @@ void MessageWindow::Draw()
|
|||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
auto pInputState = g_isGameLoaded ? SWA::CInputState::GetInstance() : nullptr;
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto& res = ImGui::GetIO().DisplaySize;
|
||||
|
||||
|
|
@ -194,8 +159,8 @@ void MessageWindow::Draw()
|
|||
|
||||
auto fontSize = Scale(28);
|
||||
auto textSize = MeasureCentredParagraph(g_fntSeurat, fontSize, 5, g_text.c_str());
|
||||
auto textMarginX = Scale(32);
|
||||
auto textMarginY = Scale(40);
|
||||
auto textMarginX = Scale(37);
|
||||
auto textMarginY = Scale(45);
|
||||
|
||||
if (DrawContainer(g_appearTime, centre, { textSize.x / 2 + textMarginX, textSize.y / 2 + textMarginY }, !g_isControlsVisible))
|
||||
{
|
||||
|
|
@ -215,12 +180,16 @@ void MessageWindow::Draw()
|
|||
|
||||
drawList->PopClipRect();
|
||||
|
||||
bool isAccepted = pInputState
|
||||
? pInputState->GetPadState().IsTapped(SWA::eKeyState_A)
|
||||
: ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
|
||||
if (g_buttons.size())
|
||||
{
|
||||
auto itemWidth = std::max(Scale(162), Scale(CalcWidestTextSize(g_fntSeurat, fontSize, g_buttons)));
|
||||
auto itemHeight = Scale(57);
|
||||
auto windowMarginX = Scale(18);
|
||||
auto windowMarginY = Scale(25);
|
||||
auto windowMarginX = Scale(23);
|
||||
auto windowMarginY = Scale(30);
|
||||
|
||||
ImVec2 controlsMax = { /* X */ itemWidth / 2 + windowMarginX, /* Y */ itemHeight / 2 * g_buttons.size() + windowMarginY };
|
||||
|
||||
|
|
@ -231,54 +200,74 @@ void MessageWindow::Draw()
|
|||
for (auto& button : g_buttons)
|
||||
DrawButton(rowCount++, windowMarginY, itemWidth, itemHeight, button);
|
||||
|
||||
drawList->PopClipRect();
|
||||
|
||||
bool upIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) ||
|
||||
pInputState->GetPadState().LeftStickVertical > 0.5f;
|
||||
|
||||
bool downIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) ||
|
||||
pInputState->GetPadState().LeftStickVertical < -0.5f;
|
||||
|
||||
bool scrollUp = !g_upWasHeld && upIsHeld;
|
||||
bool scrollDown = !g_downWasHeld && downIsHeld;
|
||||
|
||||
if (scrollUp)
|
||||
if (pInputState)
|
||||
{
|
||||
--g_selectedRowIndex;
|
||||
if (g_selectedRowIndex < 0)
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
bool upIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) ||
|
||||
pInputState->GetPadState().LeftStickVertical > 0.5f;
|
||||
|
||||
bool downIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) ||
|
||||
pInputState->GetPadState().LeftStickVertical < -0.5f;
|
||||
|
||||
bool scrollUp = !g_upWasHeld && upIsHeld;
|
||||
bool scrollDown = !g_downWasHeld && downIsHeld;
|
||||
|
||||
if (scrollUp)
|
||||
{
|
||||
--g_selectedRowIndex;
|
||||
if (g_selectedRowIndex < 0)
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
}
|
||||
else if (scrollDown)
|
||||
{
|
||||
++g_selectedRowIndex;
|
||||
if (g_selectedRowIndex >= rowCount)
|
||||
g_selectedRowIndex = 0;
|
||||
}
|
||||
|
||||
if (scrollUp || scrollDown)
|
||||
Game_PlaySound("sys_actstg_pausecursor");
|
||||
|
||||
g_upWasHeld = upIsHeld;
|
||||
g_downWasHeld = downIsHeld;
|
||||
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||
{
|
||||
g_result = -1;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausecansel");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
}
|
||||
else if (scrollDown)
|
||||
else
|
||||
{
|
||||
++g_selectedRowIndex;
|
||||
if (g_selectedRowIndex >= rowCount)
|
||||
g_selectedRowIndex = 0;
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
g_selectedRowIndex = -1;
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
ImVec2 itemMin = { clipRectMin.x + windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i };
|
||||
ImVec2 itemMax = { clipRectMax.x - windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i + itemHeight };
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(itemMin, itemMax, false))
|
||||
g_selectedRowIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollUp || scrollDown)
|
||||
Game_PlaySound("sys_actstg_pausecursor");
|
||||
|
||||
g_upWasHeld = upIsHeld;
|
||||
g_downWasHeld = downIsHeld;
|
||||
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_A))
|
||||
if (g_selectedRowIndex != -1 && isAccepted)
|
||||
{
|
||||
g_result = g_selectedRowIndex;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausedecide");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
else if (pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||
{
|
||||
g_result = -1;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausecansel");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!g_isControlsVisible && pInputState->GetPadState().IsTapped(SWA::eKeyState_A))
|
||||
if (!g_isControlsVisible && isAccepted)
|
||||
{
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
g_isControlsVisible = true;
|
||||
|
|
@ -289,10 +278,18 @@ void MessageWindow::Draw()
|
|||
}
|
||||
else
|
||||
{
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_A))
|
||||
if (isAccepted)
|
||||
{
|
||||
g_result = 0;
|
||||
|
||||
MessageWindow::Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g_isClosing)
|
||||
{
|
||||
s_isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageWindow::Open(std::string text, int* result, std::span<std::string> buttons, int defaultButtonIndex)
|
||||
|
|
@ -308,7 +305,7 @@ bool MessageWindow::Open(std::string text, int* result, std::span<std::string> b
|
|||
|
||||
g_text = text;
|
||||
g_buttons = std::vector(buttons.begin(), buttons.end());
|
||||
g_defaultButtonIndex = defaultButtonIndex;
|
||||
g_defaultButtonIndex = g_isGameLoaded ? defaultButtonIndex : -1;
|
||||
|
||||
ResetSelection();
|
||||
|
||||
|
|
@ -330,6 +327,7 @@ void MessageWindow::Close()
|
|||
g_controlsAppearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
g_isControlsVisible = false;
|
||||
g_foregroundCount = 0;
|
||||
g_isAwaitingResult = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -989,12 +989,11 @@ void OptionsMenu::Init()
|
|||
|
||||
void OptionsMenu::Draw()
|
||||
{
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
// We've entered the menu now, no need to check this.
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
if (pInputState->GetPadState().IsReleased(SWA::eKeyState_A))
|
||||
g_isEnterKeyBuffered = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
|||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
Window::s_isFocused = true;
|
||||
SDL_ShowCursor(Window::IsFullscreen() ? SDL_DISABLE : SDL_ENABLE);
|
||||
SDL_ShowCursor(Window::IsFullscreen() && !Window::s_cursorAllowed ? SDL_DISABLE : SDL_ENABLE);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public:
|
|||
|
||||
inline static bool s_isFocused;
|
||||
inline static bool s_isIconNight;
|
||||
inline static bool s_cursorAllowed = false;
|
||||
|
||||
static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize)
|
||||
{
|
||||
|
|
@ -99,7 +100,7 @@ public:
|
|||
if (isEnabled)
|
||||
{
|
||||
SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
SDL_ShowCursor(s_cursorAllowed ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -110,6 +111,14 @@ public:
|
|||
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
static void SetCursorAllowed(bool isCursorAllowed)
|
||||
{
|
||||
s_cursorAllowed = isCursorAllowed;
|
||||
|
||||
// Refresh fullscreen state to enable the right cursor behavior.
|
||||
SetFullscreen(IsFullscreen());
|
||||
}
|
||||
|
||||
static bool IsMaximised()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"features": [ "sdl2-binding" ]
|
||||
},
|
||||
"magic-enum",
|
||||
"nativefiledialog-extended",
|
||||
"miniaudio"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue