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:
parent
dad9631099
commit
9e0842e03b
|
@ -40,6 +40,9 @@ CMakeLists.txt.user
|
|||
.idea/vcs.xml
|
||||
.idea/workspace.xml
|
||||
|
||||
#Visual Studio
|
||||
.vs
|
||||
|
||||
#Dew
|
||||
.dew/*
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
0x8000207c rel_loader_hook
|
||||
0x8036F8BC PPCSetFpIEEEMode
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
0x8000207c rel_loader_hook
|
||||
0x803599e4 PPCSetFpIEEEMode
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
0x8000207c rel_loader_hook
|
||||
0x803704ec PPCSetFpIEEEMode
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
0x8000207c rel_loader_hook
|
||||
0x8035712c PPCSetFpIEEEMode
|
Binary file not shown.
|
@ -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. */
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue