mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-04-27 04:41:39 +00:00
Add installer wizard.
This commit is contained in:
parent
ca7e170f4d
commit
aeadcfcf90
13 changed files with 1158 additions and 100 deletions
|
|
@ -85,6 +85,7 @@ set(SWA_PATCHES_CXX_SOURCES
|
||||||
set(SWA_UI_CXX_SOURCES
|
set(SWA_UI_CXX_SOURCES
|
||||||
"ui/achievement_menu.cpp"
|
"ui/achievement_menu.cpp"
|
||||||
"ui/achievement_overlay.cpp"
|
"ui/achievement_overlay.cpp"
|
||||||
|
"ui/installer_wizard.cpp"
|
||||||
"ui/options_menu.cpp"
|
"ui/options_menu.cpp"
|
||||||
"ui/sdl_listener.cpp"
|
"ui/sdl_listener.cpp"
|
||||||
"ui/window.cpp"
|
"ui/window.cpp"
|
||||||
|
|
@ -106,6 +107,16 @@ set(SWA_INSTALL_CXX_SOURCES
|
||||||
"install/hashes/update.cpp"
|
"install/hashes/update.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(INSTALLER_IMAGES_DIR "../UnleashedRecompResources/images/installer")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_001.dds" HEADER_FILE "res/install_001_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install001DDS")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_002.dds" HEADER_FILE "res/install_002_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install002DDS")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_003.dds" HEADER_FILE "res/install_003_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install003DDS")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_004.dds" HEADER_FILE "res/install_004_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install004DDS")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_005.dds" HEADER_FILE "res/install_005_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install005DDS")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_006.dds" HEADER_FILE "res/install_006_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install006DDS")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_007.dds" HEADER_FILE "res/install_007_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install007DDS")
|
||||||
|
BIN2H(SOURCE_FILE "${INSTALLER_IMAGES_DIR}/install_008.dds" HEADER_FILE "res/install_008_dds.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_install008DDS")
|
||||||
|
|
||||||
set(LIBMSPACK_PATH ${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack)
|
set(LIBMSPACK_PATH ${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack)
|
||||||
|
|
||||||
set(LIBMSPACK_C_SOURCES
|
set(LIBMSPACK_C_SOURCES
|
||||||
|
|
@ -172,6 +183,7 @@ find_package(imgui CONFIG REQUIRED)
|
||||||
find_package(magic_enum CONFIG REQUIRED)
|
find_package(magic_enum CONFIG REQUIRED)
|
||||||
find_package(unofficial-tiny-aes-c CONFIG REQUIRED)
|
find_package(unofficial-tiny-aes-c CONFIG REQUIRED)
|
||||||
find_path(READERWRITERQUEUE_INCLUDE_DIRS "readerwriterqueue/atomicops.h")
|
find_path(READERWRITERQUEUE_INCLUDE_DIRS "readerwriterqueue/atomicops.h")
|
||||||
|
find_package(nfd CONFIG REQUIRED)
|
||||||
|
|
||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
|
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
|
||||||
add_custom_command(TARGET UnleashedRecomp POST_BUILD
|
add_custom_command(TARGET UnleashedRecomp POST_BUILD
|
||||||
|
|
@ -209,6 +221,7 @@ target_link_libraries(UnleashedRecomp PRIVATE
|
||||||
imgui::imgui
|
imgui::imgui
|
||||||
magic_enum::magic_enum
|
magic_enum::magic_enum
|
||||||
unofficial::tiny-aes-c::tiny-aes-c
|
unofficial::tiny-aes-c::tiny-aes-c
|
||||||
|
nfd::nfd
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(UnleashedRecomp PRIVATE
|
target_include_directories(UnleashedRecomp PRIVATE
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <ui/achievement_menu.h>
|
#include <ui/achievement_menu.h>
|
||||||
#include <ui/achievement_overlay.h>
|
#include <ui/achievement_overlay.h>
|
||||||
#include <ui/options_menu.h>
|
#include <ui/options_menu.h>
|
||||||
|
#include <ui/installer_wizard.h>
|
||||||
|
|
||||||
#include "imgui_snapshot.h"
|
#include "imgui_snapshot.h"
|
||||||
#include "imgui_common.h"
|
#include "imgui_common.h"
|
||||||
|
|
@ -1066,6 +1067,7 @@ static void CreateImGuiBackend()
|
||||||
AchievementMenu::Init();
|
AchievementMenu::Init();
|
||||||
AchievementOverlay::Init();
|
AchievementOverlay::Init();
|
||||||
OptionsMenu::Init();
|
OptionsMenu::Init();
|
||||||
|
InstallerWizard::Init();
|
||||||
|
|
||||||
ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
|
ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
|
||||||
|
|
||||||
|
|
@ -1174,7 +1176,9 @@ static void CreateImGuiBackend()
|
||||||
g_imPipeline = g_device->createGraphicsPipeline(pipelineDesc);
|
g_imPipeline = g_device->createGraphicsPipeline(pipelineDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CreateHostDevice()
|
static void BeginCommandList();
|
||||||
|
|
||||||
|
void Video::CreateHostDevice()
|
||||||
{
|
{
|
||||||
for (uint32_t i = 0; i < 16; i++)
|
for (uint32_t i = 0; i < 16; i++)
|
||||||
g_inputSlots[i].index = i;
|
g_inputSlots[i].index = i;
|
||||||
|
|
@ -1364,17 +1368,19 @@ static void CreateHostDevice()
|
||||||
desc.renderTargetCount = 1;
|
desc.renderTargetCount = 1;
|
||||||
g_gammaCorrectionPipeline = g_device->createGraphicsPipeline(desc);
|
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))
|
BeginCommandList();
|
||||||
{
|
|
||||||
// huh?
|
|
||||||
if (!achievement.pImageBuffer || !achievement.ImageBufferSize)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
g_xdbfTextureCache[achievement.ID] =
|
RenderTextureBarrier blankTextureBarriers[TEXTURE_DESCRIPTOR_NULL_COUNT];
|
||||||
LoadTexture((uint8_t*)achievement.pImageBuffer, achievement.ImageBufferSize).release();
|
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()
|
static void WaitForGPU()
|
||||||
|
|
@ -1398,12 +1404,11 @@ static void WaitForGPU()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool g_pendingRenderThread;
|
static std::atomic<bool> g_pendingRenderThread;
|
||||||
|
|
||||||
static void WaitForRenderThread()
|
static void WaitForRenderThread()
|
||||||
{
|
{
|
||||||
while (g_pendingRenderThread)
|
g_pendingRenderThread.wait(true);
|
||||||
Sleep(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void BeginCommandList()
|
static void BeginCommandList()
|
||||||
|
|
@ -1491,21 +1496,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)
|
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);
|
for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH))
|
||||||
g_backBuffer->width = 1280;
|
{
|
||||||
g_backBuffer->height = 720;
|
// huh?
|
||||||
g_backBuffer->format = BACKBUFFER_FORMAT;
|
if (!achievement.pImageBuffer || !achievement.ImageBufferSize)
|
||||||
g_backBuffer->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(1, 1, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));
|
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));
|
|
||||||
|
|
||||||
auto device = g_userHeap.AllocPhysical<GuestDevice>();
|
auto device = g_userHeap.AllocPhysical<GuestDevice>();
|
||||||
memset(device, 0, sizeof(*device));
|
memset(device, 0, sizeof(*device));
|
||||||
|
|
@ -1739,6 +1740,7 @@ static void DrawImGui()
|
||||||
AchievementMenu::Draw();
|
AchievementMenu::Draw();
|
||||||
OptionsMenu::Draw();
|
OptionsMenu::Draw();
|
||||||
AchievementOverlay::Draw();
|
AchievementOverlay::Draw();
|
||||||
|
InstallerWizard::Draw();
|
||||||
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
|
||||||
|
|
@ -1753,8 +1755,17 @@ static void DrawImGui()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SetFramebuffer(GuestSurface *renderTarget, GuestSurface *depthStencil, bool settingForClear);
|
||||||
|
static void FlushViewport();
|
||||||
|
|
||||||
static void ProcDrawImGui(const RenderCommand& cmd)
|
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];
|
auto& commandList = g_commandLists[g_frame];
|
||||||
|
|
||||||
commandList->setGraphicsPipelineLayout(g_imPipelineLayout.get());
|
commandList->setGraphicsPipelineLayout(g_imPipelineLayout.get());
|
||||||
|
|
@ -1828,21 +1839,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();
|
WaitForRenderThread();
|
||||||
|
DrawImGui();
|
||||||
|
|
||||||
g_pendingRenderThread = true;
|
g_pendingRenderThread.store(true);
|
||||||
|
|
||||||
RenderCommand cmd;
|
RenderCommand cmd;
|
||||||
cmd.type = RenderCommandType::Present;
|
cmd.type = RenderCommandType::Present;
|
||||||
g_renderQueue.enqueue(cmd);
|
g_renderQueue.enqueue(cmd);
|
||||||
|
|
||||||
// All the shaders are available at this point. We can precompile embedded PSOs then.
|
// 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.
|
// This is all the model consumer thread needs to see.
|
||||||
++g_compilingDataCount;
|
++g_compilingDataCount;
|
||||||
|
|
@ -1850,10 +1861,20 @@ static void Present()
|
||||||
if ((++g_pendingDataCount) == 1)
|
if ((++g_pendingDataCount) == 1)
|
||||||
g_pendingDataCount.notify_all();
|
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)
|
static void SetRootDescriptor(const UploadAllocation& allocation, size_t index)
|
||||||
{
|
{
|
||||||
auto& commandList = g_commandLists[g_frame];
|
auto& commandList = g_commandLists[g_frame];
|
||||||
|
|
@ -1899,11 +1920,11 @@ static void ProcPresent(const RenderCommand& cmd)
|
||||||
constants.gammaB = 1.0f / std::clamp(constants.gammaB + offset, 0.1f, 4.0f);
|
constants.gammaB = 1.0f / std::clamp(constants.gammaB + offset, 0.1f, 4.0f);
|
||||||
constants.textureDescriptorIndex = g_intermediaryBackBufferTextureDescriptorIndex;
|
constants.textureDescriptorIndex = g_intermediaryBackBufferTextureDescriptorIndex;
|
||||||
|
|
||||||
auto& framebuffer = g_backBuffer->framebuffers[swapChainTexture];
|
auto &framebuffer = g_backBuffer->framebuffers[swapChainTexture];
|
||||||
if (!framebuffer)
|
if (!framebuffer)
|
||||||
{
|
{
|
||||||
RenderFramebufferDesc desc;
|
RenderFramebufferDesc desc;
|
||||||
desc.colorAttachments = const_cast<const RenderTexture**>(&swapChainTexture);
|
desc.colorAttachments = const_cast<const RenderTexture **>(&swapChainTexture);
|
||||||
desc.colorAttachmentsCount = 1;
|
desc.colorAttachmentsCount = 1;
|
||||||
framebuffer = g_device->createFramebuffer(desc);
|
framebuffer = g_device->createFramebuffer(desc);
|
||||||
}
|
}
|
||||||
|
|
@ -1914,7 +1935,7 @@ static void ProcPresent(const RenderCommand& cmd)
|
||||||
RenderTextureBarrier(swapChainTexture, RenderTextureLayout::COLOR_WRITE)
|
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->barriers(RenderBarrierStage::GRAPHICS, srcBarriers, std::size(srcBarriers));
|
||||||
commandList->setGraphicsPipelineLayout(g_pipelineLayout.get());
|
commandList->setGraphicsPipelineLayout(g_pipelineLayout.get());
|
||||||
commandList->setPipeline(g_gammaCorrectionPipeline.get());
|
commandList->setPipeline(g_gammaCorrectionPipeline.get());
|
||||||
|
|
@ -1933,14 +1954,14 @@ static void ProcPresent(const RenderCommand& cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& commandList = g_commandLists[g_frame];
|
auto &commandList = g_commandLists[g_frame];
|
||||||
commandList->end();
|
commandList->end();
|
||||||
|
|
||||||
if (g_swapChainValid)
|
if (g_swapChainValid)
|
||||||
{
|
{
|
||||||
const RenderCommandList* commandLists[] = { commandList.get() };
|
const RenderCommandList *commandLists[] = { commandList.get() };
|
||||||
RenderCommandSemaphore* waitSemaphores[] = { g_acquireSemaphores[g_frame].get() };
|
RenderCommandSemaphore *waitSemaphores[] = { g_acquireSemaphores[g_frame].get() };
|
||||||
RenderCommandSemaphore* signalSemaphores[] = { g_renderSemaphores[g_frame].get() };
|
RenderCommandSemaphore *signalSemaphores[] = { g_renderSemaphores[g_frame].get() };
|
||||||
|
|
||||||
g_queue->executeCommandLists(
|
g_queue->executeCommandLists(
|
||||||
commandLists, std::size(commandLists),
|
commandLists, std::size(commandLists),
|
||||||
|
|
@ -1974,7 +1995,8 @@ static void ProcPresent(const RenderCommand& cmd)
|
||||||
|
|
||||||
BeginCommandList();
|
BeginCommandList();
|
||||||
|
|
||||||
g_pendingRenderThread = false;
|
g_pendingRenderThread.store(false);
|
||||||
|
g_pendingRenderThread.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
static GuestSurface* GetBackBuffer()
|
static GuestSurface* GetBackBuffer()
|
||||||
|
|
@ -4120,10 +4142,10 @@ static RenderFormat ConvertDXGIFormat(ddspp::DXGIFormat format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool LoadTexture(GuestTexture& texture, uint8_t* data, size_t dataSize)
|
static bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataSize)
|
||||||
{
|
{
|
||||||
ddspp::Descriptor ddsDesc;
|
ddspp::Descriptor ddsDesc;
|
||||||
if (ddspp::decode_header(data, ddsDesc) != ddspp::Error)
|
if (ddspp::decode_header((unsigned char *)(data), ddsDesc) != ddspp::Error)
|
||||||
{
|
{
|
||||||
RenderTextureDesc desc;
|
RenderTextureDesc desc;
|
||||||
desc.dimension = ConvertTextureDimension(ddsDesc.type);
|
desc.dimension = ConvertTextureDimension(ddsDesc.type);
|
||||||
|
|
@ -4191,7 +4213,7 @@ static bool LoadTexture(GuestTexture& texture, uint8_t* data, size_t dataSize)
|
||||||
|
|
||||||
for (auto& slice : slices)
|
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;
|
uint8_t* dstData = mappedMemory + slice.dstOffset;
|
||||||
|
|
||||||
if (slice.srcRowPitch == slice.dstRowPitch)
|
if (slice.srcRowPitch == slice.dstRowPitch)
|
||||||
|
|
@ -4284,7 +4306,7 @@ static bool LoadTexture(GuestTexture& texture, uint8_t* data, size_t dataSize)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<GuestTexture> LoadTexture(uint8_t* data, size_t dataSize)
|
std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize)
|
||||||
{
|
{
|
||||||
GuestTexture texture(ResourceType::Texture);
|
GuestTexture texture(ResourceType::Texture);
|
||||||
|
|
||||||
|
|
@ -5531,7 +5553,7 @@ GUEST_FUNCTION_HOOK(sub_82BE96F0, GetSurfaceDesc);
|
||||||
GUEST_FUNCTION_HOOK(sub_82BE04B0, GetVertexDeclaration);
|
GUEST_FUNCTION_HOOK(sub_82BE04B0, GetVertexDeclaration);
|
||||||
GUEST_FUNCTION_HOOK(sub_82BE0530, HashVertexDeclaration);
|
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_82BDD330, GetBackBuffer);
|
||||||
|
|
||||||
GUEST_FUNCTION_HOOK(sub_82BE9498, CreateTexture);
|
GUEST_FUNCTION_HOOK(sub_82BE9498, CreateTexture);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,13 @@
|
||||||
|
|
||||||
using namespace plume;
|
using namespace plume;
|
||||||
|
|
||||||
|
struct Video
|
||||||
|
{
|
||||||
|
static void CreateHostDevice();
|
||||||
|
static void HostPresent();
|
||||||
|
static void StartPipelinePrecompilation();
|
||||||
|
};
|
||||||
|
|
||||||
struct GuestSamplerState
|
struct GuestSamplerState
|
||||||
{
|
{
|
||||||
be<uint32_t> data[6];
|
be<uint32_t> data[6];
|
||||||
|
|
@ -379,6 +386,6 @@ enum GuestTextureAddress
|
||||||
D3DTADDRESS_BORDER = 6
|
D3DTADDRESS_BORDER = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
extern std::unique_ptr<GuestTexture> LoadTexture(uint8_t* data, size_t dataSize);
|
extern std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize);
|
||||||
|
|
||||||
extern void VideoConfigValueChangedCallback(class IConfigDef* config);
|
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 std::string filename(pair.first);
|
||||||
const uint32_t hashCount = pair.second;
|
const uint32_t hashCount = pair.second;
|
||||||
if (!sourceVfs.exists(filename))
|
if (!sourceVfs.exists(filename))
|
||||||
|
|
@ -136,7 +136,8 @@ static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFi
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
progressCallback(++journal.progressCounter);
|
journal.progressCounter += fileData.size();
|
||||||
|
progressCallback();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +201,25 @@ bool Installer::checkGameInstall(const std::filesystem::path &baseDirectory)
|
||||||
return std::filesystem::exists(baseDirectory / GameDirectory / GameExecutableFile);
|
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::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))
|
if (!std::filesystem::exists(targetDirectory) && !std::filesystem::create_directories(targetDirectory))
|
||||||
{
|
{
|
||||||
|
|
@ -265,45 +284,48 @@ 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)
|
||||||
{
|
{
|
||||||
|
sources = Sources();
|
||||||
|
|
||||||
// Parse the contents of the base game.
|
// Parse the contents of the base game.
|
||||||
std::unique_ptr<VirtualFileSystem> gameSource;
|
|
||||||
if (!input.gameSource.empty())
|
if (!input.gameSource.empty())
|
||||||
{
|
{
|
||||||
if (!parseContent(input.gameSource, gameSource, journal))
|
if (!parseContent(input.gameSource, sources.game, journal))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
journal.progressTotal += GameFilesSize;
|
if (!computeTotalSize({ GameFiles, GameFilesSize }, GameHashes, *sources.game, journal, sources.totalSize))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the contents of Update.
|
// Parse the contents of Update.
|
||||||
std::unique_ptr<VirtualFileSystem> updateSource;
|
|
||||||
if (!input.updateSource.empty())
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
journal.progressTotal += UpdateFilesSize;
|
if (!computeTotalSize({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *sources.update, journal, sources.totalSize))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the contents of the DLC Packs.
|
// 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)
|
for (const auto &path : input.dlcSources)
|
||||||
{
|
{
|
||||||
dlcSources.emplace_back();
|
sources.dlc.emplace_back();
|
||||||
DLCSource &dlcSource = dlcSources.back();
|
DLCSource &dlcSource = sources.dlc.back();
|
||||||
if (!parseContent(path, dlcSource.sourceVfs, journal))
|
if (!parseContent(path, dlcSource.sourceVfs, journal))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -346,17 +368,51 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
journal.progressTotal += dlcSource.filePairs.size();
|
if (!computeTotalSize(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, journal, sources.totalSize))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the base game.
|
// Add the total size in bytes as the journal progress.
|
||||||
if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *gameSource, targetDirectory / GameDirectory, GameExecutableFile, input.skipHashChecks, journal, progressCallback))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the update.
|
// Install the base game.
|
||||||
if (!copyFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *updateSource, targetDirectory / UpdateDirectory, UpdateExecutablePatchFile, input.skipHashChecks, journal, progressCallback))
|
if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *sources.game, targetDirectory / GameDirectory, GameExecutableFile, skipHashChecks, journal, progressCallback))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -374,6 +430,10 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
||||||
return false;
|
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.
|
// Replace the executable by renaming and deleting in a safe way.
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::path oldXexPath = targetDirectory / GameDirectory / (GameExecutableFile + OldExtension);
|
std::filesystem::path oldXexPath = targetDirectory / GameDirectory / (GameExecutableFile + OldExtension);
|
||||||
|
|
@ -396,20 +456,6 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
||||||
|
|
||||||
std::filesystem::remove(oldXexPath);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ enum class DLC {
|
||||||
Mazuri,
|
Mazuri,
|
||||||
Holoska,
|
Holoska,
|
||||||
ApotosShamar,
|
ApotosShamar,
|
||||||
EmpireCityAdabat
|
EmpireCityAdabat,
|
||||||
|
Count = EmpireCityAdabat
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Journal
|
struct Journal
|
||||||
|
|
@ -35,8 +36,8 @@ struct Journal
|
||||||
UnknownDLCType
|
UnknownDLCType
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t progressCounter = 0;
|
uint64_t progressCounter = 0;
|
||||||
uint32_t progressTotal = 0;
|
uint64_t progressTotal = 0;
|
||||||
std::list<std::filesystem::path> createdFiles;
|
std::list<std::filesystem::path> createdFiles;
|
||||||
std::set<std::filesystem::path> createdDirectories;
|
std::set<std::filesystem::path> createdDirectories;
|
||||||
Result lastResult = Result::Success;
|
Result lastResult = Result::Success;
|
||||||
|
|
@ -53,13 +54,29 @@ struct Installer
|
||||||
std::filesystem::path gameSource;
|
std::filesystem::path gameSource;
|
||||||
std::filesystem::path updateSource;
|
std::filesystem::path updateSource;
|
||||||
std::list<std::filesystem::path> dlcSources;
|
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 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 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 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);
|
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.
|
// Convenience method for checking if the specified file contains the game. This should be used when the user selects the file.
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
#include <user/config.h>
|
#include <user/config.h>
|
||||||
#include <user/paths.h>
|
#include <user/paths.h>
|
||||||
#include <kernel/xdbf.h>
|
#include <kernel/xdbf.h>
|
||||||
|
#include <install/installer.h>
|
||||||
|
#include <ui/installer_wizard.h>
|
||||||
|
|
||||||
#define GAME_XEX_PATH "game:\\default.xex"
|
#define GAME_XEX_PATH "game:\\default.xex"
|
||||||
|
|
||||||
|
|
@ -27,8 +29,7 @@ CodeCache g_codeCache;
|
||||||
XDBFWrapper g_xdbfWrapper;
|
XDBFWrapper g_xdbfWrapper;
|
||||||
std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;
|
std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;
|
||||||
|
|
||||||
// Name inspired from nt's entry point
|
void HostStartup()
|
||||||
void KiSystemStartup()
|
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
|
|
@ -39,7 +40,11 @@ void KiSystemStartup()
|
||||||
g_codeCache.Init();
|
g_codeCache.Init();
|
||||||
|
|
||||||
g_memory.Alloc(XMAIOBegin, 0xFFFF, MEM_COMMIT);
|
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 gameContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Game");
|
||||||
const auto updateContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Update");
|
const auto updateContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Update");
|
||||||
XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : ".");
|
XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : ".");
|
||||||
|
|
@ -136,15 +141,39 @@ uint32_t LdrLoadModule(const char* path)
|
||||||
return entry;
|
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();
|
Config::Load();
|
||||||
|
|
||||||
|
HostStartup();
|
||||||
|
|
||||||
|
Video::CreateHostDevice();
|
||||||
|
|
||||||
|
bool isGameInstalled = Installer::checkGameInstall(".");
|
||||||
|
if (forceInstaller || forceDLCInstaller || !isGameInstalled)
|
||||||
|
{
|
||||||
|
if (!InstallerWizard::Run(isGameInstalled && forceDLCInstaller))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AchievementData::Load();
|
AchievementData::Load();
|
||||||
|
|
||||||
KiSystemStartup();
|
KiSystemStartup();
|
||||||
|
|
||||||
uint32_t entry = LdrLoadModule(FileSystem::TransformPath(GAME_XEX_PATH));
|
uint32_t entry = LdrLoadModule(FileSystem::TransformPath(GAME_XEX_PATH));
|
||||||
|
|
||||||
|
Video::StartPipelinePrecompilation();
|
||||||
|
|
||||||
GuestThread::Start(entry);
|
GuestThread::Start(entry);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
902
UnleashedRecomp/ui/installer_wizard.cpp
Normal file
902
UnleashedRecomp/ui/installer_wizard.cpp
Normal file
|
|
@ -0,0 +1,902 @@
|
||||||
|
#include "installer_wizard.h"
|
||||||
|
|
||||||
|
#include <nfd.h>
|
||||||
|
|
||||||
|
#include <install/installer.h>
|
||||||
|
#include <gpu/video.h>
|
||||||
|
#include <ui/imgui_utils.h>
|
||||||
|
#include <ui/window.h>
|
||||||
|
|
||||||
|
#include <res/install_001_dds.h>
|
||||||
|
#include <res/install_002_dds.h>
|
||||||
|
#include <res/install_003_dds.h>
|
||||||
|
#include <res/install_004_dds.h>
|
||||||
|
#include <res/install_005_dds.h>
|
||||||
|
#include <res/install_006_dds.h>
|
||||||
|
#include <res/install_007_dds.h>
|
||||||
|
#include <res/install_008_dds.h>
|
||||||
|
|
||||||
|
#define SKIP_SOURCE_CHECKS 0
|
||||||
|
|
||||||
|
static constexpr double SCANLINES_ANIMATION_DURATION = 8.0;
|
||||||
|
|
||||||
|
static constexpr double TITLE_ANIMATION_TIME = SCANLINES_ANIMATION_DURATION + 8.0; // 8 frame delay
|
||||||
|
static constexpr double TITLE_ANIMATION_DURATION = 8.0;
|
||||||
|
|
||||||
|
static constexpr double CONTAINER_LINE_ANIMATION_DURATION = 8.0;
|
||||||
|
|
||||||
|
static constexpr double CONTAINER_OUTER_TIME = CONTAINER_LINE_ANIMATION_DURATION + 8.0; // 8 frame delay
|
||||||
|
static constexpr double CONTAINER_OUTER_DURATION = 8.0;
|
||||||
|
|
||||||
|
static constexpr double CONTAINER_INNER_TIME = CONTAINER_OUTER_TIME + CONTAINER_OUTER_DURATION + 8.0; // 8 frame delay
|
||||||
|
static constexpr double CONTAINER_INNER_DURATION = 8.0;
|
||||||
|
|
||||||
|
static constexpr double CONTAINER_BACKGROUND_TIME = CONTAINER_INNER_TIME + CONTAINER_INNER_DURATION + 8.0; // 8 frame delay
|
||||||
|
static constexpr double CONTAINER_BACKGROUND_DURATION = 16.0;
|
||||||
|
|
||||||
|
static constexpr double ALL_ANIMATIONS_FULL_DURATION = CONTAINER_BACKGROUND_TIME + CONTAINER_BACKGROUND_DURATION;
|
||||||
|
|
||||||
|
constexpr float IMAGE_X = 140.0f;
|
||||||
|
constexpr float IMAGE_Y = 106.0f;
|
||||||
|
constexpr float IMAGE_WIDTH = 512.0f;
|
||||||
|
constexpr float IMAGE_HEIGHT = 512.0f;
|
||||||
|
|
||||||
|
constexpr float CONTAINER_X = 510.0f;
|
||||||
|
constexpr float CONTAINER_Y = 225.0f;
|
||||||
|
constexpr float CONTAINER_WIDTH = 528.0f;
|
||||||
|
constexpr float CONTAINER_HEIGHT = 245.0f;
|
||||||
|
constexpr float SIDE_CONTAINER_WIDTH = CONTAINER_WIDTH / 2.0f;
|
||||||
|
|
||||||
|
constexpr float BOTTOM_X_GAP = 4.0f;
|
||||||
|
constexpr float BOTTOM_Y_GAP = 4.0f;
|
||||||
|
constexpr float SOURCE_BUTTON_WIDTH = 250.0f;
|
||||||
|
constexpr float SOURCE_BUTTON_GAP = 9.0f;
|
||||||
|
constexpr float BUTTON_HEIGHT = 22.0f;
|
||||||
|
constexpr float BUTTON_TEXT_GAP = 28.0f;
|
||||||
|
|
||||||
|
constexpr float BORDER_SIZE = 1.0f;
|
||||||
|
constexpr float BORDER_OVERSHOOT = 36.0f;
|
||||||
|
constexpr float FAKE_PROGRESS_RATIO = 0.25f;
|
||||||
|
|
||||||
|
static constexpr size_t GRID_SIZE = 9;
|
||||||
|
|
||||||
|
static ImFont *g_seuratFont;
|
||||||
|
static ImFont *g_dfsogeistdFont;
|
||||||
|
static ImFont *g_newRodinFont;
|
||||||
|
|
||||||
|
static double g_appearTime = 0.0;
|
||||||
|
static double g_disappearTime = DBL_MAX;
|
||||||
|
static bool g_isDisappearing = false;
|
||||||
|
static std::filesystem::path g_gameSourcePath;
|
||||||
|
static std::filesystem::path g_updateSourcePath;
|
||||||
|
static std::array<std::filesystem::path, int(DLC::Count)> g_dlcSourcePaths;
|
||||||
|
static std::array<std::unique_ptr<GuestTexture>, 8> g_installTextures;
|
||||||
|
static Journal g_installerJournal;
|
||||||
|
static Installer::Sources g_installerSources;
|
||||||
|
static uint64_t g_installerAvailableSize = 0;
|
||||||
|
static std::unique_ptr<std::thread> g_installerThread;
|
||||||
|
static double g_installerStartTime = 0.0;
|
||||||
|
static float g_installerProgressRatioCurrent = 0.0f;
|
||||||
|
static std::atomic<float> g_installerProgressRatioTarget = 0.0f;
|
||||||
|
static std::atomic<bool> g_installerFinished = false;
|
||||||
|
static bool g_installerFailed = false;
|
||||||
|
static std::string g_installerErrorMessage;
|
||||||
|
|
||||||
|
enum class WizardPage
|
||||||
|
{
|
||||||
|
Introduction,
|
||||||
|
SelectGameAndUpdate,
|
||||||
|
SelectDLC,
|
||||||
|
CheckSpace,
|
||||||
|
Installing,
|
||||||
|
InstallSucceeded,
|
||||||
|
InstallFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
static WizardPage g_currentPage = WizardPage::Introduction;
|
||||||
|
|
||||||
|
const char INSTALLER_TEXT[] = "INSTALLER";
|
||||||
|
const char INSTALLING_TEXT[] = "INSTALLING";
|
||||||
|
const char CREDITS_TEXT[] = "Sajid (RIP)";
|
||||||
|
|
||||||
|
const char *WIZARD_TEXT[] =
|
||||||
|
{
|
||||||
|
"Welcome to Unleashed Recompiled!\n\nMake sure you have a copy of Sonic Unleashed's files for Xbox 360 before proceeding with the installation.",
|
||||||
|
"Select the files for the Game and the Update. You can use digital dumps (PIRS), a folder with the game's contents or a disc image (ISO).",
|
||||||
|
"Select the files for the DLC Packs. These can be digital dumps (PIRS) or a folder with their contents.",
|
||||||
|
"The content will be installed to the program's folder. Please confirm you have enough space available.\n\n",
|
||||||
|
"Please wait while the content is being installed...",
|
||||||
|
"Installation is complete.\n\nThis project's been brought to you by:\n\n",
|
||||||
|
"Installation has failed.\n\nError:\n\n"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int WIZARD_INSTALL_TEXTURE_INDEX[] =
|
||||||
|
{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
7, // Force Werehog on InstallSucceeded.
|
||||||
|
5 // Force Eggman on InstallFailed.
|
||||||
|
};
|
||||||
|
|
||||||
|
const char GAME_SOURCE_TEXT[] = "FULL GAME";
|
||||||
|
const char UPDATE_SOURCE_TEXT[] = "UPDATE";
|
||||||
|
const char *DLC_SOURCE_TEXT[] =
|
||||||
|
{
|
||||||
|
"SPAGONIA",
|
||||||
|
"CHUN-NAN",
|
||||||
|
"MAZURI",
|
||||||
|
"HOLOSKA",
|
||||||
|
"APOTOS & SHAMAR",
|
||||||
|
"EMPIRE CITY & ADABAT",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char FILES_BUTTON_TEXT[] = "ADD FILES";
|
||||||
|
const char FOLDER_BUTTON_TEXT[] = "ADD FOLDER";
|
||||||
|
const char NEXT_BUTTON_TEXT[] = "NEXT";
|
||||||
|
const char SKIP_BUTTON_TEXT[] = "SKIP";
|
||||||
|
const char REQUIRED_SPACE_TEXT[] = "Required space";
|
||||||
|
const char AVAILABLE_SPACE_TEXT[] = "Available space";
|
||||||
|
|
||||||
|
static int DLCIndex(DLC dlc)
|
||||||
|
{
|
||||||
|
assert(dlc != DLC::Unknown);
|
||||||
|
return (int)(dlc) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double ComputeMotionInstaller(double timeAppear, double timeDisappear, double offset, double total)
|
||||||
|
{
|
||||||
|
return ComputeMotion(timeAppear, offset, total) * (1.0 - ComputeMotion(timeDisappear, ALL_ANIMATIONS_FULL_DURATION - offset - total, total));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawBackground()
|
||||||
|
{
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
drawList->AddRectFilled({ 0.0, 0.0 }, res, IM_COL32_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawLeftImage()
|
||||||
|
{
|
||||||
|
int installTextureIndex = WIZARD_INSTALL_TEXTURE_INDEX[int(g_currentPage)];
|
||||||
|
if (g_currentPage == WizardPage::Installing)
|
||||||
|
{
|
||||||
|
// Cycle through the available images while time passes during installation.
|
||||||
|
constexpr double InstallationSpeed = 1.0 / 15.0;
|
||||||
|
double installationTime = (ImGui::GetTime() - g_installerStartTime) * InstallationSpeed;
|
||||||
|
installTextureIndex += int(installationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
double imageAlpha = ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_BACKGROUND_TIME, CONTAINER_BACKGROUND_DURATION);
|
||||||
|
int a = std::lround(255.0 * imageAlpha);
|
||||||
|
GuestTexture *guestTexture = g_installTextures[installTextureIndex % g_installTextures.size()].get();
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
ImVec2 min = { Scale(IMAGE_X), Scale(IMAGE_Y) };
|
||||||
|
ImVec2 max = { Scale(IMAGE_X + IMAGE_WIDTH), Scale(IMAGE_Y + IMAGE_HEIGHT) };
|
||||||
|
drawList->AddImage(guestTexture, min, max, ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, a));
|
||||||
|
|
||||||
|
min.y = (min.y + max.y) / 2.0f;
|
||||||
|
drawList->AddRectFilledMultiColor(min, max, IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32(0, 0, 0, 255), IM_COL32(0, 0, 0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawScanlineBars()
|
||||||
|
{
|
||||||
|
constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0);
|
||||||
|
constexpr uint32_t COLOR1 = IM_COL32(203, 255, 0, 55);
|
||||||
|
constexpr uint32_t FADE_COLOR0 = IM_COL32(0, 0, 0, 255);
|
||||||
|
constexpr uint32_t FADE_COLOR1 = IM_COL32(0, 0, 0, 0);
|
||||||
|
constexpr uint32_t OUTLINE_COLOR = IM_COL32(115, 178, 104, 255);
|
||||||
|
|
||||||
|
float height = Scale(105.0f) * ComputeMotionInstaller(g_appearTime, g_disappearTime, 0.0, SCANLINES_ANIMATION_DURATION);
|
||||||
|
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
SetShaderModifier(IMGUI_SHADER_MODIFIER_SCANLINE);
|
||||||
|
|
||||||
|
// Top bar
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ 0.0f, 0.0f },
|
||||||
|
{ res.x, height },
|
||||||
|
COLOR0,
|
||||||
|
COLOR0,
|
||||||
|
COLOR1,
|
||||||
|
COLOR1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bottom bar
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ res.x, res.y },
|
||||||
|
{ 0.0f, res.y - height },
|
||||||
|
COLOR0,
|
||||||
|
COLOR0,
|
||||||
|
COLOR1,
|
||||||
|
COLOR1
|
||||||
|
);
|
||||||
|
|
||||||
|
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
|
||||||
|
|
||||||
|
// Installer text
|
||||||
|
// TODO: localise this.
|
||||||
|
const char *headerText = g_currentPage == WizardPage::Installing ? INSTALLING_TEXT : INSTALLER_TEXT;
|
||||||
|
int textAlpha = std::lround(255.0f * ComputeMotionInstaller(g_appearTime, g_disappearTime, TITLE_ANIMATION_TIME, TITLE_ANIMATION_DURATION));
|
||||||
|
DrawTextWithOutline<int>(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, textAlpha), headerText, 4, IM_COL32(0, 0, 0, textAlpha));
|
||||||
|
|
||||||
|
// Top bar line
|
||||||
|
drawList->AddLine
|
||||||
|
(
|
||||||
|
{ 0.0f, height },
|
||||||
|
{ res.x, height },
|
||||||
|
OUTLINE_COLOR,
|
||||||
|
Scale(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bottom bar line
|
||||||
|
drawList->AddLine
|
||||||
|
(
|
||||||
|
{ 0.0f, res.y - height },
|
||||||
|
{ res.x, res.y - height },
|
||||||
|
OUTLINE_COLOR,
|
||||||
|
Scale(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float AlignToNextGrid(float value)
|
||||||
|
{
|
||||||
|
return floor(value / GRID_SIZE) * GRID_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawContainer(ImVec2 min, ImVec2 max, bool useInnerColor)
|
||||||
|
{
|
||||||
|
double containerHeight = ComputeMotionInstaller(g_appearTime, g_disappearTime, 0.0, CONTAINER_LINE_ANIMATION_DURATION);
|
||||||
|
|
||||||
|
float center = (min.y + max.y) / 2.0f;
|
||||||
|
min.y = Lerp(center, min.y, containerHeight);
|
||||||
|
max.y = Lerp(center, max.y, containerHeight);
|
||||||
|
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
double outerAlpha = ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_OUTER_TIME, CONTAINER_OUTER_DURATION);
|
||||||
|
double innerAlpha = ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_INNER_TIME, CONTAINER_INNER_DURATION);
|
||||||
|
double backgroundAlpha = ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_BACKGROUND_TIME, CONTAINER_BACKGROUND_DURATION);
|
||||||
|
|
||||||
|
const uint32_t outerColor = IM_COL32(0, 60, 0, 128 * outerAlpha);
|
||||||
|
const uint32_t innerColor = IM_COL32(0, 40, 0, 96 * innerAlpha);
|
||||||
|
const uint32_t backgroundColor = IM_COL32(0, 60, 0, 96 * backgroundAlpha);
|
||||||
|
|
||||||
|
float gridSize = Scale(GRID_SIZE);
|
||||||
|
drawList->AddRectFilled(min, max, backgroundColor);
|
||||||
|
SetShaderModifier(IMGUI_SHADER_MODIFIER_CHECKERBOARD);
|
||||||
|
drawList->AddRectFilled(min, max, useInnerColor ? innerColor: outerColor);
|
||||||
|
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
|
||||||
|
|
||||||
|
// The draw area
|
||||||
|
drawList->PushClipRect({ min.x + gridSize * 2.0f, min.y + gridSize * 2.0f }, { max.x - gridSize * 2.0f + 1.0f, max.y - gridSize * 2.0f + 1.0f });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawDescriptionContainer()
|
||||||
|
{
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
ImVec2 descriptionMin = { Scale(AlignToNextGrid(CONTAINER_X)), Scale(AlignToNextGrid(CONTAINER_Y)) };
|
||||||
|
ImVec2 descriptionMax = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH)), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT)) };
|
||||||
|
DrawContainer(descriptionMin, descriptionMax, false);
|
||||||
|
|
||||||
|
char descriptionText[512];
|
||||||
|
strncpy(descriptionText, WIZARD_TEXT[int(g_currentPage)], sizeof(descriptionText) - 1);
|
||||||
|
|
||||||
|
if (g_currentPage == WizardPage::CheckSpace)
|
||||||
|
{
|
||||||
|
constexpr double DivisorGiB = (1024.0 * 1024.0 * 1024.0);
|
||||||
|
double requiredGiB = double(g_installerSources.totalSize) / DivisorGiB;
|
||||||
|
double availableGiB = double(g_installerAvailableSize) / DivisorGiB;
|
||||||
|
snprintf(descriptionText, sizeof(descriptionText), "%s%s: %2.2f GiB\n\n%s: %2.2f GiB", WIZARD_TEXT[int(g_currentPage)], REQUIRED_SPACE_TEXT, requiredGiB, AVAILABLE_SPACE_TEXT, availableGiB);
|
||||||
|
}
|
||||||
|
else if (g_currentPage == WizardPage::InstallSucceeded)
|
||||||
|
{
|
||||||
|
strncat(descriptionText, CREDITS_TEXT, sizeof(descriptionText) - 1);
|
||||||
|
}
|
||||||
|
else if (g_currentPage == WizardPage::InstallFailed)
|
||||||
|
{
|
||||||
|
strncat(descriptionText, g_installerErrorMessage.c_str(), sizeof(descriptionText) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
double textAlpha = ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_BACKGROUND_TIME, CONTAINER_BACKGROUND_DURATION);
|
||||||
|
auto clipRectMin = drawList->GetClipRectMin();
|
||||||
|
auto clipRectMax = drawList->GetClipRectMax();
|
||||||
|
auto size = Scale(26.0f);
|
||||||
|
drawList->AddText
|
||||||
|
(
|
||||||
|
g_seuratFont,
|
||||||
|
size,
|
||||||
|
{ clipRectMin.x, clipRectMin.y },
|
||||||
|
IM_COL32(255, 255, 255, 255 * textAlpha),
|
||||||
|
descriptionText,
|
||||||
|
0,
|
||||||
|
clipRectMax.x - clipRectMin.x
|
||||||
|
);
|
||||||
|
|
||||||
|
drawList->PopClipRect();
|
||||||
|
|
||||||
|
ImVec2 sideMin = { descriptionMax.x, descriptionMin.y };
|
||||||
|
ImVec2 sideMax = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH + SIDE_CONTAINER_WIDTH)), descriptionMax.y };
|
||||||
|
DrawContainer(sideMin, sideMax, true);
|
||||||
|
drawList->PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawButtonContainer(ImVec2 min, ImVec2 max, int baser, int baseg, float alpha)
|
||||||
|
{
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
SetShaderModifier(IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON);
|
||||||
|
drawList->AddRectFilledMultiColor(min, max, IM_COL32(baser, baseg + 130, 0, 223 * alpha), IM_COL32(baser, baseg + 130, 0, 178 * alpha), IM_COL32(baser, baseg + 130, 0, 223 * alpha), IM_COL32(baser, baseg + 130, 0, 178 * alpha));
|
||||||
|
drawList->AddRectFilledMultiColor(min, max, IM_COL32(baser, baseg, 0, 13 * alpha), IM_COL32(baser, baseg, 0, 0), IM_COL32(baser, baseg, 0, 55 * alpha), IM_COL32(baser, baseg, 0, 6 * alpha));
|
||||||
|
drawList->AddRectFilledMultiColor(min, max, IM_COL32(baser, baseg + 130, 0, 13 * alpha), IM_COL32(baser, baseg + 130, 0, 111 * alpha), IM_COL32(baser, baseg + 130, 0, 0), IM_COL32(baser, baseg + 130, 0, 55 * alpha));
|
||||||
|
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawButton(ImVec2 min, ImVec2 max, const char *buttonText, bool sourceButton, bool buttonEnabled, bool &buttonPressed)
|
||||||
|
{
|
||||||
|
buttonPressed = false;
|
||||||
|
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
float alpha = ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_BACKGROUND_TIME, CONTAINER_BACKGROUND_DURATION);
|
||||||
|
if (!buttonEnabled)
|
||||||
|
{
|
||||||
|
alpha *= 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int baser = 0;
|
||||||
|
int baseg = 0;
|
||||||
|
if (!sourceButton && buttonEnabled && (alpha >= 1.0f) && ImGui::IsMouseHoveringRect(min, max, false))
|
||||||
|
{
|
||||||
|
baser = 48;
|
||||||
|
baseg = 32;
|
||||||
|
|
||||||
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
||||||
|
{
|
||||||
|
buttonPressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawButtonContainer(min, max, baser, baseg, alpha);
|
||||||
|
|
||||||
|
float size = Scale(sourceButton ? 15.0f : 20.0f);
|
||||||
|
ImFont *font = sourceButton ? g_newRodinFont : g_dfsogeistdFont;
|
||||||
|
ImVec2 textSize = font->CalcTextSizeA(size, FLT_MAX, 0.0f, buttonText);
|
||||||
|
|
||||||
|
min.x += ((max.x - min.x) - textSize.x) / 2.0f;
|
||||||
|
min.y += ((max.y - min.y) - textSize.y) / 2.0f;
|
||||||
|
|
||||||
|
if (!sourceButton)
|
||||||
|
{
|
||||||
|
// Fixes slight misalignment caused by this particular font.
|
||||||
|
min.y -= Scale(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetGradient
|
||||||
|
(
|
||||||
|
min,
|
||||||
|
{ min.x + textSize.x, min.y + textSize.y },
|
||||||
|
IM_COL32(baser + 192, 255, 0, 255),
|
||||||
|
IM_COL32(baser + 128, baseg + 170, 0, 255)
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawTextWithOutline<float>
|
||||||
|
(
|
||||||
|
font,
|
||||||
|
size,
|
||||||
|
min,
|
||||||
|
IM_COL32(255, 255, 255, 255 * alpha),
|
||||||
|
buttonText,
|
||||||
|
sourceButton ? 1.0f : 1.5f,
|
||||||
|
IM_COL32(baser, baseg, 0, 255 * alpha)
|
||||||
|
);
|
||||||
|
|
||||||
|
ResetGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SourceColumn
|
||||||
|
{
|
||||||
|
SourceColumnLeft,
|
||||||
|
SourceColumnMiddle,
|
||||||
|
SourceColumnRight
|
||||||
|
};
|
||||||
|
|
||||||
|
static void DrawSourceButton(SourceColumn sourceColumn, float yRatio, const char *sourceText, bool sourceSet)
|
||||||
|
{
|
||||||
|
bool buttonPressed;
|
||||||
|
float minX, maxX;
|
||||||
|
switch (sourceColumn)
|
||||||
|
{
|
||||||
|
case SourceColumnLeft:
|
||||||
|
minX = Scale(AlignToNextGrid(CONTAINER_X) + SOURCE_BUTTON_GAP);
|
||||||
|
maxX = Scale(AlignToNextGrid(CONTAINER_X) + SOURCE_BUTTON_GAP + SOURCE_BUTTON_WIDTH);
|
||||||
|
break;
|
||||||
|
case SourceColumnMiddle:
|
||||||
|
minX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH / 2.0f) - SOURCE_BUTTON_WIDTH / 2.0f);
|
||||||
|
maxX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH / 2.0f) + SOURCE_BUTTON_WIDTH / 2.0f);
|
||||||
|
break;
|
||||||
|
case SourceColumnRight:
|
||||||
|
minX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - SOURCE_BUTTON_GAP - SOURCE_BUTTON_WIDTH);
|
||||||
|
maxX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - SOURCE_BUTTON_GAP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
float minusY = (SOURCE_BUTTON_GAP + BUTTON_HEIGHT) * yRatio;
|
||||||
|
ImVec2 min = { minX, Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) - SOURCE_BUTTON_GAP - BUTTON_HEIGHT - minusY) };
|
||||||
|
ImVec2 max = { maxX, Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) - SOURCE_BUTTON_GAP - minusY) };
|
||||||
|
DrawButton(min, max, sourceText, true, sourceSet, buttonPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawProgressBar(float progressRatio)
|
||||||
|
{
|
||||||
|
auto &res = ImGui::GetIO().DisplaySize;
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
float alpha = 1.0;
|
||||||
|
const uint32_t innerColor0 = IM_COL32(0, 65, 0, 255 * alpha);
|
||||||
|
const uint32_t innerColor1 = IM_COL32(0, 32, 0, 255 * alpha);
|
||||||
|
float xPadding = Scale(6.0f);
|
||||||
|
float yPadding = Scale(3.0f);
|
||||||
|
ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP) };
|
||||||
|
ImVec2 max = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP + BUTTON_HEIGHT) };
|
||||||
|
|
||||||
|
DrawButtonContainer(min, max, 0, 0, alpha);
|
||||||
|
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ min.x + xPadding, min.y + yPadding },
|
||||||
|
{ max.x - xPadding, max.y - yPadding },
|
||||||
|
innerColor0,
|
||||||
|
innerColor0,
|
||||||
|
innerColor1,
|
||||||
|
innerColor1
|
||||||
|
);
|
||||||
|
|
||||||
|
const uint32_t sliderColor0 = IM_COL32(57, 241, 0, 255 * alpha);
|
||||||
|
const uint32_t sliderColor1 = IM_COL32(2, 106, 0, 255 * alpha);
|
||||||
|
xPadding += Scale(1.0f);
|
||||||
|
yPadding += Scale(1.0f);
|
||||||
|
|
||||||
|
ImVec2 sliderMin = { min.x + xPadding, min.y + yPadding };
|
||||||
|
ImVec2 sliderMax = { max.x - xPadding, max.y - yPadding };
|
||||||
|
sliderMax.x = sliderMin.x + (sliderMax.x - sliderMin.x) * progressRatio;
|
||||||
|
drawList->AddRectFilledMultiColor(sliderMin, sliderMax, sliderColor0, sliderColor0, sliderColor1, sliderColor1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ConvertPathSet(const nfdpathset_t *pathSet, std::list<std::filesystem::path> &filePaths)
|
||||||
|
{
|
||||||
|
nfdpathsetsize_t pathSetCount = 0;
|
||||||
|
if (NFD_PathSet_GetCount(pathSet, &pathSetCount) != NFD_OKAY)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (nfdpathsetsize_t i = 0; i < pathSetCount; i++)
|
||||||
|
{
|
||||||
|
char *pathSetPath = nullptr;
|
||||||
|
if (NFD_PathSet_GetPathU8(pathSet, i, &pathSetPath) != NFD_OKAY)
|
||||||
|
{
|
||||||
|
filePaths.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
filePaths.emplace_back(std::filesystem::path(std::u8string_view((const char8_t *)(pathSetPath))));
|
||||||
|
NFD_PathSet_FreePathU8(pathSetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ShowFilesPicker(std::list<std::filesystem::path> &filePaths)
|
||||||
|
{
|
||||||
|
filePaths.clear();
|
||||||
|
|
||||||
|
const nfdpathset_t *pathSet;
|
||||||
|
nfdresult_t result = NFD_OpenDialogMultipleU8(&pathSet, nullptr, 0, nullptr);
|
||||||
|
if (result == NFD_OKAY)
|
||||||
|
{
|
||||||
|
bool pathsConverted = ConvertPathSet(pathSet, filePaths);
|
||||||
|
NFD_PathSet_Free(pathSet);
|
||||||
|
return pathsConverted;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ShowFoldersPicker(std::list<std::filesystem::path> &folderPaths)
|
||||||
|
{
|
||||||
|
folderPaths.clear();
|
||||||
|
|
||||||
|
const nfdpathset_t *pathSet;
|
||||||
|
nfdresult_t result = NFD_PickFolderMultipleU8(&pathSet, nullptr);
|
||||||
|
if (result == NFD_OKAY)
|
||||||
|
{
|
||||||
|
bool pathsConverted = ConvertPathSet(pathSet, folderPaths);
|
||||||
|
NFD_PathSet_Free(pathSet);
|
||||||
|
return pathsConverted;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseSourcePaths(std::list<std::filesystem::path> &paths)
|
||||||
|
{
|
||||||
|
assert((g_currentPage == WizardPage::SelectGameAndUpdate) || (g_currentPage == WizardPage::SelectDLC));
|
||||||
|
|
||||||
|
std::list<std::filesystem::path> failedPaths;
|
||||||
|
if (g_currentPage == WizardPage::SelectGameAndUpdate)
|
||||||
|
{
|
||||||
|
for (const std::filesystem::path &path : paths)
|
||||||
|
{
|
||||||
|
if (Installer::parseGame(path))
|
||||||
|
{
|
||||||
|
g_gameSourcePath = path;
|
||||||
|
}
|
||||||
|
else if (Installer::parseUpdate(path))
|
||||||
|
{
|
||||||
|
g_updateSourcePath = path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failedPaths.push_back(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(g_currentPage == WizardPage::SelectDLC)
|
||||||
|
{
|
||||||
|
for (const std::filesystem::path &path : paths)
|
||||||
|
{
|
||||||
|
DLC dlc = Installer::parseDLC(path);
|
||||||
|
if (dlc != DLC::Unknown)
|
||||||
|
{
|
||||||
|
g_dlcSourcePaths[DLCIndex(dlc)] = path;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failedPaths.push_back(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failedPaths.empty())
|
||||||
|
{
|
||||||
|
// TODO: Show list of content that failed to parse with a prompt.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawSourcePickers()
|
||||||
|
{
|
||||||
|
bool buttonPressed = false;
|
||||||
|
std::list<std::filesystem::path> paths;
|
||||||
|
if (g_currentPage == WizardPage::SelectGameAndUpdate || g_currentPage == WizardPage::SelectDLC)
|
||||||
|
{
|
||||||
|
ImVec2 textSize = g_dfsogeistdFont->CalcTextSizeA(20.0f, FLT_MAX, 0.0f, FILES_BUTTON_TEXT);
|
||||||
|
textSize.x += BUTTON_TEXT_GAP;
|
||||||
|
|
||||||
|
ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP) };
|
||||||
|
ImVec2 max = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP + textSize.x), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP + BUTTON_HEIGHT) };
|
||||||
|
DrawButton(min, max, FILES_BUTTON_TEXT, false, true, buttonPressed);
|
||||||
|
if (buttonPressed && ShowFilesPicker(paths))
|
||||||
|
{
|
||||||
|
ParseSourcePaths(paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
min.x += Scale(BOTTOM_X_GAP + textSize.x);
|
||||||
|
textSize = g_dfsogeistdFont->CalcTextSizeA(20.0f, FLT_MAX, 0.0f, FOLDER_BUTTON_TEXT);
|
||||||
|
textSize.x += BUTTON_TEXT_GAP;
|
||||||
|
|
||||||
|
max.x = min.x + Scale(textSize.x);
|
||||||
|
DrawButton(min, max, FOLDER_BUTTON_TEXT, false, true, buttonPressed);
|
||||||
|
if (buttonPressed && ShowFoldersPicker(paths))
|
||||||
|
{
|
||||||
|
ParseSourcePaths(paths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawSources()
|
||||||
|
{
|
||||||
|
if (g_currentPage == WizardPage::SelectGameAndUpdate)
|
||||||
|
{
|
||||||
|
DrawSourceButton(SourceColumnMiddle, 1.5f, GAME_SOURCE_TEXT, !g_gameSourcePath.empty());
|
||||||
|
DrawSourceButton(SourceColumnMiddle, 0.5f, UPDATE_SOURCE_TEXT, !g_updateSourcePath.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_currentPage == WizardPage::SelectDLC)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
DrawSourceButton((i < 3) ? SourceColumnLeft : SourceColumnRight, float(i % 3), DLC_SOURCE_TEXT[i], !g_dlcSourcePaths[i].empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawInstallingProgress()
|
||||||
|
{
|
||||||
|
if (g_currentPage == WizardPage::Installing)
|
||||||
|
{
|
||||||
|
float ratioTarget = g_installerProgressRatioTarget.load();
|
||||||
|
g_installerProgressRatioCurrent += (4.0f * ImGui::GetIO().DeltaTime * (ratioTarget - g_installerProgressRatioCurrent));
|
||||||
|
DrawProgressBar(g_installerProgressRatioCurrent);
|
||||||
|
|
||||||
|
if (g_installerFinished)
|
||||||
|
{
|
||||||
|
g_installerThread->join();
|
||||||
|
g_installerThread.reset();
|
||||||
|
g_currentPage = g_installerFailed ? WizardPage::InstallFailed : WizardPage::InstallSucceeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InstallerThread()
|
||||||
|
{
|
||||||
|
if (!Installer::install(g_installerSources, ".", false, g_installerJournal, [&]() {
|
||||||
|
g_installerProgressRatioTarget = float(double(g_installerJournal.progressCounter) / double(g_installerJournal.progressTotal));
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
g_installerFailed = true;
|
||||||
|
g_installerErrorMessage = g_installerJournal.lastErrorMessage;
|
||||||
|
|
||||||
|
// Delete all files that were copied.
|
||||||
|
Installer::rollback(g_installerJournal);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_installerFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InstallerStart()
|
||||||
|
{
|
||||||
|
g_installerStartTime = ImGui::GetTime();
|
||||||
|
g_installerProgressRatioCurrent = 0.0f;
|
||||||
|
g_installerProgressRatioTarget = 0.0f;
|
||||||
|
g_installerFailed = false;
|
||||||
|
g_installerFinished = false;
|
||||||
|
g_installerThread = std::make_unique<std::thread>(InstallerThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool InstallerParseSources()
|
||||||
|
{
|
||||||
|
std::filesystem::space_info spaceInfo = std::filesystem::space(".");
|
||||||
|
g_installerAvailableSize = spaceInfo.available;
|
||||||
|
|
||||||
|
Installer::Input installerInput;
|
||||||
|
installerInput.gameSource = g_gameSourcePath;
|
||||||
|
installerInput.updateSource = g_updateSourcePath;
|
||||||
|
|
||||||
|
for (std::filesystem::path &path : g_dlcSourcePaths) {
|
||||||
|
if (!path.empty())
|
||||||
|
{
|
||||||
|
installerInput.dlcSources.push_back(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Installer::parseSources(installerInput, g_installerJournal, g_installerSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawNextButton()
|
||||||
|
{
|
||||||
|
if (g_currentPage != WizardPage::Installing) {
|
||||||
|
bool nextButtonEnabled = !g_isDisappearing;
|
||||||
|
#if !SKIP_SOURCE_CHECKS
|
||||||
|
if (nextButtonEnabled && g_currentPage == WizardPage::SelectGameAndUpdate)
|
||||||
|
{
|
||||||
|
nextButtonEnabled = !g_gameSourcePath.empty() && !g_updateSourcePath.empty();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool skipButton = false;
|
||||||
|
if (g_currentPage == WizardPage::SelectDLC)
|
||||||
|
{
|
||||||
|
skipButton = std::all_of(g_dlcSourcePaths.begin(), g_dlcSourcePaths.end(), [](const std::filesystem::path &path) { return path.empty(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *buttonText = skipButton ? SKIP_BUTTON_TEXT : NEXT_BUTTON_TEXT;
|
||||||
|
ImVec2 textSize = g_newRodinFont->CalcTextSizeA(20.0f, FLT_MAX, 0.0f, buttonText);
|
||||||
|
textSize.x += BUTTON_TEXT_GAP;
|
||||||
|
|
||||||
|
ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - textSize.x - BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP) };
|
||||||
|
ImVec2 max = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP + BUTTON_HEIGHT) };
|
||||||
|
|
||||||
|
bool buttonPressed = false;
|
||||||
|
DrawButton(min, max, buttonText, false, nextButtonEnabled, buttonPressed);
|
||||||
|
|
||||||
|
if (buttonPressed)
|
||||||
|
{
|
||||||
|
XexPatcher::Result patcherResult;
|
||||||
|
if (g_currentPage == WizardPage::SelectGameAndUpdate && (patcherResult = Installer::checkGameUpdateCompatibility(g_gameSourcePath, g_updateSourcePath), patcherResult != XexPatcher::Result::Success))
|
||||||
|
{
|
||||||
|
// TODO: Show error prompt for compatibility failure check. Tell user to try with different files.
|
||||||
|
}
|
||||||
|
else if (g_currentPage == WizardPage::SelectDLC && !InstallerParseSources())
|
||||||
|
{
|
||||||
|
// TODO: Show an error that the sources were unable to be parsed. Ask to try again.
|
||||||
|
}
|
||||||
|
else if (g_currentPage == WizardPage::InstallSucceeded)
|
||||||
|
{
|
||||||
|
g_isDisappearing = true;
|
||||||
|
g_disappearTime = ImGui::GetTime();
|
||||||
|
}
|
||||||
|
else if (g_currentPage == WizardPage::InstallFailed)
|
||||||
|
{
|
||||||
|
g_currentPage = WizardPage::Introduction;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_currentPage = WizardPage(int(g_currentPage) + 1);
|
||||||
|
|
||||||
|
if (g_currentPage == WizardPage::Installing)
|
||||||
|
{
|
||||||
|
InstallerStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawHorizontalBorder(bool bottomBorder)
|
||||||
|
{
|
||||||
|
const uint32_t FADE_COLOR_LEFT = IM_COL32(155, 155, 155, 0);
|
||||||
|
const uint32_t SOLID_COLOR = IM_COL32(155, 200, 155, 255);
|
||||||
|
const uint32_t FADE_COLOR_RIGHT = IM_COL32(155, 225, 155, 0);
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
double borderScale = 1.0 - ComputeMotionInstaller(g_appearTime, g_disappearTime, 0.0, CONTAINER_LINE_ANIMATION_DURATION);
|
||||||
|
float midX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH / 5));
|
||||||
|
float minX = std::lerp(Scale(AlignToNextGrid(CONTAINER_X) - BORDER_SIZE - BORDER_OVERSHOOT), midX, borderScale);
|
||||||
|
float maxX = std::lerp(Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH + SIDE_CONTAINER_WIDTH) + BORDER_OVERSHOOT), midX, borderScale);
|
||||||
|
float minY = bottomBorder ? Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT)) : Scale(AlignToNextGrid(CONTAINER_Y) - BORDER_SIZE);
|
||||||
|
float maxY = minY + Scale(BORDER_SIZE);
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ minX, minY },
|
||||||
|
{ midX, maxY },
|
||||||
|
FADE_COLOR_LEFT,
|
||||||
|
SOLID_COLOR,
|
||||||
|
SOLID_COLOR,
|
||||||
|
FADE_COLOR_LEFT
|
||||||
|
);
|
||||||
|
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ midX, minY },
|
||||||
|
{ maxX, maxY },
|
||||||
|
SOLID_COLOR,
|
||||||
|
FADE_COLOR_RIGHT,
|
||||||
|
FADE_COLOR_RIGHT,
|
||||||
|
SOLID_COLOR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawVerticalBorder(bool rightBorder)
|
||||||
|
{
|
||||||
|
const uint32_t SOLID_COLOR = IM_COL32(155, rightBorder ? 225 : 155, 155, 255);
|
||||||
|
const uint32_t FADE_COLOR = IM_COL32(155, rightBorder ? 225 : 155, 155, 0);
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
double borderScale = 1.0 - ComputeMotionInstaller(g_appearTime, g_disappearTime, 0.0, CONTAINER_LINE_ANIMATION_DURATION);
|
||||||
|
float minX = rightBorder ? Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH)) : Scale(AlignToNextGrid(CONTAINER_X) - BORDER_SIZE);
|
||||||
|
float maxX = minX + Scale(BORDER_SIZE);
|
||||||
|
float midY = Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT / 2));
|
||||||
|
float minY = std::lerp(Scale(AlignToNextGrid(CONTAINER_Y) - BORDER_OVERSHOOT), midY, borderScale);
|
||||||
|
float maxY = std::lerp(Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BORDER_OVERSHOOT), midY, borderScale);
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ minX, minY },
|
||||||
|
{ maxX, midY },
|
||||||
|
FADE_COLOR,
|
||||||
|
FADE_COLOR,
|
||||||
|
SOLID_COLOR,
|
||||||
|
SOLID_COLOR
|
||||||
|
);
|
||||||
|
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ minX, midY },
|
||||||
|
{ maxX, maxY },
|
||||||
|
SOLID_COLOR,
|
||||||
|
SOLID_COLOR,
|
||||||
|
FADE_COLOR,
|
||||||
|
FADE_COLOR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawBorders()
|
||||||
|
{
|
||||||
|
DrawHorizontalBorder(false);
|
||||||
|
DrawHorizontalBorder(true);
|
||||||
|
DrawVerticalBorder(false);
|
||||||
|
DrawVerticalBorder(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstallerWizard::Init()
|
||||||
|
{
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
constexpr float FONT_SCALE = 2.0f;
|
||||||
|
g_seuratFont = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 26.0f * FONT_SCALE);
|
||||||
|
g_dfsogeistdFont = io.Fonts->AddFontFromFileTTF("DFSoGeiStd-W7.otf", 48.0f * FONT_SCALE);
|
||||||
|
g_newRodinFont = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE);
|
||||||
|
g_installTextures[0] = LoadTexture(g_install001DDS, g_install001DDS_size);
|
||||||
|
g_installTextures[1] = LoadTexture(g_install002DDS, g_install002DDS_size);
|
||||||
|
g_installTextures[2] = LoadTexture(g_install003DDS, g_install003DDS_size);
|
||||||
|
g_installTextures[3] = LoadTexture(g_install004DDS, g_install004DDS_size);
|
||||||
|
g_installTextures[4] = LoadTexture(g_install005DDS, g_install005DDS_size);
|
||||||
|
g_installTextures[5] = LoadTexture(g_install006DDS, g_install006DDS_size);
|
||||||
|
g_installTextures[6] = LoadTexture(g_install007DDS, g_install007DDS_size);
|
||||||
|
g_installTextures[7] = LoadTexture(g_install008DDS, g_install008DDS_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstallerWizard::Draw()
|
||||||
|
{
|
||||||
|
if (!s_isVisible)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawBackground();
|
||||||
|
DrawLeftImage();
|
||||||
|
DrawScanlineBars();
|
||||||
|
DrawDescriptionContainer();
|
||||||
|
DrawSourcePickers();
|
||||||
|
DrawSources();
|
||||||
|
DrawInstallingProgress();
|
||||||
|
DrawNextButton();
|
||||||
|
DrawBorders();
|
||||||
|
|
||||||
|
if (g_isDisappearing)
|
||||||
|
{
|
||||||
|
const double disappearDuration = ALL_ANIMATIONS_FULL_DURATION / 60.0;
|
||||||
|
if (ImGui::GetTime() > (g_disappearTime + disappearDuration))
|
||||||
|
{
|
||||||
|
s_isVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstallerWizard::Shutdown()
|
||||||
|
{
|
||||||
|
if (g_installerThread != nullptr)
|
||||||
|
{
|
||||||
|
g_installerThread->join();
|
||||||
|
g_installerThread.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_installerSources.game.reset();
|
||||||
|
g_installerSources.update.reset();
|
||||||
|
g_installerSources.dlc.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstallerWizard::Run(bool skipGame)
|
||||||
|
{
|
||||||
|
NFD_Init();
|
||||||
|
|
||||||
|
if (skipGame)
|
||||||
|
{
|
||||||
|
g_currentPage = WizardPage::SelectDLC;
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::SetCursorAllowed(true);
|
||||||
|
s_isVisible = true;
|
||||||
|
|
||||||
|
while (s_isVisible)
|
||||||
|
{
|
||||||
|
SDL_PumpEvents();
|
||||||
|
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
|
||||||
|
Window::Update();
|
||||||
|
Video::HostPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::SetCursorAllowed(false);
|
||||||
|
NFD_Quit();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
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);
|
||||||
|
};
|
||||||
|
|
@ -982,12 +982,11 @@ void OptionsMenu::Init()
|
||||||
|
|
||||||
void OptionsMenu::Draw()
|
void OptionsMenu::Draw()
|
||||||
{
|
{
|
||||||
auto pInputState = SWA::CInputState::GetInstance();
|
|
||||||
|
|
||||||
if (!s_isVisible)
|
if (!s_isVisible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// We've entered the menu now, no need to check this.
|
// We've entered the menu now, no need to check this.
|
||||||
|
auto pInputState = SWA::CInputState::GetInstance();
|
||||||
if (pInputState->GetPadState().IsReleased(SWA::eKeyState_A))
|
if (pInputState->GetPadState().IsReleased(SWA::eKeyState_A))
|
||||||
g_isEnterKeyBuffered = false;
|
g_isEnterKeyBuffered = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||||
Window::s_isFocused = true;
|
Window::s_isFocused = true;
|
||||||
SDL_ShowCursor(Window::IsFullscreen() ? SDL_DISABLE : SDL_ENABLE);
|
SDL_ShowCursor(Window::IsFullscreen() && !Window::s_cursorAllowed ? SDL_DISABLE : SDL_ENABLE);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_RESTORED:
|
case SDL_WINDOWEVENT_RESTORED:
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public:
|
||||||
|
|
||||||
inline static bool s_isFocused;
|
inline static bool s_isFocused;
|
||||||
inline static bool s_isIconNight;
|
inline static bool s_isIconNight;
|
||||||
|
inline static bool s_cursorAllowed = false;
|
||||||
|
|
||||||
static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize)
|
static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize)
|
||||||
{
|
{
|
||||||
|
|
@ -76,7 +77,7 @@ public:
|
||||||
if (isEnabled)
|
if (isEnabled)
|
||||||
{
|
{
|
||||||
SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||||
SDL_ShowCursor(SDL_DISABLE);
|
SDL_ShowCursor(s_cursorAllowed ? SDL_ENABLE : SDL_DISABLE);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -88,6 +89,14 @@ public:
|
||||||
return isEnabled;
|
return isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SetCursorAllowed(bool isCursorAllowed)
|
||||||
|
{
|
||||||
|
s_cursorAllowed = isCursorAllowed;
|
||||||
|
|
||||||
|
// Refresh fullscreen state to enable the right cursor behavior.
|
||||||
|
SetFullscreen(IsFullscreen());
|
||||||
|
}
|
||||||
|
|
||||||
static bool IsMaximised()
|
static bool IsMaximised()
|
||||||
{
|
{
|
||||||
return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_MAXIMIZED;
|
return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_MAXIMIZED;
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7183c6e0e90480fe0f2c270c87cea5cd5df7d5c3
|
Subproject commit d9063dd234b92fd7ab35e72d1839d0fcfdc83456
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
"features": [ "sdl2-binding" ]
|
"features": [ "sdl2-binding" ]
|
||||||
},
|
},
|
||||||
"magic-enum",
|
"magic-enum",
|
||||||
"readerwriterqueue"
|
"readerwriterqueue",
|
||||||
|
"nativefiledialog-extended"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue