diff --git a/Makefile.ps3 b/Makefile.ps3 index d1fdf6b415..979dbf8243 100644 --- a/Makefile.ps3 +++ b/Makefile.ps3 @@ -43,9 +43,9 @@ INCDIRS = -I. -Icommon MAKE_FSELF_NPDRM = $(CELL_SDK)/$(HOST_DIR)/bin/make_fself_npdrm MAKE_PACKAGE_NPDRM = $(CELL_SDK)/$(HOST_DIR)/bin/make_package_npdrm -OBJ = fifo_buffer.o ps3/cellframework2/fileio/file_browser.o ps3/ps3_audio.o ps3/menu.o ps3/ps3_input.o ps3/cellframework2/input/pad_input.o getopt.o ssnes.o driver.o file.o settings.o message.o rewind.o movie.o gfx/gfx_common.o ps3/ps3_video_psgl.o gfx/shader_cg.o gfx/snes_state.o ups.o bps.o strl.o screenshot.o audio/hermite.o dynamic.o ps3/main.o audio/utils.o conf/config_file.o gfx/image.o +OBJ = fifo_buffer.o ps3/cellframework2/fileio/file_browser.o ps3/ps3_audio.o ps3/menu.o ps3/ps3_input.o ps3/cellframework2/input/pad_input.o getopt.o ssnes.o driver.o file.o settings.o message.o rewind.o movie.o gfx/gfx_common.o ps3/ps3_video_psgl.o gfx/shader_cg.o gfx/snes_state.o ups.o bps.o strl.o screenshot.o audio/hermite.o dynamic.o ps3/main.o audio/utils.o conf/config_file.o ps3/image.o -LIBS = -ldbgfont -lPSGL -lPSGLcgc -lcgc -lgcm_cmd -lgcm_sys_stub -lsnes -lresc_stub -lm -lio_stub -lfs_stub -lsysutil_stub -lsysutil_game_stub -lsysutil_screenshot_stub -lsysmodule_stub -laudio_stub -lnet_stub -lpthread +LIBS = -ldbgfont -lPSGL -lPSGLcgc -lcgc -lgcm_cmd -lgcm_sys_stub -lsnes -lresc_stub -lm -lio_stub -lfs_stub -lsysutil_stub -lsysutil_game_stub -lsysutil_screenshot_stub -lpngdec_stub -ljpgdec_stub -lsysmodule_stub -laudio_stub -lnet_stub -lpthread DEFINES = -DSSNES_CONSOLE -DHAVE_OPENGL=1 -DHAVE_CG=1 -DHAVE_FBO=1 -D__CELLOS_LV2__ -DHAVE_CONFIGFILE=1 -DPACKAGE_VERSION=\"0.9.4\" -Dmain=ssnes_main diff --git a/general.h b/general.h index cee025dd8b..84228c516b 100644 --- a/general.h +++ b/general.h @@ -173,9 +173,12 @@ struct settings struct console_settings { bool block_config_read; -#ifdef __CELLOS_LV2__ bool return_to_multiman_enable; -#endif + uint32_t * supported_resolutions; + uint32_t supported_resolutions_count; + uint32_t current_resolution_index; + uint32_t current_resolution_id; + uint32_t initial_resolution_id; bool screenshots_enable; }; #endif diff --git a/ps3/image.c b/ps3/image.c new file mode 100644 index 0000000000..44e5f0d2d5 --- /dev/null +++ b/ps3/image.c @@ -0,0 +1,267 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2012 - Hans-Kristian Arntzen + * Copyright (C) 2011-2012 - Daniel De Matteis + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include "../gfx/image.h" + +#include +#include +#include +#include "../general.h" + +#include + +/******************************************************************************* + Image decompression - structs +********************************************************************************/ + +typedef struct CtrlMallocArg +{ + uint32_t mallocCallCounts; +} CtrlMallocArg; + +typedef struct CtrlFreeArg +{ + uint32_t freeCallCounts; +} CtrlFreeArg; + +void *img_malloc(uint32_t size, void * a) +{ + CtrlMallocArg *arg; + arg = (CtrlMallocArg *) a; + arg->mallocCallCounts++; + return malloc(size); +} + +static int img_free(void *ptr, void * a) +{ + CtrlFreeArg *arg; + arg = (CtrlFreeArg *) a; + arg->freeCallCounts++; + free(ptr); + return 0; +} + +/******************************************************************************* + Image decompression - libJPEG +********************************************************************************/ + +static bool ps3graphics_load_jpeg(const char * path, struct texture_image *out_img) +{ + CtrlMallocArg MallocArg; + CtrlFreeArg FreeArg; + CellJpgDecMainHandle mHandle = NULL; + CellJpgDecSubHandle sHandle = NULL; + CellJpgDecThreadInParam InParam; + CellJpgDecThreadOutParam OutParam; + CellJpgDecSrc src; + CellJpgDecOpnInfo opnInfo; + CellJpgDecInfo info; + CellJpgDecInParam inParam; + CellJpgDecOutParam outParam; + CellJpgDecDataOutInfo dOutInfo; + CellJpgDecDataCtrlParam dCtrlParam; + + MallocArg.mallocCallCounts = 0; + FreeArg.freeCallCounts = 0; + InParam.spuThreadEnable = CELL_JPGDEC_SPU_THREAD_ENABLE; + InParam.ppuThreadPriority = 1001; + InParam.spuThreadPriority = 250; + InParam.cbCtrlMallocFunc = img_malloc; + InParam.cbCtrlMallocArg = &MallocArg; + InParam.cbCtrlFreeFunc = img_free; + InParam.cbCtrlFreeArg = &FreeArg; + + int ret_jpeg, ret = -1; + ret_jpeg = cellJpgDecCreate(&mHandle, &InParam, &OutParam); + + if (ret_jpeg != CELL_OK) + { + goto error; + } + + memset(&src, 0, sizeof(CellJpgDecSrc)); + src.srcSelect = CELL_JPGDEC_FILE; + src.fileName = path; + src.fileOffset = 0; + src.fileSize = 0; + src.streamPtr = NULL; + src.streamSize = 0; + + src.spuThreadEnable = CELL_JPGDEC_SPU_THREAD_ENABLE; + + ret = cellJpgDecOpen(mHandle, &sHandle, &src, &opnInfo); + + if (ret != CELL_OK) + { + goto error; + } + + ret = cellJpgDecReadHeader(mHandle, sHandle, &info); + + if (ret != CELL_OK) + { + goto error; + } + + inParam.commandPtr = NULL; + inParam.method = CELL_JPGDEC_FAST; + inParam.outputMode = CELL_JPGDEC_TOP_TO_BOTTOM; + inParam.outputColorSpace = CELL_JPG_ARGB; + inParam.downScale = 1; + inParam.outputColorAlpha = 0xfe; + ret = cellJpgDecSetParameter(mHandle, sHandle, &inParam, &outParam); + + if (ret != CELL_OK) + { + sys_process_exit(0); + goto error; + } + + dCtrlParam.outputBytesPerLine = outParam.outputWidth * 4; + ret = cellJpgDecDecodeData(mHandle, sHandle, (uint8_t*)out_img->pixels, &dCtrlParam, &dOutInfo); + + if (ret != CELL_OK || dOutInfo.status != CELL_JPGDEC_DEC_STATUS_FINISH) + { + sys_process_exit(0); + goto error; + } + + out_img->width = outParam.outputWidth; + out_img->height = outParam.outputHeight; + + cellJpgDecClose(mHandle, sHandle); + cellJpgDecDestroy(mHandle); + + return true; + +error: + if (mHandle && sHandle) + cellJpgDecClose(mHandle, sHandle); + if (mHandle) + cellJpgDecDestroy(mHandle); + return false; +} + +/******************************************************************************* + Image decompression - libPNG +********************************************************************************/ + +static bool ps3graphics_load_png(const char * path, struct texture_image *out_img) +{ + CtrlMallocArg MallocArg; + CtrlFreeArg FreeArg; + CellPngDecMainHandle mHandle = NULL; + CellPngDecSubHandle sHandle = NULL; + CellPngDecThreadInParam InParam; + CellPngDecThreadOutParam OutParam; + CellPngDecSrc src; + CellPngDecOpnInfo opnInfo; + CellPngDecInfo info; + CellPngDecInParam inParam; + CellPngDecOutParam outParam; + CellPngDecDataOutInfo dOutInfo; + CellPngDecDataCtrlParam dCtrlParam; + + MallocArg.mallocCallCounts = 0; + FreeArg.freeCallCounts = 0; + InParam.spuThreadEnable = CELL_PNGDEC_SPU_THREAD_ENABLE; + InParam.ppuThreadPriority = 512; + InParam.spuThreadPriority = 200; + InParam.cbCtrlMallocFunc = img_malloc; + InParam.cbCtrlMallocArg = &MallocArg; + InParam.cbCtrlFreeFunc = img_free; + InParam.cbCtrlFreeArg = &FreeArg; + + int ret_png, ret = -1; + ret_png = cellPngDecCreate(&mHandle, &InParam, &OutParam); + + if (ret_png != CELL_OK) + goto error; + + memset(&src, 0, sizeof(CellPngDecSrc)); + src.srcSelect = CELL_PNGDEC_FILE; + src.fileName = path; + src.fileOffset = 0; + src.fileSize = 0; + src.streamPtr = 0; + src.streamSize = 0; + + src.spuThreadEnable = CELL_PNGDEC_SPU_THREAD_ENABLE; + + ret = cellPngDecOpen(mHandle, &sHandle, &src, &opnInfo); + + if (ret != CELL_OK) + goto error; + + ret = cellPngDecReadHeader(mHandle, sHandle, &info); + + if (ret != CELL_OK) + goto error; + + inParam.commandPtr = NULL; + inParam.outputMode = CELL_PNGDEC_TOP_TO_BOTTOM; + inParam.outputColorSpace = CELL_PNGDEC_ARGB; + inParam.outputBitDepth = 8; + inParam.outputPackFlag = CELL_PNGDEC_1BYTE_PER_1PIXEL; + inParam.outputAlphaSelect = CELL_PNGDEC_STREAM_ALPHA; + ret = cellPngDecSetParameter(mHandle, sHandle, &inParam, &outParam); + + if (ret != CELL_OK) + goto error; + + dCtrlParam.outputBytesPerLine = outParam.outputWidth * 4; + ret = cellPngDecDecodeData(mHandle, sHandle, (uint8_t*)out_img->pixels, &dCtrlParam, &dOutInfo); + + if (ret != CELL_OK || dOutInfo.status != CELL_PNGDEC_DEC_STATUS_FINISH) + goto error; + + out_img->width = outParam.outputWidth; + out_img->height = outParam.outputHeight; + + cellPngDecClose(mHandle, sHandle); + cellPngDecDestroy(mHandle); + + return true; + +error: + if (mHandle && sHandle) + cellPngDecClose(mHandle, sHandle); + if (mHandle) + cellPngDecDestroy(mHandle); + return false; +} + +bool texture_image_load(const char *path, struct texture_image *out_img) +{ + if(strstr(path, ".PNG") != NULL || strstr(path, ".png") != NULL) + { + if (!ps3graphics_load_png(path, out_img)) + return false; + } + else + { + if (!ps3graphics_load_jpeg(path, out_img)) + return false; + } + + return true; +} diff --git a/ps3/main.c b/ps3/main.c index f6cce562e2..2f58b000c0 100644 --- a/ps3/main.c +++ b/ps3/main.c @@ -66,10 +66,13 @@ if(!(config_get_array(currentconfig, charstring, setting, sizeof(setting)))) \ strncpy(setting,defaultvalue, sizeof(setting)); + uint32_t g_emulator_initialized = 0; char special_action_msg[256]; /* message which should be overlaid on top of the screen*/ uint32_t special_action_msg_expired; /* time at which the message no longer needs to be overlaid onscreen*/ +uint32_t mode_switch = MODE_MENU; +bool init_ssnes = false; char contentInfoPath[MAX_PATH_LENGTH]; char usrDirPath[MAX_PATH_LENGTH]; @@ -200,7 +203,6 @@ static void callback_sysutil_exit(uint64_t status, uint64_t param, void *userdat case CELL_SYSUTIL_REQUEST_EXITGAME: menu_is_running = 0; g_quitting = true; - sys_process_exit(0); break; } } @@ -252,23 +254,53 @@ int main(int argc, char *argv[]) ps3_input_init(); menu_init(); - menu_loop(); - char arg1[] = "ssnes"; - char arg2[PATH_MAX]; - - snprintf(arg2, sizeof(arg2), g_extern.system.fullpath); - char arg3[] = "-v"; - char arg4[] = "-c"; - char arg5[MAX_PATH_LENGTH]; +begin_loop: + if(mode_switch == MODE_EMULATION) + { + while(ssnes_main_iterate()); + ssnes_main_deinit(); + } + else if(mode_switch == MODE_MENU) + { + menu_loop(); + if(init_ssnes) + { + char arg1[] = "ssnes"; + char arg2[PATH_MAX]; - snprintf(arg5, sizeof(arg5), SYS_CONFIG_FILE); - char *argv_[] = { arg1, arg2, arg3, arg4, arg5, NULL }; + snprintf(arg2, sizeof(arg2), g_extern.system.fullpath); + char arg3[] = "-v"; + char arg4[] = "-c"; + char arg5[MAX_PATH_LENGTH]; - g_emulator_initialized = 1; + snprintf(arg5, sizeof(arg5), SYS_CONFIG_FILE); + char *argv_[] = { arg1, arg2, arg3, arg4, arg5, NULL }; - return ssnes_main(sizeof(argv_) / sizeof(argv_[0]) - 1, argv_); + g_emulator_initialized = 1; + int argc = sizeof(argv_) / sizeof(argv_[0]) - 1; + int init_ret = ssnes_main_init(argc, argv_); + printf("init_ret: %d\n", init_ret); + if(init_ret) + { + mode_switch = MODE_MENU; + ssnes_main_deinit(); + } + init_ssnes = 0; + } + } +#ifdef MULTIMAN_SUPPORT + else if(mode_switch == MODE_MULTIMAN_STARTUP) + { + } +#endif + else + goto begin_shutdown; + goto begin_loop; + +begin_shutdown: ps3_input_deinit(); ps3_video_deinit(); + sys_process_exit(0); } diff --git a/ps3/menu.c b/ps3/menu.c index 3197eefadb..91e73bd6a9 100644 --- a/ps3/menu.c +++ b/ps3/menu.c @@ -279,6 +279,7 @@ static void browser_update(filebrowser_t * b) if (g_emulator_initialized) { menu_is_running = 0; + mode_switch = MODE_EMULATION; set_text_message("", 15); } } @@ -525,6 +526,12 @@ static void set_setting_label(menu * menu_obj, int currentsetting) switch(currentsetting) { case SETTING_CHANGE_RESOLUTION: + if(g_console.initial_resolution_id == g_console.supported_resolutions[g_console.current_resolution_index]) + menu_obj->items[currentsetting].text_color = GREEN; + else + menu_obj->items[currentsetting].text_color = ORANGE; + + snprintf(menu_obj->items[currentsetting].setting_text, sizeof(menu_obj->items[currentsetting].setting_text), ps3_get_resolution_label(g_console.supported_resolutions[g_console.current_resolution_index])); break; case SETTING_SHADER_PRESETS: /* add a comment */ @@ -805,6 +812,37 @@ static void producesettingentry(menu * menu_obj, uint64_t switchvalue) switch(switchvalue) { case SETTING_CHANGE_RESOLUTION: + if(CTRL_RIGHT(state) || CTRL_LSTICK_RIGHT(state) ) + { + ps3_next_resolution(); + set_text_message("", 7); + } + if(CTRL_LEFT(state) || CTRL_LSTICK_LEFT(state) ) + { + ps3_previous_resolution(); + set_text_message("", 7); + } + if(CTRL_CROSS(state)) + { + if (g_console.supported_resolutions[g_console.current_resolution_index] == CELL_VIDEO_OUT_RESOLUTION_576) + { + if(ps3_check_resolution(CELL_VIDEO_OUT_RESOLUTION_576)) + { + //ps3graphics_set_pal60hz(Settings.PS3PALTemporalMode60Hz); + //ps3graphics_switch_resolution(ps3graphics_get_current_resolution(), Settings.PS3PALTemporalMode60Hz, Settings.TripleBuffering, Settings.ScaleEnabled, Settings.ScaleFactor); + //ps3graphics_set_vsync(Settings.Throttled); + //apply_scaling(); + } + } + else + { + //ps3graphics_set_pal60hz(0); + //ps3graphics_switch_resolution(ps3graphics_get_current_resolution(), 0, Settings.TripleBuffering, Settings.ScaleEnabled, Settings.ScaleFactor); + //ps3graphics_set_vsync(Settings.Throttled); + //apply_scaling(); + //emulator_implementation_set_texture(Settings.PS3CurrentBorder); + } + } break; /* case SETTING_PAL60_MODE: @@ -1160,6 +1198,7 @@ static void select_setting(menu * menu_obj) if (g_emulator_initialized) { menu_is_running = 0; + mode_switch = MODE_EMULATION; set_text_message("", 15); } old_state = state; @@ -1240,6 +1279,8 @@ static void select_rom(void) menu_is_running = 0; snprintf(g_extern.system.fullpath, sizeof(g_extern.system.fullpath), "%s/%s", FILEBROWSER_GET_CURRENT_DIRECTORY_NAME(browser), FILEBROWSER_GET_CURRENT_FILENAME(browser)); + init_ssnes = 1; + mode_switch = MODE_EMULATION; old_state = state; return; diff --git a/ps3/pkg/ICON0.PNG b/ps3/pkg/ICON0.PNG index 7daca7efee..92bbb49b5c 100644 Binary files a/ps3/pkg/ICON0.PNG and b/ps3/pkg/ICON0.PNG differ diff --git a/ps3/ps3_video_psgl.c b/ps3/ps3_video_psgl.c index 709c45867e..df80353472 100644 --- a/ps3/ps3_video_psgl.c +++ b/ps3/ps3_video_psgl.c @@ -85,6 +85,8 @@ bool g_quitting; unsigned g_frame_count; void *g_gl; +static CellVideoOutState g_video_state; + typedef struct gl { GLuint pbo; @@ -993,6 +995,105 @@ const video_driver_t video_gl = { .ident = "gl" }; +static void get_all_available_resolutions (void) +{ + bool defaultresolution; + uint32_t i, resolution_count; + uint16_t num_videomodes; + + defaultresolution = true; + + uint32_t videomode[] = { + CELL_VIDEO_OUT_RESOLUTION_480, CELL_VIDEO_OUT_RESOLUTION_576, + CELL_VIDEO_OUT_RESOLUTION_960x1080, CELL_VIDEO_OUT_RESOLUTION_720, + CELL_VIDEO_OUT_RESOLUTION_1280x1080, CELL_VIDEO_OUT_RESOLUTION_1440x1080, + CELL_VIDEO_OUT_RESOLUTION_1600x1080, CELL_VIDEO_OUT_RESOLUTION_1080}; + + num_videomodes = sizeof(videomode)/sizeof(uint32_t); + + resolution_count = 0; + for (i = 0; i < num_videomodes; i++) + if (cellVideoOutGetResolutionAvailability(CELL_VIDEO_OUT_PRIMARY, videomode[i], CELL_VIDEO_OUT_ASPECT_AUTO,0)) + resolution_count++; + + g_console.supported_resolutions = (uint32_t*)malloc(resolution_count * sizeof(uint32_t)); + + g_console.supported_resolutions_count = 0; + for (i = 0; i < num_videomodes; i++) + { + if (cellVideoOutGetResolutionAvailability(CELL_VIDEO_OUT_PRIMARY, videomode[i], CELL_VIDEO_OUT_ASPECT_AUTO,0)) + { + g_console.supported_resolutions[g_console.supported_resolutions_count++] = videomode[i]; + g_console.initial_resolution_id = videomode[i]; + + if (g_console.current_resolution_id == videomode[i]) + { + defaultresolution = false; + g_console.current_resolution_index = g_console.supported_resolutions_count-1; + } + } + } + + /* In case we didn't specify a resolution - make the last resolution*/ + /* that was added to the list (the highest resolution) the default resolution*/ + if (g_console.current_resolution_id > num_videomodes || defaultresolution) + g_console.current_resolution_index = g_console.supported_resolutions_count-1; +} + +void ps3_set_resolution (void) +{ + cellVideoOutGetState(CELL_VIDEO_OUT_PRIMARY, 0, &g_video_state); +} + +void ps3_next_resolution (void) +{ + if(g_console.current_resolution_index+1 < g_console.supported_resolutions_count) + { + g_console.current_resolution_index++; + g_console.current_resolution_id = g_console.supported_resolutions[g_console.current_resolution_index]; + } +} + +void ps3_previous_resolution (void) +{ + if(g_console.current_resolution_index > 0) + { + g_console.current_resolution_index--; + g_console.current_resolution_id = g_console.supported_resolutions[g_console.current_resolution_index]; + } +} + +int ps3_check_resolution(uint32_t resolution_id) +{ + return cellVideoOutGetResolutionAvailability(CELL_VIDEO_OUT_PRIMARY, resolution_id, \ + CELL_VIDEO_OUT_ASPECT_AUTO,0); +} + +const char * ps3_get_resolution_label(uint32_t resolution) +{ + switch(resolution) + { + case CELL_VIDEO_OUT_RESOLUTION_480: + return "720x480 (480p)"; + case CELL_VIDEO_OUT_RESOLUTION_576: + return "720x576 (576p)"; + case CELL_VIDEO_OUT_RESOLUTION_720: + return "1280x720 (720p)"; + case CELL_VIDEO_OUT_RESOLUTION_960x1080: + return "960x1080"; + case CELL_VIDEO_OUT_RESOLUTION_1280x1080: + return "1280x1080"; + case CELL_VIDEO_OUT_RESOLUTION_1440x1080: + return "1440x1080"; + case CELL_VIDEO_OUT_RESOLUTION_1600x1080: + return "1600x1080"; + case CELL_VIDEO_OUT_RESOLUTION_1080: + return "1920x1080 (1080p)"; + default: + return "Unknown"; + } +} + // PS3 needs a working graphics stack before SSNES even starts. // To deal with this main.c, // the top level module owns the instance, and is created beforehand. @@ -1007,6 +1108,8 @@ void ps3_video_init(void) video_info.smooth = true; video_info.input_scale = 2; g_gl = gl_init(&video_info, NULL, NULL); + get_all_available_resolutions(); + ps3_set_resolution(); } void ps3_video_deinit(void) diff --git a/ps3/ps3_video_psgl.h b/ps3/ps3_video_psgl.h index 96ab01c601..855aa191d2 100644 --- a/ps3/ps3_video_psgl.h +++ b/ps3/ps3_video_psgl.h @@ -26,6 +26,12 @@ void ps3_video_init(void); void ps3_video_deinit(void); + +void ps3_next_resolution (void); +void ps3_previous_resolution (void); +const char * ps3_get_resolution_label(uint32_t resolution); +int ps3_check_resolution(uint32_t resolution_id); + extern void *g_gl; #endif diff --git a/ps3/shared.h b/ps3/shared.h index d737f5d3bb..b903f5a6fd 100644 --- a/ps3/shared.h +++ b/ps3/shared.h @@ -18,10 +18,22 @@ #define MAX_PATH_LENGTH 1024 +enum +{ + MODE_EMULATION, + MODE_MENU, +#ifdef MULTIMAN_SUPPORT + MODE_MULTIMAN_STARTUP, +#endif + MODE_EXIT +}; + extern char special_action_msg[256]; extern uint32_t g_emulator_initialized; extern uint32_t special_action_msg_expired; +extern uint32_t mode_switch; extern unsigned g_frame_count; +extern bool init_ssnes; extern bool g_quitting; extern char contentInfoPath[MAX_PATH_LENGTH]; diff --git a/wii/pkg/icon.png b/wii/pkg/icon.png new file mode 100644 index 0000000000..8238b90e20 Binary files /dev/null and b/wii/pkg/icon.png differ diff --git a/wii/pkg/meta.xml b/wii/pkg/meta.xml new file mode 100644 index 0000000000..a1893a0749 --- /dev/null +++ b/wii/pkg/meta.xml @@ -0,0 +1,10 @@ + + + SSNES + Themaister + 0.9.4 + 2012 + Multi-system emulator + A port of SSNES to the Wii. + +