mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 09:09:29 +01:00
Merge pull request #22747 from hrydgard/virtual-pops-eboot
POPS: Boot PS1 disc images (cue/bin, ccd/img, mds/mdf, CHD, iso) as converted EBOOTs made on the fly
This commit is contained in:
83 files changed
+2016
-85
No files matched your search
@@ -1425,6 +1425,7 @@ if(UNITTEST)
|
||||
unittest/TestVertexJit.h
|
||||
unittest/TestVFS.cpp
|
||||
unittest/TestZipSlip.cpp
|
||||
unittest/TestPS1Eboot.cpp
|
||||
unittest/TestLzrc.cpp
|
||||
unittest/TestCwCheat.cpp
|
||||
unittest/TestMpegCsc.cpp
|
||||
|
||||
@@ -612,6 +612,8 @@ add_library(Core STATIC
|
||||
FileLoaders/HTTPFileLoader.h
|
||||
FileLoaders/LocalFileLoader.cpp
|
||||
FileLoaders/LocalFileLoader.h
|
||||
FileLoaders/PS1EbootFileLoader.cpp
|
||||
FileLoaders/PS1EbootFileLoader.h
|
||||
FileLoaders/RamCachingFileLoader.cpp
|
||||
FileLoaders/RamCachingFileLoader.h
|
||||
FileLoaders/RetryingFileLoader.cpp
|
||||
|
||||
@@ -587,6 +587,7 @@
|
||||
<ClCompile Include="FileLoaders\DiskCachingFileLoader.cpp" />
|
||||
<ClCompile Include="FileLoaders\HTTPFileLoader.cpp" />
|
||||
<ClCompile Include="FileLoaders\LocalFileLoader.cpp" />
|
||||
<ClCompile Include="FileLoaders\PS1EbootFileLoader.cpp" />
|
||||
<ClCompile Include="FileLoaders\RamCachingFileLoader.cpp" />
|
||||
<ClCompile Include="FileLoaders\RetryingFileLoader.cpp" />
|
||||
<ClCompile Include="FileSystems\BlockDevices.cpp" />
|
||||
@@ -931,6 +932,7 @@
|
||||
<ClInclude Include="FileLoaders\DiskCachingFileLoader.h" />
|
||||
<ClInclude Include="FileLoaders\HTTPFileLoader.h" />
|
||||
<ClInclude Include="FileLoaders\LocalFileLoader.h" />
|
||||
<ClInclude Include="FileLoaders\PS1EbootFileLoader.h" />
|
||||
<ClInclude Include="FileLoaders\RamCachingFileLoader.h" />
|
||||
<ClInclude Include="FileLoaders\RetryingFileLoader.h" />
|
||||
<ClInclude Include="FileSystems\BlockDevices.h" />
|
||||
|
||||
@@ -619,6 +619,9 @@
|
||||
<ClCompile Include="FileLoaders\LocalFileLoader.cpp">
|
||||
<Filter>FileLoaders</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FileLoaders\PS1EbootFileLoader.cpp">
|
||||
<Filter>FileLoaders</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FileLoaders\HTTPFileLoader.cpp">
|
||||
<Filter>FileLoaders</Filter>
|
||||
</ClCompile>
|
||||
@@ -1978,6 +1981,9 @@
|
||||
<ClInclude Include="FileLoaders\LocalFileLoader.h">
|
||||
<Filter>FileLoaders</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FileLoaders\PS1EbootFileLoader.h">
|
||||
<Filter>FileLoaders</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FileLoaders\HTTPFileLoader.h">
|
||||
<Filter>FileLoaders</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -473,6 +473,16 @@ GameRegion DetectGameRegionFromID(std::string_view id_full) {
|
||||
* 'X' -> first-party Minis
|
||||
* 'Z' -> third-party Minis
|
||||
*/
|
||||
} else if (id_letters[0] == 'S' && (id_letters[1] == 'L' || id_letters[1] == 'C' || id_letters[1] == 'I')) {
|
||||
// PS1 discs: (S)ony, then (L)icensed, (C)opyrighted or (I)nternational, then the region.
|
||||
switch (id_letters[2]) {
|
||||
case 'U': return GameRegion::USA;
|
||||
case 'E': return GameRegion::EUROPE;
|
||||
case 'P': return GameRegion::JAPAN;
|
||||
case 'K': return GameRegion::KOREA;
|
||||
case 'A': return GameRegion::ASIA;
|
||||
default: break;
|
||||
}
|
||||
// Misc patterns
|
||||
} else if (id_letters == "UTST") {
|
||||
return GameRegion::TEST;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2012- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/Loaders.h"
|
||||
|
||||
// A PS1 disc image (a cue sheet, a CloneCD .ccd, an Alcohol .mds, a CHD, or a bare .bin/.img/.iso) presented as a converted
|
||||
// PS1 EBOOT.PBP, the kind popstation makes, for the firmware's POPS to run. Only the headers are kept
|
||||
// in memory. The disc image is stored uncompressed, as stored DEFLATE blocks, so a read of it goes
|
||||
// straight to the image's files. See docs/POPS.md.
|
||||
class PS1EbootFileLoader : public FileLoader {
|
||||
public:
|
||||
explicit PS1EbootFileLoader(const Path &path);
|
||||
~PS1EbootFileLoader();
|
||||
|
||||
bool IsValid() const { return error_.empty(); }
|
||||
|
||||
bool Exists() override { return IsValid(); }
|
||||
bool IsDirectory() override { return false; }
|
||||
s64 FileSize() override { return size_; }
|
||||
Path GetPath() const override { return path_; }
|
||||
std::string GetFileExtension() const override { return ".pbp"; }
|
||||
size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data, Flags flags = Flags::NONE) override;
|
||||
using FileLoader::ReadAt;
|
||||
void Cancel() override;
|
||||
std::string LatestError() const override { return error_; }
|
||||
|
||||
const std::string &DiscID() const { return discID_; }
|
||||
const std::string &Title() const { return title_; }
|
||||
|
||||
private:
|
||||
struct Track {
|
||||
int number;
|
||||
bool audio;
|
||||
u32 start; // Disc LBA of INDEX 01.
|
||||
u32 pregap; // Sectors before it that belong to the track (INDEX 00, or a generated PREGAP).
|
||||
};
|
||||
// A run of disc sectors, from a file, the CHD (CHD_SOURCE, offset in frames), or (GENERATED) all zeroes.
|
||||
struct Extent {
|
||||
u32 lba;
|
||||
u32 count;
|
||||
int file;
|
||||
s64 offset;
|
||||
int sectorSize; // 2352 raw, 2336 without sync and header, 2048 user data only.
|
||||
bool byteSwapped; // CD audio in a CHD is big-endian.
|
||||
int stride; // Bytes from one sector to the next in a file, more than sectorSize with subchannel data.
|
||||
};
|
||||
static const int GENERATED = -1;
|
||||
static const int CHD_SOURCE = -2;
|
||||
|
||||
bool ParseCue(const Path &cuePath, const std::string &text);
|
||||
bool ParseCcd(const Path &ccdPath, const std::string &text);
|
||||
bool OpenBare(const Path &path);
|
||||
bool OpenCHD(const Path &path);
|
||||
bool ParseMds(const Path &mdsPath, const std::string &data);
|
||||
int AddFile(const Path &path);
|
||||
void AddExtent(u32 lba, u32 count, int file, s64 offset, int sectorSize, bool byteSwapped = false, int stride = 0);
|
||||
|
||||
bool ReadSourceSector(const Extent &e, u32 index, u8 *out);
|
||||
bool ReadRawSectors(u32 lba, u32 count, u8 *out);
|
||||
bool ReadUserData(u32 lba, u8 *out);
|
||||
void ReadDiscID();
|
||||
void BuildHeaders();
|
||||
|
||||
Path path_;
|
||||
std::string error_;
|
||||
std::vector<std::unique_ptr<FileLoader>> files_;
|
||||
std::vector<Extent> extents_;
|
||||
std::vector<Track> tracks_;
|
||||
u32 numSectors_ = 0;
|
||||
|
||||
// A CHD, read a hunk (several frames) at a time.
|
||||
struct _chd_file *chd_ = nullptr;
|
||||
std::mutex chdLock_;
|
||||
std::vector<u8> hunk_;
|
||||
u32 cachedHunk_ = 0xFFFFFFFF;
|
||||
u32 framesPerHunk_ = 0;
|
||||
|
||||
std::string discID_;
|
||||
std::string title_;
|
||||
|
||||
// The PBP up to the PSAR's block index, then the index and the blocks, which are made on the fly.
|
||||
std::vector<u8> head_;
|
||||
u32 psarOffset_ = 0;
|
||||
u32 numBlocks_ = 0;
|
||||
s64 size_ = 0;
|
||||
};
|
||||
|
||||
namespace File {
|
||||
struct FileInfo;
|
||||
}
|
||||
|
||||
// Whether a .cue, .ccd or .mds describes a disc, by its contents.
|
||||
bool IsPS1DiscSheet(FileLoader *fileLoader);
|
||||
|
||||
// Whether a CHD is of a CD (rather than a DVD, like a PSP disc).
|
||||
bool IsCDChd(FileLoader *fileLoader);
|
||||
|
||||
// The file names a cue sheet's FILE lines give.
|
||||
std::vector<std::string> CueSheetFileNames(const Path &cuePath);
|
||||
std::vector<std::string> CueSheetFileNamesFromText(std::string_view text);
|
||||
|
||||
// A file in an archive: its path there, and its size.
|
||||
struct ArchiveEntry {
|
||||
std::string name;
|
||||
s64 size;
|
||||
};
|
||||
|
||||
// The files of a PS1 disc among an archive's, by their paths in it: a cue sheet and the files it names,
|
||||
// a .ccd with its .img (and .sub), an .mds with its .mdf, or else the largest .bin or .img that's a whole
|
||||
// number of sectors.
|
||||
// readText reads a small file from the archive.
|
||||
bool FindPS1DiscFiles(const std::vector<ArchiveEntry> &entries, const std::function<bool(const std::string &name, std::string *text)> &readText, std::vector<std::string> *discFiles);
|
||||
|
||||
// The cue sheet next to a disc image whose FILE lines name it, or an empty path.
|
||||
Path FindCueSheetFor(const Path &imagePath);
|
||||
|
||||
// Removes the .bin and .img files from a directory listing that a cue sheet or .ccd in it describes, so
|
||||
// that each game shows up once, and those that can't be a raw disc image (a size that isn't a whole
|
||||
// number of sectors).
|
||||
void RemoveDescribedDiscImages(std::vector<File::FileInfo> *files);
|
||||
@@ -15,6 +15,8 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/Serialize/Serializer.h"
|
||||
#include "Common/Serialize/SerializeMap.h"
|
||||
#include "Core/FileSystems/BlobFileSystem.h"
|
||||
|
||||
BlobFileSystem::BlobFileSystem(IHandleAllocator *hAlloc, FileLoader *fileLoader, std::string alias)
|
||||
@@ -27,7 +29,11 @@ BlobFileSystem::~BlobFileSystem() {
|
||||
}
|
||||
|
||||
void BlobFileSystem::DoState(PointerWrap &p) {
|
||||
// Not used in real emulation.
|
||||
auto s = p.Section("BlobFileSystem", 1);
|
||||
if (!s)
|
||||
return;
|
||||
// The open handles and their positions. The blob is the one booted.
|
||||
Do(p, entries_);
|
||||
}
|
||||
|
||||
std::vector<PSPFileInfo> BlobFileSystem::GetDirListing(std::string_view path, bool *exists) {
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// This is used for opening a debug file as a blob, and mounting it.
|
||||
// This is used for opening a file as a blob, and mounting it.
|
||||
// Importantly, uses a fileLoader for all access, so http:// URLs are supported.
|
||||
// As of writing, only used by GE dump replay.
|
||||
// Used by GE dump replay, and for the EBOOT a PS1 disc image is presented as.
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
@@ -1061,19 +1061,14 @@ struct ExtendedCoreFile {
|
||||
uint64_t seekPos;
|
||||
};
|
||||
|
||||
CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
|
||||
: BlockDevice(fileLoader), impl_(new CHDImpl()) {
|
||||
Path paths[8];
|
||||
paths[0] = fileLoader->GetPath();
|
||||
int depth = 0;
|
||||
|
||||
core_file_ = new ExtendedCoreFile();
|
||||
core_file_->core.argp = fileLoader;
|
||||
core_file_->core.fsize = [](core_file *file) -> uint64_t {
|
||||
chd_file *OpenCHDThroughLoader(FileLoader *fileLoader, std::string *error) {
|
||||
ExtendedCoreFile *coreFile = new ExtendedCoreFile();
|
||||
coreFile->core.argp = fileLoader;
|
||||
coreFile->core.fsize = [](core_file *file) -> uint64_t {
|
||||
FileLoader *loader = (FileLoader *)file->argp;
|
||||
return loader->FileSize();
|
||||
};
|
||||
core_file_->core.fseek = [](core_file *file, int64_t offset, int seekType) -> int {
|
||||
coreFile->core.fseek = [](core_file *file, int64_t offset, int seekType) -> int {
|
||||
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
||||
switch (seekType) {
|
||||
case SEEK_SET:
|
||||
@@ -1093,7 +1088,7 @@ CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
core_file_->core.fread = [](void *out_data, size_t size, size_t count, core_file *file) {
|
||||
coreFile->core.fread = [](void *out_data, size_t size, size_t count, core_file *file) {
|
||||
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
||||
FileLoader *loader = (FileLoader *)file->argp;
|
||||
uint64_t totalSize = size * count;
|
||||
@@ -1101,12 +1096,24 @@ CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
|
||||
coreFile->seekPos += totalSize;
|
||||
return size * count;
|
||||
};
|
||||
core_file_->core.fclose = [](core_file *file) {
|
||||
coreFile->core.fclose = [](core_file *file) {
|
||||
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
||||
delete coreFile;
|
||||
return 0;
|
||||
};
|
||||
|
||||
chd_file *chd = nullptr;
|
||||
chd_error err = chd_open_core_file(&coreFile->core, CHD_OPEN_READ, NULL, &chd);
|
||||
if (err != CHDERR_NONE) {
|
||||
*error = StringFromFormat("CHD error: %s: %s", fileLoader->GetPath().c_str(), chd_error_string(err));
|
||||
return nullptr;
|
||||
}
|
||||
return chd;
|
||||
}
|
||||
|
||||
CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
|
||||
: BlockDevice(fileLoader), impl_(new CHDImpl()) {
|
||||
|
||||
/*
|
||||
// TODO: Support parent/child CHD files.
|
||||
|
||||
@@ -1158,10 +1165,8 @@ CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
|
||||
}
|
||||
*/
|
||||
|
||||
chd_file *file = nullptr;
|
||||
chd_error err = chd_open_core_file(&core_file_->core, CHD_OPEN_READ, NULL, &file);
|
||||
if (err != CHDERR_NONE) {
|
||||
errorString_ = StringFromFormat("CHD error: %s: %s", paths[depth].c_str(), chd_error_string(err));
|
||||
chd_file *file = OpenCHDThroughLoader(fileLoader, &errorString_);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,10 @@ private:
|
||||
|
||||
struct CHDImpl;
|
||||
|
||||
struct ExtendedCoreFile;
|
||||
struct _chd_file;
|
||||
|
||||
// Opens a CHD that's read through fileLoader, which has to outlive it. Null, and *error set, if it can't.
|
||||
struct _chd_file *OpenCHDThroughLoader(FileLoader *fileLoader, std::string *error);
|
||||
|
||||
class CHDFileBlockDevice : public BlockDevice {
|
||||
public:
|
||||
@@ -207,7 +210,6 @@ public:
|
||||
u32 GetNumBlocks() const override { return numBlocks; }
|
||||
bool IsDisc() const override { return true; }
|
||||
private:
|
||||
struct ExtendedCoreFile *core_file_ = nullptr;
|
||||
std::unique_ptr<CHDImpl> impl_;
|
||||
u8 *readBuffer = nullptr;
|
||||
u32 currentHunk = 0;
|
||||
|
||||
@@ -235,6 +235,10 @@ static bool ReadDiscImage(const PBPReader &pbp, size_t size, u8 *out) {
|
||||
|
||||
// The content ID (DATA.PSP + 0x560, as in the license) and the disc image's PGD header.
|
||||
static bool ReadPBPDrmHeaders(u8 *pgdHeader, u8 *contentId) {
|
||||
// A converted one has no PGD, and one made from a disc image isn't at its path.
|
||||
if (g_popsConvertedPBP) {
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<FileLoader> loader(ConstructFileLoader(g_popsPBPPath));
|
||||
PBPReader pbp(loader.get());
|
||||
return pbp.IsValid() && pbp.ReadSubFile(PBP_EXECUTABLE_PSP, 0x560, 0x30, contentId) &&
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "Core/FileLoaders/DiskCachingFileLoader.h"
|
||||
#include "Core/FileLoaders/HTTPFileLoader.h"
|
||||
#include "Core/FileLoaders/LocalFileLoader.h"
|
||||
#include "Core/FileLoaders/PS1EbootFileLoader.h"
|
||||
#include "Core/FileLoaders/RetryingFileLoader.h"
|
||||
#include "Core/FileLoaders/ZipFileLoader.h"
|
||||
#include "Core/FileSystems/MetaFileSystem.h"
|
||||
@@ -108,9 +109,21 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
|
||||
return IdentifiedFileType::ERROR_IDENTIFYING;
|
||||
}
|
||||
|
||||
if (extension == ".chd" && IsCDChd(fileLoader)) {
|
||||
// PSP discs are DVDs, so a CD is a PS1 disc.
|
||||
return IdentifiedFileType::PSX_ISO;
|
||||
}
|
||||
|
||||
bool isDiscImage = false;
|
||||
if (extension == ".iso" || extension == ".cso" || extension == ".chd") {
|
||||
isDiscImage = true;
|
||||
} else if (extension == ".cue" || extension == ".ccd" || extension == ".mds") {
|
||||
// A PS1 disc, which boots as a converted EBOOT (see PS1EbootFileLoader).
|
||||
return IsPS1DiscSheet(fileLoader) ? IdentifiedFileType::PSX_ISO : IdentifiedFileType::UNKNOWN;
|
||||
} else if (extension == ".mdf") {
|
||||
// Read through its .mds, which says how.
|
||||
std::unique_ptr<FileLoader> mds(ConstructFileLoader(fileLoader->GetPath().WithReplacedExtension(".mds")));
|
||||
return mds && mds->Exists() && IsPS1DiscSheet(mds.get()) ? IdentifiedFileType::PSX_ISO : IdentifiedFileType::UNKNOWN;
|
||||
} else if (extension == ".ppst") {
|
||||
return IdentifiedFileType::PPSSPP_SAVESTATE;
|
||||
} else if (extension == ".ppdmp") {
|
||||
@@ -302,6 +315,17 @@ FileLoader *ResolveFileLoaderTarget(FileLoader *fileLoader, IdentifiedFileType *
|
||||
// Re-identify the file.
|
||||
*fileType = Identify_File(fileLoader, errorString);
|
||||
}
|
||||
} else if (*fileType == IdentifiedFileType::PSX_ISO) {
|
||||
// A PS1 disc image runs on the firmware's POPS, as a converted EBOOT made from it.
|
||||
PS1EbootFileLoader *eboot = new PS1EbootFileLoader(fileLoader->GetPath());
|
||||
if (!eboot->IsValid()) {
|
||||
*errorString = eboot->LatestError();
|
||||
delete eboot;
|
||||
return fileLoader;
|
||||
}
|
||||
delete fileLoader;
|
||||
*fileType = IdentifiedFileType::PSP_PS1_PBP;
|
||||
return eboot;
|
||||
} else if (*fileType == IdentifiedFileType::ARCHIVE_ZIP) {
|
||||
// Handle zip files, take automatic action depending on contents.
|
||||
// Can also return nullptr.
|
||||
@@ -584,6 +608,37 @@ bool DetectArchiveContents(VFSInterface *vfs, ZipFileInfo *info) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// A PS1 disc in the root or one level down, as in a zip.
|
||||
std::vector<ArchiveEntry> entries;
|
||||
for (const File::FileInfo &file : listing) {
|
||||
if (!file.isDirectory) {
|
||||
entries.push_back(ArchiveEntry{ file.fullName.ToString(), (s64)file.size });
|
||||
continue;
|
||||
}
|
||||
std::vector<File::FileInfo> subListing;
|
||||
vfs->GetFileListing(file.fullName.ToString(), &subListing, nullptr);
|
||||
for (const File::FileInfo &sub : subListing) {
|
||||
if (!sub.isDirectory) {
|
||||
entries.push_back(ArchiveEntry{ sub.fullName.ToString(), (s64)sub.size });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (FindPS1DiscFiles(entries, [vfs](const std::string &name, std::string *text) {
|
||||
size_t size = 0;
|
||||
uint8_t *data = vfs->ReadFile(name, &size);
|
||||
if (!data || size > 64 * 1024) {
|
||||
delete[] data;
|
||||
return false;
|
||||
}
|
||||
text->assign((const char *)data, size);
|
||||
delete[] data;
|
||||
return true;
|
||||
}, &info->discFiles)) {
|
||||
info->contents = ZipFileContents::PSX_DISC;
|
||||
info->contentName = info->discFiles[0];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -618,6 +673,7 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
|
||||
bool hasPRX = false;
|
||||
bool hasPluginIni = false;
|
||||
s64 totalFileSize = 0;
|
||||
std::vector<ArchiveEntry> archiveEntries; // In the root or one level down, where a disc image can be.
|
||||
|
||||
// TODO: It might be cleaner to write separate detection functions, but this big loop doing it all at once
|
||||
// is quite convenient and makes it easy to add shared heuristics.
|
||||
@@ -644,6 +700,9 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
|
||||
|
||||
int prevSlashLocation = -1;
|
||||
int slashCount = countSlashes(lowerCaseName, &prevSlashLocation);
|
||||
if (slashCount <= 1) {
|
||||
archiveEntries.push_back(ArchiveEntry{ fileName, (s64)stat.size });
|
||||
}
|
||||
if (lowerCaseName.find("eboot.pbp") != std::string::npos) {
|
||||
if (slashCount >= 1 && (!isPSPMemstickGame || prevSlashLocation < stripChars + 1)) {
|
||||
stripChars = prevSlashLocation + 1;
|
||||
@@ -739,11 +798,23 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
|
||||
info->ignoreMetaFiles = false;
|
||||
info->totalFileSize = totalFileSize;
|
||||
|
||||
// A PS1 disc: a cue sheet, .ccd or .mds and its files, or a lone raw image, which only counts if nothing else does.
|
||||
const bool isPS1Disc = FindPS1DiscFiles(archiveEntries, [z](const std::string &name, std::string *text) {
|
||||
const zip_int64_t index = zip_name_locate(z, name.c_str(), 0);
|
||||
zip_stat_t stat{};
|
||||
return index >= 0 && zip_stat_index(z, index, 0, &stat) >= 0 && stat.size < 64 * 1024 && ZipExtractFileToMemory(z, (int)index, text);
|
||||
}, &info->discFiles);
|
||||
const std::string discExt = isPS1Disc ? Path(info->discFiles[0]).GetFileExtension() : "";
|
||||
const bool isPS1DiscWithCue = discExt == ".cue" || discExt == ".ccd" || discExt == ".mds";
|
||||
|
||||
// Priority ordering for detecting the various kinds of zip file content.s
|
||||
if (isPSPMemstickGame) {
|
||||
info->contents = ZipFileContents::PSP_GAME_DIR;
|
||||
} else if (isZippedISO) {
|
||||
info->contents = ZipFileContents::ISO_FILE;
|
||||
} else if (isPS1DiscWithCue) {
|
||||
info->contents = ZipFileContents::PSX_DISC;
|
||||
info->contentName = info->discFiles[0];
|
||||
} else if (isTexturePack) {
|
||||
info->stripChars = stripCharsTexturePack;
|
||||
info->ignoreMetaFiles = true;
|
||||
@@ -765,6 +836,9 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
|
||||
info->contents = ZipFileContents::EXTRACTED_GAME;
|
||||
} else if (hasPluginIni && hasPRX) {
|
||||
info->contents = ZipFileContents::PRX_PLUGIN;
|
||||
} else if (isPS1Disc) {
|
||||
info->contents = ZipFileContents::PSX_DISC;
|
||||
info->contentName = info->discFiles[0];
|
||||
} else {
|
||||
info->contents = ZipFileContents::UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/File/Path.h"
|
||||
@@ -192,6 +193,8 @@ enum class ZipFileContents {
|
||||
EXTRACTED_GAME,
|
||||
SAVE_STATES,
|
||||
PRX_PLUGIN,
|
||||
// A PS1 disc image, which can be one file or several (a cue sheet and what it names).
|
||||
PSX_DISC,
|
||||
};
|
||||
|
||||
// TODO: Rename to ArchiveContentsInfo or something.
|
||||
@@ -211,6 +214,7 @@ struct ZipFileInfo {
|
||||
std::string mTime;
|
||||
std::string iniContents;
|
||||
std::string isoFilename; // Used by SevenZ only right now.
|
||||
std::vector<std::string> discFiles; // For PSX_DISC, the disc's files in the archive.
|
||||
s64 totalFileSize = 0;
|
||||
|
||||
std::string contentName;
|
||||
|
||||
+13
-4
@@ -35,6 +35,7 @@
|
||||
#include "Core/ELF/ParamSFO.h"
|
||||
#include "Core/ELF/PBPReader.h"
|
||||
#include "Core/ELF/PrxDecrypter.h"
|
||||
#include "Core/FileLoaders/PS1EbootFileLoader.h"
|
||||
|
||||
#include "Core/FileSystems/BlockDevices.h"
|
||||
#include "Core/FileSystems/BlobFileSystem.h"
|
||||
@@ -671,14 +672,20 @@ bool Load_PSP_POPS(FileLoader *fileLoader, std::string *error_string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Its directory as host0:, as for a homebrew EBOOT.
|
||||
// Its directory as host0:, as for a homebrew EBOOT. An EBOOT made from a disc image is the only file there.
|
||||
const Path fullPath = fileLoader->GetPath();
|
||||
auto fs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, fullPath.NavigateUp(), FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);
|
||||
pspFileSystem.Mount("host0:", fs);
|
||||
std::string gamePath;
|
||||
if (dynamic_cast<PS1EbootFileLoader *>(fileLoader)) {
|
||||
pspFileSystem.Mount("host0:", std::make_shared<BlobFileSystem>(&pspFileSystem, fileLoader, "EBOOT.PBP"));
|
||||
gamePath = "host0:/EBOOT.PBP";
|
||||
} else {
|
||||
auto fs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, fullPath.NavigateUp(), FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);
|
||||
pspFileSystem.Mount("host0:", fs);
|
||||
gamePath = "host0:/" + fullPath.GetFilename();
|
||||
}
|
||||
pspFileSystem.SetStartingDirectory("host0:/");
|
||||
|
||||
__PopsBootInit();
|
||||
const std::string gamePath = "host0:/" + fullPath.GetFilename();
|
||||
INFO_LOG(Log::Loader, "POPS: booting %s through %s", gamePath.c_str(), popsmanPath);
|
||||
if (!__KernelLoadExec(currentMIPS, popsmanPath, 0, error_string)) {
|
||||
return false;
|
||||
@@ -687,6 +694,8 @@ bool Load_PSP_POPS(FileLoader *fileLoader, std::string *error_string) {
|
||||
if (!__PopsBootStartInit(fileLoader, gamePath, error_string)) {
|
||||
return false;
|
||||
}
|
||||
auto sy = GetI18NCategory(I18NCat::SYSTEM);
|
||||
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("PS1 support is still experimental. The game might not work, or there may be glitches."), 6.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -288,7 +288,12 @@ static void GetBootError(IdentifiedFileType type, std::string *errorString) {
|
||||
|
||||
case IdentifiedFileType::ARCHIVE_7Z: *errorString = "7z file detected (Require 7-Zip)"; break;
|
||||
case IdentifiedFileType::PSP_PKG: *errorString = "PKG game updates need to be installed, not booted."; break;
|
||||
case IdentifiedFileType::PSX_ISO: *errorString = "PSX game image detected."; break;
|
||||
case IdentifiedFileType::PSX_ISO:
|
||||
// It only gets here if it couldn't be made into an EBOOT, which says why.
|
||||
if (errorString->empty()) {
|
||||
*errorString = "PSX game image detected.";
|
||||
}
|
||||
break;
|
||||
case IdentifiedFileType::PS2_ISO: *errorString = "PS2 game image detected."; break;
|
||||
case IdentifiedFileType::PS3_ISO: *errorString = "PS2 game image detected."; break;
|
||||
case IdentifiedFileType::NORMAL_DIRECTORY: *errorString = "Just a directory."; break;
|
||||
|
||||
@@ -240,7 +240,7 @@ void DarwinFileSystemServices::presentDirectoryPanel(
|
||||
panel.canChooseDirectories = allowDirectories;
|
||||
switch (fileType) {
|
||||
case BrowseFileType::BOOTABLE:
|
||||
[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"iso", @"cso", @"chd", @"pbp", @"elf", @"zip", @"ppdmp", @"prx", nil]];
|
||||
[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"iso", @"cso", @"chd", @"pbp", @"elf", @"zip", @"ppdmp", @"prx", @"cue", @"ccd", @"mds", @"mdf", @"bin", @"img", nil]];
|
||||
break;
|
||||
case BrowseFileType::SAVE_STATE:
|
||||
[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"ppst", nil]];
|
||||
|
||||
+121
-49
@@ -234,6 +234,19 @@ static std::string ZipReadFileByIndex(struct zip *z, int file_index) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Where a PS1 disc's files go: a folder of their own, named after the cue sheet or .ccd, when there's
|
||||
// more than one.
|
||||
static Path PS1DiscInstallDir(const Path &destDir, const std::vector<std::string> &files) {
|
||||
if (files.size() <= 1) {
|
||||
return destDir;
|
||||
}
|
||||
const Path first(files[0]);
|
||||
const std::string name = first.GetFilename();
|
||||
const Path dir = destDir / name.substr(0, name.size() - first.GetFileExtension().size());
|
||||
File::CreateFullPath(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
// Parameters need to be by value, since this is a thread func.
|
||||
void GameManager::InstallZipContents(ZipFileTask task) {
|
||||
SetCurrentThreadName("InstallZipContents");
|
||||
@@ -288,68 +301,81 @@ void GameManager::InstallZipContents(ZipFileTask task) {
|
||||
if (task.zipFileInfo && task.zipFileInfo->archiveType == ArchiveType::SevenZ) {
|
||||
Path destPath = task.destination;
|
||||
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
|
||||
Path fn(task.zipFileInfo->isoFilename);
|
||||
Path destFile = destPath / fn.GetFilename();
|
||||
// An ISO, or a PS1 disc's files.
|
||||
std::vector<std::string> names;
|
||||
if (task.zipFileInfo->contents == ZipFileContents::PSX_DISC) {
|
||||
names = task.zipFileInfo->discFiles;
|
||||
destPath = PS1DiscInstallDir(destPath, names);
|
||||
} else {
|
||||
names.push_back(task.zipFileInfo->isoFilename);
|
||||
}
|
||||
const size_t blockSize = 1024 * 128;
|
||||
|
||||
SevenZipFileReader *reader = SevenZipFileReader::Create(task.fileName, "", true);
|
||||
VFSFileReference *vfsRef = nullptr;
|
||||
VFSOpenFile *vfsFile = nullptr;
|
||||
FILE *f = nullptr;
|
||||
bool success = false;
|
||||
bool success = reader != nullptr;
|
||||
bool writeFailed = false;
|
||||
bool readFailed = false;
|
||||
size_t totalSize = 0;
|
||||
size_t bytesCopied = 0;
|
||||
std::vector<Path> destFiles;
|
||||
for (size_t n = 0; n < names.size() && success; n++) {
|
||||
const std::string &name = names[n];
|
||||
const Path destFile = destPath / Path(name).GetFilename();
|
||||
VFSFileReference *vfsRef = reader->GetFile(name);
|
||||
VFSOpenFile *vfsFile = nullptr;
|
||||
FILE *f = nullptr;
|
||||
size_t totalSize = 0;
|
||||
size_t bytesCopied = 0;
|
||||
success = false;
|
||||
|
||||
if (reader) {
|
||||
vfsRef = reader->GetFile(task.zipFileInfo->isoFilename);
|
||||
}
|
||||
if (vfsRef) {
|
||||
vfsFile = reader->OpenFileForRead(vfsRef, &totalSize);
|
||||
}
|
||||
if (vfsFile) {
|
||||
f = File::OpenCFile(destFile, "wb");
|
||||
}
|
||||
|
||||
if (f) {
|
||||
std::vector<u8> buffer(blockSize);
|
||||
while (bytesCopied < totalSize) {
|
||||
size_t readSize = std::min(blockSize, totalSize - bytesCopied);
|
||||
size_t bytesRead = reader->Read(vfsFile, buffer.data(), readSize);
|
||||
if (bytesRead == 0) {
|
||||
ERROR_LOG(Log::HLE, "Failed to stream extract 7z ISO '%s'", task.zipFileInfo->isoFilename.c_str());
|
||||
readFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t written = fwrite(buffer.data(), 1, bytesRead, f);
|
||||
if (written != bytesRead) {
|
||||
ERROR_LOG(Log::HLE, "Wrote %d bytes out of %d - Disk full?", (int)written, (int)bytesRead);
|
||||
writeFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bytesCopied += bytesRead;
|
||||
installProgress_ = totalSize > 0 ? (float)bytesCopied / (float)totalSize : 1.0f;
|
||||
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 1.0f, installProgress_, 0.1f);
|
||||
if (vfsRef) {
|
||||
vfsFile = reader->OpenFileForRead(vfsRef, &totalSize);
|
||||
}
|
||||
if (vfsFile) {
|
||||
f = File::OpenCFile(destFile, "wb");
|
||||
}
|
||||
|
||||
success = bytesCopied == totalSize;
|
||||
fclose(f);
|
||||
f = nullptr;
|
||||
}
|
||||
if (f) {
|
||||
destFiles.push_back(destFile);
|
||||
std::vector<u8> buffer(blockSize);
|
||||
while (bytesCopied < totalSize) {
|
||||
size_t readSize = std::min(blockSize, totalSize - bytesCopied);
|
||||
size_t bytesRead = reader->Read(vfsFile, buffer.data(), readSize);
|
||||
if (bytesRead == 0) {
|
||||
ERROR_LOG(Log::HLE, "Failed to stream extract 7z file '%s'", name.c_str());
|
||||
readFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (vfsFile) {
|
||||
reader->CloseFile(vfsFile);
|
||||
}
|
||||
if (vfsRef) {
|
||||
reader->ReleaseFile(vfsRef);
|
||||
size_t written = fwrite(buffer.data(), 1, bytesRead, f);
|
||||
if (written != bytesRead) {
|
||||
ERROR_LOG(Log::HLE, "Wrote %d bytes out of %d - Disk full?", (int)written, (int)bytesRead);
|
||||
writeFailed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bytesCopied += bytesRead;
|
||||
const float fileProgress = totalSize > 0 ? (float)bytesCopied / (float)totalSize : 1.0f;
|
||||
installProgress_ = ((float)n + fileProgress) / (float)names.size();
|
||||
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 1.0f, installProgress_, 0.1f);
|
||||
}
|
||||
|
||||
success = bytesCopied == totalSize;
|
||||
fclose(f);
|
||||
f = nullptr;
|
||||
}
|
||||
|
||||
if (vfsFile) {
|
||||
reader->CloseFile(vfsFile);
|
||||
}
|
||||
if (vfsRef) {
|
||||
reader->ReleaseFile(vfsRef);
|
||||
}
|
||||
}
|
||||
delete reader;
|
||||
|
||||
if (!success) {
|
||||
File::Delete(destFile);
|
||||
for (const Path &destFile : destFiles) {
|
||||
File::Delete(destFile);
|
||||
}
|
||||
if (writeFailed) {
|
||||
SetInstallError(sy->T("Storage full"));
|
||||
} else if (readFailed) {
|
||||
@@ -406,6 +432,12 @@ void GameManager::InstallZipContents(ZipFileTask task) {
|
||||
success = InstallZippedISO(z, zipInfo.isoFileIndex, task.destination);
|
||||
break;
|
||||
}
|
||||
case ZipFileContents::PSX_DISC:
|
||||
{
|
||||
INFO_LOG(Log::HLE, "Installing PS1 disc '%s' into '%s'", task.fileName.c_str(), task.destination.c_str());
|
||||
success = InstallZippedPS1Disc(z, zipInfo.discFiles, task.destination);
|
||||
break;
|
||||
}
|
||||
case ZipFileContents::TEXTURE_PACK:
|
||||
{
|
||||
Path dest;
|
||||
@@ -921,6 +953,46 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameManager::InstallZippedPS1Disc(struct zip *z, const std::vector<std::string> &files, const Path &destDir) {
|
||||
Path dir = destDir.empty() ? Path(g_Config.currentDirectory) : destDir;
|
||||
dir = PS1DiscInstallDir(dir, files);
|
||||
|
||||
int64_t allBytes = 1;
|
||||
std::vector<zip_int64_t> indices;
|
||||
for (const std::string &file : files) {
|
||||
const zip_int64_t index = zip_name_locate(z, file.c_str(), 0);
|
||||
zip_stat_t zstat;
|
||||
zip_stat_init(&zstat);
|
||||
if (index < 0 || zip_stat_index(z, index, 0, &zstat) < 0) {
|
||||
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
|
||||
SetInstallError(iz->T("Zip archive corrupt"));
|
||||
return false;
|
||||
}
|
||||
allBytes += zstat.size;
|
||||
indices.push_back(index);
|
||||
}
|
||||
|
||||
auto di = GetI18NCategory(I18NCat::DIALOG);
|
||||
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
|
||||
int64_t bytesCopied = 0;
|
||||
bool success = true;
|
||||
std::vector<Path> written;
|
||||
for (size_t i = 0; i < files.size() && success; i++) {
|
||||
const Path outFile = dir / Path(files[i]).GetFilename();
|
||||
success = ExtractFile(z, (int)indices[i], outFile, &bytesCopied, allBytes);
|
||||
written.push_back(outFile);
|
||||
}
|
||||
if (!success) {
|
||||
for (const Path &file : written) {
|
||||
File::Delete(file);
|
||||
}
|
||||
} else {
|
||||
INFO_LOG(Log::IO, "Unzipped PS1 disc to '%s'", dir.c_str());
|
||||
}
|
||||
// The caller finishes the install.
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GameManager::InstallZipOnThread(ZipFileTask task) {
|
||||
if (InstallInProgress() || installDonePending_) {
|
||||
return false;
|
||||
|
||||
@@ -95,6 +95,9 @@ public:
|
||||
// Extracts the contents of an open zip archive into dest. Exposed for testing.
|
||||
// maxTotalSize limits the total decompressed size (zip bomb protection).
|
||||
bool ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot, int64_t maxTotalSize = 0x100000000ULL);
|
||||
// Extracts a PS1 disc's files (ZipFileInfo::discFiles), into a folder of their own if there's more
|
||||
// than one. Exposed for testing.
|
||||
bool InstallZippedPS1Disc(struct zip *z, const std::vector<std::string> &files, const Path &destDir);
|
||||
|
||||
private:
|
||||
void InstallZipContents(ZipFileTask task);
|
||||
|
||||
@@ -1365,6 +1365,15 @@ bool FirmwareVersionPlaysUmdVideo(std::string_view version) {
|
||||
return FirmwareVersionSupportsVSH(version) && FirmwareVersionToInt(version) >= FirmwareVersionToInt(UMD_VIDEO_MIN_FIRMWARE);
|
||||
}
|
||||
|
||||
bool InstalledFirmwareRunsPS1Discs() {
|
||||
// The POPS managers PopsBoot knows for converted EBOOTs: 3.95 to 6.61. A version in between that it
|
||||
// doesn't know fails the boot with a message saying so.
|
||||
InstalledFirmwareInfo info;
|
||||
ReadInstalledFirmwareInfo(g_Config.nandRootDirectory, &info, false);
|
||||
const int version = FirmwareVersionToInt(info.version);
|
||||
return version >= 395 && version <= 661 && File::Exists(g_Config.nandRootDirectory / "flash0/kd/popsman.prx");
|
||||
}
|
||||
|
||||
Path GetUmdVideoVSHPath() {
|
||||
InstalledFirmwareInfo info;
|
||||
ReadInstalledFirmwareInfo(g_Config.nandRootDirectory, &info, false);
|
||||
|
||||
@@ -192,6 +192,9 @@ extern const char *const UMD_VIDEO_MIN_FIRMWARE;
|
||||
// vshmain.prx, if the installed firmware's XMB can play UMD Video discs. Empty otherwise.
|
||||
Path GetUmdVideoVSHPath();
|
||||
|
||||
// Whether the installed firmware's POPS can run a PS1 disc image, as the converted EBOOT made from it.
|
||||
bool InstalledFirmwareRunsPS1Discs();
|
||||
|
||||
// Installs a firmware into the NAND directory. Two firmwares can't be merged - a file the new one
|
||||
// doesn't have would linger and still get loaded - so this replaces rather than overlays.
|
||||
//
|
||||
|
||||
+2
-2
@@ -743,8 +743,8 @@ AudioBackend *System_CreateAudioBackend() {
|
||||
static void InitializeFilters(std::vector<std::string> &filters, BrowseFileType type) {
|
||||
switch (type) {
|
||||
case BrowseFileType::BOOTABLE:
|
||||
filters.push_back("All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)");
|
||||
filters.push_back("*.pbp *.elf *.iso *.cso *.chd *.prx *.zip *.ppdmp");
|
||||
filters.push_back("All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp *.cue *.ccd *.mds *.mdf *.bin *.img)");
|
||||
filters.push_back("*.pbp *.elf *.iso *.cso *.chd *.prx *.zip *.ppdmp *.cue *.ccd *.mds *.mdf *.bin *.img");
|
||||
break;
|
||||
case BrowseFileType::SAVE_STATE:
|
||||
filters.push_back("Save state files (*.ppst)");
|
||||
|
||||
+17
-3
@@ -38,6 +38,7 @@
|
||||
#include "Core/Util/RecentFiles.h"
|
||||
#include "Core/HLE/sceCtrl.h"
|
||||
#include "Core/ELF/PBPReader.h"
|
||||
#include "Core/FileLoaders/PS1EbootFileLoader.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/Util/VideoPlayer.h"
|
||||
|
||||
@@ -77,7 +78,8 @@ static float ListTextRightMargin(GameBrowserViewMode viewMode) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
static void DrawIconWithText(UIContext &dc, ImageID image, std::string_view text, const Bounds &bounds, GameBrowserViewMode viewMode, const UI::Style &style) {
|
||||
// In the list views, rightText (like a game ID) goes at the right edge, as for a game with a picture.
|
||||
static void DrawIconWithText(UIContext &dc, ImageID image, std::string_view text, const Bounds &bounds, GameBrowserViewMode viewMode, const UI::Style &style, std::string_view rightText = {}) {
|
||||
const bool gridStyle = viewMode == GameBrowserViewMode::GRID;
|
||||
float tw, th;
|
||||
dc.MeasureText(dc.GetFontStyle(), gridStyle ? g_Config.fGameGridScale : 1.0, gridStyle ? g_Config.fGameGridScale : 1.0, text, &tw, &th, 0);
|
||||
@@ -121,6 +123,14 @@ static void DrawIconWithText(UIContext &dc, ImageID image, std::string_view text
|
||||
|
||||
float tx = textX;
|
||||
int availableWidth = bounds.w - textX - ListTextRightMargin(viewMode);
|
||||
if (!rightText.empty()) {
|
||||
float vw, vh;
|
||||
dc.MeasureText(dc.GetFontStyle(), 0.7f, 0.7f, rightText, &vw, &vh, 0);
|
||||
availableWidth -= vw + 20;
|
||||
dc.SetFontScale(0.7f, 0.7f);
|
||||
dc.DrawText(rightText, bounds.x + availableWidth + textX + 10, bounds.centerY(), style.fgColor, ALIGN_VCENTER);
|
||||
dc.SetFontScale(1.0f, 1.0f);
|
||||
}
|
||||
float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f;
|
||||
if (availableWidth < tw) {
|
||||
tx -= (1.0f + sin(time_now_d() * 1.5f)) * sineWidth;
|
||||
@@ -423,6 +433,8 @@ void GameButton::Draw(UIContext &dc) {
|
||||
// No icon, but drawBackground is set. Let's show a plain icon depending on type.
|
||||
if (ginfo->fileType == IdentifiedFileType::PSP_ISO || ginfo->fileType == IdentifiedFileType::PSP_ISO_NP) {
|
||||
imageIcon = ImageID("I_UMD");
|
||||
} else if (ginfo->fileType == IdentifiedFileType::PSX_ISO || ginfo->fileType == IdentifiedFileType::PSP_PS1_PBP) {
|
||||
imageIcon = ImageID("I_PSX_ISO");
|
||||
} else if (gamePath_.Type() == PathType::HTTP) {
|
||||
imageIcon = ginfo->fileType == IdentifiedFileType::UNKNOWN ? ImageID("I_LINK_OUT_QUESTION") : ImageID("I_LINK_OUT");
|
||||
} else {
|
||||
@@ -549,7 +561,7 @@ void GameButton::Draw(UIContext &dc) {
|
||||
if (down_) style = dc.GetTheme().itemDownStyle;
|
||||
if (!IsEnabled()) style = dc.GetTheme().itemDisabledStyle;
|
||||
|
||||
DrawIconWithText(dc, imageIcon, ginfo->GetTitle(), bounds_, viewMode_, style);
|
||||
DrawIconWithText(dc, imageIcon, ginfo->GetTitle(), bounds_, viewMode_, style, g_Config.bShowIDOnGameIcon ? std::string_view(ginfo->id_version) : std::string_view());
|
||||
|
||||
if (overlayColor) {
|
||||
dc.FillRect(Drawable(overlayColor), overlayBounds);
|
||||
@@ -1116,7 +1128,9 @@ void GameBrowser::Refresh() {
|
||||
}
|
||||
} else if (!listingPending_) {
|
||||
std::vector<File::FileInfo> fileInfo;
|
||||
path_.GetListing(fileInfo, "iso:cso:chd:pbp:elf:prx:ppdmp:");
|
||||
path_.GetListing(fileInfo, "iso:cso:chd:pbp:elf:prx:ppdmp:cue:ccd:mds:bin:img:");
|
||||
// PS1 discs: a .bin or .img shows up as its cue sheet or .ccd, when it has one.
|
||||
RemoveDescribedDiscImages(&fileInfo);
|
||||
for (size_t i = 0; i < fileInfo.size(); i++) {
|
||||
bool isGame = !fileInfo[i].isDirectory;
|
||||
bool isSaveData = false;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "Common/System/Request.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Core/FileLoaders/PS1EbootFileLoader.h"
|
||||
#include "Core/FileSystems/ISOFileSystem.h"
|
||||
#include "Core/FileSystems/DirectoryFileSystem.h"
|
||||
#include "Core/FileSystems/VirtualDiscFileSystem.h"
|
||||
@@ -597,6 +598,8 @@ public:
|
||||
switch (fileType) {
|
||||
case IdentifiedFileType::PSP_PBP:
|
||||
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
||||
case IdentifiedFileType::PSP_PS1_PBP:
|
||||
case IdentifiedFileType::PSX_ISO:
|
||||
{
|
||||
auto pbpLoader = info_->GetFileLoader();
|
||||
if (fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {
|
||||
@@ -604,6 +607,9 @@ public:
|
||||
if (ebootPath != gamePath_) {
|
||||
pbpLoader.reset(ConstructFileLoader(ebootPath));
|
||||
}
|
||||
} else if (fileType == IdentifiedFileType::PSX_ISO) {
|
||||
// The EBOOT a PS1 disc image boots as, which has its title and disc ID.
|
||||
pbpLoader.reset(new PS1EbootFileLoader(gamePath_));
|
||||
}
|
||||
|
||||
if (!pbpLoader->Exists()) {
|
||||
|
||||
+3
-1
@@ -233,7 +233,6 @@ static bool FileTypeIsPlayable(IdentifiedFileType fileType) {
|
||||
switch (fileType) {
|
||||
case IdentifiedFileType::ERROR_IDENTIFYING:
|
||||
case IdentifiedFileType::UNKNOWN:
|
||||
case IdentifiedFileType::PSX_ISO:
|
||||
case IdentifiedFileType::PS2_ISO:
|
||||
case IdentifiedFileType::PS3_ISO:
|
||||
case IdentifiedFileType::UNKNOWN_BIN:
|
||||
@@ -244,6 +243,9 @@ static bool FileTypeIsPlayable(IdentifiedFileType fileType) {
|
||||
case IdentifiedFileType::PSP_UMD_VIDEO_ISO:
|
||||
// Reverse logic.
|
||||
return false;
|
||||
case IdentifiedFileType::PSX_ISO:
|
||||
// Through the firmware's POPS.
|
||||
return InstalledFirmwareRunsPS1Discs();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ void InstallZipScreen::CreateSettingsViews(UI::ViewGroup *parent) {
|
||||
switch (zipFileInfo_.contents) {
|
||||
case ZipFileContents::ISO_FILE:
|
||||
case ZipFileContents::PSP_GAME_DIR:
|
||||
case ZipFileContents::PSX_DISC:
|
||||
installChoice_ = parent->Add(new Choice(iz->T("Install"), ImageID("I_FOLDER_UPLOAD")));
|
||||
installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall);
|
||||
|
||||
@@ -146,6 +147,7 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
|
||||
switch (zipFileInfo_.contents) {
|
||||
case ZipFileContents::ISO_FILE:
|
||||
case ZipFileContents::PSP_GAME_DIR:
|
||||
case ZipFileContents::PSX_DISC:
|
||||
{
|
||||
std::string_view question = iz->T("Install game from ZIP file?");
|
||||
|
||||
@@ -155,7 +157,7 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
|
||||
leftColumn->Add(new TextView(zipFileInfo_.contentName));
|
||||
}
|
||||
|
||||
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE) {
|
||||
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSX_DISC) {
|
||||
const bool isInDownloads = File::IsProbablyInDownloadsFolder(zipPath_);
|
||||
Path parent;
|
||||
if (!isInDownloads && zipPath_.CanNavigateUp()) {
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
<ClInclude Include="..\..\Core\FileLoaders\DiskCachingFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\HTTPFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\LocalFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\PS1EbootFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\RamCachingFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\RetryingFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\ZipFileLoader.h" />
|
||||
@@ -450,6 +451,7 @@
|
||||
<ClCompile Include="..\..\Core\FileLoaders\DiskCachingFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\HTTPFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\LocalFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\PS1EbootFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\RamCachingFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\RetryingFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\ZipFileLoader.cpp" />
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<ClCompile Include="..\..\Core\FileLoaders\DiskCachingFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\HTTPFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\LocalFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\PS1EbootFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\RamCachingFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\RetryingFileLoader.cpp" />
|
||||
<ClCompile Include="..\..\Core\FileLoaders\ZipFileLoader.cpp" />
|
||||
@@ -477,6 +478,7 @@
|
||||
<ClInclude Include="..\..\Core\FileLoaders\DiskCachingFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\HTTPFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\LocalFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\PS1EbootFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\RamCachingFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\RetryingFileLoader.h" />
|
||||
<ClInclude Include="..\..\Core\FileLoaders\ZipFileLoader.h" />
|
||||
|
||||
@@ -548,7 +548,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
|
||||
std::vector<std::string> supportedExtensions = {};
|
||||
switch ((BrowseFileType)param3) {
|
||||
case BrowseFileType::BOOTABLE:
|
||||
supportedExtensions = { ".cso", ".iso", ".chd", ".elf", ".pbp", ".zip", ".prx", ".bin" }; // should .bin even be here?
|
||||
supportedExtensions = { ".cso", ".iso", ".chd", ".elf", ".pbp", ".zip", ".prx", ".bin", ".cue", ".ccd", ".mds", ".mdf", ".img" };
|
||||
break;
|
||||
case BrowseFileType::SAVE_STATE:
|
||||
supportedExtensions = { ".ppst" };
|
||||
|
||||
+1
-1
@@ -637,7 +637,7 @@ static std::wstring FinalizeFilter(std::wstring filter) {
|
||||
static std::wstring MakeWindowsFilter(BrowseFileType type) {
|
||||
switch (type) {
|
||||
case BrowseFileType::BOOTABLE:
|
||||
return FinalizeFilter(L"All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx;*.zip;*.ppdmp|PSP ROMs (*.iso *.cso *.chd *.pbp *.elf *.prx)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx|Homebrew/Demos installers (*.zip)|*.zip|All files (*.*)|*.*||");
|
||||
return FinalizeFilter(L"All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp *.cue *.ccd *.mds *.mdf *.bin *.img)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx;*.zip;*.ppdmp;*.cue;*.ccd;*.mds;*.mdf;*.bin;*.img|PSP ROMs (*.iso *.cso *.chd *.pbp *.elf *.prx)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx|PS1 disc images (*.cue *.ccd *.mds *.mdf *.chd *.bin *.img *.iso *.pbp)|*.cue;*.ccd;*.mds;*.mdf;*.chd;*.bin;*.img;*.iso;*.pbp|Homebrew/Demos installers (*.zip)|*.zip|All files (*.*)|*.*||");
|
||||
case BrowseFileType::SAVE_STATE:
|
||||
return FinalizeFilter(L"Save state files (*.ppst)|*.ppst|All files (*.*)|*.*||");
|
||||
case BrowseFileType::INI:
|
||||
|
||||
@@ -690,6 +690,7 @@ EXEC_AND_LIB_FILES := \
|
||||
$(SRC)/Core/FileLoaders/DiskCachingFileLoader.cpp \
|
||||
$(SRC)/Core/FileLoaders/HTTPFileLoader.cpp \
|
||||
$(SRC)/Core/FileLoaders/LocalFileLoader.cpp \
|
||||
$(SRC)/Core/FileLoaders/PS1EbootFileLoader.cpp \
|
||||
$(SRC)/Core/FileLoaders/RamCachingFileLoader.cpp \
|
||||
$(SRC)/Core/FileLoaders/RetryingFileLoader.cpp \
|
||||
$(SRC)/Core/FileLoaders/ZipFileLoader.cpp \
|
||||
@@ -1131,6 +1132,7 @@ ifeq ($(UNITTEST),1)
|
||||
$(SRC)/unittest/TestSplineTessellation.cpp \
|
||||
$(SRC)/unittest/TestGEMath.cpp \
|
||||
$(SRC)/unittest/TestZipSlip.cpp \
|
||||
$(SRC)/unittest/TestPS1Eboot.cpp \
|
||||
$(SRC)/unittest/UnitTest.cpp
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = يتم دعم صور JPG و PNG فقط #
|
||||
Path does not exist! = المسار غير موجود! # AI translated
|
||||
Pause when not focused = &إيقاف مؤقت حينما لا يكون مفعلاً
|
||||
Plugins = الإضافات # AI translated
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = دعم PS1 لا يزال تجريبيًا. قد لا تعمل اللعبة، أو قد تظهر بعض الأخطاء. # AI translated
|
||||
PSP Memory Stick = شريحة ذاكرة PSP # AI translated
|
||||
PSP Model = PSP موديل
|
||||
PSP Settings = PSP إعدادات
|
||||
|
||||
@@ -1491,6 +1491,7 @@ Only JPG and PNG images are supported = Ancaq JPG və PNG şəkillər dəstəkl
|
||||
Path does not exist! = Yol yoxdur!
|
||||
Pause when not focused = Odaqlı deyilkən ara ver
|
||||
Plugins = Qoşmalar
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 dəstəyi hələ də eksperimentaldır. Oyun işləməyə bilər və ya xətalar ola bilər. # AI translated
|
||||
PSP Memory Stick = PSP Yaddaş Çubuğu
|
||||
PSP Model = PSP modeli
|
||||
PSP Settings = PSP quruluşları
|
||||
|
||||
@@ -1482,6 +1482,7 @@ Only JPG and PNG images are supported = Толькі выявы JPG і PNG
|
||||
Path does not exist! = Шляху не існуе!
|
||||
Pause when not focused = Паўза пры расфокусе
|
||||
Plugins = Плагіны
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Падтрымка PS1 пакуль эксперыментальная. Гульня можа не працаваць або мець памылкі. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Мадэль PSP
|
||||
PSP Settings = Налады PSP
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Поддържат се само JPG и
|
||||
Path does not exist! = Пътят не съществува!
|
||||
Pause when not focused = Пауза, когато не фокусиран
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Поддръжката на PS1 все още е експериментална. Играта може да не работи или да има проблеми. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP модел
|
||||
PSP Settings = PSP настройки
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &Pausar quan es canviï de finestra
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = El suport de PS1 encara és experimental. És possible que el joc no funcioni o que tingui errors. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = PSP settings
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &Pozastavit pokud okno není zaměřeno
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Podpora PS1 je zatím experimentální. Hra nemusí fungovat nebo se mohou objevit chyby. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Model PSP
|
||||
PSP Settings = Nastavení PSP
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = P&ause når ikke i fokus
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1-understøttelse er stadig eksperimentel. Spillet virker måske ikke, eller der kan være fejl. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = PSP indstillinger
|
||||
|
||||
@@ -1502,6 +1502,7 @@ Only JPG and PNG images are supported = Es werden nur JPG- und PNG-Bilder unters
|
||||
Path does not exist! = Pfad existiert nicht!
|
||||
Pause when not focused = Pausieren im Hintergrund
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Die PS1-Unterstützung ist noch experimentell. Das Spiel funktioniert eventuell nicht, oder es können Fehler auftreten. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP-Modell
|
||||
PSP Settings = PSP-Einstellungen
|
||||
|
||||
@@ -1490,6 +1490,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = Pause when not focused
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 support is still experimental. The game might not work, or there may be glitches.
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = Pangngaturan PSP
|
||||
|
||||
@@ -1522,6 +1522,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = Pause when not focused
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 support is still experimental. The game might not work, or there may be glitches.
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = PSP settings
|
||||
|
||||
@@ -1504,6 +1504,7 @@ Only JPG and PNG images are supported = Solo se admiten imágenes JPG y PNG
|
||||
Path does not exist! = ¡La ruta no existe!
|
||||
Pause when not focused = Pausar cuando la aplicación no está seleccionada
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = La compatibilidad con PS1 aún es experimental. Puede que el juego no funcione o que tenga fallos. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Modelo de PSP
|
||||
PSP Settings = Ajustes de PSP
|
||||
|
||||
@@ -1505,6 +1505,7 @@ Only JPG and PNG images are supported = Sólo se admiten imágenes JPG y PNG
|
||||
Path does not exist! = La ruta no existe.
|
||||
Pause when not focused = &Pausar al cambiar de ventana
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = La compatibilidad con PS1 aún es experimental. Es posible que el juego no funcione o que tenga fallas. # AI translated
|
||||
PSP Memory Stick = Memory Stick de PSP
|
||||
PSP Model = Modelo de PSP
|
||||
PSP Settings = Ajustes de PSP
|
||||
|
||||
@@ -1492,6 +1492,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = مسیر فایل پیدا نشد
|
||||
Pause when not focused = هنگامی که پنجره فعال نباشد بازی متوقف شود
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = پشتیبانی از PS1 هنوز آزمایشی است. ممکن است بازی اجرا نشود یا مشکلاتی داشته باشد. # AI translated
|
||||
PSP Memory Stick = حافظه psp
|
||||
PSP Model = PSP مدل
|
||||
PSP Settings = PSP تنظیمات
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Polkua ei ole olemassa!
|
||||
Pause when not focused = Pause when not focused
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1-tuki on vielä kokeellinen. Peli ei välttämättä toimi, tai siinä voi olla virheitä. # AI translated
|
||||
PSP Memory Stick = PSP-muistikortti
|
||||
PSP Model = PSP-malli
|
||||
PSP Settings = PSP-asetukset
|
||||
|
||||
@@ -1522,6 +1522,7 @@ Only JPG and PNG images are supported = Seules les images au format JPG et PNG s
|
||||
Path does not exist! = Le chemin d'accès n'existe pas !
|
||||
Pause when not focused = Mettre en pa&use si pas au premier plan
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = La prise en charge de la PS1 est encore expérimentale. Le jeu peut ne pas fonctionner ou présenter des bugs. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Modèle de PSP
|
||||
PSP Settings = Paramètres de la PSP
|
||||
|
||||
@@ -1502,6 +1502,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &Pausar ó cambiar de ventá
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = A compatibilidade con PS1 aínda é experimental. É posible que o xogo non funcione ou que teña erros. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Modelo de PSP
|
||||
PSP Settings = Axustes de PSP
|
||||
|
||||
@@ -1502,6 +1502,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = Παύση σε κατάσταση παρασκηνίου
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Η υποστήριξη PS1 είναι ακόμα πειραματική. Το παιχνίδι μπορεί να μη λειτουργεί ή να έχει σφάλματα. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Μοντέλο PSP
|
||||
PSP Settings = Ρυθμίσεις PSP
|
||||
|
||||
@@ -1492,6 +1492,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = Pause when not focused
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = התמיכה ב-PS1 עדיין ניסיונית. ייתכן שהמשחק לא יעבוד או שיופיעו תקלות. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = הגדרות PSP
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &Pauziraj kad nije fokusirano
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Podrška za PS1 još je eksperimentalna. Igra možda neće raditi ili može imati greške. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = PSP opcije
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Csak JPG és PNG képek támogatottak.
|
||||
Path does not exist! = Az elérési út nem létezik.
|
||||
Pause when not focused = Szüneteltetés, ha nincs fókuszban
|
||||
Plugins = Bővítmények
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = A PS1-támogatás még kísérleti. Előfordulhat, hogy a játék nem működik, vagy hibák jelentkeznek. # AI translated
|
||||
PSP Memory Stick = PSP Memóriakártya
|
||||
PSP Model = PSP modell
|
||||
PSP Settings = PSP beállítások
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Hanya gambar JPG dan PNG yang didukung
|
||||
Path does not exist! = Jalur/path tidak ditemukan!
|
||||
Pause when not focused = Jeda ketika tidak fokus
|
||||
Plugins = Plugin
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Dukungan PS1 masih eksperimental. Game mungkin tidak berjalan, atau mungkin ada gangguan. # AI translated
|
||||
PSP Memory Stick = Memory Stick PSP
|
||||
PSP Model = Model PSP
|
||||
PSP Settings = Pengaturan PSP
|
||||
|
||||
@@ -1490,6 +1490,7 @@ Only JPG and PNG images are supported = Sono supportate solo immagini JPG e PNG
|
||||
Path does not exist! = Il percorso non esiste!
|
||||
Pause when not focused = Pausa quando non attivo
|
||||
Plugins = Plugin
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Il supporto PS1 è ancora sperimentale. Il gioco potrebbe non funzionare o presentare problemi. # AI translated
|
||||
PSP Memory Stick = Memory Stick PSP
|
||||
Raw game image = Immagine grezza del gioco
|
||||
Recent games = Giochi recenti
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = JPGとPNG画像のみ対応していま
|
||||
Path does not exist! = パスが存在しません
|
||||
Pause when not focused = 非フォーカス時は一時停止する
|
||||
Plugins = プラグイン
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 のサポートはまだ実験的です。ゲームが動作しない、または不具合が発生する可能性があります。 # AI translated
|
||||
PSP Memory Stick = メモリースティックの設定
|
||||
PSP Model = PSPのモデル
|
||||
PSP Settings = PSPの設定
|
||||
|
||||
@@ -1490,6 +1490,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &Ngaso Nalika Ora Fokus
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 support is still experimental. The game might not work, or there may be glitches.
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = Setelan PSP
|
||||
|
||||
@@ -1480,6 +1480,7 @@ Only JPG and PNG images are supported =មានតែរូបភាព JPG ន
|
||||
Path does not exist! =ផ្លូវអត់មានទេ!
|
||||
Pause when not focused =ផ្អាកនៅពេលមិនផ្តោតអារម្មណ៍
|
||||
Plugins =កម្មវិធីជំនួយ
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 support is still experimental. The game might not work, or there may be glitches.
|
||||
PSP Memory Stick =PSP Memory Stick
|
||||
PSP Model =ម៉ូដែល PSP
|
||||
PSP Settings =ការកំណត់ PSP
|
||||
|
||||
@@ -1495,6 +1495,7 @@ Only JPG and PNG images are supported = JPG 및 PNG 이미지만 지원됩니다
|
||||
Path does not exist! = 경로가 존재하지 않습니다!
|
||||
Pause when not focused = 포커싱되지 않았을 때 일시 정지(&P)
|
||||
Plugins = 플러그인
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 지원은 아직 실험 단계입니다. 게임이 작동하지 않거나 오류가 발생할 수 있습니다. # AI translated
|
||||
PSP Memory Stick = PSP 메모리 스틱
|
||||
PSP Model = PSP 모델
|
||||
PSP Settings = PSP 설정
|
||||
|
||||
@@ -1496,6 +1496,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = Pause when not focused
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 support is still experimental. The game might not work, or there may be glitches.
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP مۆدێلی
|
||||
PSP Settings = PSP settings
|
||||
|
||||
@@ -1490,6 +1490,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &ຢຸດຊົ່ວຄາວເມື່ອບໍ່ໄດ້ຫຼິ້ນ
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 support is still experimental. The game might not work, or there may be glitches.
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = ໂມເດລ PSP
|
||||
PSP Settings = ຕັ້ງຄ່າ PSP
|
||||
|
||||
@@ -1492,6 +1492,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &Stabdyti kai nefokusuota
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 palaikymas dar eksperimentinis. Žaidimas gali neveikti arba gali būti klaidų. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Emuliuojamo PSP modelis
|
||||
PSP Settings = PSP parametrai
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Hanya imej JPG dan PNG sahaja disokong
|
||||
Path does not exist! = Laluan tidak wujud!
|
||||
Pause when not focused = Jeda apabila tidak difokuskan
|
||||
Plugins = Pemalam
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Sokongan PS1 masih dalam percubaan. Permainan mungkin tidak berfungsi, atau mungkin terdapat gangguan. # AI translated
|
||||
PSP Memory Stick = Memory Stick PSP
|
||||
PSP Model = Model PSP
|
||||
PSP Settings = Tetapan PSP
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = &Pauzeren wanneer venster inactief is
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1-ondersteuning is nog experimenteel. Het spel werkt mogelijk niet, of er kunnen fouten optreden. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP-model
|
||||
PSP Settings = PSP-instellingen
|
||||
|
||||
@@ -1520,6 +1520,7 @@ Only JPG and PNG images are supported = Berre JPG- og PNG-bilete er støtta
|
||||
Path does not exist! = Stien finst ikkje!
|
||||
Pause when not focused = Pause når ikkje i fokus
|
||||
Plugins = Programtillegg
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1-støtta er framleis eksperimentell. Spelet verkar kanskje ikkje, eller det kan vere feil. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP-modell
|
||||
PSP Settings = PSP-innstillingar
|
||||
|
||||
@@ -1520,6 +1520,7 @@ Only JPG and PNG images are supported = Bare JPG- og PNG-bilder støttes
|
||||
Path does not exist! = Banen finnes ikke!
|
||||
Pause when not focused = Pause When Not Focused
|
||||
Plugins = Programtillegg
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1-støtte er fortsatt eksperimentell. Spillet fungerer kanskje ikke, eller det kan være feil. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP-modell
|
||||
PSP Settings = PSP-innstillinger
|
||||
|
||||
@@ -1511,6 +1511,7 @@ Only JPG and PNG images are supported = Tylko pliki JPG i PNG są wspierane
|
||||
Path does not exist! = Ta ścieżka nie istnieje!
|
||||
Pause when not focused = Pauzuj, gdy okno nieaktywne
|
||||
Plugins = Wtyczki
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Obsługa PS1 jest wciąż eksperymentalna. Gra może nie działać lub mogą występować błędy. # AI translated
|
||||
PSP Memory Stick = Karta Pamięci PSP
|
||||
PSP Model = Model PSP
|
||||
PSP Settings = Ustawienia PSP
|
||||
|
||||
@@ -1519,6 +1519,7 @@ Only JPG and PNG images are supported = Só imagens JPGs e PNGs são suportadas
|
||||
Path does not exist! = O caminho não existe!
|
||||
Pause when not focused = Pausar quando não está focado
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = O suporte ao PS1 ainda é experimental. O jogo pode não funcionar, ou pode ter falhas. # AI translated
|
||||
PSP Memory Stick = Cartão de Memória do PSP
|
||||
PSP Model = Modelo do PSP
|
||||
PSP Settings = Configurações do PSP
|
||||
|
||||
@@ -1520,6 +1520,7 @@ Only JPG and PNG images are supported = Apenas imagens JPG e PNG são compatíve
|
||||
Path does not exist! = O diretório não existe!
|
||||
Pause when not focused = Pausar quando a janela não estiver em foco
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = O suporte à PS1 ainda é experimental. O jogo pode não funcionar, ou pode ter falhas. # AI translated
|
||||
PSP Memory Stick = Cartão de memória da PSP
|
||||
PSP Model = Modelo da PSP
|
||||
PSP Settings = Definições da PSP
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = Pause when not focused
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Suportul pentru PS1 este încă experimental. Este posibil ca jocul să nu funcționeze sau să aibă erori. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Model PSP
|
||||
PSP Settings = Setări PSP
|
||||
|
||||
@@ -1503,6 +1503,7 @@ Only JPG and PNG images are supported = Поддерживаются тольк
|
||||
Path does not exist! = Пути не существует!
|
||||
Pause when not focused = Пауза, когда окно не в фокусе
|
||||
Plugins = Плагины
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Поддержка PS1 пока экспериментальная. Игра может не работать или работать с ошибками. # AI translated
|
||||
PSP Memory Stick = Карта памяти PSP
|
||||
PSP Model = Модель PSP
|
||||
PSP Settings = Настройки PSP
|
||||
|
||||
@@ -1506,6 +1506,7 @@ Only JPG and PNG images are supported = Endast JPG och PNG-bilder stöds
|
||||
Path does not exist! = Sökvägen finns inte!
|
||||
Pause when not focused = Pausa när fönstret inte har fokus
|
||||
Plugins = Insticksmoduler
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1-stödet är fortfarande experimentellt. Spelet kanske inte fungerar, eller så kan det finnas fel. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP-modell
|
||||
PSP Settings = PSP-inställningar
|
||||
|
||||
@@ -1494,6 +1494,7 @@ Only JPG and PNG images are supported = Tanging ang mga JPG at PNG na mga imahe
|
||||
Path does not exist! = Hindi matagpuan ang sinasabing 'path'!
|
||||
Pause when not focused = Ihinto kapag hindi naka pokus
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 support is still experimental. The game might not work, or there may be glitches.
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Modelo ng PSP
|
||||
PSP Settings = Settings ng PSP
|
||||
|
||||
@@ -1544,6 +1544,7 @@ Only JPG and PNG images are supported = รองรับเฉพาะไฟ
|
||||
Path does not exist! = ไม่มีเส้นทางนี้อยู่!
|
||||
Pause when not focused = หยุดชั่วคราวเมื่อไม่ได้เล่น
|
||||
Plugins = ปลั๊กอิน
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = การรองรับ PS1 ยังอยู่ในขั้นทดลอง เกมอาจไม่ทำงาน หรืออาจมีข้อผิดพลาด # AI translated
|
||||
PSP Memory Stick = แหล่งเก็บข้อมูล PSP
|
||||
PSP Model = โมเดลของ PSP
|
||||
PSP Settings = การตั้งค่า PSP
|
||||
|
||||
@@ -1504,6 +1504,7 @@ Only JPG and PNG images are supported = Sadece JPG ve PNG resimleri desteklenir
|
||||
Path does not exist! = Yol mevcut değil!
|
||||
Pause when not focused = Odakdan Çıkınca Duraklat
|
||||
Plugins = Eklentiler
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 desteği hâlâ deneysel. Oyun çalışmayabilir veya hatalar olabilir. # AI translated
|
||||
PSP Memory Stick = PSP Hafıza Kartı
|
||||
PSP Model = PSP Modeli
|
||||
PSP Settings = PSP Ayarları
|
||||
|
||||
@@ -1502,6 +1502,7 @@ Only JPG and PNG images are supported = Підтримуються лише зо
|
||||
Path does not exist! = Шляху не існує!
|
||||
Pause when not focused = &Пауза коли вікно не в фокусі
|
||||
Plugins = Плагіни
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Підтримка PS1 поки експериментальна. Гра може не працювати або працювати з помилками. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = Модель PSP
|
||||
PSP Settings = Налаштування PSP
|
||||
|
||||
@@ -1502,6 +1502,7 @@ Only JPG and PNG images are supported = Only JPG and PNG images are supported
|
||||
Path does not exist! = Path does not exist!
|
||||
Pause when not focused = Dừng trò chơi khi "ko tập trung"
|
||||
Plugins = Plugins
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = Hỗ trợ PS1 vẫn đang thử nghiệm. Trò chơi có thể không hoạt động hoặc có thể gặp lỗi. # AI translated
|
||||
PSP Memory Stick = PSP Memory Stick
|
||||
PSP Model = PSP model
|
||||
PSP Settings = Thiết lập PSP
|
||||
|
||||
@@ -1447,6 +1447,7 @@ Notification screen position = 通知界面位置
|
||||
Only JPG and PNG images are supported = 只支持JPG和PNG图片
|
||||
Pause when not focused = 后台运行时自动暂停
|
||||
Plugins = 插件
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 支持仍处于实验阶段。游戏可能无法运行,或可能出现异常。 # AI translated
|
||||
Raw game image = 原始渲染图像
|
||||
Recording = 录制
|
||||
Report free storage space accurately = 准确报告可用存储空间 # AI translated
|
||||
|
||||
@@ -1495,6 +1495,7 @@ Only JPG and PNG images are supported = 僅支援 JPG 和 PNG 影像
|
||||
Path does not exist! = 路徑不存在!
|
||||
Pause when not focused = 未聚焦時暫停(&P)
|
||||
Plugins = 外掛程式
|
||||
PS1 support is still experimental. The game might not work, or there may be glitches. = PS1 支援仍處於實驗階段。遊戲可能無法執行,或可能出現異常。 # AI translated
|
||||
PSP Memory Stick = PSP 記憶棒
|
||||
PSP Model = PSP 型號
|
||||
PSP Settings = PSP 設定
|
||||
|
||||
@@ -58,6 +58,24 @@ A converted EBOOT (popstation and similar) has a plain disc image of DEFLATE blo
|
||||
does, the emulator's decoder calls go to an inflate, its CD audio index check takes the full-length branch,
|
||||
and the two MAC checks (+0A28, +0AB8) pass.
|
||||
|
||||
## Disc images
|
||||
|
||||
A PS1 disc image boots as the converted EBOOT it would make (`Core/FileLoaders/PS1EbootFileLoader.cpp`),
|
||||
which never exists as a whole. It takes a cue sheet (with any number of files, AUDIO tracks, INDEX 00
|
||||
and PREGAP), a CloneCD `.ccd` with its `.img`, an Alcohol 120% `.mds` with its `.mdf` (subchannel data
|
||||
skipped), a CD CHD (whose audio is big-endian), or a bare raw (2352-byte sector) `.bin`, `.img` or
|
||||
`.iso`, or a 2048-byte `.iso`, around whose user data Mode 2 Form 1 sectors are made (with no EDC/ECC).
|
||||
A bare image that a cue sheet next to it names, or that has a `.ccd` or `.mds` of the same name, is read
|
||||
through that. The disc ID is the one SYSTEM.CNF boots. The game browser shows each disc once, and a zip
|
||||
or 7z with one in it installs it (several files into a folder of their own).
|
||||
|
||||
It's laid out as popstation lays out a PSISOIMG: the disc ID at +0x400, the TOC at +0x800, a block
|
||||
index at +0x4000 and the blocks from +0x100000, each of 16 sectors. The manager doesn't read the disc
|
||||
image of one whose blocks are its own uncompressed size (0x9300), so each block is a stored DEFLATE
|
||||
block, which the inflate above decodes: 0x9305 bytes, so reads of the image map straight onto its files.
|
||||
There are no icons, pictures or DATA.PSP, and none of the STARTDAT that follows a popstation image. The
|
||||
EBOOT is the only file in `host0:`.
|
||||
|
||||
For any EBOOT, also as popcorn does: the manual's name check passes, and an ICON0 the emulator can't use
|
||||
(anything but an 80x80 PNG, such as a missing one or a PSP game's 144x80 one, which crashes it) reads as a
|
||||
blank 80x80 one.
|
||||
@@ -78,5 +96,7 @@ The HOME button opens the emulator's menu. Savestates work.
|
||||
- The anti-LibCrypt word some PAL games need in a converted image (PSISOIMG + 0x12B0). ARK-4's popcorn has
|
||||
a table of them by disc ID, switched off. Converting tools can patch the image instead.
|
||||
- ARK-4's per-game CONFIG.BIN next to the EBOOT, copied over the image's config (PSISOIMG + 0x420).
|
||||
- Multi-disc games from disc images (PSTITLEIMG, which has up to five PSISOIMGs), and booting an image
|
||||
straight from an archive.
|
||||
- A byte popcorn changes in reads of converted images (0x41B, from 0x27 to 0x55 when 0x41B-0x41E read
|
||||
27 19 22 41) without knowing why.
|
||||
@@ -724,6 +724,7 @@ SOURCES_CXX += \
|
||||
$(COREDIR)/FileLoaders/RetryingFileLoader.cpp \
|
||||
$(COREDIR)/FileLoaders/RamCachingFileLoader.cpp \
|
||||
$(COREDIR)/FileLoaders/LocalFileLoader.cpp \
|
||||
$(COREDIR)/FileLoaders/PS1EbootFileLoader.cpp \
|
||||
$(COREDIR)/FileLoaders/ZipFileLoader.cpp \
|
||||
$(COREDIR)/CoreTiming.cpp \
|
||||
$(COREDIR)/CwCheat.cpp \
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
// Copyright (c) 2012- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
// PS1EbootFileLoader: small disc images in a temp directory, read back through the EBOOT made from them.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ext/libzip/zip.h"
|
||||
|
||||
#include "Common/File/DirListing.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/File/Path.h"
|
||||
#include "Core/ELF/PBPReader.h"
|
||||
#include "Core/ELF/ParamSFO.h"
|
||||
#include "Core/FileLoaders/PS1EbootFileLoader.h"
|
||||
#include "Core/Loaders.h"
|
||||
#include "Core/Util/GameManager.h"
|
||||
|
||||
#include "UnitTest.h"
|
||||
|
||||
static const u32 SECTOR = 2352;
|
||||
static const u32 BLOCK = 16 * SECTOR + 5;
|
||||
static const u8 SYNC[12] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
|
||||
|
||||
static void Put32(u8 *p, u32 v) {
|
||||
p[0] = v & 0xFF;
|
||||
p[1] = (v >> 8) & 0xFF;
|
||||
p[2] = (v >> 16) & 0xFF;
|
||||
p[3] = v >> 24;
|
||||
}
|
||||
|
||||
static u32 Get32(const u8 *p) {
|
||||
return p[0] | (p[1] << 8) | (p[2] << 16) | ((u32)p[3] << 24);
|
||||
}
|
||||
|
||||
static u8 BCD(u32 v) {
|
||||
return (u8)(((v / 10) << 4) | (v % 10));
|
||||
}
|
||||
|
||||
// The user data of a small Mode 2 Form 1 data track: a PVD at 16, its root directory at 18 with one
|
||||
// SYSTEM.CNF at 20, and a pattern everywhere else.
|
||||
static std::vector<u8> MakeUserData(u32 sectors) {
|
||||
std::vector<u8> data(sectors * 2048);
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
data[i] = (u8)(i * 7 + i / 2048);
|
||||
}
|
||||
u8 *pvd = &data[16 * 2048];
|
||||
memset(pvd, 0, 2048);
|
||||
memcpy(pvd, "\x01" "CD001", 6);
|
||||
Put32(pvd + 156 + 2, 18);
|
||||
Put32(pvd + 156 + 10, 2048);
|
||||
u8 *dir = &data[18 * 2048];
|
||||
memset(dir, 0, 2048);
|
||||
const char name[] = "SYSTEM.CNF;1";
|
||||
dir[0] = (u8)(33 + sizeof(name) - 1 + 1);
|
||||
Put32(dir + 2, 20);
|
||||
const std::string cnf = "BOOT = cdrom:\\SLUS_123.45;1\r\nTCB = 4\r\n";
|
||||
Put32(dir + 10, (u32)cnf.size());
|
||||
dir[32] = (u8)(sizeof(name) - 1);
|
||||
memcpy(dir + 33, name, sizeof(name) - 1);
|
||||
memset(&data[20 * 2048], 0, 2048);
|
||||
memcpy(&data[20 * 2048], cnf.data(), cnf.size());
|
||||
return data;
|
||||
}
|
||||
|
||||
// Raw sectors around user data, as a .bin has them. The EDC and ECC are a pattern, which nothing checks.
|
||||
static std::vector<u8> MakeRawTrack(const std::vector<u8> &user) {
|
||||
const u32 sectors = (u32)(user.size() / 2048);
|
||||
std::vector<u8> raw(sectors * SECTOR);
|
||||
for (u32 s = 0; s < sectors; s++) {
|
||||
u8 *p = &raw[s * SECTOR];
|
||||
memcpy(p, SYNC, 12);
|
||||
const u32 f = s + 150;
|
||||
p[12] = BCD(f / 4500);
|
||||
p[13] = BCD(f / 75 % 60);
|
||||
p[14] = BCD(f % 75);
|
||||
p[15] = 2;
|
||||
static const u8 subheader[8] = { 0, 0, 8, 0, 0, 0, 8, 0 };
|
||||
memcpy(p + 16, subheader, 8);
|
||||
memcpy(p + 24, &user[s * 2048], 2048);
|
||||
for (u32 i = 24 + 2048; i < SECTOR; i++) {
|
||||
p[i] = (u8)(s + i);
|
||||
}
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
static std::vector<u8> MakeAudio(u32 sectors) {
|
||||
std::vector<u8> audio(sectors * SECTOR);
|
||||
for (size_t i = 0; i < audio.size(); i++) {
|
||||
audio[i] = (u8)(i * 13 + 5);
|
||||
}
|
||||
return audio;
|
||||
}
|
||||
|
||||
static std::vector<u8> ReadAll(FileLoader *loader, s64 pos, size_t size) {
|
||||
std::vector<u8> out(size);
|
||||
out.resize(loader->ReadAt(pos, size, out.data()));
|
||||
return out;
|
||||
}
|
||||
|
||||
// The PSAR's disc image as it should be: each block of 16 sectors, stored, after its DEFLATE header.
|
||||
static std::vector<u8> ExpectedBlocks(const std::vector<u8> &disc) {
|
||||
const u32 blocks = (u32)((disc.size() / SECTOR + 15) / 16);
|
||||
std::vector<u8> out;
|
||||
for (u32 b = 0; b < blocks; b++) {
|
||||
static const u8 header[5] = { 0x01, 0x00, 0x93, 0xFF, 0x6C };
|
||||
out.insert(out.end(), header, header + 5);
|
||||
for (u32 i = 0; i < 16 * SECTOR; i++) {
|
||||
const size_t pos = (size_t)b * 16 * SECTOR + i;
|
||||
out.push_back(pos < disc.size() ? disc[pos] : 0);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool CheckEboot(PS1EbootFileLoader &eboot, const std::vector<u8> &disc, u32 numTracks) {
|
||||
EXPECT_TRUE(eboot.IsValid());
|
||||
EXPECT_EQ_STR(eboot.DiscID(), std::string("SLUS12345"));
|
||||
|
||||
std::string error;
|
||||
EXPECT_TRUE(Identify_File(&eboot, &error) == IdentifiedFileType::PSP_PS1_PBP);
|
||||
PBPReader pbp(&eboot);
|
||||
EXPECT_TRUE(pbp.IsValid());
|
||||
std::vector<u8> sfoData;
|
||||
EXPECT_TRUE(pbp.GetSubFile(PBP_PARAM_SFO, &sfoData));
|
||||
ParamSFOData sfo;
|
||||
EXPECT_TRUE(sfo.ReadSFO(sfoData));
|
||||
EXPECT_EQ_STR(sfo.GetValueString("CATEGORY"), std::string("ME"));
|
||||
EXPECT_EQ_STR(sfo.GetValueString("DISC_ID"), std::string("SLUS12345"));
|
||||
|
||||
const std::vector<u8> header = ReadAll(&eboot, 0, 0x28);
|
||||
EXPECT_EQ_INT(header.size(), 0x28);
|
||||
const u32 psar = Get32(&header[0x24]);
|
||||
const std::vector<u8> psarHeader = ReadAll(&eboot, psar, 0x1300);
|
||||
EXPECT_EQ_MEM(&psarHeader[0], "PSISOIMG0000", 12);
|
||||
EXPECT_EQ_MEM(&psarHeader[0x400], "_SLUS_12345", 12);
|
||||
|
||||
// The TOC: the last track, the lead-out (at 00:02:00 plus the disc's length), and the tracks.
|
||||
const u32 sectors = (u32)(disc.size() / SECTOR);
|
||||
const u8 *toc = &psarHeader[0x800];
|
||||
EXPECT_EQ_INT(toc[0], 0x41);
|
||||
EXPECT_EQ_INT(toc[2], 0xA0);
|
||||
EXPECT_EQ_INT(toc[10 + 2], 0xA1);
|
||||
EXPECT_EQ_INT(toc[10 + 7], BCD(numTracks));
|
||||
EXPECT_EQ_INT(toc[20 + 2], 0xA2);
|
||||
EXPECT_EQ_INT(toc[20 + 8], BCD((sectors + 150) / 75 % 60));
|
||||
EXPECT_EQ_INT(toc[20 + 9], BCD((sectors + 150) % 75));
|
||||
EXPECT_EQ_INT(toc[30 + 2], 0x01);
|
||||
EXPECT_EQ_INT(toc[30 + 8], 0x02);
|
||||
EXPECT_EQ_INT(toc[30 + 9], 0x00);
|
||||
|
||||
// The index, then the blocks: all of them at once, and reads that start and end mid-sector and
|
||||
// cross blocks, as POPS's 0x200-aligned ones do.
|
||||
const u32 blocks = (sectors + 15) / 16;
|
||||
const std::vector<u8> index = ReadAll(&eboot, psar + 0x4000, (blocks + 1) * 32);
|
||||
for (u32 b = 0; b < blocks; b++) {
|
||||
EXPECT_EQ_INT(Get32(&index[b * 32]), b * BLOCK);
|
||||
EXPECT_EQ_INT(index[b * 32 + 4] | (index[b * 32 + 5] << 8), BLOCK);
|
||||
}
|
||||
EXPECT_EQ_INT(Get32(&index[blocks * 32]), 0);
|
||||
const std::vector<u8> expected = ExpectedBlocks(disc);
|
||||
const s64 data = psar + 0x100000;
|
||||
EXPECT_EQ_INT(eboot.FileSize(), data + expected.size());
|
||||
const std::vector<u8> all = ReadAll(&eboot, data, expected.size() + 100);
|
||||
EXPECT_EQ_INT(all.size(), expected.size());
|
||||
EXPECT_TRUE(all == expected);
|
||||
const size_t starts[] = { 0, 3, 0xB3, BLOCK - 1, BLOCK + 5 + SECTOR - 7, expected.size() - 0x300 };
|
||||
for (size_t start : starts) {
|
||||
const size_t len = std::min((size_t)0x9400, expected.size() - start);
|
||||
const std::vector<u8> part = ReadAll(&eboot, data + start, len);
|
||||
EXPECT_EQ_INT(part.size(), len);
|
||||
EXPECT_TRUE(memcmp(part.data(), &expected[start], len) == 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestPS1Eboot() {
|
||||
// PS1 disc IDs have a region, as PSP ones do.
|
||||
EXPECT_TRUE(DetectGameRegionFromID("SLUS00292") == GameRegion::USA);
|
||||
EXPECT_TRUE(DetectGameRegionFromID("SCES00003") == GameRegion::EUROPE);
|
||||
EXPECT_TRUE(DetectGameRegionFromID("SLPS00444") == GameRegion::JAPAN);
|
||||
EXPECT_TRUE(DetectGameRegionFromID("SCKA20001") == GameRegion::KOREA);
|
||||
EXPECT_TRUE(DetectGameRegionFromID("ULUS10041") == GameRegion::USA);
|
||||
|
||||
const Path dir = UnitTestTempPath("unittest_ps1eboot");
|
||||
File::DeleteDirRecursively(dir);
|
||||
EXPECT_TRUE(File::CreateFullPath(dir));
|
||||
|
||||
const std::vector<u8> user = MakeUserData(40);
|
||||
const std::vector<u8> data = MakeRawTrack(user);
|
||||
const std::vector<u8> audio = MakeAudio(20);
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, data.data(), data.size(), dir / "Game (Track 1).bin"));
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, audio.data(), audio.size(), dir / "Game (Track 2).bin"));
|
||||
|
||||
// A data track and an audio track in two files, the audio track's pregap ripped (INDEX 00).
|
||||
const std::string cue =
|
||||
"FILE \"Game (Track 1).bin\" BINARY\r\n"
|
||||
" TRACK 01 MODE2/2352\r\n"
|
||||
" INDEX 01 00:00:00\r\n"
|
||||
"FILE \"Game (Track 2).bin\" BINARY\r\n"
|
||||
" TRACK 02 AUDIO\r\n"
|
||||
" INDEX 00 00:00:00\r\n"
|
||||
" INDEX 01 00:00:05\r\n";
|
||||
EXPECT_TRUE(File::WriteStringToFile(true, cue, dir / "Game.cue"));
|
||||
std::vector<u8> disc = data;
|
||||
disc.insert(disc.end(), audio.begin(), audio.end());
|
||||
{
|
||||
PS1EbootFileLoader eboot(dir / "Game.cue");
|
||||
if (!CheckEboot(eboot, disc, 2)) {
|
||||
return false;
|
||||
}
|
||||
EXPECT_EQ_STR(eboot.Title(), std::string("Game"));
|
||||
// Track 2's pregap starts at LBA 40, and the track at 45.
|
||||
const std::vector<u8> toc = ReadAll(&eboot, Get32(&ReadAll(&eboot, 0, 0x28)[0x24]) + 0x800 + 40, 10);
|
||||
EXPECT_EQ_INT(toc[0], 0x01);
|
||||
EXPECT_EQ_INT(toc[2], 0x02);
|
||||
EXPECT_EQ_INT(toc[5], BCD(190 % 75));
|
||||
EXPECT_EQ_INT(toc[9], BCD(195 % 75));
|
||||
}
|
||||
// The first file opened on its own: the cue sheet that names it describes it.
|
||||
{
|
||||
EXPECT_TRUE(FindCueSheetFor(dir / "Game (Track 1).bin") == dir / "Game.cue");
|
||||
PS1EbootFileLoader eboot(dir / "Game (Track 1).bin");
|
||||
if (!CheckEboot(eboot, disc, 2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A PREGAP that isn't in the file: generated silence before the track.
|
||||
const std::string pregapCue =
|
||||
"FILE \"Game (Track 1).bin\" BINARY\r\n"
|
||||
" TRACK 01 MODE2/2352\r\n"
|
||||
" INDEX 01 00:00:00\r\n"
|
||||
"FILE \"Game (Track 2).bin\" BINARY\r\n"
|
||||
" TRACK 02 AUDIO\r\n"
|
||||
" PREGAP 00:00:03\r\n"
|
||||
" INDEX 01 00:00:00\r\n";
|
||||
EXPECT_TRUE(File::WriteStringToFile(true, pregapCue, dir / "Pregap.cue"));
|
||||
{
|
||||
std::vector<u8> gapped = data;
|
||||
gapped.resize(gapped.size() + 3 * SECTOR, 0);
|
||||
gapped.insert(gapped.end(), audio.begin(), audio.end());
|
||||
PS1EbootFileLoader eboot(dir / "Pregap.cue");
|
||||
if (!CheckEboot(eboot, gapped, 2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// MODE2/2336: sectors without their sync and header, which are made again.
|
||||
{
|
||||
std::vector<u8> stripped;
|
||||
for (u32 s = 0; s < 40; s++) {
|
||||
stripped.insert(stripped.end(), data.begin() + s * SECTOR + 16, data.begin() + (s + 1) * SECTOR);
|
||||
}
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, stripped.data(), stripped.size(), dir / "Stripped.bin"));
|
||||
EXPECT_TRUE(File::WriteStringToFile(true, "FILE \"Stripped.bin\" BINARY\n TRACK 01 MODE2/2336\n INDEX 01 00:00:00\n", dir / "Stripped.cue"));
|
||||
PS1EbootFileLoader eboot(dir / "Stripped.cue");
|
||||
if (!CheckEboot(eboot, data, 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A CloneCD image, and a bare .iso of only the user data, whose sectors are made around it.
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, data.data(), data.size(), dir / "Clone.img"));
|
||||
EXPECT_TRUE(File::WriteStringToFile(true, "[CloneCD]\r\nVersion=3\r\n[TRACK 1]\r\nMODE=2\r\nINDEX 1=0\r\n", dir / "Clone.ccd"));
|
||||
{
|
||||
PS1EbootFileLoader eboot(dir / "Clone.ccd");
|
||||
if (!CheckEboot(eboot, data, 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, user.data(), user.size(), dir / "Cooked.iso"));
|
||||
{
|
||||
std::vector<u8> cooked = data;
|
||||
for (u32 s = 0; s < 40; s++) {
|
||||
memset(&cooked[s * SECTOR + 24 + 2048], 0, SECTOR - 24 - 2048);
|
||||
}
|
||||
PS1EbootFileLoader eboot(dir / "Cooked.iso");
|
||||
if (!CheckEboot(eboot, cooked, 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// An Alcohol .mds and .mdf: sectors with 96 bytes of subchannel data after each, and an audio track
|
||||
// whose 5 sectors of pregap are stored.
|
||||
{
|
||||
const u32 stride = SECTOR + 96;
|
||||
const std::vector<u8> audio25 = MakeAudio(25);
|
||||
std::vector<u8> mdf;
|
||||
for (const std::vector<u8> *track : { &data, &audio25 }) {
|
||||
for (size_t s = 0; s < track->size() / SECTOR; s++) {
|
||||
mdf.insert(mdf.end(), track->begin() + s * SECTOR, track->begin() + (s + 1) * SECTOR);
|
||||
mdf.resize(mdf.size() + 96, 0xAA);
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, mdf.data(), mdf.size(), dir / "Alcohol.mdf"));
|
||||
std::vector<u8> mds(0x230, 0);
|
||||
memcpy(&mds[0], "MEDIA DESCRIPTOR", 16);
|
||||
Put32(&mds[0x50], 0x58);
|
||||
mds[0x58 + 0x0A] = 5;
|
||||
mds[0x58 + 0x0B] = 3;
|
||||
Put32(&mds[0x58 + 0x14], 0x70);
|
||||
const u8 points[5] = { 0xA0, 0xA1, 0xA2, 1, 2 };
|
||||
for (int i = 0; i < 5; i++) {
|
||||
u8 *b = &mds[0x70 + i * 0x50];
|
||||
b[4] = points[i];
|
||||
if (points[i] > 99) {
|
||||
continue;
|
||||
}
|
||||
const bool audioTrack = points[i] == 2;
|
||||
b[0] = audioTrack ? 0xA9 : 0xEC;
|
||||
b[1] = 0x08;
|
||||
Put32(b + 0x0C, audioTrack ? 0x208 : 0x200);
|
||||
b[0x10] = stride & 0xFF;
|
||||
b[0x11] = stride >> 8;
|
||||
Put32(b + 0x24, audioTrack ? 45 : 0);
|
||||
Put32(b + 0x28, audioTrack ? 40 * stride : 0);
|
||||
Put32(b + 0x30, 1);
|
||||
Put32(b + 0x34, 0x210);
|
||||
}
|
||||
Put32(&mds[0x200], 150);
|
||||
Put32(&mds[0x204], 40);
|
||||
Put32(&mds[0x208], 5);
|
||||
Put32(&mds[0x20C], 25);
|
||||
Put32(&mds[0x210], 0x220);
|
||||
memcpy(&mds[0x220], "*.mdf", 6);
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, mds.data(), mds.size(), dir / "Alcohol.mds"));
|
||||
|
||||
std::vector<u8> mdsDisc = data;
|
||||
mdsDisc.insert(mdsDisc.end(), audio25.begin(), audio25.end());
|
||||
PS1EbootFileLoader eboot(dir / "Alcohol.mds");
|
||||
if (!CheckEboot(eboot, mdsDisc, 2)) {
|
||||
return false;
|
||||
}
|
||||
PS1EbootFileLoader fromMdf(dir / "Alcohol.mdf");
|
||||
if (!CheckEboot(fromMdf, mdsDisc, 2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The browser shows each disc once: by its cue sheet or .ccd, and a .bin of its own only if it
|
||||
// could be a disc image.
|
||||
const std::vector<u8> loose(3 * SECTOR, 1);
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, loose.data(), loose.size(), dir / "Loose.bin"));
|
||||
EXPECT_TRUE(File::WriteDataToFile(false, loose.data(), 100, dir / "Junk.bin"));
|
||||
std::vector<File::FileInfo> files;
|
||||
File::GetFilesInDir(dir, &files);
|
||||
RemoveDescribedDiscImages(&files);
|
||||
std::vector<std::string> names;
|
||||
for (const File::FileInfo &info : files) {
|
||||
names.push_back(info.name);
|
||||
}
|
||||
std::sort(names.begin(), names.end());
|
||||
const std::vector<std::string> expectedNames = { "Alcohol.mdf", "Alcohol.mds", "Clone.ccd", "Cooked.iso", "Game.cue", "Loose.bin", "Pregap.cue", "Stripped.cue" };
|
||||
EXPECT_TRUE(names == expectedNames);
|
||||
|
||||
// Installing from a zip: the cue sheet and both its files, into a folder of their own.
|
||||
const Path zipPath = dir / "Game.zip";
|
||||
{
|
||||
int errorp = 0;
|
||||
zip_t *z = zip_open(zipPath.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorp);
|
||||
EXPECT_TRUE(z != nullptr);
|
||||
const std::pair<const char *, const std::vector<u8> *> entries[] = {
|
||||
{ "Game/Game (Track 1).bin", &data },
|
||||
{ "Game/Game (Track 2).bin", &audio },
|
||||
};
|
||||
for (const auto &entry : entries) {
|
||||
zip_source_t *source = zip_source_buffer(z, entry.second->data(), entry.second->size(), 0);
|
||||
EXPECT_TRUE(zip_file_add(z, entry.first, source, ZIP_FL_ENC_UTF_8) >= 0);
|
||||
}
|
||||
zip_source_t *cueSource = zip_source_buffer(z, cue.data(), cue.size(), 0);
|
||||
EXPECT_TRUE(zip_file_add(z, "Game/Game.cue", cueSource, ZIP_FL_ENC_UTF_8) >= 0);
|
||||
zip_source_t *readme = zip_source_buffer(z, "hi", 2, 0);
|
||||
EXPECT_TRUE(zip_file_add(z, "readme.txt", readme, ZIP_FL_ENC_UTF_8) >= 0);
|
||||
EXPECT_TRUE(zip_close(z) == 0);
|
||||
}
|
||||
{
|
||||
int errorp = 0;
|
||||
zip_t *z = zip_open(zipPath.c_str(), 0, &errorp);
|
||||
EXPECT_TRUE(z != nullptr);
|
||||
ZipFileInfo info{};
|
||||
DetectZipFileContents(z, &info);
|
||||
EXPECT_TRUE(info.contents == ZipFileContents::PSX_DISC);
|
||||
EXPECT_EQ_INT(info.discFiles.size(), 3);
|
||||
EXPECT_EQ_STR(info.discFiles[0], std::string("Game/Game.cue"));
|
||||
const Path installDir = dir / "installed";
|
||||
EXPECT_TRUE(File::CreateFullPath(installDir));
|
||||
GameManager manager;
|
||||
EXPECT_TRUE(manager.InstallZippedPS1Disc(z, info.discFiles, installDir));
|
||||
zip_close(z);
|
||||
PS1EbootFileLoader eboot(installDir / "Game" / "Game.cue");
|
||||
if (!CheckEboot(eboot, disc, 2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
File::DeleteDirRecursively(dir);
|
||||
return true;
|
||||
}
|
||||
@@ -3551,6 +3551,7 @@ bool TestIRPassSimplify();
|
||||
bool TestThreadManager();
|
||||
bool TestVFS();
|
||||
bool TestZipSlip();
|
||||
bool TestPS1Eboot();
|
||||
bool TestLzrc();
|
||||
bool TestCwCheat();
|
||||
bool TestMpegCsc();
|
||||
@@ -4076,6 +4077,7 @@ TestItem availableTests[] = {
|
||||
TEST_ITEM(Lang),
|
||||
TEST_ITEM(CmdLine),
|
||||
TEST_ITEM(ZipSlip),
|
||||
TEST_ITEM(PS1Eboot),
|
||||
TEST_ITEM(Lzrc),
|
||||
TEST_ITEM(CwCheat),
|
||||
TEST_ITEM(MpegCsc),
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
<ClCompile Include="TestVertexJit.cpp" />
|
||||
<ClCompile Include="TestVFS.cpp" />
|
||||
<ClCompile Include="TestZipSlip.cpp" />
|
||||
<ClCompile Include="TestPS1Eboot.cpp" />
|
||||
<ClCompile Include="UnitTest.cpp" />
|
||||
<ClCompile Include="TestArmEmitter.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<ClCompile Include="TestRiscVEmitter.cpp" />
|
||||
<ClCompile Include="TestVFS.cpp" />
|
||||
<ClCompile Include="TestZipSlip.cpp" />
|
||||
<ClCompile Include="TestPS1Eboot.cpp" />
|
||||
<ClCompile Include="TestLoongArch64Emitter.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
Reference in new issue
Block a user