mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-07-04 22:32:57 +12:00
Merge branch 'master' into open-bp-cpp
This commit is contained in:
commit
13ee3a1bae
37 changed files with 5520 additions and 92 deletions
|
@ -144,8 +144,8 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) {
|
|||
case ShaderOpcodes::CMP2: recCMP(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break;
|
||||
// case ShaderOpcodes::DPH:
|
||||
// case ShaderOpcodes::DPHI: recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DPH:
|
||||
case ShaderOpcodes::DPHI: recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::END: recEND(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::EX2: recEX2(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::FLR: recFLR(shaderUnit, instruction); break;
|
||||
|
@ -533,6 +533,39 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) {
|
|||
storeRegister(src1Vec, shader, dest, operandDescriptor);
|
||||
}
|
||||
|
||||
void ShaderEmitter::recDPH(const PICAShader& shader, u32 instruction) {
|
||||
const bool isDPHI = (instruction >> 26) == ShaderOpcodes::DPHI;
|
||||
|
||||
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||
const u32 src1 = isDPHI ? getBits<14, 5>(instruction) : getBits<12, 7>(instruction);
|
||||
const u32 src2 = isDPHI ? getBits<7, 7>(instruction) : getBits<7, 5>(instruction);
|
||||
const u32 idx = getBits<19, 2>(instruction);
|
||||
const u32 dest = getBits<21, 5>(instruction);
|
||||
const u32 writeMask = getBits<0, 4>(operandDescriptor);
|
||||
|
||||
// TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA)
|
||||
loadRegister<1>(src1Vec, shader, src1, isDPHI ? 0 : idx, operandDescriptor);
|
||||
loadRegister<2>(src2Vec, shader, src2, isDPHI ? idx : 0, operandDescriptor);
|
||||
// // Attach 1.0 to the w component of src1
|
||||
MOV(src1Vec.Selem()[3], onesVector.Selem()[0]);
|
||||
|
||||
// Now perform a DP4
|
||||
// Do a piecewise multiplication of the vectors first
|
||||
if constexpr (useSafeMUL) {
|
||||
emitSafeMUL(src1Vec, src2Vec, scratch1Vec);
|
||||
} else {
|
||||
FMUL(src1Vec.S4(), src1Vec.S4(), src2Vec.S4());
|
||||
}
|
||||
FADDP(src1Vec.S4(), src1Vec.S4(), src1Vec.S4()); // Now add the adjacent components together
|
||||
FADDP(src1Vec.toS(), src1Vec.toD().S2()); // Again for the bottom 2 lanes. Now the bottom lane contains the dot product
|
||||
|
||||
if (writeMask != 0x8) { // Copy bottom lane to all lanes if we're not simply writing back x
|
||||
DUP(src1Vec.S4(), src1Vec.Selem()[0]); // src1Vec = src1Vec.xxxx
|
||||
}
|
||||
|
||||
storeRegister(src1Vec, shader, dest, operandDescriptor);
|
||||
}
|
||||
|
||||
oaknut::Label ShaderEmitter::emitLog2Func() {
|
||||
oaknut::Label funcStart;
|
||||
|
||||
|
|
|
@ -355,7 +355,7 @@ namespace Audio {
|
|||
}
|
||||
|
||||
switch (buffer.format) {
|
||||
case SampleFormat::PCM8: Helpers::warn("Unimplemented sample format!"); break;
|
||||
case SampleFormat::PCM8: source.currentSamples = decodePCM8(data, buffer.sampleCount, source); break;
|
||||
case SampleFormat::PCM16: source.currentSamples = decodePCM16(data, buffer.sampleCount, source); break;
|
||||
case SampleFormat::ADPCM: source.currentSamples = decodeADPCM(data, buffer.sampleCount, source); break;
|
||||
|
||||
|
@ -406,6 +406,26 @@ namespace Audio {
|
|||
}
|
||||
}
|
||||
|
||||
HLE_DSP::SampleBuffer HLE_DSP::decodePCM8(const u8* data, usize sampleCount, Source& source) {
|
||||
SampleBuffer decodedSamples(sampleCount);
|
||||
|
||||
if (source.sourceType == SourceType::Stereo) {
|
||||
for (usize i = 0; i < sampleCount; i++) {
|
||||
const s16 left = s16(u16(*data++) << 8);
|
||||
const s16 right = s16(u16(*data++) << 8);
|
||||
decodedSamples[i] = {left, right};
|
||||
}
|
||||
} else {
|
||||
// Mono
|
||||
for (usize i = 0; i < sampleCount; i++) {
|
||||
const s16 sample = s16(u16(*data++) << 8);
|
||||
decodedSamples[i] = {sample, sample};
|
||||
}
|
||||
}
|
||||
|
||||
return decodedSamples;
|
||||
}
|
||||
|
||||
HLE_DSP::SampleBuffer HLE_DSP::decodePCM16(const u8* data, usize sampleCount, Source& source) {
|
||||
SampleBuffer decodedSamples(sampleCount);
|
||||
const s16* data16 = reinterpret_cast<const s16*>(data);
|
||||
|
|
|
@ -39,7 +39,35 @@ HorizonResult SDMCArchive::createFile(const FSPath& path, u64 size) {
|
|||
}
|
||||
|
||||
HorizonResult SDMCArchive::deleteFile(const FSPath& path) {
|
||||
Helpers::panic("[SDMC] Unimplemented DeleteFile");
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMC::DeleteFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SDMC";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
||||
if (fs::is_directory(p)) {
|
||||
Helpers::panic("SDMC::DeleteFile: Tried to delete directory");
|
||||
}
|
||||
|
||||
if (!fs::is_regular_file(p)) {
|
||||
return Result::FS::FileNotFoundAlt;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
bool success = fs::remove(p, ec);
|
||||
|
||||
// It might still be possible for fs::remove to fail, if there's eg an open handle to a file being deleted
|
||||
// In this case, print a warning, but still return success for now
|
||||
if (!success) {
|
||||
Helpers::warn("SDMC::DeleteFile: fs::remove failed\n");
|
||||
}
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
Helpers::panic("SDMCArchive::DeleteFile: Unknown path type");
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
@ -145,7 +173,7 @@ Rust::Result<DirectorySession, HorizonResult> SDMCArchive::openDirectory(const F
|
|||
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
||||
Helpers::panic("Unsafe path in SDMC::OpenDirectory");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SDMC";
|
||||
|
|
|
@ -184,7 +184,8 @@ void Kernel::setFileSize(u32 messagePointer, Handle fileHandle) {
|
|||
if (success) {
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
} else {
|
||||
Helpers::panic("FileOp::SetFileSize failed");
|
||||
Helpers::warn("FileOp::SetFileSize failed");
|
||||
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
|
||||
}
|
||||
} else {
|
||||
Helpers::panic("Tried to set file size of file without file descriptor");
|
||||
|
|
|
@ -399,3 +399,5 @@ std::string Kernel::getProcessName(u32 pid) {
|
|||
Helpers::panic("Attempted to name non-current process");
|
||||
}
|
||||
}
|
||||
|
||||
Scheduler& Kernel::getScheduler() { return cpu.getScheduler(); }
|
||||
|
|
|
@ -29,6 +29,9 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
smdh.clear();
|
||||
partitionInfo = info;
|
||||
|
||||
primaryKey = {};
|
||||
secondaryKey = {};
|
||||
|
||||
size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break
|
||||
exheaderSize = *(u32*)&header[0x180];
|
||||
|
||||
|
@ -78,11 +81,11 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
if (!primaryResult.first || !secondaryResult.first) {
|
||||
gotCryptoKeys = false;
|
||||
} else {
|
||||
Crypto::AESKey primaryKey = primaryResult.second;
|
||||
Crypto::AESKey secondaryKey = secondaryResult.second;
|
||||
primaryKey = primaryResult.second;
|
||||
secondaryKey = secondaryResult.second;
|
||||
|
||||
EncryptionInfo encryptionInfoTmp;
|
||||
encryptionInfoTmp.normalKey = primaryKey;
|
||||
encryptionInfoTmp.normalKey = *primaryKey;
|
||||
encryptionInfoTmp.initialCounter.fill(0);
|
||||
|
||||
for (std::size_t i = 1; i <= sizeof(std::uint64_t) - 1; i++) {
|
||||
|
@ -94,7 +97,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
encryptionInfoTmp.initialCounter[8] = 2;
|
||||
exeFS.encryptionInfo = encryptionInfoTmp;
|
||||
|
||||
encryptionInfoTmp.normalKey = secondaryKey;
|
||||
encryptionInfoTmp.normalKey = *secondaryKey;
|
||||
encryptionInfoTmp.initialCounter[8] = 3;
|
||||
romFS.encryptionInfo = encryptionInfoTmp;
|
||||
}
|
||||
|
@ -201,13 +204,20 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
Helpers::panic("Second code file in a single NCCH partition. What should this do?\n");
|
||||
}
|
||||
|
||||
// All files in ExeFS use the same IV, though .code uses the secondary key for decryption
|
||||
// whereas .icon/.banner use the primary key.
|
||||
FSInfo info = exeFS;
|
||||
if (encrypted && secondaryKey.has_value() && info.encryptionInfo.has_value()) {
|
||||
info.encryptionInfo->normalKey = *secondaryKey;
|
||||
}
|
||||
|
||||
if (compressCode) {
|
||||
std::vector<u8> tmp;
|
||||
tmp.resize(fileSize);
|
||||
|
||||
// A file offset of 0 means our file is located right after the ExeFS header
|
||||
// So in the ROM, files are located at (file offset + exeFS offset + exeFS header size)
|
||||
readFromFile(file, exeFS, tmp.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
readFromFile(file, info, tmp.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
|
||||
// Decompress .code file from the tmp vector to the "code" vector
|
||||
if (!CartLZ77::decompress(codeFile, tmp)) {
|
||||
|
@ -216,7 +226,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
}
|
||||
} else {
|
||||
codeFile.resize(fileSize);
|
||||
readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
readFromFile(file, info, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
}
|
||||
} else if (std::strcmp(name, "icon") == 0) {
|
||||
// Parse icon file to extract region info and more in the future (logo, etc)
|
||||
|
|
|
@ -57,24 +57,7 @@ void RendererGL::initGraphicsContextInternal() {
|
|||
OpenGL::Shader vert({vertexShaderSource.begin(), vertexShaderSource.size()}, OpenGL::Vertex);
|
||||
OpenGL::Shader frag({fragmentShaderSource.begin(), fragmentShaderSource.size()}, OpenGL::Fragment);
|
||||
triangleProgram.create({vert, frag});
|
||||
gl.useProgram(triangleProgram);
|
||||
|
||||
textureEnvSourceLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvSource");
|
||||
textureEnvOperandLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvOperand");
|
||||
textureEnvCombinerLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvCombiner");
|
||||
textureEnvColorLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvColor");
|
||||
textureEnvScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvScale");
|
||||
|
||||
depthScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_depthScale");
|
||||
depthOffsetLoc = OpenGL::uniformLocation(triangleProgram, "u_depthOffset");
|
||||
depthmapEnableLoc = OpenGL::uniformLocation(triangleProgram, "u_depthmapEnable");
|
||||
picaRegLoc = OpenGL::uniformLocation(triangleProgram, "u_picaRegs");
|
||||
|
||||
// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex0"), 0);
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex1"), 1);
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex2"), 2);
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex_lighting_lut"), 3);
|
||||
initUbershader(triangleProgram);
|
||||
|
||||
auto displayVertexShaderSource = gl_resources.open("opengl_display.vert");
|
||||
auto displayFragmentShaderSource = gl_resources.open("opengl_display.frag");
|
||||
|
@ -812,4 +795,47 @@ void RendererGL::deinitGraphicsContext() {
|
|||
// All other GL objects should be invalidated automatically and be recreated by the next call to initGraphicsContext
|
||||
// TODO: Make it so that depth and colour buffers get written back to 3DS memory
|
||||
printf("RendererGL::DeinitGraphicsContext called\n");
|
||||
}
|
||||
}
|
||||
|
||||
std::string RendererGL::getUbershader() {
|
||||
auto gl_resources = cmrc::RendererGL::get_filesystem();
|
||||
auto fragmentShader = gl_resources.open("opengl_fragment_shader.frag");
|
||||
|
||||
return std::string(fragmentShader.begin(), fragmentShader.end());
|
||||
}
|
||||
|
||||
void RendererGL::setUbershader(const std::string& shader) {
|
||||
auto gl_resources = cmrc::RendererGL::get_filesystem();
|
||||
auto vertexShaderSource = gl_resources.open("opengl_vertex_shader.vert");
|
||||
|
||||
OpenGL::Shader vert({vertexShaderSource.begin(), vertexShaderSource.size()}, OpenGL::Vertex);
|
||||
OpenGL::Shader frag(shader, OpenGL::Fragment);
|
||||
triangleProgram.create({vert, frag});
|
||||
|
||||
initUbershader(triangleProgram);
|
||||
|
||||
glUniform1f(depthScaleLoc, oldDepthScale);
|
||||
glUniform1f(depthOffsetLoc, oldDepthOffset);
|
||||
glUniform1i(depthmapEnableLoc, oldDepthmapEnable);
|
||||
}
|
||||
|
||||
void RendererGL::initUbershader(OpenGL::Program& program) {
|
||||
gl.useProgram(program);
|
||||
|
||||
textureEnvSourceLoc = OpenGL::uniformLocation(program, "u_textureEnvSource");
|
||||
textureEnvOperandLoc = OpenGL::uniformLocation(program, "u_textureEnvOperand");
|
||||
textureEnvCombinerLoc = OpenGL::uniformLocation(program, "u_textureEnvCombiner");
|
||||
textureEnvColorLoc = OpenGL::uniformLocation(program, "u_textureEnvColor");
|
||||
textureEnvScaleLoc = OpenGL::uniformLocation(program, "u_textureEnvScale");
|
||||
|
||||
depthScaleLoc = OpenGL::uniformLocation(program, "u_depthScale");
|
||||
depthOffsetLoc = OpenGL::uniformLocation(program, "u_depthOffset");
|
||||
depthmapEnableLoc = OpenGL::uniformLocation(program, "u_depthmapEnable");
|
||||
picaRegLoc = OpenGL::uniformLocation(program, "u_picaRegs");
|
||||
|
||||
// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex0"), 0);
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex1"), 1);
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex2"), 2);
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex_lighting_lut"), 3);
|
||||
}
|
||||
|
|
|
@ -343,7 +343,7 @@ void CAMService::startCapture(u32 messagePointer) {
|
|||
|
||||
if (port.isValid()) {
|
||||
for (int i : port.getPortIndices()) {
|
||||
auto& event = ports[port.getSingleIndex()].receiveEvent;
|
||||
auto& event = ports[i].receiveEvent;
|
||||
|
||||
// Until we properly implement cameras, immediately signal the receive event
|
||||
if (event.has_value()) {
|
||||
|
|
|
@ -61,6 +61,7 @@ void Y2RService::reset() {
|
|||
inputLineWidth = 420;
|
||||
|
||||
conversionCoefficients.fill(0);
|
||||
isBusy = false;
|
||||
}
|
||||
|
||||
void Y2RService::handleSyncRequest(u32 messagePointer) {
|
||||
|
@ -156,6 +157,11 @@ void Y2RService::setTransferEndInterrupt(u32 messagePointer) {
|
|||
void Y2RService::stopConversion(u32 messagePointer) {
|
||||
log("Y2R::StopConversion\n");
|
||||
|
||||
if (isBusy) {
|
||||
isBusy = false;
|
||||
kernel.getScheduler().removeEvent(Scheduler::EventType::SignalY2R);
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x27, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
@ -167,7 +173,7 @@ void Y2RService::isBusyConversion(u32 messagePointer) {
|
|||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x28, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(BusyStatus::NotBusy));
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(isBusy ? BusyStatus::Busy : BusyStatus::NotBusy));
|
||||
}
|
||||
|
||||
void Y2RService::setBlockAlignment(u32 messagePointer) {
|
||||
|
@ -434,11 +440,15 @@ void Y2RService::startConversion(u32 messagePointer) {
|
|||
mem.write32(messagePointer, IPC::responseHeader(0x26, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
||||
// Make Y2R conversion end instantly.
|
||||
// Signal the transfer end event if it's been created. TODO: Is this affected by SetTransferEndInterrupt?
|
||||
if (transferEndEvent.has_value()) {
|
||||
kernel.signalEvent(transferEndEvent.value());
|
||||
}
|
||||
// Schedule Y2R conversion end event.
|
||||
// The tick value is tweaked based on the minimum delay needed to get FIFA 15 to not hang due to a race condition on its title screen
|
||||
static constexpr u64 delayTicks = 1'350'000;
|
||||
isBusy = true;
|
||||
|
||||
// Remove any potential pending Y2R event and schedule a new one
|
||||
Scheduler& scheduler = kernel.getScheduler();
|
||||
scheduler.removeEvent(Scheduler::EventType::SignalY2R);
|
||||
scheduler.addEvent(Scheduler::EventType::SignalY2R, scheduler.currentTimestamp + delayTicks);
|
||||
}
|
||||
|
||||
void Y2RService::isFinishedSendingYUV(u32 messagePointer) {
|
||||
|
@ -484,4 +494,15 @@ void Y2RService::isFinishedReceiving(u32 messagePointer) {
|
|||
mem.write32(messagePointer, IPC::responseHeader(0x17, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, finished ? 1 : 0);
|
||||
}
|
||||
|
||||
void Y2RService::signalConversionDone() {
|
||||
if (isBusy) {
|
||||
isBusy = false;
|
||||
|
||||
// Signal the transfer end event if it's been created. TODO: Is this affected by SetTransferEndInterrupt?
|
||||
if (transferEndEvent.has_value()) {
|
||||
kernel.signalEvent(transferEndEvent.value());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,6 +84,7 @@ void Emulator::reset(ReloadOption reload) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef __LIBRETRO__
|
||||
std::filesystem::path Emulator::getAndroidAppPath() {
|
||||
// SDL_GetPrefPath fails to get the path due to no JNI environment
|
||||
std::ifstream cmdline("/proc/self/cmdline");
|
||||
|
@ -100,6 +101,7 @@ std::filesystem::path Emulator::getConfigPath() {
|
|||
return std::filesystem::current_path() / "config.toml";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Emulator::step() {}
|
||||
void Emulator::render() {}
|
||||
|
@ -169,6 +171,8 @@ void Emulator::pollScheduler() {
|
|||
break;
|
||||
}
|
||||
|
||||
case Scheduler::EventType::SignalY2R: kernel.getServiceManager().getY2R().signalConversionDone(); break;
|
||||
|
||||
default: {
|
||||
Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast<int>(eventType));
|
||||
break;
|
||||
|
@ -177,6 +181,7 @@ void Emulator::pollScheduler() {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef __LIBRETRO__
|
||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplicationName on Linux, etc)
|
||||
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
||||
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart.
|
||||
|
@ -200,6 +205,7 @@ std::filesystem::path Emulator::getAppDataRoot() {
|
|||
|
||||
return appDataPath;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||
// Reset the emulator if we've already loaded a ROM
|
||||
|
|
366
src/libretro_core.cpp
Normal file
366
src/libretro_core.cpp
Normal file
|
@ -0,0 +1,366 @@
|
|||
#include <stdexcept>
|
||||
#include <cstdio>
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
#include <emulator.hpp>
|
||||
#include <renderer_gl/renderer_gl.hpp>
|
||||
|
||||
static retro_environment_t envCallbacks;
|
||||
static retro_video_refresh_t videoCallbacks;
|
||||
static retro_audio_sample_batch_t audioBatchCallback;
|
||||
static retro_input_poll_t inputPollCallback;
|
||||
static retro_input_state_t inputStateCallback;
|
||||
|
||||
static retro_hw_render_callback hw_render;
|
||||
static std::filesystem::path savePath;
|
||||
|
||||
std::unique_ptr<Emulator> emulator;
|
||||
RendererGL* renderer;
|
||||
|
||||
std::filesystem::path Emulator::getConfigPath() {
|
||||
return std::filesystem::path(savePath / "config.toml");
|
||||
}
|
||||
|
||||
std::filesystem::path Emulator::getAppDataRoot() {
|
||||
return std::filesystem::path(savePath / "Emulator Files");
|
||||
}
|
||||
|
||||
static void* GetGLProcAddress(const char* name) {
|
||||
return (void*)hw_render.get_proc_address(name);
|
||||
}
|
||||
|
||||
static void VideoResetContext() {
|
||||
#ifdef USING_GLES
|
||||
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(GetGLProcAddress))) {
|
||||
Helpers::panic("OpenGL ES init failed");
|
||||
}
|
||||
#else
|
||||
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(GetGLProcAddress))) {
|
||||
Helpers::panic("OpenGL init failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
emulator->initGraphicsContext(nullptr);
|
||||
}
|
||||
|
||||
static void VideoDestroyContext() {
|
||||
emulator->deinitGraphicsContext();
|
||||
}
|
||||
|
||||
static bool SetHWRender(retro_hw_context_type type) {
|
||||
hw_render.context_type = type;
|
||||
hw_render.context_reset = VideoResetContext;
|
||||
hw_render.context_destroy = VideoDestroyContext;
|
||||
hw_render.bottom_left_origin = true;
|
||||
|
||||
switch (type) {
|
||||
case RETRO_HW_CONTEXT_OPENGL_CORE:
|
||||
hw_render.version_major = 4;
|
||||
hw_render.version_minor = 1;
|
||||
|
||||
if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case RETRO_HW_CONTEXT_OPENGLES3:
|
||||
case RETRO_HW_CONTEXT_OPENGL:
|
||||
hw_render.version_major = 3;
|
||||
hw_render.version_minor = 1;
|
||||
|
||||
if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void videoInit() {
|
||||
retro_hw_context_type preferred = RETRO_HW_CONTEXT_NONE;
|
||||
envCallbacks(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred);
|
||||
|
||||
if (preferred && SetHWRender(preferred)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL_CORE)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGLES3)) return;
|
||||
|
||||
hw_render.context_type = RETRO_HW_CONTEXT_NONE;
|
||||
}
|
||||
|
||||
static bool GetButtonState(uint id) { return inputStateCallback(0, RETRO_DEVICE_JOYPAD, 0, id); }
|
||||
static float GetAxisState(uint index, uint id) { return inputStateCallback(0, RETRO_DEVICE_ANALOG, index, id); }
|
||||
|
||||
static void inputInit() {
|
||||
static const retro_controller_description controllers[] = {
|
||||
{"Nintendo 3DS", RETRO_DEVICE_JOYPAD},
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
static const retro_controller_info ports[] = {
|
||||
{controllers, 1},
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
|
||||
|
||||
retro_input_descriptor desc[] = {
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "X"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Y"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Circle Pad X"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Circle Pad Y"},
|
||||
{0},
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &desc);
|
||||
}
|
||||
|
||||
static std::string FetchVariable(std::string key, std::string def) {
|
||||
retro_variable var = {nullptr};
|
||||
var.key = key.c_str();
|
||||
|
||||
if (!envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value == nullptr) {
|
||||
Helpers::warn("Fetching variable %s failed.", key.c_str());
|
||||
return def;
|
||||
}
|
||||
|
||||
return std::string(var.value);
|
||||
}
|
||||
|
||||
static bool FetchVariableBool(std::string key, bool def) {
|
||||
return FetchVariable(key, def ? "enabled" : "disabled") == "enabled";
|
||||
}
|
||||
|
||||
static void configInit() {
|
||||
static const retro_variable values[] = {
|
||||
{"panda3ds_use_shader_jit", "Enable shader JIT; enabled|disabled"},
|
||||
{"panda3ds_use_vsync", "Enable VSync; enabled|disabled"},
|
||||
{"panda3ds_dsp_emulation", "DSP emulation; Null|HLE|LLE"},
|
||||
{"panda3ds_use_audio", "Enable audio; disabled|enabled"},
|
||||
{"panda3ds_use_virtual_sd", "Enable virtual SD card; enabled|disabled"},
|
||||
{"panda3ds_write_protect_virtual_sd", "Write protect virtual SD card; disabled|enabled"},
|
||||
{"panda3ds_battery_level", "Battery percentage; 5|10|20|30|50|70|90|100"},
|
||||
{"panda3ds_use_charger", "Charger plugged; enabled|disabled"},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)values);
|
||||
}
|
||||
|
||||
static void configUpdate() {
|
||||
EmulatorConfig& config = emulator->getConfig();
|
||||
|
||||
config.rendererType = RendererType::OpenGL;
|
||||
config.vsyncEnabled = FetchVariableBool("panda3ds_use_vsync", true);
|
||||
config.shaderJitEnabled = FetchVariableBool("panda3ds_use_shader_jit", true);
|
||||
config.chargerPlugged = FetchVariableBool("panda3ds_use_charger", true);
|
||||
config.batteryPercentage = std::clamp(std::stoi(FetchVariable("panda3ds_battery_level", "5")), 0, 100);
|
||||
config.dspType = Audio::DSPCore::typeFromString(FetchVariable("panda3ds_dsp_emulation", "null"));
|
||||
config.audioEnabled = FetchVariableBool("panda3ds_use_audio", false);
|
||||
config.sdCardInserted = FetchVariableBool("panda3ds_use_virtual_sd", true);
|
||||
config.sdWriteProtected = FetchVariableBool("panda3ds_write_protect_virtual_sd", false);
|
||||
config.discordRpcEnabled = false;
|
||||
|
||||
config.save();
|
||||
}
|
||||
|
||||
static void ConfigCheckVariables() {
|
||||
bool updated = false;
|
||||
envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated);
|
||||
|
||||
if (updated) {
|
||||
configUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void retro_get_system_info(retro_system_info* info) {
|
||||
info->need_fullpath = true;
|
||||
info->valid_extensions = "3ds|3dsx|elf|axf|cci|cxi|app";
|
||||
info->library_version = "0.8";
|
||||
info->library_name = "Panda3DS";
|
||||
info->block_extract = true;
|
||||
}
|
||||
|
||||
void retro_get_system_av_info(retro_system_av_info* info) {
|
||||
info->geometry.base_width = emulator->width;
|
||||
info->geometry.base_height = emulator->height;
|
||||
|
||||
info->geometry.max_width = info->geometry.base_width;
|
||||
info->geometry.max_height = info->geometry.base_height;
|
||||
|
||||
info->geometry.aspect_ratio = float(5.0 / 6.0);
|
||||
info->timing.fps = 60.0;
|
||||
info->timing.sample_rate = 32768;
|
||||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t cb) {
|
||||
envCallbacks = cb;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t cb) {
|
||||
videoCallbacks = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) {
|
||||
audioBatchCallback = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t cb) {}
|
||||
|
||||
void retro_set_input_poll(retro_input_poll_t cb) {
|
||||
inputPollCallback = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_state(retro_input_state_t cb) {
|
||||
inputStateCallback = cb;
|
||||
}
|
||||
|
||||
void retro_init() {
|
||||
enum retro_pixel_format xrgb888 = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &xrgb888);
|
||||
|
||||
char* save_dir = nullptr;
|
||||
|
||||
if (!envCallbacks(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_dir) || save_dir == nullptr) {
|
||||
Helpers::warn("No save directory provided by LibRetro.");
|
||||
savePath = std::filesystem::current_path();
|
||||
} else {
|
||||
savePath = std::filesystem::path(save_dir);
|
||||
}
|
||||
|
||||
emulator = std::make_unique<Emulator>();
|
||||
}
|
||||
|
||||
void retro_deinit() {
|
||||
emulator = nullptr;
|
||||
}
|
||||
|
||||
bool retro_load_game(const retro_game_info* game) {
|
||||
configInit();
|
||||
configUpdate();
|
||||
|
||||
if (emulator->getRendererType() != RendererType::OpenGL) {
|
||||
Helpers::panic("Libretro: Renderer is not OpenGL");
|
||||
}
|
||||
|
||||
renderer = static_cast<RendererGL*>(emulator->getRenderer());
|
||||
emulator->setOutputSize(emulator->width, emulator->height);
|
||||
|
||||
inputInit();
|
||||
videoInit();
|
||||
|
||||
return emulator->loadROM(game->path);
|
||||
}
|
||||
|
||||
bool retro_load_game_special(uint type, const retro_game_info* info, usize num) { return false; }
|
||||
|
||||
void retro_unload_game() {
|
||||
renderer->setFBO(0);
|
||||
renderer = nullptr;
|
||||
}
|
||||
|
||||
void retro_reset() {
|
||||
emulator->reset(Emulator::ReloadOption::Reload);
|
||||
}
|
||||
|
||||
void retro_run() {
|
||||
ConfigCheckVariables();
|
||||
|
||||
renderer->setFBO(hw_render.get_current_framebuffer());
|
||||
renderer->resetStateManager();
|
||||
|
||||
inputPollCallback();
|
||||
|
||||
HIDService& hid = emulator->getServiceManager().getHID();
|
||||
|
||||
hid.setKey(HID::Keys::A, GetButtonState(RETRO_DEVICE_ID_JOYPAD_A));
|
||||
hid.setKey(HID::Keys::B, GetButtonState(RETRO_DEVICE_ID_JOYPAD_B));
|
||||
hid.setKey(HID::Keys::X, GetButtonState(RETRO_DEVICE_ID_JOYPAD_X));
|
||||
hid.setKey(HID::Keys::Y, GetButtonState(RETRO_DEVICE_ID_JOYPAD_Y));
|
||||
hid.setKey(HID::Keys::L, GetButtonState(RETRO_DEVICE_ID_JOYPAD_L));
|
||||
hid.setKey(HID::Keys::R, GetButtonState(RETRO_DEVICE_ID_JOYPAD_R));
|
||||
hid.setKey(HID::Keys::Start, GetButtonState(RETRO_DEVICE_ID_JOYPAD_START));
|
||||
hid.setKey(HID::Keys::Select, GetButtonState(RETRO_DEVICE_ID_JOYPAD_SELECT));
|
||||
hid.setKey(HID::Keys::Up, GetButtonState(RETRO_DEVICE_ID_JOYPAD_UP));
|
||||
hid.setKey(HID::Keys::Down, GetButtonState(RETRO_DEVICE_ID_JOYPAD_DOWN));
|
||||
hid.setKey(HID::Keys::Left, GetButtonState(RETRO_DEVICE_ID_JOYPAD_LEFT));
|
||||
hid.setKey(HID::Keys::Right, GetButtonState(RETRO_DEVICE_ID_JOYPAD_RIGHT));
|
||||
|
||||
// Get analog values for the left analog stick (Right analog stick is N3DS-only and unimplemented)
|
||||
float xLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
||||
float yLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
|
||||
|
||||
hid.setCirclepadX((xLeft / +32767) * 0x9C);
|
||||
hid.setCirclepadY((yLeft / -32767) * 0x9C);
|
||||
|
||||
bool touch = inputStateCallback(0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT);
|
||||
const int posX = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X);
|
||||
const int posY = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y);
|
||||
|
||||
const int newX = static_cast<int>((posX + 0x7fff) / (float)(0x7fff * 2) * emulator->width);
|
||||
const int newY = static_cast<int>((posY + 0x7fff) / (float)(0x7fff * 2) * emulator->height);
|
||||
|
||||
const int offsetX = 40;
|
||||
const int offsetY = emulator->height / 2;
|
||||
|
||||
const bool inScreenX = newX >= offsetX && newX < emulator->width - offsetX;
|
||||
const bool inScreenY = newY >= offsetY && newY <= emulator->height;
|
||||
|
||||
if (touch && inScreenX && inScreenY) {
|
||||
u16 x = static_cast<u16>(newX - offsetX);
|
||||
u16 y = static_cast<u16>(newY - offsetY);
|
||||
|
||||
hid.setTouchScreenPress(x, y);
|
||||
} else {
|
||||
hid.releaseTouchScreen();
|
||||
}
|
||||
|
||||
hid.updateInputs(emulator->getTicks());
|
||||
emulator->runFrame();
|
||||
|
||||
videoCallbacks(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0);
|
||||
}
|
||||
|
||||
void retro_set_controller_port_device(uint port, uint device) {}
|
||||
|
||||
usize retro_serialize_size() {
|
||||
usize size = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
bool retro_serialize(void* data, usize size) { return false; }
|
||||
bool retro_unserialize(const void* data, usize size) { return false; }
|
||||
|
||||
uint retro_get_region() { return RETRO_REGION_NTSC; }
|
||||
uint retro_api_version() { return RETRO_API_VERSION; }
|
||||
|
||||
usize retro_get_memory_size(uint id) {
|
||||
if (id == RETRO_MEMORY_SYSTEM_RAM) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* retro_get_memory_data(uint id) {
|
||||
if (id == RETRO_MEMORY_SYSTEM_RAM) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void retro_cheat_set(uint index, bool enabled, const char* code) {}
|
||||
void retro_cheat_reset() {}
|
25
src/panda_qt/elided_label.cpp
Normal file
25
src/panda_qt/elided_label.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "panda_qt/elided_label.hpp"
|
||||
|
||||
// Based on https://stackoverflow.com/questions/7381100/text-overflow-for-a-qlabel-s-text-rendering-in-qt
|
||||
ElidedLabel::ElidedLabel(Qt::TextElideMode elideMode, QWidget* parent) : ElidedLabel("", elideMode, parent) {}
|
||||
|
||||
ElidedLabel::ElidedLabel(QString text, Qt::TextElideMode elideMode, QWidget* parent) : QLabel(parent) {
|
||||
m_elideMode = elideMode;
|
||||
setText(text);
|
||||
}
|
||||
|
||||
void ElidedLabel::setText(QString text) {
|
||||
m_text = text;
|
||||
updateText();
|
||||
}
|
||||
|
||||
void ElidedLabel::resizeEvent(QResizeEvent* event) {
|
||||
QLabel::resizeEvent(event);
|
||||
updateText();
|
||||
}
|
||||
|
||||
void ElidedLabel::updateText() {
|
||||
QFontMetrics metrics(font());
|
||||
QString elided = metrics.elidedText(m_text, m_elideMode, width());
|
||||
QLabel::setText(elided);
|
||||
}
|
|
@ -54,11 +54,15 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS"));
|
||||
auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
|
||||
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
|
||||
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
|
||||
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
|
||||
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
|
||||
|
||||
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
||||
connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
|
||||
connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor);
|
||||
connect(luaEditorAction, &QAction::triggered, this, [this]() { luaEditor->show(); });
|
||||
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
|
||||
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
|
||||
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
|
||||
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
|
||||
|
||||
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
||||
|
@ -71,7 +75,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
aboutWindow = new AboutWindow(nullptr);
|
||||
configWindow = new ConfigWindow(this);
|
||||
cheatsEditor = new CheatsWindow(emu, {}, this);
|
||||
patchWindow = new PatchWindow(this);
|
||||
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
||||
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
|
||||
|
||||
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
|
||||
if (shaderEditor->supported) {
|
||||
shaderEditor->setText(emu->getRenderer()->getUbershader());
|
||||
}
|
||||
|
||||
auto args = QCoreApplication::arguments();
|
||||
if (args.size() > 1) {
|
||||
|
@ -291,9 +302,6 @@ void MainWindow::showAboutMenu() {
|
|||
about.exec();
|
||||
}
|
||||
|
||||
void MainWindow::openLuaEditor() { luaEditor->show(); }
|
||||
void MainWindow::openCheatsEditor() { cheatsEditor->show(); }
|
||||
|
||||
void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
||||
switch (message.type) {
|
||||
case MessageType::LoadROM:
|
||||
|
@ -347,6 +355,11 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
|||
emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y);
|
||||
break;
|
||||
case MessageType::ReleaseTouchscreen: emu->getServiceManager().getHID().releaseTouchScreen(); break;
|
||||
|
||||
case MessageType::ReloadUbershader:
|
||||
emu->getRenderer()->setUbershader(*message.string.str);
|
||||
delete message.string.str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,6 +462,14 @@ void MainWindow::loadLuaScript(const std::string& code) {
|
|||
sendMessage(message);
|
||||
}
|
||||
|
||||
void MainWindow::reloadShader(const std::string& shader) {
|
||||
EmulatorMessage message{.type = MessageType::ReloadUbershader};
|
||||
|
||||
// Make a copy of the code on the heap to send via the message queue
|
||||
message.string.str = new std::string(shader);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
void MainWindow::editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback) {
|
||||
EmulatorMessage message{.type = MessageType::EditCheat};
|
||||
|
||||
|
|
158
src/panda_qt/patch_window.cpp
Normal file
158
src/panda_qt/patch_window.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#include "panda_qt/patch_window.hpp"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <memory>
|
||||
|
||||
#include "hips.hpp"
|
||||
#include "io_file.hpp"
|
||||
|
||||
PatchWindow::PatchWindow(QWidget* parent) : QWidget(parent, Qt::Window) {
|
||||
QVBoxLayout* layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(6, 6, 6, 6);
|
||||
setLayout(layout);
|
||||
|
||||
QWidget* inputBox = new QWidget;
|
||||
QHBoxLayout* inputLayout = new QHBoxLayout;
|
||||
QLabel* inputText = new QLabel(tr("Select input file"));
|
||||
QPushButton* inputButton = new QPushButton(tr("Select"));
|
||||
inputPathLabel = new ElidedLabel("");
|
||||
inputPathLabel->setFixedWidth(200);
|
||||
|
||||
inputLayout->addWidget(inputText);
|
||||
inputLayout->addWidget(inputButton);
|
||||
inputLayout->addWidget(inputPathLabel);
|
||||
inputBox->setLayout(inputLayout);
|
||||
|
||||
QWidget* patchBox = new QWidget;
|
||||
QHBoxLayout* patchLayout = new QHBoxLayout;
|
||||
QLabel* patchText = new QLabel(tr("Select patch file"));
|
||||
QPushButton* patchButton = new QPushButton(tr("Select"));
|
||||
patchPathLabel = new ElidedLabel("");
|
||||
patchPathLabel->setFixedWidth(200);
|
||||
|
||||
patchLayout->addWidget(patchText);
|
||||
patchLayout->addWidget(patchButton);
|
||||
patchLayout->addWidget(patchPathLabel);
|
||||
patchBox->setLayout(patchLayout);
|
||||
|
||||
QWidget* actionBox = new QWidget;
|
||||
QHBoxLayout* actionLayout = new QHBoxLayout;
|
||||
QPushButton* applyPatchButton = new QPushButton(tr("Apply patch"));
|
||||
actionLayout->addWidget(applyPatchButton);
|
||||
actionBox->setLayout(actionLayout);
|
||||
|
||||
layout->addWidget(inputBox);
|
||||
layout->addWidget(patchBox);
|
||||
layout->addWidget(actionBox);
|
||||
|
||||
connect(inputButton, &QPushButton::clicked, this, [this]() {
|
||||
auto path = QFileDialog::getOpenFileName(this, tr("Select file to patch"), "", tr("All files (*.*)"));
|
||||
inputPath = std::filesystem::path(path.toStdU16String());
|
||||
|
||||
inputPathLabel->setText(path);
|
||||
});
|
||||
|
||||
connect(patchButton, &QPushButton::clicked, this, [this]() {
|
||||
auto path = QFileDialog::getOpenFileName(this, tr("Select patch file"), "", tr("Patch files (*.ips *.ups *.bps)"));
|
||||
patchPath = std::filesystem::path(path.toStdU16String());
|
||||
|
||||
patchPathLabel->setText(path);
|
||||
});
|
||||
|
||||
connect(applyPatchButton, &QPushButton::clicked, this, [this]() {
|
||||
if (inputPath.empty() || patchPath.empty()) {
|
||||
displayMessage(tr("Paths not provided correctly"), tr("Please provide paths for both the input file and the patch file"));
|
||||
return;
|
||||
}
|
||||
|
||||
// std::filesystem::path only has += and not + for reasons unknown to humanity
|
||||
auto defaultPath = inputPath.parent_path() / inputPath.stem();
|
||||
defaultPath += "-patched";
|
||||
defaultPath += inputPath.extension();
|
||||
|
||||
auto path = QFileDialog::getSaveFileName(this, tr("Select file"), QString::fromStdU16String(defaultPath.u16string()), tr("All files (*.*)"));
|
||||
std::filesystem::path outputPath = std::filesystem::path(path.toStdU16String());
|
||||
|
||||
if (outputPath.empty()) {
|
||||
displayMessage(tr("No output path"), tr("No path was provided for the output file, no patching was done"));
|
||||
return;
|
||||
}
|
||||
|
||||
Hips::PatchType patchType;
|
||||
auto extension = patchPath.extension();
|
||||
|
||||
// Figure out what sort of patch we're dealing with
|
||||
if (extension == ".ips") {
|
||||
patchType = Hips::PatchType::IPS;
|
||||
} else if (extension == ".ups") {
|
||||
patchType = Hips::PatchType::UPS;
|
||||
} else if (extension == ".bps") {
|
||||
patchType = Hips::PatchType::BPS;
|
||||
} else {
|
||||
displayMessage(tr("Unknown patch format"), tr("Unknown format for patch file. Currently IPS, UPS and BPS are supported"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read input and patch files into buffers
|
||||
IOFile input(inputPath, "rb");
|
||||
IOFile patch(patchPath, "rb");
|
||||
|
||||
if (!input.isOpen() || !patch.isOpen()) {
|
||||
displayMessage(tr("Failed to open input files"), tr("Make sure they're in a directory Panda3DS has access to"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the files into arrays
|
||||
const auto inputSize = *input.size();
|
||||
const auto patchSize = *patch.size();
|
||||
|
||||
std::unique_ptr<uint8_t[]> inputData(new uint8_t[inputSize]);
|
||||
std::unique_ptr<uint8_t[]> patchData(new uint8_t[patchSize]);
|
||||
|
||||
input.rewind();
|
||||
patch.rewind();
|
||||
input.readBytes(inputData.get(), inputSize);
|
||||
patch.readBytes(patchData.get(), patchSize);
|
||||
|
||||
auto [bytes, result] = Hips::patch(inputData.get(), inputSize, patchData.get(), patchSize, patchType);
|
||||
|
||||
// Write patched file
|
||||
if (!bytes.empty()) {
|
||||
IOFile output(outputPath, "wb");
|
||||
output.writeBytes(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case Hips::Result::Success:
|
||||
displayMessage(
|
||||
tr("Patching Success"), tr("Your file was patched successfully."), QMessageBox::Icon::Information, ":/docs/img/rpog_icon.png"
|
||||
);
|
||||
break;
|
||||
|
||||
case Hips::Result::ChecksumMismatch:
|
||||
displayMessage(
|
||||
tr("Checksum mismatch"),
|
||||
tr("Patch was applied successfully but a checksum mismatch was detected. The input or output files might not be correct")
|
||||
);
|
||||
break;
|
||||
|
||||
default: displayMessage(tr("Patching error"), tr("An error occured while patching")); break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PatchWindow::PatchWindow::displayMessage(const QString& title, const QString& message, QMessageBox::Icon icon, const char* iconPath) {
|
||||
QMessageBox messageBox(icon, title, message);
|
||||
QAbstractButton* button = messageBox.addButton(tr("OK"), QMessageBox::ButtonRole::YesRole);
|
||||
|
||||
if (iconPath != nullptr) {
|
||||
button->setIcon(QIcon(iconPath));
|
||||
}
|
||||
|
||||
messageBox.exec();
|
||||
}
|
54
src/panda_qt/shader_editor.cpp
Normal file
54
src/panda_qt/shader_editor.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "panda_qt/main_window.hpp"
|
||||
#include "panda_qt/shader_editor.hpp"
|
||||
|
||||
using namespace Zep;
|
||||
|
||||
ShaderEditorWindow::ShaderEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText)
|
||||
: QDialog(parent), zepWidget(this, qApp->applicationDirPath().toStdString(), fontSize) {
|
||||
resize(600, 600);
|
||||
|
||||
// Register our extensions
|
||||
ZepRegressExCommand::Register(zepWidget.GetEditor());
|
||||
ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider);
|
||||
|
||||
// Default to standard mode instead of vim mode, initialize text box
|
||||
zepWidget.GetEditor().InitWithText(filename, initialText);
|
||||
zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName());
|
||||
|
||||
// Layout for widgets
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout();
|
||||
setLayout(mainLayout);
|
||||
|
||||
QPushButton* button = new QPushButton(tr("Reload shader"), this);
|
||||
button->setFixedSize(100, 20);
|
||||
|
||||
// When the Load Script button is pressed, send the current text to the MainWindow, which will upload it to the emulator's lua object
|
||||
connect(button, &QPushButton::pressed, this, [this]() {
|
||||
if (parentWidget()) {
|
||||
auto buffer = zepWidget.GetEditor().GetMRUBuffer();
|
||||
const std::string text = buffer->GetBufferText(buffer->Begin(), buffer->End());
|
||||
|
||||
static_cast<MainWindow*>(parentWidget())->reloadShader(text);
|
||||
} else {
|
||||
// This should be unreachable, only here for safety purposes
|
||||
printf("Text editor does not have any parent widget, click doesn't work :(\n");
|
||||
}
|
||||
});
|
||||
|
||||
mainLayout->addWidget(button);
|
||||
mainLayout->addWidget(&zepWidget);
|
||||
}
|
||||
|
||||
void ShaderEditorWindow::setEnable(bool enable) {
|
||||
supported = enable;
|
||||
|
||||
if (enable) {
|
||||
setDisabled(false);
|
||||
} else {
|
||||
setDisabled(true);
|
||||
setText("Shader editor window is not available for this renderer backend");
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ public class GlobalConfig {
|
|||
|
||||
public static DataModel data;
|
||||
|
||||
public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", false);
|
||||
public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", true);
|
||||
public static final Key<Boolean> KEY_PICTURE_IN_PICTURE = new Key<>("app.behavior.pictureInPicture", false);
|
||||
public static final Key<Boolean> KEY_SHOW_PERFORMANCE_OVERLAY = new Key<>("dev.performanceOverlay", false);
|
||||
public static final Key<Boolean> KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue