mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-09-02 08:43:42 -04:00
6cc568d081
* Update Android toolchain to platform 37 * Mods: HttpService * Update modding.md * Update modding.md * Set minor version back to 0
704 lines
23 KiB
C++
704 lines
23 KiB
C++
#include "registry.hpp"
|
|
|
|
#include "slot_map.hpp"
|
|
|
|
#include <aurora/lib/window.hpp>
|
|
#include <borealis/file_select.hpp>
|
|
#include <borealis/io.hpp>
|
|
#include <borealis/log.hpp>
|
|
#include "dusk/config.hpp"
|
|
#include "dusk/mods/loader/loader.hpp"
|
|
#include "mods/svc/file.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace dusk::mods::svc {
|
|
namespace {
|
|
|
|
constexpr borealis::Log Log{"dusk::mods::file"};
|
|
|
|
static_assert(static_cast<int>(FILE_OPEN_READ) == static_cast<int>(borealis::io::File::Mode::Read));
|
|
static_assert(
|
|
static_cast<int>(FILE_OPEN_TRUNCATE) == static_cast<int>(borealis::io::File::Mode::Truncate));
|
|
static_assert(
|
|
static_cast<int>(FILE_OPEN_APPEND) == static_cast<int>(borealis::io::File::Mode::Append));
|
|
|
|
SlotMap<borealis::io::File> s_streams;
|
|
std::unordered_map<void*, LoadedMod*> s_buffers;
|
|
std::unordered_map<LoadedMod*, std::string> s_joinResults;
|
|
ConfigVar<std::string> s_pickerOverride{"file.pickerOverride", ""};
|
|
|
|
struct PendingPick {
|
|
LoadedMod* owner = nullptr;
|
|
FilePickFn callback = nullptr;
|
|
void* userData = nullptr;
|
|
std::optional<std::string> overrideLocation;
|
|
std::optional<std::string> exportSource;
|
|
};
|
|
|
|
std::shared_ptr<PendingPick> s_pendingPick;
|
|
|
|
ModResult map_status(borealis::io::Status status) {
|
|
switch (status) {
|
|
case borealis::io::Status::Ok:
|
|
return MOD_OK;
|
|
case borealis::io::Status::NotFound:
|
|
return MOD_UNAVAILABLE;
|
|
case borealis::io::Status::Unsupported:
|
|
return MOD_UNSUPPORTED;
|
|
case borealis::io::Status::AlreadyExists:
|
|
return MOD_CONFLICT;
|
|
case borealis::io::Status::Failed:
|
|
default:
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
ModResult map_picker_status(borealis::file_select::Status status) {
|
|
switch (status) {
|
|
case borealis::file_select::Status::Selected:
|
|
return MOD_OK;
|
|
case borealis::file_select::Status::Canceled:
|
|
return MOD_UNAVAILABLE;
|
|
case borealis::file_select::Status::Unsupported:
|
|
return MOD_UNSUPPORTED;
|
|
case borealis::file_select::Status::Busy:
|
|
return MOD_CONFLICT;
|
|
case borealis::file_select::Status::Failed:
|
|
default:
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
void invoke_pick(const std::shared_ptr<PendingPick>& pending, ModResult status,
|
|
const std::vector<std::string>& locations, const std::string& error) {
|
|
if (s_pendingPick == pending) {
|
|
s_pendingPick.reset();
|
|
}
|
|
auto* owner = pending->owner;
|
|
const auto callback = pending->callback;
|
|
if (owner == nullptr || callback == nullptr || !owner->active) {
|
|
return;
|
|
}
|
|
|
|
std::vector<const char*> rawLocations;
|
|
rawLocations.reserve(locations.size());
|
|
for (const auto& location : locations) {
|
|
rawLocations.push_back(location.c_str());
|
|
}
|
|
try {
|
|
callback(owner->context.get(), status, rawLocations.empty() ? nullptr : rawLocations.data(),
|
|
static_cast<uint32_t>(rawLocations.size()), error.c_str(), pending->userData);
|
|
} catch (const std::exception& exception) {
|
|
fail_mod(*owner, MOD_ERROR,
|
|
std::string{"exception in file picker callback: "} + exception.what());
|
|
} catch (...) {
|
|
fail_mod(*owner, MOD_ERROR, "unknown exception in file picker callback");
|
|
}
|
|
}
|
|
|
|
bool valid_pick_options(const FilePickOptions* options) {
|
|
if (options == nullptr || options->struct_size < sizeof(FilePickOptions) ||
|
|
(options->filter_count != 0 && options->filters == nullptr))
|
|
{
|
|
return false;
|
|
}
|
|
for (uint32_t i = 0; i < options->filter_count; ++i) {
|
|
if (options->filters[i].name == nullptr || options->filters[i].pattern == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ModResult begin_pick(ModContext* context, const FilePickOptions* options, FilePickFn callback,
|
|
void* userData, bool folder) {
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr || callback == nullptr || !valid_pick_options(options)) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
if (s_pendingPick != nullptr || borealis::file_select::busy()) {
|
|
return MOD_CONFLICT;
|
|
}
|
|
|
|
auto pending = std::make_shared<PendingPick>(PendingPick{
|
|
.owner = mod,
|
|
.callback = callback,
|
|
.userData = userData,
|
|
});
|
|
const auto& overrideLocation = s_pickerOverride.getValue();
|
|
if (!overrideLocation.empty()) {
|
|
pending->overrideLocation = overrideLocation;
|
|
s_pendingPick = std::move(pending);
|
|
return MOD_OK;
|
|
}
|
|
|
|
s_pendingPick = pending;
|
|
const std::string defaultLocation =
|
|
options->default_location != nullptr ? options->default_location : "";
|
|
if (folder) {
|
|
borealis::file_select::open_folder(
|
|
{
|
|
.parentWindow = aurora::window::get_sdl_window(),
|
|
.defaultLocation = defaultLocation,
|
|
},
|
|
[pending](borealis::file_select::Result result) {
|
|
invoke_pick(
|
|
pending, map_picker_status(result.status), result.locations, result.message);
|
|
});
|
|
return MOD_OK;
|
|
}
|
|
|
|
std::vector<borealis::file_select::Filter> filters;
|
|
filters.reserve(options->filter_count);
|
|
for (uint32_t i = 0; i < options->filter_count; ++i) {
|
|
filters.push_back({options->filters[i].name, options->filters[i].pattern});
|
|
}
|
|
borealis::file_select::open_file(
|
|
{
|
|
.parentWindow = aurora::window::get_sdl_window(),
|
|
.filters = std::move(filters),
|
|
.defaultLocation = defaultLocation,
|
|
},
|
|
[pending](borealis::file_select::Result result) {
|
|
invoke_pick(
|
|
pending, map_picker_status(result.status), result.locations, result.message);
|
|
});
|
|
return MOD_OK;
|
|
}
|
|
|
|
ModResult begin_export(ModContext* context, const char* sourceLocation, const char* suggestedName,
|
|
FilePickFn callback, void* userData) {
|
|
auto* mod = mod_from_context(context);
|
|
const std::string_view name = suggestedName != nullptr ? suggestedName : "";
|
|
if (mod == nullptr || sourceLocation == nullptr || callback == nullptr || name.empty() ||
|
|
name == "." || name == ".." || name.find_first_of("/\\") != std::string_view::npos)
|
|
{
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
if (s_pendingPick != nullptr || borealis::file_select::busy()) {
|
|
return MOD_CONFLICT;
|
|
}
|
|
const auto available = borealis::io::check(sourceLocation);
|
|
if (available != borealis::io::Status::Ok) {
|
|
return map_status(available);
|
|
}
|
|
|
|
auto pending = std::make_shared<PendingPick>(PendingPick{
|
|
.owner = mod,
|
|
.callback = callback,
|
|
.userData = userData,
|
|
});
|
|
const auto& overrideLocation = s_pickerOverride.getValue();
|
|
if (!overrideLocation.empty()) {
|
|
pending->overrideLocation = overrideLocation;
|
|
pending->exportSource = sourceLocation;
|
|
s_pendingPick = std::move(pending);
|
|
return MOD_OK;
|
|
}
|
|
|
|
borealis::file_select::ExportOptions options{
|
|
.parentWindow = aurora::window::get_sdl_window(),
|
|
.sourceLocation = sourceLocation,
|
|
.suggestedName = suggestedName,
|
|
};
|
|
s_pendingPick = pending;
|
|
try {
|
|
borealis::file_select::export_file(
|
|
std::move(options), [pending](borealis::file_select::Result result) {
|
|
invoke_pick(
|
|
pending, map_picker_status(result.status), result.locations, result.message);
|
|
});
|
|
} catch (...) {
|
|
if (s_pendingPick == pending) {
|
|
s_pendingPick.reset();
|
|
}
|
|
throw;
|
|
}
|
|
return MOD_OK;
|
|
}
|
|
|
|
ModResult file_pick_file(
|
|
ModContext* context, const FilePickOptions* options, FilePickFn callback, void* userData) {
|
|
return begin_pick(context, options, callback, userData, false);
|
|
}
|
|
|
|
ModResult file_pick_folder(
|
|
ModContext* context, const FilePickOptions* options, FilePickFn callback, void* userData) {
|
|
return begin_pick(context, options, callback, userData, true);
|
|
}
|
|
|
|
ModResult file_export_file(ModContext* context, const char* sourceLocation,
|
|
const char* suggestedName, FilePickFn callback, void* userData) {
|
|
try {
|
|
return begin_export(context, sourceLocation, suggestedName, callback, userData);
|
|
} catch (...) {
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
ModResult file_display_name(
|
|
ModContext* context, const char* location, char* buffer, uint32_t bufferSize) {
|
|
if (mod_from_context(context) == nullptr || location == nullptr || buffer == nullptr ||
|
|
bufferSize == 0)
|
|
{
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
const std::string name = borealis::io::display_name(location);
|
|
if (name.size() + 1 > bufferSize) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
std::memcpy(buffer, name.c_str(), name.size() + 1);
|
|
return MOD_OK;
|
|
}
|
|
|
|
ModResult file_check(ModContext* context, const char* location) {
|
|
if (mod_from_context(context) == nullptr || location == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
return map_status(borealis::io::check(location));
|
|
}
|
|
|
|
ModResult file_open(
|
|
ModContext* context, const char* location, FileOpenMode mode, FileStreamHandle* outHandle) {
|
|
if (outHandle != nullptr) {
|
|
*outHandle = 0;
|
|
}
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr || location == nullptr || outHandle == nullptr || mode < FILE_OPEN_READ ||
|
|
mode > FILE_OPEN_APPEND)
|
|
{
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
try {
|
|
const auto ioMode = static_cast<borealis::io::File::Mode>(mode);
|
|
if (mode != FILE_OPEN_READ) {
|
|
const auto available = borealis::io::check(location);
|
|
if (available != borealis::io::Status::Ok) {
|
|
return map_status(available);
|
|
}
|
|
}
|
|
auto result = borealis::io::open(location, ioMode);
|
|
if (result.status != borealis::io::Status::Ok) {
|
|
Log.warn("[{}] open '{}' failed: {}", mod->metadata.id,
|
|
borealis::io::display_name(location), result.message);
|
|
return map_status(result.status);
|
|
}
|
|
*outHandle = s_streams.emplace(*mod, std::move(result.file));
|
|
return MOD_OK;
|
|
} catch (...) {
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
borealis::io::File* find_stream(ModContext* context, FileStreamHandle handle) {
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr) {
|
|
return nullptr;
|
|
}
|
|
auto* entry = s_streams.find_owned(handle, *mod);
|
|
return entry != nullptr ? &entry->value : nullptr;
|
|
}
|
|
|
|
ModResult file_size(ModContext* context, FileStreamHandle handle, uint64_t* outSize) {
|
|
if (outSize == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
*outSize = 0;
|
|
auto* file = find_stream(context, handle);
|
|
if (file == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
*outSize = file->size();
|
|
return MOD_OK;
|
|
}
|
|
|
|
ModResult file_read(ModContext* context, FileStreamHandle handle, void* buffer, uint64_t length,
|
|
uint64_t* outRead) {
|
|
if (outRead == nullptr || (buffer == nullptr && length != 0)) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
*outRead = 0;
|
|
auto* file = find_stream(context, handle);
|
|
if (file == nullptr || file->writable()) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
*outRead = file->read(buffer, length);
|
|
return file->error().empty() ? MOD_OK : MOD_ERROR;
|
|
}
|
|
|
|
ModResult file_seek(ModContext* context, FileStreamHandle handle, uint64_t offset) {
|
|
auto* file = find_stream(context, handle);
|
|
if (file == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
return file->seek(offset) ? MOD_OK : MOD_ERROR;
|
|
}
|
|
|
|
ModResult file_close(ModContext* context, FileStreamHandle handle) {
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
auto entry = s_streams.take_owned(handle, *mod);
|
|
if (!entry.has_value()) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
return entry->value.close() ? MOD_OK : MOD_ERROR;
|
|
}
|
|
|
|
ModResult file_write(
|
|
ModContext* context, FileStreamHandle handle, const void* buffer, uint64_t length) {
|
|
if ((buffer == nullptr && length != 0) || length > std::numeric_limits<size_t>::max()) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
auto* file = find_stream(context, handle);
|
|
if (file == nullptr || !file->writable()) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
const auto bytes =
|
|
std::span{static_cast<const std::byte*>(buffer), static_cast<size_t>(length)};
|
|
return file->write(bytes) ? MOD_OK : MOD_ERROR;
|
|
}
|
|
|
|
ModResult file_flush(ModContext* context, FileStreamHandle handle) {
|
|
auto* file = find_stream(context, handle);
|
|
if (file == nullptr || !file->writable()) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
return file->flush() ? MOD_OK : MOD_ERROR;
|
|
}
|
|
|
|
ModResult file_write_all(ModContext* context, const char* location, const void* data, size_t size) {
|
|
if (mod_from_context(context) == nullptr || location == nullptr ||
|
|
(data == nullptr && size != 0))
|
|
{
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
try {
|
|
const auto available = borealis::io::check(location);
|
|
if (available != borealis::io::Status::Ok) {
|
|
return map_status(available);
|
|
}
|
|
auto opened = borealis::io::open(location, borealis::io::File::Mode::Truncate);
|
|
if (opened.status != borealis::io::Status::Ok) {
|
|
return map_status(opened.status);
|
|
}
|
|
if (!opened.file.write(std::span{static_cast<const std::byte*>(data), size})) {
|
|
return MOD_ERROR;
|
|
}
|
|
return opened.file.close() ? MOD_OK : MOD_ERROR;
|
|
} catch (...) {
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
ModResult file_read_all(ModContext* context, const char* location, FileBuffer* outBuffer) {
|
|
if (outBuffer == nullptr || outBuffer->struct_size < sizeof(FileBuffer)) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
outBuffer->data = nullptr;
|
|
outBuffer->size = 0;
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr || location == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
try {
|
|
auto opened = borealis::io::open(location);
|
|
if (opened.status != borealis::io::Status::Ok) {
|
|
return map_status(opened.status);
|
|
}
|
|
const uint64_t expectedSize = opened.file.size();
|
|
if (expectedSize > std::numeric_limits<size_t>::max()) {
|
|
return MOD_ERROR;
|
|
}
|
|
std::vector<unsigned char> bytes;
|
|
bytes.resize(static_cast<size_t>(expectedSize));
|
|
uint64_t total = 0;
|
|
while (total < expectedSize) {
|
|
const uint64_t count = opened.file.read(bytes.data() + total, expectedSize - total);
|
|
if (count == 0) {
|
|
return MOD_ERROR;
|
|
}
|
|
total += count;
|
|
}
|
|
if (expectedSize == 0) {
|
|
unsigned char chunk[64 * 1024];
|
|
while (true) {
|
|
const uint64_t count = opened.file.read(chunk, sizeof(chunk));
|
|
if (count == 0) {
|
|
if (!opened.file.error().empty()) {
|
|
return MOD_ERROR;
|
|
}
|
|
break;
|
|
}
|
|
bytes.insert(bytes.end(), chunk, chunk + count);
|
|
}
|
|
}
|
|
if (bytes.empty()) {
|
|
return MOD_OK;
|
|
}
|
|
std::unique_ptr<void, decltype(&std::free)> data{std::malloc(bytes.size()), &std::free};
|
|
if (!data) {
|
|
return MOD_ERROR;
|
|
}
|
|
std::memcpy(data.get(), bytes.data(), bytes.size());
|
|
s_buffers.emplace(data.get(), mod);
|
|
outBuffer->data = data.release();
|
|
outBuffer->size = bytes.size();
|
|
return MOD_OK;
|
|
} catch (...) {
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
void file_free(ModContext* context, FileBuffer* buffer) {
|
|
if (buffer == nullptr || buffer->struct_size < sizeof(FileBuffer) || buffer->data == nullptr) {
|
|
return;
|
|
}
|
|
auto* mod = mod_from_context(context);
|
|
const auto found = s_buffers.find(buffer->data);
|
|
if (mod == nullptr || found == s_buffers.end() || found->second != mod) {
|
|
Log.error("[{}] file free: buffer is not owned by this mod", mod_id_from_context(context));
|
|
return;
|
|
}
|
|
s_buffers.erase(found);
|
|
std::free(buffer->data);
|
|
buffer->data = nullptr;
|
|
buffer->size = 0;
|
|
}
|
|
|
|
ModResult file_list(
|
|
ModContext* context, const char* folderLocation, FileListFn callback, void* userData) {
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr || folderLocation == nullptr || callback == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
borealis::io::ListResult result;
|
|
try {
|
|
result = borealis::io::list(folderLocation);
|
|
} catch (...) {
|
|
return MOD_ERROR;
|
|
}
|
|
if (result.status != borealis::io::Status::Ok) {
|
|
return map_status(result.status);
|
|
}
|
|
try {
|
|
for (const auto& entry : result.entries) {
|
|
const FileEntry raw{
|
|
.name = entry.name.c_str(),
|
|
.location = entry.location.c_str(),
|
|
.is_directory = entry.isDirectory,
|
|
};
|
|
callback(mod->context.get(), &raw, userData);
|
|
if (!mod->active) {
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
callback(mod->context.get(), nullptr, userData);
|
|
} catch (const std::exception& exception) {
|
|
fail_mod(
|
|
*mod, MOD_ERROR, std::string{"exception in file list callback: "} + exception.what());
|
|
return MOD_ERROR;
|
|
} catch (...) {
|
|
fail_mod(*mod, MOD_ERROR, "unknown exception in file list callback");
|
|
return MOD_ERROR;
|
|
}
|
|
return MOD_OK;
|
|
}
|
|
|
|
ModResult file_join(ModContext* context, const char* folderLocation, const char* relativePath,
|
|
const char** outLocation) {
|
|
if (outLocation != nullptr) {
|
|
*outLocation = nullptr;
|
|
}
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr || folderLocation == nullptr || relativePath == nullptr ||
|
|
outLocation == nullptr)
|
|
{
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
auto result = borealis::io::join(folderLocation, relativePath);
|
|
if (result.status != borealis::io::Status::Ok) {
|
|
return map_status(result.status);
|
|
}
|
|
auto& saved = s_joinResults[mod];
|
|
saved = std::move(result.location);
|
|
*outLocation = saved.c_str();
|
|
return MOD_OK;
|
|
}
|
|
|
|
ModResult file_create_child(
|
|
ModContext* context, const char* folderLocation, const char* name, const char** outLocation) {
|
|
if (outLocation != nullptr) {
|
|
*outLocation = nullptr;
|
|
}
|
|
auto* mod = mod_from_context(context);
|
|
if (mod == nullptr || folderLocation == nullptr || name == nullptr || outLocation == nullptr) {
|
|
return MOD_INVALID_ARGUMENT;
|
|
}
|
|
try {
|
|
auto result = borealis::io::create_child(folderLocation, name);
|
|
if (result.status != borealis::io::Status::Ok) {
|
|
return map_status(result.status);
|
|
}
|
|
auto& saved = s_joinResults[mod];
|
|
saved = std::move(result.location);
|
|
*outLocation = saved.c_str();
|
|
return MOD_OK;
|
|
} catch (...) {
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
void file_initialize() {
|
|
config::Register(s_pickerOverride);
|
|
}
|
|
|
|
bool file_available() {
|
|
const auto capabilities = borealis::file_select::capabilities();
|
|
return capabilities.canOpenFile || capabilities.canOpenFolder || capabilities.canExportFile;
|
|
}
|
|
|
|
ModResult copy_picker_override(
|
|
std::string_view source, std::string_view destination, std::string& error) {
|
|
try {
|
|
if (source == destination) {
|
|
return MOD_OK;
|
|
}
|
|
auto input = borealis::io::open(source);
|
|
if (input.status != borealis::io::Status::Ok) {
|
|
error = std::move(input.message);
|
|
return map_status(input.status);
|
|
}
|
|
auto output = borealis::io::open(destination, borealis::io::File::Mode::Truncate);
|
|
if (output.status != borealis::io::Status::Ok) {
|
|
error = std::move(output.message);
|
|
return map_status(output.status);
|
|
}
|
|
std::array<std::byte, 64 * 1024> buffer{};
|
|
while (true) {
|
|
const uint64_t read = input.file.read(buffer.data(), buffer.size());
|
|
if (read == 0) {
|
|
if (!input.file.error().empty()) {
|
|
error = input.file.error();
|
|
return MOD_ERROR;
|
|
}
|
|
break;
|
|
}
|
|
if (!output.file.write(std::span{buffer.data(), static_cast<size_t>(read)})) {
|
|
error = output.file.error();
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
if (!output.file.close()) {
|
|
error = output.file.error();
|
|
return MOD_ERROR;
|
|
}
|
|
return MOD_OK;
|
|
} catch (const std::exception& exception) {
|
|
error = exception.what();
|
|
return MOD_ERROR;
|
|
} catch (...) {
|
|
error = "Unable to copy export source";
|
|
return MOD_ERROR;
|
|
}
|
|
}
|
|
|
|
void file_frame_begin() {
|
|
auto pending = s_pendingPick;
|
|
if (pending == nullptr || !pending->overrideLocation.has_value()) {
|
|
return;
|
|
}
|
|
if (!pending->exportSource.has_value()) {
|
|
invoke_pick(pending, MOD_OK, {*pending->overrideLocation}, "");
|
|
return;
|
|
}
|
|
std::string error;
|
|
const ModResult result =
|
|
copy_picker_override(*pending->exportSource, *pending->overrideLocation, error);
|
|
invoke_pick(pending, result,
|
|
result == MOD_OK ? std::vector<std::string>{*pending->overrideLocation} :
|
|
std::vector<std::string>{},
|
|
error);
|
|
}
|
|
|
|
void file_remove_mod(LoadedMod& mod) {
|
|
const size_t streams = s_streams.erase_all(mod);
|
|
size_t buffers = 0;
|
|
std::erase_if(s_buffers, [&](const auto& entry) {
|
|
if (entry.second != &mod) {
|
|
return false;
|
|
}
|
|
std::free(entry.first);
|
|
++buffers;
|
|
return true;
|
|
});
|
|
s_joinResults.erase(&mod);
|
|
if (s_pendingPick != nullptr && s_pendingPick->owner == &mod) {
|
|
s_pendingPick->owner = nullptr;
|
|
s_pendingPick->callback = nullptr;
|
|
s_pendingPick.reset();
|
|
}
|
|
if (streams != 0 || buffers != 0) {
|
|
Log.warn("[{}] reclaimed {} open stream(s) and {} file buffer(s)", mod.metadata.id, streams,
|
|
buffers);
|
|
}
|
|
}
|
|
|
|
void file_shutdown() {
|
|
config::unregister(s_pickerOverride);
|
|
s_pendingPick.reset();
|
|
}
|
|
|
|
constexpr FileService s_fileService{
|
|
.header = SERVICE_HEADER(FileService, FILE_SERVICE_MAJOR, FILE_SERVICE_MINOR),
|
|
.pick_file = file_pick_file,
|
|
.pick_folder = file_pick_folder,
|
|
.export_file = file_export_file,
|
|
.display_name = file_display_name,
|
|
.check = file_check,
|
|
.open = file_open,
|
|
.size = file_size,
|
|
.read = file_read,
|
|
.write = file_write,
|
|
.seek = file_seek,
|
|
.flush = file_flush,
|
|
.close = file_close,
|
|
.read_all = file_read_all,
|
|
.write_all = file_write_all,
|
|
.free = file_free,
|
|
.list = file_list,
|
|
.join = file_join,
|
|
.create_child = file_create_child,
|
|
};
|
|
|
|
} // namespace
|
|
|
|
constinit const ServiceModule g_fileModule{
|
|
.id = FILE_SERVICE_ID,
|
|
.majorVersion = FILE_SERVICE_MAJOR,
|
|
.minorVersion = FILE_SERVICE_MINOR,
|
|
.service = &s_fileService,
|
|
.available = file_available,
|
|
.initialize = file_initialize,
|
|
.modDetached = file_remove_mod,
|
|
.frameBegin = file_frame_begin,
|
|
.shutdown = file_shutdown,
|
|
};
|
|
|
|
} // namespace dusk::mods::svc
|