Vita porting

This commit is contained in:
2025-12-22 15:00:22 +01:00
parent 578f90b3cc
commit 73524105c9
17 changed files with 330 additions and 5 deletions

View File

@@ -95,3 +95,16 @@ Dependencies and requirements:
```sh ```sh
make -j$(nproc) # To build using all cores 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.

View File

@@ -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 # sm
Our discord server is: https://discord.gg/AJJbJAzNNJ 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 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. 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.

View File

@@ -25,6 +25,9 @@
#ifdef __SWITCH__ #ifdef __SWITCH__
#include "switch_impl.h" #include "switch_impl.h"
#endif #endif
#ifdef __VITA__
#include "platform/vita/src/vita_impl.h"
#endif
static void playAudio(Snes *snes, SDL_AudioDeviceID device, int16_t *audioBuffer); static void playAudio(Snes *snes, SDL_AudioDeviceID device, int16_t *audioBuffer);
static void renderScreen(Snes *snes, SDL_Renderer *renderer, SDL_Texture *texture); 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) { 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; uint8 *ppu_pixels = g_other_image ? g_my_pixels : g_pixels;
for (size_t y = 0; y < 240; y++) for (size_t y = 0; y < 240; y++)
memcpy((uint8_t *)pixel_buffer + y * pitch, ppu_pixels + y * 256 * 4, 256 * 4); 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); SDL_DestroyRenderer(g_renderer);
} }
static void SdlRenderer_BeforeFrame(void** ptr) {
}
static void SdlRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) { static void SdlRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) {
g_sdl_renderer_rect.w = width; g_sdl_renderer_rect.w = width;
g_sdl_renderer_rect.h = height; g_sdl_renderer_rect.h = height;
@@ -305,6 +314,7 @@ static void SdlRenderer_EndDraw(void) {
static const struct RendererFuncs kSdlRendererFuncs = { static const struct RendererFuncs kSdlRendererFuncs = {
&SdlRenderer_Init, &SdlRenderer_Init,
&SdlRenderer_Destroy, &SdlRenderer_Destroy,
&SdlRenderer_BeforeFrame,
&SdlRenderer_BeginDraw, &SdlRenderer_BeginDraw,
&SdlRenderer_EndDraw, &SdlRenderer_EndDraw,
}; };
@@ -315,6 +325,9 @@ static const struct RendererFuncs kSdlRendererFuncs = {
int main(int argc, char** argv) { int main(int argc, char** argv) {
#ifdef __SWITCH__ #ifdef __SWITCH__
SwitchImpl_Init(); SwitchImpl_Init();
#endif
#ifdef __VITA__
VitaImpl_Init();
#endif #endif
argc--, argv++; argc--, argv++;
const char *config_file = NULL; 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_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; 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) { if (g_config.output_method == kOutputMethod_OpenGL) {
g_win_flags |= SDL_WINDOW_OPENGL; g_win_flags |= SDL_WINDOW_OPENGL;
OpenGLRenderer_Create(&g_renderer_funcs); OpenGLRenderer_Create(&g_renderer_funcs);
} else { } else {
g_renderer_funcs = kSdlRendererFuncs; g_renderer_funcs = kSdlRendererFuncs;
} }
#endif
// init snes, load rom // init snes, load rom
const char* filename = argv[0] ? argv[0] : "sm.smc"; const char* filename = argv[0] ? argv[0] : "sm.smc";
@@ -507,8 +524,9 @@ int main(int argc, char** argv) {
g_gamepad_buttons = 0; g_gamepad_buttons = 0;
inputs |= g_gamepad_buttons; inputs |= g_gamepad_buttons;
uint8 is_replay = RtlRunFrame(inputs); g_renderer_funcs.BeforeFrame((void**)&snes->ppu->renderBuffer);
uint8 is_replay = RtlRunFrame(inputs);
frameCtr++; frameCtr++;
g_snes->disableRender = (g_turbo ^ (is_replay & g_replay_turbo)) && (frameCtr & (g_turbo ? 0xf : 0x7f)) != 0; 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); SDL_SetWindowTitle(g_window, kWindowTitle);
} }
} }
// if vsync isn't working, delay manually // if vsync isn't working, delay manually
curTick = SDL_GetTicks(); curTick = SDL_GetTicks();

View File

@@ -144,6 +144,9 @@ static bool OpenGLRenderer_Init(SDL_Window *window) {
static void OpenGLRenderer_Destroy(void) { static void OpenGLRenderer_Destroy(void) {
} }
static void OpenGLRenderer_BeforeFrame(void** ptr) {
}
static void OpenGLRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) { static void OpenGLRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) {
int size = width * height; int size = width * height;
@@ -207,6 +210,7 @@ static void OpenGLRenderer_EndDraw(void) {
static const struct RendererFuncs kOpenGLRendererFuncs = { static const struct RendererFuncs kOpenGLRendererFuncs = {
&OpenGLRenderer_Init, &OpenGLRenderer_Init,
&OpenGLRenderer_Destroy, &OpenGLRenderer_Destroy,
&OpenGLRenderer_BeforeFrame,
&OpenGLRenderer_BeginDraw, &OpenGLRenderer_BeginDraw,
&OpenGLRenderer_EndDraw, &OpenGLRenderer_EndDraw,
}; };

1
src/platform/vita/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/

View File

@@ -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
)

View File

@@ -0,0 +1,4 @@
# Super Metroid Vita porting
To run, you need sm.smc (sha1 hash da957f0d63d14cb441d215462904c4fa8519c613). Create a directory in <code>ux0:data/smetroid</code> and put the file in it.
Finally, install sm.vpk using VitaShell and the app bubble will appear in your LiveArea.

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<livearea style="a1" format-ver="01.00" content-rev="1">
<livearea-background>
<image>bg.png</image>
</livearea-background>
<gate>
<startup-image>startup.png</startup-image>
</gate>
</livearea>

44
src/platform/vita/sm.ini Normal file
View File

@@ -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

View File

@@ -0,0 +1,114 @@
#include <vitasdk.h>
#include <stdio.h>
#include <SDL.h>
#include <sys/stat.h>
#include <stdbool.h>
#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);
}

View File

@@ -0,0 +1,5 @@
#pragma once
void VitaImpl_Init();
void VitaRenderer_Create(struct RendererFuncs *funcs);
bool VitaRenderer_BeforeFrame(void **memoryBuffer);

View File

@@ -14,7 +14,11 @@
void RtlRunFrameCompare(uint16 input, int run_what); void RtlRunFrameCompare(uint16 input, int run_what);
enum RunMode { RM_BOTH, RM_MINE, RM_THEIRS }; enum RunMode { RM_BOTH, RM_MINE, RM_THEIRS };
#ifdef __VITA__
uint8 g_runmode = RM_MINE;
#else
uint8 g_runmode = RM_BOTH; uint8 g_runmode = RM_BOTH;
#endif
extern int g_got_mismatch_count; extern int g_got_mismatch_count;

View File

@@ -245,8 +245,8 @@ void ppu_handleVblank(Ppu* ppu) {
} }
static inline void ClearBackdrop(PpuPixelPrioBufs *buf) { static inline void ClearBackdrop(PpuPixelPrioBufs *buf) {
for (size_t i = 0; i != arraysize(buf->data); i += 4) for (size_t i = 0; i != arraysize(buf->data); i += 1)
*(uint64*)&buf->data[i] = 0x0500050005000500; buf->data[i] = 0x0000;
} }
void ppu_runLine(Ppu* ppu, int line) { void ppu_runLine(Ppu* ppu, int line) {

View File

@@ -8,6 +8,7 @@ typedef struct SDL_Window SDL_Window;
struct RendererFuncs { struct RendererFuncs {
bool (*Initialize)(SDL_Window *window); bool (*Initialize)(SDL_Window *window);
void (*Destroy)(); void (*Destroy)();
void (*BeforeFrame)(void** buffer);
void (*BeginDraw)(int width, int height, uint8 **pixels, int *pitch); void (*BeginDraw)(int width, int height, uint8 **pixels, int *pitch);
void (*EndDraw)(); void (*EndDraw)();
}; };