Update quickplay module (#38)

* Migrate to use the REL loading dol patch from aprilwade's randomprime.
* Migrate Quickplay module to use the Pwootage's PrimeAPI2
* Add Quickplay module for Echoes NTSC
This commit is contained in:
Henrique Gemignani Passos Lima 2021-05-12 21:55:55 +03:00 committed by GitHub
parent dad9631099
commit 9e0842e03b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 179 additions and 22 deletions

3
.gitignore vendored
View File

@ -40,6 +40,9 @@ CMakeLists.txt.user
.idea/vcs.xml
.idea/workspace.xml
#Visual Studio
.vs
#Dew
.dew/*

Binary file not shown.

View File

@ -0,0 +1,2 @@
0x8000207c rel_loader_hook
0x8036F8BC PPCSetFpIEEEMode

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
0x8000207c rel_loader_hook
0x803599e4 PPCSetFpIEEEMode

Binary file not shown.

View File

@ -0,0 +1,2 @@
0x8000207c rel_loader_hook
0x803704ec PPCSetFpIEEEMode

Binary file not shown.

View File

@ -0,0 +1,2 @@
0x8000207c rel_loader_hook
0x8035712c PPCSetFpIEEEMode

Binary file not shown.

View File

@ -1,6 +1,7 @@
#include "NDolphinIntegration.h"
#include "Editor/MacOSExtras.h"
#include "Editor/UICommon.h"
#include "Editor/SDolHeader.h"
#include <QFileInfo>
#include <QRegExp>
@ -18,7 +19,7 @@ namespace NDolphinIntegration
const char* const gkDolphinPathSetting = "Quickplay/DolphinPath";
const char* const gkFeaturesSetting = "Quickplay/Features";
const char* const gkRelFileName = "EditorQuickplay.rel";
const char* const gkRelFileName = "patches.rel";
const char* const gkParameterFile = "dbgconfig";
const char* const gkDolPath = "sys/main.dol";
const char* const gkDolBackupPath = "sys/main.original.dol";
@ -52,6 +53,34 @@ void CQuickplayRelay::QuickplayFinished(int ReturnCode)
CQuickplayRelay gQuickplayRelay;
uint32 AssembleBranchInstruction(uint32 instructionAddress, uint32 branchTarget)
{
int32 jumpOffset = ((int32)branchTarget - (int32)instructionAddress) / 4;
if (jumpOffset < 0)
{
jumpOffset += 1 << 24;
}
return (18 << 26) + (jumpOffset << 2);
}
std::map<TString, uint32> LoadSymbols(const TString& mapContents) {
std::map<TString, uint32> result;
for (auto& line : mapContents.Split("\n"))
{
auto separator = line.IndexOf(' ');
if (separator > 0)
{
auto address = line.SubString(0, separator);
auto name = line.SubString(separator + 1, line.Length() - separator - 1).Trimmed();
result.emplace(name, static_cast<uint32>(address.ToInt32(16)));
}
}
return result;
}
/** Attempt to launch quickplay based on the current editor state. */
EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
CGameProject* pProject,
@ -59,13 +88,19 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
{
debugf("Launching quickplay...");
// Check if quickplay is supported for this project
// Check if we have the files needed for this project's target game and version.
// The required files are split into two parts:
// * Quickplay Module: A .rel compiled from https://github.com/AxioDL/PWEQuickplayPatch/
// * Dol patch for loading RELs:
// - A .bin compiled from https://github.com/aprilwade/randomprime/tree/master/compile_to_ppc/rel_loader
// - A .map, with the symbol for the .bin and the function in the dol to patch.
TString QuickplayDir = gDataDir + "resources/quickplay" / ::GetGameShortName(pProject->Game());
TString BuildString = "v" + TString::FromFloat(pProject->BuildVersion());
TString RelFile = QuickplayDir / BuildString + ".rel";
TString PatchFile = QuickplayDir / BuildString + ".bin";
TString PatchMapFile = QuickplayDir / BuildString + ".map";
if (!FileUtil::Exists(RelFile) || !FileUtil::Exists(PatchFile))
if (!FileUtil::Exists(RelFile) || !FileUtil::Exists(PatchFile) || !FileUtil::Exists(PatchMapFile))
{
warnf("Quickplay launch failed! Quickplay is not supported for this project.");
return EQuickplayLaunchResult::UnsupportedForProject;
@ -122,14 +157,16 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
TString DiscSys = pProject->DiscDir(false) / "sys";
std::vector<uint8> DolData;
std::vector<uint8> PatchData;
TString MapData;
bool bLoadedDol = FileUtil::LoadFileToBuffer(DiscSys / "main.dol", DolData);
bool bLoadedPatch = FileUtil::LoadFileToBuffer(PatchFile, PatchData);
bool bLoadedMap = FileUtil::LoadFileToString(PatchMapFile, MapData);
if (!bLoadedDol || !bLoadedPatch)
if (!bLoadedDol || !bLoadedPatch || !bLoadedMap)
{
warnf("Quickplay launch failed! Failed to load %s into memory.",
bLoadedDol ? "patch data" : "game DOL");
const char* failedPart = !bLoadedDol ? "game DOL" : (!bLoadedPatch ? "patch data" : "patch symbols");
warnf("Quickplay launch failed! Failed to load %s into memory.", failedPart);
return EQuickplayLaunchResult::Failure;
}
@ -149,26 +186,28 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
FileUtil::CopyFile(DolPath, DolBackupPath);
}
auto symbols = LoadSymbols(MapData);
SDolHeader header(CMemoryInStream(DolData.data(), DolData.size(), EEndian::BigEndian));
// Append the patch data to the end of the dol
uint32 AlignedDolSize = VAL_ALIGN(DolData.size(), 32);
uint32 AlignedPatchSize = VAL_ALIGN(PatchData.size(), 32);
uint32 PatchOffset = AlignedDolSize;
uint32 PatchSize = AlignedPatchSize;
DolData.resize(PatchOffset + PatchSize);
memcpy(&DolData[PatchOffset], &PatchData[0], PatchData.size());
DolData.resize(AlignedDolSize + AlignedPatchSize);
memcpy(&DolData[AlignedDolSize], &PatchData[0], PatchData.size());
if (!header.AddTextSection(0x80002000, AlignedDolSize, AlignedPatchSize))
{
warnf("Quickplay launch failed! Failed to patch DOL. Is it already patched?");
return EQuickplayLaunchResult::Failure;
}
uint32 callToHook = symbols["PPCSetFpIEEEMode"] + 4;
uint32 branchTarget = symbols["rel_loader_hook"];
// These constants are hardcoded for the moment.
// We write the patch to text section 6, which must be at address 0x80002600.
// We hook over the call to LCEnable, which is at offset 0x1D64.
CMemoryOutStream Mem(DolData.data(), DolData.size(), EEndian::BigEndian);
Mem.GoTo(0x18);
Mem.WriteLong(PatchOffset);
Mem.GoTo(0x60);
Mem.WriteLong(0x80002600);
Mem.GoTo(0xA8);
Mem.WriteLong(PatchSize);
Mem.GoTo(0x1D64);
Mem.WriteLong(0x4BFFD80D); // this patches in a call to the quickplay bootstrap during game boot process
header.Write(Mem);
Mem.GoTo(header.OffsetForAddress(callToHook));
Mem.WriteLong(AssembleBranchInstruction(callToHook, branchTarget));
if (!FileUtil::SaveBufferToFile(DolPath, DolData))
{
@ -211,7 +250,8 @@ bool IsQuickplaySupported(CGameProject* pProject)
TString BuildString = "v" + TString::FromFloat(pProject->BuildVersion());
TString RelFile = QuickplayDir / BuildString + ".rel";
TString PatchFile = QuickplayDir / BuildString + ".bin";
return FileUtil::Exists(RelFile) && FileUtil::Exists(PatchFile);
TString MapFile = QuickplayDir / BuildString + ".map";
return FileUtil::Exists(RelFile) && FileUtil::Exists(PatchFile) && FileUtil::Exists(MapFile);
}
/** Kill the current quickplay process, if it exists. */

106
src/Editor/SDolHeader.h Normal file
View File

@ -0,0 +1,106 @@
#ifndef CDOLHEADER_H
#define CDOLHEADER_H
#include <Common/FileIO/IOutputStream.h>
#include <Common/FileIO/IInputStream.h>
class SDolHeader
{
public:
static const size_t kNumTextSections = 7;
static const size_t kNumDataSections = 11;
static const size_t kNumSections = kNumTextSections + kNumDataSections;
struct Section
{
uint32 Offset;
uint32 BaseAddress;
uint32 Size;
bool IsEmpty() const {
return Size == 0;
}
};
Section Sections[kNumSections];
uint32 BssAddress;
uint32 BssSize;
uint32 EntryPoint;
explicit SDolHeader(IInputStream& rInput)
{
for (size_t i = 0; i < kNumSections; ++i)
{
Sections[i].Offset = rInput.ReadLong();
}
for (size_t i = 0; i < kNumSections; ++i)
{
Sections[i].BaseAddress = rInput.ReadLong();
}
for (size_t i = 0; i < kNumSections; ++i)
{
Sections[i].Size = rInput.ReadLong();
}
BssAddress = rInput.ReadLong();
BssSize = rInput.ReadLong();
EntryPoint = rInput.ReadLong();
}
void Write(IOutputStream& rOutput) const
{
for (size_t i = 0; i < kNumSections; ++i)
{
rOutput.WriteLong(Sections[i].Offset);
}
for (size_t i = 0; i < kNumSections; ++i)
{
rOutput.WriteLong(Sections[i].BaseAddress);
}
for (size_t i = 0; i < kNumSections; ++i)
{
rOutput.WriteLong(Sections[i].Size);
}
rOutput.WriteLong(BssAddress);
rOutput.WriteLong(BssSize);
rOutput.WriteLong(EntryPoint);
}
bool AddTextSection(uint32 address, uint32 fileOffset, uint32 size)
{
if ((size & 0x1f) != 0)
{
warnf("Unable to add text section: Size not 32-bit aligned.");
return false;
}
for (size_t i = 0; i < kNumTextSections; ++i)
{
if (Sections[i].IsEmpty())
{
Sections[i].BaseAddress = address;
Sections[i].Offset = fileOffset;
Sections[i].Size = size;
return true;
}
}
warnf("Unable to add text section: no empty section found.");
return false;
}
uint32 OffsetForAddress(uint32 address)
{
for (size_t i = 0; i < kNumSections; ++i)
{
auto& sec = Sections[i];
if (address > sec.BaseAddress && address < sec.BaseAddress + sec.Size)
{
return sec.Offset + (address - sec.BaseAddress);
}
}
warnf("Unable to add section for address: %x", address);
return 0;
}
};
#endif // SDOLHEADER_H