server console with commands

This commit is contained in:
Mike 2024-12-10 02:31:39 -06:00
parent f85b8419af
commit 00a0c30a46
18 changed files with 191 additions and 42 deletions

2
.gitignore vendored
View file

@ -91,3 +91,5 @@ todo-old.txt
tools/ido5.3_compiler/usr/lib/libc.so.1
/.vs
buildInfo.txt

View file

@ -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

View file

@ -174,7 +174,7 @@ def main():
if mio0 == "@sound":
rom = roms[lang]
args = [
"python3",
"py",
"tools/disassemble_sound.py",
"baserom." + lang + ".z64",
]

View file

@ -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 <type>", argv[++i], &gCLIOpts.netSystemType);
} else if (!strcmp(argv[i], "--maxplayers") && (i + 1) < argc) {
arg_uint("--maxplayers <number>", argv[++i], &gCLIOpts.maxPlayers);
} else if (!strcmp(argv[i], "--server") && (i + 1) < argc) {
gCLIOpts.network = NT_SERVER;
arg_uint("--server <port>", 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")) {

View file

@ -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;
};

View file

@ -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;

View file

@ -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");
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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();
}

View file

@ -246,14 +246,19 @@ inline static void buffer_audio(void) {
audio_api->play((u8 *)audioBuffer, 2 * numAudioSamples * 4);
}
#include <pthread.h>
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;
}

View file

@ -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:]

View file

@ -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'):