Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix UMD disc swap with Retroachievements enabled #18143

Merged
merged 2 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Core/HLE/sceUmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,12 +487,13 @@ static u32 sceUmdGetErrorStat()

void __UmdReplace(const Path &filepath) {
std::string error = "";
if (!UmdReplace(filepath, error)) {
FileLoader *fileLoader;
if (!UmdReplace(filepath, &fileLoader, error)) {
ERROR_LOG(SCEIO, "UMD Replace failed: %s", error.c_str());
return;
}

Achievements::ChangeUMD(filepath);
Achievements::ChangeUMD(filepath, fileLoader);

UMDInserted = false;
// Wake any threads waiting for the disc to be removed.
Expand Down
5 changes: 3 additions & 2 deletions Core/Loaders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) {
return false;
}

bool UmdReplace(const Path &filepath, std::string &error) {
bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &error) {
IFileSystem *currentUMD = pspFileSystem.GetSystem("disc0:");

if (!currentUMD) {
Expand All @@ -404,6 +404,8 @@ bool UmdReplace(const Path &filepath, std::string &error) {

loadedFile = ResolveFileLoaderTarget(loadedFile);

*fileLoader = loadedFile;

std::string errorString;
IdentifiedFileType type = Identify_File(loadedFile, &errorString);

Expand All @@ -415,7 +417,6 @@ bool UmdReplace(const Path &filepath, std::string &error) {
error = "reinit memory failed";
return false;
}

break;
default:
error = "Unsupported file type: " + std::to_string((int)type) + " " + errorString;
Expand Down
2 changes: 1 addition & 1 deletion Core/Loaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,4 @@ void RegisterFileLoaderFactory(std::string prefix, std::unique_ptr<FileLoaderFac
// Can modify the string filename, as it calls IdentifyFile above.
bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string);

bool UmdReplace(const Path &filepath, std::string &error);
bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &error);
158 changes: 94 additions & 64 deletions Core/RetroAchievements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ static rc_client_t *g_rcClient;
static const std::string g_RAImageID = "I_RETROACHIEVEMENTS_LOGO";
constexpr double LOGIN_ATTEMPT_INTERVAL_S = 10.0;

struct FileContext {
BlockDevice *bd;
int64_t seekPos;
};
static BlockDevice *g_blockDevice;

#define PSP_MEMORY_OFFSET 0x08000000

static void TryLoginByToken(bool isInitialAttempt);
Expand Down Expand Up @@ -411,6 +417,60 @@ void Initialize() {

rc_client_set_event_handler(g_rcClient, event_handler_callback);

rc_hash_filereader rc_filereader;
rc_filereader.open = [](const char *utf8Path) -> void *{
if (!g_blockDevice) {
ERROR_LOG(ACHIEVEMENTS, "No block device");
return nullptr;
}

return (void *) new FileContext{ g_blockDevice, 0 };
};
rc_filereader.seek = [](void *file_handle, int64_t offset, int origin) {
FileContext *ctx = (FileContext *)file_handle;
switch (origin) {
case SEEK_SET: ctx->seekPos = offset; break;
case SEEK_END: ctx->seekPos = ctx->bd->GetBlockSize() * ctx->bd->GetNumBlocks() + offset; break;
case SEEK_CUR: ctx->seekPos += offset; break;
default: break;
}
};
rc_filereader.tell = [](void *file_handle) -> int64_t {
return ((FileContext *)file_handle)->seekPos;
};
rc_filereader.read = [](void *file_handle, void *buffer, size_t requested_bytes) -> size_t {
FileContext *ctx = (FileContext *)file_handle;

int blockSize = ctx->bd->GetBlockSize();

int64_t offset = ctx->seekPos;
int64_t endOffset = ctx->seekPos + requested_bytes;
int firstBlock = offset / blockSize;
int afterLastBlock = (endOffset + blockSize - 1) / blockSize;
int numBlocks = afterLastBlock - firstBlock;
// This is suboptimal, but good enough since we're not doing a lot of accesses.
uint8_t *buf = new uint8_t[numBlocks * blockSize];
bool success = ctx->bd->ReadBlocks(firstBlock, numBlocks, (u8 *)buf);
if (success) {
int64_t firstOffset = firstBlock * blockSize;
memcpy(buffer, buf + (offset - firstOffset), requested_bytes);
ctx->seekPos += requested_bytes;
delete[] buf;
return requested_bytes;
} else {
delete[] buf;
ERROR_LOG(ACHIEVEMENTS, "Block device load fail");
return 0;
}
};
rc_filereader.close = [](void *file_handle) {
FileContext *ctx = (FileContext *)file_handle;
delete ctx->bd;
delete ctx;
};
rc_hash_init_custom_filereader(&rc_filereader);
rc_hash_init_default_cdreader();

TryLoginByToken(true);
}

Expand Down Expand Up @@ -723,13 +783,6 @@ void identify_and_load_callback(int result, const char *error_message, rc_client
g_isIdentifying = false;
}

struct FileContext {
BlockDevice *bd;
int64_t seekPos;
};

static BlockDevice *g_blockDevice;

bool IsReadyToStart() {
return !g_isLoggingIn;
}
Expand Down Expand Up @@ -770,58 +823,6 @@ void SetGame(const Path &path, IdentifiedFileType fileType, FileLoader *fileLoad
return;
}

rc_hash_filereader rc_filereader;
rc_filereader.open = [](const char *utf8Path) -> void * {
if (!g_blockDevice) {
ERROR_LOG(ACHIEVEMENTS, "No block device");
return nullptr;
}

return (void *) new FileContext{ g_blockDevice, 0 };
};
rc_filereader.seek = [](void *file_handle, int64_t offset, int origin) {
FileContext *ctx = (FileContext *)file_handle;
switch (origin) {
case SEEK_SET: ctx->seekPos = offset; break;
case SEEK_END: ctx->seekPos = ctx->bd->GetBlockSize() * ctx->bd->GetNumBlocks() + offset; break;
case SEEK_CUR: ctx->seekPos += offset; break;
default: break;
}
};
rc_filereader.tell = [](void *file_handle) -> int64_t {
return ((FileContext *)file_handle)->seekPos;
};
rc_filereader.read = [](void *file_handle, void *buffer, size_t requested_bytes) -> size_t {
FileContext *ctx = (FileContext *)file_handle;

int blockSize = ctx->bd->GetBlockSize();

int64_t offset = ctx->seekPos;
int64_t endOffset = ctx->seekPos + requested_bytes;
int firstBlock = offset / blockSize;
int afterLastBlock = (endOffset + blockSize - 1) / blockSize;
int numBlocks = afterLastBlock - firstBlock;
// This is suboptimal, but good enough since we're not doing a lot of accesses.
uint8_t *buf = new uint8_t[numBlocks * blockSize];
bool success = ctx->bd->ReadBlocks(firstBlock, numBlocks, (u8 *)buf);
if (success) {
int64_t firstOffset = firstBlock * blockSize;
memcpy(buffer, buf + (offset - firstOffset), requested_bytes);
ctx->seekPos += requested_bytes;
delete[] buf;
return requested_bytes;
} else {
delete[] buf;
ERROR_LOG(ACHIEVEMENTS, "Block device load fail");
return 0;
}
};
rc_filereader.close = [](void *file_handle) {
FileContext *ctx = (FileContext *)file_handle;
delete ctx->bd;
delete ctx;
};

// The caller should hold off on executing game code until this turns false, checking with IsBlockingExecution()
g_isIdentifying = true;

Expand All @@ -830,8 +831,6 @@ void SetGame(const Path &path, IdentifiedFileType fileType, FileLoader *fileLoad
rc_client_set_encore_mode_enabled(g_rcClient, g_Config.bAchievementsEncoreMode ? 1 : 0);
rc_client_set_unofficial_enabled(g_rcClient, g_Config.bAchievementsUnofficial ? 1 : 0);

rc_hash_init_custom_filereader(&rc_filereader);
rc_hash_init_default_cdreader();
rc_client_begin_identify_and_load_game(g_rcClient, RC_CONSOLE_PSP, path.c_str(), nullptr, 0, &identify_and_load_callback, nullptr);

// fclose above will have deleted it.
Expand All @@ -845,25 +844,56 @@ void UnloadGame() {
}

void change_media_callback(int result, const char *error_message, rc_client_t *client, void *userdata) {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
NOTICE_LOG(ACHIEVEMENTS, "Change media callback: %d (%s)", result, error_message);
g_isIdentifying = false;

switch (result) {
case RC_OK:
{
// Successful! Later, show a message that we succeeded.
break;
}
case RC_NO_GAME_LOADED:
// The current game does not support achievements.
g_OSD.Show(OSDType::MESSAGE_INFO, ac->T("RetroAchievements are not available for this game"), "", g_RAImageID, 3.0f);
break;
case RC_NO_RESPONSE:
// We lost the internet connection at some point and can't report achievements.
ShowNotLoggedInMessage();
break;
default:
// Other various errors.
ERROR_LOG(ACHIEVEMENTS, "Failed to identify/load game: %d (%s)", result, error_message);
g_OSD.Show(OSDType::MESSAGE_ERROR, ac->T("Failed to identify game. Achievements will not unlock."), "", g_RAImageID, 6.0f);
break;
}
}

void ChangeUMD(const Path &path) {
void ChangeUMD(const Path &path, FileLoader *fileLoader) {
if (!IsActive()) {
// Nothing to do.
return;
}

rc_client_begin_change_media(g_rcClient,
g_blockDevice = constructBlockDevice(fileLoader);
if (!g_blockDevice) {
ERROR_LOG(ACHIEVEMENTS, "Failed to construct block device for '%s' - can't identify", path.c_str());
return;
}

g_isIdentifying = true;

rc_client_begin_change_media(g_rcClient,
path.c_str(),
nullptr,
0,
&change_media_callback,
nullptr
);

g_isIdentifying = true;
// fclose above will have deleted it.
g_blockDevice = nullptr;
}

std::set<uint32_t> GetActiveChallengeIDs() {
Expand Down
2 changes: 1 addition & 1 deletion Core/RetroAchievements.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void Logout();

bool IsReadyToStart();
void SetGame(const Path &path, IdentifiedFileType fileType, FileLoader *fileLoader);
void ChangeUMD(const Path &path); // for in-game UMD change
void ChangeUMD(const Path &path, FileLoader *fileLoader); // for in-game UMD change
void UnloadGame(); // Call when leaving a game.

Statistics GetStatistics();
Expand Down