System: Implement PINE server
This commit is contained in:
parent
4e905a63ec
commit
4311e08726
|
@ -91,6 +91,8 @@ add_library(core
|
||||||
pad.h
|
pad.h
|
||||||
pcdrv.cpp
|
pcdrv.cpp
|
||||||
pcdrv.h
|
pcdrv.h
|
||||||
|
pine_server.cpp
|
||||||
|
pine_server.h
|
||||||
playstation_mouse.cpp
|
playstation_mouse.cpp
|
||||||
playstation_mouse.h
|
playstation_mouse.h
|
||||||
psf_loader.cpp
|
psf_loader.cpp
|
||||||
|
|
|
@ -74,6 +74,7 @@
|
||||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="cpu_pgxp.cpp" />
|
<ClCompile Include="cpu_pgxp.cpp" />
|
||||||
|
<ClCompile Include="pine_server.cpp" />
|
||||||
<ClCompile Include="playstation_mouse.cpp" />
|
<ClCompile Include="playstation_mouse.cpp" />
|
||||||
<ClCompile Include="psf_loader.cpp" />
|
<ClCompile Include="psf_loader.cpp" />
|
||||||
<ClCompile Include="resources.cpp" />
|
<ClCompile Include="resources.cpp" />
|
||||||
|
@ -152,6 +153,7 @@
|
||||||
<ClInclude Include="pcdrv.h" />
|
<ClInclude Include="pcdrv.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="cpu_pgxp.h" />
|
<ClInclude Include="cpu_pgxp.h" />
|
||||||
|
<ClInclude Include="pine_server.h" />
|
||||||
<ClInclude Include="playstation_mouse.h" />
|
<ClInclude Include="playstation_mouse.h" />
|
||||||
<ClInclude Include="psf_loader.h" />
|
<ClInclude Include="psf_loader.h" />
|
||||||
<ClInclude Include="resources.h" />
|
<ClInclude Include="resources.h" />
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
<ClCompile Include="cpu_newrec_compiler_riscv64.cpp" />
|
<ClCompile Include="cpu_newrec_compiler_riscv64.cpp" />
|
||||||
<ClCompile Include="cpu_newrec_compiler_aarch32.cpp" />
|
<ClCompile Include="cpu_newrec_compiler_aarch32.cpp" />
|
||||||
<ClCompile Include="justifier.cpp" />
|
<ClCompile Include="justifier.cpp" />
|
||||||
|
<ClCompile Include="pine_server.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
|
@ -140,5 +141,6 @@
|
||||||
<ClInclude Include="cpu_newrec_compiler_aarch32.h" />
|
<ClInclude Include="cpu_newrec_compiler_aarch32.h" />
|
||||||
<ClInclude Include="achievements_private.h" />
|
<ClInclude Include="achievements_private.h" />
|
||||||
<ClInclude Include="justifier.h" />
|
<ClInclude Include="justifier.h" />
|
||||||
|
<ClInclude Include="pine_server.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -0,0 +1,555 @@
|
||||||
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team, Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
|
#include "pine_server.h"
|
||||||
|
#include "cpu_core.h"
|
||||||
|
#include "host.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "system.h"
|
||||||
|
|
||||||
|
#include "scmversion/scmversion.h"
|
||||||
|
|
||||||
|
#include "util/platform_misc.h"
|
||||||
|
#include "util/sockets.h"
|
||||||
|
|
||||||
|
#include "common/binary_span_reader_writer.h"
|
||||||
|
#include "common/error.h"
|
||||||
|
#include "common/file_system.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/path.h"
|
||||||
|
#include "common/small_string.h"
|
||||||
|
|
||||||
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
Log_SetChannel(PINEServer);
|
||||||
|
|
||||||
|
namespace PINEServer {
|
||||||
|
static std::shared_ptr<ListenSocket> s_listen_socket;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
static std::string s_socket_path;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum memory used by an IPC message request.
|
||||||
|
* Equivalent to 50,000 Write64 requests.
|
||||||
|
*/
|
||||||
|
static constexpr u32 MAX_IPC_SIZE = 650000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum memory used by an IPC message reply.
|
||||||
|
* Equivalent to 50,000 Read64 replies.
|
||||||
|
*/
|
||||||
|
static constexpr u32 MAX_IPC_RETURN_SIZE = 450000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC Command messages opcodes.
|
||||||
|
* A list of possible operations possible by the IPC.
|
||||||
|
* Each one of them is what we call an "opcode" and is the first
|
||||||
|
* byte sent by the IPC to differentiate between commands.
|
||||||
|
*/
|
||||||
|
enum IPCCommand : u8
|
||||||
|
{
|
||||||
|
MsgRead8 = 0, /**< Read 8 bit value to memory. */
|
||||||
|
MsgRead16 = 1, /**< Read 16 bit value to memory. */
|
||||||
|
MsgRead32 = 2, /**< Read 32 bit value to memory. */
|
||||||
|
MsgRead64 = 3, /**< Read 64 bit value to memory. */
|
||||||
|
MsgWrite8 = 4, /**< Write 8 bit value to memory. */
|
||||||
|
MsgWrite16 = 5, /**< Write 16 bit value to memory. */
|
||||||
|
MsgWrite32 = 6, /**< Write 32 bit value to memory. */
|
||||||
|
MsgWrite64 = 7, /**< Write 64 bit value to memory. */
|
||||||
|
MsgVersion = 8, /**< Returns PCSX2 version. */
|
||||||
|
MsgSaveState = 9, /**< Saves a savestate. */
|
||||||
|
MsgLoadState = 0xA, /**< Loads a savestate. */
|
||||||
|
MsgTitle = 0xB, /**< Returns the game title. */
|
||||||
|
MsgID = 0xC, /**< Returns the game ID. */
|
||||||
|
MsgUUID = 0xD, /**< Returns the game UUID. */
|
||||||
|
MsgGameVersion = 0xE, /**< Returns the game verion. */
|
||||||
|
MsgStatus = 0xF, /**< Returns the emulator status. */
|
||||||
|
MsgUnimplemented = 0xFF /**< Unimplemented IPC message. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulator status enum.
|
||||||
|
* A list of possible emulator statuses.
|
||||||
|
*/
|
||||||
|
enum class EmuStatus : u32
|
||||||
|
{
|
||||||
|
Running = 0, /**< Game is running */
|
||||||
|
Paused = 1, /**< Game is paused */
|
||||||
|
Shutdown = 2 /**< Game is shutdown */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC result codes.
|
||||||
|
* A list of possible result codes the IPC can send back.
|
||||||
|
* Each one of them is what we call an "opcode" or "tag" and is the
|
||||||
|
* first byte sent by the IPC to differentiate between results.
|
||||||
|
*/
|
||||||
|
using IPCStatus = u8;
|
||||||
|
static constexpr IPCStatus IPC_OK = 0; /**< IPC command successfully completed. */
|
||||||
|
static constexpr IPCStatus IPC_FAIL = 0xFF; /**< IPC command failed to complete. */
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class PINESocket final : public BufferedStreamSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PINESocket(SocketMultiplexer& multiplexer, SocketDescriptor descriptor);
|
||||||
|
~PINESocket() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnConnected() override;
|
||||||
|
void OnDisconnected(const Error& error) override;
|
||||||
|
void OnRead() override;
|
||||||
|
void OnWrite() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ProcessCommandsInBuffer();
|
||||||
|
bool HandleCommand(IPCCommand command, BinarySpanReader rdbuf);
|
||||||
|
|
||||||
|
bool BeginReply(BinarySpanWriter& wrbuf, size_t required_bytes);
|
||||||
|
bool EndReply(const BinarySpanWriter& sw);
|
||||||
|
|
||||||
|
bool SendErrorReply();
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
} // namespace PINEServer
|
||||||
|
|
||||||
|
bool PINEServer::IsRunning()
|
||||||
|
{
|
||||||
|
return static_cast<bool>(s_listen_socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PINEServer::Initialize(u16 slot)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
std::optional<SocketAddress> address;
|
||||||
|
#ifdef _WIN32
|
||||||
|
address = SocketAddress::Parse(SocketAddress::Type::IPv4, "127.0.0.1", slot, &error);
|
||||||
|
#else
|
||||||
|
#ifdef __APPLE__
|
||||||
|
const char* runtime_dir = std::getenv("TMPDIR");
|
||||||
|
#else
|
||||||
|
const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR");
|
||||||
|
#endif
|
||||||
|
// fallback in case macOS or other OSes don't implement the XDG base spec
|
||||||
|
runtime_dir = runtime_dir ? runtime_dir : "/tmp";
|
||||||
|
|
||||||
|
std::string socket_path;
|
||||||
|
if (slot != Settings::DEFAULT_PINE_SLOT)
|
||||||
|
socket_path = fmt::format("{}/duckstation.sock.{}", runtime_dir, slot);
|
||||||
|
else
|
||||||
|
socket_path = fmt::format("{}/duckstation.sock", runtime_dir);
|
||||||
|
|
||||||
|
// we unlink the socket so that when releasing this thread the socket gets
|
||||||
|
// freed even if we didn't close correctly the loop
|
||||||
|
FileSystem::DeleteFile(socket_path.c_str());
|
||||||
|
|
||||||
|
address = SocketAddress::Parse(SocketAddress::Type::Unix, socket_path.c_str(), 0, &error);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!address.has_value())
|
||||||
|
{
|
||||||
|
ERROR_LOG("PINE: Failed to resolve listen address: {}", error.GetDescription());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketMultiplexer* multiplexer = System::GetSocketMultiplexer();
|
||||||
|
if (!multiplexer)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
s_listen_socket = multiplexer->CreateListenSocket<PINESocket>(address.value(), &error);
|
||||||
|
if (!s_listen_socket)
|
||||||
|
{
|
||||||
|
ERROR_LOG("PINE: Failed to create listen socket: {}", error.GetDescription());
|
||||||
|
System::ReleaseSocketMultiplexer();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
s_socket_path = std::move(socket_path);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PINEServer::Shutdown()
|
||||||
|
{
|
||||||
|
// also closes the listener
|
||||||
|
if (s_listen_socket)
|
||||||
|
{
|
||||||
|
s_listen_socket.reset();
|
||||||
|
System::ReleaseSocketMultiplexer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlink the socket so nobody tries to connect to something no longer existent
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (!s_socket_path.empty())
|
||||||
|
{
|
||||||
|
FileSystem::DeleteFile(s_socket_path.c_str());
|
||||||
|
s_socket_path = {};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
PINEServer::PINESocket::PINESocket(SocketMultiplexer& multiplexer, SocketDescriptor descriptor)
|
||||||
|
: BufferedStreamSocket(multiplexer, descriptor, MAX_IPC_SIZE, MAX_IPC_RETURN_SIZE)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PINEServer::PINESocket::~PINESocket() = default;
|
||||||
|
|
||||||
|
void PINEServer::PINESocket::OnConnected()
|
||||||
|
{
|
||||||
|
INFO_LOG("PINE: New client at {} connected.", GetRemoteAddress().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PINEServer::PINESocket::OnDisconnected(const Error& error)
|
||||||
|
{
|
||||||
|
INFO_LOG("PINE: Client {} disconnected: {}", GetRemoteAddress().ToString(), error.GetDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PINEServer::PINESocket::OnRead()
|
||||||
|
{
|
||||||
|
ProcessCommandsInBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PINEServer::PINESocket::OnWrite()
|
||||||
|
{
|
||||||
|
ProcessCommandsInBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PINEServer::PINESocket::ProcessCommandsInBuffer()
|
||||||
|
{
|
||||||
|
std::span<const u8> rdbuf = AcquireReadBuffer();
|
||||||
|
if (rdbuf.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t position = 0;
|
||||||
|
size_t remaining = rdbuf.size();
|
||||||
|
while (remaining >= sizeof(u32))
|
||||||
|
{
|
||||||
|
u32 packet_size;
|
||||||
|
std::memcpy(&packet_size, &rdbuf[position], sizeof(u32));
|
||||||
|
if (packet_size > MAX_IPC_SIZE || packet_size < 5)
|
||||||
|
{
|
||||||
|
ERROR_LOG("PINE: Received invalid packet size {}", packet_size);
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// whole thing received yet yet?
|
||||||
|
if (packet_size > remaining)
|
||||||
|
break;
|
||||||
|
|
||||||
|
const IPCCommand command = static_cast<IPCCommand>(rdbuf[position + sizeof(u32)]);
|
||||||
|
if (!HandleCommand(command, BinarySpanReader(rdbuf.subspan(position + sizeof(u32) + sizeof(u8),
|
||||||
|
packet_size - sizeof(u32) - sizeof(u8)))))
|
||||||
|
{
|
||||||
|
// Out of write buffer space, abort.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
position += packet_size;
|
||||||
|
remaining -= packet_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseReadBuffer(position);
|
||||||
|
ReleaseWriteBuffer(0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PINEServer::PINESocket::HandleCommand(IPCCommand command, BinarySpanReader rdbuf)
|
||||||
|
{
|
||||||
|
// example IPC messages: MsgRead/Write
|
||||||
|
// refer to the client doc for more info on the format
|
||||||
|
// IPC Message event (1 byte)
|
||||||
|
// | Memory address (4 byte)
|
||||||
|
// | | argument (VLE)
|
||||||
|
// | | |
|
||||||
|
// format: XX YY YY YY YY ZZ ZZ ZZ ZZ
|
||||||
|
// reply code: 00 = OK, FF = NOT OK
|
||||||
|
// | return value (VLE)
|
||||||
|
// | |
|
||||||
|
// reply: XX ZZ ZZ ZZ ZZ
|
||||||
|
|
||||||
|
BinarySpanWriter reply;
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case MsgRead8:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, sizeof(u8))) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
u8 res = 0;
|
||||||
|
reply << (CPU::SafeReadMemoryByte(addr, &res) ? IPC_OK : IPC_FAIL);
|
||||||
|
reply << res;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgRead16:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, sizeof(u16))) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
u16 res = 0;
|
||||||
|
reply << (CPU::SafeReadMemoryHalfWord(addr, &res) ? IPC_OK : IPC_FAIL);
|
||||||
|
reply << res;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgRead32:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, sizeof(u32))) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
u32 res = 0;
|
||||||
|
reply << (CPU::SafeReadMemoryWord(addr, &res) ? IPC_OK : IPC_FAIL);
|
||||||
|
reply << res;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgRead64:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, sizeof(u64))) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
u32 res_low = 0, res_high = 0;
|
||||||
|
reply << ((!CPU::SafeReadMemoryWord(addr, &res_low) || !CPU::SafeReadMemoryWord(addr + sizeof(u32), &res_high)) ?
|
||||||
|
IPC_FAIL :
|
||||||
|
IPC_OK);
|
||||||
|
reply << ((ZeroExtend64(res_high) << 32) | ZeroExtend64(res_low));
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgWrite8:
|
||||||
|
{
|
||||||
|
// Don't do the actual write until we have space for the response, otherwise we might do it twice when we come
|
||||||
|
// back around.
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u8)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, 0)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
const u8 value = rdbuf.ReadU8();
|
||||||
|
reply << (CPU::SafeWriteMemoryByte(addr, value) ? IPC_OK : IPC_FAIL);
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgWrite16:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u16)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, 0)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
const u16 value = rdbuf.ReadU16();
|
||||||
|
reply << (CPU::SafeWriteMemoryHalfWord(addr, value) ? IPC_OK : IPC_FAIL);
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgWrite32:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u32)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, 0)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
const u32 value = rdbuf.ReadU32();
|
||||||
|
reply << (CPU::SafeWriteMemoryWord(addr, value) ? IPC_OK : IPC_FAIL);
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgWrite64:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(PhysicalMemoryAddress) + sizeof(u32)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
else if (!BeginReply(reply, 0)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress addr = rdbuf.ReadU32();
|
||||||
|
const u64 value = rdbuf.ReadU64();
|
||||||
|
reply << ((!CPU::SafeWriteMemoryWord(addr, Truncate32(value)) ||
|
||||||
|
!CPU::SafeWriteMemoryWord(addr + sizeof(u32), Truncate32(value >> 32))) ?
|
||||||
|
IPC_FAIL :
|
||||||
|
IPC_OK);
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgVersion:
|
||||||
|
{
|
||||||
|
const TinyString version = TinyString::from_format("DuckStation {}", g_scm_tag_str);
|
||||||
|
if (!BeginReply(reply, version.length() + 1)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reply << IPC_OK << version;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgSaveState:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(u8)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
const std::string& serial = System::GetGameSerial();
|
||||||
|
if (!serial.empty())
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
if (!BeginReply(reply, 0)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string state_filename = System::GetGameSaveStateFileName(serial, rdbuf.ReadU8());
|
||||||
|
Host::RunOnCPUThread([state_filename = std::move(state_filename)] {
|
||||||
|
Error error;
|
||||||
|
if (!System::SaveState(state_filename.c_str(), &error, false))
|
||||||
|
ERROR_LOG("PINE: Save state failed: {}", error.GetDescription());
|
||||||
|
});
|
||||||
|
|
||||||
|
reply << IPC_OK;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgLoadState:
|
||||||
|
{
|
||||||
|
if (!rdbuf.CheckRemaining(sizeof(u8)) || !System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
const std::string& serial = System::GetGameSerial();
|
||||||
|
if (!serial.empty())
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
std::string state_filename = System::GetGameSaveStateFileName(serial, rdbuf.ReadU8());
|
||||||
|
if (!FileSystem::FileExists(state_filename.c_str()))
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
if (!BeginReply(reply, 0)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Host::RunOnCPUThread([state_filename = std::move(state_filename)] {
|
||||||
|
Error error;
|
||||||
|
if (!System::LoadState(state_filename.c_str(), &error))
|
||||||
|
ERROR_LOG("PINE: Load state failed: {}", error.GetDescription());
|
||||||
|
});
|
||||||
|
|
||||||
|
reply << IPC_OK;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgTitle:
|
||||||
|
{
|
||||||
|
if (!System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
const std::string& name = System::GetGameTitle();
|
||||||
|
if (!BeginReply(reply, name.length() + 1)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reply << IPC_OK << name;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgID:
|
||||||
|
{
|
||||||
|
if (!System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
const std::string& serial = System::GetGameSerial();
|
||||||
|
if (!BeginReply(reply, serial.length() + 1)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reply << IPC_OK << serial;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgUUID:
|
||||||
|
{
|
||||||
|
if (!System::IsValid())
|
||||||
|
return SendErrorReply();
|
||||||
|
|
||||||
|
const TinyString crc = TinyString::from_format("{:016x}", System::GetGameHash());
|
||||||
|
if (!BeginReply(reply, crc.length() + 1)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reply << IPC_OK << crc;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgGameVersion:
|
||||||
|
{
|
||||||
|
ERROR_LOG("PINE: MsgGameVersion not supported.");
|
||||||
|
return SendErrorReply();
|
||||||
|
}
|
||||||
|
|
||||||
|
case MsgStatus:
|
||||||
|
{
|
||||||
|
EmuStatus status;
|
||||||
|
switch (System::GetState())
|
||||||
|
{
|
||||||
|
case System::State::Running:
|
||||||
|
status = EmuStatus::Running;
|
||||||
|
break;
|
||||||
|
case System::State::Paused:
|
||||||
|
status = EmuStatus::Paused;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
status = EmuStatus::Shutdown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!BeginReply(reply, sizeof(u32))) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reply << IPC_OK << static_cast<u32>(status);
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ERROR_LOG("PINE: Unhandled IPC command {:02X}", static_cast<u8>(command));
|
||||||
|
return SendErrorReply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PINEServer::PINESocket::BeginReply(BinarySpanWriter& wrbuf, size_t required_bytes)
|
||||||
|
{
|
||||||
|
wrbuf = (AcquireWriteBuffer(sizeof(u32) + sizeof(IPCStatus) + required_bytes, false));
|
||||||
|
if (!wrbuf.IsValid()) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wrbuf << static_cast<u32>(0); // size placeholder
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PINEServer::PINESocket::EndReply(const BinarySpanWriter& sw)
|
||||||
|
{
|
||||||
|
DebugAssert(sw.IsValid());
|
||||||
|
const size_t total_size = sw.GetBufferWritten();
|
||||||
|
std::memcpy(&sw.GetSpan()[0], &total_size, sizeof(u32));
|
||||||
|
ReleaseWriteBuffer(sw.GetBufferWritten(), false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PINEServer::PINESocket::SendErrorReply()
|
||||||
|
{
|
||||||
|
BinarySpanWriter reply;
|
||||||
|
if (!BeginReply(reply, 0)) [[unlikely]]
|
||||||
|
return false;
|
||||||
|
|
||||||
|
reply << IPC_FAIL;
|
||||||
|
return EndReply(reply);
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team, Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
|
/* A reference client implementation for interfacing with PINE is available
|
||||||
|
* here: https://code.govanify.com/govanify/pine/ */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace PINEServer {
|
||||||
|
bool IsRunning();
|
||||||
|
bool Initialize(u16 slot);
|
||||||
|
void Shutdown();
|
||||||
|
} // namespace PINEServer
|
|
@ -160,6 +160,10 @@ void Settings::Load(SettingsInterface& si)
|
||||||
rewind_save_slots = static_cast<u32>(si.GetIntValue("Main", "RewindSaveSlots", 10));
|
rewind_save_slots = static_cast<u32>(si.GetIntValue("Main", "RewindSaveSlots", 10));
|
||||||
runahead_frames = static_cast<u32>(si.GetIntValue("Main", "RunaheadFrameCount", 0));
|
runahead_frames = static_cast<u32>(si.GetIntValue("Main", "RunaheadFrameCount", 0));
|
||||||
|
|
||||||
|
pine_enable = si.GetBoolValue("PINE", "Enabled", false);
|
||||||
|
pine_slot = static_cast<u16>(
|
||||||
|
std::min<u32>(si.GetUIntValue("PINE", "Slot", DEFAULT_PINE_SLOT), std::numeric_limits<u16>::max()));
|
||||||
|
|
||||||
cpu_execution_mode =
|
cpu_execution_mode =
|
||||||
ParseCPUExecutionMode(
|
ParseCPUExecutionMode(
|
||||||
si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str())
|
si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str())
|
||||||
|
@ -456,6 +460,9 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
||||||
si.SetIntValue("Main", "RewindSaveSlots", rewind_save_slots);
|
si.SetIntValue("Main", "RewindSaveSlots", rewind_save_slots);
|
||||||
si.SetIntValue("Main", "RunaheadFrameCount", runahead_frames);
|
si.SetIntValue("Main", "RunaheadFrameCount", runahead_frames);
|
||||||
|
|
||||||
|
si.SetBoolValue("PINE", "Enabled", pine_enable);
|
||||||
|
si.SetUIntValue("PINE", "Slot", pine_slot);
|
||||||
|
|
||||||
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
|
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
|
||||||
si.SetBoolValue("CPU", "OverclockEnable", cpu_overclock_enable);
|
si.SetBoolValue("CPU", "OverclockEnable", cpu_overclock_enable);
|
||||||
si.SetIntValue("CPU", "OverclockNumerator", cpu_overclock_numerator);
|
si.SetIntValue("CPU", "OverclockNumerator", cpu_overclock_numerator);
|
||||||
|
|
|
@ -92,11 +92,13 @@ struct Settings
|
||||||
bool enable_cheats : 1 = false;
|
bool enable_cheats : 1 = false;
|
||||||
bool disable_all_enhancements : 1 = false;
|
bool disable_all_enhancements : 1 = false;
|
||||||
bool enable_discord_presence : 1 = false;
|
bool enable_discord_presence : 1 = false;
|
||||||
|
bool pine_enable : 1 = false;
|
||||||
|
|
||||||
bool rewind_enable : 1 = false;
|
bool rewind_enable : 1 = false;
|
||||||
float rewind_save_frequency = 10.0f;
|
float rewind_save_frequency = 10.0f;
|
||||||
u32 rewind_save_slots = 10;
|
u32 rewind_save_slots = 10;
|
||||||
u32 runahead_frames = 0;
|
u32 runahead_frames = 0;
|
||||||
|
u16 pine_slot = DEFAULT_PINE_SLOT;
|
||||||
|
|
||||||
GPURenderer gpu_renderer = DEFAULT_GPU_RENDERER;
|
GPURenderer gpu_renderer = DEFAULT_GPU_RENDERER;
|
||||||
std::string gpu_adapter;
|
std::string gpu_adapter;
|
||||||
|
@ -510,6 +512,12 @@ struct Settings
|
||||||
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = false;
|
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = false;
|
||||||
static constexpr bool DEFAULT_FAST_BOOT_VALUE = true;
|
static constexpr bool DEFAULT_FAST_BOOT_VALUE = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// PINE uses a concept of "slot" to be able to communicate with multiple
|
||||||
|
// emulators at the same time, each slot should be unique to each emulator to
|
||||||
|
// allow PnP and configurable by the end user so that several runs don't
|
||||||
|
// conflict with each others
|
||||||
|
static constexpr u16 DEFAULT_PINE_SLOT = 28011;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Settings g_settings;
|
extern Settings g_settings;
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include "util/iso_reader.h"
|
#include "util/iso_reader.h"
|
||||||
#include "util/platform_misc.h"
|
#include "util/platform_misc.h"
|
||||||
#include "util/postprocessing.h"
|
#include "util/postprocessing.h"
|
||||||
|
#include "util/sockets.h"
|
||||||
#include "util/state_wrapper.h"
|
#include "util/state_wrapper.h"
|
||||||
|
|
||||||
#include "common/align.h"
|
#include "common/align.h"
|
||||||
|
@ -79,6 +80,13 @@ Log_SetChannel(System);
|
||||||
#include "discord_rpc.h"
|
#include "discord_rpc.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
#define ENABLE_PINE_SERVER 1
|
||||||
|
// #define ENABLE_GDB_SERVER 1
|
||||||
|
#define ENABLE_SOCKET_MULTIPLEXER 1
|
||||||
|
#include "pine_server.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// #define PROFILE_MEMORY_SAVE_STATES 1
|
// #define PROFILE_MEMORY_SAVE_STATES 1
|
||||||
|
|
||||||
SystemBootParameters::SystemBootParameters() = default;
|
SystemBootParameters::SystemBootParameters() = default;
|
||||||
|
@ -254,6 +262,10 @@ static u32 s_runahead_replay_frames = 0;
|
||||||
// Used to track play time. We use a monotonic timer here, in case of clock changes.
|
// Used to track play time. We use a monotonic timer here, in case of clock changes.
|
||||||
static u64 s_session_start_time = 0;
|
static u64 s_session_start_time = 0;
|
||||||
|
|
||||||
|
#ifdef ENABLE_SOCKET_MULTIPLEXER
|
||||||
|
static std::unique_ptr<SocketMultiplexer> s_socket_multiplexer;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_DISCORD_PRESENCE
|
#ifdef ENABLE_DISCORD_PRESENCE
|
||||||
static bool s_discord_presence_active = false;
|
static bool s_discord_presence_active = false;
|
||||||
static time_t s_discord_presence_time_epoch;
|
static time_t s_discord_presence_time_epoch;
|
||||||
|
@ -339,11 +351,20 @@ bool System::Internal::CPUThreadInitialize(Error* error)
|
||||||
InitializeDiscordPresence();
|
InitializeDiscordPresence();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_PINE_SERVER
|
||||||
|
if (g_settings.pine_enable)
|
||||||
|
PINEServer::Initialize(g_settings.pine_slot);
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::Internal::CPUThreadShutdown()
|
void System::Internal::CPUThreadShutdown()
|
||||||
{
|
{
|
||||||
|
#ifdef ENABLE_PINE_SERVER
|
||||||
|
PINEServer::Shutdown();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_DISCORD_PRESENCE
|
#ifdef ENABLE_DISCORD_PRESENCE
|
||||||
ShutdownDiscordPresence();
|
ShutdownDiscordPresence();
|
||||||
#endif
|
#endif
|
||||||
|
@ -369,6 +390,11 @@ void System::Internal::IdlePollUpdate()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Achievements::IdleUpdate();
|
Achievements::IdleUpdate();
|
||||||
|
|
||||||
|
#ifdef ENABLE_SOCKET_MULTIPLEXER
|
||||||
|
if (s_socket_multiplexer)
|
||||||
|
s_socket_multiplexer->PollEventsWithTimeout(0);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
System::State System::GetState()
|
System::State System::GetState()
|
||||||
|
@ -1924,6 +1950,11 @@ void System::FrameDone()
|
||||||
PollDiscordPresence();
|
PollDiscordPresence();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_SOCKET_MULTIPLEXER
|
||||||
|
if (s_socket_multiplexer)
|
||||||
|
s_socket_multiplexer->PollEventsWithTimeout(0);
|
||||||
|
#endif
|
||||||
|
|
||||||
Host::FrameDone();
|
Host::FrameDone();
|
||||||
|
|
||||||
if (s_frame_step_request)
|
if (s_frame_step_request)
|
||||||
|
@ -4089,6 +4120,17 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_PINE_SERVER
|
||||||
|
if (g_settings.pine_enable != old_settings.pine_enable || g_settings.pine_slot != old_settings.pine_slot)
|
||||||
|
{
|
||||||
|
PINEServer::Shutdown();
|
||||||
|
if (g_settings.pine_enable)
|
||||||
|
PINEServer::Initialize(g_settings.pine_slot);
|
||||||
|
else
|
||||||
|
ReleaseSocketMultiplexer();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter ||
|
if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter ||
|
||||||
g_settings.log_timestamps != old_settings.log_timestamps ||
|
g_settings.log_timestamps != old_settings.log_timestamps ||
|
||||||
g_settings.log_to_console != old_settings.log_to_console ||
|
g_settings.log_to_console != old_settings.log_to_console ||
|
||||||
|
@ -5250,6 +5292,37 @@ u64 System::GetSessionPlayedTime()
|
||||||
return static_cast<u64>(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_session_start_time)));
|
return static_cast<u64>(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_session_start_time)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SocketMultiplexer* System::GetSocketMultiplexer()
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_SOCKET_MULTIPLEXER
|
||||||
|
if (s_socket_multiplexer)
|
||||||
|
return s_socket_multiplexer.get();
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
s_socket_multiplexer = SocketMultiplexer::Create(&error);
|
||||||
|
if (s_socket_multiplexer)
|
||||||
|
INFO_LOG("Created socket multiplexer.");
|
||||||
|
else
|
||||||
|
ERROR_LOG("Failed to create socket multiplexer: {}", error.GetDescription());
|
||||||
|
|
||||||
|
return s_socket_multiplexer.get();
|
||||||
|
#else
|
||||||
|
ERROR_LOG("This build does not support sockets.");
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::ReleaseSocketMultiplexer()
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_SOCKET_MULTIPLEXER
|
||||||
|
if (!s_socket_multiplexer || s_socket_multiplexer->HasAnyOpenSockets())
|
||||||
|
return;
|
||||||
|
|
||||||
|
INFO_LOG("Destroying socket multiplexer.");
|
||||||
|
s_socket_multiplexer.reset();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_DISCORD_PRESENCE
|
#ifdef ENABLE_DISCORD_PRESENCE
|
||||||
|
|
||||||
void System::InitializeDiscordPresence()
|
void System::InitializeDiscordPresence()
|
||||||
|
|
|
@ -19,6 +19,7 @@ class CDImage;
|
||||||
class Error;
|
class Error;
|
||||||
class SmallStringBase;
|
class SmallStringBase;
|
||||||
class StateWrapper;
|
class StateWrapper;
|
||||||
|
class SocketMultiplexer;
|
||||||
|
|
||||||
enum class GPUVSyncMode : u8;
|
enum class GPUVSyncMode : u8;
|
||||||
|
|
||||||
|
@ -489,6 +490,10 @@ void UpdateMemorySaveStateSettings();
|
||||||
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
|
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
|
||||||
void SetRunaheadReplayFlag();
|
void SetRunaheadReplayFlag();
|
||||||
|
|
||||||
|
/// Shared socket multiplexer, used by PINE/GDB/etc.
|
||||||
|
SocketMultiplexer* GetSocketMultiplexer();
|
||||||
|
void ReleaseSocketMultiplexer();
|
||||||
|
|
||||||
#ifdef ENABLE_DISCORD_PRESENCE
|
#ifdef ENABLE_DISCORD_PRESENCE
|
||||||
/// Called when rich presence changes.
|
/// Called when rich presence changes.
|
||||||
void UpdateDiscordPresence(bool update_session_time);
|
void UpdateDiscordPresence(bool update_session_time);
|
||||||
|
|
|
@ -263,6 +263,10 @@ void AdvancedSettingsWidget::addTweakOptions()
|
||||||
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
|
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
|
||||||
"AllowBootingWithoutSBIFile", false);
|
"AllowBootingWithoutSBIFile", false);
|
||||||
|
|
||||||
|
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PINE"), "PINE", "Enabled", false);
|
||||||
|
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("PINE Slot"), "PINE", "Slot", 0, 65535,
|
||||||
|
Settings::DEFAULT_PINE_SLOT);
|
||||||
|
|
||||||
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv"), "PCDrv", "Enabled", false);
|
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv"), "PCDrv", "Enabled", false);
|
||||||
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv Writes"), "PCDrv", "EnableWrites", false);
|
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv Writes"), "PCDrv", "EnableWrites", false);
|
||||||
addDirectoryOption(m_dialog, m_ui.tweakOptionTable, tr("PCDrv Root Directory"), "PCDrv", "Root");
|
addDirectoryOption(m_dialog, m_ui.tweakOptionTable, tr("PCDrv Root Directory"), "PCDrv", "Root");
|
||||||
|
@ -292,12 +296,14 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
|
||||||
setChoiceTweakOption(m_ui.tweakOptionTable, i++,
|
setChoiceTweakOption(m_ui.tweakOptionTable, i++,
|
||||||
Settings::DEFAULT_CPU_FASTMEM_MODE); // Recompiler fastmem mode
|
Settings::DEFAULT_CPU_FASTMEM_MODE); // Recompiler fastmem mode
|
||||||
setChoiceTweakOption(m_ui.tweakOptionTable, i++,
|
setChoiceTweakOption(m_ui.tweakOptionTable, i++,
|
||||||
Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version
|
Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PINE
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV Writes
|
setIntRangeTweakOption(m_ui.tweakOptionTable, i++, Settings::DEFAULT_PINE_SLOT); // PINE Slot
|
||||||
setDirectoryOption(m_ui.tweakOptionTable, i++, ""); // PCDrv Root Directory
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV
|
||||||
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV Writes
|
||||||
|
setDirectoryOption(m_ui.tweakOptionTable, i++, ""); // PCDrv Root Directory
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -322,6 +328,8 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
|
||||||
sif->DeleteValue("CDROM", "MechaconVersion");
|
sif->DeleteValue("CDROM", "MechaconVersion");
|
||||||
sif->DeleteValue("CDROM", "RegionCheck");
|
sif->DeleteValue("CDROM", "RegionCheck");
|
||||||
sif->DeleteValue("CDROM", "AllowBootingWithoutSBIFile");
|
sif->DeleteValue("CDROM", "AllowBootingWithoutSBIFile");
|
||||||
|
sif->DeleteValue("PINE", "Enabled");
|
||||||
|
sif->DeleteValue("PINE", "Slot");
|
||||||
sif->DeleteValue("PCDrv", "Enabled");
|
sif->DeleteValue("PCDrv", "Enabled");
|
||||||
sif->DeleteValue("PCDrv", "EnableWrites");
|
sif->DeleteValue("PCDrv", "EnableWrites");
|
||||||
sif->DeleteValue("PCDrv", "Root");
|
sif->DeleteValue("PCDrv", "Root");
|
||||||
|
|
Loading…
Reference in New Issue