Skip to content

Commit f7c587b

Browse files
committed
Updated mod build-system to move from a singleton to an splitted build to avoid corruption issues and added mods view
1 parent 7a9f922 commit f7c587b

10 files changed

Lines changed: 156 additions & 87 deletions

File tree

mod-src/CMakeLists.txt

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,36 @@ set(INCLUDE_DIRS
1616
${PARENT_DIR}/libultraship/include/libultraship
1717
)
1818

19-
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*")
20-
21-
set(AMALGAMATED_FILE ${CMAKE_CURRENT_SOURCE_DIR}/output/mod.c)
22-
get_filename_component(OUTPUT_DIR ${AMALGAMATED_FILE} DIRECTORY)
23-
24-
add_custom_command(
25-
OUTPUT ${AMALGAMATED_FILE}
26-
COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTPUT_DIR}
27-
COMMAND Python3::Interpreter ${PARENT_DIR}/tools/merge.py
28-
--out ${AMALGAMATED_FILE}
29-
--includes ${INCLUDE_DIRS}
30-
--srcs ${SRC_FILES}
31-
DEPENDS ${SRC_FILES} ${PARENT_DIR}/tools/merge.py
32-
VERBATIM
33-
)
19+
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c")
20+
21+
set(BUILD_GEN ${CMAKE_CURRENT_SOURCE_DIR}/output/build.gen)
22+
get_filename_component(OUTPUT_DIR ${BUILD_GEN} DIRECTORY)
23+
file(MAKE_DIRECTORY ${OUTPUT_DIR})
24+
25+
set(INTERMEDIATE_OUTPUTS "")
26+
set(DIST_OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/output/dist)
27+
28+
foreach(SOURCE_FILE ${SRC_FILES})
29+
file(RELATIVE_PATH RELATIVE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src ${SOURCE_FILE})
30+
set(OUTPUT_FILE ${DIST_OUTPUT}/${RELATIVE_FILE})
31+
get_filename_component(INTERMEDIATE_DIR ${OUTPUT_FILE} DIRECTORY)
32+
33+
add_custom_command(
34+
OUTPUT ${OUTPUT_FILE}
35+
COMMAND ${CMAKE_COMMAND} -E make_directory ${INTERMEDIATE_DIR}
36+
COMMAND Python3::Interpreter ${PARENT_DIR}/tools/merge.py
37+
--out ${OUTPUT_FILE}
38+
--includes ${INCLUDE_DIRS}
39+
--src ${SOURCE_FILE}
40+
DEPENDS ${SOURCE_FILE} ${PARENT_DIR}/tools/merge.py
41+
VERBATIM
42+
)
43+
44+
list(APPEND INTERMEDIATE_OUTPUTS ${OUTPUT_FILE})
45+
endforeach()
46+
47+
string(REPLACE ";" "\n" FORMATTED_FILE_LIST "${INTERMEDIATE_OUTPUTS}")
48+
string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/output/" "" FORMATTED_FILE_LIST "${FORMATTED_FILE_LIST}")
49+
file(WRITE ${BUILD_GEN} "${FORMATTED_FILE_LIST}\n")
3450

35-
add_custom_target(generate_packaged_mod ALL DEPENDS ${AMALGAMATED_FILE})
51+
add_custom_target(generate_packaged_mod ALL DEPENDS ${INTERMEDIATE_OUTPUTS})

mod-src/output/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Demo Mod",
3-
"main": "mod.c",
3+
"main": "build.gen",
44
"version": "1.0.0",
55
"website": "https://example.com",
66
"description": "A simple demo mod for testing purposes.",

src/port/api/ui.cpp

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88

99
// Helper function to map C Enums to C++ Enums safely
1010
static UIWidgets::ComponentAlignments MapAlign(C_ComponentAlign align) {
11-
if (align == C_ALIGN_RIGHT) return UIWidgets::ComponentAlignments::Right;
11+
if (align == C_ALIGN_RIGHT)
12+
return UIWidgets::ComponentAlignments::Right;
1213
return UIWidgets::ComponentAlignments::Left;
1314
}
1415

1516
static UIWidgets::LabelPositions MapLabel(C_LabelPos pos) {
16-
if (pos == C_LABEL_FAR) return UIWidgets::LabelPositions::Far;
17+
if (pos == C_LABEL_FAR)
18+
return UIWidgets::LabelPositions::Far;
1719
return UIWidgets::LabelPositions::Near;
1820
}
1921

@@ -34,19 +36,22 @@ extern "C" void C_RemoveSidebarEntry(const char* sidebarName) {
3436
}
3537

3638
extern "C" void C_AddWidget(const char* sidebar, int column, const char* label, C_WidgetConfig* config) {
37-
if (!config) return;
39+
if (!config)
40+
return;
3841

3942
WidgetPath path = { "Mods", sidebar, static_cast<SectionColumns>(column) };
4043

4144
// 1. Begin the Widget Builder Chain
4245
WidgetInfo& widget = GhostshipGui::mGhostshipMenu->AddWidget(path, label, static_cast<WidgetType>(config->type))
43-
.RaceDisable(config->race_disable)
44-
.SameLine(config->same_line)
45-
.HideInSearch(config->hide_in_search);
46+
.RaceDisable(config->race_disable)
47+
.SameLine(config->same_line)
48+
.HideInSearch(config->hide_in_search);
4649

4750
// 2. Apply Base Attributes
48-
if (config->cvar) widget.CVar(config->cvar);
49-
if (config->window_name) widget.WindowName(config->window_name);
51+
if (config->cvar)
52+
widget.CVar(config->cvar);
53+
if (config->window_name)
54+
widget.WindowName(config->window_name);
5055

5156
// 3. Apply Callbacks
5257
if (config->callback) {
@@ -67,10 +72,10 @@ extern "C" void C_AddWidget(const char* sidebar, int column, const char* label,
6772
case C_WIDGET_AUDIO_BACKEND:
6873
case C_WIDGET_VIDEO_BACKEND: {
6974
auto opt = UIWidgets::ComboboxOptions()
70-
.Tooltip(config->opts.combo.tooltip ? config->opts.combo.tooltip : "")
71-
.DefaultIndex(config->opts.combo.default_index)
72-
.ComponentAlignment(MapAlign(config->opts.combo.alignment))
73-
.LabelPosition(MapLabel(config->opts.combo.label_pos));
75+
.Tooltip(config->opts.combo.tooltip ? config->opts.combo.tooltip : "")
76+
.DefaultIndex(config->opts.combo.default_index)
77+
.ComponentAlignment(MapAlign(config->opts.combo.alignment))
78+
.LabelPosition(MapLabel(config->opts.combo.label_pos));
7479
widget.Options(opt);
7580
break;
7681
}
@@ -79,13 +84,14 @@ extern "C" void C_AddWidget(const char* sidebar, int column, const char* label,
7984
case C_WIDGET_SLIDER_INT:
8085
case C_WIDGET_CVAR_SLIDER_INT: {
8186
auto opt = UIWidgets::IntSliderOptions()
82-
.Tooltip(config->opts.slider_int.tooltip ? config->opts.slider_int.tooltip : "")
83-
.Min(config->opts.slider_int.min)
84-
.Max(config->opts.slider_int.max)
85-
.DefaultValue(config->opts.slider_int.default_val)
86-
.Step(config->opts.slider_int.step);
87-
88-
if (config->opts.slider_int.format) opt.Format(config->opts.slider_int.format);
87+
.Tooltip(config->opts.slider_int.tooltip ? config->opts.slider_int.tooltip : "")
88+
.Min(config->opts.slider_int.min)
89+
.Max(config->opts.slider_int.max)
90+
.DefaultValue(config->opts.slider_int.default_val)
91+
.Step(config->opts.slider_int.step);
92+
93+
if (config->opts.slider_int.format)
94+
opt.Format(config->opts.slider_int.format);
8995
widget.Options(opt);
9096
break;
9197
}
@@ -94,13 +100,14 @@ extern "C" void C_AddWidget(const char* sidebar, int column, const char* label,
94100
case C_WIDGET_SLIDER_FLOAT:
95101
case C_WIDGET_CVAR_SLIDER_FLOAT: {
96102
auto opt = UIWidgets::FloatSliderOptions()
97-
.Tooltip(config->opts.slider_float.tooltip ? config->opts.slider_float.tooltip : "")
98-
.Min(config->opts.slider_float.min)
99-
.Max(config->opts.slider_float.max)
100-
.DefaultValue(config->opts.slider_float.default_val)
101-
.Step(config->opts.slider_float.step);
102-
103-
if (config->opts.slider_float.format) opt.Format(config->opts.slider_float.format);
103+
.Tooltip(config->opts.slider_float.tooltip ? config->opts.slider_float.tooltip : "")
104+
.Min(config->opts.slider_float.min)
105+
.Max(config->opts.slider_float.max)
106+
.DefaultValue(config->opts.slider_float.default_val)
107+
.Step(config->opts.slider_float.step);
108+
109+
if (config->opts.slider_float.format)
110+
opt.Format(config->opts.slider_float.format);
104111
widget.Options(opt);
105112
break;
106113
}
@@ -109,16 +116,16 @@ extern "C" void C_AddWidget(const char* sidebar, int column, const char* label,
109116
case C_WIDGET_CHECKBOX:
110117
case C_WIDGET_CVAR_CHECKBOX: {
111118
widget.Options(UIWidgets::CheckboxOptions()
112-
.Tooltip(config->opts.checkbox.tooltip ? config->opts.checkbox.tooltip : "")
113-
.DefaultValue(config->opts.checkbox.default_val));
119+
.Tooltip(config->opts.checkbox.tooltip ? config->opts.checkbox.tooltip : "")
120+
.DefaultValue(config->opts.checkbox.default_val));
114121
break;
115122
}
116123

117124
// --- TEXT ---
118125
case C_WIDGET_TEXT:
119126
case C_WIDGET_SEPARATOR_TEXT: {
120127
widget.Options(UIWidgets::TextOptions()
121-
// .Center() or whatever alignment method your UIWidgets uses
128+
// .Center() or whatever alignment method your UIWidgets uses
122129
);
123130
break;
124131
}
@@ -127,33 +134,30 @@ extern "C" void C_AddWidget(const char* sidebar, int column, const char* label,
127134
case C_WIDGET_COLOR_PICKER:
128135
case C_WIDGET_CVAR_COLOR_PICKER: {
129136
// Assuming Color_RGBA8 or similar constructor for default color
130-
widget.Options(UIWidgets::ColorPickerOptions()
131-
.Tooltip(config->opts.color_picker.tooltip ? config->opts.color_picker.tooltip : "")
132-
// .DefaultColor(Color_RGBA8(config->opts.color_picker.default_r, ...))
137+
widget.Options(UIWidgets::ColorPickerOptions().Tooltip(
138+
config->opts.color_picker.tooltip ? config->opts.color_picker.tooltip : "")
139+
// .DefaultColor(Color_RGBA8(config->opts.color_picker.default_r, ...))
133140
);
134141
break;
135142
}
136143

137144
// --- BUTTONS / WINDOWS / SEPARATORS ---
138145
case C_WIDGET_WINDOW_BUTTON: {
139-
widget.Options(UIWidgets::WindowButtonOptions()
140-
.Tooltip(config->opts.generic.tooltip ? config->opts.generic.tooltip : "")
141-
);
146+
widget.Options(UIWidgets::WindowButtonOptions().Tooltip(
147+
config->opts.generic.tooltip ? config->opts.generic.tooltip : ""));
142148
break;
143149
}
144150

145151
case C_WIDGET_BUTTON: {
146-
widget.Options(UIWidgets::ButtonOptions()
147-
.Tooltip(config->opts.generic.tooltip ? config->opts.generic.tooltip : "")
148-
);
152+
widget.Options(
153+
UIWidgets::ButtonOptions().Tooltip(config->opts.generic.tooltip ? config->opts.generic.tooltip : ""));
149154
break;
150155
}
151156

152157
// --- DEFAULT FALLBACK ---
153158
default: {
154-
widget.Options(UIWidgets::WidgetOptions()
155-
.Tooltip(config->opts.generic.tooltip ? config->opts.generic.tooltip : "")
156-
);
159+
widget.Options(
160+
UIWidgets::WidgetOptions().Tooltip(config->opts.generic.tooltip ? config->opts.generic.tooltip : ""));
157161
break;
158162
}
159163
}

src/port/scripting/loader.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,23 @@ std::string ModInstance::GenerateTempFile() {
6666
void FixELFHeader(const std::string& path) {
6767
// 1. Read the file into a buffer
6868
std::ifstream in(path, std::ios::binary | std::ios::ate);
69-
if (!in.is_open()) return;
69+
if (!in.is_open())
70+
return;
7071

7172
std::streamsize size = in.tellg();
7273
in.seekg(0, std::ios::beg);
7374

7475
std::vector<uint8_t> buffer(size);
75-
if (!in.read(reinterpret_cast<char*>(buffer.data()), size)) return;
76+
if (!in.read(reinterpret_cast<char*>(buffer.data()), size))
77+
return;
7678
in.close();
7779

78-
if (buffer.size() < sizeof(Elf64_Ehdr)) return;
80+
if (buffer.size() < sizeof(Elf64_Ehdr))
81+
return;
7982

8083
// 2. ELF Surgery
8184
Elf64_Ehdr* header = reinterpret_cast<Elf64_Ehdr*>(buffer.data());
82-
85+
8386
if (header->e_ident[EI_CLASS] == ELFCLASS64) {
8487
Elf64_Phdr* phdr = reinterpret_cast<Elf64_Phdr*>(buffer.data() + header->e_phoff);
8588

@@ -92,12 +95,12 @@ void FixELFHeader(const std::string& path) {
9295
phdr[i].p_flags = PF_R | PF_W; // Set to RW (No Execute)
9396
phdr[i].p_align = 0x10;
9497
patched = true;
95-
break;
98+
break;
9699
}
97100
}
98101

99102
if (!patched) {
100-
// Last resort: if we can't find either, we'll try to find any
103+
// Last resort: if we can't find either, we'll try to find any
101104
// non-critical segment that isn't LOAD (1) or DYNAMIC (2).
102105
for (int i = 0; i < header->e_phnum; i++) {
103106
if (phdr[i].p_type != PT_LOAD && phdr[i].p_type != PT_DYNAMIC) {
@@ -111,7 +114,8 @@ void FixELFHeader(const std::string& path) {
111114

112115
// 3. Write it back
113116
std::ofstream out(path, std::ios::binary | std::ios::trunc);
114-
if (!out.is_open()) return;
117+
if (!out.is_open())
118+
return;
115119
out.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
116120
out.close();
117121

src/port/scripting/scripting.cpp

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ std::optional<std::vector<uint8_t>> LoadFromO2R(const std::string& path,
2828
return std::vector<uint8_t>(file->Buffer->begin(), file->Buffer->end());
2929
}
3030

31+
std::string_view trim(std::string_view v) {
32+
v.remove_prefix(std::min(v.find_first_not_of(" \t\r\n"), v.size()));
33+
auto last = v.find_last_not_of(" \t\r\n");
34+
if (last != std::string_view::npos) {
35+
v.remove_suffix(v.size() - last - 1);
36+
} else {
37+
v = "";
38+
}
39+
return v;
40+
}
41+
3142
void ScriptingLayer::Load(const std::string& path, const std::shared_ptr<Ship::Archive>& archive, int codeVersion) {
3243
const auto result = LoadFromO2R(path, archive);
3344

@@ -57,9 +68,35 @@ void ScriptingLayer::Load(const std::string& path, const std::shared_ptr<Ship::A
5768

5869
tcc_set_output_type(s, TCC_OUTPUT_DLL);
5970

60-
if (tcc_compile_string(s, reinterpret_cast<const char*>(raw.data())) == -1) {
61-
tcc_delete(s);
62-
throw std::runtime_error("Failed to compile " + path);
71+
std::string gen_content(raw.begin(), raw.end());
72+
std::istringstream stream(gen_content);
73+
std::string line;
74+
75+
while (std::getline(stream, line)) {
76+
if (line.empty()) {
77+
continue;
78+
}
79+
80+
line.erase(line.find_last_not_of(" \r\n\t") + 1);
81+
line.erase(0, line.find_first_not_of(" \r\n\t"));
82+
83+
if (line.empty() || line[0] == '#') {
84+
continue;
85+
}
86+
87+
std::string safe_path = line;
88+
89+
auto buf = LoadFromO2R(safe_path, archive);
90+
if (!buf.has_value()) {
91+
tcc_delete(s);
92+
throw std::runtime_error("Failed to load script file: '" + safe_path + "'");
93+
}
94+
95+
std::string sourceCode(reinterpret_cast<const char*>(buf->data()), buf->size());
96+
if (tcc_compile_string(s, sourceCode.c_str()) == -1) {
97+
tcc_delete(s);
98+
throw std::runtime_error("TCC Error in " + safe_path);
99+
}
63100
}
64101

65102
ModInstance instance;

src/port/ui/GhostshipMenu.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ void GhostshipMenu::InitElement() {
148148
AddMenuRando();
149149
AddMenuAchievements();
150150
AddMenuDevTools();
151-
AddMenuEntry("Mods", CVAR_SETTING("Menu.ModsSidebarSection"));
151+
AddModMenu();
152152

153153
if (CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0)) {
154154
InsertSidebarSearch();

src/port/ui/GhostshipMenu.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class GhostshipMenu : public Ship::Menu {
2626
void AddMenuDevTools();
2727
void AddMenuRando();
2828
void AddMenuAchievements();
29+
void AddModMenu();
2930

3031
private:
3132
char mGitCommitHashTruncated[8];

src/port/ui/GhostshipMenuDevTools.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,13 @@ void GhostshipMenu::AddMenuDevTools() {
143143
.Options(WindowButtonOptions().Tooltip("Enables the separate Object Viewer Window."));
144144
}
145145

146+
void GhostshipMenu::AddModMenu() {
147+
AddMenuEntry("Mods", CVAR_SETTING("Menu.ModsSidebarSection"));
148+
149+
WidgetPath path = { "Mods", "General", SECTION_COLUMN_1 };
150+
151+
AddSidebarEntry(path.sectionName, path.sidebarName, 1);
152+
AddWidget(path, "Mod Settings", WIDGET_TEXT);
153+
};
154+
146155
} // namespace GhostshipGui

src/port/ui/Menu.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -943,18 +943,18 @@ void Menu::DrawElement() {
943943
}
944944
ImGui::End();
945945

946-
if(menuEntries.contains(headerIndex) && menuEntries.at(headerIndex).markForDelete) {
946+
if (menuEntries.contains(headerIndex) && menuEntries.at(headerIndex).markForDelete) {
947947
menuEntries.erase(headerIndex);
948948
std::erase(menuOrder, headerIndex);
949949
return;
950950
}
951951

952-
if(sidebar->contains(sectionIndex) && sidebar->at(sectionIndex).markForDelete) {
952+
if (sidebar->contains(sectionIndex) && sidebar->at(sectionIndex).markForDelete) {
953953
sidebar->erase(sectionIndex);
954-
menuEntries.at(headerIndex).sidebarOrder.erase(
955-
std::remove(menuEntries.at(headerIndex).sidebarOrder.begin(), menuEntries.at(headerIndex).sidebarOrder.end(),
956-
sectionIndex),
957-
menuEntries.at(headerIndex).sidebarOrder.end());
954+
menuEntries.at(headerIndex)
955+
.sidebarOrder.erase(std::remove(menuEntries.at(headerIndex).sidebarOrder.begin(),
956+
menuEntries.at(headerIndex).sidebarOrder.end(), sectionIndex),
957+
menuEntries.at(headerIndex).sidebarOrder.end());
958958
}
959959
}
960960
} // namespace Ship

0 commit comments

Comments
 (0)