25static bool campaign_map_exists(
const char *campaign_folder,
const char *map_name)
27 char map_path[MAX_PATH];
28 snprintf(map_path,
sizeof(map_path),
"map\\%s\\%s.map", campaign_folder, map_name);
31 uiAPI.SFStringConstructor_char(&path, map_path);
33 uiAPI.SFStringDestructor(&path);
45static const char *k_reserved_folders[] = {
"save",
"temp",
"char",
"campaign2",
"campaign3" };
47static bool equals_ignore_case(
const char *a,
const char *b)
49 while (*a !=
'\0' && *b !=
'\0')
51 if (tolower((
unsigned char)*a) != tolower((
unsigned char)*b))
65static bool is_campaign_folder_safe(
const char *folder,
const char *source)
67 if (strchr(folder,
'\\') != NULL || strchr(folder,
'/') != NULL ||
68 strchr(folder,
':') != NULL || strstr(folder,
"..") != NULL)
71 "Campaign [%s]: campaign_folder \"%s\" must be a plain folder name (no slashes, colons or \"..\")",
76 for (
size_t i = 0; i < (
sizeof(k_reserved_folders) /
sizeof(k_reserved_folders[0])); i++)
78 if (equals_ignore_case(folder, k_reserved_folders[i]))
81 "Campaign [%s]: campaign_folder \"%s\" is reserved by the engine",
94static bool validate_campaign(
CampaignJson *campaign,
const char *source)
125 "Campaign [%s]: avatar_type %u is outside the custom range %u-%u",
142 "Campaign [%s]: start_map \"%s\" not found at map\\%s\\%s.map",
151 "Campaign [%s]: tutorial_map \"%s\" not found at map\\%s\\%s.map",
167 if (def->
author[0] ==
'\0')
174 log_debug(
DEBUG_MED,
"| - Campaign [%s]: no tutorial_map, the tutorial checkbox will be ignored", source);
184static bool check_campaign_conflicts(
const CampaignJson *campaign,
const char *source)
187 bool conflict_free =
true;
194 conflict_free =
false;
200 log_warning(
"| - Campaign [%s] reuses the name \"%s\"", source, def->
name);
205 0,
"Campaign avatar type"))
208 conflict_free =
false;
211 return conflict_free;
215static bool load_campaign_file(
const char *path,
const char *filename)
220 "Campaign [%s] skipped: the registry is full (%d campaigns max)",
232 if (!validate_campaign(&campaign, filename))
237 if (!check_campaign_conflicts(&campaign, filename))
260 char version_tag_buffer[128];
261 snprintf(version_tag_buffer,
sizeof(version_tag_buffer),
"%d.%d.%d-%s",
266 "Muddykat, UnSchtalch",
267 "Loads custom campaigns defined by JSON files in the sfsf\\campaigns folder.");
271 char currentDir[MAX_PATH];
272 GetCurrentDirectory(MAX_PATH, currentDir);
274 char campaignDirectory[MAX_PATH];
275 snprintf(campaignDirectory,
sizeof(campaignDirectory),
"%s\\sfsf\\campaigns", currentDir);
277 char searchPath[MAX_PATH];
278 snprintf(searchPath,
sizeof(searchPath),
"%s\\*.json", campaignDirectory);
280 WIN32_FIND_DATA findFileData;
281 HANDLE hFind = FindFirstFile(searchPath, &findFileData);
282 if (hFind == INVALID_HANDLE_VALUE)
285 log_info(
"| - No campaigns found in %s (custom campaigns disabled)", campaignDirectory);
290 uint32_t skipped = 0;
293 if ((findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
298 char campaignPath[MAX_PATH];
299 snprintf(campaignPath,
sizeof(campaignPath),
"%s\\%s", campaignDirectory, findFileData.cFileName);
301 log_info(
"| - Reading campaign definition [%s]", findFileData.cFileName);
303 if (load_campaign_file(campaignPath, findFileData.cFileName))
311 }
while (FindNextFile(hFind, &findFileData) != 0);
315 log_info(
"| - %u campaign(s) registered, %u skipped", loaded, skipped);
uint32_t g_campaign_count
int32_t register_campaign(const SFSF_CampaignDef *def)
#define SFSF_AVATAR_TYPE_BASE
SFSF_CampaignDef g_campaigns[16]
#define SFSF_AVATAR_TYPE_MAX
checkFileExists_ptr checkFileExists
#define SFSF_MAX_CAMPAIGNS
bool parse_campaign_json_file(const char *path, CampaignJson *out_campaign)
Reads and parses a campaign JSON file.
void initialize_campaign_module()
Creates the module's mod entry and loads sfsf\campaigns*.json.
bool claim_numeric_id(ConflictDomain domain, uint32_t id, SFMod *mod, uint32_t vanilla_max, const char *id_label)
Claims a numeric ID within a domain for a mod.
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.
bool claim_string_id(ConflictDomain domain, const char *key, SFMod *mod, const char *id_label)
Claims a string key within a domain for a mod. Comparison is case-insensitive, since the keys are Win...
void report_mod_warning(SFMod *mod, const char *format,...)
Logs a warning against a mod at DEBUG_INFO, i.e. always shown.
void report_mod_error(SFMod *mod, const char *format,...)
Logs an error, appends it to the mod's error buffer and counts it.
@ CONFLICT_CAMPAIGN_FOLDER
@ CONFLICT_CAMPAIGN_AVATAR_TYPE
void log_error(const char *format,...)
void log_warning(const char *format,...)
Logs a warning at DEBUG_INFO, i.e. always shown.
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
#define SPELLFRAMEWORK_VERSION_PATCH
#define SPELLFRAMEWORK_VERSION_MAJOR
#define SPELLFRAMEWORK_VERSION_MINOR
Parse result: the definition plus which keys were actually present.
bool found_campaign_folder