diff --git a/.github/workflows/build_gc.yml b/.github/workflows/build_gc.yml new file mode 100644 index 000000000..cb15b242c --- /dev/null +++ b/.github/workflows/build_gc.yml @@ -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 }}' diff --git a/.github/workflows/build_wiigc.yml b/.github/workflows/build_wii.yml similarity index 69% rename from .github/workflows/build_wiigc.yml rename to .github/workflows/build_wii.yml index e65ff0d05..ffd92052a 100644 --- a/.github/workflows/build_wiigc.yml +++ b/.github/workflows/build_wii.yml @@ -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 }}' diff --git a/misc/gc/Makefile b/misc/gc/Makefile index b2f93414d..812ad2ca9 100644 --- a/misc/gc/Makefile +++ b/misc/gc/Makefile @@ -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 diff --git a/readme.md b/readme.md index d3056a38c..231464b35 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/src/gcwii/Audio_GCWii.c b/src/gcwii/Audio_GCWii.c index b3c9731f8..e8fb41e49 100644 --- a/src/gcwii/Audio_GCWii.c +++ b/src/gcwii/Audio_GCWii.c @@ -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) { diff --git a/src/gcwii/Platform_GCWii.h b/src/gcwii/Platform_GCWii.h index 6fc5edbbc..63b9137f9 100644 --- a/src/gcwii/Platform_GCWii.h +++ b/src/gcwii/Platform_GCWii.h @@ -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; diff --git a/src/gcwii/Platform_Gamecube.c b/src/gcwii/Platform_Gamecube.c index 4f1db52d3..fc39391d5 100644 --- a/src/gcwii/Platform_Gamecube.c +++ b/src/gcwii/Platform_Gamecube.c @@ -24,10 +24,10 @@ #include #include #include -#include -#include +#include +#include +#include #include -#include 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); diff --git a/src/gcwii/Platform_Wii.c b/src/gcwii/Platform_Wii.c index 37694d273..91a9d2bf8 100644 --- a/src/gcwii/Platform_Wii.c +++ b/src/gcwii/Platform_Wii.c @@ -28,6 +28,7 @@ #include #include #include +#include #include const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */ diff --git a/src/gcwii/Window_GCWii.c b/src/gcwii/Window_GCWii.c index 9f0c07409..ccd29a8b3 100644 --- a/src/gcwii/Window_GCWii.c +++ b/src/gcwii/Window_GCWii.c @@ -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 }