mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-04-07 05:56:53 +02:00
DnD prototype.
This commit is contained in:
parent
cdde3d47bb
commit
efb3fe4fbc
@ -175,6 +175,7 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_elements.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_details_panel.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_installer.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp
|
||||
|
@ -114,6 +114,8 @@ namespace recompui {
|
||||
void queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height);
|
||||
void queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes);
|
||||
void release_image(const std::string &src);
|
||||
|
||||
void drop_files(const std::list<std::filesystem::path> &file_list);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -42,6 +42,10 @@ static struct {
|
||||
bool rumble_active;
|
||||
} InputState;
|
||||
|
||||
static struct {
|
||||
std::list<std::filesystem::path> files_dropped;
|
||||
} DropState;
|
||||
|
||||
std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
|
||||
std::atomic<recomp::InputField> scanned_input;
|
||||
|
||||
@ -276,6 +280,18 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
InputState.pending_mouse_delta[0] += motion_event->xrel;
|
||||
InputState.pending_mouse_delta[1] += motion_event->yrel;
|
||||
}
|
||||
queue_if_enabled(event);
|
||||
break;
|
||||
case SDL_EventType::SDL_DROPBEGIN:
|
||||
DropState.files_dropped.clear();
|
||||
break;
|
||||
case SDL_EventType::SDL_DROPFILE:
|
||||
DropState.files_dropped.emplace_back(std::filesystem::path(std::u8string_view((const char8_t *)(event->drop.file))));
|
||||
SDL_free(event->drop.file);
|
||||
break;
|
||||
case SDL_EventType::SDL_DROPCOMPLETE:
|
||||
recompui::drop_files(DropState.files_dropped);
|
||||
break;
|
||||
default:
|
||||
queue_if_enabled(event);
|
||||
break;
|
||||
|
299
src/ui/ui_mod_installer.cpp
Normal file
299
src/ui/ui_mod_installer.cpp
Normal file
@ -0,0 +1,299 @@
|
||||
#include "ui_mod_installer.h"
|
||||
|
||||
#include "librecomp/mods.hpp"
|
||||
|
||||
namespace recompui {
|
||||
static const std::string ManifestFilename = "mod.json";
|
||||
static const char *TextureDatabaseFilename = "rt64.json";
|
||||
static const std::u8string OldExtension = u8".old";
|
||||
static const std::u8string NewExtension = u8".new";
|
||||
|
||||
size_t zip_write_func(void *opaque, mz_uint64 offset, const void *bytes, size_t count) {
|
||||
std::ofstream &stream = *(std::ofstream *)(opaque);
|
||||
stream.seekp(offset, std::ios::beg);
|
||||
stream.write((const char *)(bytes), count);
|
||||
return stream.bad() ? 0 : count;
|
||||
}
|
||||
|
||||
void start_single_mod_installation(const std::filesystem::path &file_path, recomp::mods::ZipModFileHandle &file_handle, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, ModInstaller::Result &result) {
|
||||
// Check for the existence of the manifest file.
|
||||
std::filesystem::path mods_directory = recomp::mods::get_mods_directory();
|
||||
std::filesystem::path target_path = mods_directory / file_path.filename();
|
||||
std::filesystem::path target_write_path = target_path.u8string() + NewExtension;
|
||||
ModInstaller::Installation installation;
|
||||
bool exists = false;
|
||||
std::vector<char> manifest_bytes = file_handle.read_file(ManifestFilename, exists);
|
||||
if (exists) {
|
||||
// Parse the manifest file to check for its validity.
|
||||
std::string error;
|
||||
recomp::mods::ModManifest manifest;
|
||||
recomp::mods::ModOpenError open_error = parse_manifest(manifest, manifest_bytes, error);
|
||||
exists = (open_error == recomp::mods::ModOpenError::Good);
|
||||
|
||||
if (exists) {
|
||||
installation.mod_id = manifest.mod_id;
|
||||
installation.display_name = manifest.display_name;
|
||||
installation.mod_version = manifest.version;
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
}
|
||||
}
|
||||
else if (file_path.extension() == ".rtz") {
|
||||
// When it's an rtz file, check if the texture database file exists.
|
||||
exists = mz_zip_reader_locate_file(file_handle.archive.get(), TextureDatabaseFilename, nullptr, 0) >= 0;
|
||||
|
||||
if (exists) {
|
||||
installation.mod_id = std::string((const char *)(target_path.stem().u8string().c_str()));
|
||||
installation.display_name = installation.mod_id;
|
||||
installation.mod_version = recomp::Version();
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (exists) {
|
||||
std::filesystem::copy(file_path, target_path, ec);
|
||||
if (ec) {
|
||||
result.error_messages.emplace_back(std::format("Unable to copy to {}.", target_write_path.string()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.error_messages.emplace_back(std::format("Unable to install {} as it does not seem to be a mod.", file_path.string()));
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.pending_installations.emplace_back(installation);
|
||||
}
|
||||
|
||||
void start_package_mod_installation(recomp::mods::ZipModFileHandle &file_handle, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, ModInstaller::Result &result) {
|
||||
std::error_code ec;
|
||||
char filename[1024];
|
||||
std::filesystem::path mods_directory = recomp::mods::get_mods_directory();
|
||||
mz_zip_archive *zip_archive = file_handle.archive.get();
|
||||
mz_uint num_files = mz_zip_reader_get_num_files(file_handle.archive.get());
|
||||
std::list<std::filesystem::path> dynamic_lib_files;
|
||||
std::list<ModInstaller::Installation>::iterator first_nrm_iterator = result.pending_installations.end();
|
||||
for (mz_uint i = 0; i < num_files; i++) {
|
||||
mz_uint filename_length = mz_zip_reader_get_filename(zip_archive, i, filename, sizeof(filename));
|
||||
if (filename_length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::filesystem::path target_path = mods_directory / std::u8string_view((const char8_t *)(filename));
|
||||
if ((target_path.extension() == ".rtz") || (target_path.extension() == ".nrm")) {
|
||||
ModInstaller::Installation installation;
|
||||
std::filesystem::path target_write_path = target_path.u8string() + NewExtension;
|
||||
std::ofstream output_stream(target_write_path, std::ios::binary);
|
||||
if (!output_stream.is_open()) {
|
||||
result.error_messages.emplace_back(std::format("Unable to open {} for writing.", target_write_path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mz_zip_reader_extract_to_callback(zip_archive, i, &zip_write_func, &output_stream, 0)) {
|
||||
output_stream.close();
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to extract to {}.", target_write_path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
output_stream.close();
|
||||
if (output_stream.bad()) {
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to write to {}.", target_write_path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to load the extracted file as a mod file handle.
|
||||
recomp::mods::ModOpenError open_error;
|
||||
recomp::mods::ZipModFileHandle extracted_file_handle(target_write_path, open_error);
|
||||
if (open_error != recomp::mods::ModOpenError::Good) {
|
||||
result.error_messages.emplace_back(std::format("Unable to open {}.", target_write_path.string()));
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for the existence of the manifest file.
|
||||
bool exists = false;
|
||||
std::vector<char> manifest_bytes = extracted_file_handle.read_file(ManifestFilename, exists);
|
||||
if (exists) {
|
||||
// Parse the manifest file to check for its validity.
|
||||
std::string error;
|
||||
recomp::mods::ModManifest manifest;
|
||||
open_error = parse_manifest(manifest, manifest_bytes, error);
|
||||
exists = (open_error == recomp::mods::ModOpenError::Good);
|
||||
|
||||
if (exists) {
|
||||
installation.mod_id = manifest.mod_id;
|
||||
installation.display_name = manifest.display_name;
|
||||
installation.mod_version = manifest.version;
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
}
|
||||
}
|
||||
else if (target_path.extension() == ".rtz") {
|
||||
// When it's an rtz file, check if the texture database file exists.
|
||||
exists = mz_zip_reader_locate_file(extracted_file_handle.archive.get(), TextureDatabaseFilename, nullptr, 0) >= 0;
|
||||
|
||||
if (exists) {
|
||||
installation.mod_id = std::string((const char *)(target_path.stem().u8string().c_str()));
|
||||
installation.display_name = installation.mod_id;
|
||||
installation.mod_version = recomp::Version();
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
result.error_messages.emplace_back(std::format("Unable to install {} as it does not seem to be a mod.", target_path.filename().string()));
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.pending_installations.emplace_back(installation);
|
||||
|
||||
// Store the first nrm found for any dynamic libraries that might be found.
|
||||
if ((first_nrm_iterator == result.pending_installations.end()) && (target_path.extension() == ".nrm")) {
|
||||
first_nrm_iterator = std::prev(result.pending_installations.end());
|
||||
}
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
else if (target_path.extension() == ".dll") {
|
||||
#elif defined(__linux__)
|
||||
else if (target_path.extension() == ".so") {
|
||||
#elif defined(__APPLE__)
|
||||
else if (target_path.extension() == ".dylib") {
|
||||
#else
|
||||
static_assert(false, "Unimplemented for this platform."); {
|
||||
#endif
|
||||
std::filesystem::path target_write_path = target_path.u8string() + NewExtension;
|
||||
std::ofstream output_stream(target_write_path, std::ios::binary);
|
||||
if (!output_stream.is_open()) {
|
||||
result.error_messages.emplace_back(std::format("Unable to open {} for writing.", target_write_path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mz_zip_reader_extract_to_callback(zip_archive, i, &zip_write_func, &output_stream, 0)) {
|
||||
output_stream.close();
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to extract to {}.", target_write_path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
output_stream.close();
|
||||
if (output_stream.bad()) {
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to write to {}.", target_write_path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
dynamic_lib_files.emplace_back(target_write_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!dynamic_lib_files.empty()) {
|
||||
if (first_nrm_iterator != result.pending_installations.end()) {
|
||||
// Associate all these files to the first mod that is found.
|
||||
for (const std::filesystem::path &path : dynamic_lib_files) {
|
||||
first_nrm_iterator->mod_files.emplace_back(path);
|
||||
|
||||
// Run verification against for overwrite confirmations.
|
||||
for (const std::filesystem::path &path : first_nrm_iterator->mod_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
first_nrm_iterator->needs_overwrite_confirmation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// These library files were not required by any mod, just delete them.
|
||||
for (const std::filesystem::path &path : dynamic_lib_files) {
|
||||
std::filesystem::path new_path(path.u8string() + NewExtension);
|
||||
std::filesystem::remove(new_path, ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModInstaller::start_mod_installation(const std::list<std::filesystem::path> &file_paths, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, Result &result) {
|
||||
result = Result();
|
||||
|
||||
for (const std::filesystem::path &path : file_paths) {
|
||||
recomp::mods::ModOpenError open_error;
|
||||
recomp::mods::ZipModFileHandle file_handle(path, open_error);
|
||||
if (open_error != recomp::mods::ModOpenError::Good) {
|
||||
result.error_messages = { std::format("File %s is not a valid container.", path.string()) };
|
||||
continue;
|
||||
}
|
||||
|
||||
// First we verify if the container itself isn't a mod already.
|
||||
if ((path.extension() == ".rtz") || (path.extension() == ".nrm")) {
|
||||
start_single_mod_installation(path, file_handle, progress_callback, result);
|
||||
}
|
||||
else {
|
||||
// Scan the container for compatible mods instead. This is the case for packages made by users or how they're tipically uploaded to Thunderstore.
|
||||
start_package_mod_installation(file_handle, progress_callback, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModInstaller::finish_mod_installation(const std::unordered_set<std::string> &confirmed_overwrites, Result &result) {
|
||||
result.error_messages.clear();
|
||||
|
||||
std::error_code ec;
|
||||
for (const Installation &installation : result.pending_installations) {
|
||||
if (installation.needs_overwrite_confirmation && !confirmed_overwrites.contains(installation.mod_id)) {
|
||||
// If the user hasn't confirmed this overwrite, simply delete all the files that were extracted for this mod.
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
std::filesystem::path new_path(path.u8string() + NewExtension);
|
||||
std::filesystem::remove(new_path, ec);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
std::filesystem::path old_path(path.u8string() + OldExtension);
|
||||
std::filesystem::path new_path(path.u8string() + NewExtension);
|
||||
|
||||
// Rename the current path to a temporary old path, but only if the current path already exists.
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
std::filesystem::remove(old_path, ec);
|
||||
std::filesystem::rename(path, old_path, ec);
|
||||
if (ec) {
|
||||
// If it fails, remove the new path.
|
||||
std::filesystem::remove(new_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to rename {}.", path.string()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the new path to the current path.
|
||||
std::filesystem::rename(new_path, path, ec);
|
||||
if (ec) {
|
||||
// If it fails, remove the new path and also restore the temporary old path to the current path.
|
||||
std::filesystem::remove(new_path, ec);
|
||||
std::filesystem::rename(old_path, path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to rename {}.", path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If nothing failed, just remove the temporary old path.
|
||||
std::filesystem::remove(old_path, ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
28
src/ui/ui_mod_installer.h
Normal file
28
src/ui/ui_mod_installer.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef RECOMPUI_MOD_INSTALLER_H
|
||||
#define RECOMPUI_MOD_INSTALLER_H
|
||||
|
||||
#include <librecomp/game.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace recompui {
|
||||
struct ModInstaller {
|
||||
struct Installation {
|
||||
std::string mod_id;
|
||||
std::string display_name;
|
||||
recomp::Version mod_version;
|
||||
std::list<std::filesystem::path> mod_files;
|
||||
bool needs_overwrite_confirmation = false;
|
||||
};
|
||||
|
||||
struct Result {
|
||||
std::list<std::string> error_messages;
|
||||
std::list<Installation> pending_installations;
|
||||
};
|
||||
|
||||
static void start_mod_installation(const std::list<std::filesystem::path> &file_paths, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, Result &result);
|
||||
static void finish_mod_installation(const std::unordered_set<std::string> &confirmed_overwrites, Result &result);
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
@ -23,6 +23,7 @@
|
||||
#include "ui_rml_hacks.hpp"
|
||||
#include "ui_elements.h"
|
||||
#include "ui_mod_menu.h"
|
||||
#include "ui_mod_installer.h"
|
||||
#include "ui_renderer.h"
|
||||
|
||||
bool can_focus(Rml::Element* element) {
|
||||
@ -833,3 +834,10 @@ void recompui::queue_image_from_bytes_rgba32(const std::string &src, const std::
|
||||
void recompui::release_image(const std::string &src) {
|
||||
Rml::ReleaseTexture(src);
|
||||
}
|
||||
|
||||
void recompui::drop_files(const std::list<std::filesystem::path> &file_list) {
|
||||
// TODO: Needs a progress callback and a prompt for every mod that needs to be confirmed to be overwritten.
|
||||
ModInstaller::Result result;
|
||||
ModInstaller::start_mod_installation(file_list, nullptr, result);
|
||||
ModInstaller::finish_mod_installation({}, result);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user