diff --git a/.gitignore b/.gitignore index 6a1fdac0e..31a4244ed 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,5 @@ todo-old.txt tools/ido5.3_compiler/usr/lib/libc.so.1 /.vs + +buildInfo.txt diff --git a/Makefile b/Makefile index 7e942ece2..4d851ef32 100644 --- a/Makefile +++ b/Makefile @@ -381,6 +381,11 @@ endif ifeq ($(HEADLESS),1) $(info Compiling headless) + + DEFINES += RAPI_DUMMY=1 + DEFINES += WAPI_DUMMY=1 + DEFINES += NO_AUDIO=1 + RENDER_API := DUMMY WINDOW_API := DUMMY AUDIO_API := DUMMY @@ -438,7 +443,7 @@ TOOLS_DIR := tools # on tools and assets, and we use directory globs further down # in the makefile that we want should cover assets.) -PYTHON := python3 +PYTHON := py ifeq ($(filter clean distclean print-%,$(MAKECMDGOALS)),) @@ -478,6 +483,10 @@ BUILD_DIR_BASE := build # BUILD_DIR is the location where all build artifacts are placed BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION)_pc +ifeq ($(HEADLESS),1) +BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION)_headless +endif + ifeq ($(WINDOWS_BUILD),1) EXE := $(BUILD_DIR)/sm64coopdx.exe else # Linux builds/binary namer diff --git a/extract_assets.py b/extract_assets.py index 160fb7c66..6ecc3dc34 100755 --- a/extract_assets.py +++ b/extract_assets.py @@ -174,7 +174,7 @@ def main(): if mio0 == "@sound": rom = roms[lang] args = [ - "python3", + "py", "tools/disassemble_sound.py", "baserom." + lang + ".z64", ] diff --git a/src/pc/cliopts.c b/src/pc/cliopts.c index 29aaf91cf..26a5ef81c 100644 --- a/src/pc/cliopts.c +++ b/src/pc/cliopts.c @@ -68,7 +68,11 @@ bool parse_cli_opts(int argc, char* argv[]) { gCLIOpts.fullscreen = 2; } else if (!strcmp(argv[i], "--skip-intro")) { gCLIOpts.skipIntro = true; - } else if (!strcmp(argv[i], "--server") && (i + 1) < argc) { + } else if (!strcmp(argv[i], "--network") && (i + 1) < argc) { + arg_uint("--network ", argv[++i], &gCLIOpts.netSystemType); + } else if (!strcmp(argv[i], "--maxplayers") && (i + 1) < argc) { + arg_uint("--maxplayers ", argv[++i], &gCLIOpts.maxPlayers); + } else if (!strcmp(argv[i], "--server") && (i + 1) < argc) { gCLIOpts.network = NT_SERVER; arg_uint("--server ", argv[++i], &gCLIOpts.networkPort); } else if (!strcmp(argv[i], "--client") && (((i + 1) < argc) || (i + 2) < argc)) { @@ -81,6 +85,12 @@ bool parse_cli_opts(int argc, char* argv[]) { } } else if (!strcmp(argv[i], "--playername") && (i + 1) < argc) { arg_string("--playername", argv[++i], gCLIOpts.playerName, MAX_CONFIG_STRING); + } else if (!strcmp(argv[i],"--servername") && (i + 1) < argc) { + arg_string("--servername", argv[++i], gCLIOpts.coopnetName, MAX_CONFIG_STRING); + } else if (!strcmp(argv[i],"--serverdesc") && (i + 1) < argc) { + arg_string("--serverdesc", argv[++i], gCLIOpts.coopnetDesc, 1024); + } else if (!strcmp(argv[i],"--serverpass") && (i + 1) < argc) { + arg_string("--serverpass", argv[++i], gCLIOpts.coopnetPass, 64); } else if (!strcmp(argv[i], "--skip-update-check")) { gCLIOpts.skipUpdateCheck = true; } else if (!strcmp(argv[i], "--help")) { diff --git a/src/pc/cliopts.h b/src/pc/cliopts.h index 98b1abfbe..ff1fe06e7 100644 --- a/src/pc/cliopts.h +++ b/src/pc/cliopts.h @@ -22,9 +22,14 @@ struct CLIOptions { unsigned int fullscreen; bool skipIntro; enum NetworkType network; + unsigned int netSystemType; unsigned int networkPort; + unsigned int maxPlayers; char joinIp[IP_MAX_LEN]; char playerName[MAX_CONFIG_STRING]; + char coopnetName[MAX_CONFIG_STRING]; + char coopnetDesc[1024]; + char coopnetPass[64]; bool hideLoadingScreen; bool skipUpdateCheck; }; diff --git a/src/pc/configfile.h b/src/pc/configfile.h index aa080e1e7..996d8bede 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -17,6 +17,8 @@ #define DEFAULT_COOPNET_IP "net.coop64.us" #define DEFAULT_COOPNET_PORT 34197 +#define DEVELOPMENT + typedef struct { unsigned int x, y, w, h; bool vsync; diff --git a/src/pc/discord/discord_activity.c b/src/pc/discord/discord_activity.c index 304044471..ca8e33863 100644 --- a/src/pc/discord/discord_activity.c +++ b/src/pc/discord/discord_activity.c @@ -135,7 +135,7 @@ void discord_activity_update(void) { LOG_INFO("no update_activity"); return; } - + app.activities->update_activity(app.activities, &sCurActivity, NULL, on_activity_update_callback); LOG_INFO("set activity"); } diff --git a/src/pc/djui/djui_chat_message.c b/src/pc/djui/djui_chat_message.c index 84ebe1272..ed99ceff5 100644 --- a/src/pc/djui/djui_chat_message.c +++ b/src/pc/djui/djui_chat_message.c @@ -66,10 +66,19 @@ void djui_chat_message_create_from(u8 globalIndex, const char* message) { snprintf(chatMsg, 256, "%s%s\\#dcdcdc\\: %s", playerColorString, (np != NULL) ? np->name : "Player", message); play_sound((globalIndex == gNetworkPlayerLocal->globalIndex) ? SOUND_MENU_MESSAGE_DISAPPEAR : SOUND_MENU_MESSAGE_APPEAR, gGlobalSoundSource); - djui_chat_message_create(chatMsg); + + #if defined(RAPI_DUMMY) || defined(WAPI_DUMMY) + if (gCLIOpts.console) return; + #endif + + djui_chat_message_create(chatMsg); } void djui_chat_message_create(const char* message) { + #if defined(RAPI_DUMMY) || defined(WAPI_DUMMY) + if (gCLIOpts.console) printf("%s\n",message); + #endif + if (gDjuiChatBox == NULL || gDjuiChatBox->chatFlow == NULL) { return; } struct DjuiChatMessage* chatMessage = calloc(1, sizeof(struct DjuiChatMessage)); struct DjuiBase* base = &chatMessage->base; diff --git a/src/pc/lua/utils/smlua_audio_utils.c b/src/pc/lua/utils/smlua_audio_utils.c index 4cde88afc..23574202c 100644 --- a/src/pc/lua/utils/smlua_audio_utils.c +++ b/src/pc/lua/utils/smlua_audio_utils.c @@ -168,11 +168,13 @@ static struct DynamicPool *sModAudioPool; static void smlua_audio_custom_init(void) { sModAudioPool = dynamic_pool_init(); - - ma_result result = ma_engine_init(NULL, &sModAudioEngine); + + #ifndef NO_AUDIO + ma_result result = ma_engine_init(NULL, &sModAudioEngine); if (result != MA_SUCCESS) { LOG_ERROR("failed to init Miniaudio: %d", result); } + #endif } static struct ModAudio* find_mod_audio(struct ModFile* file) { @@ -187,6 +189,9 @@ static struct ModAudio* find_mod_audio(struct ModFile* file) { } static bool audio_sanity_check(struct ModAudio* audio, bool isStream, const char* action) { + #ifdef NO_AUDIO + return false; + #endif if (!audio || !audio->loaded) { LOG_LUA_LINE("Tried to %s unloaded audio %s", action, audio ? (audio->isStream ? "stream" : "sample") : "(NULL)"); return false; @@ -265,6 +270,7 @@ struct ModAudio* audio_load_internal(const char* filename, bool isStream) { audio->file = modFile; // load audio + #ifndef NO_AUDIO FILE *f = f_open_r(modFile->cachedPath); if (!f) { LOG_ERROR("failed to load audio file '%s': file not found", filename); @@ -311,11 +317,12 @@ struct ModAudio* audio_load_internal(const char* filename, bool isStream) { LOG_ERROR("failed to load audio file '%s': %d", filename, result); return NULL; } - audio->buffer = buffer; audio->bufferSize = size; audio->isStream = isStream; audio->loaded = true; + #endif + return audio; } diff --git a/src/pc/network/coopnet/coopnet.c b/src/pc/network/coopnet/coopnet.c index a752ceafc..90fa84013 100644 --- a/src/pc/network/coopnet/coopnet.c +++ b/src/pc/network/coopnet/coopnet.c @@ -185,7 +185,18 @@ static void coopnet_populate_description(void) { snprintf(buffer, bufferLength, "%s", version); buffer += versionLength; bufferLength -= versionLength; - + + //this will probably result in a buffer overflow + int customDescLen = strlen(gCLIOpts.coopnetDesc); + if (customDescLen > 0) { + snprintf(buffer, bufferLength, "\n\n"); + buffer += 2; + bufferLength -= 2; + snprintf(buffer, bufferLength, "%s", gCLIOpts.coopnetDesc); + buffer += customDescLen; + bufferLength -= customDescLen; + } + // get mod strings if (gActiveMods.entryCount <= 0) { return; } char* strings[gActiveMods.entryCount]; @@ -204,6 +215,10 @@ static void coopnet_populate_description(void) { str_seperator_concat(buffer, bufferLength, strings, gActiveMods.entryCount, "\\#dcdcdc\\\n"); } +const char* coopnet_ServerName() { + return strlen(gCLIOpts.coopnetName) > 0 ? gCLIOpts.coopnetName : configPlayerName; +} + void ns_coopnet_update(void) { if (!coopnet_is_connected()) { return; } @@ -215,12 +230,12 @@ void ns_coopnet_update(void) { if (sReconnecting) { LOG_INFO("Update lobby"); coopnet_populate_description(); - coopnet_lobby_update(sLocalLobbyId, GAME_NAME, get_version_online(), configPlayerName, mode, sCoopNetDescription); + coopnet_lobby_update(sLocalLobbyId, GAME_NAME, get_version_online(), coopnet_ServerName(), mode, sCoopNetDescription); } else { LOG_INFO("Create lobby"); snprintf(gCoopNetPassword, 64, "%s", configPassword); coopnet_populate_description(); - coopnet_lobby_create(GAME_NAME, get_version_online(), configPlayerName, mode, (uint16_t)configAmountofPlayers, gCoopNetPassword, sCoopNetDescription); + coopnet_lobby_create(GAME_NAME, get_version_online(), coopnet_ServerName(), mode, (uint16_t)configAmountofPlayers, gCoopNetPassword, sCoopNetDescription); } } else if (sNetworkType == NT_CLIENT) { LOG_INFO("Join lobby"); @@ -281,13 +296,13 @@ static CoopNetRc coopnet_initialize(void) { gCoopNetCallbacks.OnError = coopnet_on_error; gCoopNetCallbacks.OnPeerDisconnected = coopnet_on_peer_disconnected; gCoopNetCallbacks.OnLoadBalance = coopnet_on_load_balance; - + if (coopnet_is_connected()) { return COOPNET_OK; } char* endptr = NULL; uint64_t destId = strtoull(configDestId, &endptr, 10); - CoopNetRc rc = coopnet_begin(configCoopNetIp, configCoopNetPort, configPlayerName, destId); + CoopNetRc rc = coopnet_begin(configCoopNetIp, configCoopNetPort, coopnet_ServerName(), destId); if (rc == COOPNET_FAILED) { djui_popup_create(DLANG(NOTIF, COOPNET_CONNECTION_FAILED), 2); } diff --git a/src/pc/network/network.c b/src/pc/network/network.c index 105fadc93..e1f89cf55 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -162,8 +162,9 @@ bool network_init(enum NetworkType inNetworkType, bool reconnecting) { smlua_init(); dynos_behavior_hook_all_custom_behaviors(); - - network_player_connected(NPT_LOCAL, 0, configPlayerModel, &configPlayerPalette, configPlayerName, get_local_discord_id()); + + network_player_connected(NPT_LOCAL, 0, configPlayerModel, &configPlayerPalette, configPlayerName, get_local_discord_id()); + extern u8* gOverrideEeprom; gOverrideEeprom = NULL; diff --git a/src/pc/network/network_player.c b/src/pc/network/network_player.c index dd234f6ab..5b832bc41 100644 --- a/src/pc/network/network_player.c +++ b/src/pc/network/network_player.c @@ -250,6 +250,7 @@ u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex, u8 mode } else { assert(false); } + struct NetworkPlayer *np = &gNetworkPlayers[localIndex]; // ensure that a name is given @@ -336,6 +337,7 @@ u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex, u8 mode construct_player_popup(np, DLANG(NOTIF, CONNECTED), NULL); } LOG_INFO("player connected, local %d, global %d", localIndex, np->globalIndex); + printf("%s connected\n",np->name); smlua_call_event_hooks_mario_param(HOOK_ON_PLAYER_CONNECTED, &gMarioStates[localIndex]); @@ -365,6 +367,9 @@ u8 network_player_disconnected(u8 globalIndex) { struct NetworkPlayer* np = &gNetworkPlayers[i]; if (!np->connected) { continue; } if (np->globalIndex != globalIndex) { continue; } + + printf("%s disconnected\n",np->name); + if (gNetworkType == NT_SERVER) { network_send_leaving(np->globalIndex); } np->connected = false; np->currCourseNum = -1; diff --git a/src/pc/network/packets/packet_chat.c b/src/pc/network/packets/packet_chat.c index fc6e97423..80c7fe5d1 100644 --- a/src/pc/network/packets/packet_chat.c +++ b/src/pc/network/packets/packet_chat.c @@ -103,7 +103,8 @@ void network_receive_chat(struct Packet* p) { if (gNetworkSystem && gNetworkSystem->get_id_str && np->connected && strlen(np->name) > 0) { LOG_CONSOLE("[%s] %s: %s", gNetworkSystem->get_id_str(np->localIndex), np->name, remoteMessage); LOG_INFO("[%s] %s: %s", gNetworkSystem->get_id_str(np->localIndex), np->name, remoteMessage); + printf("[%s]: %s\n", np->name, remoteMessage); } else { LOG_INFO("rx chat: %s", remoteMessage); } -} +} \ No newline at end of file diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c index 1f7c41032..118f56a5e 100644 --- a/src/pc/network/packets/packet_join.c +++ b/src/pc/network/packets/packet_join.c @@ -81,6 +81,10 @@ void network_send_join(struct Packet* joinRequestPacket) { // figure out id u8 globalIndex = joinRequestPacket->localIndex; u8 connectedCount = 1; + + //setting this to zero when headless to allow for genuine maxPlayers + if (gServerSettings.headlessServer) connectedCount = 0; + if (globalIndex == UNKNOWN_LOCAL_INDEX) { for (u32 i = 1; i < MAX_PLAYERS; i++) { if (!gNetworkPlayers[i].connected) { @@ -91,15 +95,16 @@ void network_send_join(struct Packet* joinRequestPacket) { } } if (globalIndex == UNKNOWN_LOCAL_INDEX || connectedCount >= gServerSettings.maxPlayers) { + printf("%s kicked: too many players\n",sJoinRequestPlayerName); network_send_kick(0, EKT_FULL_PARTY); return; } } LOG_INFO("chose globalIndex: %d", globalIndex); - + // do connection event network_player_connected(NPT_CLIENT, globalIndex, sJoinRequestPlayerModel, &sJoinRequestPlayerPalette, sJoinRequestPlayerName, sJoinRequestDiscordId); - + fs_file_t* fp = fs_open(SAVE_FILENAME); if (fp != NULL) { fs_read(fp, eeprom, 512); diff --git a/src/pc/network/packets/packet_mod_list.c b/src/pc/network/packets/packet_mod_list.c index 2a4a7baf8..637074594 100644 --- a/src/pc/network/packets/packet_mod_list.c +++ b/src/pc/network/packets/packet_mod_list.c @@ -34,7 +34,7 @@ void network_receive_mod_list_request(UNUSED struct Packet* p) { return; } LOG_INFO("received mod list request"); - + network_send_mod_list(); } diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index f3e0809dc..ed9ab1dee 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -246,14 +246,19 @@ inline static void buffer_audio(void) { audio_api->play((u8 *)audioBuffer, 2 * numAudioSamples * 4); } +#include +pthread_mutex_t luaMutex = PTHREAD_MUTEX_INITIALIZER; + void produce_one_frame(void) { CTX_EXTENT(CTX_NETWORK, network_update); CTX_EXTENT(CTX_INTERP, patch_interpolations_before); CTX_EXTENT(CTX_GAME_LOOP, game_loop_one_iteration); - + + pthread_mutex_lock(&luaMutex); CTX_EXTENT(CTX_SMLUA, smlua_update); + pthread_mutex_unlock(&luaMutex); CTX_EXTENT(CTX_AUDIO, buffer_audio); @@ -356,6 +361,37 @@ void* main_game_init(UNUSED void* dummy) { gGameInited = true; } +//used with console only +pthread_t mainLoopThread; + +void* mainLoopFunc(UNUSED void* dummy) { + while (true) { + debug_context_reset(); + CTX_BEGIN(CTX_TOTAL); + WAPI.main_loop(produce_one_frame); +#ifdef DISCORD_SDK + discord_update(); +#endif + mumble_update(); +#ifdef DEBUG + fflush(stdout); + fflush(stderr); +#endif + CTX_END(CTX_TOTAL); + +#ifdef DEVELOPMENT + djui_ctx_display_update(); +#endif + djui_lua_profiler_update(); + } +} + +#ifdef _WIN32 +#define clrscr() system("cls") +#else +#define clrscr() system("clear") +#endif + int main(int argc, char *argv[]) { // handle terminal arguments if (!parse_cli_opts(argc, argv)) { return 0; } @@ -452,8 +488,15 @@ int main(int argc, char *argv[]) { configJoinPort = gCLIOpts.networkPort; network_init(NT_CLIENT, false); } else if (gCLIOpts.network == NT_SERVER) { - configNetworkSystem = NS_SOCKET; + //configNetworkSystem = NS_SOCKET; + configNetworkSystem = gCLIOpts.netSystemType; configHostPort = gCLIOpts.networkPort; + + if (gCLIOpts.maxPlayers > 0) + configAmountofPlayers = gCLIOpts.maxPlayers; + + if (strlen(gCLIOpts.coopnetPass) > 0) + snprintf(configPassword,64,"%s",gCLIOpts.coopnetPass); // horrible, hacky fix for mods that access marioObj straight away // best fix: host with the standard main menu method @@ -467,25 +510,60 @@ int main(int argc, char *argv[]) { } // main loop - while (true) { - debug_context_reset(); - CTX_BEGIN(CTX_TOTAL); - WAPI.main_loop(produce_one_frame); -#ifdef DISCORD_SDK - discord_update(); -#endif - mumble_update(); -#ifdef DEBUG - fflush(stdout); - fflush(stderr); -#endif - CTX_END(CTX_TOTAL); - -#ifdef DEVELOPMENT - djui_ctx_display_update(); -#endif - djui_lua_profiler_update(); - } + if (gCLIOpts.console && gCLIOpts.network == NT_SERVER) { + pthread_create(&mainLoopThread, NULL, mainLoopFunc, NULL); + char input[256]; + while (true) { + //read console input + fgets(input,256,stdin); + input[strcspn(input, "\n")] = '\0'; + + if (strlen(input) > 1) { + if (!strcmp(input,"stop")) { + printf("Server shutting down..."); + network_shutdown(true,true,false,false); + break; + } + /*else if (!strcmp(input,"restart")) { + printf("Restarting..."); + network_reset_reconnect_and_rehost(); + network_shutdown(false, false, false, true); + static struct Object sHackyObject = { 0 }; + gMarioStates[0].marioObj = &sHackyObject; + network_init(NT_SERVER,true); + }*/ + else if (!memcmp(input,"clear",5)) { + clrscr(); + continue; + } + else if (!memcmp(input,"say ",4) && strlen(input) > 4) { + network_send_chat(&input[4], gNetworkPlayerLocal->globalIndex); + continue; + } + else if (!memcmp(input,"luaf ",5) && strlen(input) > 5) { + pthread_mutex_lock(&luaMutex); + smlua_exec_file(&input[5]); + pthread_mutex_unlock(&luaMutex); + continue; + } + else if (!memcmp(input,"lua ",4) && strlen(input) > 4) { + pthread_mutex_lock(&luaMutex); + smlua_exec_str(&input[4]); + pthread_mutex_unlock(&luaMutex); + continue; + } + + //try chat commands + pthread_mutex_lock(&luaMutex); + extern bool exec_chat_command(char* command); + exec_chat_command(input); + pthread_mutex_unlock(&luaMutex); + } + } + } + else { + mainLoopFunc(NULL); + } return 0; } diff --git a/tools/asm_processor/build.py b/tools/asm_processor/build.py index 228dcb030..e221bac9e 100644 --- a/tools/asm_processor/build.py +++ b/tools/asm_processor/build.py @@ -6,7 +6,7 @@ import subprocess import tempfile dir_path = os.path.dirname(os.path.realpath(__file__)) -asm_processor = ['python3', os.path.join(dir_path, "asm-processor.py")] +asm_processor = ['py', os.path.join(dir_path, "asm-processor.py")] prelude = os.path.join(dir_path, "prelude.inc") all_args = sys.argv[1:] diff --git a/tools/assemble_sound.py b/tools/assemble_sound.py index f0918bb82..efa3f7885 100755 --- a/tools/assemble_sound.py +++ b/tools/assemble_sound.py @@ -754,7 +754,7 @@ def serialize_seqfile( out_offsets_filename = out_filename.replace('sound_data.tbl', 'samples_offsets.inc.c') with open(out_offsets_filename, "w") as f: for fname in asset_offsets: - macro_name = 'SAMPLE_' + fname.split('/samples/')[-1].replace('/', '_').replace('.', '_').replace('-', '_') + macro_name = 'SAMPLE_' + fname.split('/samples/')[-1].replace('/', '_').replace('.', '_').replace('-', '_').replace("\\","_") f.write(f'#define {macro_name} {hex(asset_offsets[fname] + data_start)} // {fname}\n') if out_filename.endswith('sequences.bin'):