Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
54484e6
Import vendored zip library for mod loading
Vagabond Sep 13, 2025
9677023
WIP
Vagabond Sep 14, 2025
d4928ea
Enumerate zip files in mod directories and their contents
Vagabond Sep 19, 2025
21127f2
Get background overrides working and make progress on sprites
Vagabond Sep 21, 2025
fb7cb59
Support loading sprites from mods
Vagabond Sep 21, 2025
afa945b
Support music mods
Vagabond Sep 22, 2025
ae15300
Support multiple music remixes co-existing
Vagabond Sep 22, 2025
6356348
Allow selection of the music source (originals/remixes/both)
Vagabond Sep 22, 2025
215695b
Support parsing bk/af animdata.ini and fighter header.ini from mods
Vagabond Sep 22, 2025
ecc61b3
Fix minizip build on Windows
Nopey Sep 23, 2025
775498b
Fix clang-tidy issue and a leak
Vagabond Sep 23, 2025
0dc42c6
Try to fix another clang-tidy issue in miniz
Vagabond Sep 23, 2025
77b8ebd
Handle "common" sprite loads and fix some naming to match wiki
Vagabond Sep 23, 2025
2c80b9a
Add fallback "common" loads for AF/BK animdata
Vagabond Sep 23, 2025
b8dbd2d
Switch to filename, not id lookups, fix sprite load bug
Vagabond Sep 23, 2025
173d42b
Remove unused var
Vagabond Sep 23, 2025
d91ef7f
Cleanup some leaks
Vagabond Sep 24, 2025
0846c04
Try to fix #1317
katajakasa Oct 9, 2025
dd43f7e
Improvements to mod loading
Vagabond Oct 6, 2025
a04d4cf
Sort mods by load order and dedup by version
Vagabond Oct 6, 2025
ddc5fc3
WIP on tournament support
Vagabond Oct 9, 2025
255de71
Fixes and clang-tidy issues
Vagabond Oct 10, 2025
f6e07d7
More tidy
Vagabond Oct 10, 2025
4360f52
Fix another leak
Vagabond Oct 10, 2025
3c31c48
Switch to omf_strncasecmp
Vagabond Oct 10, 2025
bea820e
Switch to omf_strcasecmp
Vagabond Oct 11, 2025
3420969
Constrain pilot photo select to PLAYERS.PIC
Vagabond Oct 11, 2025
cbffbf8
Support replacing PLAYER.pic portraits via mods
Vagabond Oct 12, 2025
64636ec
Allow larger sd_sprites by making len field u32
Vagabond Oct 14, 2025
a98e5c3
Support portrait width/height in ini files, load HAR palettes
Vagabond Oct 15, 2025
239b567
Allow for integer multiple sizes of assets (2x 4x etc) and pick best
Vagabond Jan 2, 2026
2ab7473
Better resolution rounding
Vagabond Feb 12, 2026
398968e
Fix the leaks
Vagabond Feb 13, 2026
5544ae6
fmt
Vagabond Feb 13, 2026
abd999f
Death to strcmp, all hail str_equal_c
Vagabond Feb 13, 2026
892b460
Tidy
Vagabond Feb 13, 2026
89b6e6e
Reorg headers, tidy and fmt
Vagabond Feb 13, 2026
5439c51
Prevent most mod assets loading during netplay
Vagabond Feb 13, 2026
45cb6a9
Some review comments
Vagabond Feb 13, 2026
5c31ddb
More review comments
Vagabond Feb 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -411,10 +411,10 @@ endif()

# Make sure libraries are linked
target_link_libraries(openomf PRIVATE ${CORELIBS})
target_link_libraries(openomf PRIVATE openomf::argtable openomf::SDL2main openomf::epoxy)
target_link_libraries(openomf PRIVATE openomf::argtable openomf::zip openomf::SDL2main openomf::epoxy)
foreach(TARGET ${TOOL_TARGET_NAMES})
target_link_libraries(${TARGET} PRIVATE ${CORELIBS})
target_link_libraries(${TARGET} PRIVATE openomf::argtable openomf::SDL2main openomf::epoxy)
target_link_libraries(${TARGET} PRIVATE openomf::argtable openomf::zip openomf::SDL2main openomf::epoxy)
endforeach()

# Testing stuff
Expand Down Expand Up @@ -462,7 +462,8 @@ set(DOC_FILES
README.md
LICENSE
resources/gamecontrollerdb/LICENSE.gamecontrollerdb
src/vendored/LICENSE.argtable3
src/vendored/argtable/LICENSE.argtable3
src/vendored/zip/LICENSE.zip
)

# Installation
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ for more information.

OpenOMF contains pieces of other software, which have licenses of their own:
- resources/gamecontrollerdb.txt is under [zlib license](resources/gamecontrollerdb/LICENSE.gamecontrollerdb)
- src/vendored/argtable3 is under multiple licenses, please see [LICENSE](src/vendored/LICENSE.argtable3)
- src/vendored/argtable3 is under multiple licenses, please see [LICENSE](src/vendored/argtable/LICENSE.argtable3)
- src/vendored/zip and miniz is under the MIT license, please see [LICENSE](src/vendored/zip/LICENSE.zip)

And finally, the icon resources in resources/icons fall under CC-BY 4.0 license; please see
[LICENSE](resources/icons/LICENSE) and https://creativecommons.org/licenses/by/4.0/ for details.
Expand Down
6 changes: 5 additions & 1 deletion cmake-scripts/Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ endif()

# argtable
add_library(openomf::argtable INTERFACE IMPORTED)
target_include_directories(openomf::argtable INTERFACE "${CMAKE_SOURCE_DIR}/src/vendored")
target_include_directories(openomf::argtable INTERFACE "${CMAKE_SOURCE_DIR}/src/vendored/argtable")

# zip
add_library(openomf::zip INTERFACE IMPORTED)
target_include_directories(openomf::zip INTERFACE "${CMAKE_SOURCE_DIR}/src/vendored/zip")

# enet
add_library(openomf::enet INTERFACE IMPORTED)
Expand Down
50 changes: 30 additions & 20 deletions src/audio/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
#include "audio/backends/audio_backend.h"
#include "audio/sources/opus_source.h"
#include "audio/sources/psm_source.h"
#include "game/utils/settings.h"
#include "resources/modmanager.h"
#include "resources/resource_files.h"
#include "resources/sounds_loader.h"
#include "utils/c_array_util.h"
#include "utils/log.h"
#include "utils/path.h"
#include "utils/random.h"

#include <assert.h>

Expand Down Expand Up @@ -212,31 +215,22 @@ static void load_xmp_music(const char *src) {
}
}

static void load_opus_music(const char *src) {
static void load_opus_music(unsigned char *buf, size_t len) {
music_source music;
unsigned channels;
unsigned sample_rate;
current_backend.get_info(current_backend.ctx, &sample_rate, &channels, NULL);
if(opus_load(&music, channels, sample_rate, src)) {
if(opus_load_memory(&music, channels, sample_rate, buf, len)) {
current_backend.play_music(current_backend.ctx, &music);
}
}

static path get_music_path(music_file_type *type, unsigned int resource_id) {
assert(is_music(resource_id));
path original_music, new_music;
original_music = new_music = get_resource_filename(get_resource_file(resource_id));
path_set_ext(&new_music, ".ogg");

if(path_exists(&new_music)) {
log_debug("Found alternate music file %s", path_c(&new_music));
*type = MUSIC_FILE_TYPE_OGG;
return new_music;
} else {
log_debug("Found original music file %s", path_c(&original_music));
*type = MUSIC_FILE_TYPE_PSM;
return original_music;
}
path original_music;
original_music = get_resource_filename(get_resource_file(resource_id));
*type = MUSIC_FILE_TYPE_PSM;
return original_music;
}

void audio_play_music(resource_id id) {
Expand All @@ -245,12 +239,28 @@ void audio_play_music(resource_id id) {
const path music = get_music_path(&file_type, id);

switch(file_type) {
case MUSIC_FILE_TYPE_PSM:
load_xmp_music(path_c(&music));
break;
case MUSIC_FILE_TYPE_OGG:
load_opus_music(path_c(&music));
case MUSIC_FILE_TYPE_PSM: {
str fn;
unsigned char *buf;
size_t len;
// check the modmanager here for a music mod
path_stem(&music, &fn);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

str_free(fn)

int music_count = modmanager_count_music(&fn);
int rand = rand_int(music_count + 1);
int music_type = settings_get()->sound.music_type;
if(music_count > 0 && music_type == 1 && rand == 0) {
// remixes only, never select an original track (0)
rand = rand_int(music_count) + 1;
}
if(music_type != 0 && rand && modmanager_get_music(&fn, rand - 1, &buf, &len)) {
log_debug("found replacement music file for %s.PSM", str_c(&fn));
load_opus_music(buf, len);
} else {
log_debug("Found original music file %s", path_c(&music));
load_xmp_music(path_c(&music));
}
break;
}
default:
log_error("Unable to load music file %s due to unsupported audio format", path_c(&music));
break;
Expand Down
25 changes: 25 additions & 0 deletions src/audio/sources/opus_source.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,31 @@ bool opus_load(music_source *src, int channels, int sample_rate, const char *fil
return false;
}

bool opus_load_memory(music_source *src, int channels, int sample_rate, const unsigned char *buffer, size_t buflen) {
opus_source *context = omf_calloc(1, sizeof(opus_source));
rb_create(&context->buffer, RING_SIZE);
if((context->handle = op_open_memory(buffer, buflen, NULL)) == NULL) {
log_error("Failed to open opus file");
goto exit_0;
}
if(SDL_BuildAudioCVT(&context->cvt, AUDIO_S16, 2, 48000, AUDIO_S16, channels, sample_rate) < 0) {
log_error("Audio converter creation failed: %s", SDL_GetError());
goto exit_1;
}

src->context = context;
src->set_volume = opus_set_volume;
src->render = opus_render;
src->close = opus_close;
return true;

exit_1:
op_free(context->handle);
exit_0:
omf_free(context);
return false;
}

#else

bool opus_load(music_source *src, int channels, int sample_rate, const char *file) {
Expand Down
1 change: 1 addition & 0 deletions src/audio/sources/opus_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
#include "audio/sources/music_source.h"

bool opus_load(music_source *src, int channels, int sample_rate, const char *file);
bool opus_load_memory(music_source *src, int channels, int sample_rate, const unsigned char *buffer, size_t buflen);

#endif // OPUS_SOURCE_H
7 changes: 7 additions & 0 deletions src/engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "game/game_state.h"
#include "game/utils/settings.h"
#include "resources/languages.h"
#include "resources/modmanager.h"
#include "resources/resource_files.h"
#include "resources/resource_paths.h"
#include "resources/sounds_loader.h"
Expand Down Expand Up @@ -78,6 +79,9 @@ int engine_init(engine_init_flags *init_flags) {
if(!console_init()) {
goto exit_6;
}
if(!modmanager_init()) {
goto exit_7;
}
vga_state_init();

// Return successfully
Expand All @@ -86,6 +90,8 @@ int engine_init(engine_init_flags *init_flags) {
return 0;

// If something failed, close in correct order
exit_7:
console_close();
exit_6:
altpals_close();
exit_5:
Expand Down Expand Up @@ -409,5 +415,6 @@ void engine_close(void) {
audio_close();
video_close();
vga_state_close();
modmanager_shutdown();
log_info("Engine deinit successful.");
}
33 changes: 21 additions & 12 deletions src/formats/chr.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "formats/tournament.h"
#include "game/scenes/mechlab/har_economy.h"
#include "game/scenes/mechlab/lab_menu_customize.h"
#include "resources/modmanager.h"
#include "resources/resource_files.h"
#include "resources/trnmanager.h"
#include "utils/allocator.h"
Expand Down Expand Up @@ -94,17 +95,6 @@ int sd_chr_load(sd_chr_file *chr, const path *filename) {
log_error("failed to load tournament image from %s", path_c(&image_path));
}

// Load PIC file and make a surface
const path players_path = get_resource_filename("PLAYERS.PIC");
sd_pic_create(&players);
const int ret = sd_pic_load(&players, &players_path);
if(ret == SD_SUCCESS) {
// Load player gender from PLAYERS.PIC
const sd_pic_photo *photo = sd_pic_get(&players, chr->pilot.photo_id);
chr->pilot.sex = photo->sex;
sd_pic_free(&players);
}

if(*chr->pilot.trn_name != '\0') {
trn_loaded = trn_load(&trn, chr->pilot.trn_name) == 0;
}
Expand Down Expand Up @@ -139,7 +129,12 @@ int sd_chr_load(sd_chr_file *chr, const path *filename) {
if(trn_loaded) {
memcpy(&chr->enemies[i]->pilot.palette, &pic.photos[trn.enemies[i]->photo_id]->pal, sizeof(vga_palette));
chr->enemies[i]->pilot.photo = omf_calloc(1, sizeof(sd_sprite));
sd_sprite_copy(chr->enemies[i]->pilot.photo, pic.photos[trn.enemies[i]->photo_id]->sprite);
if(trn.enemies[i]->photo) {
log_info("using pilot photo %d from tournament", i);
sd_sprite_copy(chr->enemies[i]->pilot.photo, trn.enemies[i]->photo);
} else {
sd_sprite_copy(chr->enemies[i]->pilot.photo, pic.photos[trn.enemies[i]->photo_id]->sprite);
}
// copy all the "pilot" fields (eg. winnings) over from the tournament file
chr->enemies[i]->pilot.trn_rank_money = trn.enemies[i]->trn_rank_money;
chr->enemies[i]->pilot.trn_winnings_mult = trn.enemies[i]->trn_winnings_mult;
Expand Down Expand Up @@ -225,6 +220,20 @@ int sd_chr_load(sd_chr_file *chr, const path *filename) {

chr->pilot.photo = chr->photo;

// Load PIC file and make a surface
const path players_path = get_resource_filename("PLAYERS.PIC");
sd_pic_create(&players);
const int ret = sd_pic_load(&players, &players_path);
if(ret == SD_SUCCESS) {
modmanager_get_player_pics(&players);
// Load player gender from PLAYERS.PIC
const sd_pic_photo *photo = sd_pic_get(&players, chr->pilot.photo_id);
chr->pilot.sex = photo->sex;
chr->pilot.photo->render_width = photo->sprite->render_width;
chr->pilot.photo->render_height = photo->sprite->render_height;
sd_pic_free(&players);
}

// Load colors from other files
sd_pilot_set_player_color(&chr->pilot, PRIMARY, chr->pilot.color_1);
sd_pilot_set_player_color(&chr->pilot, SECONDARY, chr->pilot.color_2);
Expand Down
2 changes: 2 additions & 0 deletions src/formats/pilot.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "formats/error.h"
#include "formats/pic.h"
#include "formats/pilot.h"
#include "resources/modmanager.h"
#include "resources/resource_files.h"
#include "utils/allocator.h"
#include "utils/c_string_util.h"
Expand Down Expand Up @@ -350,6 +351,7 @@ void sd_pilot_set_player_color(sd_pilot *pilot, player_color index, uint8_t colo
sd_pic_create(&players);
int ret = sd_pic_load(&players, &players_filename);
if(ret == SD_SUCCESS) {
modmanager_get_player_pics(&players);
// Load player palette from PLAYERS.PIC
const sd_pic_photo *photo = sd_pic_get(&players, pilot->photo_id);
if(photo) {
Expand Down
14 changes: 11 additions & 3 deletions src/formats/sprite.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ int sd_sprite_copy(sd_sprite *dst, const sd_sprite *src) {
dst->missing = src->missing;
dst->width = src->width;
dst->height = src->height;
dst->render_width = src->render_width;
dst->render_height = src->render_height;

dst->len = src->len;

if(src->data != NULL) {
Expand Down Expand Up @@ -57,6 +60,8 @@ int sd_sprite_load(sd_reader *r, sd_sprite *sprite) {
sprite->pos_y = sd_read_word(r);
sprite->width = sd_read_uword(r);
sprite->height = sd_read_uword(r);
sprite->render_height = sprite->height;
sprite->render_width = sprite->width;
sprite->index = sd_read_ubyte(r);
sprite->missing = sd_read_ubyte(r);

Expand Down Expand Up @@ -202,7 +207,7 @@ int sd_sprite_rgba_encode(sd_sprite *dst, const sd_rgba_image *src, const vga_pa
int sd_sprite_rgba_decode(sd_rgba_image *dst, const sd_sprite *src, const vga_palette *pal) {
uint16_t x = 0;
uint16_t y = 0;
int i = 0;
uint32_t i = 0;
uint16_t c = 0;
uint16_t data = 0;
char op = 0;
Expand Down Expand Up @@ -269,7 +274,7 @@ int sd_sprite_rgba_decode(sd_rgba_image *dst, const sd_sprite *src, const vga_pa
int sd_sprite_vga_decode(sd_vga_image *dst, const sd_sprite *src) {
uint16_t x = 0;
uint16_t y = 0;
int i = 0;
uint32_t i = 0;
uint16_t c = 0;
uint16_t data = 0;
char op = 0;
Expand Down Expand Up @@ -311,7 +316,7 @@ int sd_sprite_vga_decode(sd_vga_image *dst, const sd_sprite *src) {
y = data;
break;
case 1:
while(data > 0) {
while(data > 0 && i < src->len) {
uint8_t b = src->data[i];
unsigned int pos = ((y * src->width) + x);
// if we're about to overflow the `dst` buffer, don't.
Expand Down Expand Up @@ -422,6 +427,9 @@ int sd_sprite_vga_encode(sd_sprite *dst, const sd_vga_image *src) {
// Copy data
dst->width = src->w;
dst->height = src->h;
dst->render_width = src->w;
dst->render_height = src->h;

dst->len = i;
dst->missing = 0;
dst->data = omf_calloc(i, 1);
Expand Down
6 changes: 4 additions & 2 deletions src/formats/sprite.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ typedef struct {
uint8_t missing; ///< Is sprite data missing? If this is 1, then data points to the data of another sprite.
uint16_t width; ///< Pixel width of the sprite
uint16_t height; ///< Pixel height of the sprite
uint16_t len; ///< Byte length of the packed sprite data
char *data; ///< Packed sprite data
uint16_t render_width;
uint16_t render_height;
uint32_t len; ///< Byte length of the packed sprite data
char *data; ///< Packed sprite data
} sd_sprite;

/*! \brief Initialize sprite structure
Expand Down
2 changes: 1 addition & 1 deletion src/formats/tournament.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ int sd_tournament_set_pic_name(sd_tournament_file *trn, const char *pic_name) {
return SD_SUCCESS;
}

static void parse_tournament_description(sd_tournament_locale *locale) {
void parse_tournament_description(sd_tournament_locale *locale) {
int width = 320, center = 0, vmove = 0, size = -1, color = -1;
const char *desc = locale->description;
const char *end = desc;
Expand Down
9 changes: 9 additions & 0 deletions src/formats/tournament.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,13 @@ int sd_tournament_set_pic_name(sd_tournament_file *trn, const char *pic_name);
*/
void sd_tournament_free(sd_tournament_file *trn);

/*! \brief Parse the tournament description
*
* Parses the tournament description tags into the locale struct
*
* \param trn TRN locale pointer.
*/

void parse_tournament_description(sd_tournament_locale *locale);

#endif // SD_TOURNAMENT_H
Loading