diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index cf2d324f0ec2..8fd7c6d3e41b 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -314,3 +314,48 @@ void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) { lock_.unlock(); delete file; } + +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) { + return false; + } + + struct zip_stat zstat; + if (zip_stat(zip, path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat) != 0) { + return false; + } + zip_file *file = zip_fopen_index(zip, zstat.index, ZIP_FL_UNCHANGED); + if (!file) { + return false; + } + if (mutex) { + mutex->lock(); + } + data->resize(zstat.size); + if (zip_fread(file, data->data(), zstat.size) != zstat.size) { + if (mutex) { + mutex->unlock(); + } + zip_fclose(file); + zip_close(zip); + return false; + } + if (mutex) { + mutex->unlock(); + } + zip_fclose(file); + zip_close(zip); + return true; +} diff --git a/Common/File/VFS/ZipFileReader.h b/Common/File/VFS/ZipFileReader.h index 4bd510b46396..4c40877f17d5 100644 --- a/Common/File/VFS/ZipFileReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -54,3 +54,7 @@ class ZipFileReader : public VFSBackend { std::string inZipPath_; Path zipPath_; }; + +// When you just want a single file from a ZIP, and don't care about accurate error reporting, use this. +// The buffer should be free'd with free. Mutex will be locked while updating data, if non-null. +bool ReadSingleFileFromZip(Path zipFile, const char *path, std::string *data, std::mutex *mutex); diff --git a/Core/HLE/sceNet_lib.cpp b/Core/HLE/sceNet_lib.cpp index a72bfcbbcb72..6fd2f956f441 100644 --- a/Core/HLE/sceNet_lib.cpp +++ b/Core/HLE/sceNet_lib.cpp @@ -103,7 +103,7 @@ u32 sceNetStrchr(void *str, int ch) { u32 sceNetStrlen(const char* str) { // Redirect that to libc - u32 res = std::strlen(str); + u32 res = (u32)std::strlen(str); return hleLogDebug(Log::sceNet, res); } diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 2c8a02e88b2a..37709616a5e7 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -1,3 +1,4 @@ + // Copyright (c) 2013- PPSSPP Project. // This program is free software: you can redistribute it and/or modify @@ -25,6 +26,7 @@ #include "Common/GPU/thin3d.h" #include "Common/Thread/ThreadManager.h" #include "Common/File/VFS/VFS.h" +#include "Common/File/VFS/ZipFileReader.h" #include "Common/File/FileUtil.h" #include "Common/File/Path.h" #include "Common/Render/ManagedTexture.h" @@ -453,6 +455,22 @@ static bool ReadVFSToString(const char *filename, std::string *contents, std::mu return true; } +static bool LoadReplacementImage(GameInfo *info, GameInfoTex *tex, const char *filename) { + if (g_Config.bReplaceTextures) { + const Path customIconFilename = GetSysDirectory(DIRECTORY_TEXTURES) / info->id / filename; + const Path zipFilename = GetSysDirectory(DIRECTORY_TEXTURES) / info->id / "textures.zip"; + if (File::Exists(customIconFilename)) { + tex->dataLoaded = ReadLocalFileToString(customIconFilename, &tex->data, &info->lock); + } else if (File::Exists(zipFilename)) { + // Read file from zip if available. + tex->dataLoaded = ReadSingleFileFromZip(zipFilename, filename, &tex->data, &info->lock); + } + return tex->dataLoaded; + } else { + return false; + } +} + class GameInfoWorkItem : public Task { public: GameInfoWorkItem(const Path &gamePath, std::shared_ptr &info, GameInfoFlags flags) @@ -542,20 +560,23 @@ class GameInfoWorkItem : public Task { // Then, ICON0.PNG. if (flags_ & GameInfoFlags::ICON) { - if (pbp.GetSubFileSize(PBP_ICON0_PNG) > 0) { + if (LoadReplacementImage(info_.get(), &info_->icon, "icon.png")) { + // Nothing more to do + } else if (pbp.GetSubFileSize(PBP_ICON0_PNG) > 0) { std::lock_guard lock(info_->lock); pbp.GetSubFileAsString(PBP_ICON0_PNG, &info_->icon.data); } else { Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg"); Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png"); // Try using png/jpg screenshots first - if (File::Exists(screenshot_png)) + if (File::Exists(screenshot_png)) { ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock); - else if (File::Exists(screenshot_jpg)) + } else if (File::Exists(screenshot_jpg)) { ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock); - else + } else { // Read standard icon ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock); + } } info_->icon.dataLoaded = true; } @@ -755,8 +776,13 @@ class GameInfoWorkItem : public Task { } // Fall back to unknown icon if ISO is broken/is a homebrew ISO, override is allowed though + // First, do try to get an icon from the replacement texture pack, if available. if (flags_ & GameInfoFlags::ICON) { - if (!ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock)) { + if (LoadReplacementImage(info_.get(), &info_->icon, "icon.png")) { + // Nothing more to do + } else if (ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock)) { + info_->icon.dataLoaded = true; + } else { Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg"); Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png"); // Try using png/jpg screenshots first @@ -768,8 +794,6 @@ class GameInfoWorkItem : public Task { DEBUG_LOG(Log::Loader, "Loading unknown.png because no icon was found"); info_->icon.dataLoaded = ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock); } - } else { - info_->icon.dataLoaded = true; } } break; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 8c276f141c62..e7334d95f65a 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -280,6 +280,7 @@ void GameSettingsScreen::CreateTabs() { void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) { auto gr = GetI18NCategory(I18NCat::GRAPHICS); auto vr = GetI18NCategory(I18NCat::VR); + auto dev = GetI18NCategory(I18NCat::DEVELOPER); using namespace UI; @@ -415,6 +416,8 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) }); } + graphicsSettings->Add(new CheckBox(&g_Config.bReplaceTextures, dev->T("Replace textures"))); + graphicsSettings->Add(new ItemHeader(gr->T("Frame Rate Control"))); static const char *frameSkip[] = {"Off", "1", "2", "3", "4", "5", "6", "7", "8"}; graphicsSettings->Add(new PopupMultiChoice(&g_Config.iFrameSkip, gr->T("Frame Skipping"), frameSkip, 0, ARRAY_SIZE(frameSkip), I18NCat::GRAPHICS, screenManager())); @@ -527,8 +530,6 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) return DoesBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform; }); - // In case we're going to add few other antialiasing option like MSAA in the future. - // graphicsSettings->Add(new CheckBox(&g_Config.bFXAA, gr->T("FXAA"))); graphicsSettings->Add(new ItemHeader(gr->T("Texture Scaling"))); #ifndef MOBILE_DEVICE static const char *texScaleLevels[] = {"Off", "2x", "3x", "4x", "5x"};