diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 2fd3f634..49905e35 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -812,6 +812,17 @@ if(BUILD_TESTING) "${PSXRECOMP_ROOT}/runtime/src/netplay_bios_settle.c") target_link_libraries(lobby_chat_ring_test PRIVATE recomp_net) add_test(NAME lobby_chat_ring_test COMMAND lobby_chat_ring_test) + add_executable(netplay_bios_wire_test + tests/test_netplay_bios_wire.c + src/netplay_bios_settle.c + src/lobby_ws/rnet_ws.c + src/lobby_ws/rnet_sha1.c) + target_include_directories(netplay_bios_wire_test PRIVATE + include src src/lobby_ws ${_RNET_ROOT}/include) + target_compile_definitions(netplay_bios_wire_test PRIVATE + PSX_HAS_LOBBY_CLIENT=1 PSX_HAS_RECOMP_NET=1) + target_link_libraries(netplay_bios_wire_test PRIVATE recomp_net) + add_test(NAME netplay_bios_wire_test COMMAND netplay_bios_wire_test) endif() add_executable(rb_post_tip_filter_test tests/test_rb_post_tip_filter.c) diff --git a/runtime/tests/test_netplay_bios_wire.c b/runtime/tests/test_netplay_bios_wire.c new file mode 100644 index 00000000..8154d5c5 --- /dev/null +++ b/runtime/tests/test_netplay_bios_wire.c @@ -0,0 +1,86 @@ +/* Exercise the actual lobby parser/serializer and seat reconciliation. + * No server or sockets: link the same real dependencies as chat-ring tests. */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include +#include +#include "../src/psx_lobby_client.c" + +uint64_t psx_host_mono_ms(void) { return 1; } +static int failures; +#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%d: %s\n", __LINE__, #c); ++failures; } } while (0) + +static void wire_roundtrip(void) { + PsxLobbyMatchCaps before, after; + char wire[2048], object[1024]; + memset(&before, 0, sizeof(before)); + before.valid = 1; + before.aspect_num = 16; + before.aspect_den = 9; + before.rollback = 1; + strcpy(before.language, "it"); + strcpy(before.session_bios, "scph1001"); + before.session_bios_crc = 0xD786F0B9u; + CHECK(append_match_caps_json(wire, sizeof(wire), &before) > 0); + CHECK(json_extract_object(wire, "match_caps", object, sizeof(object))); + parse_match_caps_object(object, &after); + CHECK(after.valid && after.session_bios_crc == before.session_bios_crc); + CHECK(after.rollback == 1 && after.aspect_num == 16 && after.aspect_den == 9); + CHECK(strcmp(after.language, "it") == 0); + parse_match_caps_object("{\"session_bios\":\"scph1001\"}", &after); + CHECK(after.valid && after.session_bios_crc == 0); /* legacy host */ + parse_match_caps_object("{\"session_bios\":\"openbios\",\"session_bios_crc\":\"d786f0b9\"}", &after); + CHECK(after.session_bios_crc == 0); /* irrelevant retail CRC ignored */ + parse_match_caps_object("{\"session_bios\":\"scph1001\",\"session_bios_crc\":\"invalid\"}", &after); + CHECK(after.session_bios_crc == 0); +} + +static void local_offer_wins(void) { + char token[16], why[192]; + uint32_t crc; + memset(&g_lc, 0, sizeof(g_lc)); + strcpy(g_lc.player_id, "me"); + g_lc.is_host = 1; + g_lc.member_count = 2; + strcpy(g_lc.members[0].player_id, "me"); + strcpy(g_lc.members[1].player_id, "peer"); + for (int i = 0; i < 2; ++i) { + g_lc.members[i].bios_offer_valid = 1; + g_lc.members[i].bios_can_scph1001 = 1; + g_lc.members[i].bios_retail_crc = 0x37157331u; + } + /* The local file changed after the stale server echo, now matching peer. */ + g_lc.members[0].bios_retail_crc = 0xD786F0B9u; + g_lc.bios_offer.valid = 1; + g_lc.bios_offer.can_scph1001 = 1; + g_lc.bios_offer.retail_crc = 0x37157331u; + CHECK(psx_lobby_settle_session_bios(token, sizeof(token), &crc, why, sizeof(why)) == 0); + CHECK(strcmp(token, "scph1001") == 0 && crc == 0x37157331u && !why[0]); + g_lc.members[1].bios_retail_crc = 0xD786F0B9u; + CHECK(psx_lobby_settle_session_bios(token, sizeof(token), &crc, why, sizeof(why)) == 1); + CHECK(!token[0] && crc == 0 && strstr(why, "SCPH-1001") && strstr(why, "SCPH-5552")); + g_lc.bios_offer.can_openbios = 1; + g_lc.members[1].bios_can_openbios = 1; + CHECK(psx_lobby_settle_session_bios(token, sizeof(token), &crc, why, sizeof(why)) == 0); + CHECK(strcmp(token, "openbios") == 0 && crc == 0); +} + +static void exit_message_bounds(void) { + char long_message[512]; + memset(long_message, 'x', sizeof(long_message)); + long_message[sizeof(long_message)-1] = 0; + psx_lobby_set_last_error(long_message); + CHECK(strlen(g_lc.join.last_error) == sizeof(g_lc.join.last_error)-1); + psx_lobby_set_last_error(NULL); + CHECK(!g_lc.join.last_error[0]); +} + +int main(void) { + wire_roundtrip(); + local_offer_wins(); + exit_message_bounds(); + if (failures) return 1; + puts("netplay BIOS wire/local-offer regression: PASS"); + return 0; +}