Merge pull request #21092 from white-axe/libretro-vfs

Use the libretro VFS interface in libretro builds
This commit is contained in:
Henrik Rydgård
2026-01-10 16:36:01 +01:00
committed by GitHub
45 changed files with 682 additions and 4773 deletions
+13
View File
@@ -256,6 +256,7 @@ if(LIBRETRO)
if(NOT MSVC)
add_compile_options(-fPIC)
endif()
add_compile_definitions(HAVE_LIBRETRO_VFS)
endif()
if(ANDROID)
@@ -944,6 +945,10 @@ add_library(Common STATIC
Common/TimeUtil.h
)
if(LIBRETRO)
target_include_directories(Common PRIVATE libretro/libretro-common/include)
endif()
include_directories(Common)
setup_target_project(Common Common)
@@ -1685,6 +1690,10 @@ add_library(native STATIC
ext/jpge/jpge.h
)
if(LIBRETRO)
target_include_directories(native PRIVATE libretro/libretro-common/include)
endif()
if(LINUX AND NOT ANDROID)
set(RT_LIB rt)
endif()
@@ -2511,6 +2520,10 @@ add_library(${CoreLibName} ${CoreLinkType}
${CMAKE_CURRENT_BINARY_DIR}/git-version.cpp
)
if(LIBRETRO)
target_include_directories(${CoreLibName} PRIVATE libretro/libretro-common/include)
endif()
if(ANDROID)
set(CoreExtraLibs ${CoreExtraLibs} android)
if(X86_64)
-1
View File
@@ -14,7 +14,6 @@
#endif
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
+41 -13
View File
@@ -134,12 +134,7 @@ bool PNGHeaderPeek::IsValidPNGHeader() const {
}
bool pngSave(const Path &filename, const void *buffer, int w, int h, int bytesPerPixel) {
png_image png{};
png.version = PNG_IMAGE_VERSION;
png.format = bytesPerPixel == 3 ? PNG_FORMAT_RGB : PNG_FORMAT_RGBA;
png.width = w;
png.height = h;
const int row_stride = w * bytesPerPixel;
png_bytepp row_ptrs = nullptr;
FILE *fp = File::OpenCFile(filename, "wb");
if (!fp) {
@@ -147,16 +142,28 @@ bool pngSave(const Path &filename, const void *buffer, int w, int h, int bytesPe
return false;
}
int result = png_image_write_to_stdio(&png, fp, 0, buffer, row_stride, nullptr);
if (png.warning_or_error >= 2) {
ERROR_LOG(Log::IO, "Saving image to PNG produced errors.");
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, pngErrorHandler, pngWarningHandler);
if (png_ptr == nullptr) {
fclose(fp);
ERROR_LOG(Log::IO, "PNG encode failed.");
return false;
}
png_image_free(&png);
fclose(fp);
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == nullptr) {
png_destroy_write_struct(&png_ptr, nullptr);
fclose(fp);
ERROR_LOG(Log::IO, "PNG encode failed.");
return false;
}
if (setjmp(png_jmpbuf(png_ptr))) {
if (row_ptrs != nullptr) {
png_free(png_ptr, row_ptrs);
}
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
if (!result) {
// Should we even do this?
File::Delete(filename);
@@ -164,5 +171,26 @@ bool pngSave(const Path &filename, const void *buffer, int w, int h, int bytesPe
return false;
}
png_set_write_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data, png_size_t size) {
if (fwrite(data, 1, size, (FILE *)png_get_io_ptr(png_ptr)) < size) {
png_error(png_ptr, "Failed to write to file.");
}
}, [](png_structp png_ptr) {
fflush((FILE *)png_get_io_ptr(png_ptr));
});
png_set_IHDR(png_ptr, info_ptr, w, h, 8, bytesPerPixel == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
row_ptrs = (png_bytepp)png_malloc(png_ptr, (png_alloc_size_t)h * (png_alloc_size_t)sizeof(png_bytep));
for (png_alloc_size_t i = 0; i < h; ++i) {
row_ptrs[i] = (png_bytep)buffer + (png_alloc_size_t)w * (png_alloc_size_t)bytesPerPixel * i;
}
png_set_rows(png_ptr, info_ptr, row_ptrs);
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
png_free(png_ptr, row_ptrs);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return true;
}
+169 -54
View File
@@ -77,6 +77,10 @@
#endif // !PPSSPP_PLATFORM(IOS)
#endif // __APPLE__
#ifdef HAVE_LIBRETRO_VFS
#include <file/file_path.h>
#endif
#include "Common/Data/Encoding/Utf8.h"
#include <sys/stat.h>
@@ -105,11 +109,33 @@ constexpr bool LOG_IO = false;
#define DIR_SEP_CHRS "/"
#endif
#ifdef HAVE_LIBRETRO_VFS
static retro_vfs_mkdir_t LibretroMkdirCallback = nullptr;
// Creates a directory at the given path. Parent directories are not created if
// they are missing. If the libretro VFS is supported by the libretro frontend,
// it will be used; if the libretro VFS is not supported by the frontend, the
// mkdir function will be used instead. Returns 0 if the directory did not exist
// and was successfully created, -2 if the directory already exists or -1 if
// some other error occurred.
static int LibretroMkdir(const char *path) noexcept {
return LibretroMkdirCallback != nullptr ? LibretroMkdirCallback(path) : retro_vfs_mkdir_impl(path);
}
#endif
// This namespace has various generic functions related to files and paths.
// The code still needs a ton of cleanup.
// REMEMBER: strdup considered harmful!
namespace File {
#ifdef HAVE_LIBRETRO_VFS
void InitLibretroVFS(const struct retro_vfs_interface_info *vfs) noexcept {
filestream_vfs_init(vfs);
path_vfs_init(vfs);
LibretroMkdirCallback = vfs->required_interface_version >= 3 ? vfs->iface->mkdir : nullptr;
}
#endif
FILE *OpenCFile(const Path &path, const char *mode) {
if (LOG_IO) {
INFO_LOG(Log::IO, "OpenCFile %s, %s", path.c_str(), mode);
@@ -120,6 +146,7 @@ FILE *OpenCFile(const Path &path, const char *mode) {
switch (path.Type()) {
case PathType::NATIVE:
break;
#ifndef HAVE_LIBRETRO_VFS
case PathType::CONTENT_URI:
// We're gonna need some error codes..
if (!strcmp(mode, "r") || !strcmp(mode, "rb") || !strcmp(mode, "rt")) {
@@ -167,7 +194,7 @@ FILE *OpenCFile(const Path &path, const char *mode) {
FILE *f = fdopen(descriptor, fmode);
if (f && (!strcmp(mode, "at") || !strcmp(mode, "a"))) {
// Append mode - not sure we got a "true" append mode, so seek to the end.
fseek(f, 0, SEEK_END);
Fseek(f, 0, SEEK_END);
}
return f;
} else {
@@ -175,12 +202,29 @@ FILE *OpenCFile(const Path &path, const char *mode) {
return nullptr;
}
break;
#endif
default:
ERROR_LOG(Log::IO, "OpenCFile(%s): PathType not yet supported", path.c_str());
return nullptr;
}
#if defined(_WIN32) && defined(UNICODE)
#ifdef HAVE_LIBRETRO_VFS
if (!strcmp(mode, "r") || !strcmp(mode, "rb") || !strcmp(mode, "rt")) {
INFO_LOG(Log::IO, "OpenCFile(%s): Opening content file for read.", path.c_str());
return filestream_open(path.c_str(), RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
} else if (!strcmp(mode, "w") || !strcmp(mode, "wb") || !strcmp(mode, "wt") || !strcmp(mode, "at") || !strcmp(mode, "a")) {
INFO_LOG(Log::IO, "OpenCFile(%s): Opening content file for write.", path.c_str());
bool append = !strcmp(mode, "at") || !strcmp(mode, "a");
FILE *f = filestream_open(path.c_str(), append && Exists(path) ? RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (f != nullptr && append) {
Fseek(f, 0, SEEK_END);
}
return f;
} else {
ERROR_LOG(Log::IO, "OpenCFile(%s): Mode not yet supported: %s", path.c_str(), mode);
return nullptr;
}
#elif defined(_WIN32) && defined(UNICODE)
#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)
// We shouldn't use _wfopen here,
// this function is not allowed to read outside Local and Installation folders
@@ -438,7 +482,9 @@ bool Exists(const Path &path) {
return Android_FileExists(path.c_str());
}
#if defined(_WIN32)
#ifdef HAVE_LIBRETRO_VFS
return path_is_valid(path.c_str());
#elif defined(_WIN32)
// Make sure Windows will no longer handle critical errors, which means no annoying "No disk" dialog
#if !PPSSPP_PLATFORM(UWP)
@@ -488,7 +534,9 @@ bool IsDirectory(const Path &path) {
return false;
}
#if defined(_WIN32)
#ifdef HAVE_LIBRETRO_VFS
return path_is_directory(path.c_str());
#elif defined(_WIN32)
WIN32_FILE_ATTRIBUTE_DATA data{};
#if PPSSPP_PLATFORM(UWP)
if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
@@ -545,7 +593,12 @@ bool Delete(const Path &filename, bool quiet) {
return false;
}
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
if (filestream_delete(filename.c_str()) != 0) {
WARN_LOG(Log::IO, "Delete: DeleteFile failed on %s", filename.c_str());
return false;
}
#elif defined(_WIN32)
#if PPSSPP_PLATFORM(UWP)
if (!DeleteFileFromAppW(filename.ToWString().c_str())) {
WARN_LOG(Log::IO, "Delete: DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg().c_str());
@@ -605,7 +658,17 @@ bool CreateDir(const Path &path) {
}
DEBUG_LOG(Log::IO, "CreateDir('%s')", path.c_str());
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
switch (LibretroMkdir(path.ToString().c_str())) {
case -2:
DEBUG_LOG(Log::IO, "CreateDir: mkdir failed on %s: already exists", path.c_str());
case 0:
return true;
default:
ERROR_LOG(Log::IO, "CreateDir: mkdir failed on %s", path.c_str());
return false;
}
#elif defined(_WIN32)
#if PPSSPP_PLATFORM(UWP)
if (CreateDirectoryFromAppW(path.ToWString().c_str(), NULL))
return true;
@@ -717,6 +780,13 @@ bool Rename(const Path &srcFilename, const Path &destFilename) {
INFO_LOG(Log::IO, "Rename: %s --> %s", srcFilename.c_str(), destFilename.c_str());
#ifdef HAVE_LIBRETRO_VFS
if (filestream_rename(srcFilename.c_str(), destFilename.c_str()) == 0)
return true;
ERROR_LOG(Log::IO, "Rename: failed %s --> %s",
srcFilename.c_str(), destFilename.c_str());
return false;
#else
#if defined(_WIN32) && defined(UNICODE)
#if PPSSPP_PLATFORM(UWP)
if (MoveFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str()))
@@ -735,6 +805,7 @@ bool Rename(const Path &srcFilename, const Path &destFilename) {
ERROR_LOG(Log::IO, "Rename: failed %s --> %s: %s",
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str());
return false;
#endif
}
// copies file srcFilename to destFilename, returns true on success
@@ -764,7 +835,7 @@ bool Copy(const Path &srcFilename, const Path &destFilename) {
}
INFO_LOG(Log::IO, "Copy by OpenCFile: %s --> %s", srcFilename.c_str(), destFilename.c_str());
#ifdef _WIN32
#if defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS)
#if PPSSPP_PLATFORM(UWP)
if (CopyFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE))
return true;
@@ -908,7 +979,9 @@ uint64_t GetFileSize(const Path &filename) {
return false;
}
#if defined(_WIN32) && defined(UNICODE)
#ifdef HAVE_LIBRETRO_VFS
return path_get_size(filename.c_str());
#elif defined(_WIN32) && defined(UNICODE)
WIN32_FILE_ATTRIBUTE_DATA attr;
#if PPSSPP_PLATFORM(UWP)
if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &attr))
@@ -941,45 +1014,19 @@ uint64_t GetFileSize(const Path &filename) {
}
uint64_t GetFileSize(FILE *f) {
// This will only support 64-bit when large file support is available.
// That won't be the case on some versions of Android, at least.
#if defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
int fd = fileno(f);
off64_t pos = lseek64(fd, 0, SEEK_CUR);
off64_t size = lseek64(fd, 0, SEEK_END);
if (size != pos && lseek64(fd, pos, SEEK_SET) != pos) {
uint64_t pos = Ftell(f);
if (Fseek(f, 0, SEEK_END) != 0) {
return 0;
}
uint64_t size = Ftell(f);
// Reset the seek position to where it was when we started.
if (size != pos && Fseek(f, pos, SEEK_SET) != 0) {
// Should error here.
return 0;
}
if (size == -1)
return 0;
return size;
#else
#ifdef _WIN32
uint64_t pos = _ftelli64(f);
#else
uint64_t pos = ftello(f);
#endif
if (fseek(f, 0, SEEK_END) != 0) {
return 0;
}
#ifdef _WIN32
uint64_t size = _ftelli64(f);
// Reset the seek position to where it was when we started.
if (size != pos && _fseeki64(f, pos, SEEK_SET) != 0) {
#else
uint64_t size = ftello(f);
// Reset the seek position to where it was when we started.
if (size != pos && fseeko(f, pos, SEEK_SET) != 0) {
#endif
// Should error here.
return 0;
}
if (size == -1)
return 0;
return size;
#endif
}
// creates an empty file filename, returns true on success
@@ -1019,6 +1066,12 @@ bool DeleteDir(const Path &path) {
return false;
}
#ifdef HAVE_LIBRETRO_VFS
if (filestream_delete(path.c_str()) == 0)
return true;
ERROR_LOG(Log::IO, "DeleteDir: %s", path.c_str());
return false;
#else
#ifdef _WIN32
#if PPSSPP_PLATFORM(UWP)
if (RemoveDirectoryFromAppW(path.ToWString().c_str()))
@@ -1034,6 +1087,7 @@ bool DeleteDir(const Path &path) {
ERROR_LOG(Log::IO, "DeleteDir: %s: %s", path.c_str(), GetLastErrorMsg().c_str());
return false;
#endif
}
// Deletes the given directory and anything under it. Returns true on success.
@@ -1166,6 +1220,64 @@ const Path &GetExeDirectory() {
return ExePath;
}
int Fseek(FILE *file, int64_t offset, int whence) {
#ifdef HAVE_LIBRETRO_VFS
switch (whence) {
default:
whence = RETRO_VFS_SEEK_POSITION_START;
break;
case SEEK_CUR:
whence = RETRO_VFS_SEEK_POSITION_CURRENT;
break;
case SEEK_END:
whence = RETRO_VFS_SEEK_POSITION_END;
break;
}
return filestream_seek(file, offset, whence) != 0 ? -1 : 0;
#elif defined(_WIN32)
return _fseeki64(file, offset, whence);
#elif (defined(__ANDROID__) && __ANDROID_API__ < 24) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
return fseek(file, offset, whence);
#else
return fseeko(file, offset, whence);
#endif
}
int64_t Fseektell(FILE *file, int64_t offset, int whence) {
#ifdef HAVE_LIBRETRO_VFS
switch (whence) {
default:
whence = RETRO_VFS_SEEK_POSITION_START;
break;
case SEEK_CUR:
whence = RETRO_VFS_SEEK_POSITION_CURRENT;
break;
case SEEK_END:
whence = RETRO_VFS_SEEK_POSITION_END;
break;
}
return filestream_seek(file, offset, whence) != 0 ? -1 : filestream_tell(file);
#elif defined(_WIN32)
return _fseeki64(file, offset, whence) != 0 ? -1 : _ftelli64(file);
#elif (defined(__ANDROID__) && __ANDROID_API__ < 24) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
return fseek(file, offset, whence) != 0 ? -1 : ftell(file);
#else
return fseeko(file, offset, whence) != 0 ? -1 : ftello(file);
#endif
}
int64_t Ftell(FILE *file) {
#ifdef HAVE_LIBRETRO_VFS
return filestream_tell(file);
#elif defined(_WIN32)
return _ftelli64(file);
#elif defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
return ftell(file);
#else
return ftello(file);
#endif
}
IOFile::IOFile(const Path &filename, const char openmode[]) {
Open(filename, openmode);
@@ -1185,21 +1297,21 @@ bool IOFile::Open(const Path& filename, const char openmode[])
bool IOFile::Close()
{
if (!IsOpen() || 0 != std::fclose(m_file))
if (!IsOpen() || 0 != fclose(m_file))
m_good = false;
m_file = NULL;
return m_good;
}
std::FILE* IOFile::ReleaseHandle()
FILE* IOFile::ReleaseHandle()
{
std::FILE* const ret = m_file;
FILE* const ret = m_file;
m_file = NULL;
return ret;
}
void IOFile::SetHandle(std::FILE* file)
void IOFile::SetHandle(FILE* file)
{
Close();
Clear();
@@ -1216,7 +1328,7 @@ uint64_t IOFile::GetSize()
bool IOFile::Seek(int64_t off, int origin)
{
if (!IsOpen() || 0 != fseeko(m_file, off, origin))
if (!IsOpen() || 0 != Fseek(m_file, off, origin))
m_good = false;
return m_good;
@@ -1225,14 +1337,14 @@ bool IOFile::Seek(int64_t off, int origin)
uint64_t IOFile::Tell()
{
if (IsOpen())
return ftello(m_file);
return Ftell(m_file);
else
return -1;
}
bool IOFile::Flush()
{
if (!IsOpen() || 0 != std::fflush(m_file))
if (!IsOpen() || 0 != fflush(m_file))
m_good = false;
return m_good;
@@ -1241,7 +1353,9 @@ bool IOFile::Flush()
bool IOFile::Resize(uint64_t size)
{
if (!IsOpen() || 0 !=
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
filestream_truncate(m_file, size)
#elif defined(_WIN32)
// ector: _chsize sucks, not 64-bit safe
// F|RES: changed to _chsize_s. i think it is 64-bit safe
_chsize_s(_fileno(m_file), size)
@@ -1282,7 +1396,7 @@ bool ReadFileToStringOptions(bool textFile, bool allowShort, const Path &filenam
if (textFile) {
// totalRead doesn't take \r into account since they might be skipped in this mode.
// So let's just ask how far the cursor got.
totalRead = ftell(f);
totalRead = Ftell(f);
}
success = allowShort ? (totalRead <= len) : (totalRead == len);
}
@@ -1296,14 +1410,13 @@ uint8_t *ReadLocalFile(const Path &filename, size_t *size) {
*size = 0;
return nullptr;
}
fseek(file, 0, SEEK_END);
size_t f_size = ftell(file);
if ((long)f_size < 0) {
int64_t f_size = Fseektell(file, 0, SEEK_END);
if (f_size < 0) {
*size = 0;
fclose(file);
return nullptr;
}
fseek(file, 0, SEEK_SET);
Fseek(file, 0, SEEK_SET);
// NOTE: If you find ~10 memory leaks from here, with very varying sizes, it might be the VFPU LUTs.
uint8_t *contents = new uint8_t[f_size + 1];
if (fread(contents, 1, f_size, file) != f_size) {
@@ -1351,6 +1464,7 @@ void ChangeMTime(const Path &path, time_t mtime) {
return;
}
#ifndef HAVE_LIBRETRO_VFS
#ifdef _WIN32
_utimbuf buf{};
buf.actime = mtime;
@@ -1362,6 +1476,7 @@ void ChangeMTime(const Path &path, time_t mtime) {
buf.modtime = mtime;
utime(path.c_str(), &buf);
#endif
#endif
}
bool IsProbablyInDownloadsFolder(const Path &filename) {
+27 -7
View File
@@ -17,7 +17,6 @@
#pragma once
#include <fstream>
#include <cstdio>
#include <string>
#include <string_view>
@@ -137,6 +136,19 @@ const Path &GetExeDirectory();
const Path GetCurDirectory();
// Portable version of fseek() that works with 64-bit offsets if supported by
// the operating system.
int Fseek(FILE *file, int64_t offset, int whence);
// Portable version of fseek() that works with 64-bit offsets if supported by
// the operating system, and returns the new offset from the start of the file
// or -1 if it failed.
int64_t Fseektell(FILE *file, int64_t offset, int whence);
// Portable version of ftell() that works with 64-bit offsets if supported by
// the operating system.
int64_t Ftell(FILE *file);
// simple wrapper for cstdlib file functions to
// hopefully will make error checking easier
// and make forgetting an fclose() harder
@@ -156,7 +168,7 @@ public:
template <typename T>
bool ReadArray(T* data, size_t length)
{
if (!IsOpen() || length != std::fread(data, sizeof(T), length, m_file))
if (!IsOpen() || length != fread(data, sizeof(T), length, m_file))
m_good = false;
return m_good;
@@ -165,7 +177,7 @@ public:
template <typename T>
bool WriteArray(const T* data, size_t length)
{
if (!IsOpen() || length != std::fwrite(data, sizeof(T), length, m_file))
if (!IsOpen() || length != fwrite(data, sizeof(T), length, m_file))
m_good = false;
return m_good;
@@ -187,11 +199,11 @@ public:
bool IsGood() const { return m_good; }
operator bool() const { return IsGood() && IsOpen(); }
std::FILE* ReleaseHandle();
FILE* ReleaseHandle();
std::FILE* GetHandle() { return m_file; }
FILE* GetHandle() { return m_file; }
void SetHandle(std::FILE* file);
void SetHandle(FILE* file);
bool Seek(int64_t off, int origin);
uint64_t Tell();
@@ -202,15 +214,23 @@ public:
// clear error state
void Clear() {
m_good = true;
#ifndef HAVE_LIBRETRO_VFS
#undef clearerr
std::clearerr(m_file);
#endif
}
private:
std::FILE *m_file = nullptr;
FILE *m_file = nullptr;
bool m_good = true;
};
#ifdef HAVE_LIBRETRO_VFS
// Call this on libretro core initialization with the libretro virtual file
// system interface.
void InitLibretroVFS(const struct retro_vfs_interface_info *vfs) noexcept;
#endif
// TODO: Refactor, this was moved from the old file_util.cpp.
// Whole-file reading/writing
+4
View File
@@ -5,6 +5,10 @@
#include <string>
#include <string_view>
#ifdef HAVE_LIBRETRO_VFS
#include "libretro/LibretroFileStreamTransforms.h"
#endif
#if defined(__APPLE__)
#if TARGET_OS_IPHONE
+4 -4
View File
@@ -77,9 +77,9 @@ VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference, si
if (!file) {
return nullptr;
}
fseek(file, 0, SEEK_END);
*size = ftell(file);
fseek(file, 0, SEEK_SET);
File::Fseek(file, 0, SEEK_END);
*size = File::Ftell(file);
File::Fseek(file, 0, SEEK_SET);
DirectoryReaderOpenFile *openFile = new DirectoryReaderOpenFile();
openFile->file = file;
return openFile;
@@ -87,7 +87,7 @@ VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference, si
void DirectoryReader::Rewind(VFSOpenFile *vfsOpenFile) {
DirectoryReaderOpenFile *openFile = (DirectoryReaderOpenFile *)vfsOpenFile;
fseek(openFile->file, 0, SEEK_SET);
File::Fseek(openFile->file, 0, SEEK_SET);
}
size_t DirectoryReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) {
+132 -39
View File
@@ -15,41 +15,149 @@
#include "Common/File/VFS/ZipFileReader.h"
#include "Common/StringUtils.h"
ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath, bool logErrors) {
int error = 0;
zip *zip_file;
if (zipFile.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(zipFile, File::OPEN_READ);
if (!fd) {
if (logErrors) {
ERROR_LOG(Log::IO, "Failed to open FD for '%s' as zip file", zipFile.c_str());
ZipContainer::ZipContainer() noexcept : sourceData_(nullptr), zip_(nullptr) {}
ZipContainer::ZipContainer(const Path &path) : sourceData_(new SourceData {path, nullptr}), zip_(nullptr) {
zip_source_t *source = zip_source_function_create(SourceCallback, sourceData_, nullptr);
if (source != nullptr && (zip_ = zip_open_from_source(source, ZIP_RDONLY, nullptr)) == nullptr) {
zip_source_free(source);
}
}
ZipContainer::ZipContainer(ZipContainer &&other) noexcept {
*this = std::move(other);
}
ZipContainer &ZipContainer::operator=(ZipContainer &&other) noexcept {
sourceData_ = other.sourceData_;
zip_ = other.zip_;
other.sourceData_ = nullptr;
other.zip_ = nullptr;
return *this;
}
ZipContainer::~ZipContainer() {
close();
}
void ZipContainer::close() noexcept {
if (zip_ != nullptr) {
zip_close(zip_);
zip_ = nullptr;
}
if (sourceData_ != nullptr) {
delete sourceData_;
sourceData_ = nullptr;
}
}
ZipContainer::operator zip_t *() const noexcept {
return zip_;
}
zip_int64_t ZipContainer::SourceCallback(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd) {
SourceData &sourceData = *(SourceData *)userdata;
switch (cmd) {
case ZIP_SOURCE_SUPPORTS:
return zip_source_make_command_bitmap(
ZIP_SOURCE_SUPPORTS,
ZIP_SOURCE_OPEN,
ZIP_SOURCE_READ,
ZIP_SOURCE_CLOSE,
ZIP_SOURCE_STAT,
ZIP_SOURCE_ERROR,
ZIP_SOURCE_SEEK,
ZIP_SOURCE_TELL,
-1);
case ZIP_SOURCE_OPEN:
{
FILE *newFile = File::OpenCFile(sourceData.path, "rb");
if (newFile == nullptr) {
return -1;
}
sourceData.file = newFile;
return 0;
}
return nullptr;
}
zip_file = zip_fdopen(fd, 0, &error);
} else {
zip_file = zip_open(zipFile.c_str(), 0, &error);
}
if (!zip_file) {
if (logErrors) {
ERROR_LOG(Log::IO, "Failed to open %s as a zip file", zipFile.c_str());
}
return nullptr;
}
case ZIP_SOURCE_READ:
{
size_t n = fread(data, 1, len, sourceData.file);
return ferror(sourceData.file) ? -1 : n;
}
case ZIP_SOURCE_CLOSE:
fclose(sourceData.file);
sourceData.file = nullptr;
return 0;
case ZIP_SOURCE_STAT:
if (sourceData.file == nullptr) {
zip_stat_t *stat = (zip_stat_t *)data;
zip_stat_init(stat);
stat->valid = 0;
} else {
int64_t pos = File::Ftell(sourceData.file);
if (pos == -1) {
return -1;
}
if (File::Fseek(sourceData.file, 0, SEEK_END) != 0) {
return -1;
}
int64_t size = File::Ftell(sourceData.file);
if (size != pos && File::Fseek(sourceData.file, pos, SEEK_SET) != 0) {
return -1;
}
zip_stat_t *stat = (zip_stat_t *)data;
zip_stat_init(stat);
stat->valid = ZIP_STAT_SIZE;
stat->size = size;
}
return 0;
case ZIP_SOURCE_ERROR:
return ZIP_ER_INTERNAL;
case ZIP_SOURCE_SEEK:
{
zip_source_args_seek_t *args = (zip_source_args_seek_t *)data;
if (args == nullptr) {
return -1;
}
return File::Fseek(sourceData.file, args->offset, args->whence) ? -1 : 0;
}
case ZIP_SOURCE_TELL:
return File::Ftell(sourceData.file);
default:
return -1;
}
}
ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath, bool logErrors) {
// The inZipPath is supposed to be a folder, and internally in this class, we suffix
// folder paths with '/', matching how the zip library works.
std::string path = inZipPath;
if (!path.empty() && path.back() != '/') {
path.push_back('/');
}
return new ZipFileReader(zip_file, zipFile, path);
ZipContainer zip(zipFile);
if (zip == nullptr) {
if (logErrors) {
ERROR_LOG(Log::IO, "Failed to open %s as a zip file", zipFile.c_str());
}
return nullptr;
}
return new ZipFileReader(std::move(zip), zipFile, path);
}
ZipFileReader::~ZipFileReader() {
std::lock_guard<std::mutex> guard(lock_);
zip_close(zip_file_);
zip_file_.close();
}
uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) {
@@ -320,30 +428,17 @@ void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) {
}
bool ReadSingleFileFromZip(Path zipFile, const char *path, std::string *data, std::mutex *mutex) {
zip *zip = nullptr;
int error = 0;
if (zipFile.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(zipFile, File::OPEN_READ);
if (!fd) {
return false;
}
zip = zip_fdopen(fd, 0, &error);
} else {
zip = zip_open(zipFile.c_str(), 0, &error);
}
if (!zip) {
ZipContainer zip(zipFile);
if (zip == nullptr) {
return false;
}
struct zip_stat zstat;
if (zip_stat(zip, path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat) != 0) {
zip_close(zip);
return false;
}
zip_file *file = zip_fopen_index(zip, zstat.index, ZIP_FL_UNCHANGED);
if (!file) {
zip_close(zip);
return false;
}
if (mutex) {
@@ -356,13 +451,11 @@ bool ReadSingleFileFromZip(Path zipFile, const char *path, std::string *data, st
}
data->resize(0);
zip_fclose(file);
zip_close(zip);
return false;
}
if (mutex) {
mutex->unlock();
}
zip_fclose(file);
zip_close(zip);
return true;
}
+25 -2
View File
@@ -9,11 +9,34 @@
#include <mutex>
#include <set>
#include <string>
#include <utility>
#include "Common/File/VFS/VFS.h"
#include "Common/File/FileUtil.h"
#include "Common/File/Path.h"
class ZipContainer {
public:
ZipContainer() noexcept;
ZipContainer(const Path &path);
~ZipContainer();
ZipContainer(const ZipContainer &) = delete;
ZipContainer(ZipContainer &&) noexcept;
ZipContainer &operator=(const ZipContainer &) = delete;
ZipContainer &operator=(ZipContainer &&) noexcept;
void close() noexcept;
operator zip_t *() const noexcept;
private:
struct SourceData {
Path path;
FILE *file;
} *sourceData_;
zip_t *zip_;
static zip_int64_t SourceCallback(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd);
};
class ZipFileReader : public VFSBackend {
public:
static ZipFileReader *Create(const Path &zipFile, const char *inZipPath, bool logErrors = true);
@@ -45,11 +68,11 @@ public:
}
private:
ZipFileReader(zip *zip_file, const Path &zipPath, const std::string &inZipPath) : zip_file_(zip_file), zipPath_(zipPath), inZipPath_(inZipPath) {}
ZipFileReader(ZipContainer &&zip, const Path &zipPath, const std::string &inZipPath) : zip_file_(std::move(zip)), zipPath_(zipPath), inZipPath_(inZipPath) {}
// Path has to be either an empty string, or a string ending with a /.
bool GetZipListings(const std::string &path, std::set<std::string> &files, std::set<std::string> &directories);
zip *zip_file_ = nullptr;
ZipContainer zip_file_;
std::mutex lock_;
std::string inZipPath_;
Path zipPath_;
+4
View File
@@ -382,6 +382,10 @@ void OutputDebugStringUTF8(const char *p) {
#endif
#ifdef HAVE_LIBRETRO_VFS
#undef fprintf
#endif
void LogManager::StdioLog(const LogMessage &message) {
#if PPSSPP_PLATFORM(ANDROID)
#ifndef LOG_APP_NAME
+2 -1
View File
@@ -20,6 +20,7 @@
#include "Common/Data/Convert/ColorConv.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/File/FileUtil.h"
#include "Common/File/VFS/VFS.h"
#include "Common/Render/AtlasGen.h"
@@ -74,7 +75,7 @@ void Image::SaveZIM(const char *zim_name, int zim_format) {
for (int y = 0; y < height(); y++) {
memcpy(image_data + y * width() * 4, (dat.data() + y * w), width() * 4);
}
FILE *f = fopen(zim_name, "wb");
FILE *f = File::OpenCFile(Path(zim_name), "wb");
// SaveZIM takes ownership over image_data, there's no leak.
::SaveZIM(f, width(), height(), width() * 4, zim_format | ZIM_DITHER, image_data);
fclose(f);
+2 -1
View File
@@ -6,6 +6,7 @@
#include "Common/Data/Format/PNGLoad.h"
#include "Common/Log.h"
#include "Common/GPU/thin3d.h"
#include "Common/File/FileUtil.h"
#define ICON_CACHE_VERSION 1
#define MK_FOURCC(str) (str[0] | ((uint8_t)str[1] << 8) | ((uint8_t)str[2] << 16) | ((uint8_t)str[3] << 24))
@@ -88,7 +89,7 @@ bool IconCache::LoadFromFile(FILE *file) {
// Check if we already have the entry somehow.
if (cache_.find(key) != cache_.end()) {
// Seek past the data and go to the next entry.
fseek(file, entryHeader.dataLen, SEEK_CUR);
File::Fseek(file, entryHeader.dataLen, SEEK_CUR);
continue;
}
+2
View File
@@ -6,6 +6,8 @@
#include <mutex>
#include <cstdint>
#include "Common/File/Path.h"
class UIContext;
+70 -19
View File
@@ -36,6 +36,8 @@
#include "zlib.h"
#include "ext/armips/Core/Assembler.h"
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/File/FileUtil.h"
@@ -44,7 +46,6 @@
#include "Core/MemMap.h"
#include "Core/Config.h"
#include "Core/Debugger/SymbolMap.h"
#include "ext/armips/Core/Assembler.h"
SymbolMap *g_symbolMap;
@@ -228,30 +229,80 @@ bool SymbolMap::SaveSymbolMap(const Path &filename) const {
std::string data;
buf.TakeAll(&data);
FILE *file = File::OpenCFile(filename, "wb");
if (file == nullptr) {
return false;
}
if (g_Config.bCompressSymbols) {
// TODO: Wrap this in some nicer way.
gzFile f;
if (filename.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(filename, File::OPEN_WRITE);
f = gzdopen(fd, "w9");
if (f == Z_NULL) {
File::CloseFD(fd);
return false;
}
} else {
f = gzopen(filename.c_str(), "w9");
if (f == Z_NULL) {
return false;
}
uInt out_size = 4096;
Bytef *out_data = (Bytef *)std::malloc(out_size);
if (out_data == nullptr) {
fclose(file);
return false;
}
gzwrite(f, data.data(), (unsigned int)data.size());
gzclose(f);
z_stream strm;
strm.zalloc = nullptr;
strm.zfree = nullptr;
strm.opaque = nullptr;
if (deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
std::free(out_data);
fclose(file);
return false;
}
strm.next_in = (Bytef *)data.data();
strm.avail_in = data.size();
strm.next_out = out_data;
strm.avail_out = out_size;
int flush = Z_NO_FLUSH;
for (;;) {
int status = deflate(&strm, flush);
switch (status) {
case Z_OK:
case Z_STREAM_END:
if (strm.avail_out != out_size) {
fwrite(out_data, 1, out_size - strm.avail_out, file);
}
break;
case Z_BUF_ERROR:
{
std::free(out_data);
uInt new_out_size = 2 * out_size;
if (new_out_size < out_size) {
deflateEnd(&strm);
fclose(file);
return false;
}
out_size = new_out_size;
out_data = (Bytef *)std::malloc(out_size);
if (out_data == nullptr) {
deflateEnd(&strm);
fclose(file);
return false;
}
}
break;
default:
deflateEnd(&strm);
std::free(out_data);
fclose(file);
return false;
}
if (status == Z_STREAM_END) {
break;
}
if (strm.avail_in == 0) {
flush = Z_FINISH;
}
strm.next_out = out_data;
strm.avail_out = out_size;
}
deflateEnd(&strm);
std::free(out_data);
} else {
// Just plain write it.
FILE *file = File::OpenCFile(filename, "wb");
fwrite(data.data(), 1, data.size(), file);
fclose(file);
}
fclose(file);
return true;
}
+7 -37
View File
@@ -37,11 +37,6 @@
#include <fileapifromapp.h>
#endif
#if PPSSPP_PLATFORM(SWITCH)
// Far from optimal, but I guess it works...
#define fseeko fseek
#endif
static const char * const CACHEFILE_MAGIC = "ppssppDC";
static const s64 SAFETY_FREE_DISK_SPACE = 768 * 1024 * 1024; // 768 MB
// Aim to allow this many files cached at once.
@@ -203,7 +198,7 @@ void DiskCachingFileLoaderCache::InitCache(const Path &filename) {
void DiskCachingFileLoaderCache::ShutdownCache() {
if (f_) {
bool failed = false;
if (fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {
if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {
failed = true;
} else if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) {
failed = true;
@@ -460,19 +455,11 @@ bool DiskCachingFileLoaderCache::ReadBlockData(u8 *dest, BlockInfo &info, size_t
fflush(f_);
bool failed = false;
#ifdef __ANDROID__
if (lseek64(fd_, blockOffset, SEEK_SET) != blockOffset) {
failed = true;
} else if (read(fd_, dest + offset, size) != (ssize_t)size) {
failed = true;
}
#else
if (fseeko(f_, blockOffset, SEEK_SET) != 0) {
if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) {
failed = true;
} else if (fread(dest + offset, size, 1, f_) != 1) {
failed = true;
}
#endif
if (failed) {
ERROR_LOG(Log::Loader, "Unable to read disk cache data entry.");
@@ -488,19 +475,11 @@ void DiskCachingFileLoaderCache::WriteBlockData(BlockInfo &info, const u8 *src)
s64 blockOffset = GetBlockOffset(info.block);
bool failed = false;
#ifdef __ANDROID__
if (lseek64(fd_, blockOffset, SEEK_SET) != blockOffset) {
failed = true;
} else if (write(fd_, src, blockSize_) != (ssize_t)blockSize_) {
failed = true;
}
#else
if (fseeko(f_, blockOffset, SEEK_SET) != 0) {
if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) {
failed = true;
} else if (fwrite(src, blockSize_, 1, f_) != 1) {
failed = true;
}
#endif
if (failed) {
ERROR_LOG(Log::Loader, "Unable to write disk cache data entry.");
@@ -516,7 +495,7 @@ void DiskCachingFileLoaderCache::WriteIndexData(u32 indexPos, BlockInfo &info) {
u32 offset = (u32)sizeof(FileHeader) + indexPos * (u32)sizeof(BlockInfo);
bool failed = false;
if (fseek(f_, offset, SEEK_SET) != 0) {
if (File::Fseek(f_, offset, SEEK_SET) != 0) {
failed = true;
} else if (fwrite(&info, sizeof(BlockInfo), 1, f_) != 1) {
failed = true;
@@ -553,11 +532,6 @@ bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) {
if (valid) {
f_ = fp;
#ifdef __ANDROID__
// Android NDK does not support 64-bit file I/O using C streams
fd_ = fileno(f_);
#endif
// Now let's load the index.
blockSize_ = header.blockSize;
maxBlocks_ = header.maxBlocks;
@@ -572,7 +546,7 @@ bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) {
}
void DiskCachingFileLoaderCache::LoadCacheIndex() {
if (fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {
if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) {
CloseFileHandle();
return;
}
@@ -631,10 +605,6 @@ void DiskCachingFileLoaderCache::CreateCacheFile(const Path &path) {
ERROR_LOG(Log::Loader, "Could not create disk cache file");
return;
}
#ifdef __ANDROID__
// Android NDK does not support 64-bit file I/O using C streams
fd_ = fileno(f_);
#endif
blockSize_ = DEFAULT_BLOCK_SIZE;
@@ -677,7 +647,7 @@ bool DiskCachingFileLoaderCache::LockCacheFile(bool lockStatus) {
u32 offset = (u32)offsetof(FileHeader, flags);
bool failed = false;
if (fseek(f_, offset, SEEK_SET) != 0) {
if (File::Fseek(f_, offset, SEEK_SET) != 0) {
failed = true;
} else if (fread(&flags_, sizeof(u32), 1, f_) != 1) {
failed = true;
@@ -704,7 +674,7 @@ bool DiskCachingFileLoaderCache::LockCacheFile(bool lockStatus) {
flags_ &= ~FLAG_LOCKED;
}
if (fseek(f_, offset, SEEK_SET) != 0) {
if (File::Fseek(f_, offset, SEEK_SET) != 0) {
failed = true;
} else if (fwrite(&flags_, sizeof(u32), 1, f_) != 1) {
failed = true;
+12 -16
View File
@@ -62,15 +62,6 @@ LocalFileLoader::LocalFileLoader(const Path &filename)
return;
}
#if HAVE_LIBRETRO_VFS
isOpenedByFd_ = false;
handle_ = filestream_open(filename.c_str(), RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
filestream_seek(handle_, 0, RETRO_VFS_SEEK_POSITION_END);
filesize_ = filestream_tell(handle_);
filestream_seek(handle_, 0, RETRO_VFS_SEEK_POSITION_START);
return;
#endif
#if PPSSPP_PLATFORM(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
if (filename.Type() == PathType::CONTENT_URI) {
int fd = Android_OpenContentUriFd(filename.ToString(), Android_OpenContentUriMode::READ);
@@ -87,7 +78,11 @@ LocalFileLoader::LocalFileLoader(const Path &filename)
#endif
#if defined(HAVE_LIBRETRO_VFS)
// Nothing to do here...
file_ = File::OpenCFile(filename, "rb");
if (file_ == nullptr) {
return;
}
filesize_ = File::GetFileSize(file_);
#elif !defined(_WIN32)
fd_ = open(filename.c_str(), O_RDONLY | O_CLOEXEC);
@@ -123,7 +118,9 @@ LocalFileLoader::LocalFileLoader(const Path &filename)
LocalFileLoader::~LocalFileLoader() {
#if defined(HAVE_LIBRETRO_VFS)
filestream_close(handle_);
if (file_ != nullptr) {
fclose(file_);
}
#elif !defined(_WIN32)
if (fd_ != -1) {
close(fd_);
@@ -138,8 +135,7 @@ LocalFileLoader::~LocalFileLoader() {
bool LocalFileLoader::Exists() {
// If we opened it for reading, it must exist. Done.
#if defined(HAVE_LIBRETRO_VFS)
return handle_ != 0;
return file_ != nullptr;
#elif !defined(_WIN32)
if (isOpenedByFd_) {
// As an optimization, if we already tried and failed, quickly return.
@@ -178,9 +174,9 @@ size_t LocalFileLoader::ReadAt(s64 absolutePos, size_t bytes, size_t count, void
}
#if defined(HAVE_LIBRETRO_VFS)
std::lock_guard<std::mutex> guard(readLock_);
filestream_seek(handle_, absolutePos, RETRO_VFS_SEEK_POSITION_START);
return filestream_read(handle_, data, bytes * count) / bytes;
std::lock_guard<std::mutex> guard(readLock_);
File::Fseek(file_, absolutePos, SEEK_SET);
return fread(data, bytes, count, file_);
#elif PPSSPP_PLATFORM(SWITCH)
// Toolchain has no fancy IO API. We must lock.
std::lock_guard<std::mutex> guard(readLock_);
+4 -7
View File
@@ -24,15 +24,10 @@
#include "Common/StringUtils.h"
#include "Core/Loaders.h"
#ifdef _WIN32
#if defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS)
typedef void *HANDLE;
#endif
#ifdef HAVE_LIBRETRO_VFS
#include <streams/file_stream.h>
typedef RFILE* HANDLE;
#endif
class LocalFileLoader : public FileLoader {
public:
LocalFileLoader(const Path &filename);
@@ -47,7 +42,9 @@ public:
size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data, Flags flags = Flags::NONE) override;
private:
#if !defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS)
#ifdef HAVE_LIBRETRO_VFS
FILE *file_;
#elif !defined(_WIN32)
void DetectSizeFd();
int fd_ = -1;
#else
+61 -9
View File
@@ -155,7 +155,28 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File
}
//TODO: tests, should append seek to end of file? seeking in a file opened for append?
#if PPSSPP_PLATFORM(WINDOWS)
#ifdef HAVE_LIBRETRO_VFS
int flags = 0;
if (access & FILEACCESS_READ && access & FILEACCESS_WRITE)
flags = RETRO_VFS_FILE_ACCESS_READ_WRITE;
else if (access & FILEACCESS_WRITE)
flags = RETRO_VFS_FILE_ACCESS_WRITE;
else if (access & FILEACCESS_READ && access & FILEACCESS_CREATE)
flags = RETRO_VFS_FILE_ACCESS_READ_WRITE;
else
flags = RETRO_VFS_FILE_ACCESS_READ;
bool success = false;
if (!(access & FILEACCESS_CREATE)) {
hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE);
success = hFile != nullptr;
} else if (!(access & FILEACCESS_EXCL)) {
hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE && File::Exists(fullName) ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE);
success = hFile != nullptr;
} else if (!File::Exists(fullName)) {
hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE);
success = hFile != nullptr;
}
#elif PPSSPP_PLATFORM(WINDOWS)
// Convert parameters to Windows permissions and access
DWORD desired = 0;
DWORD sharemode = 0;
@@ -278,7 +299,19 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File
DEBUG_LOG(Log::FileSystem, "Case may have been incorrect, second try opening %s (%s)", fullName.c_str(), fileName.c_str());
// And try again with the correct case this time
#if PPSSPP_PLATFORM(UWP)
#ifdef HAVE_LIBRETRO_VFS
success = false;
if (!(access & FILEACCESS_CREATE)) {
hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE);
success = hFile != nullptr;
} else if (!(access & FILEACCESS_EXCL)) {
hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE && File::Exists(fullName) ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE);
success = hFile != nullptr;
} else if (!File::Exists(fullName)) {
hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE);
success = hFile != nullptr;
}
#elif PPSSPP_PLATFORM(UWP)
// Should never get here.
#elif PPSSPP_PLATFORM(WINDOWS)
// Unlikely to get here, heh.
@@ -291,7 +324,7 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File
}
}
#if !PPSSPP_PLATFORM(WINDOWS)
#if !PPSSPP_PLATFORM(WINDOWS) && !defined(HAVE_LIBRETRO_VFS)
if (success) {
// Reject directories, even if we succeed in opening them.
// TODO: Might want to do this stat first...
@@ -337,7 +370,9 @@ size_t DirectoryFileHandle::Read(u8* pointer, s64 size)
}
}
if (size > 0) {
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
bytesRead = fread(pointer, 1, size, hFile);
#elif defined(_WIN32)
::ReadFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesRead, 0);
#else
bytesRead = read(hFile, pointer, size);
@@ -351,7 +386,9 @@ size_t DirectoryFileHandle::Write(const u8* pointer, s64 size)
size_t bytesWritten = 0;
bool diskFull = false;
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
bytesWritten = fwrite(pointer, 1, size, hFile);
#elif defined(_WIN32)
BOOL success = ::WriteFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesWritten, 0);
if (success == FALSE) {
DWORD err = GetLastError();
@@ -406,7 +443,15 @@ size_t DirectoryFileHandle::Seek(s32 position, FileMove type)
}
size_t result;
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
int moveMethod = 0;
switch (type) {
case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break;
case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break;
case FILEMOVE_END: moveMethod = SEEK_END; break;
}
result = File::Fseektell(hFile, position, moveMethod);
#elif defined(_WIN32)
DWORD moveMethod = 0;
switch (type) {
case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break;
@@ -434,7 +479,11 @@ size_t DirectoryFileHandle::Seek(s32 position, FileMove type)
void DirectoryFileHandle::Close() {
if (needsTrunc_ != -1) {
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
if (filestream_truncate(hFile, needsTrunc_) != 0) {
ERROR_LOG(Log::FileSystem, "Failed to truncate file to %d bytes", (int)needsTrunc_);
}
#elif defined(_WIN32)
Seek((s32)needsTrunc_, FILEMOVE_BEGIN);
if (SetEndOfFile(hFile) == 0) {
ERROR_LOG(Log::FileSystem, "Failed to truncate file to %d bytes", (int)needsTrunc_);
@@ -447,7 +496,10 @@ void DirectoryFileHandle::Close() {
#endif
}
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
if (hFile != nullptr)
fclose(hFile);
#elif defined(_WIN32)
if (hFile != (HANDLE)-1)
CloseHandle(hFile);
#else
@@ -598,7 +650,7 @@ int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const
}
return err;
} else {
#ifdef _WIN32
#if defined(_WIN32) || defined(HAVE_LIBRETRO_VFS)
if (access & FILEACCESS_APPEND) {
entry.hFile.Seek(0, FILEMOVE_END);
}
+4 -2
View File
@@ -24,7 +24,7 @@
#include "Common/File/Path.h"
#include "Core/FileSystems/FileSystem.h"
#ifdef _WIN32
#if defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS)
typedef void * HANDLE;
#endif
@@ -34,7 +34,9 @@ struct DirectoryFileHandle {
SKIP_REPLAY,
};
#ifdef _WIN32
#ifdef HAVE_LIBRETRO_VFS
FILE *hFile = nullptr;
#elif defined(_WIN32)
HANDLE hFile = (HANDLE)-1;
#else
int hFile = -1;
+8 -22
View File
@@ -319,39 +319,25 @@ bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &erro
}
// Close the return value with ZipClose (if non-null, of course).
struct zip *ZipOpenPath(const Path &fileName) {
int error = 0;
// Need to special case for content URI here, similar to OpenCFile.
struct zip *z;
#if PPSSPP_PLATFORM(ANDROID)
if (fileName.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(fileName, File::OPEN_READ);
z = zip_fdopen(fd, 0, &error);
} else
#endif
{ // continuation of above else in the ifdef
z = zip_open(fileName.c_str(), 0, &error);
}
if (!z) {
ERROR_LOG(Log::HLE, "Failed to open ZIP file '%s', error code=%i", fileName.c_str(), error);
ZipContainer ZipOpenPath(const Path &fileName) {
ZipContainer z(fileName);
if (z == nullptr) {
ERROR_LOG(Log::HLE, "Failed to open ZIP file '%s'", fileName.c_str());
}
return z;
}
void ZipClose(struct zip *z) {
if (z)
zip_close(z);
void ZipClose(ZipContainer &z) {
z.close();
}
bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info) {
struct zip *z = ZipOpenPath(fileName);
ZipContainer z = ZipOpenPath(fileName);
if (!z) {
info->contents = ZipFileContents::UNKNOWN;
return false;
}
DetectZipFileContents(z, info);
zip_close(z);
return true;
}
@@ -408,7 +394,7 @@ static bool ZipExtractFileToMemory(struct zip *z, int fileIndex, std::string *da
}
}
void DetectZipFileContents(struct zip *z, ZipFileInfo *info) {
void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
int numFiles = zip_get_num_files(z);
_dbg_assert_(numFiles >= 0);
+4 -8
View File
@@ -20,13 +20,9 @@
#include <string>
#include <memory>
#ifdef SHARED_LIBZIP
#include <zip.h>
#else
#include "ext/libzip/zip.h"
#endif
#include "Common/CommonTypes.h"
#include "Common/File/Path.h"
#include "Common/File/VFS/ZipFileReader.h"
enum class IdentifiedFileType {
ERROR_IDENTIFYING,
@@ -191,8 +187,8 @@ struct ZipFileInfo {
std::string contentName;
};
struct zip *ZipOpenPath(const Path &fileName);
void ZipClose(zip *z);
ZipContainer ZipOpenPath(const Path &fileName);
void ZipClose(ZipContainer &z);
bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info);
void DetectZipFileContents(struct zip *z, ZipFileInfo *info);
void DetectZipFileContents(zip_t *z, ZipFileInfo *info);
+2 -2
View File
@@ -119,7 +119,7 @@ bool MIPSTracer::flush_to_file() {
}
INFO_LOG(Log::JIT, "Trace flushed, closing the file...");
std::fclose(output);
fclose(output);
clear();
return true;
@@ -144,7 +144,7 @@ void MIPSTracer::flush_block_to_file(const TraceBlockInfo& block_info) {
snprintf(buffer, sizeof(buffer), "0x%08x: ", addr);
MIPSDisAsm(storage.read_asm(index), addr, buffer + prefix_size, sizeof(buffer) - prefix_size, true);
std::fprintf(output, "%s\n", buffer);
fprintf(output, "%s\n", buffer);
}
}
+3 -18
View File
@@ -244,7 +244,7 @@ void GameManager::InstallZipContents(ZipFileTask task) {
int error = 0;
struct zip *z = ZipOpenPath(task.fileName);
ZipContainer z = ZipOpenPath(task.fileName);
if (!z) {
g_OSD.RemoveProgressBar("install", false, 1.5f);
SetInstallError(sy->T("Unable to open zip file"));
@@ -281,22 +281,18 @@ void GameManager::InstallZipContents(ZipFileTask task) {
}
case ZipFileContents::TEXTURE_PACK:
{
// InstallMemstickGame contains code to close z, and works for textures too.
Path dest;
if (DetectTexturePackDest(z, zipInfo.textureIniIndex, dest)) {
INFO_LOG(Log::HLE, "Installing texture pack '%s' into '%s'", task.fileName.c_str(), dest.c_str());
File::CreateFullPath(dest);
// Install as a zip file if textures.ini is in the root. Performs better on Android.
if (zipInfo.stripChars == 0) {
success = InstallMemstickZip(z, task.fileName, dest / "textures.zip", zipInfo);
success = InstallMemstickZip(task.fileName, dest / "textures.zip", zipInfo);
} else {
// TODO: Can probably remove this, as we now put .nomedia in /TEXTURES directly.
File::CreateEmptyFile(dest / ".nomedia");
success = ExtractZipContents(z, dest, zipInfo, true);
}
} else {
zip_close(z);
z = nullptr;
}
break;
}
@@ -309,8 +305,6 @@ void GameManager::InstallZipContents(ZipFileTask task) {
default:
ERROR_LOG(Log::HLE, "File not a PSP game, no EBOOT.PBP found.");
SetInstallError(sy->T("Not a PSP game"));
zip_close(z);
z = nullptr;
break;
}
@@ -593,13 +587,10 @@ bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipF
}
INFO_LOG(Log::HLE, "Unzipped %d files (%d bytes / %d).", info.numFiles, (int)bytesCopied, (int)allBytes);
zip_close(z);
z = nullptr;
return true;
bail:
// We end up here if disk is full or couldn't write to storage for some other reason.
zip_close(z);
// We don't delete the original in this case. Try to delete the files we created so far.
for (size_t i = 0; i < createdFiles.size(); i++) {
File::Delete(createdFiles[i]);
@@ -611,16 +602,12 @@ bail:
return false;
}
bool GameManager::InstallMemstickZip(struct zip *z, const Path &zipfile, const Path &dest, const ZipFileInfo &info) {
bool GameManager::InstallMemstickZip(const Path &zipfile, const Path &dest, const ZipFileInfo &info) {
size_t allBytes = 0;
size_t bytesCopied = 0;
auto sy = GetI18NCategory(I18NCat::SYSTEM);
// We don't need the zip anymore, as we're going to copy it as-is.
zip_close(z);
z = nullptr;
// Not using File::Copy() so we can report progress.
FILE *inf = File::OpenCFile(zipfile, "rb");
if (!inf)
@@ -705,10 +692,8 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
INFO_LOG(Log::IO, "Successfully unzipped ISO file to '%s'", outputISOFilename.c_str());
success = true;
}
zip_close(z);
g_OSD.RemoveProgressBar("install", success, 0.5f);
z = 0;
installProgress_ = 1.0f;
InstallDone();
ResetInstallError();
+1 -1
View File
@@ -92,7 +92,7 @@ private:
void InstallZipContents(ZipFileTask task);
bool ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot);
bool InstallMemstickZip(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info);
bool InstallMemstickZip(const Path &zipFile, const Path &dest, const ZipFileInfo &info);
bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &destDir);
void UninstallGame(const std::string &name);
+1 -1
View File
@@ -249,7 +249,7 @@ static void DiscHandler(const http::ServerRequest &request, const Path &filename
}
FILE *fp = File::OpenCFile(filename, "rb");
if (!fp || fseek(fp, begin, SEEK_SET) != 0) {
if (!fp || File::Fseek(fp, begin, SEEK_SET) != 0) {
request.WriteHttpResponseHeader("1.0", 500, -1, "text/plain");
request.Out()->Push("File access failed.");
if (fp) {
+2 -2
View File
@@ -659,7 +659,7 @@ void PipelineManagerVulkan::SavePipelineCache(FILE *file, bool saveRawPipelineCa
NOTICE_LOG(Log::G3D, "Saved Vulkan pipeline cache (%d bytes).", (int)size);
}
size_t seekPosOnFailure = ftell(file);
int64_t seekPosOnFailure = File::Ftell(file);
bool failed = false;
bool writeFailed = false;
@@ -711,7 +711,7 @@ void PipelineManagerVulkan::SavePipelineCache(FILE *file, bool saveRawPipelineCa
ERROR_LOG(Log::G3D, "Failed to write pipeline cache, some shader was missing");
// Write a zero in the right place so it doesn't try to load the pipelines next time.
size = 0;
fseek(file, (long)seekPosOnFailure, SEEK_SET);
File::Fseek(file, seekPosOnFailure, SEEK_SET);
writeFailed = fwrite(&size, sizeof(size), 1, file) != 1;
if (writeFailed) {
ERROR_LOG(Log::G3D, "Failed to write pipeline cache, disk full?");
+2
View File
@@ -30,6 +30,8 @@
#include "GPU/Vulkan/VulkanQueueRunner.h"
#include "GPU/Vulkan/VulkanRenderManager.h"
#include "Common/File/FileUtil.h"
struct VKRGraphicsPipeline;
class VulkanRenderManager;
class VulkanContext;
+2 -2
View File
@@ -506,10 +506,10 @@ struct VulkanCacheHeader {
bool ShaderManagerVulkan::LoadCacheFlags(FILE *f, DrawEngineVulkan *drawEngine) {
VulkanCacheHeader header{};
long pos = ftell(f);
int64_t pos = File::Ftell(f);
bool success = fread(&header, sizeof(header), 1, f) == 1;
// We'll read it again later, this is just to check the flags.
success = success && fseek(f, pos, SEEK_SET) == 0;
success = success && File::Fseek(f, pos, SEEK_SET) == 0;
if (!success || header.magic != CACHE_HEADER_MAGIC) {
WARN_LOG(Log::G3D, "Shader cache magic mismatch");
return false;
+2
View File
@@ -32,6 +32,8 @@
#include "Common/Math/lin/matrix4x4.h"
#include "GPU/Common/ShaderUniforms.h"
#include "Common/File/FileUtil.h"
class VulkanContext;
class DrawEngineVulkan;
class VulkanPushPool;
+2 -2
View File
@@ -35,7 +35,7 @@
InstallZipScreen::InstallZipScreen(const Path &zipPath) : UITwoPaneBaseDialogScreen(Path(), TwoPaneFlags::SettingsToTheRight | TwoPaneFlags::ContentsCanScroll), zipPath_(zipPath) {
g_GameManager.ResetInstallError();
struct zip *zipFile = ZipOpenPath(zipPath_);
ZipContainer zipFile = ZipOpenPath(zipPath_);
if (zipFile) {
DetectZipFileContents(zipFile, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents.
ZipClose(zipFile);
@@ -177,7 +177,7 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
leftColumn->Add(new TextView(zipFileInfo_.gameTitle + ": " + zipFileInfo_.savedataDir));
Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA);
struct zip *zipFile = ZipOpenPath(zipPath_);
ZipContainer zipFile = ZipOpenPath(zipPath_);
bool overwrite = !CanExtractWithoutOverwrite(zipFile, savedataDir, 50);
ZipClose(zipFile);
+14
View File
@@ -12,6 +12,19 @@ if(WIN32)
LibretroD3D11Context.cpp)
endif()
set(LIBRETRO_SRCS ${LIBRETRO_SRCS}
libretro-common/compat/compat_posix_string.c
libretro-common/compat/fopen_utf8.c
libretro-common/compat/compat_strl.c
libretro-common/encodings/encoding_utf.c
libretro-common/file/file_path.c
libretro-common/file/file_path_io.c
libretro-common/streams/file_stream.c
libretro-common/streams/file_stream_transforms.c
libretro-common/string/stdstring.c
libretro-common/time/rtime.c
libretro-common/vfs/vfs_implementation.c)
include_directories(libretro)
add_library(ppsspp_libretro SHARED ${LIBRETRO_SRCS})
@@ -20,6 +33,7 @@ set_target_properties(ppsspp_libretro PROPERTIES PREFIX "")
if(ANDROID)
set_target_properties(ppsspp_libretro PROPERTIES SUFFIX "_android.so")
endif()
target_include_directories(ppsspp_libretro PRIVATE libretro-common/include)
if(NOT MSVC)
if (APPLE OR IOS)
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#define HAVE_D3D11
#include "libretro/libretro_d3d.h"
#include <libretro_d3d.h>
#include "libretro/LibretroGraphicsContext.h"
class LibretroD3D11Context : public LibretroHWRenderContext {
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <streams/file_stream_transforms.h>
#undef fopen
#undef fseek
#undef ftell
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include <atomic>
#include "libretro/libretro.h"
#include <libretro.h>
#include "Common/GraphicsContext.h"
#include "Common/GPU/thin3d_create.h"
+3 -1
View File
@@ -10,9 +10,11 @@
#include "Common/Data/Text/Parsers.h"
#include "libretro/LibretroVulkanContext.h"
#include "libretro/libretro_vulkan.h"
#include <libretro_vulkan.h>
#include <GPU/Vulkan/VulkanRenderManager.h>
#undef fflush
static VulkanContext *vk;
void vk_libretro_init(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr, const char **required_device_extensions, unsigned num_required_device_extensions, const char **required_device_layers, unsigned num_required_device_layers, const VkPhysicalDeviceFeatures *required_features);
+7 -3
View File
@@ -1,3 +1,5 @@
HAVE_LIBRETRO_VFS ?= 1
FFMPEGDIR = $(CORE_DIR)/ffmpeg
LIBRETRODIR = $(CORE_DIR)/libretro
COREDIR = $(CORE_DIR)/Core
@@ -1121,13 +1123,13 @@ SOURCES_C += \
endif
ifeq ($(HAVE_LIBRETRO_VFS), 1)
DEFINES += -DHAVE_LIBRETRO_VFS
INCFLAGS += -I$(LIBRETROCOMMONDIR)/include
COREFLAGS += -DHAVE_LIBRETRO_VFS
SOURCES_C += $(LIBRETROCOMMONDIR)/compat/compat_posix_string.c \
$(LIBRETROCOMMONDIR)/compat/fopen_utf8.c \
$(LIBRETROCOMMONDIR)/encodings/encoding_utf.c \
$(LIBRETROCOMMONDIR)/compat/compat_strl.c \
$(LIBRETROCOMMONDIR)/encodings/encoding_utf.c \
$(LIBRETROCOMMONDIR)/file/file_path.c \
$(LIBRETROCOMMONDIR)/file/file_path_io.c \
$(LIBRETROCOMMONDIR)/streams/file_stream.c \
$(LIBRETROCOMMONDIR)/streams/file_stream_transforms.c \
$(LIBRETROCOMMONDIR)/string/stdstring.c \
@@ -1135,6 +1137,8 @@ SOURCES_C += $(LIBRETROCOMMONDIR)/compat/compat_posix_string.c \
$(LIBRETROCOMMONDIR)/vfs/vfs_implementation.c
endif
INCFLAGS += -I$(LIBRETROCOMMONDIR)/include
GIT_VERSION_SRC = $(CORE_DIR)/git-version.cpp
GIT_VERSION := $(shell git describe --always || echo v-1.unknown)
GIT_VERSION_NO_UPDATE = $(findstring 1,$(shell grep -s PPSSPP_GIT_VERSION_NO_UPDATE $(GIT_VERSION_SRC)))
-1
View File
@@ -10,7 +10,6 @@ CORE_DIR := ../..
FFMPEGDIR := $(CORE_DIR)/ffmpeg
FFMPEGLIBS += libavformat libavcodec libavutil libswresample libswscale
WITH_DYNAREC := 1
HAVE_LIBRETRO_VFS := 1
ifeq ($(TARGET_ARCH),arm64)
COREFLAGS += -D_ARCH_64
+33 -21
View File
@@ -46,7 +46,7 @@
#include "UI/AudioCommon.h"
#include "libretro/libretro.h"
#include <libretro.h>
#include "libretro/LibretroGraphicsContext.h"
#include "libretro/libretro_core_options.h"
@@ -401,11 +401,11 @@ void retro_set_environment(retro_environment_t cb)
update_display_cb.callback = set_variable_visibility;
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK, &update_display_cb);
#ifdef HAVE_LIBRETRO_VFS
struct retro_vfs_interface_info vfs_iface_info { 1, nullptr };
if (cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info))
filestream_vfs_init(&vfs_iface_info);
#endif
#ifdef HAVE_LIBRETRO_VFS
struct retro_vfs_interface_info vfs_iface_info { 2, nullptr };
if (cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info))
File::InitLibretroVFS(&vfs_iface_info);
#endif
}
static int get_language_auto(void)
@@ -1473,7 +1473,7 @@ static void retro_check_backend(void)
else if (!strcmp(var.value, "vulkan"))
backend = RETRO_HW_CONTEXT_VULKAN;
else if (!strcmp(var.value, "d3d11"))
backend = RETRO_HW_CONTEXT_DIRECT3D;
backend = RETRO_HW_CONTEXT_D3D11;
else if (!strcmp(var.value, "none"))
backend = RETRO_HW_CONTEXT_NONE;
}
@@ -1857,10 +1857,11 @@ void retro_cheat_reset(void) {
Path file=cheatEngine->CheatFilename();
// Output cheats to cheat file
std::ofstream outFile;
outFile.open(file.c_str());
outFile << "_S " << g_paramSFO.GetDiscID() << std::endl;
outFile.close();
FILE *outFile = File::OpenCFile(file, "wb");
if (outFile != nullptr) {
fprintf(outFile, "_S %s\n", g_paramSFO.GetDiscID().c_str());
fclose(outFile);
}
g_Config.bReloadCheats = true;
@@ -1880,10 +1881,20 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code) {
// Read cheats file
std::vector<std::string> cheats;
std::ifstream cheat_content(file.c_str());
std::stringstream buffer;
buffer << cheat_content.rdbuf();
std::string existing_cheats=ReplaceAll(buffer.str(), std::string("\n_C"), std::string("|"));
std::string cheat_content;
FILE *inFile = File::OpenCFile(file, "rb");
if (inFile != nullptr) {
std::array<uint8_t, 4096> buffer;
for (;;) {
size_t n = fread(buffer.data(), 1, buffer.size(), inFile);
cheat_content.append((const char *)buffer.data(), n);
if (n < buffer.size()) {
break;
}
}
fclose(inFile);
}
std::string existing_cheats=ReplaceAll(cheat_content, std::string("\n_C"), std::string("|"));
SplitString(existing_cheats, '|', cheats);
// Generate Cheat String
@@ -1915,13 +1926,14 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code) {
}
// Output cheats to cheat file
std::ofstream outFile;
outFile.open(file.c_str());
outFile << "_S " << g_paramSFO.GetDiscID() << std::endl;
for (int i=1; i < cheats.size(); i++) {
outFile << "_C" << cheats[i] << std::endl;
FILE *outFile = File::OpenCFile(file, "wb");
if (outFile != nullptr) {
fprintf(outFile, "_S %s\n", g_paramSFO.GetDiscID().c_str());
for (const std::string &cheat : cheats) {
fprintf(outFile, "_C%s\n", cheat.c_str());
}
fclose(outFile);
}
outFile.close();
g_Config.bReloadCheats = true;
-3968
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -4,8 +4,8 @@
#include <stdlib.h>
#include <string.h>
#include "libretro.h"
#include "retro_inline.h"
#include <libretro.h>
#include <retro_inline.h>
#ifndef HAVE_NO_LANGEXTRA
#include "libretro_core_options_intl.h"
-60
View File
@@ -1,60 +0,0 @@
/* Copyright (C) 2010-2016 The RetroArch team
*
* ---------------------------------------------------------------------------------------------
* The following license statement only applies to this libretro API header (libretro_vulkan.h)
* ---------------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the
* "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBRETRO_DIRECT3D_H__
#define LIBRETRO_DIRECT3D_H__
#include "libretro.h"
#ifdef HAVE_D3D11
#include <d3d11.h>
#include <D3Dcompiler.h>
#define RETRO_HW_RENDER_INTERFACE_D3D11_VERSION 1
struct retro_hw_render_interface_d3d11
{
/* Must be set to RETRO_HW_RENDER_INTERFACE_D3D11. */
enum retro_hw_render_interface_type interface_type;
/* Must be set to RETRO_HW_RENDER_INTERFACE_D3D11_VERSION. */
unsigned interface_version;
/* Opaque handle to the d3d11 backend in the frontend
* which must be passed along to all function pointers
* in this interface.
*/
void* handle;
ID3D11Device *device;
ID3D11DeviceContext *context;
D3D_FEATURE_LEVEL featureLevel;
pD3DCompile D3DCompile;
};
#endif
#endif /* LIBRETRO_DIRECT3D_H__ */
+2 -1
View File
@@ -9,7 +9,8 @@
#include "Core/Config.h"
#define VK_NO_PROTOTYPES
#include "libretro/libretro_vulkan.h"
#include <libretro_vulkan.h>
#include "libretro/LibretroGraphicsContext.h"
using namespace PPSSPP_VK;
-404
View File
@@ -1,404 +0,0 @@
/* Copyright (C) 2010-2016 The RetroArch team
*
* ---------------------------------------------------------------------------------------------
* The following license statement only applies to this libretro API header (libretro_vulkan.h)
* ---------------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the
* "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBRETRO_VULKAN_H__
#define LIBRETRO_VULKAN_H__
#include "ext/vulkan/vulkan.h"
#include "libretro.h"
#include "LibretroGraphicsContext.h"
#define RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION 5
#define RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION 1
struct retro_vulkan_image
{
VkImageView image_view;
VkImageLayout image_layout;
VkImageViewCreateInfo create_info;
};
typedef void (*retro_vulkan_set_image_t)(void* handle, const struct retro_vulkan_image* image,
uint32_t num_semaphores, const VkSemaphore* semaphores,
uint32_t src_queue_family);
typedef uint32_t (*retro_vulkan_get_sync_index_t)(void* handle);
typedef uint32_t (*retro_vulkan_get_sync_index_mask_t)(void* handle);
typedef void (*retro_vulkan_set_command_buffers_t)(void* handle, uint32_t num_cmd,
const VkCommandBuffer* cmd);
typedef void (*retro_vulkan_wait_sync_index_t)(void* handle);
typedef void (*retro_vulkan_lock_queue_t)(void* handle);
typedef void (*retro_vulkan_unlock_queue_t)(void* handle);
typedef void (*retro_vulkan_set_signal_semaphore_t)(void* handle, VkSemaphore semaphore);
typedef const VkApplicationInfo* (*retro_vulkan_get_application_info_t)(void);
struct retro_vulkan_context
{
VkPhysicalDevice gpu;
VkDevice device;
VkQueue queue;
uint32_t queue_family_index;
VkQueue presentation_queue;
uint32_t presentation_queue_family_index;
};
typedef bool (*retro_vulkan_create_device_t)(
struct retro_vulkan_context* context, VkInstance instance, VkPhysicalDevice gpu,
VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr,
const char** required_device_extensions, unsigned num_required_device_extensions,
const char** required_device_layers, unsigned num_required_device_layers,
const VkPhysicalDeviceFeatures* required_features);
typedef void (*retro_vulkan_destroy_device_t)(void);
/* Note on thread safety:
* The Vulkan API is heavily designed around multi-threading, and
* the libretro interface for it should also be threading friendly.
* A core should be able to build command buffers and submit
* command buffers to the GPU from any thread.
*/
struct retro_hw_render_context_negotiation_interface_vulkan
{
/* Must be set to RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN. */
enum retro_hw_render_context_negotiation_interface_type interface_type;
/* Must be set to RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION. */
unsigned interface_version;
/* If non-NULL, returns a VkApplicationInfo struct that the frontend can use instead of
* its "default" application info.
*/
retro_vulkan_get_application_info_t get_application_info;
/* If non-NULL, the libretro core will choose one or more physical devices,
* create one or more logical devices and create one or more queues.
* The core must prepare a designated PhysicalDevice, Device, Queue and queue family index
* which the frontend will use for its internal operation.
*
* If gpu is not VK_NULL_HANDLE, the physical device provided to the frontend must be this
* PhysicalDevice.
* The core is still free to use other physical devices.
*
* The frontend will request certain extensions and layers for a device which is created.
* The core must ensure that the queue and queue_family_index support GRAPHICS and COMPUTE.
*
* If surface is not VK_NULL_HANDLE, the core must consider presentation when creating the queues.
* If presentation to "surface" is supported on the queue, presentation_queue must be equal to
* queue.
* If not, a second queue must be provided in presentation_queue and presentation_queue_index.
* If surface is not VK_NULL_HANDLE, the instance from frontend will have been created with
* supported for
* VK_KHR_surface extension.
*
* The core is free to set its own queue priorities.
* Device provided to frontend is owned by the frontend, but any additional device resources must
* be freed by core
* in destroy_device callback.
*
* If this function returns true, a PhysicalDevice, Device and Queues are initialized.
* If false, none of the above have been initialized and the frontend will attempt
* to fallback to "default" device creation, as if this function was never called.
*/
retro_vulkan_create_device_t create_device;
/* If non-NULL, this callback is called similar to context_destroy for HW_RENDER_INTERFACE.
* However, it will be called even if context_reset was not called.
* This can happen if the context never succeeds in being created.
* destroy_device will always be called before the VkInstance
* of the frontend is destroyed if create_device was called successfully so that the core has a
* chance of
* tearing down its own device resources.
*
* Only auxillary resources should be freed here, i.e. resources which are not part of
* retro_vulkan_context.
*/
retro_vulkan_destroy_device_t destroy_device;
};
struct retro_hw_render_interface_vulkan
{
/* Must be set to RETRO_HW_RENDER_INTERFACE_VULKAN. */
enum retro_hw_render_interface_type interface_type;
/* Must be set to RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION. */
unsigned interface_version;
/* Opaque handle to the Vulkan backend in the frontend
* which must be passed along to all function pointers
* in this interface.
*
* The rationale for including a handle here (which libretro v1
* doesn't currently do in general) is:
*
* - Vulkan cores should be able to be freely threaded without lots of fuzz.
* This would break frontends which currently rely on TLS
* to deal with multiple cores loaded at the same time.
* - Fixing this in general is TODO for an eventual libretro v2.
*/
void* handle;
/* The Vulkan instance the context is using. */
VkInstance instance;
/* The physical device used. */
VkPhysicalDevice gpu;
/* The logical device used. */
VkDevice device;
/* Allows a core to fetch all its needed symbols without having to link
* against the loader itself. */
PFN_vkGetDeviceProcAddr get_device_proc_addr;
PFN_vkGetInstanceProcAddr get_instance_proc_addr;
/* The queue the core must use to submit data.
* This queue and index must remain constant throughout the lifetime
* of the context.
*
* This queue will be the queue that supports graphics and compute
* if the device supports compute.
*/
VkQueue queue;
unsigned queue_index;
/* Before calling retro_video_refresh_t with RETRO_HW_FRAME_BUFFER_VALID,
* set which image to use for this frame.
*
* If num_semaphores is non-zero, the frontend will wait for the
* semaphores provided to be signaled before using the results further
* in the pipeline.
*
* Semaphores provided by a single call to set_image will only be
* waited for once (waiting for a semaphore resets it).
* E.g. set_image, video_refresh, and then another
* video_refresh without set_image,
* but same image will only wait for semaphores once.
*
* For this reason, ownership transfer will only occur if semaphores
* are waited on for a particular frame in the frontend.
*
* Using semaphores is optional for synchronization purposes,
* but if not using
* semaphores, an image memory barrier in vkCmdPipelineBarrier
* should be used in the graphics_queue.
* Example:
*
* vkCmdPipelineBarrier(cmd,
* srcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
* dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
* image_memory_barrier = {
* srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
* dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
* });
*
* The use of pipeline barriers instead of semaphores is encouraged
* as it is simpler and more fine-grained. A layout transition
* must generally happen anyways which requires a
* pipeline barrier.
*
* The image passed to set_image must have imageUsage flags set to at least
* VK_IMAGE_USAGE_TRANSFER_SRC_BIT and VK_IMAGE_USAGE_SAMPLED_BIT.
* The core will naturally want to use flags such as
* VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT and/or
* VK_IMAGE_USAGE_TRANSFER_DST_BIT depending
* on how the final image is created.
*
* The image must also have been created with MUTABLE_FORMAT bit set if
* 8-bit formats are used, so that the frontend can reinterpret sRGB
* formats as it sees fit.
*
* Images passed to set_image should be created with TILING_OPTIMAL.
* The image layout should be transitioned to either
* VK_IMAGE_LAYOUT_GENERIC or VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
* The actual image layout used must be set in image_layout.
*
* The image must be a 2D texture which may or not be layered
* and/or mipmapped.
*
* The image must be suitable for linear sampling.
* While the image_view is typically the only field used,
* the frontend may want to reinterpret the texture as sRGB vs.
* non-sRGB for example so the VkImageViewCreateInfo used to
* create the image view must also be passed in.
*
* The data in the pointer to the image struct will not be copied
* as the pNext field in create_info cannot be reliably deep-copied.
* The image pointer passed to set_image must be valid until
* retro_video_refresh_t has returned.
*
* If frame duping is used when passing NULL to retro_video_refresh_t,
* the frontend is free to either use the latest image passed to
* set_image or reuse the older pointer passed to set_image the
* frame RETRO_HW_FRAME_BUFFER_VALID was last used.
*
* Essentially, the lifetime of the pointer passed to
* set_image should be extended if frame duping is used
* so that the frontend can reuse the older pointer.
*
* The image itself however, must not be touched by the core until
* wait_sync_index has been completed later. The frontend may perform
* layout transitions on the image, so even read-only access is not defined.
* The exception to read-only rule is if GENERAL layout is used for the image.
* In this case, the frontend is not allowed to perform any layout transitions,
* so concurrent reads from core and frontend are allowed.
*
* If frame duping is used, or if set_command_buffers is used,
* the frontend will not wait for any semaphores.
*
* The src_queue_family is used to specify which queue family
* the image is currently owned by. If using multiple queue families
* (e.g. async compute), the frontend will need to acquire ownership of the
* image before rendering with it and release the image afterwards.
*
* If src_queue_family is equal to the queue family (queue_index),
* no ownership transfer will occur.
* Similarly, if src_queue_family is VK_QUEUE_FAMILY_IGNORED,
* no ownership transfer will occur.
*
* The frontend will always release ownership back to src_queue_family.
* Waiting for frontend to complete with wait_sync_index() ensures that
* the frontend has released ownership back to the application.
* Note that in Vulkan, transfering ownership is a two-part process.
*
* Example frame:
* - core releases ownership from src_queue_index to queue_index with VkImageMemoryBarrier.
* - core calls set_image with src_queue_index.
* - Frontend will acquire the image with src_queue_index -> queue_index as well, completing the
* ownership transfer.
* - Frontend renders the frame.
* - Frontend releases ownership with queue_index -> src_queue_index.
* - Next time image is used, core must acquire ownership from queue_index ...
*
* Since the frontend releases ownership, we cannot necessarily dupe the frame because
* the core needs to make the roundtrip of ownership transfer.
*/
retro_vulkan_set_image_t set_image;
/* Get the current sync index for this frame which is obtained in
* frontend by calling e.g. vkAcquireNextImageKHR before calling
* retro_run().
*
* This index will correspond to which swapchain buffer is currently
* the active one.
*
* Knowing this index is very useful for maintaining safe asynchronous CPU
* and GPU operation without stalling.
*
* The common pattern for synchronization is to receive fences when
* submitting command buffers to Vulkan (vkQueueSubmit) and add this fence
* to a list of fences for frame number get_sync_index().
*
* Next time we receive the same get_sync_index(), we can wait for the
* fences from before, which will usually return immediately as the
* frontend will generally also avoid letting the GPU run ahead too much.
*
* After the fence has signaled, we know that the GPU has completed all
* GPU work related to work submitted in the frame we last saw get_sync_index().
*
* This means we can safely reuse or free resources allocated in this frame.
*
* In theory, even if we wait for the fences correctly, it is not technically
* safe to write to the image we earlier passed to the frontend since we're
* not waiting for the frontend GPU jobs to complete.
*
* The frontend will guarantee that the appropriate pipeline barrier
* in graphics_queue has been used such that
* VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT cannot
* start until the frontend is done with the image.
*/
retro_vulkan_get_sync_index_t get_sync_index;
/* Returns a bitmask of how many swapchain images we currently have
* in the frontend.
*
* If bit #N is set in the return value, get_sync_index can return N.
* Knowing this value is useful for preallocating per-frame management
* structures ahead of time.
*
* While this value will typically remain constant throughout the
* applications lifecycle, it may for example change if the frontend
* suddently changes fullscreen state and/or latency.
*
* If this value ever changes, it is safe to assume that the device
* is completely idle and all synchronization objects can be deleted
* right away as desired.
*/
retro_vulkan_get_sync_index_mask_t get_sync_index_mask;
/* Instead of submitting the command buffer to the queue first, the core
* can pass along its command buffer to the frontend, and the frontend
* will submit the command buffer together with the frontends command buffers.
*
* This has the advantage that the overhead of vkQueueSubmit can be
* amortized into a single call. For this mode, semaphores in set_image
* will be ignored, so vkCmdPipelineBarrier must be used to synchronize
* the core and frontend.
*
* The command buffers in set_command_buffers are only executed once,
* even if frame duping is used.
*
* If frame duping is used, set_image should be used for the frames
* which should be duped instead.
*
* Command buffers passed to the frontend with set_command_buffers
* must not actually be submitted to the GPU until retro_video_refresh_t
* is called.
*
* The frontend must submit the command buffer before submitting any
* other command buffers provided by set_command_buffers. */
retro_vulkan_set_command_buffers_t set_command_buffers;
/* Waits on CPU for device activity for the current sync index to complete.
* This is useful since the core will not have a relevant fence to sync with
* when the frontend is submitting the command buffers. */
retro_vulkan_wait_sync_index_t wait_sync_index;
/* If the core submits command buffers itself to any of the queues provided
* in this interface, the core must lock and unlock the frontend from
* racing on the VkQueue.
*
* Queue submission can happen on any thread.
* Even if queue submission happens on the same thread as retro_run(),
* the lock/unlock functions must still be called.
*
* NOTE: Queue submissions are heavy-weight. */
retro_vulkan_lock_queue_t lock_queue;
retro_vulkan_unlock_queue_t unlock_queue;
/* Sets a semaphore which is signaled when the image in set_image can safely be reused.
* The semaphore is consumed next call to retro_video_refresh_t.
* The semaphore will be signalled even for duped frames.
* The semaphore will be signalled only once, so set_signal_semaphore should be called every
* frame.
* The semaphore may be VK_NULL_HANDLE, which disables semaphore signalling for next call to
* retro_video_refresh_t.
*
* This is mostly useful to support use cases where you're rendering to a single image that
* is recycled in a ping-pong fashion with the frontend to save memory (but potentially less
* throughput).
*/
retro_vulkan_set_signal_semaphore_t set_signal_semaphore;
};
#endif
-39
View File
@@ -1,39 +0,0 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_inline.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_INLINE_H
#define __LIBRETRO_SDK_INLINE_H
#ifndef INLINE
#if defined(_WIN32) || defined(__INTEL_COMPILER)
#define INLINE __inline
#elif defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L
#define INLINE inline
#elif defined(__GNUC__)
#define INLINE __inline__
#else
#define INLINE
#endif
#endif
#endif