From 73524105c9b4b043cfcb0c49420ed83a16dd47b9 Mon Sep 17 00:00:00 2001 From: panzone Date: Mon, 22 Dec 2025 15:00:22 +0100 Subject: [PATCH] Vita porting --- BUILDING.md | 13 ++ README.md | 8 +- src/main.c | 21 +++- src/opengl.c | 4 + src/platform/vita/.gitignore | 1 + src/platform/vita/CMakeLists.txt | 101 ++++++++++++++++ src/platform/vita/README.md | 4 + src/platform/vita/sce_sys/icon0.png | Bin 0 -> 97 bytes .../vita/sce_sys/livearea/contents/bg.png | Bin 0 -> 167 bytes .../sce_sys/livearea/contents/startup.png | Bin 0 -> 121 bytes .../sce_sys/livearea/contents/template.xml | 11 ++ src/platform/vita/sm.ini | 44 +++++++ src/platform/vita/src/vita_impl.c | 114 ++++++++++++++++++ src/platform/vita/src/vita_impl.h | 5 + src/sm_cpu_infra.c | 4 + src/snes/ppu.c | 4 +- src/util.h | 1 + 17 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 src/platform/vita/.gitignore create mode 100644 src/platform/vita/CMakeLists.txt create mode 100644 src/platform/vita/README.md create mode 100644 src/platform/vita/sce_sys/icon0.png create mode 100644 src/platform/vita/sce_sys/livearea/contents/bg.png create mode 100644 src/platform/vita/sce_sys/livearea/contents/startup.png create mode 100644 src/platform/vita/sce_sys/livearea/contents/template.xml create mode 100644 src/platform/vita/sm.ini create mode 100644 src/platform/vita/src/vita_impl.c create mode 100644 src/platform/vita/src/vita_impl.h diff --git a/BUILDING.md b/BUILDING.md index e40b009..b751b52 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -95,3 +95,16 @@ Dependencies and requirements: ```sh make -j$(nproc) # To build using all cores ``` + +# Playstation Vita + +Dependencies and requirements: + +* [VitaSDK](https://vitasdk.org/) + +1. Install VitaSDK and SDL2 (included automatically in the standard installation of VitaSDK) +2. CD to `vita` folder by typing `cd src/platform/vita` in the terminal on the `sm` root folder. +3. Create a build folder and enter it `mkdir -p build && cd build` +4. type `cmake ..` to create the makefile for Vita adapted to your development configuration. +5. type `make` to compile the Vita version. +6. Transfer the `sm.vpk` to your Vita and install it with VitaShell or your favorite installer. diff --git a/README.md b/README.md index 4e9dc03..b2ee2b5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +# sm-vita + +This is a (work in progress) porting of [sm](https://github.com/snesrev/sm) project on the Vita console. + +- For building, check [BUILDING.md](BUILDING.md#playstation-vita) +- For more details on how to run it, check the Vita-specific [README](src/platform/vita/README.md) + # sm Our discord server is: https://discord.gg/AJJbJAzNNJ @@ -7,4 +14,3 @@ Early version. It has bugs and the code is messy. For building instructions, see: https://github.com/snesrev/sm/blob/main/BUILDING.md Put sm.smc (sha1 hash da957f0d63d14cb441d215462904c4fa8519c613) in the root folder. When running, it will run both versions and compare frame by frame. If it detects a mismatch, it saves a snapshot in saves/ and displays a counter on screen counting down from 300. - diff --git a/src/main.c b/src/main.c index 64ececa..7cd974f 100644 --- a/src/main.c +++ b/src/main.c @@ -25,6 +25,9 @@ #ifdef __SWITCH__ #include "switch_impl.h" #endif +#ifdef __VITA__ +#include "platform/vita/src/vita_impl.h" +#endif static void playAudio(Snes *snes, SDL_AudioDeviceID device, int16_t *audioBuffer); static void renderScreen(Snes *snes, SDL_Renderer *renderer, SDL_Texture *texture); @@ -163,6 +166,8 @@ static SDL_HitTestResult HitTestCallback(SDL_Window *win, const SDL_Point *pt, v } void RtlDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) { + if(pixel_buffer == NULL) + return; uint8 *ppu_pixels = g_other_image ? g_my_pixels : g_pixels; for (size_t y = 0; y < 240; y++) memcpy((uint8_t *)pixel_buffer + y * pitch, ppu_pixels + y * 256 * 4, 256 * 4); @@ -282,6 +287,10 @@ static void SdlRenderer_Destroy(void) { SDL_DestroyRenderer(g_renderer); } +static void SdlRenderer_BeforeFrame(void** ptr) { + +} + static void SdlRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) { g_sdl_renderer_rect.w = width; g_sdl_renderer_rect.h = height; @@ -305,6 +314,7 @@ static void SdlRenderer_EndDraw(void) { static const struct RendererFuncs kSdlRendererFuncs = { &SdlRenderer_Init, &SdlRenderer_Destroy, + &SdlRenderer_BeforeFrame, &SdlRenderer_BeginDraw, &SdlRenderer_EndDraw, }; @@ -315,6 +325,9 @@ static const struct RendererFuncs kSdlRendererFuncs = { int main(int argc, char** argv) { #ifdef __SWITCH__ SwitchImpl_Init(); +#endif +#ifdef __VITA__ + VitaImpl_Init(); #endif argc--, argv++; const char *config_file = NULL; @@ -367,12 +380,16 @@ int main(int argc, char** argv) { int window_width = custom_size ? g_config.window_width : g_current_window_scale * g_snes_width; int window_height = custom_size ? g_config.window_height : g_current_window_scale * g_snes_height; +#ifdef __VITA__ + VitaRenderer_Create(&g_renderer_funcs); +#else if (g_config.output_method == kOutputMethod_OpenGL) { g_win_flags |= SDL_WINDOW_OPENGL; OpenGLRenderer_Create(&g_renderer_funcs); } else { g_renderer_funcs = kSdlRendererFuncs; } +#endif // init snes, load rom const char* filename = argv[0] ? argv[0] : "sm.smc"; @@ -507,8 +524,9 @@ int main(int argc, char** argv) { g_gamepad_buttons = 0; inputs |= g_gamepad_buttons; - uint8 is_replay = RtlRunFrame(inputs); + g_renderer_funcs.BeforeFrame((void**)&snes->ppu->renderBuffer); + uint8 is_replay = RtlRunFrame(inputs); frameCtr++; g_snes->disableRender = (g_turbo ^ (is_replay & g_replay_turbo)) && (frameCtr & (g_turbo ? 0xf : 0x7f)) != 0; @@ -526,7 +544,6 @@ int main(int argc, char** argv) { SDL_SetWindowTitle(g_window, kWindowTitle); } } - // if vsync isn't working, delay manually curTick = SDL_GetTicks(); diff --git a/src/opengl.c b/src/opengl.c index bcb49e1..874accb 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -144,6 +144,9 @@ static bool OpenGLRenderer_Init(SDL_Window *window) { static void OpenGLRenderer_Destroy(void) { } +static void OpenGLRenderer_BeforeFrame(void** ptr) { +} + static void OpenGLRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) { int size = width * height; @@ -207,6 +210,7 @@ static void OpenGLRenderer_EndDraw(void) { static const struct RendererFuncs kOpenGLRendererFuncs = { &OpenGLRenderer_Init, &OpenGLRenderer_Destroy, + &OpenGLRenderer_BeforeFrame, &OpenGLRenderer_BeginDraw, &OpenGLRenderer_EndDraw, }; diff --git a/src/platform/vita/.gitignore b/src/platform/vita/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/src/platform/vita/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/src/platform/vita/CMakeLists.txt b/src/platform/vita/CMakeLists.txt new file mode 100644 index 0000000..242f238 --- /dev/null +++ b/src/platform/vita/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.16) + +if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) + if(DEFINED ENV{VITASDK}) + set(CMAKE_TOOLCHAIN_FILE "$ENV{VITASDK}/share/vita.toolchain.cmake" CACHE PATH "toolchain file") + else() + message(FATAL_ERROR "Please define VITASDK to point to your SDK path!") + endif() +endif() + +project(sm) +include("${VITASDK}/share/vita.cmake" REQUIRED) + +find_package(SDL2 REQUIRED PATHS ${VITASDK}/arm-vita-eabi/lib/cmake/SDL2) + +set(VITA_APP_NAME "Super Metroid") +set(VITA_TITLEID "SPMETROID") +set(VITA_VERSION "00.10") +set(VITA_VPKNAME "sm") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-q,--wrap,fopen,--wrap,mkdir -Ofast -fomit-frame-pointer -ffast-math \ + -mfloat-abi=hard -ffast-math -fsingle-precision-constant -fno-tree-vectorize -mtune=cortex-a9 -mfpu=neon -DNDEBUG -D__VITA__") + +add_executable(${PROJECT_NAME} + ../../main.c + ../../sm_80.c + ../../sm_81.c + ../../sm_82.c + ../../sm_84.c + ../../sm_85.c + ../../sm_86.c + ../../sm_87.c + ../../sm_88.c + ../../sm_89.c + ../../sm_8b.c + ../../sm_8d.c + ../../sm_8f.c + ../../sm_90.c + ../../sm_91.c + ../../sm_92.c + ../../sm_93.c + ../../sm_94.c + ../../sm_9b.c + ../../sm_a0.c + ../../sm_a2.c + ../../sm_a3.c + ../../sm_a4.c + ../../sm_a5.c + ../../sm_a6.c + ../../sm_a7.c + ../../sm_a8.c + ../../sm_a9.c + ../../sm_aa.c + ../../sm_ad.c + ../../sm_b2.c + ../../sm_b3.c + ../../sm_b4.c + ../../sm_cpu_infra.c + ../../sm_rtl.c + ../../snes/apu.c + ../../snes/cart.c + ../../snes/cpu.c + ../../snes/dma.c + ../../snes/dsp.c + ../../snes/input.c + ../../snes/ppu.c + ../../snes/snes.c + ../../snes/snes_other.c + ../../snes/spc.c + ../../spc_player.c + ../../util.c + ../../config.c + ../../tracing.c + src/vita_impl.c +) + +target_link_libraries(${PROJECT_NAME} + SDL2::SDL2 +) + +vita_create_self(${PROJECT_NAME}.self ${PROJECT_NAME}) +vita_create_vpk(${PROJECT_NAME}.vpk ${VITA_TITLEID} ${PROJECT_NAME}.self + VERSION ${VITA_VERSION} + NAME ${VITA_APP_NAME} + FILE sm.ini sm.ini + FILE sce_sys/icon0.png sce_sys/icon0.png + FILE sce_sys/livearea/contents/bg.png sce_sys/livearea/contents/bg.png + FILE sce_sys/livearea/contents/startup.png sce_sys/livearea/contents/startup.png + FILE sce_sys/livearea/contents/template.xml sce_sys/livearea/contents/template.xml +) + +set(PSVITAIP "192.168.178.32" CACHE STRING "PSVita IP (for FTP access)") + +add_custom_target(send + COMMAND echo Sending to ${PSVITAIP}... + COMMAND echo destroy | nc ${PSVITAIP} 1338 + COMMAND cp sm.self eboot.bin + COMMAND curl -T ftp://${PSVITAIP}:1337/ux0:/app/${VITA_TITLEID}/ + COMMAND echo launch ${VITA_TITLEID} | nc ${PSVITAIP} 1338 + DEPENDS ${VITA_VPKNAME}.vpk-vpk +) diff --git a/src/platform/vita/README.md b/src/platform/vita/README.md new file mode 100644 index 0000000..dcb1b2a --- /dev/null +++ b/src/platform/vita/README.md @@ -0,0 +1,4 @@ +# Super Metroid Vita porting + +To run, you need sm.smc (sha1 hash da957f0d63d14cb441d215462904c4fa8519c613). Create a directory in ux0:data/smetroid and put the file in it. +Finally, install sm.vpk using VitaShell and the app bubble will appear in your LiveArea. diff --git a/src/platform/vita/sce_sys/icon0.png b/src/platform/vita/sce_sys/icon0.png new file mode 100644 index 0000000000000000000000000000000000000000..00167d04b543999625a3e0062609899a084aab64 GIT binary patch literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?O3?zSk_}l@cm;-!5T>t<7zx;^w2_RR})5S5w t<9Kp{1nc4ik)EbN11`6N8VmFp7&saj81uAcLx74HJYD@<);T3K0RY8P8YTb$ literal 0 HcmV?d00001 diff --git a/src/platform/vita/sce_sys/livearea/contents/bg.png b/src/platform/vita/sce_sys/livearea/contents/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..d5e0a1a7c9416ad0cd4f7cb5cd2a66784195fb29 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0y~yVD?~OVEn?!3>2~5H-A5n;wb| zfr88dJ|V9E|NmcpMEV4f>*eX<7*cWT?Lk9EAn(wEul~8*`U`+eQbC2(c@74~O~3Rq PK{`EM{an^LB{Ts5d3qp2 literal 0 HcmV?d00001 diff --git a/src/platform/vita/sce_sys/livearea/contents/startup.png b/src/platform/vita/sce_sys/livearea/contents/startup.png new file mode 100644 index 0000000000000000000000000000000000000000..c1450e6d43f172cc07e17385a5ce6c3570b34aed GIT binary patch literal 121 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|=P@z^$;!+0*}aI1_r((Aj~*bn@<`j z$Q + + + + bg.png + + + + startup.png + + diff --git a/src/platform/vita/sm.ini b/src/platform/vita/sm.ini new file mode 100644 index 0000000..ede277a --- /dev/null +++ b/src/platform/vita/sm.ini @@ -0,0 +1,44 @@ +[General] +# Automatically save state on quit and reload on start +Autosave = 0 + +# Disable the SDL_Delay that happens each frame (Gives slightly better perf if your +# display is set to exactly 60hz) +DisableFrameDelay = 1 + +[Graphics] +# Window size ( Auto or WidthxHeight ) +WindowSize = 960x544 + +# Window scale (1=100%, 2=200%, 3=300%, etc.) +WindowScale = 1 + +# Use an optimized (but potentially more buggy) SNES PPU implementation +NewRenderer = 1 + +# Display the world map with higher resolution +EnhancedMode7 = 0 + +# Don't keep the aspect ratio +IgnoreAspectRatio = 1 + +# Enable this option to remove the sprite limits per scan line +NoSpriteLimits = 0 + +[Sound] +EnableAudio = 1 + +# DSP frequency in samples per second (e.g. 48000, 44100, 32000, 22050, 11025) +AudioFreq = 48000 + +# number of separate sound channels (1=mono, 2=stereo) +AudioChannels = 2 + +# Audio buffer size in samples (power of 2; e.g., 4096, 2048, 1024) [try 1024 if sound is crackly]. The higher the more lag before you hear sounds. +AudioSamples = 512 + +[GamepadMap] +# Any keys used in KeyMap can be used also in this section. +# The shoulder button is called L1/Lb and L2, and the thumbstick button is called L3 +Controls = DpadUp, DpadDown, DpadLeft, DpadRight, Back, Start, B, A, Y, X, Lb, Rb + diff --git a/src/platform/vita/src/vita_impl.c b/src/platform/vita/src/vita_impl.c new file mode 100644 index 0000000..2503a19 --- /dev/null +++ b/src/platform/vita/src/vita_impl.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include "../../../types.h" +#include "../../../util.h" + +int __real_mkdir(const char *fname, mode_t mode); +FILE * __real_fopen(char *fname, char *mode); + +int _newlib_heap_size_user = 128 * 1024 * 1024; + +void VitaImpl_Init() { + scePowerSetArmClockFrequency(444); + scePowerSetBusClockFrequency(222); + scePowerSetGpuClockFrequency(222); + scePowerSetGpuXbarClockFrequency(166); + //To create base directory if necessary + mkdir("", 0777); +} + +void OpenGLRenderer_Create(struct RendererFuncs *funcs) { + +} + +// State Vita SDL Renderer +static SDL_Renderer *vita_renderer; +static SDL_Texture *vita_texture; +static SDL_Rect window_rect, rendering_rect, texture_rect; + +static bool VitaRenderer_Init(SDL_Window *window) { + SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (renderer == NULL) { + printf("Failed to create renderer: %s\n", SDL_GetError()); + return false; + } + + vita_renderer = renderer; + + vita_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 256, 240); + if (vita_texture == NULL) { + printf("Failed to create texture: %s\n", SDL_GetError()); + return false; + } + + window_rect.x = 169; + window_rect.y = 0; + window_rect.w = 622; + window_rect.h = 544; + + rendering_rect.x = 0; + rendering_rect.y = 0; + rendering_rect.w = 256; + rendering_rect.h = 240; + + texture_rect.x = 0; + texture_rect.y = 0; + texture_rect.w = 256; + texture_rect.h = 224; + return true; +} + +static void VitaRenderer_Destroy(void) { + SDL_DestroyTexture(vita_texture); + SDL_DestroyRenderer(vita_renderer); +} + +static void VitaRenderer_BeforeFrame(void **memoryBuffer) { + int pitch; + if (SDL_LockTexture(vita_texture, &rendering_rect, memoryBuffer, &pitch) != 0) { + printf("Failed to lock texture: %s\n", SDL_GetError()); + //TODO fail program + } +} + +static void VitaRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) { + *pixels = NULL; +} + +static void VitaRenderer_EndDraw(void) { + SDL_UnlockTexture(vita_texture); + SDL_RenderClear(vita_renderer); + SDL_RenderCopy(vita_renderer, vita_texture, &texture_rect, &window_rect); + SDL_RenderPresent(vita_renderer); +} + +static const struct RendererFuncs kVitaRendererFuncs = { + &VitaRenderer_Init, + &VitaRenderer_Destroy, + &VitaRenderer_BeforeFrame, + &VitaRenderer_BeginDraw, + &VitaRenderer_EndDraw, +}; + +void VitaRenderer_Create(struct RendererFuncs *funcs) { + *funcs = kVitaRendererFuncs; +} + +int __wrap_mkdir(const char *fname, mode_t mode) { + char patched_fname[256]; + sprintf(patched_fname, "ux0:data/smetroid/%s", fname); + return __real_mkdir(patched_fname, mode); +} + +FILE *__wrap_fopen(char *fname, char *mode) { + //Use the vpk-provided ini + if(strcmp(fname, "sm.ini") == 0) { + return __real_fopen(fname, mode); + } + char patched_fname[256]; + sprintf(patched_fname, "ux0:data/smetroid/%s", fname); + return __real_fopen(patched_fname, mode); +} diff --git a/src/platform/vita/src/vita_impl.h b/src/platform/vita/src/vita_impl.h new file mode 100644 index 0000000..20c74e3 --- /dev/null +++ b/src/platform/vita/src/vita_impl.h @@ -0,0 +1,5 @@ +#pragma once + +void VitaImpl_Init(); +void VitaRenderer_Create(struct RendererFuncs *funcs); +bool VitaRenderer_BeforeFrame(void **memoryBuffer); diff --git a/src/sm_cpu_infra.c b/src/sm_cpu_infra.c index 6b48a43..92e7914 100644 --- a/src/sm_cpu_infra.c +++ b/src/sm_cpu_infra.c @@ -14,7 +14,11 @@ void RtlRunFrameCompare(uint16 input, int run_what); enum RunMode { RM_BOTH, RM_MINE, RM_THEIRS }; +#ifdef __VITA__ +uint8 g_runmode = RM_MINE; +#else uint8 g_runmode = RM_BOTH; +#endif extern int g_got_mismatch_count; diff --git a/src/snes/ppu.c b/src/snes/ppu.c index 95e5088..c69bbbe 100644 --- a/src/snes/ppu.c +++ b/src/snes/ppu.c @@ -245,8 +245,8 @@ void ppu_handleVblank(Ppu* ppu) { } static inline void ClearBackdrop(PpuPixelPrioBufs *buf) { - for (size_t i = 0; i != arraysize(buf->data); i += 4) - *(uint64*)&buf->data[i] = 0x0500050005000500; + for (size_t i = 0; i != arraysize(buf->data); i += 1) + buf->data[i] = 0x0000; } void ppu_runLine(Ppu* ppu, int line) { diff --git a/src/util.h b/src/util.h index 7ae3ecd..4147a53 100644 --- a/src/util.h +++ b/src/util.h @@ -8,6 +8,7 @@ typedef struct SDL_Window SDL_Window; struct RendererFuncs { bool (*Initialize)(SDL_Window *window); void (*Destroy)(); + void (*BeforeFrame)(void** buffer); void (*BeginDraw)(int width, int height, uint8 **pixels, int *pitch); void (*EndDraw)(); };