// Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include #include #include #include #include // The same DOL must be run on the console and in Dolphin. Do not depend on // timings or on allocated IOS file descriptor numbers in the comparison. #define SCHEMA_VERSION 1 #define ROUNDS 64 #define RECORDS_PER_ROUND 29 #define SKIPPED (-32768) #define SD_TEST_FILE "sd:/wii-conformance-data.bin" static FILE* output; static unsigned int sequence; static void record(const char* test, unsigned int round, int value, int extra) { if (output) fprintf(output, "%u,%s,%u,%d,%d\n", sequence, test, round, value, extra); ++sequence; } static void probe_open(const char* name, const char* path, unsigned int round, unsigned int mode) { char test[48]; const s32 fd = IOS_Open(path, mode); snprintf(test, sizeof(test), "open_%s_mode%u", name, mode); // File descriptor allocation can legitimately differ between runs. record(test, round, fd >= 0 ? 0 : fd, 0); snprintf(test, sizeof(test), "close_%s_mode%u", name, mode); record(test, round, fd >= 0 ? IOS_Close(fd) : SKIPPED, 0); } static void probe_sd(unsigned int round) { static u8 expected[4096] __attribute__((aligned(32))); static u8 actual[4096] __attribute__((aligned(32))); const size_t length = 1 + ((round * 67) % sizeof(expected)); for (size_t i = 0; i < length; ++i) expected[i] = (u8)(round * 131 + i * 17); FILE* file = fopen(SD_TEST_FILE, "wb"); record("sd_open_write", round, file ? 0 : -1, (int)length); record("sd_write_bytes", round, file ? (int)fwrite(expected, 1, length, file) : SKIPPED, (int)length); record("sd_close_write", round, file ? fclose(file) : SKIPPED, 0); file = fopen(SD_TEST_FILE, "rb"); record("sd_open_read", round, file ? 0 : -1, 0); const size_t read_count = file ? fread(actual, 1, length, file) : 0; record("sd_read_bytes", round, file ? (int)read_count : SKIPPED, (int)length); u32 hash = 2166136261u; for (size_t i = 0; i < read_count; ++i) hash = (hash ^ actual[i]) * 16777619u; record("sd_read_hash", round, file ? (int)hash : SKIPPED, 0); record("sd_data_equal", round, file ? (read_count == length && memcmp(expected, actual, length) == 0) : SKIPPED, 0); record("sd_close_read", round, file ? fclose(file) : SKIPPED, 0); record("sd_remove", round, remove(SD_TEST_FILE), 0); } static void init_display(void) { VIDEO_Init(); GXRModeObj* mode = VIDEO_GetPreferredMode(NULL); void* framebuffer = MEM_K0_TO_K1(SYS_AllocateFramebuffer(mode)); console_init(framebuffer, 20, 20, mode->fbWidth - 40, mode->xfbHeight - 40, mode->fbWidth * VI_DISPLAY_PIX_SZ); VIDEO_Configure(mode); VIDEO_SetNextFramebuffer(framebuffer); VIDEO_SetBlack(FALSE); VIDEO_Flush(); VIDEO_WaitVSync(); if (mode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync(); } int main(void) { init_display(); printf("Wii IOS conformance probe v%d\n", SCHEMA_VERSION); if (fatInitDefault()) output = fopen("sd:/wii-conformance.csv", "wb"); if (!output) { printf("Cannot write sd:/wii-conformance.csv\n"); printf("Insert a writable SD card and restart.\n"); for (int i = 0; i < 600; ++i) VIDEO_WaitVSync(); return 1; } // Metadata is checked before comparing individual observations. fprintf(output, "schema,%d\n", SCHEMA_VERSION); fprintf(output, "rounds,%d\n", ROUNDS); fprintf(output, "observations,%d\n", 4 + ROUNDS * RECORDS_PER_ROUND); fprintf(output, "ios_version,%d\n", IOS_GetVersion()); fprintf(output, "ios_revision,%d\n", IOS_GetRevision()); fprintf(output, "seq,test,round,value,extra\n"); const s32 fs_init = ISFS_Initialize(); record("isfs_init", 0, fs_init, 0); const s32 es_init = __ES_Init(); record("es_init", 0, es_init, 0); for (unsigned int round = 0; round < ROUNDS; ++round) { // A missing resource is a useful stable IPC error-path test. probe_open("missing", "/dev/wii_conformance_probe_absent", round, IPC_OPEN_NONE); for (unsigned int mode = IPC_OPEN_NONE; mode <= IPC_OPEN_RW; ++mode) { probe_open("fs", "/dev/fs", round, mode); probe_open("es", "/dev/es", round, mode); } if (fs_init >= 0) { u32 count __attribute__((aligned(32))) = 0; const s32 result = ISFS_ReadDir("/", NULL, &count); record("isfs_root_count", round, result, result >= 0 ? (int)count : 0); } else { record("isfs_root_count", round, SKIPPED, 0); } if (es_init >= 0) { u32 title_count __attribute__((aligned(32))) = 0; const s32 title_result = ES_GetNumTitles(&title_count); record("es_title_count", round, title_result, title_result >= 0 ? (int)title_count : 0); } else { record("es_title_count", round, SKIPPED, 0); } probe_sd(round); } if (fs_init >= 0) record("isfs_deinit", 0, ISFS_Deinitialize(), 0); else record("isfs_deinit", 0, SKIPPED, 0); if (es_init >= 0) record("es_deinit", 0, __ES_Close(), 0); else record("es_deinit", 0, SKIPPED, 0); const int write_error = ferror(output); const int close_error = fclose(output); printf("%u observations. CSV: sd:/wii-conformance.csv\n", sequence); printf("Write status: %d, close status: %d\n", write_error, close_error); printf("Returning to loader in about 10 seconds.\n"); for (int i = 0; i < 600; ++i) VIDEO_WaitVSync(); return write_error || close_error ? 1 : 0; }