mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-31 19:47:06 +01:00
An .m3u of up to five disc images (any format PS1EbootFileLoader takes) becomes a multi-disc EBOOT: a PSTITLEIMG header listing a PSISOIMG per disc, as the firmware's POPS takes them. The discs share the first one's ID for the memory card, and each keeps its own for the emulator's per-game fixes. The game browser lists playlists, and hides the discs they list. A converted multi-disc EBOOT has no MACs for its disc headers, which the manager's disc header check compares when there are several discs, so that comparison passes. It also has none of the key that the emulator's disc switching strings (the HOME menu's item, the switch, confirmation and reset dialogs) are descrambled with. Those are skipped, and ours, from our translations, take their place, with the disc number's positions laid out for them with the firmware's ltn0.pgf. On Android, a cue sheet, .ccd, .mds or playlist given to PPSSPP as a single file, rather than through a folder it has access to, now says so rather than failing to find the other files. Not done: the reset dialog's current disc number, which isn't drawn where its gap is yet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
177 lines
6.5 KiB
C++
177 lines
6.5 KiB
C++
// 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"
|
|
|
|
// One PS1 disc image (a cue sheet, a CloneCD .ccd, an Alcohol .mds, a CHD, or a bare .bin/.img/.iso):
|
|
// its tracks, and its sectors as raw 2352-byte ones, however the image stores them.
|
|
class PS1Disc {
|
|
public:
|
|
explicit PS1Disc(const Path &path);
|
|
~PS1Disc();
|
|
|
|
bool IsValid() const { return error_.empty(); }
|
|
const std::string &Error() const { return error_; }
|
|
const std::string &DiscID() const { return discID_; }
|
|
u32 NumBlocks() const { return numBlocks_; }
|
|
|
|
// Its PSISOIMG header, up to the block index (PSAR_HEADER_SIZE bytes).
|
|
void BuildHeader(const std::string &title, u8 *psisoimg) const;
|
|
bool ReadRawSectors(u32 lba, u32 count, u8 *out);
|
|
void Cancel();
|
|
|
|
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 ReadUserData(u32 lba, u8 *out);
|
|
void ReadDiscID();
|
|
|
|
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_;
|
|
u32 numBlocks_ = 0;
|
|
};
|
|
|
|
// One or more PS1 discs presented as a converted PS1 EBOOT.PBP, the kind popstation makes, for the
|
|
// firmware's POPS to run: one disc image, or an .m3u playlist of up to five, which become a multi-disc
|
|
// EBOOT. Only the headers are kept in memory. The disc images are stored uncompressed, as stored DEFLATE
|
|
// blocks, so a read of one goes straight to its 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 discs_[0]->DiscID(); }
|
|
const std::string &Title() const { return title_; }
|
|
int NumDiscs() const { return (int)discs_.size(); }
|
|
|
|
private:
|
|
void BuildHeaders();
|
|
size_t ReadDisc(int disc, s64 pos, size_t bytes, u8 *out);
|
|
|
|
Path path_;
|
|
std::string error_;
|
|
std::string title_;
|
|
std::vector<std::unique_ptr<PS1Disc>> discs_;
|
|
|
|
// The PBP up to the PSAR, then (with several discs) the PSTITLEIMG header. Each disc's PSISOIMG
|
|
// header follows at its offset in the PSAR, then its block index and blocks, made on the fly.
|
|
std::vector<u8> head_;
|
|
std::vector<std::vector<u8>> discHeaders_;
|
|
std::vector<s64> discOffsets_;
|
|
u32 psarOffset_ = 0;
|
|
s64 size_ = 0;
|
|
};
|
|
|
|
// The disc images an .m3u playlist lists, as written.
|
|
std::vector<std::string> PlaylistFileNames(std::string_view text);
|
|
|
|
namespace File {
|
|
struct FileInfo;
|
|
}
|
|
|
|
// Whether a .cue, .ccd or .mds describes a disc, or an .m3u lists disc images, by its contents.
|
|
bool IsPS1DiscSheet(FileLoader *fileLoader);
|
|
|
|
// Whether a file with this extension can be (part of) a PS1 disc image.
|
|
bool IsPS1DiscImageExtension(const std::string &ext);
|
|
|
|
// 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 disc images from a directory listing that a playlist in it lists, and the .bin and .img
|
|
// files 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);
|