Skip to content

Commit 2ed84f4

Browse files
Mr-WiseguyDarioSamothecozies
authored
Implement mod configuration, mod reordering, and extended exports (#95)
* init recomp config_store * Use a custom hash class to enable hetereogenous lookup * Added config registry/option files * switch to using usings * dropdown config type * Added TextField option type * parse/validate button config type * wip callback registry * Add auto enabled. * Cleanup. * Add support for config schema. * Add float arg1 helpers * Config storage for mods. * Proper enum parsing. * Persist mod order and enable. * Enable new mods by default. * Mods directory. * Parse thumbnail when opening mods. * Auto-enabled mods. * Implement extended function exports that pass the caller mod's index as an extra argument * Fix mod configs not saving and default value not getting parsed * Implement API to allow mods to read their config values * Fix config value parsing to allow integral values for double fields * Change construction of ModConfigQueueSaveMod. * Fix N64Recomp commit after rebase --------- Co-authored-by: Dario <[email protected]> Co-authored-by: thecozies <[email protected]>
1 parent ec56fb3 commit 2ed84f4

File tree

9 files changed

+1181
-54
lines changed

9 files changed

+1181
-54
lines changed

librecomp/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ add_library(librecomp STATIC
2020
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_events.cpp"
2121
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_hooks.cpp"
2222
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_manifest.cpp"
23+
"${CMAKE_CURRENT_SOURCE_DIR}/src/mod_config_api.cpp"
2324
"${CMAKE_CURRENT_SOURCE_DIR}/src/overlays.cpp"
2425
"${CMAKE_CURRENT_SOURCE_DIR}/src/pak.cpp"
2526
"${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp"
@@ -35,6 +36,7 @@ add_library(librecomp STATIC
3536

3637
target_include_directories(librecomp PUBLIC
3738
"${CMAKE_CURRENT_SOURCE_DIR}/include"
39+
"${CMAKE_CURRENT_SOURCE_DIR}/src"
3840
"${PROJECT_SOURCE_DIR}/../ultramodern/include"
3941
"${PROJECT_SOURCE_DIR}/../thirdparty"
4042
"${PROJECT_SOURCE_DIR}/../thirdparty/concurrentqueue"

librecomp/include/librecomp/helpers.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef __RECOMP_HELPERS__
22
#define __RECOMP_HELPERS__
33

4+
#include <string>
5+
46
#include "recomp.h"
57
#include <ultramodern/ultra64.h>
68

@@ -36,6 +38,41 @@ T _arg(uint8_t* rdram, recomp_context* ctx) {
3638
}
3739
}
3840

41+
inline float _arg_float_a1(uint8_t* rdram, recomp_context* ctx) {
42+
(void)rdram;
43+
union {
44+
u32 as_u32;
45+
float as_float;
46+
} ret{};
47+
ret.as_u32 = _arg<1, u32>(rdram, ctx);
48+
return ret.as_float;
49+
}
50+
51+
inline float _arg_float_f14(uint8_t* rdram, recomp_context* ctx) {
52+
(void)rdram;
53+
return ctx->f14.fl;
54+
}
55+
56+
template <int arg_index>
57+
std::string _arg_string(uint8_t* rdram, recomp_context* ctx) {
58+
PTR(char) str = _arg<arg_index, PTR(char)>(rdram, ctx);
59+
60+
// Get the length of the byteswapped string.
61+
size_t len = 0;
62+
while (MEM_B(str, len) != 0x00) {
63+
len++;
64+
}
65+
66+
std::string ret{};
67+
ret.reserve(len + 1);
68+
69+
for (size_t i = 0; i < len; i++) {
70+
ret += (char)MEM_B(str, i);
71+
}
72+
73+
return ret;
74+
}
75+
3976
template <typename T>
4077
void _return(recomp_context* ctx, T val) {
4178
static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently");

librecomp/include/librecomp/mods.hpp

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include <array>
1414
#include <cstddef>
1515
#include <variant>
16+
#include <mutex>
17+
18+
#include "blockingconcurrentqueue.h"
1619

1720
#define MINIZ_NO_DEFLATE_APIS
1821
#define MINIZ_NO_ARCHIVE_WRITING_APIS
@@ -27,6 +30,7 @@
2730
namespace N64Recomp {
2831
class Context;
2932
struct LiveGeneratorOutput;
33+
class ShimFunction;
3034
};
3135

3236
namespace recomp {
@@ -55,6 +59,9 @@ struct std::hash<recomp::mods::HookDefinition>
5559

5660
namespace recomp {
5761
namespace mods {
62+
static constexpr std::string_view mods_directory = "mods";
63+
static constexpr std::string_view mod_config_directory = "mod_config";
64+
5865
enum class ModOpenError {
5966
Good,
6067
DoesNotExist,
@@ -65,6 +72,9 @@ namespace recomp {
6572
FailedToParseManifest,
6673
InvalidManifestSchema,
6774
IncorrectManifestFieldType,
75+
MissingConfigSchemaField,
76+
IncorrectConfigSchemaType,
77+
InvalidConfigSchemaDefault,
6878
InvalidVersionString,
6979
InvalidMinimumRecompVersionString,
7080
InvalidDependencyString,
@@ -115,6 +125,13 @@ namespace recomp {
115125

116126
std::string error_to_string(CodeModLoadError);
117127

128+
enum class ConfigOptionType {
129+
None,
130+
Enum,
131+
Number,
132+
String
133+
};
134+
118135
struct ModFileHandle {
119136
virtual ~ModFileHandle() = default;
120137
virtual std::vector<char> read_file(const std::string& filepath, bool& exists) const = 0;
@@ -154,6 +171,45 @@ namespace recomp {
154171
Version version;
155172
};
156173

174+
struct ConfigOptionEnum {
175+
std::vector<std::string> options;
176+
uint32_t default_value = 0;
177+
};
178+
179+
struct ConfigOptionNumber {
180+
double min = 0.0;
181+
double max = 0.0;
182+
double step = 0.0;
183+
int precision = 0;
184+
bool percent = false;
185+
double default_value = 0.0;
186+
};
187+
188+
struct ConfigOptionString {
189+
std::string default_value;
190+
};
191+
192+
typedef std::variant<ConfigOptionEnum, ConfigOptionNumber, ConfigOptionString> ConfigOptionVariant;
193+
194+
struct ConfigOption {
195+
std::string id;
196+
std::string name;
197+
std::string description;
198+
ConfigOptionType type;
199+
ConfigOptionVariant variant;
200+
};
201+
202+
struct ConfigSchema {
203+
std::vector<ConfigOption> options;
204+
std::unordered_map<std::string, size_t> options_by_id;
205+
};
206+
207+
typedef std::variant<std::monostate, uint32_t, double, std::string> ConfigValueVariant;
208+
209+
struct ConfigStorage {
210+
std::unordered_map<std::string, ConfigValueVariant> value_map;
211+
};
212+
157213
struct ModDetails {
158214
std::string mod_id;
159215
std::string display_name;
@@ -176,6 +232,7 @@ namespace recomp {
176232
std::vector<std::string> authors;
177233
std::vector<Dependency> dependencies;
178234
std::unordered_map<std::string, size_t> dependencies_by_id;
235+
ConfigSchema config_schema;
179236
Version minimum_recomp_version;
180237
Version version;
181238
bool runtime_toggleable;
@@ -203,6 +260,7 @@ namespace recomp {
203260
};
204261

205262
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
263+
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
206264

207265
// Internal functions, TODO move to an internal header.
208266
struct PatchData {
@@ -244,6 +302,20 @@ namespace recomp {
244302
bool requires_manifest;
245303
};
246304

305+
struct ModConfigQueueSaveMod {
306+
std::string mod_id;
307+
};
308+
309+
struct ModConfigQueueSave {
310+
uint32_t pad;
311+
};
312+
313+
struct ModConfigQueueEnd {
314+
uint32_t pad;
315+
};
316+
317+
typedef std::variant<ModConfigQueueSaveMod, ModConfigQueueSave, ModConfigQueueEnd> ModConfigQueueVariant;
318+
247319
class LiveRecompilerCodeHandle;
248320
class ModContext {
249321
public:
@@ -252,12 +324,23 @@ namespace recomp {
252324

253325
void register_game(const std::string& mod_game_id);
254326
std::vector<ModOpenErrorDetails> scan_mod_folder(const std::filesystem::path& mod_folder);
255-
void enable_mod(const std::string& mod_id, bool enabled);
327+
void load_mods_config();
328+
void enable_mod(const std::string& mod_id, bool enabled, bool trigger_save);
256329
bool is_mod_enabled(const std::string& mod_id);
330+
bool is_mod_auto_enabled(const std::string& mod_id);
257331
size_t num_opened_mods();
258332
std::vector<ModLoadErrorDetails> load_mods(const GameEntry& game_entry, uint8_t* rdram, int32_t load_address, uint32_t& ram_used);
259333
void unload_mods();
260334
std::vector<ModDetails> get_mod_details(const std::string& mod_game_id);
335+
void set_mod_index(const std::string &mod_game_id, const std::string &mod_id, size_t index);
336+
const ConfigSchema &get_mod_config_schema(const std::string &mod_id) const;
337+
const std::vector<char> &get_mod_thumbnail(const std::string &mod_id) const;
338+
void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value);
339+
void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value);
340+
ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id);
341+
ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id);
342+
void set_mods_config_path(const std::filesystem::path &path);
343+
void set_mod_config_directory(const std::filesystem::path &path);
261344
ModContentTypeId register_content_type(const ModContentType& type);
262345
bool register_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
263346
ModContentTypeId get_code_content_type() const { return code_content_type_id; }
@@ -268,14 +351,15 @@ namespace recomp {
268351
void check_dependencies(ModHandle& mod, std::vector<std::pair<ModLoadError, std::string>>& errors);
269352
CodeModLoadError init_mod_code(uint8_t* rdram, const std::unordered_map<uint32_t, uint16_t>& section_vrom_map, ModHandle& mod, int32_t load_address, bool hooks_available, uint32_t& ram_used, std::string& error_param);
270353
CodeModLoadError load_mod_code(uint8_t* rdram, ModHandle& mod, uint32_t base_event_index, std::string& error_param);
271-
CodeModLoadError resolve_code_dependencies(ModHandle& mod, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
272-
void add_opened_mod(ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types);
354+
CodeModLoadError resolve_code_dependencies(ModHandle& mod, size_t mod_index, const std::unordered_map<recomp_func_t*, recomp::overlays::BasePatchedFunction>& base_patched_funcs, std::string& error_param);
355+
void add_opened_mod(ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& detected_content_types, std::vector<char>&& thumbnail);
273356
void close_mods();
274357
std::vector<ModLoadErrorDetails> regenerate_with_hooks(
275358
const std::vector<std::pair<HookDefinition, size_t>>& sorted_unprocessed_hooks,
276359
const std::unordered_map<uint32_t, uint16_t>& section_vrom_map,
277360
const std::unordered_map<recomp_func_t*, overlays::BasePatchedFunction>& base_patched_funcs,
278361
std::span<const uint8_t> decompressed_rom);
362+
void dirty_mod_configuration_thread_process();
279363

280364
static void on_code_mod_enabled(ModContext& context, const ModHandle& mod);
281365

@@ -285,10 +369,18 @@ namespace recomp {
285369
std::unordered_map<std::string, size_t> mod_game_ids;
286370
std::vector<ModHandle> opened_mods;
287371
std::unordered_map<std::string, size_t> opened_mods_by_id;
372+
std::vector<size_t> opened_mods_order;
373+
std::mutex opened_mods_mutex;
288374
std::unordered_set<std::string> mod_ids;
289375
std::unordered_set<std::string> enabled_mods;
376+
std::unordered_set<std::string> auto_enabled_mods;
290377
std::unordered_map<recomp_func_t*, PatchData> patched_funcs;
291378
std::unordered_map<std::string, size_t> loaded_mods_by_id;
379+
std::unique_ptr<std::thread> mod_configuration_thread;
380+
moodycamel::BlockingConcurrentQueue<ModConfigQueueVariant> mod_configuration_thread_queue;
381+
std::filesystem::path mods_config_path;
382+
std::filesystem::path mod_config_directory;
383+
std::mutex mod_config_storage_mutex;
292384
std::vector<size_t> loaded_code_mods;
293385
// Code handle for vanilla code that was regenerated to add hooks.
294386
std::unique_ptr<LiveRecompilerCodeHandle> regenerated_code_handle;
@@ -299,6 +391,10 @@ namespace recomp {
299391
// Tracks which hook slots have already been processed. Used to regenerate vanilla functions as needed
300392
// to add hooks to any functions that weren't already replaced by a mod.
301393
std::vector<bool> processed_hook_slots;
394+
// Generated shim functions to use for implementing shim exports.
395+
std::vector<std::unique_ptr<N64Recomp::ShimFunction>> shim_functions;
396+
ConfigSchema empty_schema;
397+
std::vector<char> empty_bytes;
302398
size_t num_events = 0;
303399
ModContentTypeId code_content_type_id;
304400
size_t active_game = (size_t)-1;
@@ -321,13 +417,15 @@ namespace recomp {
321417
public:
322418
// TODO make these private and expose methods for the functionality they're currently used in.
323419
ModManifest manifest;
420+
ConfigStorage config_storage;
324421
std::unique_ptr<ModCodeHandle> code_handle;
325422
std::unique_ptr<N64Recomp::Context> recompiler_context;
326423
std::vector<uint32_t> section_load_addresses;
327424
// Content types present in this mod.
328425
std::vector<ModContentTypeId> content_types;
426+
std::vector<char> thumbnail;
329427

330-
ModHandle(const ModContext& context, ModManifest&& manifest, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types);
428+
ModHandle(const ModContext& context, ModManifest&& manifest, ConfigStorage&& config_storage, std::vector<size_t>&& game_indices, std::vector<ModContentTypeId>&& content_types, std::vector<char>&& thumbnail);
331429
ModHandle(const ModHandle& rhs) = delete;
332430
ModHandle& operator=(const ModHandle& rhs) = delete;
333431
ModHandle(ModHandle&& rhs);
@@ -457,12 +555,23 @@ namespace recomp {
457555

458556
CodeModLoadError validate_api_version(uint32_t api_version, std::string& error_param);
459557

460-
void initialize_mod_recompiler();
558+
void initialize_mods();
461559
void scan_mods();
560+
std::filesystem::path get_mods_directory();
462561
void enable_mod(const std::string& mod_id, bool enabled);
463562
bool is_mod_enabled(const std::string& mod_id);
563+
bool is_mod_auto_enabled(const std::string& mod_id);
564+
const ConfigSchema &get_mod_config_schema(const std::string &mod_id);
565+
const std::vector<char> &get_mod_thumbnail(const std::string &mod_id);
566+
void set_mod_config_value(size_t mod_index, const std::string &option_id, const ConfigValueVariant &value);
567+
void set_mod_config_value(const std::string &mod_id, const std::string &option_id, const ConfigValueVariant &value);
568+
ConfigValueVariant get_mod_config_value(size_t mod_index, const std::string &option_id);
569+
ConfigValueVariant get_mod_config_value(const std::string &mod_id, const std::string &option_id);
464570
ModContentTypeId register_mod_content_type(const ModContentType& type);
465571
bool register_mod_container_type(const std::string& extension, const std::vector<ModContentTypeId>& content_types, bool requires_manifest);
572+
573+
574+
void register_config_exports();
466575
}
467576
};
468577

librecomp/include/librecomp/overlays.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace recomp {
2525

2626
void register_patches(const char* patch_data, size_t patch_size, SectionTableEntry* code_sections, size_t num_sections);
2727
void register_base_export(const std::string& name, recomp_func_t* func);
28+
void register_ext_base_export(const std::string& name, recomp_func_ext_t* func);
2829
void register_base_exports(const FunctionExport* exports);
2930
void register_base_events(char const* const* event_names);
3031
void register_manual_patch_symbols(const ManualPatchSymbol* manual_patch_symbols);
@@ -38,6 +39,7 @@ namespace recomp {
3839
bool get_func_entry_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset, FuncEntry& func_out);
3940
recomp_func_t* get_func_by_section_index_function_offset(uint16_t code_section_index, uint32_t function_offset);
4041
recomp_func_t* get_base_export(const std::string& export_name);
42+
recomp_func_ext_t* get_ext_base_export(const std::string& export_name);
4143
size_t get_base_event_index(const std::string& event_name);
4244
size_t num_base_events();
4345

librecomp/src/mod_config_api.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#include "librecomp/mods.hpp"
2+
#include "librecomp/helpers.hpp"
3+
#include "librecomp/addresses.hpp"
4+
5+
void recomp_get_config_u32(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
6+
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
7+
if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) {
8+
_return(ctx, *as_u32);
9+
}
10+
else if (double* as_double = std::get_if<double>(&val)) {
11+
_return(ctx, uint32_t(int32_t(*as_double)));
12+
}
13+
else {
14+
_return(ctx, uint32_t{0});
15+
}
16+
}
17+
18+
void recomp_get_config_double(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
19+
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
20+
if (uint32_t* as_u32 = std::get_if<uint32_t>(&val)) {
21+
ctx->f0.d = double(*as_u32);
22+
}
23+
else if (double* as_double = std::get_if<double>(&val)) {
24+
ctx->f0.d = *as_double;
25+
}
26+
else {
27+
ctx->f0.d = 0.0;
28+
}
29+
}
30+
31+
void recomp_get_config_string(uint8_t* rdram, recomp_context* ctx, size_t mod_index) {
32+
recomp::mods::ConfigValueVariant val = recomp::mods::get_mod_config_value(mod_index, _arg_string<0>(rdram, ctx));
33+
if (std::string* as_string = std::get_if<std::string>(&val)) {
34+
const std::string& str = *as_string;
35+
// Allocate space in the recomp heap to hold the string, including the null terminator.
36+
size_t alloc_size = (str.size() + 1 + 15) & ~15;
37+
gpr offset = reinterpret_cast<uint8_t*>(recomp::alloc(rdram, alloc_size)) - rdram;
38+
gpr addr = offset + 0xFFFFFFFF80000000ULL;
39+
40+
// Copy the string's data into the allocated memory and null terminate it.
41+
for (size_t i = 0; i < str.size(); i++) {
42+
MEM_B(i, addr) = str[i];
43+
}
44+
MEM_B(str.size(), addr) = 0;
45+
46+
// Return the allocated memory.
47+
ctx->r2 = addr;
48+
}
49+
else {
50+
_return(ctx, NULLPTR);
51+
}
52+
}
53+
54+
void recomp_free_config_string(uint8_t* rdram, recomp_context* ctx) {
55+
gpr str_rdram = (gpr)_arg<0, PTR(char)>(rdram, ctx);
56+
gpr offset = str_rdram - 0xFFFFFFFF80000000ULL;
57+
58+
recomp::free(rdram, rdram + offset);
59+
}
60+
61+
void recomp::mods::register_config_exports() {
62+
recomp::overlays::register_ext_base_export("recomp_get_config_u32", recomp_get_config_u32);
63+
recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double);
64+
recomp::overlays::register_ext_base_export("recomp_get_config_string", recomp_get_config_string);
65+
recomp::overlays::register_base_export("recomp_free_config_string", recomp_free_config_string);
66+
}

0 commit comments

Comments
 (0)