diff --git a/cmake/targets/gamecube.cmake b/cmake/targets/gamecube.cmake index f8234be9..5aa608b2 100644 --- a/cmake/targets/gamecube.cmake +++ b/cmake/targets/gamecube.cmake @@ -6,7 +6,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC # Link libraries target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE - # bba + bba ) # ISO post-build: produce NTSC-J, NTSC-U and PAL disc images diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 4561e511..7450852a 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -12,20 +12,15 @@ #include "locale/localemanager.h" #include "display/display.h" #include "scene/scene.h" -#include "entity/entitymanager.h" -#include "entity/component/display/entityposition.h" -#include "entity/component/display/entityrenderable.h" -#include "entity/component/physics/entityphysics.h" -#include "display/color.h" #include "asset/asset.h" #include "ui/ui.h" #include "assert/assert.h" #include "network/network.h" -#include "network/http/networkhttprequest.h" #include "system/system.h" #include "console/console.h" #include "save/save.h" #include "save/savesettings.h" +#include "log/log.h" engine_t ENGINE; @@ -51,85 +46,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(networkInit()); errorChain(sceneInit()); - // Test: three colored cubes falling onto each other and a static floor, - // viewed by a static camera. - ENGINE.testSceneId = sceneCreate(); - sceneSetActive(ENGINE.testSceneId); - entitymanager_t *testEntities = sceneGetEntities(ENGINE.testSceneId); - - entityid_t testCamera = entityManagerAdd(testEntities); - componentid_t testCameraPosition = entityAddComponent( - testEntities, testCamera, COMPONENT_TYPE_POSITION - ); - entityAddComponent(testEntities, testCamera, COMPONENT_TYPE_CAMERA); - entityPositionLookAt( - testEntities, testCamera, testCameraPosition, - (vec3){ 4.0f, 3.0f, 4.0f }, - (vec3){ 0.0f, 0.0f, 0.0f }, - (vec3){ 0.0f, 1.0f, 0.0f } - ); - - // Static floor: a wide, thin box. Its render scale matches its physics - // half-extents exactly, since CUBE_MESH_SIMPLE and PHYSICS_SHAPE_CUBE are - // both centered on the entity's position. - entityid_t floorEntity = entityManagerAdd(testEntities); - componentid_t floorPosition = entityAddComponent( - testEntities, floorEntity, COMPONENT_TYPE_POSITION - ); - entityPositionSetLocalPosition( - testEntities, floorEntity, floorPosition, (vec3){ 0.0f, -1.0f, 0.0f } - ); - entityPositionSetLocalScale( - testEntities, floorEntity, floorPosition, (vec3){ 10.0f, 1.0f, 10.0f } - ); - componentid_t floorPhysics = entityAddComponent( - testEntities, floorEntity, COMPONENT_TYPE_PHYSICS - ); - entityPhysicsSetBodyType( - testEntities, floorEntity, floorPhysics, PHYSICS_BODY_STATIC - ); - entityPhysicsSetShape(testEntities, floorEntity, floorPhysics, (physicsshape_t){ - .type = PHYSICS_SHAPE_CUBE, - .data.cube.halfExtents = { 5.0f, 0.5f, 5.0f } - }); - componentid_t floorRenderable = entityAddComponent( - testEntities, floorEntity, COMPONENT_TYPE_RENDERABLE - ); - entityRenderableSetColor(testEntities, floorEntity, floorRenderable, COLOR_GRAY); - - // Three dynamic cubes, staggered above the floor with slight offsets so - // they tumble and land on each other instead of falling in perfect sync. - vec3 cubeStartPositions[3] = { - { -0.3f, 1.5f, 0.1f }, - { 0.1f, 3.0f, -0.2f }, - { -0.1f, 4.5f, 0.2f }, - }; - color_t cubeColors[3] = { COLOR_RED, COLOR_GREEN, COLOR_BLUE }; - - for(uint8_t i = 0; i < 3; i++) { - entityid_t cubeEntity = entityManagerAdd(testEntities); - componentid_t cubePosition = entityAddComponent( - testEntities, cubeEntity, COMPONENT_TYPE_POSITION - ); - entityPositionSetLocalPosition( - testEntities, cubeEntity, cubePosition, cubeStartPositions[i] - ); - entityAddComponent(testEntities, cubeEntity, COMPONENT_TYPE_PHYSICS); - componentid_t cubeRenderable = entityAddComponent( - testEntities, cubeEntity, COMPONENT_TYPE_RENDERABLE - ); - entityRenderableSetColor( - testEntities, cubeEntity, cubeRenderable, cubeColors[i] - ); - } - - networkRequestConnection( - engineNetworkOnConnected, - engineNetworkOnFailed, - engineNetworkOnDisconnect, - NULL - ); - consolePrint("Engine initialized"); #ifdef DUSK_ASSERTIONS_FAKED @@ -148,14 +64,6 @@ errorret_t engineUpdate(void) { errorChain(networkUpdate()); timeUpdate(); - if( - ENGINE.networkDisconnectTestPending && - TIME.time >= ENGINE.networkDisconnectTestAt - ) { - ENGINE.networkDisconnectTestPending = false; - networkRequestDisconnection(engineNetworkDisconnectTestOnComplete, NULL); - } - const systemdialogtype_t dialogType = systemGetActiveDialogType(); if(dialogType == SYSTEM_DIALOG_TYPE_NONE) { inputUpdate(); @@ -193,63 +101,3 @@ errorret_t engineDispose(void) { errorOk(); } - -void engineHttpTestOnComplete(void *params, void *user) { - networkhttprequest_t *request = (networkhttprequest_t *)params; - const networkhttpresponse_t *response = &request->response; - - consolePrint("frogfind.com response status: %d", response->status); - - for(uint32_t i = 0; i < response->headers.count; i++) { - const networkhttpheader_t *header = &response->headers.headers[i]; - consolePrint("frogfind.com header: %s: %s", header->name, header->value); - } - - consolePrint("frogfind.com body length: %d", (int32_t)response->bodyLength); - - const size_t chunkMax = CONSOLE_LINE_MAX - 32; - for(size_t offset = 0; offset < response->bodyLength; offset += chunkMax) { - size_t chunkLength = response->bodyLength - offset; - if(chunkLength > chunkMax) chunkLength = chunkMax; - - consolePrint("frogfind.com body: %.*s", - (int32_t)chunkLength, (const char_t *)response->body + offset); - } -} - -void engineHttpTestOnError(void *params, void *user) { - consolePrint("frogfind.com request failed"); -} - -void engineNetworkOnConnected(void *user) { - consolePrint("Network connected"); - - errorret_t ret = networkHttpRequest( - NETWORK_HTTP_METHOD_GET, - "http://frogfind.com", - NULL, 0, - NULL, 0, - NULL, 0, - engineHttpTestOnComplete, - engineHttpTestOnError, - NULL - ); - errorCatch(errorPrint(ret)); - - ENGINE.networkDisconnectTestAt = TIME.time + 10.0f; - ENGINE.networkDisconnectTestPending = true; -} - -void engineNetworkOnFailed(errorret_t error, void *user) { - consolePrint("Network connection failed"); - errorCatch(errorPrint(error)); -} - -void engineNetworkOnDisconnect(errorret_t error, void *user) { - consolePrint("Network disconnected"); - errorCatch(errorPrint(error)); -} - -void engineNetworkDisconnectTestOnComplete(void *user) { - consolePrint("Network disconnect test complete"); -} diff --git a/src/dusk/engine/engine.h b/src/dusk/engine/engine.h index 31fc6667..2fc4eadb 100644 --- a/src/dusk/engine/engine.h +++ b/src/dusk/engine/engine.h @@ -10,22 +10,12 @@ // Important to be included first: #include "display/display.h" #include "error/error.h" -#include "scene/scenebase.h" -#include "entity/entitybase.h" typedef struct { bool_t running; int32_t argc; const char_t **argv; const char_t *version; - - // Test: disconnects the network 10 seconds after it connects. - bool_t networkDisconnectTestPending; - float_t networkDisconnectTestAt; - - // Test: three colored cubes falling onto each other and a static floor, - // viewed by a static camera. - sceneid_t testSceneId; } engine_t; extern engine_t ENGINE; @@ -47,52 +37,3 @@ errorret_t engineUpdate(void); * Shuts down the engine. */ errorret_t engineDispose(void); - -/** - * Logs the response status, headers and body of the frogfind.com test - * request to the console. - * - * @param params The completed networkhttprequest_t. - * @param user Unused. - */ -void engineHttpTestOnComplete(void *params, void *user); - -/** - * Logs that the frogfind.com test request failed. - * - * @param params The failed networkhttprequest_t. - * @param user Unused. - */ -void engineHttpTestOnError(void *params, void *user); - -/** - * Fires the frogfind.com test request once the network connection is - * up (some platforms, such as PSP, only bring the network stack up - * asynchronously after networkRequestConnection is called). - * - * @param user Unused. - */ -void engineNetworkOnConnected(void *user); - -/** - * Logs that the network connection could not be established. - * - * @param error The error describing why the connection failed. - * @param user Unused. - */ -void engineNetworkOnFailed(errorret_t error, void *user); - -/** - * Logs that the network connection was lost after having connected. - * - * @param error The error describing why the connection was lost. - * @param user Unused. - */ -void engineNetworkOnDisconnect(errorret_t error, void *user); - -/** - * Logs that the 10-second test disconnect completed. - * - * @param user Unused. - */ -void engineNetworkDisconnectTestOnComplete(void *user); diff --git a/src/duskdolphin/log/log.c b/src/duskdolphin/log/log.c index d06663d0..03835916 100644 --- a/src/duskdolphin/log/log.c +++ b/src/duskdolphin/log/log.c @@ -116,16 +116,18 @@ void logError(const char_t *message, ...) { // PAD_Init is idempotent - safe even if inputInit already called it, // and handles the case where the error occurred before inputInit ran. + // logError is used for routine, already-handled errors (e.g. a rejected + // network connection) as well as genuine assertion failures, so it must + // only pause to let the message be seen, not decide to terminate the + // program -- that decision belongs to the caller (assertTrueImpl calls + // abort() right after logError returns; a plain errorPrint() just + // continues running). PAD_Init(); while(SYS_MainLoop()) { PAD_ScanPads(); - // START button matches the RAGEQUIT bind - allows exit on GC controller - // when there is no Wiimote HOME button available. + // START button matches the RAGEQUIT bind - allows dismissing the + // message on a GC controller when there is no Wiimote HOME button. if(PAD_ButtonsDown(0) & PAD_BUTTON_START) break; VIDEO_WaitVSync(); } - - // Exit cleanly to HBC regardless of engine state. engineDispose() is not - // called here because the engine may be partially initialized. - exit(0); } \ No newline at end of file diff --git a/src/duskdolphin/network/networkdolphin.c b/src/duskdolphin/network/networkdolphin.c index 580031fb..4827ff1b 100644 --- a/src/duskdolphin/network/networkdolphin.c +++ b/src/duskdolphin/network/networkdolphin.c @@ -7,7 +7,10 @@ #include "network/network.h" #include "util/memory.h" +#include "util/string.h" #include "assert/assert.h" +#include "log/log.h" +#include errorret_t networkDolphinInit() { // s32 ret = net_init(); @@ -45,43 +48,86 @@ void networkDolphinRequestConnection( NETWORK.platform.onFailed = onFailed; NETWORK.platform.onConnectedUser = user; - memoryZero(NETWORK.platform.ip, sizeof(NETWORK.platform.ip)); - memoryZero(NETWORK.platform.netmask, sizeof(NETWORK.platform.netmask)); - memoryZero(NETWORK.platform.gateway, sizeof(NETWORK.platform.gateway)); + // TEMPORARY: static IP instead of DHCP, to isolate whether if_config()'s + // DHCP handshake (and the packet-receive/DMA path it triggers in gcif.c) + // is what's corrupting memory, vs. link-up/NWAY negotiation alone. These + // match Dolphin's Broadband Adapter (Built-In) default subnet. Revert to + // DHCP once this is diagnosed. + stringCopy(NETWORK.platform.ip, "10.0.1.10", sizeof(NETWORK.platform.ip)); + stringCopy( + NETWORK.platform.netmask, "255.255.255.0", + sizeof(NETWORK.platform.netmask) + ); + stringCopy( + NETWORK.platform.gateway, "10.0.1.1", sizeof(NETWORK.platform.gateway) + ); - // Negotiate DHCP using the Wi-Fi settings saved in Wii System Menu. - // This call blocks until the interface is configured or times out. - #ifdef DUSK_WII - s32 ret = if_config( - NETWORK.platform.ip, - NETWORK.platform.netmask, - NETWORK.platform.gateway, - true - ); - #else - s32 ret = -1; - #endif + // Diagnostic: log the live MEM1 arena headroom (assets/heap already + // accounted for, since this runs after assetInit()/scene setup) right + // around if_config(), to check whether linking `bba` (~400KB+ of new + // static lwIP pools, see lwipopts.h) is pushing memory too tight. + logDebug( + "networkDolphinRequestConnection: MEM1 arena free before if_config(): " + "%u bytes (lo=%p hi=%p)\n", + (uint32_t)((uint8_t *)SYS_GetArena1Hi() - (uint8_t *)SYS_GetArena1Lo()), + SYS_GetArena1Lo(), SYS_GetArena1Hi() + ); + // Negotiate DHCP. This call blocks until the interface is configured or + // times out. Wii (IOS) and GameCube (bundled lwIP + BBA driver, linked + // via `bba` in gamecube.cmake) expose the same if_config()/net_*() API, + // so this call is identical on both platforms. + logDebug("networkDolphinRequestConnection: calling if_config()\n"); + s32 ret = if_config( + NETWORK.platform.ip, + NETWORK.platform.netmask, + NETWORK.platform.gateway, + false + ); + logDebug( + "networkDolphinRequestConnection: if_config() returned %d " + "(ip=%s netmask=%s gateway=%s)\n", + (int_t)ret, + NETWORK.platform.ip, + NETWORK.platform.netmask, + NETWORK.platform.gateway + ); + logDebug( + "networkDolphinRequestConnection: MEM1 arena free after if_config(): " + "%u bytes (lo=%p hi=%p)\n", + (uint32_t)((uint8_t *)SYS_GetArena1Hi() - (uint8_t *)SYS_GetArena1Lo()), + SYS_GetArena1Lo(), SYS_GetArena1Hi() + ); + + // Rejecting the connection (no cable/AP, DHCP timeout) is a routine, + // expected outcome, not a programming error. It must never be able to + // bring the whole program down, so this only logs and skips the + // callback if it's unexpectedly missing, rather than asserting. if(ret >= 0) { NETWORK.state = NETWORK_STATE_CONNECTED; - assertNotNull( - NETWORK.platform.onConnected, - "Network platform onConnected callback should be set." - ); - NETWORK.platform.onConnected(NETWORK.platform.onConnectedUser); - } else { - NETWORK.state = NETWORK_STATE_DISCONNECTED; - assertNotNull( - NETWORK.platform.onFailed, - "Network platform onFailed callback should be set." - ); - errorret_t error = errorThrowImpl( - &NETWORK.errorState, - ERROR_NOT_OK, - __FILE__, __func__, __LINE__, - "Failed to connect to network" - ); + logDebug("networkDolphinRequestConnection: connected\n"); + if(NETWORK.platform.onConnected) { + NETWORK.platform.onConnected(NETWORK.platform.onConnectedUser); + } + return; + } + + NETWORK.state = NETWORK_STATE_DISCONNECTED; + logDebug( + "networkDolphinRequestConnection: failed to connect (if_config " + "returned %d)\n", + (int_t)ret + ); + errorret_t error = errorThrowImpl( + &NETWORK.errorState, + ERROR_NOT_OK, + __FILE__, __func__, __LINE__, + "Failed to connect to network" + ); + if(NETWORK.platform.onFailed) { NETWORK.platform.onFailed(error, NETWORK.platform.onConnectedUser); + } else { + errorCatch(errorPrint(error)); } } diff --git a/src/duskdolphin/network/networkdolphin.h b/src/duskdolphin/network/networkdolphin.h index 993219a0..95c32ff1 100644 --- a/src/duskdolphin/network/networkdolphin.h +++ b/src/duskdolphin/network/networkdolphin.h @@ -12,6 +12,15 @@ #define NETWORK_DOLPHIN_IP_MAX 16 +#ifdef DUSK_GAMECUBE + // No DNS client is implemented for GameCube yet (see + // networkSocketDolphinConnect) -- only literal IP addresses work. + // A future resolver should query this server. + #ifndef NETWORK_DOLPHIN_DNS_SERVER_IP + #define NETWORK_DOLPHIN_DNS_SERVER_IP "0.0.0.0" + #endif +#endif + typedef struct { char_t ip[NETWORK_DOLPHIN_IP_MAX]; char_t netmask[NETWORK_DOLPHIN_IP_MAX]; @@ -23,37 +32,39 @@ typedef struct { } networkdolphin_t; /** - * Initializes the Wii network stack via IOS. Does not connect; use - * networkDolphinRequestConnection for that. + * Initializes the network stack (IOS on Wii, bundled lwIP + BBA driver on + * GameCube). Does not connect; use networkDolphinRequestConnection for + * that. * * @return Error state (if any). */ errorret_t networkDolphinInit(); /** - * Called each frame. No-op on Wii since connection is synchronous. + * Called each frame. No-op since connection is synchronous. * * @return Error state (if any). */ errorret_t networkDolphinUpdate(); /** - * Disposes the Wii network stack. + * Disposes the network stack. * * @return Error state (if any). */ errorret_t networkDolphinDispose(); /** - * Returns true if the Wii is connected to a network. + * Returns true if connected to a network. * * @return True if connected. */ bool_t networkDolphinIsConnected(); /** - * Requests the Wii to connect to the network using the Wi-Fi settings saved - * in the Wii System Menu. Blocks until connected or failed. + * Requests a connection to the network: the Wi-Fi settings saved in the + * Wii System Menu on Wii, or DHCP over the Broadband Adapter on GameCube. + * Blocks until connected or failed. * * @param onConnected Callback on successful connection. * @param onFailed Callback if connection fails. @@ -68,7 +79,7 @@ void networkDolphinRequestConnection( ); /** - * Requests the Wii to disconnect from the network. + * Requests to disconnect from the network. * * @param onComplete Callback when disconnection is complete. * @param user User data passed to the callback. diff --git a/src/duskdolphin/network/networksocketdolphin.c b/src/duskdolphin/network/networksocketdolphin.c index 125e1a83..e436eb6b 100644 --- a/src/duskdolphin/network/networksocketdolphin.c +++ b/src/duskdolphin/network/networksocketdolphin.c @@ -8,8 +8,9 @@ #include "networksocketdolphin.h" #include "util/memory.h" #include "assert/assert.h" +#include "log/log.h" -#ifdef DUSK_WII +#if defined(DUSK_WII) || defined(DUSK_GAMECUBE) #include #endif @@ -21,24 +22,60 @@ errorret_t networkSocketDolphinConnect( assertNotNull(sock, "sock must not be NULL"); assertNotNull(host, "host must not be NULL"); - #ifdef DUSK_WII - struct hostent *entry = net_gethostbyname(host); - if(entry == NULL || entry->h_addr_list[0] == NULL) { - errorThrow("Failed to resolve host %s", host); - } + logDebug( + "networkSocketDolphinConnect: resolving %s:%u\n", host, (uint32_t)port + ); + + #if defined(DUSK_WII) || defined(DUSK_GAMECUBE) + struct in_addr resolved; + + #ifdef DUSK_WII + struct hostent *entry = net_gethostbyname(host); + if(entry == NULL || entry->h_addr_list[0] == NULL) { + logDebug( + "networkSocketDolphinConnect: net_gethostbyname(%s) failed\n", host + ); + errorThrow("Failed to resolve host %s", host); + } + memoryCopy(&resolved, entry->h_addr_list[0], sizeof(resolved)); + #else + // GameCube's bundled lwIP stack has no DNS resolver (see + // NETWORK_DOLPHIN_DNS_SERVER_IP in networkdolphin.h) -- only + // literal IP addresses work until a resolver is written against + // that server. + if(!inet_aton(host, &resolved)) { + logDebug( + "networkSocketDolphinConnect: inet_aton(%s) failed\n", host + ); + errorThrow( + "Failed to resolve host %s (GameCube requires a literal IP " + "address; DNS is not yet supported)", host + ); + } + #endif + + logDebug( + "networkSocketDolphinConnect: resolved %s to %s\n", + host, inet_ntoa(resolved) + ); const s32 fd = net_socket(AF_INET, SOCK_STREAM, 0); + logDebug( + "networkSocketDolphinConnect: net_socket() returned fd=%d\n", + (int_t)fd + ); if(fd < 0) errorThrow("Failed to create socket: %d", (int_t)fd); struct sockaddr_in serverAddr; memoryZero(&serverAddr, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(port); - memoryCopy( - &serverAddr.sin_addr, entry->h_addr_list[0], sizeof(serverAddr.sin_addr) - ); + serverAddr.sin_addr = resolved; const s32 ret = net_connect(fd, (void *)&serverAddr, sizeof(serverAddr)); + logDebug( + "networkSocketDolphinConnect: net_connect() returned %d\n", (int_t)ret + ); if(ret < 0) { net_close(fd); errorThrow("Failed to connect to %s:%u: %d", host, port, (int_t)ret); @@ -63,8 +100,12 @@ errorret_t networkSocketDolphinSend( assertNotNull(data, "data must not be NULL"); assertNotNull(outSent, "outSent must not be NULL"); - #ifdef DUSK_WII + #if defined(DUSK_WII) || defined(DUSK_GAMECUBE) const s32 sent = net_send(sock->fd, data, (s32)length, 0); + logDebug( + "networkSocketDolphinSend: net_send(fd=%d, len=%u) returned %d\n", + sock->fd, (uint32_t)length, (int_t)sent + ); if(sent < 0) errorThrow("Failed to send data: %d", (int_t)sent); *outSent = (size_t)sent; @@ -84,8 +125,12 @@ errorret_t networkSocketDolphinReceive( assertNotNull(buffer, "buffer must not be NULL"); assertNotNull(outReceived, "outReceived must not be NULL"); - #ifdef DUSK_WII + #if defined(DUSK_WII) || defined(DUSK_GAMECUBE) const s32 received = net_recv(sock->fd, buffer, (s32)bufferSize, 0); + logDebug( + "networkSocketDolphinReceive: net_recv(fd=%d, max=%u) returned %d\n", + sock->fd, (uint32_t)bufferSize, (int_t)received + ); if(received < 0) errorThrow("Failed to receive data: %d", (int_t)received); *outReceived = (size_t)received; @@ -98,7 +143,8 @@ errorret_t networkSocketDolphinReceive( void networkSocketDolphinClose(networksocketdolphin_t *sock) { assertNotNull(sock, "sock must not be NULL"); - #ifdef DUSK_WII + #if defined(DUSK_WII) || defined(DUSK_GAMECUBE) + logDebug("networkSocketDolphinClose: closing fd=%d\n", sock->fd); if(sock->fd >= 0) net_close(sock->fd); #endif sock->fd = -1; diff --git a/src/duskdolphin/network/networksocketdolphin.h b/src/duskdolphin/network/networksocketdolphin.h index b59dab78..f59104b5 100644 --- a/src/duskdolphin/network/networksocketdolphin.h +++ b/src/duskdolphin/network/networksocketdolphin.h @@ -14,8 +14,10 @@ typedef struct { /** * Resolves host and opens a blocking TCP connection to host:port. - * Always fails under plain DUSK_GAMECUBE -- retail GameCube units have - * no network hardware. + * On Wii, host may be a hostname (resolved via IOS's DNS resolver) or an + * IP address. On GameCube, host must be a literal IP address -- the + * bundled lwIP stack has no DNS resolver yet (see + * NETWORK_DOLPHIN_DNS_SERVER_IP in networkdolphin.h). * * @param sock The socket structure to initialize. * @param host The hostname or IP address to connect to.