@@ -6,7 +6,7 @@ set(CMAKE_CXX_VISIBILITY_PRESET hidden)
66
77project (ToastyReplay VERSION 1.3.1 LANGUAGES CXX )
88
9- file ( GLOB SOURCES
9+ set (TOASTYREPLAY_SOURCES
1010 src/ttr_format.cpp
1111 src/core/checkpoint_handler.cpp
1212 src/core/replay_engine.cpp
@@ -28,17 +28,263 @@ file(GLOB SOURCES
2828 src/render/watermark.cpp
2929 src/audio/clicksounds.cpp
3030 src/audio/playsound.cpp
31+ src/i18n/localization.cpp
3132 src/online/online_client.cpp
3233)
3334
34- add_library (${PROJECT_NAME} SHARED ${SOURCES} )
35+ function (toastyreplay_replace_once_or_verify content_var search replacement )
36+ string (FIND "${${content_var} }" "${replacement} " replacement_index)
37+ if (replacement_index GREATER -1)
38+ set (${content_var} "${${content_var} }" PARENT_SCOPE )
39+ return ()
40+ endif ()
41+
42+ string (FIND "${${content_var} }" "${search} " search_index)
43+ if (search_index EQUAL -1)
44+ message (FATAL_ERROR "Unable to patch gd-imgui-cocos backend. Missing expected snippet: ${search} " )
45+ endif ()
46+
47+ string (REPLACE "${search} " "${replacement} " updated_content "${${content_var} }" )
48+ set (${content_var} "${updated_content} " PARENT_SCOPE )
49+ endfunction ()
50+
51+ function (patch_gd_imgui_cocos_backend backend_file )
52+ file (READ "${backend_file} " backend_content )
53+
54+ toastyreplay_replace_once_or_verify (
55+ backend_content
56+ "#include <Geode/Geode.hpp>"
57+ "#include <Geode/Geode.hpp>\n #include <Geode/utils/string.hpp>"
58+ )
59+ toastyreplay_replace_once_or_verify (
60+ backend_content
61+ "static const auto iniPath = (Mod::get()->getSaveDir() / \" imgui.ini\" ).string();"
62+ "static const auto iniPath = new std::string(geode::utils::string::pathToString(Mod::get()->getSaveDir() / \" imgui.ini\" ));"
63+ )
64+ toastyreplay_replace_once_or_verify (
65+ backend_content
66+ "static const auto iniPath = new std::string((Mod::get()->getSaveDir() / \" imgui.ini\" ).string());"
67+ "static const auto iniPath = new std::string(geode::utils::string::pathToString(Mod::get()->getSaveDir() / \" imgui.ini\" ));"
68+ )
69+ toastyreplay_replace_once_or_verify (
70+ backend_content
71+ "io.IniFilename = iniPath.c_str();"
72+ "io.IniFilename = iniPath->c_str();"
73+ )
74+ toastyreplay_replace_once_or_verify (
75+ backend_content
76+ "#ifdef GEODE_IS_MOBILE\n\n class ImGuiIMEDelegate"
77+ "#ifdef GEODE_IS_WINDOWS\n static std::string readClipboardText() {\n\t if (!OpenClipboard(nullptr)) return {};\n\n\t HANDLE hData = GetClipboardData(CF_UNICODETEXT);\n\t if (hData == nullptr) {\n\t\t CloseClipboard();\n\t\t return {};\n\t }\n\n\t auto pszText = static_cast<wchar_t*>(GlobalLock(hData));\n\t if (pszText == nullptr) {\n\t\t CloseClipboard();\n\t\t return {};\n\t }\n\n\t int size = WideCharToMultiByte(CP_UTF8, 0, pszText, -1, nullptr, 0, nullptr, nullptr);\n\t std::string text;\n\t if (size > 0) {\n\t\t text.resize(static_cast<size_t>(size));\n\t\t WideCharToMultiByte(CP_UTF8, 0, pszText, -1, text.data(), size, nullptr, nullptr);\n\t\t if (!text.empty() && text.back() == '\\ 0') {\n\t\t\t text.pop_back();\n\t\t }\n\t }\n\n\t GlobalUnlock(hData);\n\t CloseClipboard();\n\t return text;\n }\n #endif\n\n #ifdef GEODE_IS_MOBILE\n\n class ImGuiIMEDelegate"
78+ )
79+ toastyreplay_replace_once_or_verify (
80+ backend_content
81+ "auto static read = geode::utils::clipboard::read();"
82+ "auto static read = new std::string();"
83+ )
84+ toastyreplay_replace_once_or_verify (
85+ backend_content
86+ "read = geode::utils::clipboard::read();"
87+ "*read = readClipboardText();"
88+ )
89+ toastyreplay_replace_once_or_verify (
90+ backend_content
91+ "return read.c_str();"
92+ "return read->c_str();"
93+ )
94+
95+ file (WRITE "${backend_file} " "${backend_content} " )
96+ endfunction ()
97+
98+ function (patch_gd_replay_format_header header_file )
99+ file (READ "${header_file} " replay_content )
100+
101+ string (FIND "${replay_content} " "static std::optional<Self> tryImportData(std::vector<uint8_t> const& data, bool importInputs = true)" already_patched_index)
102+ if (already_patched_index GREATER -1)
103+ return ()
104+ endif ()
105+
106+ toastyreplay_replace_once_or_verify (
107+ replay_content
108+ [=[ static Self importData(std::vector<uint8_t> const& data, bool importInputs = true) {
109+ Self replay;
110+ json replayJson;
111+
112+ try {
113+ replayJson = json::from_msgpack(data);
114+ } catch(std::exception& e) {
115+ replayJson = json::parse(data);
116+ }
117+
118+ replay.gameVersion = replayJson["gameVersion"];
119+ replay.description = replayJson["description"];
120+ replay.version = replayJson["version"];
121+ replay.duration = replayJson["duration"];
122+ replay.botInfo.name = replayJson["bot"]["name"];
123+ replay.botInfo.version = replayJson["bot"]["version"];
124+ replay.levelInfo.id = replayJson["level"]["id"];
125+ replay.levelInfo.name = replayJson["level"]["name"];
126+ replay.author = replayJson["author"];
127+ replay.seed = replayJson["seed"];
128+ replay.coins = replayJson["coins"];
129+ replay.ldm = replayJson["ldm"];
130+
131+ if(replayJson.contains("framerate"))
132+ replay.framerate = replayJson["framerate"];
133+ replay.parseExtension(replayJson.get<json::object_t>());
134+
135+ if(!importInputs)
136+ return replay;
137+
138+ for (json const& inputJson : replayJson["inputs"]) {
139+ InputType input;
140+ input.frame = inputJson["frame"];
141+ input.button = inputJson["btn"];
142+ input.player2 = inputJson["2p"];
143+ input.down = inputJson["down"];
144+ input.parseExtension(inputJson.get<json::object_t>());
145+
146+ replay.inputs.push_back(input);
147+ }
148+
149+ return replay;
150+ }
151+ ]=]
152+ [=[ static std::optional<Self> tryImportData(std::vector<uint8_t> const& data, bool importInputs = true) {
153+ Self replay;
154+ json replayJson = json::from_msgpack(data, true, false);
155+ if (replayJson.is_discarded()) {
156+ replayJson = json::parse(data, nullptr, false);
157+ }
158+ if (!replayJson.is_object()) {
159+ return std::nullopt;
160+ }
161+
162+ auto const* root = replayJson.get_ptr<const json::object_t*>();
163+ if (!root) {
164+ return std::nullopt;
165+ }
166+
167+ auto getValue = [](json::object_t const& object, char const* key) -> json const* {
168+ auto it = object.find(key);
169+ return it == object.end() ? nullptr : &it->second;
170+ };
171+
172+ auto getString = [&](json::object_t const& object, char const* key, std::string& out) -> bool {
173+ auto const* value = getValue(object, key);
174+ if (!value) return false;
175+ auto const* stringValue = value->template get_ptr<const std::string*>();
176+ if (!stringValue) return false;
177+ out = *stringValue;
178+ return true;
179+ };
180+
181+ auto getBool = [&](json::object_t const& object, char const* key, bool& out) -> bool {
182+ auto const* value = getValue(object, key);
183+ if (!value) return false;
184+ auto const* boolValue = value->template get_ptr<const bool*>();
185+ if (!boolValue) return false;
186+ out = *boolValue;
187+ return true;
188+ };
189+
190+ auto getNumber = [&](json::object_t const& object, char const* key, auto& out) -> bool {
191+ auto const* value = getValue(object, key);
192+ if (!value) return false;
193+
194+ if (auto const* floatValue = value->template get_ptr<const json::number_float_t*>()) {
195+ out = static_cast<std::decay_t<decltype(out)>>(*floatValue);
196+ return true;
197+ }
198+ if (auto const* signedValue = value->template get_ptr<const json::number_integer_t*>()) {
199+ out = static_cast<std::decay_t<decltype(out)>>(*signedValue);
200+ return true;
201+ }
202+ if (auto const* unsignedValue = value->template get_ptr<const json::number_unsigned_t*>()) {
203+ out = static_cast<std::decay_t<decltype(out)>>(*unsignedValue);
204+ return true;
205+ }
206+
207+ return false;
208+ };
209+
210+ auto const* botJson = getValue(*root, "bot");
211+ auto const* levelJson = getValue(*root, "level");
212+ auto const* inputsJson = getValue(*root, "inputs");
213+ if (!botJson || !botJson->is_object() || !levelJson || !levelJson->is_object()) {
214+ return std::nullopt;
215+ }
216+
217+ auto const* botObject = botJson->template get_ptr<const json::object_t*>();
218+ auto const* levelObject = levelJson->template get_ptr<const json::object_t*>();
219+ if (!botObject || !levelObject) {
220+ return std::nullopt;
221+ }
222+
223+ if (!getNumber(*root, "gameVersion", replay.gameVersion)
224+ !getString(*root, "description", replay.description)
225+ !getNumber(*root, "version", replay.version)
226+ !getNumber(*root, "duration", replay.duration)
227+ !getString(*botObject, "name", replay.botInfo.name)
228+ !getString(*botObject, "version", replay.botInfo.version)
229+ !getNumber(*levelObject, "id", replay.levelInfo.id)
230+ !getString(*levelObject, "name", replay.levelInfo.name)
231+ !getString(*root, "author", replay.author)
232+ !getNumber(*root, "seed", replay.seed)
233+ !getNumber(*root, "coins", replay.coins)
234+ !getBool(*root, "ldm", replay.ldm)) {
235+ return std::nullopt;
236+ }
237+
238+ getNumber(*root, "framerate", replay.framerate);
239+ replay.parseExtension(*root);
240+
241+ if(!importInputs)
242+ return replay;
243+
244+ if (!inputsJson || !inputsJson->is_array()) {
245+ return std::nullopt;
246+ }
247+
248+ for (json const& inputJson : *inputsJson) {
249+ auto const* inputObject = inputJson.template get_ptr<const json::object_t*>();
250+ if (!inputObject) {
251+ return std::nullopt;
252+ }
253+
254+ InputType input;
255+ if (!getNumber(*inputObject, "frame", input.frame)
256+ !getNumber(*inputObject, "btn", input.button)
257+ !getBool(*inputObject, "2p", input.player2)
258+ !getBool(*inputObject, "down", input.down)) {
259+ return std::nullopt;
260+ }
261+ input.parseExtension(*inputObject);
262+
263+ replay.inputs.push_back(input);
264+ }
265+
266+ return replay;
267+ }
268+
269+ static Self importData(std::vector<uint8_t> const& data, bool importInputs = true) {
270+ auto replay = tryImportData(data, importInputs);
271+ return replay.value_or(Self{});
272+ }
273+ ]=]
274+ )
275+
276+ file (WRITE "${header_file} " "${replay_content} " )
277+ endfunction ()
278+
279+ add_library (${PROJECT_NAME} SHARED ${TOASTYREPLAY_SOURCES} )
35280
36281target_include_directories (${PROJECT_NAME} PRIVATE
37282 src
38- ${gd-imgui-cocos_SOURCE_DIR}/include
39283)
40284
41285if (MSVC )
286+ target_compile_options (${PROJECT_NAME} PRIVATE /FS )
287+
42288 set_source_files_properties (
43289 src/audio/clicksounds.cpp
44290 src/audio/playsound.cpp
@@ -62,66 +308,31 @@ endif()
62308add_subdirectory ($ENV{GEODE_SDK} $ENV{GEODE_SDK} /build )
63309
64310CPMAddPackage ("gh:maxnut/GDReplayFormat#9c32b5529067901418ea6e4dfbd36437dc8712bc" )
311+ patch_gd_replay_format_header ("${GDReplayFormat_SOURCE_DIR} /include/gdr/gdr.hpp" )
65312set (IMGUI_VERSION "v1.91.4" CACHE STRING "Pin imgui version" FORCE )
66313CPMAddPackage ("gh:matcool/gd-imgui-cocos#97643336209b760a3d59d83b770546b1cfef4e7d" )
67- set (GD_IMGUI_COCOS_BACKEND_FILE "${gd-imgui-cocos_SOURCE_DIR}/src/backend.cpp" )
68- file (READ "${GD_IMGUI_COCOS_BACKEND_FILE} " GD_IMGUI_COCOS_BACKEND_CONTENT )
69- string (REPLACE
70- "static const auto iniPath = (Mod::get()->getSaveDir() / \" imgui.ini\" ).string();"
71- "static const auto iniPath = new std::string((Mod::get()->getSaveDir() / \" imgui.ini\" ).string());"
72- GD_IMGUI_COCOS_BACKEND_CONTENT
73- "${GD_IMGUI_COCOS_BACKEND_CONTENT} "
74- )
75- string (REPLACE
76- "io.IniFilename = iniPath.c_str();"
77- "io.IniFilename = iniPath->c_str();"
78- GD_IMGUI_COCOS_BACKEND_CONTENT
79- "${GD_IMGUI_COCOS_BACKEND_CONTENT} "
80- )
81- if (NOT GD_IMGUI_COCOS_BACKEND_CONTENT MATCHES "readClipboardText" )
82- string (REPLACE
83- "#ifdef GEODE_IS_MOBILE\n\n class ImGuiIMEDelegate"
84- "#ifdef GEODE_IS_WINDOWS\n static std::string readClipboardText() {\n\t if (!OpenClipboard(nullptr)) return {};\n\n\t HANDLE hData = GetClipboardData(CF_UNICODETEXT);\n\t if (hData == nullptr) {\n\t\t CloseClipboard();\n\t\t return {};\n\t }\n\n\t auto pszText = static_cast<wchar_t*>(GlobalLock(hData));\n\t if (pszText == nullptr) {\n\t\t CloseClipboard();\n\t\t return {};\n\t }\n\n\t int size = WideCharToMultiByte(CP_UTF8, 0, pszText, -1, nullptr, 0, nullptr, nullptr);\n\t std::string text;\n\t if (size > 0) {\n\t\t text.resize(static_cast<size_t>(size));\n\t\t WideCharToMultiByte(CP_UTF8, 0, pszText, -1, text.data(), size, nullptr, nullptr);\n\t\t if (!text.empty() && text.back() == '\\ 0') {\n\t\t\t text.pop_back();\n\t\t }\n\t }\n\n\t GlobalUnlock(hData);\n\t CloseClipboard();\n\t return text;\n }\n #endif\n\n #ifdef GEODE_IS_MOBILE\n\n class ImGuiIMEDelegate"
85- GD_IMGUI_COCOS_BACKEND_CONTENT
86- "${GD_IMGUI_COCOS_BACKEND_CONTENT} "
87- )
88- endif ()
89- string (REPLACE
90- "auto static read = geode::utils::clipboard::read();"
91- "auto static read = new std::string();"
92- GD_IMGUI_COCOS_BACKEND_CONTENT
93- "${GD_IMGUI_COCOS_BACKEND_CONTENT} "
94- )
95- string (REPLACE
96- "read = geode::utils::clipboard::read();"
97- "*read = readClipboardText();"
98- GD_IMGUI_COCOS_BACKEND_CONTENT
99- "${GD_IMGUI_COCOS_BACKEND_CONTENT} "
100- )
101- string (REPLACE
102- "return read.c_str();"
103- "return read->c_str();"
104- GD_IMGUI_COCOS_BACKEND_CONTENT
105- "${GD_IMGUI_COCOS_BACKEND_CONTENT} "
106- )
107- file (WRITE "${GD_IMGUI_COCOS_BACKEND_FILE} " "${GD_IMGUI_COCOS_BACKEND_CONTENT} " )
314+ target_include_directories (${PROJECT_NAME} PRIVATE ${gd-imgui-cocos_SOURCE_DIR}/include )
315+ patch_gd_imgui_cocos_backend ("${gd-imgui-cocos_SOURCE_DIR}/src/backend.cpp" )
316+
108317CPMAddPackage (
109318 NAME ZLIB
110319 GITHUB_REPOSITORY madler/zlib
111320 VERSION 1.3.1
112321 OPTIONS "ZLIB_BUILD_EXAMPLES OFF"
113322)
114- if (ZLIB_ADDED)
115- set (ZLIB_INCLUDE_DIR ${ZLIB_SOURCE_DIR} ${ZLIB_BINARY_DIR} )
116- set (ZLIB_LIBRARY zlibstatic)
117- target_include_directories (${PROJECT_NAME} PRIVATE ${ZLIB_INCLUDE_DIR} )
323+
324+ set (TOASTYREPLAY_LINK_LIBRARIES imgui-cocos libGDR)
325+ if (TARGET zlibstatic)
326+ target_include_directories (${PROJECT_NAME} PRIVATE ${ZLIB_SOURCE_DIR} ${ZLIB_BINARY_DIR} )
327+ list (APPEND TOASTYREPLAY_LINK_LIBRARIES zlibstatic)
118328endif ()
329+
119330target_compile_definitions (${PROJECT_NAME} PRIVATE MOD_VERSION= "${CMAKE_PROJECT_VERSION} " )
120331
121332if (WIN32 )
122- target_link_libraries (${PROJECT_NAME} imgui-cocos libGDR zlibstatic )
333+ target_link_libraries (${PROJECT_NAME} ${TOASTYREPLAY_LINK_LIBRARIES} )
123334elseif (APPLE )
124- target_link_libraries (${PROJECT_NAME} imgui-cocos libGDR zlibstatic "-framework IOKit" )
335+ target_link_libraries (${PROJECT_NAME} ${TOASTYREPLAY_LINK_LIBRARIES} "-framework IOKit" )
125336endif ()
126337
127338
0 commit comments