mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-19 06:27:02 -04:00
state share feature
This commit is contained in:
@@ -220,6 +220,8 @@ set(GAME_INCLUDE_DIRS
|
||||
set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd
|
||||
aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient)
|
||||
|
||||
list(APPEND GAME_LIBS libzstd_static)
|
||||
|
||||
if (DUSK_MOVIE_SUPPORT)
|
||||
if (TARGET libjpeg-turbo::turbojpeg-static)
|
||||
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
|
||||
|
||||
@@ -1373,6 +1373,8 @@ set(DUSK_FILES
|
||||
src/dusk/imgui/ImGuiStubLog.cpp
|
||||
src/dusk/imgui/ImGuiMapLoader.cpp
|
||||
src/dusk/imgui/ImGuiSaveEditor.cpp
|
||||
src/dusk/imgui/ImGuiStateShare.hpp
|
||||
src/dusk/imgui/ImGuiStateShare.cpp
|
||||
src/dusk/offset_ptr.cpp
|
||||
src/dusk/OSContext.cpp
|
||||
src/dusk/OSThread.cpp
|
||||
|
||||
@@ -17,6 +17,7 @@ constexpr const char* SHOW_HEAP_VIEWER = "F4";
|
||||
constexpr const char* SHOW_STUB_LOG = "F5";
|
||||
constexpr const char* SHOW_CAMERA_DEBUG = "F6";
|
||||
constexpr const char* SHOW_AUDIO_DEBUG = "F7";
|
||||
constexpr const char* SHOW_STATE_SHARE = "F8";
|
||||
|
||||
constexpr const char* TURBO = "Tab";
|
||||
|
||||
|
||||
@@ -269,6 +269,7 @@ namespace dusk {
|
||||
m_menuTools.ShowAudioDebug();
|
||||
m_menuTools.ShowSaveEditor();
|
||||
}
|
||||
m_menuTools.ShowStateShare();
|
||||
DuskDebugPad(); // temporary, remove later
|
||||
|
||||
// Only show cursor when menu or any windows are open
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace dusk {
|
||||
ImGui::MenuItem("Map Loader", nullptr, &m_showMapLoader);
|
||||
ImGui::MenuItem("Player Info", nullptr, &m_showPlayerInfo);
|
||||
ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor);
|
||||
ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare);
|
||||
ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug);
|
||||
|
||||
if (!dusk::IsGameLaunched) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiSaveEditor.hpp"
|
||||
#include "ImGuiStateShare.hpp"
|
||||
|
||||
namespace dusk {
|
||||
class ImGuiMenuTools {
|
||||
@@ -23,6 +24,7 @@ namespace dusk {
|
||||
void ShowPlayerInfo();
|
||||
void ShowAudioDebug();
|
||||
void ShowSaveEditor();
|
||||
void ShowStateShare();
|
||||
|
||||
private:
|
||||
bool m_showDebugOverlay = false;
|
||||
@@ -57,6 +59,9 @@ namespace dusk {
|
||||
|
||||
bool m_showSaveEditor = false;
|
||||
ImGuiSaveEditor m_saveEditor;
|
||||
|
||||
bool m_showStateShare = false;
|
||||
ImGuiStateShare m_stateShare;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "ImGuiStateShare.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "ImGuiConsole.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "fmt/format.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "dusk/main.h"
|
||||
|
||||
#include <zstd.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct StateSharePacket {
|
||||
char stageName[8];
|
||||
int8_t roomNo;
|
||||
int8_t layer;
|
||||
int16_t startPoint;
|
||||
// followed by raw dSv_info_c bytes
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static constexpr size_t PACKET_TOTAL = sizeof(StateSharePacket) + sizeof(dSv_info_c);
|
||||
|
||||
void ImGuiStateShare::copyState() {
|
||||
dSv_restart_c& restart = g_dComIfG_gameInfo.info.getRestart();
|
||||
|
||||
StateSharePacket pkt = {};
|
||||
if (const char* s = g_dComIfG_gameInfo.play.getLastPlayStageName())
|
||||
strncpy(pkt.stageName, s, 7);
|
||||
pkt.roomNo = restart.getRoomNo();
|
||||
pkt.layer = dComIfGp_getStartStageLayer();
|
||||
pkt.startPoint = restart.getStartPoint();
|
||||
|
||||
std::string raw(PACKET_TOTAL, '\0');
|
||||
memcpy(raw.data(), &pkt, sizeof(pkt));
|
||||
memcpy(raw.data() + sizeof(pkt), &g_dComIfG_gameInfo.info, sizeof(dSv_info_c));
|
||||
|
||||
size_t bound = ZSTD_compressBound(raw.size());
|
||||
std::string compressed(bound, '\0');
|
||||
compressed.resize(ZSTD_compress(compressed.data(), bound, raw.data(), raw.size(), 1));
|
||||
|
||||
std::string encoded = absl::Base64Escape(compressed);
|
||||
ImGui::SetClipboardText(encoded.c_str());
|
||||
m_statusMsg = "Copied to clipboard.";
|
||||
}
|
||||
|
||||
bool ImGuiStateShare::pasteState() {
|
||||
const char* clip = ImGui::GetClipboardText();
|
||||
if (!clip || clip[0] == '\0') {
|
||||
m_statusMsg = "Clipboard is empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string decoded;
|
||||
if (!absl::Base64Unescape(clip, &decoded)) {
|
||||
m_statusMsg = "Invalid base64.";
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long long dSize = ZSTD_getFrameContentSize(decoded.data(), decoded.size());
|
||||
if (dSize == ZSTD_CONTENTSIZE_ERROR || dSize == ZSTD_CONTENTSIZE_UNKNOWN || dSize < PACKET_TOTAL) {
|
||||
m_statusMsg = "Not a valid state string.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string raw(static_cast<size_t>(dSize), '\0');
|
||||
size_t result = ZSTD_decompress(raw.data(), raw.size(), decoded.data(), decoded.size());
|
||||
if (ZSTD_isError(result)) {
|
||||
m_statusMsg = fmt::format("Decompression failed: {}", ZSTD_getErrorName(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
StateSharePacket pkt;
|
||||
memcpy(&pkt, raw.data(), sizeof(pkt));
|
||||
pkt.stageName[7] = '\0';
|
||||
|
||||
memcpy(&g_dComIfG_gameInfo.info, raw.data() + sizeof(pkt), sizeof(dSv_info_c));
|
||||
dComIfGp_setNextStage(pkt.stageName, pkt.startPoint, pkt.roomNo, pkt.layer);
|
||||
m_pendingInfo = g_dComIfG_gameInfo.info;
|
||||
|
||||
m_statusMsg = fmt::format("Warping to {} room {} layer {}.", pkt.stageName, (int)pkt.roomNo, (int)pkt.layer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGuiStateShare::tickPendingApply() {
|
||||
if (!m_pendingInfo.has_value() || dComIfGp_isEnableNextStage())
|
||||
return;
|
||||
g_dComIfG_gameInfo.info = *m_pendingInfo;
|
||||
m_pendingInfo.reset();
|
||||
}
|
||||
|
||||
void ImGuiStateShare::draw(bool& open) {
|
||||
if (dusk::IsGameLaunched)
|
||||
tickPendingApply();
|
||||
|
||||
if (!open)
|
||||
return;
|
||||
|
||||
if (!ImGui::Begin("State Share", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dusk::IsGameLaunched) ImGui::BeginDisabled();
|
||||
if (ImGui::Button("Copy State")) copyState();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Import State")) pasteState();
|
||||
if (!dusk::IsGameLaunched) ImGui::EndDisabled();
|
||||
|
||||
if (!m_statusMsg.empty()) {
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", m_statusMsg.c_str());
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ImGuiMenuTools::ShowStateShare() {
|
||||
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare))
|
||||
return;
|
||||
m_stateShare.draw(m_showStateShare);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef DUSK_IMGUI_STATESHARE_HPP
|
||||
#define DUSK_IMGUI_STATESHARE_HPP
|
||||
|
||||
#include "d/d_save.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace dusk {
|
||||
class ImGuiStateShare {
|
||||
public:
|
||||
void draw(bool& open);
|
||||
|
||||
private:
|
||||
void copyState();
|
||||
bool pasteState();
|
||||
void tickPendingApply();
|
||||
|
||||
std::string m_statusMsg;
|
||||
std::optional<dSv_info_c> m_pendingInfo;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user