Files
Matthew Stanley fe42da2744 Merge release/native-rendering-20261003 into master
Keeps both lines: master's PGXP session, draw-distance clamps, masked rejects
and saturated-winding NCLIP; the release's render-pass kit additions, verified
media/disc extents, timeline/present rings, cull-scale sites. Overlay ABI v30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 19:27:02 -07:00

143 lines
6.5 KiB
C++

// Launcher input validation only. The guest/runtime SBI reader is unchanged.
#pragma once
#include "psx_sha256.h"
#include <array>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <string>
#include <regex>
#include <sstream>
#include <stdexcept>
namespace PSXRecompV4 {
struct SbiRequirement {
uint64_t size;
const char* data_sha256;
const char* sbi_sha256;
const char* title;
const char* serial;
};
#include "sbi_registry.h"
struct SbiSetupResult {
bool required = false;
bool ready = true;
std::string message;
};
inline std::string sbi_setup_hash(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary);
if (!input) return {};
psx_sha256_ctx ctx;
psx_sha256_init(&ctx);
std::array<uint8_t, 65536> buffer;
while (input) {
input.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
psx_sha256_update(&ctx, buffer.data(), static_cast<size_t>(input.gcount()));
}
if (!input.eof()) return {};
uint8_t digest[32];
psx_sha256_final(&ctx, digest);
std::string result;
for (auto byte : digest) {
result += "0123456789abcdef"[byte >> 4];
result += "0123456789abcdef"[byte & 15];
}
return result;
}
inline SbiSetupResult check_sbi_setup(
const std::filesystem::path& data_track, const std::filesystem::path& mounted_image,
const SbiRequirement* requirements = kSbiRequirements,
size_t count = sizeof(kSbiRequirements) / sizeof(kSbiRequirements[0])) {
std::error_code error;
const auto size = std::filesystem::file_size(data_track, error);
if (error) return {}; // Existing disc validation owns unreadable media.
std::string data_hash;
for (size_t i = 0; i < count; ++i) {
const auto& rule = requirements[i];
if (size != rule.size) continue;
if (data_hash.empty()) data_hash = sbi_setup_hash(data_track);
if (data_hash != rule.data_sha256) continue;
auto companion = mounted_image;
companion.replace_extension(".sbi");
const bool exists = std::filesystem::is_regular_file(companion, error);
if (exists && sbi_setup_hash(companion) == rule.sbi_sha256)
return {true, true, {}};
std::string message = std::string(rule.title) + " (" + rule.serial + ") needs a matching SBI file.\n\n";
message += exists ? "The selected disc's SBI file does not match this supported revision.\n\n"
: "The SBI file is missing. It contains disc subchannel data required by this revision.\n\n";
message += "Place your lawfully obtained matching SBI file here:\n" + companion.string();
message += "\n\nThen select the disc again. Play remains blocked until the file matches."
"\nSetup does not supply or download SBI files.";
return {true, false, message};
}
return {}; // Unknown revision, not proof that subchannels are complete.
}
// Stage only CUE metadata and the companion. The user's disc stays untouched.
inline std::filesystem::path import_sbi_setup(
const std::filesystem::path& data, const std::filesystem::path& mounted,
const std::filesystem::path& selected, const std::filesystem::path& destination,
const SbiRequirement* rules = kSbiRequirements,
size_t count = sizeof(kSbiRequirements) / sizeof(kSbiRequirements[0])) {
namespace fs = std::filesystem;
std::error_code ec;
const auto size = fs::file_size(data, ec);
if (ec) throw std::runtime_error("Select a readable disc first.");
const SbiRequirement* matched = nullptr;
std::string hash;
for (size_t i = 0; i < count; ++i) {
if (rules[i].size != size) continue;
if (hash.empty()) hash = sbi_setup_hash(data);
if (hash == rules[i].data_sha256) { matched = &rules[i]; break; }
}
if (!matched) throw std::runtime_error("The selected disc has no supported SBI requirement.");
if (sbi_setup_hash(selected) != matched->sbi_sha256)
throw std::runtime_error("This SBI file does not match the selected disc. Select its matching SBI file.");
std::string cue;
auto extension = mounted.extension().string();
for (auto& c : extension) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (extension == ".cue") {
std::ifstream input(mounted);
if (!input) throw std::runtime_error("Cannot read the selected CUE file.");
const std::regex file_line(R"re(^(\s*FILE\s+)(?:"([^"]+)"|(\S+))(\s+.*)$)re", std::regex::icase);
std::string line; bool found = false;
while (std::getline(input, line)) {
if (!line.empty() && line.back() == '\r') line.pop_back();
std::smatch match;
if (std::regex_match(line, match, file_line)) {
fs::path track(match[2].matched ? match[2].str() : match[3].str());
if (track.is_relative()) track = mounted.parent_path() / track;
track = fs::absolute(track).lexically_normal();
if (!fs::is_regular_file(track)) throw std::runtime_error("A CUE track file is missing.");
line = match[1].str() + "\"" + track.generic_string() + "\"" + match[4].str();
found = true;
}
cue += line + "\n";
}
if (!input.eof() || !found) throw std::runtime_error("Cannot import this CUE file.");
} else if (extension == ".bin") {
cue = "FILE \"" + fs::absolute(data).generic_string() + "\" BINARY\n TRACK 01 MODE2/2352\n INDEX 01 00:00:00\n";
} else throw std::runtime_error("Select this disc's CUE or BIN file first.");
// Unique directory avoids replacing any existing disc or companion.
fs::create_directories(destination);
fs::path folder;
for (unsigned n = 1; n != 100000; ++n) {
auto candidate = destination / ("disc-" + std::to_string(n));
if (fs::create_directory(candidate)) { folder = candidate; break; }
}
if (folder.empty()) throw std::runtime_error("Cannot create the SBI input folder.");
try {
fs::copy_file(selected, folder / "disc.sbi");
if (sbi_setup_hash(folder / "disc.sbi") != matched->sbi_sha256)
throw std::runtime_error("The SBI file changed during import. Select it again.");
std::ofstream output(folder / "disc.cue", std::ios::binary);
output << cue;
output.close();
if (!output) throw std::runtime_error("Cannot save the imported CUE file.");
} catch (...) { fs::remove_all(folder, ec); throw; }
return fs::absolute(folder / "disc.cue");
}
} // namespace PSXRecompV4