Merge pull request #1471 from Extrems/master

Migrate GameCube port to libogc2
This commit is contained in:
UnknownShadow200 2025-12-12 19:47:17 +11:00 committed by GitHub
commit 1cdc87082f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 154 additions and 55 deletions

52
.github/workflows/build_gc.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Build latest (GameCube)
# trigger via either push to selected branches or on manual run
on:
push:
branches:
- main
- master
workflow_dispatch:
concurrency:
group: ${{ github.ref }}-gc
cancel-in-progress: true
jobs:
build:
timeout-minutes: 10
runs-on: ubuntu-latest
container:
image: ghcr.io/extremscorner/libogc2:latest
steps:
- uses: actions/checkout@v4
- name: Compile GameCube build
id: compile
run: |
make gamecube
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
SOURCE_FILE: 'ClassiCube-gc.dol'
DEST_NAME: 'ClassiCube-gc.dol'
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
SOURCE_FILE: 'ClassiCube-gc.elf'
DEST_NAME: 'ClassiCube-gc.elf'
- uses: ./.github/actions/notify_success
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
DESTINATION_URL: '${{ secrets.NOTIFY_URL }}'
WORKFLOW_NAME: 'gc'
- uses: ./.github/actions/notify_failure
if: failure()
with:
NOTIFY_MESSAGE: 'Failed to produce GameCube build'
WEBHOOK_URL: '${{ secrets.WEBHOOK_URL }}'

View File

@ -1,4 +1,4 @@
name: Build latest (Wii/GameCube)
name: Build latest (Wii)
# trigger via either push to selected branches or on manual run
on:
push:
@ -8,7 +8,7 @@ on:
workflow_dispatch:
concurrency:
group: ${{ github.ref }}-wiigc
group: ${{ github.ref }}-wii
cancel-in-progress: true
jobs:
@ -19,11 +19,10 @@ jobs:
image: devkitpro/devkitppc:latest
steps:
- uses: actions/checkout@v4
- name: Compile Wii and GameCube build
- name: Compile Wii build
id: compile
run: |
make wii
make gamecube
- name: Create Wii homebrew
run: |
@ -39,24 +38,12 @@ jobs:
SOURCE_FILE: 'ClassiCube-wii.dol'
DEST_NAME: 'ClassiCube-wii.dol'
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
SOURCE_FILE: 'ClassiCube-gc.dol'
DEST_NAME: 'ClassiCube-gc.dol'
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
SOURCE_FILE: 'ClassiCube-wii.elf'
DEST_NAME: 'ClassiCube-wii.elf'
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
SOURCE_FILE: 'ClassiCube-gc.elf'
DEST_NAME: 'ClassiCube-gc.elf'
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
@ -68,11 +55,11 @@ jobs:
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
DESTINATION_URL: '${{ secrets.NOTIFY_URL }}'
WORKFLOW_NAME: 'wiigc'
WORKFLOW_NAME: 'wii'
- uses: ./.github/actions/notify_failure
if: failure()
with:
NOTIFY_MESSAGE: 'Failed to produce Wii/Gamecube build'
NOTIFY_MESSAGE: 'Failed to produce Wii build'
WEBHOOK_URL: '${{ secrets.WEBHOOK_URL }}'

View File

@ -32,10 +32,10 @@ DEPFILES := $(OBJS:%.o=%.d)
# Code generation
#---------------------------------------------------------------------------------
MACHDEP = -DGEKKO -mogc -mcpu=750 -meabi -mhard-float
CFLAGS = -g -O2 -Wall $(MACHDEP) -I$(DEVKITPRO)/libogc/include -DPLAT_GAMECUBE
MACHDEP = -DGEKKO -mogc -mcpu=750 -meabi -msdata=sysv -mhard-float
CFLAGS = -g -O2 -Wall $(MACHDEP) -I$(DEVKITPRO)/libogc2/gamecube/include -DPLAT_GAMECUBE
LDFLAGS = -g $(MACHDEP) -L$(DEVKITPRO)/libogc/lib/cube
LDFLAGS = -g $(MACHDEP) -L$(DEVKITPRO)/libogc2/gamecube/lib
# Additional libraries to link against
LIBS = -lasnd -lbba -lfat -logc -lm

View File

@ -247,9 +247,9 @@ Run `make wii`. You'll need [libogc](https://github.com/devkitPro/libogc)
#### GameCube
Run `make gamecube`. You'll need [libogc](https://github.com/devkitPro/libogc)
Run `make gamecube`. You'll need [libogc2](https://github.com/extremscorner/libogc2)
**NOTE: It is highly recommended that you install the precompiled devkitpro packages from [here](https://devkitpro.org/wiki/Getting_Started) - you need the `gamecube-dev` group)**
**NOTE: It is highly recommended that you install the precompiled libogc2 packages from [here](https://github.com/extremscorner/pacman-packages#readme) - you need the `gamecube-dev` group)**
#### Nintendo DS/DSi
@ -454,8 +454,9 @@ Further information (e.g. style) for ClassiCube's source code can be found in th
* [libctru](https://github.com/devkitPro/libctru) - Backend for 3DS
* [citro3D](https://github.com/devkitPro/citro3d) - Rendering backend for 3DS
* [Citra](https://github.com/citra-emu/citra) - Emulator used to test 3DS port
* [libogc](https://github.com/devkitPro/libogc) - Backend for Wii and GameCube
* [libfat](https://github.com/devkitPro/libfat) - Filesystem backend for Wii/GC
* [libogc](https://github.com/devkitPro/libogc) - Backend for Wii
* [libogc2](https://github.com/extremscorner/libogc2) - Backend for GameCube
* [libdvm](https://github.com/devkitPro/libdvm) - Filesystem backend for Wii/GC
* [Dolphin](https://github.com/dolphin-emu/dolphin) - Emulator used to test Wii/GC port
* [libdragon](https://github.com/DragonMinded/libdragon) - Backend for Nintendo 64
* [ares](https://github.com/ares-emulator/ares) - Emulator used to test Nintendo 64 port

View File

@ -60,7 +60,7 @@ void MusicCallback(s32 voice) {
cc_result Audio_Init(struct AudioContext* ctx, int buffers) {
ctx->chanID = -1;
ctx->count = buffers;
ctx->volume = 255;
ctx->volume = MAX_VOLUME;
ctx->bufHead = 0;
ctx->makeAvailable = false;
@ -88,7 +88,7 @@ cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate
}
void Audio_SetVolume(struct AudioContext* ctx, int volume) {
ctx->volume = (volume / 100.0f) * 255;
ctx->volume = (volume / 100.0f) * MAX_VOLUME;
}
cc_result Audio_QueueChunk(struct AudioContext* ctx, struct AudioChunk* chunk) {

View File

@ -1,6 +1,7 @@
/*########################################################################################################################*
*------------------------------------------------------Logging/Time-------------------------------------------------------*
*#########################################################################################################################*/
#ifdef HW_RVL
static void LogOverEXI(char* msg, int len) {
u32 cmd = 0x80000000 | (0x800400 << 6); // write flag, UART base address
@ -27,13 +28,16 @@ void Platform_Log(const char* msg, int len) {
LogOverEXI(tmp, len + 1);
}
#define GCWII_EPOCH_ADJUST 946684800ULL // GameCube/Wii time epoch is year 2000, not 1970
#else
void Platform_Log(const char* msg, int len) {
SYS_Report("%.*s\n", len, msg);
}
#endif
TimeMS DateTime_CurrentUTC(void) {
u64 raw = gettime();
u64 secs = ticks_to_secs(raw);
return secs + UNIX_EPOCH_SECONDS + GCWII_EPOCH_ADJUST;
struct timeval cur;
gettimeofday(&cur, NULL);
return (cc_uint64)cur.tv_sec + UNIX_EPOCH_SECONDS;
}
void DateTime_CurrentLocal(struct cc_datetime* t) {
@ -51,7 +55,11 @@ void DateTime_CurrentLocal(struct cc_datetime* t) {
}
cc_uint64 Stopwatch_Measure(void) {
return gettime();
#ifdef HW_RVL
return SYS_Time();
#else
return __SYS_GetSystemTime();
#endif
}
cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
@ -123,12 +131,15 @@ cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCall
DIR* dirPtr = opendir(str.buffer);
if (!dirPtr) return errno;
// POSIX docs: "When the end of the directory is encountered, a null pointer is returned and errno is not changed."
// errno is sometimes leftover from previous calls, so always reset it before readdir gets called
errno = 0;
String_InitArray(path, pathBuffer);
while ((entry = readdir(dirPtr))) {
do {
// POSIX docs: "When the end of the directory is encountered, a null pointer is returned and errno is not changed."
// errno is sometimes leftover from previous calls, so always reset it before readdir gets called
errno = 0;
entry = readdir(dirPtr);
if (!entry) continue;
path.length = 0;
String_Format1(&path, "%s/", dirPath);
@ -143,8 +154,7 @@ cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCall
// TODO: fallback to stat when this fails
callback(&path, obj, is_dir);
errno = 0;
}
} while (entry || errno == EOVERFLOW);
res = errno; // return code from readdir
closedir(dirPtr);
@ -216,13 +226,16 @@ void Thread_Run(void** handle, Thread_StartFunc func, int stackSize, const char*
lwp_t* thread = (lwp_t*)Mem_Alloc(1, sizeof(lwp_t), "thread");
*handle = thread;
int res = LWP_CreateThread(thread, ExecThread, (void*)func, NULL, stackSize, 80);
int res = LWP_CreateThread(thread, ExecThread, (void*)func, NULL, stackSize, 64);
if (res) Process_Abort2(res, "Creating thread");
}
void Thread_Detach(void* handle) {
// TODO: Leaks return value of thread ???
lwp_t* ptr = (lwp_t*)handle;
#ifndef HW_RVL
LWP_DetachThread(*ptr);
#endif
Mem_Free(ptr);
}
@ -259,6 +272,7 @@ void Mutex_Unlock(void* handle) {
if (res) Process_Abort2(res, "Unlocking mutex");
}
#ifdef HW_RVL
// should really use a semaphore with max 1.. too bad no 'TimedWait' though
struct WaitData {
cond_t cond;
@ -320,8 +334,8 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
struct timespec ts;
int res;
ts.tv_sec = milliseconds / 1000;
ts.tv_nsec = milliseconds % 1000;
ts.tv_sec = milliseconds / TB_MSPERSEC;
ts.tv_nsec = (milliseconds % TB_MSPERSEC) * TB_NSPERMS;
Mutex_Lock(&ptr->mutex);
if (!ptr->signalled) {
@ -331,6 +345,45 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
ptr->signalled = false;
Mutex_Unlock(&ptr->mutex);
}
#else
void* Waitable_Create(const char* name) {
sem_t* ptr = (sem_t*)Mem_Alloc(1, sizeof(sem_t), "waitable");
int res = LWP_SemInit(ptr, 0, 1);
if (res) Process_Abort2(res, "Creating waitable");
return ptr;
}
void Waitable_Free(void* handle) {
sem_t* ptr = (sem_t*)handle;
int res = LWP_SemDestroy(*ptr);
if (res) Process_Abort2(res, "Destroying waitable");
Mem_Free(handle);
}
void Waitable_Signal(void* handle) {
sem_t* ptr = (sem_t*)handle;
int res = LWP_SemPost(*ptr);
if (res && res != EOVERFLOW) Process_Abort2(res, "Signalling event");
}
void Waitable_Wait(void* handle) {
sem_t* ptr = (sem_t*)handle;
int res = LWP_SemWait(*ptr);
if (res) Process_Abort2(res, "Event wait");
}
void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
sem_t* ptr = (sem_t*)handle;
struct timespec ts;
int res;
ts.tv_sec = milliseconds / TB_MSPERSEC;
ts.tv_nsec = (milliseconds % TB_MSPERSEC) * TB_NSPERMS;
res = LWP_SemTimedWait(*ptr, &ts);
if (res && res != ETIMEDOUT) Process_Abort2(res, "Event timed wait");
}
#endif
/*########################################################################################################################*
@ -359,11 +412,11 @@ cc_result Socket_Create(cc_socket* s, cc_sockaddr* addr, cc_bool nonblocking) {
struct sockaddr* raw = (struct sockaddr*)addr->data;
if (!net_supported) { *s = -1; return ERR_NO_NETWORKING; }
*s = net_socket(raw->sa_family, SOCK_STREAM, 0);
*s = net_socket(raw->sa_family, SOCK_STREAM, IPPROTO_IP);
if (*s < 0) return *s;
if (nonblocking) {
int blocking_raw = -1; /* non-blocking mode */
int blocking_raw = 1; /* non-blocking mode */
net_ioctl(*s, FIONBIO, &blocking_raw);
}
return 0;

View File

@ -24,10 +24,10 @@
#include <network.h>
#include <ogc/lwp.h>
#include <ogc/mutex.h>
#include <ogc/cond.h>
#include <ogc/lwp_watchdog.h>
#include <ogc/semaphore.h>
#include <ogc/system.h>
#include <ogc/timesupp.h>
#include <fat.h>
#include <ogc/exi.h>
const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */
const cc_result ReturnCode_FileNotFound = ENOENT;
@ -65,10 +65,13 @@ int main(int argc, char** argv) {
static cc_result ParseHost(const char* host, int port, cc_sockaddr* addrs, int* numValidAddrs) {
// DNS resolution not implemented in gamecube libbba
static struct fixed_dns_map {
const cc_string host, ip;
const cc_string host, ip[2];
} mappings[] = {
{ String_FromConst("cdn.classicube.net"), String_FromConst("104.20.90.158") },
{ String_FromConst("www.classicube.net"), String_FromConst("104.20.90.158") }
{ String_FromConst("cdn.classicube.net"), { String_FromConst("172.66.134.165"), String_FromConst("172.66.138.91") }},
{ String_FromConst("static.classicube.net"), { String_FromConst("172.66.134.165"), String_FromConst("172.66.138.91") }},
{ String_FromConst("www.classicube.net"), { String_FromConst("172.66.134.165"), String_FromConst("172.66.138.91") }},
{ String_FromConst("resources.download.minecraft.net"), { String_FromConst("13.107.213.36"), String_FromConst("13.107.246.36") }},
{ String_FromConst("launcher.mojang.com"), { String_FromConst("13.107.213.36"), String_FromConst("13.107.246.36") }}
};
if (!net_supported) return ERR_NO_NETWORKING;
@ -76,8 +79,9 @@ static cc_result ParseHost(const char* host, int port, cc_sockaddr* addrs, int*
{
if (!String_CaselessEqualsConst(&mappings[i].host, host)) continue;
ParseIPv4(&mappings[i].ip, port, &addrs[0]);
*numValidAddrs = 1;
ParseIPv4(&mappings[i].ip[0], port, &addrs[0]);
ParseIPv4(&mappings[i].ip[1], port, &addrs[1]);
*numValidAddrs = 2;
return 0;
}
return ERR_NOT_SUPPORTED;
@ -104,7 +108,7 @@ cc_result Socket_GetLastError(cc_socket s) {
u32 errSize = sizeof(error);
/* https://stackoverflow.com/questions/29479953/so-error-value-after-successful-socket-operation */
net_getsockopt(s, SOL_SOCKET, SO_ERROR, &error, errSize);
net_getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &errSize);
return error;
}
@ -114,7 +118,7 @@ static void InitSockets(void) {
char netmask[16] = {0};
char gateway[16] = {0};
int ret = if_config(localip, netmask, gateway, TRUE, 20);
int ret = if_config(localip, netmask, gateway, true);
if (ret >= 0) {
cc_string str; char buffer[256];
String_InitArray_NT(str, buffer);

View File

@ -28,6 +28,7 @@
#include <ogc/lwp_watchdog.h>
#include <fat.h>
#include <ogc/exi.h>
#include <ogc/system.h>
#include <ogc/wiilaunch.h>
const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */

View File

@ -158,11 +158,12 @@ static void ProcessPAD_Buttons(int port, int mods) {
static void ProcessPADInput(PADStatus* pad, int i, float delta) {
int error = pad->err;
if (error == 0) {
if (error == PAD_ERR_NONE) {
gc_pads[i] = *pad; // new state arrived
} else if (error == PAD_ERR_TRANSFER) {
// usually means still busy transferring state - use last state
} else {
if (error == PAD_ERR_NO_CONTROLLER) PAD_Reset(PAD_CHAN0_BIT >> i);
return; // not connected, still busy, etc
}