PSP Networking refactor

This commit is contained in:
2026-04-17 14:57:10 -05:00
parent 715ecffa18
commit 225f405592
28 changed files with 758 additions and 1031 deletions
+1
View File
@@ -72,6 +72,7 @@ add_subdirectory(locale)
add_subdirectory(physics) add_subdirectory(physics)
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(script) add_subdirectory(script)
add_subdirectory(system)
add_subdirectory(time) add_subdirectory(time)
add_subdirectory(ui) add_subdirectory(ui)
add_subdirectory(network) add_subdirectory(network)
+44 -42
View File
@@ -20,46 +20,41 @@
#include "entity/component/physics/entityphysics.h" #include "entity/component/physics/entityphysics.h"
#include "game/game.h" #include "game/game.h"
#include "physics/physicsmanager.h" #include "physics/physicsmanager.h"
#include "network/networkmanager.h" #include "network/network.h"
#include "network/networkhttprequest.h"
#include "display/mesh/cube.h" #include "display/mesh/cube.h"
#include "display/mesh/plane.h" #include "display/mesh/plane.h"
engine_t ENGINE; engine_t ENGINE;
entityid_t phBoxEnt;
componentid_t phBoxPhys;
float_t disconnectTime = FLT_MAX;
static entityid_t phBoxEnt; void onNetworkConnected(void *user) {
static componentid_t phBoxPhys; disconnectTime = TIME.time + 3.0f;
printf("Network connected, I will disconnect at: %.2f1.\n", disconnectTime);
static void onTestResponse( // networkHTTPRequest(
const uint16_t status, // "http://10.0.0.94:3000/test",
const char_t *body, // NETWORK_HTTP_REQUEST_METHOD_GET,
const networkhttpheader_t *responseHeaders, // NULL, NULL, 0, NULL,
const uint32_t responseHeaderCount, // onTestResponse,
void *user // onTestError
) { // );
printf("HTTP %d: %s\n", status, body);
} }
static void onTestError(errorret_t error, void *user) { void onNetworkFailed(errorret_t error, void *user) {
printf("HTTP error: %s\n", error.state->message);
}
static void onNetworkConnected(void *user) {
printf("Network connected.\n");
networkHTTPRequest(
"http://10.0.0.94:3000/test",
NETWORK_HTTP_REQUEST_METHOD_GET,
NULL, NULL, 0, NULL,
onTestResponse,
onTestError
);
}
static void onNetworkFailed(errorret_t error, void *user) {
printf("Network connection failed: %s\n", error.state->message); printf("Network connection failed: %s\n", error.state->message);
} }
void onNetworkDisconnected(errorret_t error, void *user) {
printf("Network disconnected.\n");
errorCatch(errorPrint(error));
}
void onNetworkDisconnectFinished(void *user) {
printf("Finished disconnecting from network.\n");
}
errorret_t engineInit(const int32_t argc, const char_t **argv) { errorret_t engineInit(const int32_t argc, const char_t **argv) {
memoryZero(&ENGINE, sizeof(engine_t)); memoryZero(&ENGINE, sizeof(engine_t));
ENGINE.running = true; ENGINE.running = true;
@@ -77,11 +72,19 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(sceneInit()); errorChain(sceneInit());
entityManagerInit(); entityManagerInit();
physicsManagerInit(); physicsManagerInit();
errorChain(networkManagerInit()); errorChain(networkInit());
networkManagerRequestConnection(onNetworkConnected, onNetworkFailed, NULL);
errorChain(gameInit()); errorChain(gameInit());
/* ---- Camera ---- */ printf("Going online...\n");
networkRequestConnection(
onNetworkConnected,
onNetworkFailed,
onNetworkDisconnected,
NULL
);
// Camera
entityid_t cam = entityManagerAdd(); entityid_t cam = entityManagerAdd();
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION); componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
float_t distance = 6.0f; float_t distance = 6.0f;
@@ -94,7 +97,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA); componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA);
entityCameraSetZFar(cam, camCam, distance * 6.0f); entityCameraSetZFar(cam, camCam, distance * 6.0f);
/* ---- Static floor (visual + physics) ---- */ // Floor
entityid_t floorEnt = entityManagerAdd(); entityid_t floorEnt = entityManagerAdd();
componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION); componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION);
componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH); componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH);
@@ -123,9 +126,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE); entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE);
entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED; entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED;
/* Physics position lives in the POSITION component. CUBE_MESH_SIMPLE is
* centred at origin (-0.5..0.5), so entity position == physics centre. */
entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f }); entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f });
/* Run the init script. */ /* Run the init script. */
@@ -138,11 +138,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
} }
errorret_t engineUpdate(void) { errorret_t engineUpdate(void) {
errorChain(networkManagerUpdate()); errorChain(networkUpdate());
if(networkManagerIsConnectionModalOpen()) {
errorChain(displayUpdate());
errorOk();
}
timeUpdate(); timeUpdate();
inputUpdate(); inputUpdate();
@@ -165,6 +161,12 @@ errorret_t engineUpdate(void) {
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false; if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
if(TIME.time >= disconnectTime) {
printf("Time to disconnect from the network.\n");
networkRequestDisconnection(onNetworkDisconnectFinished, NULL);
disconnectTime = FLT_MAX;
}
errorOk(); errorOk();
} }
@@ -173,7 +175,7 @@ void engineExit(void) {
} }
errorret_t engineDispose(void) { errorret_t engineDispose(void) {
errorChain(networkManagerDispose()); errorChain(networkDispose());
sceneDispose(); sceneDispose();
errorChain(gameDispose()); errorChain(gameDispose());
entityManagerDispose(); entityManagerDispose();
+1 -2
View File
@@ -5,6 +5,5 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
networkmanager.c network.c
networkhttprequest.c
) )
+116
View File
@@ -0,0 +1,116 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "log/log.h"
network_t NETWORK;
errorret_t networkInit() {
memoryZero(&NETWORK, sizeof(network_t));
NETWORK.errorState.code = ERROR_OK;
NETWORK.onDisconnect = NULL;
return networkPlatformInit();
}
errorret_t networkUpdate() {
errorChain(networkPlatformUpdate());
if(NETWORK.state == NETWORK_STATE_CONNECTED && !networkIsConnected()) {
NETWORK.state = NETWORK_STATE_DISCONNECTED;
if(NETWORK.onDisconnect) {
errorret_t ret;
if(NETWORK.errorState.code == ERROR_OK) {
ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"Network connection lost"
);
} else {
ret.code = NETWORK.errorState.code;
ret.state = &NETWORK.errorState;
}
NETWORK.onDisconnect(ret, NETWORK.disconnectUser);
}
}
errorOk();
}
bool_t networkIsConnected() {
return networkPlatformIsConnected();
}
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
) {
assertNotNull(onConnected, "onConnected callback must not be null");
assertNotNull(onFailed, "onFailed callback must not be null");
assertNotNull(onDisconnect, "onDisconnect callback must not be null");
NETWORK.state = NETWORK_STATE_CONNECTING;
NETWORK.onDisconnect = onDisconnect;
NETWORK.disconnectUser = user;
#ifndef networkPlatformRequestConnection
// This is a platform cannot be requested to go online, this would basically
// be for platforms like Linux or Windows where the OS is responsible for
// maintaining the network connection.
if(networkIsConnected()) {
NETWORK.state = NETWORK_STATE_CONNECTED;
onConnected(user);
} else {
errorret_t ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"No network connection available"
);
onFailed(ret, user);
}
#else
networkPlatformRequestConnection(onConnected, onFailed, onDisconnect, user);
#endif
}
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
) {
assertNotNull(onComplete, "onComplete callback must not be null");
NETWORK.state = NETWORK_STATE_DISCONNECTING;
#ifndef networkPlatformRequestDisconnection
NETWORK.state = NETWORK_STATE_DISCONNECTED;
onComplete(user);
#else
networkPlatformRequestDisconnection(onComplete, user);
#endif
}
void networkDisconnectedDuringDispose(void *u) {
logDebug("Network disconnected during dispose\n");
}
errorret_t networkDispose() {
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
networkRequestDisconnection(networkDisconnectedDuringDispose, NULL);
}
errorChain(networkPlatformDispose());
errorOk();
}
+126
View File
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "network/networkplatform.h"
#ifndef networkPlatformInit
#error "networkPlatformInit must be defined"
#endif
#ifndef networkPlatformUpdate
#error "networkPlatformUpdate must be defined"
#endif
#ifndef networkPlatformDispose
#error "networkPlatformDispose must be defined"
#endif
#ifndef networkPlatformIsConnected
#error "networkPlatformIsConnected must be defined"
#endif
typedef enum {
NETWORK_STATE_DISCONNECTED,
NETWORK_STATE_CONNECTING,
NETWORK_STATE_CONNECTED,
NETWORK_STATE_DISCONNECTING,
} networkstate_t;
typedef struct {
networkplatform_t platform;
errorstate_t errorState;
networkstate_t state;
void (*onDisconnect)(errorret_t error, void *user);
void *disconnectUser;
} network_t;
extern network_t NETWORK;
/**
* Initializes the network system. This will NOT connect to the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkInit();
/**
* Updates the network manager, dispatching any completed async request
* callbacks on the main thread.
*
* @return An error code indicating success or failure.
*/
errorret_t networkUpdate();
/**
* Disposes of the network manager. This will NOT disconnect from the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkDispose();
/**
* Returns true if the system is connected to AN network, this doesn't mean that
* requests will succeed, doesn't mean there's internet, just that we could
* possibly make network requests. If this is false, this usually means
* something like;
* - A network cable is not connnected
* - No Wi-Fi Connection has been established
* - No IP Address has been assigned
*
* That kinda stuff.
*
* In future I will probably have REASONS for why it's not connected, for
* example;
* - On PSP you need to "request" network access
* - On GameCube, network settings need to be defined, including DHCP, etc.
* - On Windows, this may require additional permissions
*
* @return True if some network connection is detected.
*/
bool_t networkIsConnected();
/**
* See networkIsConnected for a bit more info, but this is for some
* platforms (mainly PSP) to request the system to start doing networking.
*
* You should only call this once and assume that it is "pending" until either
* onComplete or onFailed is invoked. If you call this twice it is undefined
* behavior.
*
* onDisconnect must be provided, and is called whenever the network is lost
* after a successful connection. This will NOT be called if disconnect is
* manually triggered, but WILL if an error occurs, or a network stack bug, etc.
*
* @param onConnected Callback to invoke when the network is connected.
* @param onFailed Callback to invoke if the network connection fails.
* @param onDisconnect Called after a successful connection, when disconnected.
* @param user User data to pass to the callbacks.
*/
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
);
/**
* Requests the system to disconnect from the network. This is basically just
* for PSP, but I guess it could be used on other platforms in future if they
* have some kind of "network connection mode" that needs to be exited.
*
* You should only call this once and assume that it is "pending" until
* onComplete is invoked. If you call this twice it is undefined behavior.
*
* If it fails, you still get onComplete called, but may fail if you try to
* reconnect later unfortunately.
*
* @param onComplete Callback to invoke when the network is disconnected.
* @param user User data to pass to the callback.
*/
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
);
-32
View File
@@ -1,32 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networkhttprequest.h"
#include "assert/assert.h"
#include "network/networkplatform.h"
void networkHTTPRequest(
const char_t *url,
const networkhttprequestmethod_t method,
const char_t *bodyOrNull,
const networkhttpheader_t *headers,
const uint32_t headerCount,
void *user,
networkhttpcallback_t callback,
networkhttperrorcallback_t errorCallback
) {
assertStrLenMin(url, 1, "URL must be non-empty");
assertNotNull(callback, "Callback must be non-NULL");
assertNotNull(errorCallback, "Error callback must be non-NULL");
assertTrue(headerCount == 0 || headers != NULL, "Headers must not be NULL when headerCount > 0");
uint32_t count = (headers == NULL) ? 0 : headerCount;
networkPlatformHTTPRequest(
url, method, bodyOrNull, headers, count, user, callback, errorCallback
);
}
-62
View File
@@ -1,62 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef enum {
NETWORK_HTTP_REQUEST_METHOD_GET,
NETWORK_HTTP_REQUEST_METHOD_POST,
NETWORK_HTTP_REQUEST_METHOD_PUT,
NETWORK_HTTP_REQUEST_METHOD_DELETE,
NETWORK_HTTP_REQUEST_METHOD_HEAD,
NETWORK_HTTP_REQUEST_METHOD_OPTIONS,
} networkhttprequestmethod_t;
typedef struct {
const char_t *key;
const char_t *value;
} networkhttpheader_t;
typedef void (*networkhttpcallback_t)(
const uint16_t status,
const char_t *body,
const networkhttpheader_t *responseHeaders,
const uint32_t responseHeaderCount,
void *user
);
typedef void (*networkhttperrorcallback_t)(
errorret_t error,
void *user
);
/**
* Makes a HTTP Request. The request itself is asynchronous but the callback
* will be invoked on the main thread when next available.
*
* @param url URL to request.
* @param method Method to use for the request.
* @param bodyOrNull If POST or PUT, custom body string, can be NULL.
* @param headers Array of key-value headers.
* @param headerCount Count of headers, can be anything if headers is NULL.
* @param user Callback pointer received to the callback.
* @param callback The callback to invoke when the request completes.
* @param errorCallback The callback to invoke if the request fails.
* Note, this doesn't count Non-200 status codes, just
* network errors.
*/
void networkHTTPRequest(
const char_t *url,
const networkhttprequestmethod_t method,
const char_t *bodyOrNull,
const networkhttpheader_t *headers,
const uint32_t headerCount,
void *user,
networkhttpcallback_t callback,
networkhttperrorcallback_t errorCallback
);
-44
View File
@@ -1,44 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networkmanager.h"
#include "util/memory.h"
#include "network/networkplatform.h"
networkmanager_t NETWORK_MANAGER;
errorret_t networkManagerInit() {
memoryZero(&NETWORK_MANAGER, sizeof(networkmanager_t));
errorChain(networkPlatformInit());
errorOk();
}
errorret_t networkManagerUpdate() {
errorChain(networkPlatformUpdate());
errorOk();
}
bool_t networkManagerIsConnected() {
return networkPlatformIsConnected();
}
void networkManagerRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void *user
) {
networkPlatformRequestConnection(onConnected, onFailed, user);
}
bool_t networkManagerIsConnectionModalOpen() {
return networkPlatformIsConnectionModalOpen();
}
errorret_t networkManagerDispose() {
errorChain(networkPlatformDispose());
errorOk();
}
-86
View File
@@ -1,86 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct {
void *nothing;
} networkmanager_t;
extern networkmanager_t NETWORK_MANAGER;
/**
* Initializes the network manager. This will NOT connect to the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkManagerInit();
/**
* Updates the network manager, dispatching any completed async request
* callbacks on the main thread.
*
* @return An error code indicating success or failure.
*/
errorret_t networkManagerUpdate();
/**
* Returns true if the system is connected to AN network, this doesn't mean that
* requests will succeed, doesn't mean there's internet, just that we could
* possibly make network requests. If this is false, this usually means
* something like;
* - A network cable is not connnected
* - No Wi-Fi Connection has been established
* - No IP Address has been assigned
*
* That kinda stuff.
*
* In future I will probably have REASONS for why it's not connected, for
* example;
* - On PSP you need to "request" network access
* - On GameCube, network settings need to be defined, including DHCP, etc.
* - On Windows, this may require additional permissions
*
* @return True if some network connection is detected.
*/
bool_t networkManagerIsConnected();
/**
* See networkManagerIsConnected for a bit more info, but this is for some
* platforms (mainly PSP) to request the system to start doing networking.
*
* You should only call this once and assume that it is "pending" until either
* onComplete or onFailed is invoked. If you call this twice it is undefined
* behavior.
*
* @param onConnected Callback to invoke when the network is connected.
* @param onFailed Callback to invoke if the network connection fails.
* @param user User data to pass to the callbacks.
*/
void networkManagerRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void *user
);
/**
* This is highly platform specific unfortunately, but basically for PSP and
* maybe some other systems in future, I need a way to tell the engine that the
* network manager is blocking rendering while it lets the user connect.
*
* @return If true, then the network manager is asking the engine to not do any
* thing, including time stepping, until this complete.
*/
bool_t networkManagerIsConnectionModalOpen();
/**
* Disposes of the network manager. This will NOT disconnect from the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkManagerDispose();
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
system.c
)
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "system.h"
#include "system/systemplatform.h"
#ifndef systemGetActiveDialogTypePlatform
#error "systemGetActiveDialogTypePlatform is not defined"
#endif
systemdialogtype_t systemGetActiveDialogType() {
return systemGetActiveDialogTypePlatform();
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
SYSTEM_DIALOG_TYPE_NONE,
SYSTEM_DIALOG_TYPE_RENDER_BLOCKING,
SYSTEM_DIALOG_TYPE_TICK_BLOCKING
} systemdialogtype_t;
/**
* Basically this is only used on a few system types, it is to ask the plaform,
* e.g. PSP "What dialog is currently open?" and then the engine will change the
* behavior of the main loop to accomodate.
*
* For example, PSP can show dialogs for things like, save files, wifi, system
* information, etc. When these are open, the engine really can't do much and
* the system needs to finish processing.
*
* For most systems this will go unused.
*
* @return Dialog type currently open.
*/
systemdialogtype_t systemGetActiveDialogType();
+1
View File
@@ -14,3 +14,4 @@ add_subdirectory(asset)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(network) add_subdirectory(network)
add_subdirectory(system)
+24 -253
View File
@@ -9,130 +9,8 @@
#include "util/memory.h" #include "util/memory.h"
#include "util/string.h" #include "util/string.h"
#include "assert/assert.h" #include "assert/assert.h"
#include <curl/curl.h>
static networklinux_t NETWORK_LINUX;
static size_t networkLinuxCurlWrite(
char *ptr,
size_t size,
size_t nmemb,
void *userdata
) {
assertNotNull(ptr, "curl write ptr must not be NULL");
assertNotNull(userdata, "curl write userdata must not be NULL");
networkhttppendingitem_t *item = (networkhttppendingitem_t *)userdata;
size_t incoming = size * nmemb;
size_t written = strlen(item->responseBody);
size_t space = NETWORK_HTTP_RESPONSE_MAX - 1 - written;
if(incoming > space) incoming = space;
if(incoming > 0) {
memcpy(item->responseBody + written, ptr, incoming);
item->responseBody[written + incoming] = '\0';
}
/* Return the original byte count — curl treats anything else as an error. */
return size * nmemb;
}
static void networkLinuxHTTPThread(thread_t *thread) {
assertNotNull(thread, "Thread must not be NULL");
assertNotNull(thread->data, "Thread data must not be NULL");
networkhttppendingitem_t *item = (networkhttppendingitem_t *)thread->data;
CURL *curl = curl_easy_init();
if(!curl) {
stringCopy(
item->errorMessage, "curl_easy_init failed", NETWORK_ERROR_MESSAGE_MAX - 1
);
item->isError = true;
goto done;
}
curl_easy_setopt(curl, CURLOPT_URL, item->url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, networkLinuxCurlWrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, item);
switch(item->method) {
case NETWORK_HTTP_REQUEST_METHOD_POST:
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(
curl, CURLOPT_POSTFIELDS, item->hasBody ? item->body : ""
);
curl_easy_setopt(
curl, CURLOPT_POSTFIELDSIZE,
(long)(item->hasBody ? strlen(item->body) : 0)
);
break;
case NETWORK_HTTP_REQUEST_METHOD_PUT:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
if(item->hasBody) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, item->body);
curl_easy_setopt(
curl, CURLOPT_POSTFIELDSIZE, (long)strlen(item->body)
);
}
break;
case NETWORK_HTTP_REQUEST_METHOD_DELETE:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
case NETWORK_HTTP_REQUEST_METHOD_HEAD:
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
break;
case NETWORK_HTTP_REQUEST_METHOD_OPTIONS:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
break;
case NETWORK_HTTP_REQUEST_METHOD_GET:
default:
break;
}
/* Build header list */
struct curl_slist *curlHeaders = NULL;
if(item->headerCount > 0) {
char_t line[NETWORK_HTTP_HEADER_KEY_MAX + NETWORK_HTTP_HEADER_VAL_MAX + 3];
for(uint32_t i = 0; i < item->headerCount; i++) {
snprintf(
line, sizeof(line), "%s: %s", item->headerKeys[i], item->headerVals[i]
);
curlHeaders = curl_slist_append(curlHeaders, line);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curlHeaders);
}
CURLcode res = curl_easy_perform(curl);
if(res != CURLE_OK) {
stringCopy(
item->errorMessage, curl_easy_strerror(res), NETWORK_ERROR_MESSAGE_MAX - 1
);
item->isError = true;
} else {
long code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
item->status = (uint16_t)code;
item->isError = false;
}
if(curlHeaders) curl_slist_free_all(curlHeaders);
curl_easy_cleanup(curl);
done:
threadMutexLock(&NETWORK_LINUX.resultsMutex);
item->resultReady = true;
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
}
errorret_t networkLinuxInit() { errorret_t networkLinuxInit() {
memoryZero(&NETWORK_LINUX, sizeof(networklinux_t));
threadMutexInit(&NETWORK_LINUX.resultsMutex);
CURLcode res = curl_global_init(CURL_GLOBAL_ALL); CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
if(res != CURLE_OK) { if(res != CURLE_OK) {
errorThrow("curl_global_init failed: %s", curl_easy_strerror(res)); errorThrow("curl_global_init failed: %s", curl_easy_strerror(res));
@@ -141,146 +19,39 @@ errorret_t networkLinuxInit() {
} }
errorret_t networkLinuxUpdate() { errorret_t networkLinuxUpdate() {
for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) {
/* Check under lock whether this slot has a result waiting. */
threadMutexLock(&NETWORK_LINUX.resultsMutex);
networkhttppendingitem_t *item = &NETWORK_LINUX.requests[i];
if(!item->used || !item->resultReady) {
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
continue;
}
/* Snapshot the small values we need after we drop the lock. */
bool_t isError = item->isError;
uint16_t status = item->status;
networkhttpcallback_t cb = item->callback;
networkhttperrorcallback_t errCb = item->errorCallback;
void *user = item->user;
/*
* Keep pointers into item's string buffers — valid because item->used is
* still true here and only this (main) thread clears it.
*/
const char_t *responseBody = item->responseBody;
const char_t *errorMessage = item->errorMessage;
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
/* Fire the callback without holding the lock. */
if(isError) {
errorstate_t errState;
errState.code = ERROR_NOT_OK;
errState.message = (char_t *)errorMessage;
errState.lines = (char_t *)"";
errorret_t err;
err.code = ERROR_NOT_OK;
err.state = &errState;
errCb(err, user);
} else {
cb(status, responseBody, NULL, 0, user);
}
/* Release the slot now that the callback has consumed it. */
threadMutexLock(&NETWORK_LINUX.resultsMutex);
item->used = false;
item->resultReady = false;
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
}
errorOk(); errorOk();
} }
bool_t networkLinuxIsConnected() { bool_t networkLinuxIsConnected() {
return true; // Call the OS network stack to check for connectivity.
struct ifaddrs *ifaddr, *ifa;
if(getifaddrs(&ifaddr) == -1) {
return false;
} }
void networkLinuxRequestConnection( // Check if any non loopback interfaces have running flag set.
void (*onConnected)(void *user), bool_t connected = false;
void (*onFailed)(errorret_t error, void *user), for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
void *user if (!ifa->ifa_name || !ifa->ifa_flags) continue;
) {
assertNotNull(onConnected, "onConnected must not be NULL"); // Skip loopback (localhost)
assertNotNull(onFailed, "onFailed must not be NULL"); if(ifa->ifa_flags & IFF_LOOPBACK) continue;
onConnected(user);
// Is interface up and running?
if(!(ifa->ifa_flags & IFF_UP)) continue;
if(!(ifa->ifa_flags & IFF_RUNNING)) continue;
connected = true;
break;
}
// Free the linked list of interfaces
freeifaddrs(ifaddr);
return connected;
} }
errorret_t networkLinuxDispose() { errorret_t networkLinuxDispose() {
curl_global_cleanup(); curl_global_cleanup();
threadMutexDispose(&NETWORK_LINUX.resultsMutex);
errorOk(); errorOk();
} }
void networkLinuxHTTPRequest(
const char_t *url,
const networkhttprequestmethod_t method,
const char_t *bodyOrNull,
const networkhttpheader_t *headers,
const uint32_t headerCount,
void *user,
networkhttpcallback_t callback,
networkhttperrorcallback_t errorCallback
) {
assertStrLenMin(url, 1, "URL must be non-empty");
assertStrLenMax(url, NETWORK_HTTP_URL_MAX, "URL exceeds maximum length");
assertNotNull(callback, "Callback must be non-NULL");
assertNotNull(errorCallback, "Error callback must be non-NULL");
assertTrue(headerCount == 0 || headers != NULL, "Headers must not be NULL when headerCount > 0");
/* Allocate a slot. */
threadMutexLock(&NETWORK_LINUX.resultsMutex);
networkhttppendingitem_t *item = NULL;
for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) {
if(!NETWORK_LINUX.requests[i].used) {
item = &NETWORK_LINUX.requests[i];
break;
}
}
if(!item) {
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
errorstate_t errState;
errState.code = ERROR_NOT_OK;
errState.message = (char_t *)"No free HTTP request slots";
errState.lines = (char_t *)"";
errorret_t err;
err.code = ERROR_NOT_OK;
err.state = &errState;
errorCallback(err, user);
return;
}
memoryZero(item, sizeof(networkhttppendingitem_t));
item->used = true;
threadMutexUnlock(&NETWORK_LINUX.resultsMutex);
/* Fill the slot (safe: only we have access while used=true, resultReady=false). */
stringCopy(item->url, url, NETWORK_HTTP_URL_MAX - 1);
item->method = method;
if(bodyOrNull != NULL) {
assertStrLenMax(bodyOrNull, NETWORK_HTTP_BODY_MAX, "Body exceeds maximum length");
stringCopy(item->body, bodyOrNull, NETWORK_HTTP_BODY_MAX - 1);
item->hasBody = true;
}
uint32_t hdrCount = headerCount;
if(hdrCount > NETWORK_HTTP_HEADER_MAX) hdrCount = NETWORK_HTTP_HEADER_MAX;
item->headerCount = hdrCount;
for(uint32_t i = 0; i < hdrCount; i++) {
assertStrLenMax(headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX, "Header key exceeds maximum length");
assertStrLenMax(headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX, "Header value exceeds maximum length");
stringCopy(item->headerKeys[i], headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX - 1);
stringCopy(item->headerVals[i], headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX - 1);
}
item->callback = callback;
item->errorCallback = errorCallback;
item->user = user;
/* Kick off the background thread. */
threadInit(&item->thread, networkLinuxHTTPThread);
item->thread.data = item;
threadStartRequest(&item->thread);
}
+5 -76
View File
@@ -6,46 +6,14 @@
*/ */
#pragma once #pragma once
#include "dusk.h" #include "error/error.h"
#include "network/networkhttprequest.h" #include <curl/curl.h>
#include "thread/thread.h" #include <ifaddrs.h>
#include <net/if.h>
#define NETWORK_HTTP_PENDING_MAX 16
#define NETWORK_HTTP_URL_MAX 512
#define NETWORK_HTTP_BODY_MAX 8192
#define NETWORK_HTTP_RESPONSE_MAX 65536
#define NETWORK_HTTP_HEADER_MAX 16
#define NETWORK_HTTP_HEADER_KEY_MAX 64
#define NETWORK_HTTP_HEADER_VAL_MAX 256
#define NETWORK_ERROR_MESSAGE_MAX 256
typedef struct { typedef struct {
bool_t used; void *nothing;
volatile bool_t resultReady;
bool_t isError;
char_t url[NETWORK_HTTP_URL_MAX];
networkhttprequestmethod_t method;
char_t body[NETWORK_HTTP_BODY_MAX];
bool_t hasBody;
char_t headerKeys[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_KEY_MAX];
char_t headerVals[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_VAL_MAX];
uint32_t headerCount;
networkhttpcallback_t callback;
networkhttperrorcallback_t errorCallback;
void *user;
uint16_t status;
char_t responseBody[NETWORK_HTTP_RESPONSE_MAX];
char_t errorMessage[NETWORK_ERROR_MESSAGE_MAX];
thread_t thread;
} networkhttppendingitem_t;
typedef struct {
networkhttppendingitem_t requests[NETWORK_HTTP_PENDING_MAX];
threadmutex_t resultsMutex;
} networklinux_t; } networklinux_t;
/** /**
@@ -71,48 +39,9 @@ errorret_t networkLinuxUpdate();
*/ */
bool_t networkLinuxIsConnected(); bool_t networkLinuxIsConnected();
/**
* No-op — Linux connection is managed by the OS. Calls onConnected immediately.
*
* @param onConnected Callback invoked immediately.
* @param onFailed Unused.
* @param user Passed to onConnected.
*/
void networkLinuxRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void *user
);
/** /**
* Disposes the network manager. * Disposes the network manager.
* *
* @return Any error that occurs. * @return Any error that occurs.
*/ */
errorret_t networkLinuxDispose(); errorret_t networkLinuxDispose();
/**
* Submits an asynchronous HTTP request. The callback will be invoked on the
* main thread when next available.
*
* @param url URL to request.
* @param method Method to use for the request.
* @param bodyOrNull If POST or PUT, custom body string, can be NULL.
* @param headers Array of key-value headers.
* @param headerCount Count of headers, can be anything if headers is NULL.
* @param user Callback pointer received to the callback.
* @param callback The callback to invoke when the request completes.
* @param errorCallback The callback to invoke if the request fails.
* Note, this doesn't count Non-200 status codes, just
* network errors.
*/
void networkLinuxHTTPRequest(
const char_t *url,
const networkhttprequestmethod_t method,
const char_t *bodyOrNull,
const networkhttpheader_t *headers,
const uint32_t headerCount,
void *user,
networkhttpcallback_t callback,
networkhttperrorcallback_t errorCallback
);
+2 -2
View File
@@ -12,5 +12,5 @@
#define networkPlatformUpdate networkLinuxUpdate #define networkPlatformUpdate networkLinuxUpdate
#define networkPlatformDispose networkLinuxDispose #define networkPlatformDispose networkLinuxDispose
#define networkPlatformIsConnected networkLinuxIsConnected #define networkPlatformIsConnected networkLinuxIsConnected
#define networkPlatformRequestConnection networkLinuxRequestConnection
#define networkPlatformHTTPRequest networkLinuxHTTPRequest typedef networklinux_t networkplatform_t;
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
systemlinux.c
)
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "systemlinux.h"
systemdialogtype_t systemGetActiveDialogTypeLinux() {
return SYSTEM_DIALOG_TYPE_NONE;
}
+16
View File
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/system.h"
/**
* Currently just returns SYSTEM_DIALOG_TYPE_NONE.
*
* @return Currently open system dialog type.
*/
systemdialogtype_t systemGetActiveDialogTypeLinux();
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/systemlinux.h"
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeLinux
+1
View File
@@ -19,3 +19,4 @@ add_subdirectory(asset)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(network) add_subdirectory(network)
add_subdirectory(system)
+7 -7
View File
@@ -8,10 +8,10 @@
#pragma once #pragma once
#include "networkpsp.h" #include "networkpsp.h"
#define networkPlatformInit networkPspInit #define networkPlatformInit networkPSPInit
#define networkPlatformUpdate networkPspUpdate #define networkPlatformUpdate networkPSPUpdate
#define networkPlatformDispose networkPspDispose #define networkPlatformDispose networkPSPDispose
#define networkPlatformIsConnected networkPspIsConnected #define networkPlatformIsConnected networkPSPIsConnected
#define networkPlatformRequestConnection networkPspRequestConnection #define networkPlatformRequestConnection networkPSPRequestConnection
#define networkPlatformHTTPRequest networkPspHTTPRequest
#define networkPlatformIsConnectionModalOpen networkPspIsConnectionModalOpen typedef networkpsp_t networkplatform_t;
+156 -336
View File
@@ -5,390 +5,210 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#include "networkpsp.h" #include "network/network.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/string.h" #include "util/string.h"
#include "assert/assert.h" #include "assert/assert.h"
#include <psphttp.h> #include "display/displaysdl2.h"
#include <pspnet.h>
#include <pspnet_inet.h>
#include <pspnet_apctl.h>
static networkpsp_t NETWORK_PSP; #include <pspsdk.h>
/* ---- HTTP thread ---- */
static int networkPspMapMethod(const networkhttprequestmethod_t method) {
switch(method) {
case NETWORK_HTTP_REQUEST_METHOD_POST: return PSP_HTTP_METHOD_POST;
case NETWORK_HTTP_REQUEST_METHOD_HEAD: return PSP_HTTP_METHOD_HEAD;
case NETWORK_HTTP_REQUEST_METHOD_GET:
default:
return PSP_HTTP_METHOD_GET;
}
}
static void networkPspHTTPThread(thread_t *thread) {
assertNotNull(thread, "Thread must not be NULL");
assertNotNull(thread->data, "Thread data must not be NULL");
networkhttppendingitem_t *item = (networkhttppendingitem_t *)thread->data;
int templateId = -1;
int connId = -1;
int reqId = -1;
templateId = sceHttpCreateTemplate(NETWORK_PSP_AGENT, PSP_HTTP_VERSION_1_1, 1);
if(templateId < 0) {
stringCopy(item->errorMessage, "sceHttpCreateTemplate failed",
NETWORK_ERROR_MESSAGE_MAX - 1);
item->isError = true;
goto done;
}
connId = sceHttpCreateConnectionWithURL(templateId, item->url, 0);
if(connId < 0) {
stringCopy(item->errorMessage, "sceHttpCreateConnectionWithURL failed",
NETWORK_ERROR_MESSAGE_MAX - 1);
item->isError = true;
goto done;
}
unsigned int contentLength = item->hasBody ? (unsigned int)strlen(item->body) : 0;
reqId = sceHttpCreateRequestWithURL(
connId, networkPspMapMethod(item->method), item->url,
(SceULong64)contentLength
);
if(reqId < 0) {
stringCopy(item->errorMessage, "sceHttpCreateRequestWithURL failed",
NETWORK_ERROR_MESSAGE_MAX - 1);
item->isError = true;
goto done;
}
for(uint32_t i = 0; i < item->headerCount; i++) {
sceHttpAddExtraHeader(reqId, item->headerKeys[i], item->headerVals[i], 0);
}
errorret_t networkPSPInit() {
// Requests the PSP to load the network modules.
int ret; int ret;
if(item->hasBody) {
ret = sceHttpSendRequest(reqId, item->body, contentLength);
} else {
ret = sceHttpSendRequest(reqId, NULL, 0);
}
if(ret < 0) {
stringCopy(item->errorMessage, "sceHttpSendRequest failed",
NETWORK_ERROR_MESSAGE_MAX - 1);
item->isError = true;
goto done;
}
int statusCode = 0;
ret = sceHttpGetStatusCode(reqId, &statusCode);
if(ret < 0) {
stringCopy(item->errorMessage, "sceHttpGetStatusCode failed",
NETWORK_ERROR_MESSAGE_MAX - 1);
item->isError = true;
goto done;
}
item->status = (uint16_t)statusCode;
/* Read response body in chunks into the fixed buffer. */
size_t written = 0;
char_t buf[512];
while(1) {
int read = sceHttpReadData(reqId, buf, sizeof(buf));
if(read <= 0) break;
size_t space = NETWORK_HTTP_RESPONSE_MAX - 1 - written;
size_t copy = (size_t)read;
if(copy > space) copy = space;
if(copy > 0) {
memcpy(item->responseBody + written, buf, copy);
written += copy;
item->responseBody[written] = '\0';
}
if(space == 0) break;
}
item->isError = false;
done:
if(reqId >= 0) sceHttpDeleteRequest(reqId);
if(connId >= 0) sceHttpDeleteConnection(connId);
if(templateId >= 0) sceHttpDeleteTemplate(templateId);
threadMutexLock(&NETWORK_PSP.resultsMutex);
item->resultReady = true;
threadMutexUnlock(&NETWORK_PSP.resultsMutex);
}
/* ---- Dialog state machine (pumped every frame) ---- */
static void networkPspPumpDialog() {
switch(sceUtilityNetconfGetStatus()) {
case PSP_UTILITY_DIALOG_VISIBLE:
sceUtilityNetconfUpdate(1);
break;
case PSP_UTILITY_DIALOG_QUIT:
sceUtilityNetconfShutdownStart();
break;
case PSP_UTILITY_DIALOG_FINISHED: {
int apState = 0;
sceNetApctlGetState(&apState);
if(apState == PSP_NET_APCTL_STATE_GOT_IP) {
sceUtilityLoadNetModule(PSP_NET_MODULE_HTTP);
sceUtilityLoadNetModule(PSP_NET_MODULE_SSL);
sceHttpInit(NETWORK_PSP_HTTP_HEAP_SIZE);
NETWORK_PSP.state = NETWORK_PSP_STATE_CONNECTED;
if(NETWORK_PSP.onConnected) NETWORK_PSP.onConnected(NETWORK_PSP.connectionUser);
} else {
NETWORK_PSP.state = NETWORK_PSP_STATE_FAILED;
if(NETWORK_PSP.onFailed) {
errorstate_t errState;
errState.code = ERROR_NOT_OK;
errState.message = (char_t *)"WiFi connection failed or cancelled";
errState.lines = (char_t *)"";
errorret_t err;
err.code = ERROR_NOT_OK;
err.state = &errState;
NETWORK_PSP.onFailed(err, NETWORK_PSP.connectionUser);
}
}
break;
}
default:
break;
}
}
/* ---- Public API ---- */
errorret_t networkPspInit() {
memoryZero(&NETWORK_PSP, sizeof(networkpsp_t));
threadMutexInit(&NETWORK_PSP.resultsMutex);
NETWORK_PSP.state = NETWORK_PSP_STATE_DISCONNECTED;
/* Load base net modules (FW 2.00+ style). HTTP/SSL loaded after connection. */
int ret;
ret = sceUtilityLoadNetModule(PSP_NET_MODULE_COMMON); ret = sceUtilityLoadNetModule(PSP_NET_MODULE_COMMON);
if(ret < 0) errorThrow("sceUtilityLoadNetModule(COMMON) failed: 0x%08X", ret); if(ret < 0) errorThrow("Failed to init NET COMMON: 0x%08X", ret);
ret = sceUtilityLoadNetModule(PSP_NET_MODULE_INET); ret = sceUtilityLoadNetModule(PSP_NET_MODULE_INET);
if(ret < 0) errorThrow("sceUtilityLoadNetModule(INET) failed: 0x%08X", ret); if(ret < 0) errorThrow("Failed to init NET INET: 0x%08X", ret);
/* Init the network stack. */ // ret = sceUtilityLoadNetModule(PSP_NET_MODULE_PARSEURI);
ret = sceNetInit(0x20000, 0x20, 0x1000, 0x20, 0x1000); // if(ret < 0) errorThrow("Failed to init NET PARSEURI: 0x%08X", ret);
// ret = sceUtilityLoadNetModule(PSP_NET_MODULE_PARSEHTTP);
// if(ret < 0) errorThrow("Failed to init NET PARSEHTTP: 0x%08X", ret);
// ret = sceUtilityLoadNetModule(PSP_NET_MODULE_SSL);
// if(ret < 0) errorThrow("Failed to init NET SSL: 0x%08X", ret);
// ret = sceUtilityLoadNetModule(PSP_NET_MODULE_HTTP);
// if(ret < 0) errorThrow("Failed to init NET HTTP: 0x%08X", ret);
// Init the PSP network stack.
ret = sceNetInit(0x20000, 0x20, 4096, 0x20, 4096);
if(ret < 0) errorThrow("sceNetInit failed: 0x%08X", ret); if(ret < 0) errorThrow("sceNetInit failed: 0x%08X", ret);
ret = sceNetInetInit(); ret = sceNetInetInit();
if(ret < 0) errorThrow("sceNetInetInit failed: 0x%08X", ret); if(ret < 0) errorThrow("sceNetInetInit failed: 0x%08X", ret);
ret = sceNetResolverInit();
if(ret < 0) errorThrow("sceNetResolverInit failed: 0x%08X", ret);
ret = sceNetApctlInit(0x1800, 0x30); ret = sceNetApctlInit(0x1800, 0x30);
if(ret < 0) errorThrow("sceNetApctlInit failed: 0x%08X", ret); if(ret < 0) errorThrow("sceNetApctlInit failed: 0x%08X", ret);
// ret = sceSslInit(0x28000);
// if(ret < 0) errorThrow("sceSslInit failed: 0x%08X", ret);
// ret = sceHttpInit(0x25800);
// if(ret < 0) errorThrow("sceHttpInit failed: 0x%08X", ret);
// ret = sceHttpsInit(0, 0, 0, 0);
// if(ret < 0) errorThrow("sceHttpsInit failed: 0x%08X", ret);
// ret = sceHttpsLoadDefaultCert(0, 0);
// if(ret < 0) errorThrow("sceHttpsLoadDefaultCert failed: 0x%08X", ret);
// ret = sceHttpLoadSystemCookie();
// if(ret < 0) errorThrow("sceHttpLoadSystemCookie failed: 0x%08X", ret);
// All good.
errorOk(); errorOk();
} }
bool_t networkPspIsConnected() { errorret_t networkPSPUpdate() {
return NETWORK_PSP.state == NETWORK_PSP_STATE_CONNECTED; int ret;
} if(
NETWORK.state == NETWORK_STATE_CONNECTING ||
void networkPspRequestConnection( NETWORK.state == NETWORK_STATE_DISCONNECTING
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void *user
) { ) {
assertNotNull(onConnected, "onConnected must not be NULL"); switch(sceUtilityNetconfGetStatus()) {
assertNotNull(onFailed, "onFailed must not be NULL"); case PSP_UTILITY_DIALOG_INIT:
break;
NETWORK_PSP.onConnected = onConnected; case PSP_UTILITY_DIALOG_NONE:
NETWORK_PSP.onFailed = onFailed; printf("Diag none?\n");
NETWORK_PSP.connectionUser = user; NETWORK.state = NETWORK_STATE_DISCONNECTED;
NETWORK_PSP.state = NETWORK_PSP_STATE_DIALOG; errorThrow("PSP Netconf dialog disappeared without result");
break;
/* Configure and launch the netconf WiFi dialog. case PSP_UTILITY_DIALOG_VISIBLE:
* The dialog is pumped each frame in networkPspUpdate; it must not block. */ // 1 is mandatory?
memoryZero(&NETWORK_PSP.dialogAdhoc, sizeof(NETWORK_PSP.dialogAdhoc)); ret = sceUtilityNetconfUpdate(1);
memoryZero(&NETWORK_PSP.dialogData, sizeof(NETWORK_PSP.dialogData)); if(ret != 0) {
NETWORK_PSP.dialogData.base.size = sizeof(pspUtilityNetconfData); errorThrow("sceUtilityNetconfUpdate failed: 0x%08X", ret);
NETWORK_PSP.dialogData.base.language = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
NETWORK_PSP.dialogData.base.buttonSwap = PSP_UTILITY_ACCEPT_CROSS;
NETWORK_PSP.dialogData.base.graphicsThread = 17;
NETWORK_PSP.dialogData.base.accessThread = 19;
NETWORK_PSP.dialogData.base.fontThread = 18;
NETWORK_PSP.dialogData.base.soundThread = 16;
NETWORK_PSP.dialogData.action = PSP_NETCONF_ACTION_CONNECTAP;
NETWORK_PSP.dialogData.adhocparam = &NETWORK_PSP.dialogAdhoc;
int ret = sceUtilityNetconfInitStart(&NETWORK_PSP.dialogData);
if(ret < 0) {
NETWORK_PSP.state = NETWORK_PSP_STATE_FAILED;
errorstate_t errState;
errState.code = ERROR_NOT_OK;
errState.message = (char_t *)"sceUtilityNetconfInitStart failed";
errState.lines = (char_t *)"";
errorret_t err;
err.code = ERROR_NOT_OK;
err.state = &errState;
onFailed(err, user);
} }
break;
case PSP_UTILITY_DIALOG_QUIT:
ret = sceUtilityNetconfShutdownStart();
if(ret != 0) {
errorThrow("sceUtilityNetconfShutdownStart failed: 0x%08X", ret);
} }
break;
errorret_t networkPspUpdate() { case PSP_UTILITY_DIALOG_FINISHED:
if(NETWORK_PSP.state == NETWORK_PSP_STATE_DIALOG) { // Did we connect?
networkPspPumpDialog(); int apState = 0;
errorOk(); sceNetApctlGetState(&apState);
}
if(NETWORK_PSP.state == NETWORK_PSP_STATE_FAILED) { if(apState == PSP_NET_APCTL_STATE_GOT_IP) {
errorOk(); NETWORK.state = NETWORK_STATE_CONNECTED;
} assertNotNull(
NETWORK.platform.onConnected,
/* Drain completed HTTP requests on the main thread. */ "Network platform onConnected callback should be set."
for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) { );
threadMutexLock(&NETWORK_PSP.resultsMutex); NETWORK.platform.onConnected(NETWORK.platform.onConnectedUser);
networkhttppendingitem_t *item = &NETWORK_PSP.requests[i];
if(!item->used || !item->resultReady) {
threadMutexUnlock(&NETWORK_PSP.resultsMutex);
continue;
}
bool_t isError = item->isError;
uint16_t status = item->status;
networkhttpcallback_t cb = item->callback;
networkhttperrorcallback_t errCb = item->errorCallback;
void *user = item->user;
const char_t *responseBody = item->responseBody;
const char_t *errorMessage = item->errorMessage;
threadMutexUnlock(&NETWORK_PSP.resultsMutex);
if(isError) {
errorstate_t errState;
errState.code = ERROR_NOT_OK;
errState.message = (char_t *)errorMessage;
errState.lines = (char_t *)"";
errorret_t err;
err.code = ERROR_NOT_OK;
err.state = &errState;
errCb(err, user);
} else { } else {
cb(status, responseBody, NULL, 0, user); printf("Offline.\n");
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"
);
NETWORK.platform.onFailed(error, NETWORK.platform.onConnectedUser);
}
break;
default:
errorThrow("Unknown PSP Netconf dialog status: %d", sceUtilityNetconfGetStatus());
}
} }
threadMutexLock(&NETWORK_PSP.resultsMutex);
item->used = false;
item->resultReady = false;
threadMutexUnlock(&NETWORK_PSP.resultsMutex);
}
errorOk(); errorOk();
} }
errorret_t networkPspDispose() { errorret_t networkPSPDispose() {
if(NETWORK_PSP.state == NETWORK_PSP_STATE_CONNECTED) {
sceHttpEnd();
sceUtilityUnloadNetModule(PSP_NET_MODULE_SSL);
sceUtilityUnloadNetModule(PSP_NET_MODULE_HTTP);
}
sceNetApctlTerm(); sceNetApctlTerm();
sceNetResolverTerm();
sceNetInetTerm(); sceNetInetTerm();
sceNetTerm(); sceNetTerm();
sceUtilityUnloadNetModule(PSP_NET_MODULE_HTTP);
sceUtilityUnloadNetModule(PSP_NET_MODULE_INET); sceUtilityUnloadNetModule(PSP_NET_MODULE_INET);
sceUtilityUnloadNetModule(PSP_NET_MODULE_COMMON); sceUtilityUnloadNetModule(PSP_NET_MODULE_COMMON);
threadMutexDispose(&NETWORK_PSP.resultsMutex);
errorOk(); errorOk();
} }
void networkPspHTTPRequest( bool_t networkPSPIsConnected() {
const char_t *url, return NETWORK.state == NETWORK_STATE_CONNECTED;
const networkhttprequestmethod_t method, }
const char_t *bodyOrNull,
const networkhttpheader_t *headers, void networkPSPRequestConnection(
const uint32_t headerCount, void (*onConnected)(void *user),
void *user, void (*onFailed)(errorret_t error, void *user),
networkhttpcallback_t callback, void (*onDisconnect)(errorret_t error, void *user),
networkhttperrorcallback_t errorCallback void *user
) { ) {
assertStrLenMin(url, 1, "URL must be non-empty"); assertTrue(
assertStrLenMax(url, NETWORK_HTTP_URL_MAX, "URL exceeds maximum length"); NETWORK.state == NETWORK_STATE_CONNECTING,
assertNotNull(callback, "Callback must be non-NULL"); "Network host should be in a connecting state."
assertNotNull(errorCallback, "Error callback must be non-NULL"); );
assertTrue(headerCount == 0 || headers != NULL, "Headers must not be NULL when headerCount > 0");
if(NETWORK_PSP.state != NETWORK_PSP_STATE_CONNECTED) { NETWORK.platform.onConnected = onConnected;
errorstate_t errState; NETWORK.platform.onFailed = onFailed;
errState.code = ERROR_NOT_OK; NETWORK.platform.onConnectedUser = user;
errState.message = (char_t *)"Network not connected";
errState.lines = (char_t *)""; memoryZero(&NETWORK.platform.dialogData, sizeof(NETWORK.platform.dialogData));
errorret_t err; memoryZero(&NETWORK.platform.dialogAdhoc, sizeof(NETWORK.platform.dialogAdhoc));
err.code = ERROR_NOT_OK;
err.state = &errState; // This is all related to getting the PSP online, refer to;
errorCallback(err, user); // https://github.com/joel16/CMFileManager-PSP/blob/00dab16c64cd48bf6452fc274a3b898d77c39a8d/app/source/net.cpp#L97
return; // since I follow this implementation closely.
NETWORK.platform.dialogData.base.size = sizeof(pspUtilityNetconfData);
NETWORK.platform.dialogData.base.language = systemPSPGetLanguage();
NETWORK.platform.dialogData.base.buttonSwap = systemPSPGetCrossButtonSetting();
NETWORK.platform.dialogData.base.graphicsThread = 17;
NETWORK.platform.dialogData.base.accessThread = 19;
NETWORK.platform.dialogData.base.fontThread = 18;
NETWORK.platform.dialogData.base.soundThread = 16;
NETWORK.platform.dialogData.action = PSP_NETCONF_ACTION_CONNECTAP;
NETWORK.platform.dialogData.adhocparam = (
&NETWORK.platform.dialogAdhoc
);
int ret = sceUtilityNetconfInitStart(&NETWORK.platform.dialogData);
if(ret < 0) {
assertUnreachable("Failed to init netconf");
}
// At this point, PSP is in control.
} }
threadMutexLock(&NETWORK_PSP.resultsMutex); void networkPSPRequestDisconection(
void (*onComplete)(void *user),
void *user
) {
assertTrue(
NETWORK.state == NETWORK_STATE_DISCONNECTING,
"Network host should be in a disconnecting state."
);
networkhttppendingitem_t *item = NULL; NETWORK.platform.onComplete = onComplete;
for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) { NETWORK.platform.onCompleteUser = user;
if(!NETWORK_PSP.requests[i].used) {
item = &NETWORK_PSP.requests[i]; int ret = sceUtilityNetconfShutdownStart();
break; if(ret < 0) {
assertUnreachable("Failed to start netconf shutdown");
} }
} }
if(!item) {
threadMutexUnlock(&NETWORK_PSP.resultsMutex);
errorstate_t errState;
errState.code = ERROR_NOT_OK;
errState.message = (char_t *)"No free HTTP request slots";
errState.lines = (char_t *)"";
errorret_t err;
err.code = ERROR_NOT_OK;
err.state = &errState;
errorCallback(err, user);
return;
}
memoryZero(item, sizeof(networkhttppendingitem_t));
item->used = true;
threadMutexUnlock(&NETWORK_PSP.resultsMutex);
stringCopy(item->url, url, NETWORK_HTTP_URL_MAX - 1);
item->method = method;
if(bodyOrNull != NULL) {
assertStrLenMax(bodyOrNull, NETWORK_HTTP_BODY_MAX, "Body exceeds maximum length");
stringCopy(item->body, bodyOrNull, NETWORK_HTTP_BODY_MAX - 1);
item->hasBody = true;
}
uint32_t hdrCount = headerCount;
if(hdrCount > NETWORK_HTTP_HEADER_MAX) hdrCount = NETWORK_HTTP_HEADER_MAX;
item->headerCount = hdrCount;
for(uint32_t i = 0; i < hdrCount; i++) {
assertStrLenMax(headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX, "Header key exceeds maximum length");
assertStrLenMax(headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX, "Header value exceeds maximum length");
stringCopy(item->headerKeys[i], headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX - 1);
stringCopy(item->headerVals[i], headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX - 1);
}
item->callback = callback;
item->errorCallback = errorCallback;
item->user = user;
threadInit(&item->thread, networkPspHTTPThread);
item->thread.data = item;
threadStartRequest(&item->thread);
}
bool_t networkPspIsConnectionModalOpen() {
return sceUtilityNetconfGetStatus() == PSP_UTILITY_DIALOG_VISIBLE;
}
+62 -63
View File
@@ -6,83 +6,82 @@
*/ */
#pragma once #pragma once
#include "dusk.h" #include "error/error.h"
#include "network/networkhttprequest.h" #include "system/systempsp.h"
#include "thread/thread.h" #include <psphttp.h>
#include <psputility.h> #include <pspnet.h>
#include <pspnet_inet.h>
#include <pspnet_apctl.h>
#include <pspssl.h>
#include <pspnet_resolver.h>
#include <psphttp.h>
#define NETWORK_HTTP_PENDING_MAX 4 // #define NETWORK_HTTP_PENDING_MAX 4
#define NETWORK_HTTP_URL_MAX 512 // #define NETWORK_HTTP_URL_MAX 512
#define NETWORK_HTTP_BODY_MAX 2048 // #define NETWORK_HTTP_BODY_MAX 2048
#define NETWORK_HTTP_RESPONSE_MAX 16384 // #define NETWORK_HTTP_RESPONSE_MAX 16384
#define NETWORK_HTTP_HEADER_MAX 8 // #define NETWORK_HTTP_HEADER_MAX 8
#define NETWORK_HTTP_HEADER_KEY_MAX 64 // #define NETWORK_HTTP_HEADER_KEY_MAX 64
#define NETWORK_HTTP_HEADER_VAL_MAX 256 // #define NETWORK_HTTP_HEADER_VAL_MAX 256
#define NETWORK_ERROR_MESSAGE_MAX 256 // #define NETWORK_ERROR_MESSAGE_MAX 256
#define NETWORK_PSP_AGENT "DuskEngine/1.0" // #define NETWORK_PSP_AGENT "DuskEngine/1.0"
#define NETWORK_PSP_HTTP_HEAP_SIZE 0x25800
typedef enum {
NETWORK_PSP_STATE_DISCONNECTED, /* Init done, no connection requested yet */
NETWORK_PSP_STATE_DIALOG, /* WiFi netconf dialog is active */
NETWORK_PSP_STATE_CONNECTED, /* AP connected, HTTP ready */
NETWORK_PSP_STATE_FAILED, /* Connection attempt failed or cancelled */
} networkpspstate_t;
typedef struct { typedef struct {
bool_t used;
volatile bool_t resultReady;
bool_t isError;
char_t url[NETWORK_HTTP_URL_MAX];
networkhttprequestmethod_t method;
char_t body[NETWORK_HTTP_BODY_MAX];
bool_t hasBody;
char_t headerKeys[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_KEY_MAX];
char_t headerVals[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_VAL_MAX];
uint32_t headerCount;
networkhttpcallback_t callback;
networkhttperrorcallback_t errorCallback;
void *user;
uint16_t status;
char_t responseBody[NETWORK_HTTP_RESPONSE_MAX];
char_t errorMessage[NETWORK_ERROR_MESSAGE_MAX];
thread_t thread;
} networkhttppendingitem_t;
typedef struct {
networkpspstate_t state;
pspUtilityNetconfData dialogData; pspUtilityNetconfData dialogData;
struct pspUtilityNetconfAdhoc dialogAdhoc; struct pspUtilityNetconfAdhoc dialogAdhoc;
// Used during establishing connection
void *onConnectedUser;
void (*onConnected)(void *user); void (*onConnected)(void *user);
void (*onFailed)(errorret_t error, void *user); void (*onFailed)(errorret_t error, void *user);
void *connectionUser;
networkhttppendingitem_t requests[NETWORK_HTTP_PENDING_MAX]; // Used during disconnecting
threadmutex_t resultsMutex; void *onCompelteUser;
void (*onComplete)(void *user);
} networkpsp_t; } networkpsp_t;
errorret_t networkPspInit(); /**
* Initializes the PSP Network manager. This will NOT do network connecting,
* only prep it for being able to connect in future.
*
* @return Error state (if any).
*/
errorret_t networkPSPInit();
errorret_t networkPspUpdate(); /**
* Called each frame for handling PSP requests, basically this is where all
* communication between the HTTP thread and the main thread happens.
*
* @return Error state (if any).
*/
errorret_t networkPSPUpdate();
errorret_t networkPspDispose(); /**
* Disposes the PSP Network manager, this will clean all resources and, if the
* network is connected, it will disconnect it safely.
*
* @return Error state (if any).
*/
errorret_t networkPSPDispose();
bool_t networkPspIsConnected(); /**
* Checks if the PSP network is connected.
*
* @return True if the PSP is connected to a network, false otherwise.
*/
bool_t networkPSPIsConnected();
void networkPspRequestConnection( /**
* Requests the PSP to connect to a network (Shows the Wi-Fi connected).
*
* @param onConnected Callback connected successfully.
* @param onFailed Callback if the connection failed.
* @param onDisconnect Callback when connection is lost.
* @param user User data to pass to the callbacks.
*/
void networkPSPRequestConnection(
void (*onConnected)(void *user), void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user), void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user void *user
); );
void networkPspHTTPRequest(
const char_t *url,
const networkhttprequestmethod_t method,
const char_t *bodyOrNull,
const networkhttpheader_t *headers,
const uint32_t headerCount,
void *user,
networkhttpcallback_t callback,
networkhttperrorcallback_t errorCallback
);
bool_t networkPspIsConnectionModalOpen();
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
systempsp.c
)
+11
View File
@@ -0,0 +1,11 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/systempsp.h"
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypePSP
+29
View File
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "systempsp.h"
systemdialogtype_t systemGetActiveDialogTypePSP() {
return SYSTEM_DIALOG_TYPE_NONE;
}
int systemPSPGetLanguage() {
int ret;
sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_LANGUAGE, &ret);
return ret;
}
int systemPSPGetCrossButtonSetting() {
int ret;
// See: https://pspdev.github.io/pspsdk/psputility__sysparam_8h.html#ab588fd5a14adc025f065e09325ffe729
sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_UNKNOWN, &ret);
return (
ret == 1 ? PSP_UTILITY_ACCEPT_CROSS : PSP_UTILITY_ACCEPT_CIRCLE
);
}
+33
View File
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/system.h"
#include <psputility.h>
/**
* Returns which PSP system dialog is currently open (if any).
*
* @return Currently open system dialog type.
*/
systemdialogtype_t systemGetActiveDialogTypePSP();
/**
* Returns the PSP_SYSTEMPARAM language for the current system.
*
* @return PSP_SYSTEMPARAM language value.
*/
int systemPSPGetLanguage();
/**
* Returns the user's setting for the PSP "Cross Button" configuration, which
* determines whether the "Cross" or "Circle" button is used for "Accept"
* actions in system dialogs.
*
* @return PSP_UTILITY_ACCEPT_CROSS or PSP_UTILITY_ACCEPT_CIRCLE.
*/
int systemPSPGetCrossButtonSetting();