Spellforce-Spell-framework
Loading...
Searching...
No Matches
sf_screens_module.c
Go to the documentation of this file.
1
5
6#include <windows.h>
7#include <ctype.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include "sf_screens_module.h"
13#include "sf_screens_loader.h"
14#include "sf_hooks.h"
15#include "sf_wrappers.h"
16
17#include "hooks/sf_campaign_hook.h" /* checkFileExists */
18
21
23
24static ScreenEntry s_screen_entries[MAX_SCREEN_ENTRIES];
25static int s_screen_entry_count = 0;
26
27static void str_to_lower(char *str)
28{
29 for (int i = 0; str[i] != '\0'; i++)
30 {
31 str[i] = (char)tolower((unsigned char)str[i]);
32 }
33}
34
35static bool has_msb_extension(const char *name)
36{
37 size_t len = strlen(name);
38 if (len < 4)
39 {
40 return false;
41 }
42
43 const char *ext = name + (len - 4);
44 return (tolower((unsigned char)ext[0]) == '.' &&
45 tolower((unsigned char)ext[1]) == 'm' &&
46 tolower((unsigned char)ext[2]) == 's' &&
47 tolower((unsigned char)ext[3]) == 'b');
48}
49
51static bool screen_mesh_exists(const char *msb_file)
52{
53 char mesh_path[MAX_PATH];
54 snprintf(mesh_path, sizeof(mesh_path), "mesh\\%s", msb_file);
55
56 SF_String path;
57 uiAPI.SFStringConstructor_char(&path, mesh_path);
58 bool exists = (checkFileExists(&path) != 0);
59 uiAPI.SFStringDestructor(&path);
60
61 if (!exists)
62 {
63 log_debug(DEBUG_MED, "| - Loading screen mesh not found: %s", mesh_path);
64 }
65
66 return exists;
67}
68
73static bool accept_screen_entry(const ScreenEntry *entry)
74{
75 if (entry->map_name[0] == '\0' || entry->msb_file[0] == '\0')
76 {
77 report_mod_error(g_screens_mod, "screens.json: entry with an empty map name or mesh name");
78 return false;
79 }
80
81 if (!has_msb_extension(entry->msb_file))
82 {
84 "screens.json: \"%s\" maps to \"%s\", which is not a .msb mesh",
85 entry->map_name, entry->msb_file);
86 return false;
87 }
88
89 for (int i = 0; i < s_screen_entry_count; i++)
90 {
91 if (strcmp(s_screen_entries[i].map_name, entry->map_name) == 0)
92 {
94 "screens.json: map \"%s\" is listed twice, keeping \"%s\"",
95 entry->map_name, s_screen_entries[i].msb_file);
96 return false;
97 }
98 }
99
100 if (!screen_mesh_exists(entry->msb_file))
101 {
103 "screens.json: map \"%s\" needs \"%s\", which is missing from mesh\\",
104 entry->map_name, entry->msb_file);
105 return false;
106 }
107
108 s_screen_entries[s_screen_entry_count] = *entry;
109 s_screen_entry_count++;
110 return true;
111}
112
114{
115 char version_tag_buffer[128];
116 snprintf(version_tag_buffer, sizeof(version_tag_buffer), "%d.%d.%d-%s",
119
120 g_screens_mod = createModInfo("Loading Screen Module", version_tag_buffer,
121 "Muddykat, UnSchtalch",
122 "Swaps in custom loading screens listed by the sfsf\\screens.json file.");
125
126 s_screen_entry_count = 0;
127
128 char currentDir[MAX_PATH];
129 GetCurrentDirectory(MAX_PATH, currentDir);
130
131 char path[MAX_PATH];
132 snprintf(path, sizeof(path), "%s\\sfsf\\screens.json", currentDir);
133
135 int parsed_count = 0;
136
137 if (!parse_screens_json_file(path, parsed, MAX_SCREEN_ENTRIES, &parsed_count))
138 {
139 // A missing screens.json is a normal install, not a mod error.
140 log_info("| - No screens.json found at %s (custom loading screens disabled)", path);
141 return;
142 }
143
144 int rejected = 0;
145 for (int i = 0; i < parsed_count; i++)
146 {
147 if (!accept_screen_entry(&parsed[i]))
148 {
149 rejected++;
150 }
151 }
152
153 log_info("| - %d loading screen(s) registered, %d rejected", s_screen_entry_count, rejected);
154}
155
156const char *find_screen_for_map(const char *map_name)
157{
158 if (map_name == NULL || s_screen_entry_count == 0)
159 {
160 return NULL;
161 }
162
163 char lowered[SCREEN_MAP_NAME_LEN];
164 size_t len = strlen(map_name);
165 if (len >= sizeof(lowered))
166 {
167 len = sizeof(lowered) - 1;
168 }
169 memcpy(lowered, map_name, len);
170 lowered[len] = '\0';
171 str_to_lower(lowered);
172
173 /* Strip the directory, then the extension. */
174 const char *last_separator = strrchr(lowered, '\\');
175 char *filename = (char *)(last_separator ? last_separator + 1 : lowered);
176
177 char *dot = strrchr(filename, '.');
178 if (dot != NULL)
179 {
180 *dot = '\0';
181 }
182
183 /* Vanilla maps are prefixed, e.g. "000_greyfell" or "p101_mirraw_thur".
184 * Match on the part after the first underscore when there is one, so
185 * screens.json can key on the readable name. */
186 const char *underscore = strchr(filename, '_');
187 const char *extracted = (underscore != NULL) ? underscore + 1 : filename;
188
189 if (extracted[0] == '\0')
190 {
191 return NULL;
192 }
193
194 for (int i = 0; i < s_screen_entry_count; i++)
195 {
196 if (strcmp(s_screen_entries[i].map_name, extracted) == 0)
197 {
198 return s_screen_entries[i].msb_file;
199 }
200 }
201
202 return NULL;
203}
204
checkFileExists_ptr checkFileExists
void clear_mod_errors(SFMod *mod)
Empties a mod's error buffer.
void register_mod_for_listing(SFMod *mod, ModType type)
Adds a mod to the list rendered by the in-game mod list screen.
void report_mod_error(SFMod *mod, const char *format,...)
Logs an error, appends it to the mod's error buffer and counts it.
@ MOD_TYPE_CORE
UiFunctions uiAPI
Definition sf_hooks.c:50
#define SCREEN_MAP_NAME_LEN
bool parse_screens_json_file(const char *path, ScreenEntry *out_entries, int max_entries, int *out_count)
Reads a screens.json: a single object of map_name : msb_file pairs.
const char * find_screen_for_map(const char *map_name)
Finds the loading screen mesh for a map, or NULL for the default.
#define MAX_SCREEN_ENTRIES
void initialize_screens_module()
Loads and validates sfsf\screens.json.
SFMod * g_screens_mod
void log_debug(DebugLevel level, const char *format,...)
void log_info(const char *format,...)
SFMod * createModInfo(const char *mod_id, const char *mod_version, const char *mod_author, const char *mod_description)
#define SPELLFRAMEWORK_TAG
Definition sf_registry.h:3
#define SPELLFRAMEWORK_VERSION_PATCH
Definition sfsf.h:20
#define SPELLFRAMEWORK_VERSION_MAJOR
Definition sfsf.h:18
#define SPELLFRAMEWORK_VERSION_MINOR
Definition sfsf.h:19
One "map name -> loading screen mesh" pair.
char msb_file[128]
char map_name[128]