96 files changed
+8323
-285
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# GEYJ6E - Virtua Striker 2002 (Japan)
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
@@ -0,0 +1,4 @@
|
||||
# GEYP6E - Virtua Striker 2002 (Export, Type3)
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
@@ -0,0 +1,5 @@
|
||||
# GGGE6E - F-ZERO AX
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# GHAE6E - F-ZERO AX (Monster)
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# GHZW6E - VIRTUA STRIKER 4 (Asia)
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
@@ -0,0 +1,4 @@
|
||||
# GJAP6E - VIRTUA STRIKER 4 (Export)
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
@@ -0,0 +1,4 @@
|
||||
# GLKJ6E - VIRTUA STRIKER 4 Ver.2006 (Japan)
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
@@ -0,0 +1,4 @@
|
||||
# GLLP6E - VIRTUA STRIKER 4 Ver.2006 (Export)
|
||||
|
||||
[Core]
|
||||
FPRF = True
|
||||
Vendored
+2
@@ -76,6 +76,8 @@ Dolphin includes or links code of the following third-party software projects:
|
||||
[MIT](https://github.com/microsoft/wil/blob/master/LICENSE)
|
||||
- [xxHash](https://github.com/Cyan4973/xxHash):
|
||||
[BSD 2-Clause](https://github.com/Cyan4973/xxHash/blob/master/LICENSE)
|
||||
- [YACardEmu](https://github.com/GXTX/YACardEmu)
|
||||
[GPLv2+](https://github.com/GXTX/YACardEmu/blob/master/license.txt)
|
||||
- [zlib-ng](https://github.com/zlib-ng/zlib-ng):
|
||||
[zlib license](https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md)
|
||||
- [Zstandard](https://facebook.github.io/zstd/):
|
||||
|
||||
+6
@@ -69,6 +69,12 @@ enum class StringSetting(
|
||||
""
|
||||
),
|
||||
MAIN_WFS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GENERAL, "WFSPath", ""),
|
||||
MAIN_TRIFORCE_IP_REDIRECTIONS(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_CORE,
|
||||
"TriforceIPRedirections",
|
||||
"0.0.0.0/0=127.0.0.1"
|
||||
),
|
||||
GFX_ENHANCE_POST_SHADER(
|
||||
Settings.FILE_GFX,
|
||||
Settings.SECTION_GFX_ENHANCEMENTS,
|
||||
|
||||
+12
-2
@@ -1276,7 +1276,17 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addSerialPortSubSettings(sl: ArrayList<SettingsItem>, serialPort1Type: Int) {
|
||||
if (serialPort1Type == 10) {
|
||||
if (serialPort1Type == 6) {
|
||||
// Triforce Baseboard
|
||||
sl.add(
|
||||
InputStringSetting(
|
||||
context,
|
||||
StringSetting.MAIN_TRIFORCE_IP_REDIRECTIONS,
|
||||
R.string.triforce_ip_redirections,
|
||||
0
|
||||
)
|
||||
)
|
||||
} else if (serialPort1Type == 10) {
|
||||
// Broadband Adapter (XLink Kai)
|
||||
sl.add(HyperLinkHeaderSetting(context, R.string.xlink_kai_guide_header, 0))
|
||||
sl.add(
|
||||
@@ -2352,7 +2362,7 @@ class SettingsFragmentPresenter(
|
||||
|
||||
private fun addGcPadSubSettings(sl: ArrayList<SettingsItem>, gcPadNumber: Int, gcPadType: Int) {
|
||||
when (gcPadType) {
|
||||
6, 8, 9, 10 -> {
|
||||
6, 8, 9, 10, 11 -> {
|
||||
// Emulated
|
||||
val gcPad = EmulatedController.getGcPad(gcPadNumber)
|
||||
|
||||
|
||||
@@ -2285,6 +2285,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||
const val OVERLAY_NONE = 5
|
||||
private const val DISABLED_GAMECUBE_CONTROLLER = 0
|
||||
private const val EMULATED_GAMECUBE_CONTROLLER = 6
|
||||
private const val EMULATED_AM_BASEBOARD = 11
|
||||
private const val GAMECUBE_ADAPTER = 12
|
||||
|
||||
// Buttons that have special positions in Wiimote only
|
||||
@@ -2337,8 +2338,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||
|
||||
if (controllerIndex in 0 until 4) {
|
||||
// GameCube controller
|
||||
if (getSettingForSIDevice(controllerIndex).int == 6)
|
||||
return OVERLAY_GAMECUBE
|
||||
when (getSettingForSIDevice(controllerIndex).int) {
|
||||
EMULATED_GAMECUBE_CONTROLLER, EMULATED_AM_BASEBOARD -> {
|
||||
return OVERLAY_GAMECUBE
|
||||
}
|
||||
}
|
||||
} else if (controllerIndex in 4 until 8) {
|
||||
// Wii Remote
|
||||
val wiimoteIndex = controllerIndex - 4
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<item>@string/broadband_adapter_hle</item>
|
||||
<item>@string/broadband_adapter_tapserver</item>
|
||||
<item>@string/modem_adapter_tapserver</item>
|
||||
<item>@string/sp1_am_baseboard</item>
|
||||
</string-array>
|
||||
<integer-array name="serialPort1DeviceValues">
|
||||
<item>255</item>
|
||||
@@ -113,6 +114,7 @@
|
||||
<item>12</item>
|
||||
<item>11</item>
|
||||
<item>13</item>
|
||||
<item>6</item>
|
||||
</integer-array>
|
||||
|
||||
<!-- Wii System Languages -->
|
||||
@@ -375,6 +377,7 @@
|
||||
<item>@string/gcpad_steering_wheel</item>
|
||||
<item>@string/gcpad_dance_mat</item>
|
||||
<item>@string/gcpad_taru_konga</item>
|
||||
<item>@string/gcpad_am_baseboard</item>
|
||||
<item>@string/gcpad_gc_adapter</item>
|
||||
</string-array>
|
||||
<integer-array name="gcpadTypeValues">
|
||||
@@ -384,6 +387,7 @@
|
||||
<item>8</item>
|
||||
<item>9</item>
|
||||
<item>10</item>
|
||||
<item>11</item>
|
||||
<item>12</item>
|
||||
</integer-array>
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
<string name="modem_tapserver_destination_description">Enter the socket path or netloc (address:port) of the tapserver instance</string>
|
||||
<string name="bba_builtin_dns">DNS Server</string>
|
||||
<string name="bba_builtin_dns_description">Use 8.8.8.8 for normal DNS, else enter your custom one</string>
|
||||
<string name="triforce_ip_redirections">IP Redirections</string>
|
||||
|
||||
<!-- Interface Preference Fragment -->
|
||||
<string name="interface_submenu">Interface</string>
|
||||
@@ -707,6 +708,7 @@ It can efficiently compress both junk data and encrypted Wii data.
|
||||
<string name="broadband_adapter_hle">Broadband Adapter (HLE)</string>
|
||||
<string name="broadband_adapter_tapserver">Broadband Adapter (tapserver)</string>
|
||||
<string name="modem_adapter_tapserver">Modem Adapter (tapserver)</string>
|
||||
<string name="sp1_am_baseboard">Triforce Baseboard</string>
|
||||
|
||||
<!-- Sound Mode -->
|
||||
<string name="sound_mode_mono">Mono</string>
|
||||
@@ -811,6 +813,7 @@ It can efficiently compress both junk data and encrypted Wii data.
|
||||
<string name="gcpad_steering_wheel">Steering Wheel</string>
|
||||
<string name="gcpad_dance_mat">Dance Mat</string>
|
||||
<string name="gcpad_taru_konga">DK Bongos</string>
|
||||
<string name="gcpad_am_baseboard">Triforce Baseboard</string>
|
||||
<string name="gcpad_gc_adapter">GameCube Adapter</string>
|
||||
|
||||
<!-- Wiimote Types -->
|
||||
|
||||
@@ -189,7 +189,7 @@ template <typename ValueType, typename From>
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SetBit(T& value, size_t bit_number, bool bit_value)
|
||||
constexpr void SetBit(T& value, size_t bit_number, bool bit_value)
|
||||
{
|
||||
static_assert(std::is_unsigned<T>(), "SetBit is only sane on unsigned types.");
|
||||
|
||||
@@ -200,7 +200,7 @@ void SetBit(T& value, size_t bit_number, bool bit_value)
|
||||
}
|
||||
|
||||
template <size_t bit_number, typename T>
|
||||
void SetBit(T& value, bool bit_value)
|
||||
constexpr void SetBit(T& value, bool bit_value)
|
||||
{
|
||||
SetBit(value, bit_number, bit_value);
|
||||
}
|
||||
|
||||
@@ -41,11 +41,13 @@
|
||||
#define USA_DIR "USA"
|
||||
#define JAP_DIR "JAP"
|
||||
#define JPN_DIR "JPN"
|
||||
#define DEV_DIR "DEV"
|
||||
|
||||
// Subdirs in the User dir returned by GetUserPath(D_USER_IDX)
|
||||
#define GC_USER_DIR "GC"
|
||||
#define GBA_USER_DIR "GBA"
|
||||
#define WII_USER_DIR "Wii"
|
||||
#define TRI_USER_DIR "Triforce"
|
||||
#define CONFIG_DIR "Config"
|
||||
#define GAMESETTINGS_DIR "GameSettings"
|
||||
#define MAPS_DIR "Maps"
|
||||
@@ -155,6 +157,7 @@
|
||||
// Subdirs in Sys
|
||||
#define GC_SYS_DIR "GC"
|
||||
#define WII_SYS_DIR "Wii"
|
||||
#define TRI_SYS_DIR "Triforce"
|
||||
|
||||
// Subdirs in Config
|
||||
#define GRAPHICSMOD_CONFIG_DIR "GraphicMods"
|
||||
|
||||
@@ -838,6 +838,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
||||
case D_USER_IDX:
|
||||
s_user_paths[D_GCUSER_IDX] = s_user_paths[D_USER_IDX] + GC_USER_DIR DIR_SEP;
|
||||
s_user_paths[D_WIIROOT_IDX] = s_user_paths[D_USER_IDX] + WII_USER_DIR DIR_SEP;
|
||||
s_user_paths[D_TRIUSER_IDX] = s_user_paths[D_USER_IDX] + TRI_USER_DIR DIR_SEP;
|
||||
s_user_paths[D_CONFIG_IDX] = s_user_paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
|
||||
s_user_paths[D_GAMESETTINGS_IDX] = s_user_paths[D_USER_IDX] + GAMESETTINGS_DIR DIR_SEP;
|
||||
s_user_paths[D_MAPS_IDX] = s_user_paths[D_USER_IDX] + MAPS_DIR DIR_SEP;
|
||||
|
||||
@@ -26,7 +26,8 @@ enum
|
||||
{
|
||||
D_USER_IDX,
|
||||
D_GCUSER_IDX,
|
||||
D_WIIROOT_IDX, // always points to User/Wii or global user-configured directory
|
||||
D_WIIROOT_IDX, // always points to User/Wii or global user-configured directory
|
||||
D_TRIUSER_IDX,
|
||||
D_SESSION_WIIROOT_IDX, // may point to minimal temporary directory for determinism
|
||||
D_CONFIG_IDX, // global settings
|
||||
D_GAMESETTINGS_IDX, // user-specified settings which override both the global and the default
|
||||
|
||||
@@ -27,6 +27,8 @@ enum class LogType : int
|
||||
DSP_MAIL,
|
||||
DSPINTERFACE,
|
||||
DVDINTERFACE,
|
||||
AMMEDIABOARD,
|
||||
AMMEDIABOARD_NET,
|
||||
DYNA_REC,
|
||||
EXPANSIONINTERFACE,
|
||||
FILEMON,
|
||||
@@ -58,6 +60,9 @@ enum class LogType : int
|
||||
PROCESSORINTERFACE,
|
||||
POWERPC,
|
||||
SERIALINTERFACE,
|
||||
SERIALINTERFACE_AMBB,
|
||||
SERIALINTERFACE_CARD,
|
||||
SERIALINTERFACE_JVSIO,
|
||||
SP1,
|
||||
SYMBOLS,
|
||||
VIDEO,
|
||||
|
||||
@@ -111,6 +111,8 @@ LogManager::LogManager()
|
||||
m_log[LogType::DSP_MAIL] = {"DSPMails", "DSP Mails"};
|
||||
m_log[LogType::DSPINTERFACE] = {"DSP", "DSP Interface"};
|
||||
m_log[LogType::DVDINTERFACE] = {"DVD", "DVD Interface"};
|
||||
m_log[LogType::AMMEDIABOARD] = {"AMMB", "AMMB Interface"};
|
||||
m_log[LogType::AMMEDIABOARD_NET] = {"AMMB_NET", "AMMB - Network"};
|
||||
m_log[LogType::DYNA_REC] = {"JIT", "JIT Dynamic Recompiler"};
|
||||
m_log[LogType::EXPANSIONINTERFACE] = {"EXI", "Expansion Interface"};
|
||||
m_log[LogType::FILEMON] = {"FileMon", "File Monitor"};
|
||||
@@ -142,6 +144,9 @@ LogManager::LogManager()
|
||||
m_log[LogType::PROCESSORINTERFACE] = {"PI", "Processor Interface"};
|
||||
m_log[LogType::POWERPC] = {"PowerPC", "PowerPC IBM CPU"};
|
||||
m_log[LogType::SERIALINTERFACE] = {"SI", "Serial Interface"};
|
||||
m_log[LogType::SERIALINTERFACE_AMBB] = {"SI_AMBB", "AMBB Interface"};
|
||||
m_log[LogType::SERIALINTERFACE_CARD] = {"SI_CARD", "CARD Interface"};
|
||||
m_log[LogType::SERIALINTERFACE_JVSIO] = {"SI_JVS", "JVS-I/O"};
|
||||
m_log[LogType::SP1] = {"SP1", "Serial Port 1"};
|
||||
m_log[LogType::SYMBOLS] = {"SYMBOLS", "Symbols"};
|
||||
m_log[LogType::VIDEO] = {"Video", "Video Backend"};
|
||||
|
||||
@@ -9,21 +9,234 @@
|
||||
#include <vector>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cstring>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#else
|
||||
#include <WinSock2.h>
|
||||
#endif
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Random.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
|
||||
static constexpr auto GetMaskFromNetworkPrefixLength(u32 network_prefix_length)
|
||||
{
|
||||
return (network_prefix_length == 0) ?
|
||||
u32(0) :
|
||||
u32(BigEndianValue(u32(-1) >> (32 - network_prefix_length)));
|
||||
}
|
||||
|
||||
u32 IPv4Port::GetIPAddressValue() const
|
||||
{
|
||||
return std::bit_cast<Common::BigEndianValue<u32>>(ip_address);
|
||||
}
|
||||
|
||||
u16 IPv4Port::GetPortValue() const
|
||||
{
|
||||
return std::bit_cast<Common::BigEndianValue<u16>>(port);
|
||||
}
|
||||
|
||||
bool IPv4PortRange::IsMatch(IPv4Port subject) const
|
||||
{
|
||||
const u32 ip_u32 = subject.GetIPAddressValue();
|
||||
const u16 port_u16 = subject.GetPortValue();
|
||||
|
||||
return ip_u32 >= first.GetIPAddressValue() && ip_u32 <= last.GetIPAddressValue() &&
|
||||
port_u16 >= first.GetPortValue() && port_u16 <= last.GetPortValue();
|
||||
}
|
||||
|
||||
std::string IPv4PortRange::ToString() const
|
||||
{
|
||||
const u32 first_ip_value = first.GetIPAddressValue();
|
||||
const u32 last_ip_value = last.GetIPAddressValue();
|
||||
const u32 different_bits = first_ip_value ^ last_ip_value;
|
||||
const u32 common_high_bits = std::countl_zero(different_bits);
|
||||
|
||||
std::string ip_range = Common::IPAddressToString(first.ip_address);
|
||||
if (common_high_bits == 32)
|
||||
{
|
||||
// Identical first and last IP.
|
||||
}
|
||||
else if ((last_ip_value - first_ip_value + 1) << common_high_bits == 0)
|
||||
{
|
||||
// An exact network range can use CIDR notation.
|
||||
ip_range = fmt::format("{}/{}", ip_range, common_high_bits);
|
||||
}
|
||||
else if (common_high_bits >= 24)
|
||||
{
|
||||
// Only the last octet is different.
|
||||
ip_range = fmt::format("{}-{}", ip_range, last.ip_address.back());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Plainly specified range.
|
||||
ip_range = fmt::format("{}-{}", ip_range, Common::IPAddressToString(last.ip_address));
|
||||
}
|
||||
|
||||
if (first.port == 0)
|
||||
return ip_range;
|
||||
|
||||
if (first.port == last.port)
|
||||
return fmt::format("{}:{}", ip_range, first.GetPortValue());
|
||||
|
||||
return fmt::format("{}:{}-{}", ip_range, first.GetPortValue(), last.GetPortValue());
|
||||
}
|
||||
|
||||
std::string IPAddressToString(IPAddress ip_address)
|
||||
{
|
||||
return fmt::format("{}", fmt::join(ip_address, "."));
|
||||
}
|
||||
|
||||
std::optional<IPv4PortRange> StringToIPv4PortRange(std::string_view subject)
|
||||
{
|
||||
// TODO: This could be optimized and improved a lot.
|
||||
|
||||
auto read_pos = std::ranges::find_first_of(subject, std::string_view{"-/:"});
|
||||
|
||||
const auto first_ip_octets = SplitStringIntoArray<4>({subject.begin(), read_pos}, '.');
|
||||
|
||||
if (!first_ip_octets.has_value())
|
||||
return std::nullopt; // Wrong number of octets.
|
||||
|
||||
std::optional<IPv4PortRange> result;
|
||||
result.emplace();
|
||||
|
||||
for (u32 i = 0; i != result->first.ip_address.size(); ++i)
|
||||
{
|
||||
if (!TryParse(std::string((*first_ip_octets)[i]), &result->first.ip_address[i]))
|
||||
return std::nullopt; // Bad octet.
|
||||
}
|
||||
|
||||
if (read_pos == subject.end())
|
||||
{
|
||||
// Last IP and ports not specified.
|
||||
result->last.ip_address = result->first.ip_address;
|
||||
result->first.port = 0;
|
||||
result->last.port = u16(-1);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (*read_pos == '-')
|
||||
{
|
||||
// Read last IP.
|
||||
++read_pos;
|
||||
|
||||
const auto last_ip_end = std::ranges::find(read_pos, subject.end(), ':');
|
||||
const auto last_ip_octets = SplitString({read_pos, last_ip_end}, '.');
|
||||
|
||||
// Copy octets from first_ip when less than 4 are specified.
|
||||
// FYI: This currently allows for syntax like: 10.0.0.1-7.255
|
||||
// I don't know if it's desirable or not to allow 2 or 3 octets on the last IP.
|
||||
const std::size_t octet_share_count = 4 - last_ip_octets.size();
|
||||
|
||||
if (octet_share_count >= 4)
|
||||
return std::nullopt; // Too many octets.
|
||||
|
||||
std::copy_n(result->first.ip_address.begin(), octet_share_count,
|
||||
result->last.ip_address.begin());
|
||||
|
||||
std::size_t i = octet_share_count;
|
||||
for (auto&& octet_str : last_ip_octets)
|
||||
{
|
||||
if (!TryParse(octet_str, &result->last.ip_address[i]))
|
||||
return std::nullopt; // Bad octet.
|
||||
++i;
|
||||
}
|
||||
|
||||
if (result->first.ip_address > result->last.ip_address)
|
||||
return std::nullopt; // Reversed range.
|
||||
|
||||
read_pos = last_ip_end;
|
||||
}
|
||||
else if (*read_pos == '/')
|
||||
{
|
||||
// Read network prefix length.
|
||||
++read_pos;
|
||||
|
||||
u32 network_prefix_length{};
|
||||
const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, network_prefix_length);
|
||||
|
||||
if (ec != std::errc{} || network_prefix_length > 32)
|
||||
return std::nullopt; // Bad network prefix.
|
||||
|
||||
read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators..
|
||||
|
||||
const auto net_mask = GetMaskFromNetworkPrefixLength(network_prefix_length);
|
||||
const auto first_ip_address_u32 = std::bit_cast<u32>(result->first.ip_address) & net_mask;
|
||||
result->first.ip_address = std::bit_cast<IPAddress>(first_ip_address_u32);
|
||||
result->last.ip_address = std::bit_cast<IPAddress>(first_ip_address_u32 | ~net_mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Last IP not specified.
|
||||
result->last.ip_address = result->first.ip_address;
|
||||
}
|
||||
|
||||
if (read_pos == subject.end())
|
||||
{
|
||||
// Ports not specified.
|
||||
result->first.port = 0;
|
||||
result->last.port = u16(-1);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (*read_pos != ':')
|
||||
return std::nullopt; // Unexpected character.
|
||||
|
||||
u16 first_port_u16{};
|
||||
{
|
||||
// Read first port.
|
||||
++read_pos;
|
||||
|
||||
const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, first_port_u16);
|
||||
if (ec != std::errc{})
|
||||
return std::nullopt; // Bad first port.
|
||||
|
||||
result->first.port = std::bit_cast<u16>(Common::BigEndianValue(first_port_u16));
|
||||
|
||||
read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators..
|
||||
}
|
||||
|
||||
if (read_pos == subject.end())
|
||||
{
|
||||
// Last port not specified.
|
||||
result->last.port = result->first.port;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (*read_pos != '-')
|
||||
return std::nullopt; // Unexpected character.
|
||||
|
||||
u16 first_last_u16{};
|
||||
{
|
||||
// Read last port.
|
||||
++read_pos;
|
||||
|
||||
const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, first_last_u16);
|
||||
if (ec != std::errc{} || first_last_u16 < first_port_u16)
|
||||
return std::nullopt; // Bad last port.
|
||||
|
||||
result->last.port = std::bit_cast<u16>(Common::BigEndianValue(first_last_u16));
|
||||
|
||||
read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators..
|
||||
}
|
||||
|
||||
if (read_pos != subject.end())
|
||||
return std::nullopt;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MACAddress GenerateMacAddress(const MACConsumer type)
|
||||
{
|
||||
constexpr std::array<u8, 3> oui_bba{{0x00, 0x09, 0xbf}};
|
||||
@@ -586,4 +799,18 @@ const char* StrNetworkError()
|
||||
#endif
|
||||
return DecodeNetworkError(error_code);
|
||||
}
|
||||
|
||||
bool SetPlatformSocketOptions(int fd [[maybe_unused]])
|
||||
{
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
int opt_no_sigpipe = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0)
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "Failed to set SO_NOSIGPIPE on socket: {}", StrNetworkError());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -10,6 +10,10 @@
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__linux__) || defined(__HAIKU__)
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct sockaddr_in;
|
||||
@@ -264,6 +268,30 @@ struct NetworkErrorState
|
||||
#endif
|
||||
};
|
||||
|
||||
struct IPv4Port
|
||||
{
|
||||
IPAddress ip_address;
|
||||
u16 port; // Network byte order.
|
||||
|
||||
// These convert to host byte order.
|
||||
u32 GetIPAddressValue() const;
|
||||
u16 GetPortValue() const;
|
||||
};
|
||||
|
||||
struct IPv4PortRange
|
||||
{
|
||||
IPv4Port first;
|
||||
IPv4Port last;
|
||||
|
||||
bool IsMatch(IPv4Port subject) const;
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
std::string IPAddressToString(IPAddress ip_address);
|
||||
|
||||
// Syntax is: first_ip[-last_ip|/network_prefix_length][:first_port[-last_port]]
|
||||
std::optional<IPv4PortRange> StringToIPv4PortRange(std::string_view subject);
|
||||
|
||||
MACAddress GenerateMacAddress(MACConsumer type);
|
||||
|
||||
std::string MacAddressToString(const MACAddress& mac);
|
||||
@@ -279,4 +307,15 @@ NetworkErrorState SaveNetworkErrorState();
|
||||
void RestoreNetworkErrorState(const NetworkErrorState& state);
|
||||
const char* DecodeNetworkError(s32 error_code);
|
||||
const char* StrNetworkError();
|
||||
|
||||
// Sets SO_NOSIGPIPE when available.
|
||||
bool SetPlatformSocketOptions(int fd);
|
||||
|
||||
// Pass this to all `send` calls to avoid SIGPIPE.
|
||||
#if defined(__linux__) || defined(__HAIKU__)
|
||||
static constexpr int SEND_FLAGS = MSG_NOSIGNAL;
|
||||
#else
|
||||
static constexpr int SEND_FLAGS = 0;
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -182,6 +183,32 @@ std::from_chars_result FromChars(std::string_view sv, std::floating_point auto&
|
||||
|
||||
std::vector<std::string> SplitString(const std::string& str, char delim);
|
||||
|
||||
// Returns `nullopt` if subject does not contain exactly (Count - 1) delim.
|
||||
template <std::size_t Count>
|
||||
requires(Count > 1)
|
||||
std::optional<std::array<std::string_view, Count>> SplitStringIntoArray(std::string_view subject,
|
||||
char delim)
|
||||
{
|
||||
std::optional<std::array<std::string_view, Count>> result;
|
||||
result.emplace();
|
||||
|
||||
std::size_t index = 0;
|
||||
for (auto&& item : subject | std::views::split(delim))
|
||||
{
|
||||
// Too many delim.
|
||||
if (index == Count)
|
||||
return std::nullopt;
|
||||
|
||||
(*result)[index++] = std::string_view{item};
|
||||
}
|
||||
|
||||
// Too few delim.
|
||||
if (index != Count)
|
||||
return std::nullopt;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
|
||||
// This requires forward slashes to be used for the path separators, even on Windows.
|
||||
bool SplitPath(std::string_view full_path, std::string* path, std::string* filename,
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <sys/eventfd.h>
|
||||
#endif
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
namespace UnixUtil
|
||||
{
|
||||
|
||||
#if defined(__linux__)
|
||||
inline int CreateEventFD(unsigned int count, int flags)
|
||||
{
|
||||
const int result = eventfd(count, flags);
|
||||
@@ -21,6 +26,7 @@ inline int CreateEventFD(unsigned int count, int flags)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Repeatedly call a function that can erroneously produce EINTR.
|
||||
auto RetryOnEINTR(auto func, auto... args)
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/FifoPlayer/FifoPlayer.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/DVD/AMMediaboard.h"
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
@@ -373,7 +374,8 @@ bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename)
|
||||
constexpr u32 PAL_v1_0 = 0x4F319F43;
|
||||
// DOL-101(EUR) (PAL Revision 1.2)
|
||||
constexpr u32 PAL_v1_2 = 0xAD1B7F16;
|
||||
constexpr u32 Triforce = 0xD1883221; // The Triforce's special IPL
|
||||
// Triforce Arcade IPL (DEV Revision 1.0)
|
||||
constexpr u32 Triforce = 0xD1883221;
|
||||
|
||||
// Load the IPL ROM dump, limited to 2MiB which is the size of the official IPLs.
|
||||
constexpr size_t max_ipl_size = 2 * 1024 * 1024;
|
||||
@@ -407,6 +409,7 @@ bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename)
|
||||
break;
|
||||
case Triforce:
|
||||
known_ipl = true;
|
||||
break;
|
||||
default:
|
||||
PanicAlertFmtT("The IPL file is not a known good dump. (CRC32: {0:x})", ipl_hash);
|
||||
break;
|
||||
@@ -457,6 +460,14 @@ bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename)
|
||||
ppc_state.spr[SPR_DBAT3L] = 0xfff00001;
|
||||
SetupBAT(system, /*is_wii*/ false);
|
||||
|
||||
if (ipl_hash == Triforce)
|
||||
{
|
||||
HLE::Patch(system, 0x813048B8, "OSReport");
|
||||
HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader
|
||||
|
||||
AMMediaboard::FirmwareMap(true);
|
||||
}
|
||||
|
||||
ppc_state.pc = 0x81200150;
|
||||
|
||||
system.GetPowerPC().MSRUpdated();
|
||||
@@ -490,6 +501,12 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
|
||||
{
|
||||
SConfig& config = SConfig::GetInstance();
|
||||
|
||||
// Triforce systems are region free and always must use the NTSC video mode
|
||||
if (system.IsTriforce())
|
||||
{
|
||||
config.m_region = DiscIO::Region::NTSC_J;
|
||||
}
|
||||
|
||||
// PAL Wii uses NTSC framerate and linecount in 60Hz modes
|
||||
system.GetVideoInterface().Preset(DiscIO::IsNTSC(config.m_region) ||
|
||||
(system.IsWii() && Config::Get(Config::SYSCONF_PAL60)));
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/DVD/AMMediaboard.h"
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
@@ -228,12 +229,16 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
||||
ppc_state.pc = ppc_state.gpr[3];
|
||||
|
||||
branch_watch.SetRecordingActive(guard, resume_branch_watch);
|
||||
// Blank out session key (https://debugmo.de/2008/05/part-2-dumping-the-media-board/)
|
||||
if (volume.GetVolumeType() == DiscIO::Platform::Triforce)
|
||||
|
||||
if (system.IsTriforce())
|
||||
{
|
||||
auto& memory = system.GetMemory();
|
||||
|
||||
memory.Memset(0, 0, 12);
|
||||
AMMediaboard::InitDIMM(volume);
|
||||
|
||||
// Triforce disc register obfuscation
|
||||
AMMediaboard::InitKeys(memory.Read_U32(0), memory.Read_U32(4), memory.Read_U32(8));
|
||||
AMMediaboard::FirmwareMap(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "Core/AchievementManager.h"
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigLoaders/BaseConfigLoader.h"
|
||||
#include "Core/ConfigLoaders/NetPlayConfigLoader.h"
|
||||
@@ -117,7 +118,7 @@ bool BootCore(Core::System& system, std::unique_ptr<BootParameters> boot,
|
||||
case DiscIO::Region::NTSC_K:
|
||||
Config::SetCurrent(Config::SYSCONF_COUNTRY, 0x88); // South Korea
|
||||
break;
|
||||
case DiscIO::Region::Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -133,6 +134,28 @@ bool BootCore(Core::System& system, std::unique_ptr<BootParameters> boot,
|
||||
if (!boot->riivolution_patches.empty())
|
||||
Config::SetCurrent(Config::MAIN_FAST_DISC_SPEED, true);
|
||||
|
||||
if (system.IsTriforce())
|
||||
{
|
||||
// Attach Triforce Baseboard hardware if not overridden in any layer.
|
||||
// Users may set these in their GameConfig if they want them not automatically attached.
|
||||
if (GetActiveLayerForConfig(Config::MAIN_SERIAL_PORT_1) == Config::LayerType::Base)
|
||||
{
|
||||
Config::SetCurrent(Config::MAIN_SERIAL_PORT_1, ExpansionInterface::EXIDeviceType::Baseboard);
|
||||
}
|
||||
if (GetActiveLayerForConfig(Config::GetInfoForSIDevice(0)) == Config::LayerType::Base)
|
||||
{
|
||||
Config::SetCurrent(Config::GetInfoForSIDevice(0),
|
||||
SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD);
|
||||
}
|
||||
|
||||
// Mario Kart Arcade GP has widescreen heuristic issues.
|
||||
// All Triforce games are 4:3 so we'll just disable the heuristic for now.
|
||||
if (GetActiveLayerForConfig(Config::GFX_SUGGESTED_ASPECT_RATIO) == Config::LayerType::Base)
|
||||
{
|
||||
Config::SetCurrent(Config::GFX_SUGGESTED_ASPECT_RATIO, AspectMode::ForceStandard);
|
||||
}
|
||||
}
|
||||
|
||||
system.Initialize();
|
||||
|
||||
Core::UpdateWantDeterminism(system, /*initial*/ true);
|
||||
|
||||
@@ -181,6 +181,8 @@ add_library(core
|
||||
HW/DSPLLE/DSPSymbols.h
|
||||
HW/DVD/DVDInterface.cpp
|
||||
HW/DVD/DVDInterface.h
|
||||
HW/DVD/AMMediaboard.cpp
|
||||
HW/DVD/AMMediaboard.h
|
||||
HW/DVD/DVDMath.cpp
|
||||
HW/DVD/DVDMath.h
|
||||
HW/DVD/DVDThread.cpp
|
||||
@@ -252,6 +254,12 @@ add_library(core
|
||||
HW/HSP/HSP_DeviceNull.h
|
||||
HW/HW.cpp
|
||||
HW/HW.h
|
||||
HW/MagCard/C1231BR.cpp
|
||||
HW/MagCard/C1231BR.h
|
||||
HW/MagCard/C1231LR.cpp
|
||||
HW/MagCard/C1231LR.h
|
||||
HW/MagCard/MagneticCardReader.cpp
|
||||
HW/MagCard/MagneticCardReader.h
|
||||
HW/Memmap.cpp
|
||||
HW/Memmap.h
|
||||
HW/MemoryInterface.cpp
|
||||
@@ -274,6 +282,8 @@ add_library(core
|
||||
HW/SI/SI_DeviceGCSteeringWheel.h
|
||||
HW/SI/SI_DeviceKeyboard.cpp
|
||||
HW/SI/SI_DeviceKeyboard.h
|
||||
HW/SI/SI_DeviceAMBaseboard.cpp
|
||||
HW/SI/SI_DeviceAMBaseboard.h
|
||||
HW/SI/SI_DeviceNull.cpp
|
||||
HW/SI/SI_DeviceNull.h
|
||||
HW/SI/SI.cpp
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <sstream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "Common/Assert.h"
|
||||
@@ -20,13 +21,16 @@
|
||||
#include "Common/Version.h"
|
||||
#include "Core/AchievementManager.h"
|
||||
#include "Core/Config/DefaultLocale.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/EXI/EXI.h"
|
||||
#include "Core/HW/EXI/EXI_Device.h"
|
||||
#include "Core/HW/GCMemcard/GCMemcard.h"
|
||||
#include "Core/HW/HSP/HSP_Device.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/SI/SI_Device.h"
|
||||
#include "Core/IOS/Network/Socket.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/USBUtils.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
@@ -639,6 +643,45 @@ const std::array<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUM
|
||||
Info<s16>{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0},
|
||||
Info<s16>{{System::Main, "EmulatedUSBDevices", "LogitechMic4VolumeModifier"}, 0}};
|
||||
|
||||
static std::string GetDefaultTriforceIPRedirections()
|
||||
{
|
||||
#if defined(ANDROID)
|
||||
return "0.0.0.0/0=127.0.0.1";
|
||||
#else
|
||||
constexpr std::string_view entries[] = {
|
||||
|
||||
// Mario Kart Arcade GP 1 + 2
|
||||
// This config allows for 4x Multicabinet on the same PC.
|
||||
"192.168.29.150:5000-5008=127.0.0.1:5000-5008 MarioKart Seat #1 Static",
|
||||
"192.168.29.151:5000-5008=127.0.0.1:5010-5018 MarioKart Seat #2 Static",
|
||||
"192.168.29.152:5000-5008=127.0.0.1:5020-5028 MarioKart Seat #3 Static",
|
||||
"192.168.29.153:5000-5008=127.0.0.1:5030-5038 MarioKart Seat #4 Static",
|
||||
// Ephemeral ports are constrained to differentiate incoming connections.
|
||||
"192.168.29.150=127.0.0.1:50000-50999 MarioKart Seat #1 Ephemeral",
|
||||
"192.168.29.151=127.0.0.1:51000-51999 MarioKart Seat #2 Ephemeral",
|
||||
"192.168.29.152=127.0.0.1:52000-52999 MarioKart Seat #3 Ephemeral",
|
||||
"192.168.29.153=127.0.0.1:53000-53999 MarioKart Seat #4 Ephemeral",
|
||||
// The cameras.
|
||||
"192.168.29.104-107=127.0.0.1 MarioKart namcam2",
|
||||
|
||||
// CyCraft Connect IP
|
||||
"192.168.11.0/24=127.0.0.1 CyCraft",
|
||||
|
||||
// The Key of Avalon
|
||||
// This config isn't usable as-is. It's just here for reference.
|
||||
"192.168.13.0/24=127.0.0.1 The Key of Avalon",
|
||||
|
||||
// Sega ALL.Net
|
||||
"192.168.150.0/24=127.0.0.1 ALL.Net",
|
||||
};
|
||||
|
||||
return fmt::format("{}", fmt::join(entries, ","));
|
||||
#endif
|
||||
}
|
||||
|
||||
const Info<std::string> MAIN_TRIFORCE_IP_REDIRECTIONS{
|
||||
{System::Main, "Core", "TriforceIPRedirections"}, GetDefaultTriforceIPRedirections()};
|
||||
|
||||
// The reason we need this function is because some memory card code
|
||||
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
|
||||
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
|
||||
@@ -672,6 +715,9 @@ const char* GetDirectoryForRegion(DiscIO::Region region, RegionDirectoryStyle st
|
||||
ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region");
|
||||
return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR;
|
||||
|
||||
case DiscIO::Region::DEV:
|
||||
return DEV_DIR;
|
||||
|
||||
default:
|
||||
ASSERT_MSG(BOOT, false, "Default case should not be reached");
|
||||
return EUR_DIR;
|
||||
@@ -725,6 +771,7 @@ std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::
|
||||
constexpr std::string_view us_region = "." USA_DIR;
|
||||
constexpr std::string_view jp_region = "." JAP_DIR;
|
||||
constexpr std::string_view eu_region = "." EUR_DIR;
|
||||
constexpr std::string_view dv_region = "." DEV_DIR;
|
||||
std::optional<DiscIO::Region> path_region = std::nullopt;
|
||||
if (name.ends_with(us_region))
|
||||
{
|
||||
@@ -741,6 +788,11 @@ std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::
|
||||
name = name.substr(0, name.size() - eu_region.size());
|
||||
path_region = DiscIO::Region::PAL;
|
||||
}
|
||||
else if (name.ends_with(dv_region))
|
||||
{
|
||||
name = name.substr(0, name.size() - dv_region.size());
|
||||
path_region = DiscIO::Region::DEV;
|
||||
}
|
||||
|
||||
const DiscIO::Region used_region =
|
||||
region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION));
|
||||
@@ -784,6 +836,7 @@ std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::
|
||||
constexpr std::string_view us_region = "/" USA_DIR;
|
||||
constexpr std::string_view jp_region = "/" JPN_DIR;
|
||||
constexpr std::string_view eu_region = "/" EUR_DIR;
|
||||
constexpr std::string_view dv_region = "/" DEV_DIR;
|
||||
std::string_view base_path = configured_folder;
|
||||
std::optional<DiscIO::Region> path_region = std::nullopt;
|
||||
if (base_path.ends_with(us_region))
|
||||
@@ -801,6 +854,11 @@ std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::
|
||||
base_path = base_path.substr(0, base_path.size() - eu_region.size());
|
||||
path_region = DiscIO::Region::PAL;
|
||||
}
|
||||
else if (base_path.ends_with(dv_region))
|
||||
{
|
||||
base_path = base_path.substr(0, base_path.size() - dv_region.size());
|
||||
path_region = DiscIO::Region::DEV;
|
||||
}
|
||||
|
||||
const DiscIO::Region used_region =
|
||||
region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION));
|
||||
|
||||
@@ -386,6 +386,8 @@ extern const std::array<Info<std::string>, EMULATED_LOGITECH_MIC_COUNT>
|
||||
extern const std::array<Info<bool>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED;
|
||||
extern const std::array<Info<s16>, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER;
|
||||
|
||||
extern const Info<std::string> MAIN_TRIFORCE_IP_REDIRECTIONS;
|
||||
|
||||
// GameCube path utility functions
|
||||
|
||||
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
|
||||
|
||||
@@ -142,12 +142,6 @@ const std::string SConfig::GetTitleDescription() const
|
||||
return m_title_description;
|
||||
}
|
||||
|
||||
std::string SConfig::GetTriforceID() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
|
||||
return m_triforce_id;
|
||||
}
|
||||
|
||||
u64 SConfig::GetTitleID() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
|
||||
@@ -163,7 +157,7 @@ u16 SConfig::GetRevision() const
|
||||
void SConfig::ResetRunningGameMetadata()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
|
||||
SetRunningGameMetadata("00000000", "", "", 0, 0, DiscIO::Region::Unknown);
|
||||
SetRunningGameMetadata("00000000", "", 0, 0, DiscIO::Region::Unknown);
|
||||
}
|
||||
|
||||
void SConfig::SetRunningGameMetadata(const DiscIO::Volume& volume,
|
||||
@@ -172,14 +166,14 @@ void SConfig::SetRunningGameMetadata(const DiscIO::Volume& volume,
|
||||
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
|
||||
if (partition == volume.GetGamePartition())
|
||||
{
|
||||
SetRunningGameMetadata(volume.GetGameID(), volume.GetGameTDBID(), volume.GetTriforceID(),
|
||||
SetRunningGameMetadata(volume.GetGameID(), volume.GetGameTDBID(),
|
||||
volume.GetTitleID().value_or(0), volume.GetRevision().value_or(0),
|
||||
volume.GetRegion());
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRunningGameMetadata(volume.GetGameID(partition), volume.GetGameTDBID(partition),
|
||||
volume.GetTriforceID(), volume.GetTitleID(partition).value_or(0),
|
||||
volume.GetTitleID(partition).value_or(0),
|
||||
volume.GetRevision(partition).value_or(0), volume.GetRegion());
|
||||
}
|
||||
}
|
||||
@@ -197,28 +191,25 @@ void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Plat
|
||||
!Core::System::GetInstance().GetDVDInterface().UpdateRunningGameMetadata(tmd_title_id))
|
||||
{
|
||||
// If not launching a disc game, just read everything from the TMD.
|
||||
SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), "", tmd_title_id,
|
||||
tmd.GetTitleVersion(), tmd.GetRegion());
|
||||
SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), tmd_title_id, tmd.GetTitleVersion(),
|
||||
tmd.GetRegion());
|
||||
}
|
||||
}
|
||||
|
||||
void SConfig::SetRunningGameMetadata(const std::string& game_id)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
|
||||
SetRunningGameMetadata(game_id, "", "", 0, 0, DiscIO::Region::Unknown);
|
||||
SetRunningGameMetadata(game_id, "", 0, 0, DiscIO::Region::Unknown);
|
||||
}
|
||||
|
||||
void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id,
|
||||
std::string triforce_id, u64 title_id, u16 revision,
|
||||
DiscIO::Region region)
|
||||
u64 title_id, u16 revision, DiscIO::Region region)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
|
||||
const bool was_changed = m_game_id != game_id || m_gametdb_id != gametdb_id ||
|
||||
m_triforce_id != triforce_id || m_title_id != title_id ||
|
||||
m_revision != revision;
|
||||
m_title_id != title_id || m_revision != revision;
|
||||
m_game_id = game_id;
|
||||
m_gametdb_id = gametdb_id;
|
||||
m_triforce_id = triforce_id;
|
||||
m_title_id = title_id;
|
||||
m_revision = revision;
|
||||
|
||||
@@ -251,7 +242,7 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri
|
||||
const Core::TitleDatabase title_database;
|
||||
auto& system = Core::System::GetInstance();
|
||||
const DiscIO::Language language = GetLanguageAdjustedForRegion(system.IsWii(), region);
|
||||
m_title_name = title_database.GetTitleName(m_gametdb_id, m_triforce_id, language);
|
||||
m_title_name = title_database.GetTitleName(m_gametdb_id, language);
|
||||
m_title_description = title_database.Describe(m_gametdb_id, language);
|
||||
NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description);
|
||||
Host_TitleChanged();
|
||||
@@ -458,6 +449,9 @@ bool SConfig::SetPathsAndGameMetadata(Core::System& system, const BootParameters
|
||||
if (m_region == DiscIO::Region::Unknown)
|
||||
m_region = Config::Get(Config::MAIN_FALLBACK_REGION);
|
||||
|
||||
if (system.IsTriforce())
|
||||
m_region = DiscIO::Region::DEV;
|
||||
|
||||
// Set up paths
|
||||
const std::string region_dir = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(m_region));
|
||||
m_strSRAM = File::GetUserPath(F_GCSRAM_IDX);
|
||||
|
||||
@@ -54,7 +54,6 @@ struct SConfig
|
||||
// files
|
||||
std::string m_strBootROM;
|
||||
std::string m_strSRAM;
|
||||
|
||||
std::string m_debugger_game_id;
|
||||
|
||||
// TODO: remove this as soon as the ticket view hack in IOS/ES/Views is dropped.
|
||||
@@ -64,7 +63,6 @@ struct SConfig
|
||||
const std::string GetGameTDBID() const;
|
||||
const std::string GetTitleName() const;
|
||||
const std::string GetTitleDescription() const;
|
||||
std::string GetTriforceID() const;
|
||||
u64 GetTitleID() const;
|
||||
u16 GetRevision() const;
|
||||
void ResetRunningGameMetadata();
|
||||
@@ -121,15 +119,13 @@ private:
|
||||
static void ReloadTextures(Core::System& system);
|
||||
|
||||
void SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id,
|
||||
std::string triforce_id, u64 title_id, u16 revision,
|
||||
DiscIO::Region region);
|
||||
u64 title_id, u16 revision, DiscIO::Region region);
|
||||
|
||||
static SConfig* m_Instance;
|
||||
mutable std::recursive_mutex m_metadata_lock;
|
||||
|
||||
std::string m_game_id;
|
||||
std::string m_gametdb_id;
|
||||
std::string m_triforce_id;
|
||||
std::string m_title_name;
|
||||
std::string m_title_description;
|
||||
u64 m_title_id;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,274 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Network.h"
|
||||
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AMMediaboard
|
||||
{
|
||||
|
||||
enum GameType
|
||||
{
|
||||
FZeroAX = 1,
|
||||
FZeroAXMonster,
|
||||
MarioKartGP,
|
||||
MarioKartGP2,
|
||||
VirtuaStriker3,
|
||||
VirtuaStriker4,
|
||||
VirtuaStriker4_2006,
|
||||
GekitouProYakyuu,
|
||||
KeyOfAvalon,
|
||||
FirmwareUpdate,
|
||||
};
|
||||
|
||||
enum MediaType
|
||||
{
|
||||
GDROM = 1,
|
||||
NAND,
|
||||
};
|
||||
|
||||
enum MediaBoardType
|
||||
{
|
||||
NANDMaskBoardHDD = 0,
|
||||
NANDMaskBoardMask = 1,
|
||||
NANDMaskBoardNAND = 2,
|
||||
DIMMBoardType3 = 4,
|
||||
};
|
||||
|
||||
enum MediaBoardStatus
|
||||
{
|
||||
Initializing = 0,
|
||||
CheckingNetwork = 1,
|
||||
SystemDisc = 2,
|
||||
TestingGameProgram = 3,
|
||||
LoadingGameProgram = 4,
|
||||
LoadedGameProgram = 5,
|
||||
Error = 6,
|
||||
};
|
||||
|
||||
enum InquiryType
|
||||
{
|
||||
Version1 = 0x21484100,
|
||||
Version2 = 0x29484100,
|
||||
};
|
||||
|
||||
struct MediaBoardRange
|
||||
{
|
||||
u32 start;
|
||||
u32 end;
|
||||
u8* buffer;
|
||||
std::size_t buffer_size;
|
||||
|
||||
MediaBoardRange(u32 start_, u32 size_, std::span<u8> buffer_);
|
||||
};
|
||||
|
||||
enum class AMMBDICommand : u16
|
||||
{
|
||||
Inquiry = 0x12,
|
||||
Read = 0xA8,
|
||||
Write = 0xAA,
|
||||
Execute = 0xAB,
|
||||
};
|
||||
|
||||
enum class AMMBCommand : u16
|
||||
{
|
||||
Unknown_000 = 0x000,
|
||||
GetDIMMSize = 0x001,
|
||||
|
||||
GetMediaBoardStatus = 0x100,
|
||||
GetSegaBootVersion = 0x101,
|
||||
GetSystemFlags = 0x102,
|
||||
GetMediaBoardSerial = 0x103,
|
||||
Unknown_104 = 0x104,
|
||||
|
||||
NetworkReInit = 0x204,
|
||||
|
||||
TestHardware = 0x301,
|
||||
|
||||
// Network used by Mario Kart GPs
|
||||
Accept = 0x401,
|
||||
Bind = 0x402,
|
||||
Closesocket = 0x403,
|
||||
Connect = 0x404,
|
||||
GetIPbyDNS = 0x405,
|
||||
InetAddr = 0x406,
|
||||
Ioctl = 0x407, // Unused
|
||||
Listen = 0x408,
|
||||
Recv = 0x409,
|
||||
Send = 0x40A,
|
||||
Socket = 0x40B,
|
||||
Select = 0x40C,
|
||||
Shutdown = 0x40D, // Unused
|
||||
SetSockOpt = 0x40E,
|
||||
GetSockOpt = 0x40F, // Unused
|
||||
SetTimeOuts = 0x410,
|
||||
GetLastError = 0x411,
|
||||
RouteAdd = 0x412, // Unused
|
||||
RouteDelete = 0x413, // Unused
|
||||
GetParambyDHCPExec = 0x414,
|
||||
ModifyMyIPaddr = 0x415,
|
||||
Recvfrom = 0x416, // Unused
|
||||
Sendto = 0x417, // Unused
|
||||
RecvDimmImage = 0x418, // Unused
|
||||
SendDimmImage = 0x419, // Unused
|
||||
|
||||
// Network used by F-Zero AX
|
||||
InitLink = 0x601,
|
||||
Unknown_605 = 0x605,
|
||||
SetupLink = 0x606,
|
||||
SearchDevices = 0x607,
|
||||
Unknown_608 = 0x608,
|
||||
Unknown_614 = 0x614,
|
||||
|
||||
// All.Net used by Virtua Striker 4 2006 (Japan)
|
||||
AllNetInit = 0x700,
|
||||
Unknown_701 = 0x701,
|
||||
Unknown_702 = 0x702,
|
||||
Unknown_703 = 0x703,
|
||||
Unknown_704 = 0x704,
|
||||
|
||||
// NETDIMM Commands
|
||||
Unknown_001 = 0x001,
|
||||
GetNetworkFirmVersion = 0x101,
|
||||
Unknown_103 = 0x103,
|
||||
};
|
||||
|
||||
enum MediaBoardAddress : u32
|
||||
{
|
||||
MediaBoardStatus1 = 0x80000000,
|
||||
MediaBoardStatus2 = 0x80000020,
|
||||
MediaBoardStatus3 = 0x80000040,
|
||||
|
||||
FirmwareStatus1 = 0x80000120,
|
||||
FirmwareStatus2 = 0x80000140,
|
||||
|
||||
BackupMemory = 0x000006A0,
|
||||
|
||||
DIMMMemory = 0x1F000000,
|
||||
DIMMMemory2 = 0xFF000000,
|
||||
|
||||
DIMMExtraSettings = 0x1FFEFFE0,
|
||||
|
||||
AllNetSettings = 0x1EFF8000,
|
||||
|
||||
NetworkControl = 0xFFFF0000,
|
||||
|
||||
DIMMCommandVersion1 = 0x1F900000,
|
||||
DIMMCommandVersion2 = 0x84000000,
|
||||
DIMMCommandVersion2_2 = 0x89000000,
|
||||
DIMMCommandExecute1 = 0x84000040,
|
||||
DIMMCommandExecute2 = 0x88000000,
|
||||
|
||||
NetworkCommandAddress1 = 0x1F800200,
|
||||
NetworkCommandAddress2 = 0x89040200,
|
||||
|
||||
NetworkBufferAddress1 = 0x1FA00000,
|
||||
NetworkBufferAddress2 = 0x1FD00000,
|
||||
NetworkBufferAddress3 = 0x89100000,
|
||||
NetworkBufferAddress4 = 0x89180000,
|
||||
NetworkBufferAddress5 = 0x1FB00000,
|
||||
|
||||
AllNetBuffer = 0x89010000,
|
||||
|
||||
FirmwareAddress = 0x84800000,
|
||||
|
||||
FirmwareMagicWrite1 = 0x00600000,
|
||||
FirmwareMagicWrite2 = 0x00700000,
|
||||
};
|
||||
|
||||
// Mario Kart GP2 has a complete list of them
|
||||
// but in japanese
|
||||
// They somewhat match WSA errors codes
|
||||
enum SocketStatusCodes
|
||||
{
|
||||
SSC_E_4 = -4, // Failure (abnormal argument)
|
||||
SSC_E_3 = -3, // Success (unsupported command)
|
||||
SSC_E_2 =
|
||||
-2, // Failure (failed to send, abnormal argument, or communication condition violation)
|
||||
SSC_E_1 = -1, // Failure (error termination)
|
||||
|
||||
SSC_EINTR = 4, // An interrupt occurred before data reception was completed
|
||||
SSC_EBADF = 9, // Invalid descriptor
|
||||
SSC_E_11 = 11, // Send operation was blocked on a non-blocking mode socket
|
||||
SSC_EACCES = 13, // The socket does not support broadcast addresses, but the destination address
|
||||
// is a broadcast address
|
||||
SSC_EFAULT =
|
||||
14, // The name argument specifies a location other than an address used by the process.
|
||||
SSC_E_23 = 23, // System file table is full.
|
||||
SSC_AEMFILE = 24, // Process descriptor table is full.
|
||||
SSC_EMSGSIZE =
|
||||
36, // Socket tried to send message without splitting, but message size is too large.
|
||||
SSC_EAFNOSUPPORT = 47, // Address prohibited for use on this socket.
|
||||
SSC_EADDRINUSE = 48, // Address already in use.
|
||||
SSC_EADDRNOTAVAIL = 49, // Prohibited address.
|
||||
SSC_E_50 = 50, // Non-socket descriptor.
|
||||
SSC_ENETUNREACH = 51, // Cannot access specified network.
|
||||
SSC_ENOBUFS = 55, // Insufficient buffer
|
||||
SSC_EISCONN = 56, // Already connected socket
|
||||
SSC_ENOTCONN = 57, // No connection for connection-type socket
|
||||
SSC_ETIMEDOUT = 60, // Timeout
|
||||
SSC_ECONNREFUSED = 61, // Connection request forcibly rejected
|
||||
SSC_EHOSTUNREACH = 65, // Remote host cannot be reached
|
||||
SSC_EHOSTDOWN = 67, // Remote host is down
|
||||
SSC_EWOULDBLOCK = 70, // Socket is in non-blocking mode and connection has not been completed
|
||||
SSC_E_69 = 69, // Socket is in non-blocking mode and a previously issued Connect command has not
|
||||
// been completed
|
||||
SSC_SUCCESS = 70,
|
||||
};
|
||||
|
||||
static constexpr std::size_t SOCKET_FD_MAX = 64;
|
||||
|
||||
void Init();
|
||||
void FirmwareMap(bool on);
|
||||
void InitDIMM(const DiscIO::Volume& volume);
|
||||
void InitKeys(u32 key_a, u32 key_b, u32 key_c);
|
||||
u32 ExecuteCommand(std::array<u32, 3>& dicmd_buf, u32* diimm_buf, u32 address, u32 length);
|
||||
u32 GetGameType();
|
||||
u32 GetMediaType();
|
||||
bool GetTestMenu();
|
||||
void Shutdown();
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
struct ParsedIPRedirection
|
||||
{
|
||||
std::string_view original;
|
||||
std::string_view replacement;
|
||||
std::string_view description;
|
||||
};
|
||||
|
||||
std::optional<ParsedIPRedirection> ParseIPRedirection(std::string_view str);
|
||||
|
||||
struct IPRedirection
|
||||
{
|
||||
Common::IPv4PortRange original;
|
||||
Common::IPv4PortRange replacement;
|
||||
|
||||
// Caller should check if it matches first!
|
||||
Common::IPv4Port Apply(Common::IPv4Port subject) const;
|
||||
Common::IPv4Port Reverse(Common::IPv4Port subject) const;
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
using IPRedirections = std::vector<IPRedirection>;
|
||||
|
||||
IPRedirections GetIPRedirections();
|
||||
|
||||
s32 DebuggerGetSocket(u32 triforce_fd);
|
||||
|
||||
}; // namespace AMMediaboard
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/DolphinAnalytics.h"
|
||||
#include "Core/HW/AudioInterface.h"
|
||||
#include "Core/HW/DVD/AMMediaboard.h"
|
||||
#include "Core/HW/DVD/DVDMath.h"
|
||||
#include "Core/HW/DVD/DVDThread.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
@@ -123,6 +124,12 @@ void DVDInterface::DoState(PointerWrap& p)
|
||||
m_system.GetDVDThread().DoState(p);
|
||||
|
||||
m_adpcm_decoder.DoState(p);
|
||||
|
||||
if (m_system.IsTriforce())
|
||||
{
|
||||
AMMediaboard::DoState(p);
|
||||
p.DoMarker("AMMediaboard");
|
||||
}
|
||||
}
|
||||
|
||||
size_t DVDInterface::ProcessDTKSamples(s16* target_samples, size_t target_block_count,
|
||||
@@ -281,6 +288,17 @@ void DVDInterface::Init()
|
||||
|
||||
u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::TCINT);
|
||||
core_timing.ScheduleEvent(0, m_finish_executing_command, userdata);
|
||||
|
||||
if (m_system.IsTriforce())
|
||||
{
|
||||
AMMediaboard::Init();
|
||||
|
||||
// The Triforce IPL expects the cover to be closed
|
||||
m_DICVR.Hex = 0;
|
||||
// The Triforce IPL checks this bit to set the physical memory to
|
||||
// either 50MB(unset) or 24MB(set)
|
||||
m_DICFG.Hex |= 8;
|
||||
}
|
||||
}
|
||||
|
||||
// Resets state on the MN102 chip in the drive itself, but not the DI registers exposed on the
|
||||
@@ -310,7 +328,7 @@ void DVDInterface::ResetDrive(bool spinup)
|
||||
else if (!spinup)
|
||||
{
|
||||
// Wii hardware tests indicate that this is used when ejecting and inserting a new disc, or
|
||||
// performing a reset without spinup.
|
||||
// performing a reset without spin-up.
|
||||
SetDriveState(DriveState::DiscChangeDetected);
|
||||
}
|
||||
else
|
||||
@@ -330,6 +348,11 @@ void DVDInterface::ResetDrive(bool spinup)
|
||||
void DVDInterface::Shutdown()
|
||||
{
|
||||
m_system.GetDVDThread().Stop();
|
||||
|
||||
if (m_system.IsTriforce())
|
||||
{
|
||||
AMMediaboard::Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
static u64 GetDiscEndOffset(const DiscIO::VolumeDisc& disc)
|
||||
@@ -697,11 +720,16 @@ bool DVDInterface::CheckReadPreconditions()
|
||||
SetDriveError(DriveError::MotorStopped);
|
||||
return false;
|
||||
}
|
||||
if (m_drive_state == DriveState::DiscIdNotRead)
|
||||
|
||||
// SegaBoot doesn't read the Disc ID
|
||||
if (!m_system.IsTriforce())
|
||||
{
|
||||
ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read.");
|
||||
SetDriveError(DriveError::NoDiscID);
|
||||
return false;
|
||||
if (m_drive_state == DriveState::DiscIdNotRead)
|
||||
{
|
||||
ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read.");
|
||||
SetDriveError(DriveError::NoDiscID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -753,12 +781,19 @@ void DVDInterface::ExecuteCommand(ReplyType reply_type)
|
||||
DIInterruptType interrupt_type = DIInterruptType::TCINT;
|
||||
bool command_handled_by_thread = false;
|
||||
|
||||
// Swaps endian of Triforce DI commands, and zeroes out random bytes to prevent unknown read
|
||||
// subcommand errors
|
||||
auto& dvd_thread = m_system.GetDVDThread();
|
||||
if (dvd_thread.HasDisc() && dvd_thread.GetDiscType() == DiscIO::Platform::Triforce)
|
||||
if (m_system.IsTriforce())
|
||||
{
|
||||
// TODO(C++23): Use std::byteswap and a bitwise AND for increased clarity
|
||||
if (!AMMediaboard::ExecuteCommand(m_DICMDBUF, &m_DIIMMBUF, m_DIMAR, m_DILENGTH))
|
||||
{
|
||||
// Transfer is done
|
||||
m_DICR.TSTART = 0;
|
||||
m_DIMAR += m_DILENGTH;
|
||||
m_DILENGTH = 0;
|
||||
GenerateDIInterrupt(DIInterruptType::TCINT);
|
||||
m_error_code = DriveError::None;
|
||||
return;
|
||||
}
|
||||
// Normal read command pass on to normal handling
|
||||
m_DICMDBUF[0] <<= 24;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,7 @@ using ws_ssize_t = int;
|
||||
using ws_ssize_t = ssize_t;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#define SEND_FLAGS MSG_NOSIGNAL
|
||||
#else
|
||||
#define SEND_FLAGS 0
|
||||
#endif
|
||||
using Common::SEND_FLAGS;
|
||||
|
||||
TAPServerConnection::TAPServerConnection(const std::string& destination,
|
||||
std::function<void(std::string&&)> recv_cb,
|
||||
@@ -114,11 +110,7 @@ static int ConnectToDestination(const std::string& destination)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
int opt_no_sigpipe = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0)
|
||||
INFO_LOG_FMT(SP1, "Failed to set SO_NOSIGPIPE on socket\n");
|
||||
#endif
|
||||
Common::SetPlatformSocketOptions(fd);
|
||||
|
||||
if (connect(fd, reinterpret_cast<sockaddr*>(&ss), ss_size) == -1)
|
||||
{
|
||||
|
||||
@@ -66,18 +66,6 @@ void ExpansionInterfaceManager::AddMemoryCard(Slot slot)
|
||||
m_channels[SlotToEXIChannel(slot)]->AddDevice(memorycard_device, SlotToEXIDevice(slot));
|
||||
}
|
||||
|
||||
void ExpansionInterfaceManager::AddSP1Device()
|
||||
{
|
||||
EXIDeviceType sp1_device = EXIDeviceType::Baseboard;
|
||||
auto& system = Core::System::GetInstance();
|
||||
if (system.IsTriforce())
|
||||
{
|
||||
sp1_device = Config::Get(Config::MAIN_SERIAL_PORT_1);
|
||||
}
|
||||
|
||||
m_channels[0]->AddDevice(sp1_device, SlotToEXIDevice(Slot::SP1));
|
||||
}
|
||||
|
||||
u8 SlotToEXIChannel(Slot slot)
|
||||
{
|
||||
switch (slot)
|
||||
@@ -157,9 +145,15 @@ void ExpansionInterfaceManager::Init(const Sram* override_sram)
|
||||
AddMemoryCard(slot);
|
||||
|
||||
m_channels[0]->AddDevice(EXIDeviceType::MaskROM, 1);
|
||||
AddSP1Device();
|
||||
m_channels[SlotToEXIChannel(Slot::SP1)]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_1),
|
||||
SlotToEXIDevice(Slot::SP1));
|
||||
|
||||
// Automatically connect the Triforce Baseboard in Triforce mode
|
||||
EXIDeviceType sp1_device = Config::Get(Config::MAIN_SERIAL_PORT_1);
|
||||
if (m_system.IsTriforce())
|
||||
{
|
||||
sp1_device = EXIDeviceType::Baseboard;
|
||||
}
|
||||
|
||||
m_channels[SlotToEXIChannel(Slot::SP1)]->AddDevice(sp1_device, SlotToEXIDevice(Slot::SP1));
|
||||
m_channels[SlotToEXIChannel(Slot::SP2)]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_2),
|
||||
SlotToEXIDevice(Slot::SP2));
|
||||
|
||||
|
||||
@@ -89,7 +89,6 @@ public:
|
||||
|
||||
private:
|
||||
void AddMemoryCard(Slot slot);
|
||||
void AddSP1Device();
|
||||
|
||||
static void ChangeDeviceCallback(Core::System& system, u64 userdata, s64 cycles_late);
|
||||
static void UpdateInterruptsCallback(Core::System& system, u64 userdata, s64 cycles_late);
|
||||
|
||||
@@ -1,20 +1,107 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceBaseboard.h"
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/Buffer.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/DVD/AMMediaboard.h"
|
||||
#include "Core/HW/EXI/EXI.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
static bool s_interrupt_set = false;
|
||||
static u32 s_irq_timer = 0;
|
||||
static u32 s_irq_status = 0;
|
||||
|
||||
static u16 CheckSum(const u8* data, u32 length)
|
||||
{
|
||||
return std::accumulate(data, data + length, 0);
|
||||
}
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
|
||||
void GenerateInterrupt(int flag)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
s_interrupt_set = true;
|
||||
s_irq_timer = 0;
|
||||
s_irq_status = flag;
|
||||
|
||||
system.GetExpansionInterface().UpdateInterrupts();
|
||||
}
|
||||
|
||||
CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system)
|
||||
{
|
||||
std::string backup_filename(fmt::format("{}tribackup_{}.bin", File::GetUserPath(D_TRIUSER_IDX),
|
||||
SConfig::GetInstance().GetGameID()));
|
||||
|
||||
m_backup = File::IOFile(backup_filename, "rb+");
|
||||
if (!m_backup.IsOpen())
|
||||
{
|
||||
m_backup = File::IOFile(backup_filename, "wb+");
|
||||
}
|
||||
|
||||
// Some games share the same ID Client/Server
|
||||
if (!m_backup.IsGood())
|
||||
{
|
||||
PanicAlertFmt("Failed to open {}\nFile might be in use.", backup_filename.c_str());
|
||||
|
||||
std::srand(static_cast<u32>(std::time(nullptr)));
|
||||
|
||||
backup_filename = fmt::format("{}tribackup_tmp_{}{}.bin", File::GetUserPath(D_TRIUSER_IDX),
|
||||
rand(), SConfig::GetInstance().GetGameID());
|
||||
|
||||
m_backup = File::IOFile(backup_filename, "wb+");
|
||||
}
|
||||
|
||||
// Virtua Striker 4 and Gekitou Pro Yakyuu need a higher FIRM version
|
||||
// Which is read from the backup data?!
|
||||
if (AMMediaboard::GetGameType() == AMMediaboard::VirtuaStriker4 ||
|
||||
AMMediaboard::GetGameType() == AMMediaboard::GekitouProYakyuu)
|
||||
{
|
||||
const u64 backup_size = m_backup.GetSize();
|
||||
if (backup_size >= 0x20C + 0x1F4)
|
||||
{
|
||||
Common::UniqueBuffer<u8> data(backup_size);
|
||||
m_backup.ReadBytes(data.data(), data.size());
|
||||
|
||||
// Set FIRM version
|
||||
Common::BitCastPtr<u16>(data.data() + 0x12) = 0x1703;
|
||||
Common::BitCastPtr<u16>(data.data() + 0x212) = 0x1703;
|
||||
|
||||
// Update checksum
|
||||
Common::BitCastPtr<u16>(data.data() + 0x0A) = Common::swap16(CheckSum(&data[0xC], 0x1F4));
|
||||
Common::BitCastPtr<u16>(data.data() + 0x20A) = Common::swap16(CheckSum(&data[0x20C], 0x1F4));
|
||||
|
||||
m_backup.Seek(0, File::SeekOrigin::Begin);
|
||||
m_backup.WriteBytes(data.data(), data.size());
|
||||
m_backup.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CEXIBaseboard::~CEXIBaseboard()
|
||||
{
|
||||
}
|
||||
|
||||
void CEXIBaseboard::SetCS(int cs)
|
||||
{
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB: ChipSelect={}", cs);
|
||||
if (cs)
|
||||
m_position = 0;
|
||||
}
|
||||
@@ -24,33 +111,203 @@ bool CEXIBaseboard::IsPresent() const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CEXIBaseboard::IsInterruptSet()
|
||||
{
|
||||
if (s_interrupt_set)
|
||||
{
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB: IRQ");
|
||||
if (++s_irq_timer > 12)
|
||||
s_interrupt_set = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CEXIBaseboard::DMAWrite(u32 addr, u32 size)
|
||||
{
|
||||
const auto& system = Core::System::GetInstance();
|
||||
const auto& memory = system.GetMemory();
|
||||
auto span = memory.GetSpanForAddress(addr);
|
||||
|
||||
if (span.size() < size)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "AM-BB: Backup DMA Write overflow: address=0x{:08x}, length={}, span={}",
|
||||
addr, size, span.size());
|
||||
return;
|
||||
}
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Write: {:08x} {:x}", addr, size);
|
||||
|
||||
m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin);
|
||||
m_backup.WriteBytes(span.data(), size);
|
||||
m_backup.Flush();
|
||||
}
|
||||
|
||||
void CEXIBaseboard::DMARead(u32 addr, u32 size)
|
||||
{
|
||||
const auto& system = Core::System::GetInstance();
|
||||
const auto& memory = system.GetMemory();
|
||||
auto span = memory.GetSpanForAddress(addr);
|
||||
|
||||
if (span.size() < size)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "AM-BB: Backup DMA Read overflow: address=0x{:08x}, length={}, span={}",
|
||||
addr, size, span.size());
|
||||
return;
|
||||
}
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Read: {:08x} {:x}", addr, size);
|
||||
|
||||
m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin);
|
||||
m_backup.ReadBytes(span.data(), size);
|
||||
}
|
||||
|
||||
void CEXIBaseboard::TransferByte(u8& byte)
|
||||
{
|
||||
if (m_position == 0)
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB: > {:02x}", byte);
|
||||
if (m_position < 4)
|
||||
{
|
||||
m_command = byte;
|
||||
m_command[m_position] = byte;
|
||||
byte = 0xFF;
|
||||
}
|
||||
|
||||
if ((m_position >= 2) && (m_command[0] == 0 && m_command[1] == 0))
|
||||
{
|
||||
// Read serial ID
|
||||
byte = "\x06\x04\x10\x00"[(m_position - 2) & 3];
|
||||
}
|
||||
else if (m_position == 3)
|
||||
{
|
||||
u32 checksum = (m_command[0] << 24) | (m_command[1] << 16) | (m_command[2] << 8);
|
||||
u32 bit = 0x80000000UL;
|
||||
u32 check = 0x8D800000UL;
|
||||
while (bit >= 0x100)
|
||||
{
|
||||
if (checksum & bit)
|
||||
checksum ^= check;
|
||||
check >>= 1;
|
||||
bit >>= 1;
|
||||
}
|
||||
|
||||
if (m_command[3] != (checksum & 0xFF))
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB: cs: {:02x}, w: {:02x}", m_command[3], checksum & 0xFF);
|
||||
}
|
||||
else if (m_position == 4)
|
||||
{
|
||||
switch (m_command[0])
|
||||
{
|
||||
case BackupOffsetSet:
|
||||
m_backup_offset = (m_command[1] << 8) | m_command[2];
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupOffsetSet:{:04x}", m_backup_offset);
|
||||
m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin);
|
||||
byte = 0x01;
|
||||
break;
|
||||
case BackupWrite:
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupWrite:{:04x}-{:02x}", m_backup_offset,
|
||||
m_command[1]);
|
||||
m_backup.WriteBytes(&m_command[1], 1);
|
||||
m_backup.Flush();
|
||||
byte = 0x01;
|
||||
break;
|
||||
case BackupRead:
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupRead :{:04x}", m_backup_offset);
|
||||
byte = 0x01;
|
||||
break;
|
||||
case DMAOffsetLengthSet:
|
||||
m_backup_dma_offset = (m_command[1] << 8) | m_command[2];
|
||||
m_backup_dma_length = m_command[3];
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: DMAOffsetLengthSet :{:04x} {:02x}", m_backup_dma_offset,
|
||||
m_backup_dma_length);
|
||||
byte = 0x01;
|
||||
break;
|
||||
case ReadISR:
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: ReadISR :{:02x} {:02x}:{:02x} {:02x}", m_command[1],
|
||||
m_command[2], 4, s_irq_status);
|
||||
byte = 0x04;
|
||||
break;
|
||||
case WriteISR:
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteISR :{:02x} {:02x}", m_command[1], m_command[2]);
|
||||
s_irq_status &= ~(m_command[2]);
|
||||
byte = 0x04;
|
||||
break;
|
||||
// 2 byte out
|
||||
case ReadIMR:
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: ReadIMR :{:02x} {:02x}", m_command[1], m_command[2]);
|
||||
byte = 0x04;
|
||||
break;
|
||||
case WriteIMR:
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteIMR :{:02x} {:02x}", m_command[1], m_command[2]);
|
||||
byte = 0x04;
|
||||
break;
|
||||
case WriteLANCNT:
|
||||
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteLANCNT :{:02x} {:02x}", m_command[1], m_command[2]);
|
||||
if ((m_command[1] == 0) && (m_command[2] == 0))
|
||||
{
|
||||
s_interrupt_set = true;
|
||||
s_irq_timer = 0;
|
||||
s_irq_status = 0x02;
|
||||
}
|
||||
if ((m_command[1] == 2) && (m_command[2] == 1))
|
||||
{
|
||||
s_irq_status = 0;
|
||||
}
|
||||
byte = 0x08;
|
||||
break;
|
||||
default:
|
||||
byte = 4;
|
||||
ERROR_LOG_FMT(SP1, "AM-BB: COMMAND: {:02x} {:02x} {:02x}", m_command[0], m_command[1],
|
||||
m_command[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (m_position > 4)
|
||||
{
|
||||
switch (m_command[0])
|
||||
{
|
||||
// 1 byte out
|
||||
case BackupRead:
|
||||
m_backup.ReadBytes(&byte, 1);
|
||||
break;
|
||||
case DMAOffsetLengthSet:
|
||||
byte = 0x01;
|
||||
break;
|
||||
// 2 byte out
|
||||
case ReadISR:
|
||||
if (m_position == 6)
|
||||
{
|
||||
byte = s_irq_status;
|
||||
s_interrupt_set = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte = 0x04;
|
||||
}
|
||||
break;
|
||||
// 2 byte out
|
||||
case ReadIMR:
|
||||
if (m_position == 5)
|
||||
byte = 0xFF;
|
||||
if (m_position == 6)
|
||||
byte = 0x81;
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG_FMT(SP1, "Unknown AM-BB command: {:02x}", m_command[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (m_command)
|
||||
{
|
||||
case 0x00:
|
||||
{
|
||||
static constexpr std::array<u8, 4> ID = {0x06, 0x04, 0x10, 0x00};
|
||||
byte = ID[(m_position - 2) & 3];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "EXI BASEBOARD: Unhandled command {:#x} {:#x}", m_command,
|
||||
m_position);
|
||||
}
|
||||
byte = 0xFF;
|
||||
}
|
||||
|
||||
DEBUG_LOG_FMT(SP1, "AM-BB < {:02x}", byte);
|
||||
m_position++;
|
||||
}
|
||||
|
||||
void CEXIBaseboard::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(m_position);
|
||||
p.Do(s_interrupt_set);
|
||||
p.Do(m_command);
|
||||
}
|
||||
|
||||
} // namespace ExpansionInterface
|
||||
@@ -1,27 +1,59 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Core/HW/EXI/EXI_Device.h"
|
||||
|
||||
class PointerWrap;
|
||||
namespace Core
|
||||
{
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
class CEXIBaseboard : public IEXIDevice
|
||||
void GenerateInterrupt(int flag);
|
||||
|
||||
class CEXIBaseboard final : public IEXIDevice
|
||||
{
|
||||
public:
|
||||
CEXIBaseboard(Core::System& system);
|
||||
explicit CEXIBaseboard(Core::System& system);
|
||||
~CEXIBaseboard() override;
|
||||
|
||||
void SetCS(int cs) override;
|
||||
bool IsInterruptSet() override;
|
||||
bool IsPresent() const override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
void DMAWrite(u32 addr, u32 size) override;
|
||||
void DMARead(u32 addr, u32 size) override;
|
||||
|
||||
private:
|
||||
// STATE_TO_SAVE
|
||||
u32 m_position = 0;
|
||||
u32 m_command = 0;
|
||||
enum Command
|
||||
{
|
||||
BackupOffsetSet = 0x01,
|
||||
BackupWrite = 0x02,
|
||||
BackupRead = 0x03,
|
||||
|
||||
DMAOffsetLengthSet = 0x05,
|
||||
|
||||
ReadISR = 0x82,
|
||||
WriteISR = 0x83,
|
||||
ReadIMR = 0x86,
|
||||
WriteIMR = 0x87,
|
||||
|
||||
WriteLANCNT = 0xFF,
|
||||
};
|
||||
|
||||
u32 m_position = 0;
|
||||
u32 m_backup_dma_offset = 0;
|
||||
u32 m_backup_dma_length = 0;
|
||||
std::array<u8, 4> m_command{};
|
||||
u16 m_backup_offset = 0;
|
||||
File::IOFile m_backup;
|
||||
|
||||
protected:
|
||||
void TransferByte(u8& byte) override;
|
||||
};
|
||||
} // namespace ExpansionInterface
|
||||
@@ -34,6 +34,7 @@ static const u16 trigger_bitmasks[] = {
|
||||
|
||||
static const u16 dpad_bitmasks[] = {PAD_BUTTON_UP, PAD_BUTTON_DOWN, PAD_BUTTON_LEFT,
|
||||
PAD_BUTTON_RIGHT};
|
||||
static const u8 triforce_bitmask[] = {SWITCH_TEST, SWITCH_SERVICE, SWITCH_COIN};
|
||||
|
||||
GCPad::GCPad(const unsigned int index) : m_index(index)
|
||||
{
|
||||
@@ -68,6 +69,13 @@ GCPad::GCPad(const unsigned int index) : m_index(index)
|
||||
m_dpad->AddInput(Translatability::Translate, named_direction);
|
||||
}
|
||||
|
||||
// triforce
|
||||
groups.emplace_back(m_triforce = new ControllerEmu::Buttons(TRIFORCE_GROUP));
|
||||
for (const char* named_button : {TEST_BUTTON, SERVICE_BUTTON, COIN_BUTTON})
|
||||
{
|
||||
m_triforce->AddInput(Translatability::Translate, named_button);
|
||||
}
|
||||
|
||||
// Microphone
|
||||
groups.emplace_back(m_mic = new ControllerEmu::Buttons(MIC_GROUP));
|
||||
m_mic->AddInput(Translatability::Translate, _trans("Button"));
|
||||
@@ -119,6 +127,8 @@ ControllerEmu::ControlGroup* GCPad::GetGroup(PadGroup group)
|
||||
return m_mic;
|
||||
case PadGroup::Options:
|
||||
return m_options;
|
||||
case PadGroup::Triforce:
|
||||
return m_triforce;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
@@ -150,6 +160,9 @@ GCPadStatus GCPad::GetInput() const
|
||||
// dpad
|
||||
m_dpad->GetState(&pad.button, dpad_bitmasks, m_input_override_function);
|
||||
|
||||
// triforce
|
||||
m_triforce->GetState(&pad.switches, triforce_bitmask, m_input_override_function);
|
||||
|
||||
// sticks
|
||||
const auto main_stick_state = m_main_stick->GetState(m_input_override_function);
|
||||
pad.stickX = MapFloat<u8>(main_stick_state.x, GCPadStatus::MAIN_STICK_CENTER_X, 1);
|
||||
@@ -181,6 +194,9 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface)
|
||||
#ifdef ANDROID
|
||||
// Rumble
|
||||
m_rumble->SetControlExpression(0, "`Android/0/Device Sensors:Motor 0`");
|
||||
|
||||
// Triforce Coin
|
||||
m_triforce->SetControlExpression(2, "pulse(`Android/0/Device Sensors:Accel Down` > 15, 0.1)");
|
||||
#else
|
||||
// Buttons: A, B, X, Y, Z
|
||||
m_buttons->SetControlExpression(0, "`X`");
|
||||
@@ -202,6 +218,11 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface)
|
||||
m_dpad->SetControlExpression(2, "`F`"); // Left
|
||||
m_dpad->SetControlExpression(3, "`H`"); // Right
|
||||
|
||||
// Triforce
|
||||
m_triforce->SetControlExpression(0, "`1`"); // Test
|
||||
m_triforce->SetControlExpression(1, "`2`"); // Service
|
||||
m_triforce->SetControlExpression(2, "`3`"); // Coin
|
||||
|
||||
// C Stick
|
||||
m_c_stick->SetControlExpression(0, "`I`"); // Up
|
||||
m_c_stick->SetControlExpression(1, "`K`"); // Down
|
||||
|
||||
@@ -29,7 +29,8 @@ enum class PadGroup
|
||||
Triggers,
|
||||
Rumble,
|
||||
Mic,
|
||||
Options
|
||||
Options,
|
||||
Triforce,
|
||||
};
|
||||
|
||||
class GCPad : public ControllerEmu::EmulatedController
|
||||
@@ -61,6 +62,7 @@ public:
|
||||
static constexpr const char* RUMBLE_GROUP = _trans("Rumble");
|
||||
static constexpr const char* MIC_GROUP = _trans("Microphone");
|
||||
static constexpr const char* OPTIONS_GROUP = _trans("Options");
|
||||
static constexpr const char* TRIFORCE_GROUP = _trans("Triforce");
|
||||
|
||||
static constexpr const char* A_BUTTON = "A";
|
||||
static constexpr const char* B_BUTTON = "B";
|
||||
@@ -69,6 +71,11 @@ public:
|
||||
static constexpr const char* Z_BUTTON = "Z";
|
||||
static constexpr const char* START_BUTTON = "Start";
|
||||
|
||||
// Special Triforce buttons
|
||||
static constexpr const char* TEST_BUTTON = _trans("Test");
|
||||
static constexpr const char* SERVICE_BUTTON = _trans("Service");
|
||||
static constexpr const char* COIN_BUTTON = _trans("Coin");
|
||||
|
||||
// i18n: The left trigger button (labeled L on real controllers)
|
||||
static constexpr const char* L_DIGITAL = _trans("L");
|
||||
// i18n: The right trigger button (labeled R on real controllers)
|
||||
@@ -87,6 +94,7 @@ private:
|
||||
ControllerEmu::ControlGroup* m_rumble;
|
||||
ControllerEmu::Buttons* m_mic;
|
||||
ControllerEmu::ControlGroup* m_options;
|
||||
ControllerEmu::Buttons* m_triforce;
|
||||
|
||||
ControllerEmu::SettingValue<bool> m_always_connected_setting;
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on: https://github.com/GXTX/YACardEmu
|
||||
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
|
||||
// Copyright (C) 2022-2023 tugpoat (https://github.com/tugpoat)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/MagCard/C1231BR.h"
|
||||
|
||||
namespace MagCard
|
||||
{
|
||||
|
||||
void C1231BR::Command_10_Initialize()
|
||||
{
|
||||
// TODO: Not hardware tested.
|
||||
if (!m_current_packet.empty())
|
||||
{
|
||||
if (InitMode(m_current_packet[0]) == InitMode::EjectAfter)
|
||||
{
|
||||
m_is_shutter_open = true;
|
||||
MoveCard(R::ENTRY_SLOT);
|
||||
}
|
||||
}
|
||||
|
||||
// m_status.SoftReset(); // TODO: Needed ?
|
||||
FinishCommand();
|
||||
}
|
||||
|
||||
void C1231BR::Command_D0_ControlShutter()
|
||||
{
|
||||
enum class Action : u8
|
||||
{
|
||||
Close = 0x30,
|
||||
Open = 0x31,
|
||||
};
|
||||
|
||||
if (m_current_packet.empty())
|
||||
{
|
||||
SetPError(P::SYSTEM_ERR);
|
||||
FinishCommand();
|
||||
return;
|
||||
}
|
||||
|
||||
m_is_shutter_open = Action(m_current_packet[0]) == Action::Open;
|
||||
|
||||
INFO_LOG_FMT(SERIALINTERFACE_CARD, "Shutter: {}", m_is_shutter_open ? "Open" : "Close");
|
||||
|
||||
FinishCommand();
|
||||
}
|
||||
|
||||
u8 C1231BR::GetPositionValue()
|
||||
{
|
||||
// True "R" (Card Position) values.
|
||||
// FYI: I see a 0xd0 command transition from: 11000 -> 01110 -> 00111
|
||||
// Are these 5 bits individual sensors placed within the machine ?
|
||||
static constexpr std::array<u8, std::size_t(R::MAX_POSITIONS)> POSITION_VALUES{
|
||||
0b00000, // NO_CARD
|
||||
0b11000, // READ_WRITE_HEAD
|
||||
0b00111, // THERMAL_HEAD
|
||||
0b11100, // DISPENSER_THERMAL
|
||||
0b00001, // ENTRY_SLOT
|
||||
};
|
||||
|
||||
return (m_is_shutter_open ? 0x80u : 0x40u) |
|
||||
(m_card_settings->report_dispenser_empty ? 0x00u : 0x20u) |
|
||||
POSITION_VALUES[u8(m_status.r)];
|
||||
}
|
||||
|
||||
void C1231BR::DoState(PointerWrap& p)
|
||||
{
|
||||
MagneticCardReader::DoState(p);
|
||||
|
||||
p.Do(m_is_shutter_open);
|
||||
}
|
||||
|
||||
bool C1231BR::IsReadyForCard()
|
||||
{
|
||||
return !IsCardPresent() && m_is_shutter_open;
|
||||
}
|
||||
|
||||
} // namespace MagCard
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on: https://github.com/GXTX/YACardEmu
|
||||
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
|
||||
// Copyright (C) 2022-2023 tugpoat (https://github.com/tugpoat)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Core/HW/MagCard/MagneticCardReader.h"
|
||||
|
||||
namespace MagCard
|
||||
{
|
||||
|
||||
// CRP-1231BR-10
|
||||
// Used by F-Zero AX
|
||||
class C1231BR final : public MagneticCardReader
|
||||
{
|
||||
public:
|
||||
using MagneticCardReader::MagneticCardReader;
|
||||
|
||||
protected:
|
||||
// Specific to this model.
|
||||
bool m_is_shutter_open = true;
|
||||
|
||||
bool IsReadyForCard() override;
|
||||
|
||||
u8 GetPositionValue() override;
|
||||
|
||||
void Command_10_Initialize() override;
|
||||
void Command_D0_ControlShutter() override;
|
||||
|
||||
void DoState(PointerWrap& p) override;
|
||||
};
|
||||
|
||||
} // namespace MagCard
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on: https://github.com/GXTX/YACardEmu
|
||||
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/MagCard/C1231LR.h"
|
||||
|
||||
namespace MagCard
|
||||
{
|
||||
|
||||
u8 C1231LR::GetPositionValue()
|
||||
{
|
||||
// True "R" (Card Position) values.
|
||||
static constexpr std::array<u8, std::size_t(R::MAX_POSITIONS)> POSITION_VALUES{
|
||||
0x30, // NO_CARD
|
||||
0x31, // READ_WRITE_HEAD
|
||||
0x32, // THERMAL_HEAD
|
||||
0x33, // DISPENSER_THERMAL
|
||||
0x34, // ENTRY_SLOT
|
||||
};
|
||||
|
||||
return POSITION_VALUES[u8(m_status.r)];
|
||||
}
|
||||
|
||||
} // namespace MagCard
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on: https://github.com/GXTX/YACardEmu
|
||||
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Core/HW/MagCard/MagneticCardReader.h"
|
||||
|
||||
namespace MagCard
|
||||
{
|
||||
|
||||
// CRP-1231LR-10NAB
|
||||
// Used by Mario Kart Arcade GP + GP2
|
||||
class C1231LR final : public MagneticCardReader
|
||||
{
|
||||
public:
|
||||
using MagneticCardReader::MagneticCardReader;
|
||||
|
||||
protected:
|
||||
u8 GetPositionValue() override;
|
||||
};
|
||||
|
||||
} // namespace MagCard
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,207 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on: https://github.com/GXTX/YACardEmu
|
||||
// Copyright (C) 2020-2023 wutno (https://github.com/GXTX)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace MagCard
|
||||
{
|
||||
|
||||
class MagneticCardReader
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t TRACK_SIZE = 69; // A nice amount of data.
|
||||
static constexpr std::size_t NUM_TRACKS = 3;
|
||||
|
||||
// This data is not part of the card reader state.
|
||||
struct Settings
|
||||
{
|
||||
std::string card_name;
|
||||
std::string card_path;
|
||||
|
||||
bool insert_card_when_possible = true;
|
||||
bool report_dispenser_empty = false;
|
||||
};
|
||||
|
||||
explicit MagneticCardReader(Settings* settings);
|
||||
virtual ~MagneticCardReader();
|
||||
|
||||
MagneticCardReader(const MagneticCardReader&) = delete;
|
||||
MagneticCardReader& operator=(const MagneticCardReader&) = delete;
|
||||
MagneticCardReader(MagneticCardReader&&) = delete;
|
||||
MagneticCardReader& operator=(MagneticCardReader&&) = delete;
|
||||
|
||||
// TODO: This std:vector buffer interface is a bit funky..
|
||||
|
||||
// read = MagCard <-- Baseboard.
|
||||
// write = Magcard --> Baseboard.
|
||||
void Process(std::vector<u8>* read, std::vector<u8>* write);
|
||||
|
||||
virtual void DoState(PointerWrap& p);
|
||||
|
||||
protected:
|
||||
// Status bytes:
|
||||
enum class R : u8
|
||||
{
|
||||
// Note: Derived classes define the real values in GetPositionValue().
|
||||
NO_CARD,
|
||||
READ_WRITE_HEAD,
|
||||
THERMAL_HEAD,
|
||||
DISPENSER_THERMAL,
|
||||
ENTRY_SLOT,
|
||||
|
||||
MAX_POSITIONS,
|
||||
};
|
||||
enum class P : u8
|
||||
{
|
||||
NO_ERR = 0x30,
|
||||
READ_ERR = 0x31,
|
||||
WRITE_ERR = 0x32,
|
||||
CARD_JAM = 0x33,
|
||||
MOTOR_ERR = 0x34, // transport system motor error
|
||||
PRINT_ERR = 0x35,
|
||||
ILLEGAL_ERR = 0x38, // generic error
|
||||
BATTERY_ERR = 0x40, // low battery voltage
|
||||
SYSTEM_ERR = 0x41, // reader/writer system err
|
||||
TRACK_1_READ_ERR = 0x51,
|
||||
TRACK_2_READ_ERR = 0x52,
|
||||
TRACK_3_READ_ERR = 0x53,
|
||||
TRACK_1_AND_2_READ_ERR = 0x54,
|
||||
TRACK_1_AND_3_READ_ERR = 0x55,
|
||||
TRACK_2_AND_3_READ_ERR = 0x56,
|
||||
};
|
||||
enum class S : u8
|
||||
{
|
||||
NO_JOB = 0x30,
|
||||
ILLEGAL_COMMAND = 0x32,
|
||||
RUNNING_COMMAND = 0x33,
|
||||
WAITING_FOR_CARD = 0x34,
|
||||
DISPENSER_EMPTY = 0x35,
|
||||
NO_DISPENSER = 0x36,
|
||||
CARD_FULL = 0x37,
|
||||
};
|
||||
|
||||
struct Status
|
||||
{
|
||||
R r = R::NO_CARD;
|
||||
P p = P::NO_ERR;
|
||||
S s = S::NO_JOB;
|
||||
|
||||
void SoftReset()
|
||||
{
|
||||
p = P::NO_ERR;
|
||||
s = S::NO_JOB;
|
||||
}
|
||||
};
|
||||
|
||||
enum class InitMode : u8
|
||||
{
|
||||
Standard = 0x30,
|
||||
EjectAfter = 0x31,
|
||||
ResetSpecifications = 0x32,
|
||||
};
|
||||
|
||||
bool ReceivePacket(std::span<const u8> packet);
|
||||
void BuildPacket(std::vector<u8>& write_buffer);
|
||||
|
||||
void StepStateMachine(DT elapsed_time);
|
||||
void StepStatePerson(DT elapsed_time);
|
||||
|
||||
// This is what actually maps the "R" position value to a number.
|
||||
virtual u8 GetPositionValue() = 0;
|
||||
|
||||
void SetPError(P error_code);
|
||||
void SetSError(S error_code);
|
||||
|
||||
void ClearCardInMemory();
|
||||
|
||||
// Read/Write with host filesystem.
|
||||
void ReadCardFile();
|
||||
void WriteCardFile();
|
||||
|
||||
bool IsRunningCommand() const;
|
||||
void FinishCommand();
|
||||
|
||||
u8 GetCurrentCommand() const;
|
||||
|
||||
// Is a card anywhere the machine can sense it ?
|
||||
bool IsCardPresent() const;
|
||||
|
||||
// Is a card beyond the "ENTRY_SLOT" position ?
|
||||
bool IsCardLoaded() const;
|
||||
|
||||
virtual bool IsReadyForCard();
|
||||
|
||||
// Move the card through the machine.
|
||||
void MoveCard(R new_position);
|
||||
|
||||
// Unloads the card and moves it to the "ENTRY_SLOT" position.
|
||||
void EjectCard();
|
||||
|
||||
// Feeds a new card into the machine from an internal hopper.
|
||||
void DispenseCard();
|
||||
|
||||
// Commands
|
||||
virtual void Command_10_Initialize();
|
||||
void Command_20_ReadStatus();
|
||||
void Command_33_ReadData(); // Multi-track read.
|
||||
void Command_35_GetData(); // Read the entire card.
|
||||
void Command_40_Cancel();
|
||||
void Command_53_WriteData(); // Multi-track write.
|
||||
void Command_78_PrintSettings();
|
||||
void Command_7A_RegisterFont();
|
||||
void Command_7B_PrintImage();
|
||||
void Command_7C_PrintLine();
|
||||
void Command_7D_Erase(); // Erase the printed image.
|
||||
void Command_7E_PrintBarcode();
|
||||
void Command_80_EjectCard();
|
||||
void Command_A0_Clean();
|
||||
void Command_B0_DispenseCard();
|
||||
void Command_C0_ControlLED();
|
||||
void Command_C1_SetPrintRetry();
|
||||
virtual void Command_D0_ControlShutter();
|
||||
void Command_F0_GetVersion();
|
||||
|
||||
Settings* const m_card_settings;
|
||||
|
||||
// R P S values that are sent in every ENQUIRY response.
|
||||
Status m_status{};
|
||||
|
||||
// A byte sent in every ENQUIRY response.
|
||||
// TODO: What's the default value on just powered up hardware ?
|
||||
u8 m_current_command = 0;
|
||||
|
||||
// The data included in every ENQUIRY response.
|
||||
std::vector<u8> m_command_payload;
|
||||
|
||||
// The card track data in memory.
|
||||
using TrackData = std::optional<std::array<u8, TRACK_SIZE>>;
|
||||
std::array<TrackData, NUM_TRACKS> m_card_data;
|
||||
|
||||
// The parameter data of the command being processed.
|
||||
std::vector<u8> m_current_packet;
|
||||
|
||||
// The progress state of the current command.
|
||||
// 0 == not running a command.
|
||||
// TODO: Change this to emulated milliseconds or something.
|
||||
s32 m_current_step = 0;
|
||||
|
||||
// Used to delay re-insertion of cards after ejection.
|
||||
// This avoids having to expose card "Take"/"Insert" actions.
|
||||
DT m_time_since_machine_moved_card{};
|
||||
DT m_time_since_person_moved_card{};
|
||||
};
|
||||
|
||||
} // namespace MagCard
|
||||
@@ -123,7 +123,6 @@ public:
|
||||
// If the specified range is within a single valid memory region, returns a pointer to the start
|
||||
// of the corresponding range in host memory. Otherwise, returns nullptr.
|
||||
u8* GetPointerForRange(u32 address, size_t size) const;
|
||||
|
||||
void CopyFromEmu(void* data, u32 address, size_t size) const;
|
||||
void CopyToEmu(u32 address, const void* data, size_t size);
|
||||
void Memset(u32 address, u8 value, size_t size);
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
void RemoveEvent(int device_number);
|
||||
|
||||
void UpdateDevices();
|
||||
u32 GetInLength() const { return m_com_csr.INLNGTH; }
|
||||
|
||||
void RemoveDevice(int device_number);
|
||||
void AddDevice(SIDevices device, int device_number);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifdef HAS_LIBMGBA
|
||||
#include "Core/HW/SI/SI_DeviceGBAEmu.h"
|
||||
#endif
|
||||
#include "Core/HW/SI/SI_DeviceAMBaseboard.h"
|
||||
#include "Core/HW/SI/SI_DeviceGCAdapter.h"
|
||||
#include "Core/HW/SI/SI_DeviceGCController.h"
|
||||
#include "Core/HW/SI/SI_DeviceGCSteeringWheel.h"
|
||||
@@ -149,6 +150,7 @@ bool SIDevice_IsGCController(SIDevices type)
|
||||
case SIDEVICE_GC_TARUKONGA:
|
||||
case SIDEVICE_DANCEMAT:
|
||||
case SIDEVICE_GC_STEERING:
|
||||
case SIDEVICE_AM_BASEBOARD:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -191,6 +193,8 @@ std::unique_ptr<ISIDevice> SIDevice_Create(Core::System& system, const SIDevices
|
||||
return std::make_unique<CSIDevice_Keyboard>(system, device, port_number);
|
||||
|
||||
case SIDEVICE_AM_BASEBOARD:
|
||||
return std::make_unique<CSIDevice_AMBaseboard>(system, device, port_number);
|
||||
|
||||
case SIDEVICE_NONE:
|
||||
default:
|
||||
return std::make_unique<CSIDevice_Null>(system, device, port_number);
|
||||
|
||||
@@ -97,8 +97,6 @@ enum SIDevices : int
|
||||
SIDEVICE_GC_STEERING,
|
||||
SIDEVICE_DANCEMAT,
|
||||
SIDEVICE_GC_TARUKONGA,
|
||||
// Was used for Triforce in the past, but the implementation is no longer in Dolphin.
|
||||
// It's kept here so that values below will stay constant.
|
||||
SIDEVICE_AM_BASEBOARD,
|
||||
SIDEVICE_WIIU_ADAPTER,
|
||||
SIDEVICE_GC_GBA_EMULATED,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,315 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Core/HW/GCPad.h"
|
||||
#include "Core/HW/MagCard/MagneticCardReader.h"
|
||||
#include "Core/HW/SI/SI_Device.h"
|
||||
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
|
||||
namespace Movie
|
||||
{
|
||||
class MovieManager;
|
||||
}
|
||||
|
||||
namespace SerialInterface
|
||||
{
|
||||
|
||||
// "JAMMA Video Standard" I/O
|
||||
class JVSIOMessage
|
||||
{
|
||||
public:
|
||||
void Start(int node);
|
||||
void AddData(const u8* dst, std::size_t len, int sync);
|
||||
void AddData(const void* data, std::size_t len);
|
||||
void AddData(const char* data);
|
||||
void AddData(u32 n);
|
||||
void End();
|
||||
|
||||
u32 m_pointer = 0;
|
||||
std::array<u8, 0x80> m_message;
|
||||
|
||||
private:
|
||||
u32 m_last_start = 0;
|
||||
u32 m_checksum = 0;
|
||||
};
|
||||
|
||||
// Triforce (GC-AM) baseboard
|
||||
class CSIDevice_AMBaseboard : public ISIDevice
|
||||
{
|
||||
enum EButtonCombo
|
||||
{
|
||||
COMBO_NONE = 0,
|
||||
COMBO_ORIGIN,
|
||||
COMBO_RESET
|
||||
};
|
||||
|
||||
public:
|
||||
// constructor
|
||||
CSIDevice_AMBaseboard(Core::System& system, SIDevices device, int device_number);
|
||||
|
||||
// run the SI Buffer
|
||||
int RunBuffer(u8* buffer, int request_length) override;
|
||||
|
||||
// Reply has to be delayed due a bug in the parser
|
||||
void SwapBuffers(u8* buffer, u32* buffer_length);
|
||||
|
||||
// return true on new data
|
||||
DataResponse GetData(u32& hi, u32& low) override;
|
||||
|
||||
// send a command directly
|
||||
void SendCommand(u32 command, u8 poll) override;
|
||||
|
||||
virtual GCPadStatus GetPadStatus();
|
||||
virtual u32 MapPadStatus(const GCPadStatus& pad_status);
|
||||
virtual EButtonCombo HandleButtonCombos(const GCPadStatus& pad_status);
|
||||
|
||||
static void HandleMoviePadStatus(Movie::MovieManager& movie, int device_number,
|
||||
GCPadStatus* pad_status);
|
||||
|
||||
// Send and Receive pad input from network
|
||||
static bool NetPlay_GetInput(int pad_num, GCPadStatus* status);
|
||||
static int NetPlay_InGamePadToLocalPad(int pad_num);
|
||||
|
||||
void DoState(PointerWrap&) override;
|
||||
|
||||
protected:
|
||||
struct SOrigin
|
||||
{
|
||||
u16 button;
|
||||
u8 origin_stick_x;
|
||||
u8 origin_stick_y;
|
||||
u8 substick_x;
|
||||
u8 substick_y;
|
||||
u8 trigger_left;
|
||||
u8 trigger_right;
|
||||
u8 unk_4;
|
||||
u8 unk_5;
|
||||
};
|
||||
|
||||
// struct to compare input against
|
||||
// Set on connection to perfect neutral values
|
||||
// (standard pad only) Set on button combo to current input state
|
||||
SOrigin m_origin = {};
|
||||
|
||||
// PADAnalogMode
|
||||
// Dunno if we need to do this, game/lib should set it?
|
||||
u8 m_mode = 0x3;
|
||||
|
||||
// Timer to track special button combos:
|
||||
// y, X, start for 3 seconds updates origin with current status
|
||||
// Technically, the above is only on standard pad, wavebird does not support it for example
|
||||
// b, x, start for 3 seconds triggers reset (PI reset button interrupt)
|
||||
u64 m_timer_button_combo_start = 0;
|
||||
// Type of button combo from the last/current poll
|
||||
EButtonCombo m_last_button_combo = COMBO_NONE;
|
||||
void SetOrigin(const GCPadStatus& pad_status);
|
||||
|
||||
private:
|
||||
enum BaseBoardCommand
|
||||
{
|
||||
GCAM_Reset = 0x00,
|
||||
GCAM_Command = 0x70,
|
||||
};
|
||||
|
||||
enum GCAMCommand
|
||||
{
|
||||
StatusSwitches = 0x10,
|
||||
SerialNumber = 0x11,
|
||||
Unknown_12 = 0x12,
|
||||
Unknown_14 = 0x14,
|
||||
FirmVersion = 0x15,
|
||||
FPGAVersion = 0x16,
|
||||
RegionSettings = 0x1F,
|
||||
|
||||
Unknown_21 = 0x21,
|
||||
Unknown_22 = 0x22,
|
||||
Unknown_23 = 0x23,
|
||||
Unknown_24 = 0x24,
|
||||
|
||||
SerialA = 0x31,
|
||||
SerialB = 0x32,
|
||||
|
||||
JVSIOA = 0x40,
|
||||
JVSIOB = 0x41,
|
||||
|
||||
Unknown_60 = 0x60,
|
||||
};
|
||||
|
||||
enum JVSIOCommand
|
||||
{
|
||||
IOID = 0x10,
|
||||
CommandRevision = 0x11,
|
||||
JVRevision = 0x12,
|
||||
CommunicationVersion = 0x13,
|
||||
CheckFunctionality = 0x14,
|
||||
MainID = 0x15,
|
||||
|
||||
SwitchesInput = 0x20,
|
||||
CoinInput = 0x21,
|
||||
AnalogInput = 0x22,
|
||||
RotaryInput = 0x23,
|
||||
KeyCodeInput = 0x24,
|
||||
PositionInput = 0x25,
|
||||
GeneralSwitchInput = 0x26,
|
||||
|
||||
PayoutRemain = 0x2E,
|
||||
Retrans = 0x2F,
|
||||
CoinSubOutput = 0x30,
|
||||
PayoutAddOutput = 0x31,
|
||||
GeneralDriverOutput = 0x32,
|
||||
AnalogOutput = 0x33,
|
||||
CharacterOutput = 0x34,
|
||||
CoinAddOutput = 0x35,
|
||||
PayoutSubOutput = 0x36,
|
||||
GeneralDriverOutput2 = 0x37,
|
||||
GeneralDriverOutput3 = 0x38,
|
||||
|
||||
NAMCOCommand = 0x70,
|
||||
|
||||
Reset = 0xF0,
|
||||
SetAddress = 0xF1,
|
||||
ChangeComm = 0xF2,
|
||||
};
|
||||
|
||||
enum JVSIOStatusCode
|
||||
{
|
||||
StatusOkay = 1,
|
||||
UnsupportedCommand = 2,
|
||||
ChecksumError = 3,
|
||||
AcknowledgeOverflow = 4,
|
||||
};
|
||||
|
||||
enum CARDCommand
|
||||
{
|
||||
Init = 0x10,
|
||||
GetState = 0x20,
|
||||
Read = 0x33,
|
||||
IsPresent = 0x40,
|
||||
Write = 0x53,
|
||||
SetPrintParam = 0x78,
|
||||
RegisterFont = 0x7A,
|
||||
WriteInfo = 0x7C,
|
||||
Erase = 0x7D,
|
||||
Eject = 0x80,
|
||||
Clean = 0xA0,
|
||||
Load = 0xB0,
|
||||
SetShutter = 0xD0,
|
||||
};
|
||||
|
||||
enum ICCARDCommand
|
||||
{
|
||||
GetStatus = 0x10,
|
||||
SetBaudrate = 0x11,
|
||||
FieldOn = 0x14,
|
||||
FieldOff = 0x15,
|
||||
InsertCheck = 0x20,
|
||||
AntiCollision = 0x21,
|
||||
SelectCard = 0x22,
|
||||
ReadPage = 0x24,
|
||||
WritePage = 0x25,
|
||||
DecreaseUseCount = 0x26,
|
||||
ReadUseCount = 0x33,
|
||||
ReadPages = 0x34,
|
||||
WritePages = 0x35,
|
||||
};
|
||||
|
||||
enum ICCARDStatus
|
||||
{
|
||||
Okay = 0,
|
||||
NoCard = 0x8000,
|
||||
Unknown = 0x800E,
|
||||
BadCard = 0xFFFF,
|
||||
};
|
||||
|
||||
enum CDReaderCommand
|
||||
{
|
||||
ShutterAuto = 0x61,
|
||||
BootVersion = 0x62,
|
||||
SensLock = 0x63,
|
||||
SensCard = 0x65,
|
||||
FirmwareUpdate = 0x66,
|
||||
ShutterGet = 0x67,
|
||||
CameraCheck = 0x68,
|
||||
ShutterCard = 0x69,
|
||||
ProgramChecksum = 0x6B,
|
||||
BootChecksum = 0x6D,
|
||||
ShutterLoad = 0x6F,
|
||||
ReadCard = 0x72,
|
||||
ShutterSave = 0x73,
|
||||
SelfTest = 0x74,
|
||||
ProgramVersion = 0x76,
|
||||
};
|
||||
|
||||
// NOTE: Used to be an union with `u8 data[81 + 4 + 4 + 4]`
|
||||
// TODO: Should the struct be packed?
|
||||
struct ICCommand
|
||||
{
|
||||
u32 pktcmd : 8;
|
||||
u32 pktlen : 8;
|
||||
u32 fixed : 8;
|
||||
u32 command : 8;
|
||||
u32 flag : 8;
|
||||
u32 length : 8;
|
||||
u32 status : 16;
|
||||
|
||||
u8 extdata[81] = {};
|
||||
u32 extlen;
|
||||
};
|
||||
|
||||
u8 m_last[2][0x80] = {};
|
||||
u32 m_lastptr[2] = {};
|
||||
|
||||
std::array<u16, 2> m_coin{};
|
||||
std::array<u32, 2> m_coin_pressed{};
|
||||
|
||||
u8 m_ic_card_data[2048] = {};
|
||||
|
||||
// Setup IC-card
|
||||
u16 m_ic_card_state = 0x20;
|
||||
u16 m_ic_card_status = ICCARDStatus::Okay;
|
||||
u16 m_ic_card_session = 0x23;
|
||||
|
||||
u8 m_ic_write_buffer[512] = {};
|
||||
u32 m_ic_write_offset = 0;
|
||||
u32 m_ic_write_size = 0;
|
||||
|
||||
// Magnetic Card Reader
|
||||
MagCard::MagneticCardReader::Settings m_mag_card_settings;
|
||||
|
||||
std::vector<u8> m_mag_card_in_buffer;
|
||||
std::vector<u8> m_mag_card_out_buffer;
|
||||
|
||||
std::unique_ptr<MagCard::MagneticCardReader> m_mag_card_reader;
|
||||
|
||||
// Serial
|
||||
u32 m_wheel_init = 0;
|
||||
|
||||
u32 m_motor_init = 0;
|
||||
u8 m_motor_reply[64] = {};
|
||||
s16 m_motor_force_y = 0;
|
||||
|
||||
// F-Zero AX (DX)
|
||||
bool m_fzdx_seatbelt = true;
|
||||
bool m_fzdx_motion_stop = false;
|
||||
bool m_fzdx_sensor_right = false;
|
||||
bool m_fzdx_sensor_left = false;
|
||||
u8 m_rx_reply = 0xF0;
|
||||
|
||||
// F-Zero AX (CyCraft)
|
||||
bool m_fzcc_seatbelt = true;
|
||||
bool m_fzcc_sensor = false;
|
||||
bool m_fzcc_emergency = false;
|
||||
bool m_fzcc_service = false;
|
||||
|
||||
u32 m_dip_switch_1 = 0xFE;
|
||||
u32 m_dip_switch_0 = 0xFF;
|
||||
|
||||
int m_delay = 0;
|
||||
|
||||
void ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length);
|
||||
};
|
||||
|
||||
} // namespace SerialInterface
|
||||
@@ -163,7 +163,9 @@ void VideoInterfaceManager::Preset(bool _bNTSC)
|
||||
|
||||
// Say component cable is plugged
|
||||
m_dtv_status.component_plugged = Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN);
|
||||
m_dtv_status.ntsc_j = region == DiscIO::Region::NTSC_J;
|
||||
|
||||
// Triforce IPL requires the DTV NTSC-J flag to be set.
|
||||
m_dtv_status.ntsc_j = m_system.IsTriforce() || region == DiscIO::Region::NTSC_J;
|
||||
|
||||
m_fb_width.Hex = 0;
|
||||
m_border_hblank.Hex = 0;
|
||||
|
||||
@@ -893,11 +893,7 @@ s32 WiiSockMan::AddSocket(s32 fd, bool is_rw)
|
||||
sock.SetWiiFd(wii_fd);
|
||||
m_ios.GetSystem().GetPowerPC().GetDebugInterface().NetworkLogger()->OnNewSocket(fd);
|
||||
|
||||
#ifdef __APPLE__
|
||||
int opt_no_sigpipe = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0)
|
||||
ERROR_LOG_FMT(IOS_NET, "Failed to set SO_NOSIGPIPE on socket");
|
||||
#endif
|
||||
Common::SetPlatformSocketOptions(fd);
|
||||
|
||||
// Wii UDP sockets can use broadcast address by default
|
||||
if (!is_rw)
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "Core/HW/GCPad.h"
|
||||
#include "Core/HW/SI/SI.h"
|
||||
#include "Core/HW/SI/SI_Device.h"
|
||||
#include "Core/HW/SI/SI_DeviceAMBaseboard.h"
|
||||
#include "Core/HW/SI/SI_DeviceGCController.h"
|
||||
#include "Core/HW/Sram.h"
|
||||
#include "Core/HW/WiiSave.h"
|
||||
@@ -1883,6 +1884,8 @@ void NetPlayClient::UpdateDevices()
|
||||
auto& si = Core::System::GetInstance().GetSerialInterface();
|
||||
for (auto player_id : m_pad_map)
|
||||
{
|
||||
const SerialInterface::SIDevices si_device = Config::Get(Config::GetInfoForSIDevice(local_pad));
|
||||
|
||||
if (m_gba_config[pad].enabled && player_id > 0)
|
||||
{
|
||||
si.ChangeDevice(SerialInterface::SIDEVICE_GC_GBA_EMULATED, pad);
|
||||
@@ -1890,8 +1893,6 @@ void NetPlayClient::UpdateDevices()
|
||||
else if (player_id == m_local_player->pid)
|
||||
{
|
||||
// Use local controller types for local controllers if they are compatible
|
||||
const SerialInterface::SIDevices si_device =
|
||||
Config::Get(Config::GetInfoForSIDevice(local_pad));
|
||||
if (SerialInterface::SIDevice_IsGCController(si_device))
|
||||
{
|
||||
si.ChangeDevice(si_device, pad);
|
||||
@@ -1909,7 +1910,8 @@ void NetPlayClient::UpdateDevices()
|
||||
}
|
||||
else if (player_id > 0)
|
||||
{
|
||||
si.ChangeDevice(SerialInterface::SIDEVICE_GC_CONTROLLER, pad);
|
||||
if (si_device != SerialInterface::SIDEVICE_AM_BASEBOARD)
|
||||
si.ChangeDevice(SerialInterface::SIDEVICE_GC_CONTROLLER, pad);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2772,6 +2774,16 @@ bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int pad_num, GCPa
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SerialInterface::CSIDevice_AMBaseboard::NetPlay_GetInput(int pad_num, GCPadStatus* status)
|
||||
{
|
||||
std::lock_guard lk(NetPlay::crit_netplay_client);
|
||||
|
||||
if (NetPlay::netplay_client)
|
||||
return NetPlay::netplay_client->GetNetPads(pad_num, NetPlay::s_si_poll_batching, status);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetPlay::NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries)
|
||||
{
|
||||
std::lock_guard lk(crit_netplay_client);
|
||||
@@ -2835,12 +2847,21 @@ u64 ExpansionInterface::CEXIIPL::NetPlay_GetEmulatedTime()
|
||||
|
||||
// called from ---CPU--- thread
|
||||
// return the local pad num that should rumble given a ingame pad num
|
||||
int SerialInterface::CSIDevice_GCController::NetPlay_InGamePadToLocalPad(int numPAD)
|
||||
int SerialInterface::CSIDevice_GCController::NetPlay_InGamePadToLocalPad(int pad_num)
|
||||
{
|
||||
std::lock_guard lk(NetPlay::crit_netplay_client);
|
||||
|
||||
if (NetPlay::netplay_client)
|
||||
return NetPlay::netplay_client->InGamePadToLocalPad(numPAD);
|
||||
return NetPlay::netplay_client->InGamePadToLocalPad(pad_num);
|
||||
|
||||
return numPAD;
|
||||
return pad_num;
|
||||
}
|
||||
int SerialInterface::CSIDevice_AMBaseboard::NetPlay_InGamePadToLocalPad(int pad_num)
|
||||
{
|
||||
std::lock_guard lk(NetPlay::crit_netplay_client);
|
||||
|
||||
if (NetPlay::netplay_client)
|
||||
return NetPlay::netplay_client->InGamePadToLocalPad(pad_num);
|
||||
|
||||
return pad_num;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ struct CompressAndDumpStateArgs
|
||||
static Common::WorkQueueThreadSP<CompressAndDumpStateArgs> s_compress_and_dump_thread;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
constexpr u32 STATE_VERSION = 176; // Last changed in PR 13768
|
||||
constexpr u32 STATE_VERSION = 177; // Last changed in PR 13844
|
||||
|
||||
// Increase this if the StateExtendedHeader definition changes
|
||||
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
|
||||
|
||||
@@ -91,32 +91,8 @@ TitleDatabase::TitleDatabase()
|
||||
TitleDatabase::~TitleDatabase() = default;
|
||||
|
||||
const std::string& TitleDatabase::GetTitleName(const std::string& gametdb_id,
|
||||
const std::string& triforce_id,
|
||||
DiscIO::Language language) const
|
||||
{
|
||||
if (triforce_id != "")
|
||||
{
|
||||
const Map& map = *m_triforce_title_maps.at(DiscIO::Language::English);
|
||||
auto it = map.find(triforce_id);
|
||||
if (it != map.end())
|
||||
return it->second;
|
||||
|
||||
// This code has been commented out as there is currently only a english title map, and all
|
||||
// Triforce games are detected as Japanese.
|
||||
|
||||
// if (language != DiscIO::Language::English)
|
||||
//{
|
||||
// const Map& english_triforce_map = *m_triforce_title_maps.at(DiscIO::Language::English);
|
||||
// it = english_triforce_map.find(triforce_id);
|
||||
// if (it != english_triforce_map.end())
|
||||
// return it->second;
|
||||
//}
|
||||
|
||||
// it = m_base_map.find(triforce_id);
|
||||
// if (it != m_base_map.end())
|
||||
// return it->second;
|
||||
}
|
||||
|
||||
auto it = m_user_title_map.find(gametdb_id);
|
||||
if (it != m_user_title_map.end())
|
||||
return it->second;
|
||||
@@ -149,12 +125,12 @@ const std::string& TitleDatabase::GetChannelName(u64 title_id, DiscIO::Language
|
||||
const std::string id{
|
||||
{static_cast<char>((title_id >> 24) & 0xff), static_cast<char>((title_id >> 16) & 0xff),
|
||||
static_cast<char>((title_id >> 8) & 0xff), static_cast<char>(title_id & 0xff)}};
|
||||
return GetTitleName(id, "", language);
|
||||
return GetTitleName(id, language);
|
||||
}
|
||||
|
||||
std::string TitleDatabase::Describe(const std::string& gametdb_id, DiscIO::Language language) const
|
||||
{
|
||||
const std::string& title_name = GetTitleName(gametdb_id, "", language);
|
||||
const std::string& title_name = GetTitleName(gametdb_id, language);
|
||||
if (title_name.empty())
|
||||
return gametdb_id;
|
||||
return fmt::format("{} ({})", title_name, gametdb_id);
|
||||
|
||||
@@ -25,8 +25,7 @@ public:
|
||||
|
||||
// Get a user friendly title name for a GameTDB ID.
|
||||
// This falls back to returning an empty string if none could be found.
|
||||
const std::string& GetTitleName(const std::string& gametdb_id, const std::string& triforce_id,
|
||||
DiscIO::Language language) const;
|
||||
const std::string& GetTitleName(const std::string& gametdb_id, DiscIO::Language language) const;
|
||||
|
||||
// Same as above, but takes a title ID instead of a GameTDB ID, and only works for channels.
|
||||
const std::string& GetChannelName(u64 title_id, DiscIO::Language language) const;
|
||||
|
||||
@@ -97,6 +97,9 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true only for CachedBlobReader.
|
||||
virtual bool IsCached() const { return false; }
|
||||
|
||||
protected:
|
||||
BlobReader() {}
|
||||
};
|
||||
|
||||
@@ -219,6 +219,8 @@ public:
|
||||
return m_cache_filler->Read(offset, size, out_ptr) || m_reader->Read(offset, size, out_ptr);
|
||||
}
|
||||
|
||||
bool IsCached() const override { return true; }
|
||||
|
||||
private:
|
||||
// A shared object does the cache filling for sensible CopyReader behavior.
|
||||
const std::shared_ptr<CacheFiller> m_cache_filler;
|
||||
@@ -228,11 +230,17 @@ private:
|
||||
|
||||
std::unique_ptr<BlobReader> CreateCachedBlobReader(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
if (reader->IsCached()) // This is already CachedBlobReader.
|
||||
return reader;
|
||||
|
||||
return std::make_unique<CachedBlobReader>(std::move(reader), false);
|
||||
}
|
||||
|
||||
std::unique_ptr<BlobReader> CreateScrubbingCachedBlobReader(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
if (reader->IsCached()) // This reader is already a CachedBlobReader.
|
||||
return reader;
|
||||
|
||||
return std::make_unique<CachedBlobReader>(std::move(reader), true);
|
||||
}
|
||||
|
||||
|
||||
@@ -407,7 +407,7 @@ std::string GetSysMenuVersionString(u16 title_version, bool is_vwii)
|
||||
case Region::NTSC_K:
|
||||
region_letter = 'K';
|
||||
break;
|
||||
case Region::Unknown:
|
||||
default:
|
||||
WARN_LOG_FMT(DISCIO, "Unknown region for Wii Menu version {}", title_version);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ enum class Platform
|
||||
WiiDisc,
|
||||
WiiWAD,
|
||||
ELFOrDOL,
|
||||
NumberOfPlatforms
|
||||
NumberOfPlatforms,
|
||||
};
|
||||
|
||||
enum class Country
|
||||
@@ -38,7 +38,7 @@ enum class Country
|
||||
Taiwan,
|
||||
World,
|
||||
Unknown,
|
||||
NumberOfCountries
|
||||
NumberOfCountries,
|
||||
};
|
||||
|
||||
// This numbering matches Nintendo's GameCube/Wii region numbering.
|
||||
@@ -48,7 +48,8 @@ enum class Region
|
||||
NTSC_U = 1, // Mainly North America
|
||||
PAL = 2, // Mainly Europe and Oceania
|
||||
Unknown = 3, // Nintendo uses this to mean region free, but we also use it for unknown regions
|
||||
NTSC_K = 4 // South Korea (Wii only)
|
||||
NTSC_K = 4, // South Korea (Wii only)
|
||||
DEV = 5, // Triforce has no regions
|
||||
};
|
||||
|
||||
// Languages 0 - 9 match Nintendo's Wii language numbering.
|
||||
@@ -66,7 +67,7 @@ enum class Language
|
||||
SimplifiedChinese = 7, // Not selectable on any unmodded retail Wii
|
||||
TraditionalChinese = 8, // Not selectable on any unmodded retail Wii
|
||||
Korean = 9,
|
||||
Unknown
|
||||
Unknown,
|
||||
};
|
||||
|
||||
std::string GetName(Country country, bool translate);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -96,7 +97,6 @@ public:
|
||||
}
|
||||
virtual std::string GetGameID(const Partition& partition = PARTITION_NONE) const = 0;
|
||||
virtual std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const = 0;
|
||||
virtual std::string GetTriforceID() const { return ""; }
|
||||
virtual std::string GetMakerID(const Partition& partition = PARTITION_NONE) const = 0;
|
||||
virtual std::optional<u16> GetRevision(const Partition& partition = PARTITION_NONE) const = 0;
|
||||
virtual std::string GetInternalName(const Partition& partition = PARTITION_NONE) const = 0;
|
||||
@@ -144,16 +144,15 @@ public:
|
||||
virtual std::array<u8, 20> GetSyncHash() const = 0;
|
||||
|
||||
protected:
|
||||
template <u32 N>
|
||||
std::string DecodeString(const char (&data)[N]) const
|
||||
std::string DecodeString(std::span<const char> data) const
|
||||
{
|
||||
// strnlen to trim NULLs
|
||||
std::string string(data, strnlen(data, sizeof(data)));
|
||||
std::string string(data.data(), strnlen(data.data(), data.size()));
|
||||
|
||||
if (GetRegion() == Region::NTSC_J)
|
||||
return SHIFTJISToUTF8(string);
|
||||
else
|
||||
return CP1252ToUTF8(string);
|
||||
|
||||
return CP1252ToUTF8(string);
|
||||
}
|
||||
|
||||
void ReadAndAddToSyncHash(Common::SHA1::Context* context, u64 offset, u64 length,
|
||||
|
||||
@@ -12,12 +12,46 @@
|
||||
#include "DiscIO/DiscUtils.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
#include "DiscIO/VolumeGC.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
std::string VolumeDisc::GetGameID(const Partition& partition) const
|
||||
{
|
||||
char id[6];
|
||||
char id[6]{};
|
||||
|
||||
if (GetVolumeType() == Platform::Triforce)
|
||||
{
|
||||
// Triforce games have their Game ID stored in the boot.id file
|
||||
const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
|
||||
|
||||
// Construct game ID from the BTID
|
||||
id[0] = 'G';
|
||||
|
||||
memcpy(id + 1, boot_id->game_id.data() + 2, 2);
|
||||
|
||||
switch (GetCountry())
|
||||
{
|
||||
default:
|
||||
case Country::Japan:
|
||||
id[3] = 'J';
|
||||
break;
|
||||
case Country::Taiwan:
|
||||
id[3] = 'W';
|
||||
break;
|
||||
case Country::USA:
|
||||
id[3] = 'E';
|
||||
break;
|
||||
case Country::Europe:
|
||||
id[3] = 'P';
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string maker_id{GetMakerID()};
|
||||
memcpy(id + 4, maker_id.c_str(), std::min<std::size_t>(maker_id.size(), 2));
|
||||
|
||||
return DecodeString(id);
|
||||
}
|
||||
|
||||
if (!Read(0, sizeof(id), reinterpret_cast<u8*>(id), partition))
|
||||
return std::string();
|
||||
@@ -27,8 +61,35 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const
|
||||
|
||||
Country VolumeDisc::GetCountry(const Partition& partition) const
|
||||
{
|
||||
// The 0 that we use as a default value is mapped to Country::Unknown and Region::Unknown
|
||||
const u8 country_byte = ReadSwapped<u8>(3, partition).value_or(0);
|
||||
u8 country_byte = 0;
|
||||
|
||||
if (GetVolumeType() == Platform::Triforce)
|
||||
{
|
||||
const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
|
||||
|
||||
switch (boot_id->region_flags)
|
||||
{
|
||||
default:
|
||||
case 0x02: // JAPAN
|
||||
country_byte = 'J';
|
||||
break;
|
||||
case 0x08: // ASIA
|
||||
country_byte = 'W';
|
||||
break;
|
||||
case 0x0E: // USA
|
||||
country_byte = 'E';
|
||||
break;
|
||||
case 0x0C: // EXPORT
|
||||
country_byte = 'P';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The 0 that we use as a default value is mapped to Country::Unknown and Region::Unknown
|
||||
country_byte = ReadSwapped<u8>(3, partition).value_or(0);
|
||||
}
|
||||
|
||||
const Region region = GetRegion();
|
||||
const std::optional<u16> revision = GetRevision();
|
||||
|
||||
@@ -51,6 +112,29 @@ std::string VolumeDisc::GetMakerID(const Partition& partition) const
|
||||
{
|
||||
char maker_id[2];
|
||||
|
||||
// Triforce games have their maker stored in the boot.id file as an actual string
|
||||
if (GetVolumeType() == Platform::Triforce)
|
||||
{
|
||||
const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
|
||||
|
||||
// There only seem to be three different makers here,
|
||||
// so we can just check the first char to difference between them.
|
||||
//
|
||||
// NAMCO CORPORATION, SEGA CORPORATION and Hitmaker co,ltd.
|
||||
|
||||
switch (boot_id->manufacturer[0])
|
||||
{
|
||||
case 'S': // SEGA CORPORATION
|
||||
case 'H': // Hitmaker co,ltd
|
||||
return DecodeString("6E");
|
||||
case 'N': // NAMCO CORPORATION
|
||||
return DecodeString("82");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Fall back to normal maker from header
|
||||
}
|
||||
|
||||
if (!Read(0x4, sizeof(maker_id), reinterpret_cast<u8*>(&maker_id), partition))
|
||||
return std::string();
|
||||
|
||||
@@ -66,6 +150,14 @@ std::optional<u16> VolumeDisc::GetRevision(const Partition& partition) const
|
||||
std::string VolumeDisc::GetInternalName(const Partition& partition) const
|
||||
{
|
||||
char name[0x60];
|
||||
|
||||
// Triforce games have their Title stored in the boot.id file
|
||||
if (GetVolumeType() == Platform::Triforce)
|
||||
{
|
||||
const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
|
||||
return DecodeString(boot_id->game_name);
|
||||
}
|
||||
|
||||
if (!Read(0x20, sizeof(name), reinterpret_cast<u8*>(&name), partition))
|
||||
return std::string();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
@@ -15,6 +16,30 @@ namespace DiscIO
|
||||
class VolumeDisc : public Volume
|
||||
{
|
||||
public:
|
||||
struct BootID
|
||||
{
|
||||
u32 magic; // 0x00 (Always BTID)
|
||||
std::array<uint32_t, 3> unknown0;
|
||||
std::array<uint32_t, 4> unknown1;
|
||||
std::array<char, 4> mediaboard_type; // 0x20 (GCAM for Triforce)
|
||||
uint32_t unknown2;
|
||||
uint16_t year; // 0x28
|
||||
uint8_t month; // 0x2A
|
||||
uint8_t day; // 0x2B
|
||||
uint8_t video_mode; // 0x2C unknown bitmask, resolutions + horizontal/vertical
|
||||
uint8_t unknown3;
|
||||
uint8_t type_3_compatible; // 0x2E (Type-3 compatible titles have this set to 1)
|
||||
uint8_t unknown4;
|
||||
std::array<char, 8> game_id; // 0x30
|
||||
uint32_t region_flags; // 0x38
|
||||
std::array<uint32_t, 9> unknown6;
|
||||
std::array<char, 0x20> manufacturer; // 0x60
|
||||
std::array<char, 0x20> game_name; // 0x80
|
||||
std::array<char, 0x20> game_executable; // 0xA0
|
||||
std::array<char, 0x20> test_executable; // 0xC0
|
||||
std::array<std::array<char, 0x20>, 8> credit_types; // 0xE0
|
||||
};
|
||||
|
||||
std::string GetGameID(const Partition& partition = PARTITION_NONE) const override;
|
||||
Country GetCountry(const Partition& partition = PARTITION_NONE) const override;
|
||||
std::string GetMakerID(const Partition& partition = PARTITION_NONE) const override;
|
||||
@@ -29,4 +54,6 @@ protected:
|
||||
void AddGamePartitionToSyncHash(Common::SHA1::Context* context) const;
|
||||
};
|
||||
|
||||
static_assert(sizeof(VolumeDisc::BootID) == 480);
|
||||
|
||||
} // namespace DiscIO
|
||||
@@ -45,13 +45,11 @@ VolumeGC::VolumeGC(std::unique_ptr<BlobReader> reader)
|
||||
std::unique_ptr<FileInfo> file_info = tmp_fs->FindFileInfo("boot.id");
|
||||
if (!file_info)
|
||||
return;
|
||||
BootID triforce_header;
|
||||
const u64 file_size = ReadFile(*this, PARTITION_NONE, file_info.get(),
|
||||
reinterpret_cast<u8*>(&triforce_header), sizeof(BootID));
|
||||
if (file_size >= 4 && triforce_header.magic == BTID_MAGIC)
|
||||
reinterpret_cast<u8*>(&m_triforce_header), sizeof(BootID));
|
||||
if (file_size >= 4 && m_triforce_header.magic == BTID_MAGIC)
|
||||
{
|
||||
m_is_triforce = true;
|
||||
m_triforce_id = triforce_header.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,16 +88,23 @@ std::string VolumeGC::GetGameTDBID(const Partition& partition) const
|
||||
return GetGameID(partition);
|
||||
}
|
||||
|
||||
std::string VolumeGC::GetTriforceID() const
|
||||
{
|
||||
if (m_is_triforce)
|
||||
return (std::string(m_triforce_id.data(), m_triforce_id.size()));
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
Region VolumeGC::GetRegion() const
|
||||
{
|
||||
if (m_is_triforce)
|
||||
{
|
||||
switch (m_triforce_header.region_flags)
|
||||
{
|
||||
default:
|
||||
case 0x02: // JAPAN
|
||||
case 0x08: // ASIA
|
||||
return Region::NTSC_J;
|
||||
case 0x0E: // USA
|
||||
return Region::NTSC_U;
|
||||
case 0x0C: // EXPORT
|
||||
return Region::PAL;
|
||||
}
|
||||
}
|
||||
|
||||
return RegionCodeToRegion(m_reader->ReadSwapped<u32>(0x458));
|
||||
}
|
||||
|
||||
@@ -184,6 +189,12 @@ std::array<u8, 20> VolumeGC::GetSyncHash() const
|
||||
|
||||
VolumeGC::ConvertedGCBanner VolumeGC::LoadBannerFile() const
|
||||
{
|
||||
// There is at least one Triforce game that has an opening.bnr file but from a different game.
|
||||
if (m_is_triforce)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
GCBanner banner_file;
|
||||
const u64 file_size = ReadFile(*this, PARTITION_NONE, "opening.bnr",
|
||||
reinterpret_cast<u8*>(&banner_file), sizeof(GCBanner));
|
||||
|
||||
@@ -34,7 +34,6 @@ public:
|
||||
const Partition& partition = PARTITION_NONE) const override;
|
||||
const FileSystem* GetFileSystem(const Partition& partition = PARTITION_NONE) const override;
|
||||
std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const override;
|
||||
std::string GetTriforceID() const override;
|
||||
std::map<Language, std::string> GetShortNames() const override;
|
||||
std::map<Language, std::string> GetLongNames() const override;
|
||||
std::map<Language, std::string> GetShortMakers() const override;
|
||||
@@ -50,9 +49,10 @@ public:
|
||||
DataSizeType GetDataSizeType() const override;
|
||||
u64 GetRawSize() const override;
|
||||
const BlobReader& GetBlobReader() const override;
|
||||
|
||||
std::array<u8, 20> GetSyncHash() const override;
|
||||
|
||||
const BootID* GetTriforceBootID() const { return &m_triforce_header; }
|
||||
|
||||
private:
|
||||
static constexpr u32 GC_BANNER_WIDTH = 96;
|
||||
static constexpr u32 GC_BANNER_HEIGHT = 32;
|
||||
@@ -77,13 +77,6 @@ private:
|
||||
// (only one for BNR1 type)
|
||||
};
|
||||
|
||||
struct BootID
|
||||
{
|
||||
u32 magic; // "BTID"
|
||||
u32 padding[11];
|
||||
std::array<char, 4> id;
|
||||
};
|
||||
|
||||
struct ConvertedGCBanner
|
||||
{
|
||||
ConvertedGCBanner();
|
||||
@@ -112,8 +105,9 @@ private:
|
||||
|
||||
std::unique_ptr<BlobReader> m_reader;
|
||||
|
||||
BootID m_triforce_header;
|
||||
|
||||
bool m_is_triforce;
|
||||
std::array<char, 4> m_triforce_id;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
||||
@@ -279,6 +279,7 @@
|
||||
<ClInclude Include="Core\HW\DSPLLE\DSPDebugInterface.h" />
|
||||
<ClInclude Include="Core\HW\DSPLLE\DSPLLE.h" />
|
||||
<ClInclude Include="Core\HW\DSPLLE\DSPSymbols.h" />
|
||||
<ClInclude Include="Core\HW\DVD\AMMediaboard.h" />
|
||||
<ClInclude Include="Core\HW\DVD\DVDInterface.h" />
|
||||
<ClInclude Include="Core\HW\DVD\DVDMath.h" />
|
||||
<ClInclude Include="Core\HW\DVD\DVDThread.h" />
|
||||
@@ -316,12 +317,16 @@
|
||||
<ClInclude Include="Core\HW\HSP\HSP_DeviceARAMExpansion.h" />
|
||||
<ClInclude Include="Core\HW\HSP\HSP_DeviceNull.h" />
|
||||
<ClInclude Include="Core\HW\HW.h" />
|
||||
<ClInclude Include="Core\HW\MagCard\C1231BR.h" />
|
||||
<ClInclude Include="Core\HW\MagCard\C1231LR.h" />
|
||||
<ClInclude Include="Core\HW\MagCard\MagneticCardReader.h" />
|
||||
<ClInclude Include="Core\HW\Memmap.h" />
|
||||
<ClInclude Include="Core\HW\MemoryInterface.h" />
|
||||
<ClInclude Include="Core\HW\MMIO.h" />
|
||||
<ClInclude Include="Core\HW\MMIOHandlers.h" />
|
||||
<ClInclude Include="Core\HW\ProcessorInterface.h" />
|
||||
<ClInclude Include="Core\HW\SI\SI_Device.h" />
|
||||
<ClInclude Include="Core\HW\SI\SI_DeviceAMBaseboard.h" />
|
||||
<ClInclude Include="Core\HW\SI\SI_DeviceDanceMat.h" />
|
||||
<ClInclude Include="Core\HW\SI\SI_DeviceGBA.h" />
|
||||
<ClInclude Include="Core\HW\SI\SI_DeviceGBAEmu.h" />
|
||||
@@ -971,6 +976,7 @@
|
||||
<ClCompile Include="Core\HW\DSPLLE\DSPHost.cpp" />
|
||||
<ClCompile Include="Core\HW\DSPLLE\DSPLLE.cpp" />
|
||||
<ClCompile Include="Core\HW\DSPLLE\DSPSymbols.cpp" />
|
||||
<ClCompile Include="Core\HW\DVD\AMMediaboard.cpp" />
|
||||
<ClCompile Include="Core\HW\DVD\DVDInterface.cpp" />
|
||||
<ClCompile Include="Core\HW\DVD\DVDMath.cpp" />
|
||||
<ClCompile Include="Core\HW\DVD\DVDThread.cpp" />
|
||||
@@ -1012,11 +1018,15 @@
|
||||
<ClCompile Include="Core\HW\HSP\HSP_DeviceARAMExpansion.cpp" />
|
||||
<ClCompile Include="Core\HW\HSP\HSP_DeviceNull.cpp" />
|
||||
<ClCompile Include="Core\HW\HW.cpp" />
|
||||
<ClCompile Include="Core\HW\MagCard\C1231BR.cpp" />
|
||||
<ClCompile Include="Core\HW\MagCard\C1231LR.cpp" />
|
||||
<ClCompile Include="Core\HW\MagCard\MagneticCardReader.cpp" />
|
||||
<ClCompile Include="Core\HW\Memmap.cpp" />
|
||||
<ClCompile Include="Core\HW\MemoryInterface.cpp" />
|
||||
<ClCompile Include="Core\HW\MMIO.cpp" />
|
||||
<ClCompile Include="Core\HW\ProcessorInterface.cpp" />
|
||||
<ClCompile Include="Core\HW\SI\SI_Device.cpp" />
|
||||
<ClCompile Include="Core\HW\SI\SI_DeviceAMBaseboard.cpp" />
|
||||
<ClCompile Include="Core\HW\SI\SI_DeviceDanceMat.cpp" />
|
||||
<ClCompile Include="Core\HW\SI\SI_DeviceGBA.cpp" />
|
||||
<ClCompile Include="Core\HW\SI\SI_DeviceGBAEmu.cpp" />
|
||||
|
||||
@@ -376,6 +376,8 @@ add_executable(dolphin-emu
|
||||
Settings/OnScreenDisplayPane.h
|
||||
Settings/PathPane.cpp
|
||||
Settings/PathPane.h
|
||||
Settings/TriforcePane.cpp
|
||||
Settings/TriforcePane.h
|
||||
Settings/USBDevicePicker.cpp
|
||||
Settings/USBDevicePicker.h
|
||||
Settings/WiiPane.cpp
|
||||
|
||||
@@ -42,6 +42,7 @@ static constexpr std::array s_gc_types = {
|
||||
#endif
|
||||
SIDeviceName{SerialInterface::SIDEVICE_GC_GBA, _trans("GBA (TCP)")},
|
||||
SIDeviceName{SerialInterface::SIDEVICE_GC_KEYBOARD, _trans("Keyboard Controller")},
|
||||
SIDeviceName{SerialInterface::SIDEVICE_AM_BASEBOARD, _trans("Triforce Baseboard")},
|
||||
};
|
||||
|
||||
static std::optional<int> ToGCMenuIndex(const SerialInterface::SIDevices sidevice)
|
||||
@@ -156,6 +157,9 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index)
|
||||
case SerialInterface::SIDEVICE_GC_KEYBOARD:
|
||||
type = MappingWindow::Type::MAPPING_GC_KEYBOARD;
|
||||
break;
|
||||
case SerialInterface::SIDEVICE_AM_BASEBOARD:
|
||||
type = MappingWindow::Type::MAPPING_AM_BASEBOARD;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,12 +122,6 @@ QGroupBox* InfoWidget::CreateGameDetails()
|
||||
m_game.GetMakerID() + ")");
|
||||
|
||||
layout->addRow(tr("Name:"), internal_name);
|
||||
if (m_game.GetPlatform() == DiscIO::Platform::Triforce)
|
||||
{
|
||||
const auto triforce_id_string = QString::fromStdString(m_game.GetTriforceID());
|
||||
auto* const triforce_id = CreateValueDisplay(triforce_id_string);
|
||||
layout->addRow(tr("Triforce ID:"), triforce_id);
|
||||
}
|
||||
layout->addRow(tr("Game ID:"), game_id);
|
||||
layout->addRow(tr("Country:"), country);
|
||||
layout->addRow(tr("Maker:"), maker);
|
||||
|
||||
@@ -11,19 +11,25 @@
|
||||
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
GCPadEmu::GCPadEmu(MappingWindow* window) : MappingWidget(window)
|
||||
GCPadEmu::GCPadEmu(MappingWindow* window, SubType sub_type) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
CreateMainLayout(sub_type);
|
||||
}
|
||||
|
||||
void GCPadEmu::CreateMainLayout()
|
||||
void GCPadEmu::CreateMainLayout(SubType sub_type)
|
||||
{
|
||||
auto* layout = new QGridLayout;
|
||||
|
||||
layout->addWidget(CreateGroupBox(tr("Buttons"), Pad::GetGroup(GetPort(), PadGroup::Buttons)), 0,
|
||||
0);
|
||||
layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)), 1, 0, -1,
|
||||
1);
|
||||
layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)), 1, 0);
|
||||
|
||||
if (sub_type == SubType::AMBaseboard)
|
||||
{
|
||||
layout->addWidget(CreateGroupBox(tr("Triforce"), Pad::GetGroup(GetPort(), PadGroup::Triforce)),
|
||||
2, 0);
|
||||
}
|
||||
|
||||
layout->addWidget(
|
||||
CreateGroupBox(tr("Control Stick"), Pad::GetGroup(GetPort(), PadGroup::MainStick)), 0, 1, -1,
|
||||
1);
|
||||
|
||||
@@ -9,12 +9,28 @@ class GCPadEmu final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GCPadEmu(MappingWindow* window);
|
||||
enum class SubType
|
||||
{
|
||||
StandardController,
|
||||
AMBaseboard,
|
||||
};
|
||||
|
||||
GCPadEmu(MappingWindow* window, SubType sub_type);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
void CreateMainLayout(SubType sub_type);
|
||||
};
|
||||
|
||||
[[nodiscard]] inline auto CreateStandardControllerMappingWidget(MappingWindow* parent)
|
||||
{
|
||||
return new GCPadEmu{parent, GCPadEmu::SubType::StandardController};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto CreateAMBaseboardMappingWidget(MappingWindow* parent)
|
||||
{
|
||||
return new GCPadEmu{parent, GCPadEmu::SubType::AMBaseboard};
|
||||
}
|
||||
@@ -26,6 +26,9 @@
|
||||
#include "Common/IniFile.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/HW/SI/SI.h"
|
||||
#include "Core/HW/SI/SI_DeviceAMBaseboard.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/FreeLookGeneral.h"
|
||||
#include "DolphinQt/Config/Mapping/FreeLookRotation.h"
|
||||
#include "DolphinQt/Config/Mapping/GBAPadEmu.h"
|
||||
@@ -457,7 +460,7 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
|
||||
case Type::MAPPING_GC_STEERINGWHEEL:
|
||||
case Type::MAPPING_GC_DANCEMAT:
|
||||
case Type::MAPPING_GCPAD:
|
||||
widget = new GCPadEmu(this);
|
||||
widget = CreateStandardControllerMappingWidget(this);
|
||||
setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1));
|
||||
AddWidget(tr("GameCube Controller"), widget);
|
||||
break;
|
||||
@@ -516,6 +519,11 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
|
||||
setWindowTitle(tr("Free Look Controller %1").arg(GetPort() + 1));
|
||||
}
|
||||
break;
|
||||
case Type::MAPPING_AM_BASEBOARD:
|
||||
widget = CreateAMBaseboardMappingWidget(this);
|
||||
setWindowTitle(tr("Triforce Baseboard at Port %1").arg(GetPort() + 1));
|
||||
AddWidget(tr("Triforce Baseboard"), widget);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public:
|
||||
MAPPING_HOTKEYS,
|
||||
// Freelook
|
||||
MAPPING_FREELOOK,
|
||||
// Triforce
|
||||
MAPPING_AM_BASEBOARD,
|
||||
};
|
||||
|
||||
explicit MappingWindow(QWidget* parent, Type type, int port_num);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "DolphinQt/Settings/InterfacePane.h"
|
||||
#include "DolphinQt/Settings/OnScreenDisplayPane.h"
|
||||
#include "DolphinQt/Settings/PathPane.h"
|
||||
#include "DolphinQt/Settings/TriforcePane.h"
|
||||
#include "DolphinQt/Settings/WiiPane.h"
|
||||
|
||||
StackedSettingsWindow::StackedSettingsWindow(QWidget* parent) : QDialog{parent}
|
||||
@@ -197,8 +198,9 @@ SettingsWindow::SettingsWindow(MainWindow* parent) : StackedSettingsWindow{paren
|
||||
AddWrappedPane(new OnScreenDisplayPane, tr("On-Screen Display"));
|
||||
AddWrappedPane(new AudioPane, tr("Audio"));
|
||||
AddWrappedPane(new PathPane, tr("Paths"));
|
||||
AddWrappedPane(new GameCubePane, tr("GameCube"));
|
||||
AddWrappedPane(new GameCubePane{parent}, tr("GameCube"));
|
||||
AddWrappedPane(new WiiPane, tr("Wii"));
|
||||
AddWrappedPane(new TriforcePane, tr("Triforce"));
|
||||
AddWrappedPane(new AdvancedPane, tr("Advanced"));
|
||||
|
||||
OnDoneCreatingPanes();
|
||||
|
||||
@@ -49,6 +49,7 @@ enum class SettingsWindowPaneIndex : int
|
||||
Paths,
|
||||
GameCube,
|
||||
Wii,
|
||||
Triforce,
|
||||
Advanced,
|
||||
};
|
||||
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <bit>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Network.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/DVD/AMMediaboard.h"
|
||||
#include "Core/IOS/Network/SSL.h"
|
||||
#include "Core/IOS/Network/Socket.h"
|
||||
#include "Core/System.h"
|
||||
@@ -96,7 +100,7 @@ QTableWidgetItem* GetSocketState(s32 host_fd)
|
||||
return new QTableWidgetItem(QTableWidget::tr("Unbound"));
|
||||
}
|
||||
|
||||
static QTableWidgetItem* GetSocketBlocking(const IOS::HLE::WiiSockMan& socket_manager, s32 wii_fd)
|
||||
QTableWidgetItem* GetSocketBlocking(const IOS::HLE::WiiSockMan& socket_manager, s32 wii_fd)
|
||||
{
|
||||
if (socket_manager.GetHostSocket(wii_fd) < 0)
|
||||
return new QTableWidgetItem();
|
||||
@@ -104,7 +108,7 @@ static QTableWidgetItem* GetSocketBlocking(const IOS::HLE::WiiSockMan& socket_ma
|
||||
return new QTableWidgetItem(is_blocking ? QTableWidget::tr("Yes") : QTableWidget::tr("No"));
|
||||
}
|
||||
|
||||
static QString GetAddressAndPort(const sockaddr_in& addr)
|
||||
QString GetAddressAndPort(const sockaddr_in& addr)
|
||||
{
|
||||
char buffer[16];
|
||||
const char* addr_str = inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));
|
||||
@@ -139,6 +143,50 @@ QTableWidgetItem* GetSocketName(s32 host_fd)
|
||||
|
||||
return new QTableWidgetItem(QStringLiteral("%1->%2").arg(sock_name).arg(peer_name));
|
||||
}
|
||||
|
||||
QTableWidgetItem* GetSocketRedirections(s32 host_fd,
|
||||
const AMMediaboard::IPRedirections& ip_redirections)
|
||||
{
|
||||
if (host_fd < 0 || ip_redirections.empty())
|
||||
return new QTableWidgetItem();
|
||||
|
||||
sockaddr_in sock_addr;
|
||||
socklen_t sock_addr_len = sizeof(sockaddr_in);
|
||||
if (getsockname(host_fd, reinterpret_cast<sockaddr*>(&sock_addr), &sock_addr_len) != 0)
|
||||
return new QTableWidgetItem();
|
||||
const Common::IPv4Port sock_ip_port{std::bit_cast<Common::IPAddress>(sock_addr.sin_addr),
|
||||
sock_addr.sin_port};
|
||||
|
||||
sockaddr_in peer_addr;
|
||||
socklen_t peer_addr_len = sizeof(sockaddr_in);
|
||||
bool has_peer =
|
||||
getpeername(host_fd, reinterpret_cast<sockaddr*>(&peer_addr), &peer_addr_len) == 0;
|
||||
const Common::IPv4Port peer_ip_port =
|
||||
has_peer ? Common::IPv4Port{std::bit_cast<Common::IPAddress>(peer_addr.sin_addr),
|
||||
peer_addr.sin_port} :
|
||||
Common::IPv4Port{};
|
||||
|
||||
QStringList sock_rules;
|
||||
QStringList peer_rules;
|
||||
for (const auto& rule : ip_redirections)
|
||||
{
|
||||
if (rule.replacement.IsMatch(sock_ip_port))
|
||||
sock_rules << QString::fromStdString(rule.ToString());
|
||||
if (!has_peer)
|
||||
continue;
|
||||
if (rule.replacement.IsMatch(peer_ip_port))
|
||||
peer_rules << QString::fromStdString(rule.ToString());
|
||||
}
|
||||
|
||||
if (sock_rules.isEmpty() && peer_rules.isEmpty())
|
||||
return new QTableWidgetItem();
|
||||
if (peer_rules.isEmpty())
|
||||
return new QTableWidgetItem(sock_rules.join(QStringLiteral("\n")));
|
||||
return new QTableWidgetItem(QStringLiteral("Sock rules:\n%1\n"
|
||||
"\nPeer rules:%2")
|
||||
.arg(sock_rules.join(QStringLiteral("\n")))
|
||||
.arg(peer_rules.join(QStringLiteral("\n"))));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NetworkWidget::NetworkWidget(QWidget* parent) : QDockWidget(parent)
|
||||
@@ -184,7 +232,7 @@ void NetworkWidget::closeEvent(QCloseEvent*)
|
||||
Settings::Instance().SetNetworkVisible(false);
|
||||
}
|
||||
|
||||
void NetworkWidget::showEvent(QShowEvent* event)
|
||||
void NetworkWidget::showEvent(QShowEvent*)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
@@ -208,45 +256,34 @@ void NetworkWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_dump_format_combo, &QComboBox::currentIndexChanged, this,
|
||||
&NetworkWidget::OnDumpFormatComboChanged);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_dump_ssl_read_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_ssl_write_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_WRITE, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_root_ca_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_ROOT_CA, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_peer_cert_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_PEER_CERT, state == Qt::Checked);
|
||||
});
|
||||
connect(m_verify_certificates_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_VERIFY_CERTIFICATES, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_bba_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
|
||||
});
|
||||
using CheckState = Qt::CheckState;
|
||||
static constexpr auto checkStateChanged = &QCheckBox::checkStateChanged;
|
||||
#else
|
||||
connect(m_dump_ssl_read_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
using CheckState = int;
|
||||
static constexpr auto checkStateChanged = &QCheckBox::stateChanged;
|
||||
#endif
|
||||
|
||||
connect(m_dump_ssl_read_checkbox, checkStateChanged, [](CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_ssl_write_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
connect(m_dump_ssl_write_checkbox, checkStateChanged, [](CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_WRITE, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_root_ca_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
connect(m_dump_root_ca_checkbox, checkStateChanged, [](CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_ROOT_CA, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_peer_cert_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
connect(m_dump_peer_cert_checkbox, checkStateChanged, [](CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_PEER_CERT, state == Qt::Checked);
|
||||
});
|
||||
connect(m_verify_certificates_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
connect(m_verify_certificates_checkbox, checkStateChanged, [](CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_VERIFY_CERTIFICATES, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_bba_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
connect(m_dump_bba_checkbox, checkStateChanged, [](CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
|
||||
});
|
||||
#endif
|
||||
|
||||
connect(m_open_dump_folder, &QPushButton::clicked, [] {
|
||||
const std::string location = File::GetUserPath(D_DUMPSSL_IDX);
|
||||
const QUrl url = QUrl::fromLocalFile(QString::fromStdString(location));
|
||||
@@ -254,24 +291,12 @@ void NetworkWidget::ConnectWidgets()
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkWidget::Update()
|
||||
void NetworkWidget::UpdateWiiSocketTable(Core::System& system)
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
if (Core::GetState(system) != Core::State::Paused)
|
||||
{
|
||||
m_socket_table->setDisabled(true);
|
||||
m_ssl_table->setDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
m_socket_table->setDisabled(false);
|
||||
m_ssl_table->setDisabled(false);
|
||||
|
||||
// needed because there's a race condition on the IOS instance otherwise
|
||||
const Core::CPUThreadGuard guard(system);
|
||||
// Show Wii socket blocking state
|
||||
m_socket_table->showColumn(4);
|
||||
// Hide Triforce IP redirections
|
||||
m_socket_table->hideColumn(6);
|
||||
|
||||
auto* ios = system.GetIOS();
|
||||
if (!ios)
|
||||
@@ -281,7 +306,6 @@ void NetworkWidget::Update()
|
||||
if (!socket_manager)
|
||||
return;
|
||||
|
||||
m_socket_table->setRowCount(0);
|
||||
for (s32 wii_fd = 0; wii_fd < IOS::HLE::WII_SOCKET_FD_MAX; wii_fd++)
|
||||
{
|
||||
m_socket_table->insertRow(wii_fd);
|
||||
@@ -293,9 +317,7 @@ void NetworkWidget::Update()
|
||||
m_socket_table->setItem(wii_fd, 4, GetSocketBlocking(*socket_manager, wii_fd));
|
||||
m_socket_table->setItem(wii_fd, 5, GetSocketName(host_fd));
|
||||
}
|
||||
m_socket_table->resizeColumnsToContents();
|
||||
|
||||
m_ssl_table->setRowCount(0);
|
||||
for (s32 ssl_id = 0; ssl_id < IOS::HLE::NET_SSL_MAXINSTANCES; ssl_id++)
|
||||
{
|
||||
m_ssl_table->insertRow(ssl_id);
|
||||
@@ -312,6 +334,58 @@ void NetworkWidget::Update()
|
||||
m_ssl_table->setItem(ssl_id, 3, GetSocketState(host_fd));
|
||||
m_ssl_table->setItem(ssl_id, 4, GetSocketName(host_fd));
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkWidget::UpdateTriforceSocketTable()
|
||||
{
|
||||
// No easy way to get socket blocking state on Windows
|
||||
m_socket_table->hideColumn(4);
|
||||
// Show active IP redirections
|
||||
m_socket_table->showColumn(6);
|
||||
const auto ip_redirections = AMMediaboard::GetIPRedirections();
|
||||
for (s32 triforce_fd = 0; triforce_fd != AMMediaboard::SOCKET_FD_MAX; ++triforce_fd)
|
||||
{
|
||||
m_socket_table->insertRow(triforce_fd);
|
||||
const s32 host_fd = AMMediaboard::DebuggerGetSocket(triforce_fd);
|
||||
m_socket_table->setItem(triforce_fd, 0, new QTableWidgetItem(QString::number(triforce_fd)));
|
||||
m_socket_table->setItem(triforce_fd, 1, GetSocketDomain(host_fd));
|
||||
m_socket_table->setItem(triforce_fd, 2, GetSocketType(host_fd));
|
||||
m_socket_table->setItem(triforce_fd, 3, GetSocketState(host_fd));
|
||||
m_socket_table->setItem(triforce_fd, 4, new QTableWidgetItem(QTableWidget::tr("Unknown")));
|
||||
m_socket_table->setItem(triforce_fd, 5, GetSocketName(host_fd));
|
||||
m_socket_table->setItem(triforce_fd, 6, GetSocketRedirections(host_fd, ip_redirections));
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkWidget::Update()
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
if (Core::GetState(system) != Core::State::Paused)
|
||||
{
|
||||
m_socket_table->setDisabled(true);
|
||||
m_ssl_table->setDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// needed because there's a race condition on the IOS instance otherwise
|
||||
const Core::CPUThreadGuard guard(system);
|
||||
m_socket_table->setDisabled(false);
|
||||
m_socket_table->setRowCount(0);
|
||||
m_ssl_table->setRowCount(0);
|
||||
if (system.IsTriforce())
|
||||
{
|
||||
UpdateTriforceSocketTable();
|
||||
}
|
||||
else if (system.IsWii())
|
||||
{
|
||||
m_ssl_table->setDisabled(false);
|
||||
UpdateWiiSocketTable(system);
|
||||
}
|
||||
m_socket_table->resizeColumnsToContents();
|
||||
m_socket_table->resizeRowsToContents();
|
||||
m_ssl_table->resizeColumnsToContents();
|
||||
|
||||
const bool is_pcap = Config::Get(Config::MAIN_NETWORK_DUMP_AS_PCAP);
|
||||
@@ -328,27 +402,31 @@ void NetworkWidget::Update()
|
||||
const int combo_index = int([is_pcap, is_ssl_read, is_ssl_write]() -> FormatComboId {
|
||||
if (is_pcap)
|
||||
return FormatComboId::PCAP;
|
||||
else if (is_ssl_read && is_ssl_write)
|
||||
|
||||
if (is_ssl_read && is_ssl_write)
|
||||
return FormatComboId::BinarySSL;
|
||||
else if (is_ssl_read)
|
||||
|
||||
if (is_ssl_read)
|
||||
return FormatComboId::BinarySSLRead;
|
||||
else if (is_ssl_write)
|
||||
|
||||
if (is_ssl_write)
|
||||
return FormatComboId::BinarySSLWrite;
|
||||
else
|
||||
return FormatComboId::None;
|
||||
|
||||
return FormatComboId::None;
|
||||
}());
|
||||
m_dump_format_combo->setCurrentIndex(combo_index);
|
||||
}
|
||||
|
||||
QGroupBox* NetworkWidget::CreateSocketTableGroup()
|
||||
{
|
||||
QGroupBox* socket_table_group = new QGroupBox(tr("Socket table"));
|
||||
QGridLayout* socket_table_layout = new QGridLayout;
|
||||
auto* const socket_table_group = new QGroupBox(tr("Socket table"));
|
||||
auto* const socket_table_layout = new QGridLayout;
|
||||
socket_table_group->setLayout(socket_table_layout);
|
||||
|
||||
m_socket_table = new QTableWidget();
|
||||
// i18n: FD stands for file descriptor (and in this case refers to sockets, not regular files)
|
||||
QStringList header{tr("FD"), tr("Domain"), tr("Type"), tr("State"), tr("Blocking"), tr("Name")};
|
||||
QStringList header{tr("FD"), tr("Domain"), tr("Type"), tr("State"),
|
||||
tr("Blocking"), tr("Name"), tr("Redirections")};
|
||||
m_socket_table->setColumnCount(static_cast<int>(header.size()));
|
||||
|
||||
m_socket_table->setHorizontalHeaderLabels(header);
|
||||
@@ -365,8 +443,8 @@ QGroupBox* NetworkWidget::CreateSocketTableGroup()
|
||||
|
||||
QGroupBox* NetworkWidget::CreateSSLContextGroup()
|
||||
{
|
||||
QGroupBox* ssl_context_group = new QGroupBox(tr("SSL context"));
|
||||
QGridLayout* ssl_context_layout = new QGridLayout;
|
||||
auto* const ssl_context_group = new QGroupBox(tr("SSL context"));
|
||||
auto* const ssl_context_layout = new QGridLayout;
|
||||
ssl_context_group->setLayout(ssl_context_layout);
|
||||
|
||||
m_ssl_table = new QTableWidget();
|
||||
@@ -387,8 +465,8 @@ QGroupBox* NetworkWidget::CreateSSLContextGroup()
|
||||
|
||||
QGroupBox* NetworkWidget::CreateDumpOptionsGroup()
|
||||
{
|
||||
auto* dump_options_group = new QGroupBox(tr("Dump options"));
|
||||
auto* dump_options_layout = new QVBoxLayout;
|
||||
auto* const dump_options_group = new QGroupBox(tr("Dump options"));
|
||||
auto* const dump_options_layout = new QVBoxLayout;
|
||||
dump_options_group->setLayout(dump_options_layout);
|
||||
|
||||
m_dump_format_combo = CreateDumpFormatCombo();
|
||||
@@ -402,9 +480,9 @@ QGroupBox* NetworkWidget::CreateDumpOptionsGroup()
|
||||
m_open_dump_folder = new QPushButton(tr("Open dump folder"));
|
||||
m_open_dump_folder->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
auto* combo_label = new QLabel(tr("Network dump format:"));
|
||||
auto* const combo_label = new QLabel(tr("Network dump format:"));
|
||||
combo_label->setBuddy(m_dump_format_combo);
|
||||
auto* combo_layout = new QHBoxLayout;
|
||||
auto* const combo_layout = new QHBoxLayout;
|
||||
combo_layout->addWidget(combo_label);
|
||||
const int combo_label_space =
|
||||
combo_label->fontMetrics().boundingRect(QStringLiteral("__")).width();
|
||||
@@ -426,8 +504,8 @@ QGroupBox* NetworkWidget::CreateDumpOptionsGroup()
|
||||
|
||||
QGroupBox* NetworkWidget::CreateSecurityOptionsGroup()
|
||||
{
|
||||
auto* security_options_group = new QGroupBox(tr("Security options"));
|
||||
auto* security_options_layout = new QVBoxLayout;
|
||||
auto* const security_options_group = new QGroupBox(tr("Security options"));
|
||||
auto* const security_options_layout = new QVBoxLayout;
|
||||
security_options_group->setLayout(security_options_layout);
|
||||
|
||||
m_verify_certificates_checkbox = new QCheckBox(tr("Verify certificates"));
|
||||
|
||||
@@ -14,6 +14,11 @@ class QShowEvent;
|
||||
class QTableWidget;
|
||||
class QTableWidgetItem;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class System;
|
||||
}
|
||||
|
||||
class NetworkWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -29,6 +34,8 @@ private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void UpdateWiiSocketTable(Core::System& system);
|
||||
void UpdateTriforceSocketTable();
|
||||
void Update();
|
||||
|
||||
QGroupBox* CreateSocketTableGroup();
|
||||
|
||||
@@ -223,6 +223,7 @@
|
||||
<ClCompile Include="Settings\GeneralPane.cpp" />
|
||||
<ClCompile Include="Settings\InterfacePane.cpp" />
|
||||
<ClCompile Include="Settings\OnScreenDisplayPane.cpp" />
|
||||
<ClCompile Include="Settings\TriforcePane.cpp" />
|
||||
<ClCompile Include="Settings\PathPane.cpp" />
|
||||
<ClCompile Include="Settings\USBDevicePicker.cpp" />
|
||||
<ClCompile Include="Settings\WiiPane.cpp" />
|
||||
@@ -439,6 +440,7 @@
|
||||
<QtMoc Include="Settings\GeneralPane.h" />
|
||||
<QtMoc Include="Settings\InterfacePane.h" />
|
||||
<QtMoc Include="Settings\PathPane.h" />
|
||||
<QtMoc Include="Settings\TriforcePane.h" />
|
||||
<QtMoc Include="Settings\USBDevicePicker.h" />
|
||||
<QtMoc Include="Settings\WiiPane.h" />
|
||||
<QtMoc Include="SkylanderPortal\SkylanderPortalWindow.h" />
|
||||
@@ -523,4 +525,4 @@
|
||||
<Message Text="Copy: @(BinaryFiles) -> $(BinaryOutputDir)" Importance="High" />
|
||||
<Copy SourceFiles="@(BinaryFiles)" DestinationFolder="$(BinaryOutputDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1151,6 +1151,32 @@ void MainWindow::StartGame(std::unique_ptr<BootParameters>&& parameters)
|
||||
if (!NKitWarningDialog::ShowUnlessDisabled())
|
||||
return;
|
||||
}
|
||||
|
||||
const auto volume_type =
|
||||
std::get<BootParameters::Disc>(parameters->parameters).volume->GetVolumeType();
|
||||
if (volume_type != DiscIO::Platform::Triforce)
|
||||
{
|
||||
const bool triforce_hardware_sp1 =
|
||||
Config::Get(Config::MAIN_SERIAL_PORT_1) == ExpansionInterface::EXIDeviceType::Baseboard;
|
||||
const bool triforce_hardware_port_1 = Config::Get(Config::GetInfoForSIDevice(0)) ==
|
||||
SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD;
|
||||
|
||||
// Some Triforce tools don't include a boot.id file, but they can still be launched.
|
||||
if (triforce_hardware_sp1)
|
||||
{
|
||||
ModalMessageBox::warning(this, tr("Warning"),
|
||||
tr("Non-Triforce games cannot be booted with Triforce hardware "
|
||||
"attached.\nPlease remove the Triforce Baseboard from SP1."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
if (triforce_hardware_port_1)
|
||||
{
|
||||
ModalMessageBox::warning(this, tr("Warning"),
|
||||
tr("Non-Triforce games cannot be booted with Triforce hardware "
|
||||
"attached.\nPlease remove the Triforce Baseboard from Port 1."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're running, only start a new game once we've stopped the last.
|
||||
@@ -1289,6 +1315,12 @@ void MainWindow::ShowControllersWindow()
|
||||
m_settings_window->SelectPane(SettingsWindowPaneIndex::Controllers);
|
||||
}
|
||||
|
||||
void MainWindow::ShowTriforceWindow()
|
||||
{
|
||||
ShowSettingsWindow();
|
||||
m_settings_window->SelectPane(SettingsWindowPaneIndex::Triforce);
|
||||
}
|
||||
|
||||
void MainWindow::ShowFreeLookWindow()
|
||||
{
|
||||
if (!m_freelook_window)
|
||||
|
||||
@@ -92,6 +92,8 @@ public:
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
QMenu* createPopupMenu() override;
|
||||
|
||||
void ShowTriforceWindow();
|
||||
|
||||
signals:
|
||||
void ReadOnlyModeChanged(bool read_only);
|
||||
void RecordingStatusChanged(bool recording);
|
||||
|
||||
@@ -319,6 +319,9 @@ void MenuBar::AddToolsMenu()
|
||||
m_pal_ipl =
|
||||
gc_ipl->addAction(tr("PAL"), this, [this] { emit BootGameCubeIPL(DiscIO::Region::PAL); });
|
||||
|
||||
m_dev_ipl = gc_ipl->addAction(tr("Triforce"), this,
|
||||
[this] { emit BootGameCubeIPL(DiscIO::Region::Unknown); });
|
||||
|
||||
tools_menu->addAction(tr("Memory Card Manager"), this, [this] { emit ShowMemcardManager(); });
|
||||
|
||||
tools_menu->addSeparator();
|
||||
@@ -1091,6 +1094,7 @@ void MenuBar::UpdateToolsMenu(const Core::State state)
|
||||
m_ntscj_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(JAP_DIR)));
|
||||
m_ntscu_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(USA_DIR)));
|
||||
m_pal_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(EUR_DIR)));
|
||||
m_dev_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(DEV_DIR)));
|
||||
m_wad_install_action->setEnabled(is_uninitialized);
|
||||
m_import_backup->setEnabled(is_uninitialized);
|
||||
m_check_nand->setEnabled(is_uninitialized);
|
||||
|
||||
@@ -227,6 +227,7 @@ private:
|
||||
QAction* m_ntscj_ipl;
|
||||
QAction* m_ntscu_ipl;
|
||||
QAction* m_pal_ipl;
|
||||
QAction* m_dev_ipl;
|
||||
QMenu* m_manage_nand_menu;
|
||||
QAction* m_import_backup;
|
||||
QAction* m_check_nand;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/EXI/EXI.h"
|
||||
#include "Core/HW/GCMemcard/GCMemcard.h"
|
||||
#include "DolphinQt/Config/SettingsWindow.h"
|
||||
#ifdef HAS_LIBMGBA
|
||||
#include "Core/NetPlayServer.h"
|
||||
#endif
|
||||
@@ -50,15 +51,18 @@
|
||||
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
#include "DolphinQt/Settings/BroadbandAdapterSettingsDialog.h"
|
||||
#include "DolphinQt/Settings/TriforcePane.h"
|
||||
|
||||
constexpr std::initializer_list<ExpansionInterface::Slot> GUI_SLOTS = {
|
||||
ExpansionInterface::Slot::A, ExpansionInterface::Slot::B, ExpansionInterface::Slot::SP1};
|
||||
|
||||
GameCubePane::GameCubePane()
|
||||
GameCubePane::GameCubePane(MainWindow* main_window)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
|
||||
connect(this, &GameCubePane::ShowTriforceWindow, main_window, &MainWindow::ShowTriforceWindow);
|
||||
}
|
||||
|
||||
void GameCubePane::CreateWidgets()
|
||||
@@ -151,6 +155,7 @@ void GameCubePane::CreateWidgets()
|
||||
EXIDeviceType::EthernetIPC,
|
||||
#endif
|
||||
EXIDeviceType::ModemTapServer,
|
||||
EXIDeviceType::Baseboard,
|
||||
})
|
||||
{
|
||||
m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()),
|
||||
@@ -350,11 +355,12 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
|
||||
break;
|
||||
}
|
||||
case ExpansionInterface::Slot::SP1:
|
||||
has_config = (device == ExpansionInterface::EXIDeviceType::Ethernet ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetXLink ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetTapServer ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn ||
|
||||
device == ExpansionInterface::EXIDeviceType::ModemTapServer);
|
||||
has_config = device == ExpansionInterface::EXIDeviceType::Ethernet ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetXLink ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetTapServer ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn ||
|
||||
device == ExpansionInterface::EXIDeviceType::ModemTapServer ||
|
||||
device == ExpansionInterface::EXIDeviceType::Baseboard;
|
||||
break;
|
||||
case ExpansionInterface::Slot::SP2:
|
||||
has_config = false;
|
||||
@@ -418,6 +424,11 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
|
||||
dialog.exec();
|
||||
return;
|
||||
}
|
||||
case ExpansionInterface::EXIDeviceType::Baseboard:
|
||||
{
|
||||
ShowTriforceWindow();
|
||||
return;
|
||||
}
|
||||
default:
|
||||
PanicAlertFmt("Unknown settings pressed for {}", device);
|
||||
return;
|
||||
@@ -452,19 +463,23 @@ bool GameCubePane::SetMemcard(ExpansionInterface::Slot slot, const QString& file
|
||||
const std::string jp_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::NTSC_J);
|
||||
const std::string us_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::NTSC_U);
|
||||
const std::string eu_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::PAL);
|
||||
const bool raw_path_valid = raw_path == jp_path || raw_path == us_path || raw_path == eu_path;
|
||||
const std::string dv_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::DEV);
|
||||
const bool raw_path_valid =
|
||||
raw_path == jp_path || raw_path == us_path || raw_path == eu_path || raw_path == dv_path;
|
||||
|
||||
if (!raw_path_valid)
|
||||
{
|
||||
// TODO: We could try to autodetect the card region here and offer automatic renaming.
|
||||
ModalMessageBox::critical(this, tr("Error"),
|
||||
tr("The filename %1 does not conform to Dolphin's region code format "
|
||||
"for memory cards. Please rename this file to either %2, %3, or "
|
||||
"%4, matching the region of the save files that are on it.")
|
||||
.arg(QString::fromStdString(PathToFileName(raw_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(us_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(eu_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(jp_path))));
|
||||
ModalMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The filename %1 does not conform to Dolphin's region code format "
|
||||
"for memory cards. Please rename this file to either %2, %3, %4, or "
|
||||
"%5, matching the region of the save files that are on it.")
|
||||
.arg(QString::fromStdString(PathToFileName(raw_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(us_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(eu_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(jp_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(dv_path))));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -556,8 +571,9 @@ bool GameCubePane::SetGCIFolder(ExpansionInterface::Slot slot, const QString& pa
|
||||
const std::string default_jp_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::NTSC_J);
|
||||
const std::string default_us_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::NTSC_U);
|
||||
const std::string default_eu_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::PAL);
|
||||
const bool is_default_path =
|
||||
raw_path == default_jp_path || raw_path == default_us_path || raw_path == default_eu_path;
|
||||
const std::string default_dv_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::DEV);
|
||||
const bool is_default_path = raw_path == default_jp_path || raw_path == default_us_path ||
|
||||
raw_path == default_eu_path || raw_path == default_dv_path;
|
||||
|
||||
bool path_changed;
|
||||
if (is_default_path)
|
||||
@@ -574,7 +590,9 @@ bool GameCubePane::SetGCIFolder(ExpansionInterface::Slot slot, const QString& pa
|
||||
const std::string jp_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::NTSC_J);
|
||||
const std::string us_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::NTSC_U);
|
||||
const std::string eu_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::PAL);
|
||||
const bool raw_path_valid = raw_path == jp_path || raw_path == us_path || raw_path == eu_path;
|
||||
const std::string dv_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::DEV);
|
||||
const bool raw_path_valid =
|
||||
raw_path == jp_path || raw_path == us_path || raw_path == eu_path || raw_path == dv_path;
|
||||
|
||||
if (!raw_path_valid)
|
||||
{
|
||||
@@ -582,12 +600,13 @@ bool GameCubePane::SetGCIFolder(ExpansionInterface::Slot slot, const QString& pa
|
||||
ModalMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The folder %1 does not conform to Dolphin's region code format "
|
||||
"for GCI folders. Please rename this folder to either %2, %3, or "
|
||||
"%4, matching the region of the save files that are in it.")
|
||||
"for GCI folders. Please rename this folder to either %2, %3, %4, or "
|
||||
"%5, matching the region of the save files that are in it.")
|
||||
.arg(QString::fromStdString(PathToFileName(raw_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(us_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(eu_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(jp_path))));
|
||||
.arg(QString::fromStdString(PathToFileName(jp_path)))
|
||||
.arg(QString::fromStdString(PathToFileName(dv_path))));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "Common/EnumMap.h"
|
||||
#include "Core/HW/EXI/EXI.h"
|
||||
#include "DolphinQt/MainWindow.h"
|
||||
|
||||
class ConfigBool;
|
||||
class ConfigChoice;
|
||||
@@ -28,10 +29,13 @@ class GameCubePane : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GameCubePane();
|
||||
explicit GameCubePane(MainWindow* main_window);
|
||||
|
||||
static std::string GetOpenGBARom(std::string_view title);
|
||||
|
||||
signals:
|
||||
void ShowTriforceWindow();
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "DolphinQt/Settings/TriforcePane.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHeaderView>
|
||||
#include <QPushButton>
|
||||
#include <QTableWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/HW/DVD/AMMediaboard.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
|
||||
#include "DolphinQt/QtUtils/QtUtils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr int COLUMN_EMULATED = 0;
|
||||
constexpr int COLUMN_REAL = 1;
|
||||
constexpr int COLUMN_DESCRIPTION = 2;
|
||||
|
||||
class IPRedirectionsDialog final : public QDialog
|
||||
{
|
||||
public:
|
||||
explicit IPRedirectionsDialog(QWidget* parent);
|
||||
|
||||
private:
|
||||
void LoadConfig();
|
||||
void SaveConfig() const;
|
||||
|
||||
void LoadDefault();
|
||||
void OnClear();
|
||||
|
||||
QTableWidget* m_ip_redirections_table;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TriforcePane::TriforcePane()
|
||||
{
|
||||
auto* const main_layout = new QVBoxLayout{this};
|
||||
|
||||
auto* const controllers_group = new QGroupBox{tr("Controllers")};
|
||||
main_layout->addWidget(controllers_group);
|
||||
|
||||
auto* const controllers_layout = new QVBoxLayout{controllers_group};
|
||||
|
||||
auto* const configure_controllers_button = new NonDefaultQPushButton{tr("Configure")};
|
||||
controllers_layout->addWidget(configure_controllers_button);
|
||||
|
||||
connect(configure_controllers_button, &QPushButton::clicked, this, [this] {
|
||||
auto* const window = new MappingWindow(this, MappingWindow::Type::MAPPING_AM_BASEBOARD, 0);
|
||||
window->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
window->setWindowModality(Qt::WindowModality::WindowModal);
|
||||
window->show();
|
||||
});
|
||||
|
||||
auto* const ip_redirection_group = new QGroupBox{tr("IP Address Redirections")};
|
||||
main_layout->addWidget(ip_redirection_group);
|
||||
|
||||
auto* const ip_redirection_layout = new QVBoxLayout{ip_redirection_group};
|
||||
|
||||
auto* const configure_ip_redirections_button = new NonDefaultQPushButton{tr("Configure")};
|
||||
ip_redirection_layout->addWidget(configure_ip_redirections_button);
|
||||
|
||||
connect(configure_ip_redirections_button, &QPushButton::clicked, this, [this] {
|
||||
auto* const ip_redirections = new IPRedirectionsDialog{this};
|
||||
ip_redirections->setAttribute(Qt::WA_DeleteOnClose);
|
||||
ip_redirections->open();
|
||||
});
|
||||
|
||||
main_layout->addStretch(1);
|
||||
}
|
||||
|
||||
IPRedirectionsDialog::IPRedirectionsDialog(QWidget* parent) : QDialog{parent}
|
||||
{
|
||||
setWindowTitle(tr("IP Address Redirections"));
|
||||
|
||||
auto* const main_layout = new QVBoxLayout{this};
|
||||
|
||||
auto* const load_default_button = new QPushButton(tr("Default"));
|
||||
connect(load_default_button, &QPushButton::clicked, this, &IPRedirectionsDialog::LoadDefault);
|
||||
|
||||
auto* const clear_button = new QPushButton(tr("Clear"));
|
||||
connect(clear_button, &QPushButton::clicked, this, &IPRedirectionsDialog::OnClear);
|
||||
|
||||
m_ip_redirections_table = new QTableWidget;
|
||||
main_layout->addWidget(m_ip_redirections_table);
|
||||
|
||||
m_ip_redirections_table->setColumnCount(3);
|
||||
m_ip_redirections_table->setHorizontalHeaderLabels(
|
||||
{tr("Emulated"), tr("Real"), tr("Description")});
|
||||
m_ip_redirections_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
m_ip_redirections_table->setSizeAdjustPolicy(QTableWidget::AdjustToContents);
|
||||
|
||||
m_ip_redirections_table->setEditTriggers(QAbstractItemView::DoubleClicked |
|
||||
QAbstractItemView::EditKeyPressed);
|
||||
|
||||
auto* const button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
main_layout->addWidget(button_box);
|
||||
|
||||
button_box->addButton(load_default_button, QDialogButtonBox::ActionRole);
|
||||
button_box->addButton(clear_button, QDialogButtonBox::ActionRole);
|
||||
|
||||
connect(button_box, &QDialogButtonBox::accepted, this, &IPRedirectionsDialog::accept);
|
||||
connect(button_box, &QDialogButtonBox::rejected, this, &IPRedirectionsDialog::reject);
|
||||
|
||||
connect(this, &IPRedirectionsDialog::accepted, this, &IPRedirectionsDialog::SaveConfig);
|
||||
|
||||
connect(m_ip_redirections_table, &QTableWidget::itemChanged, this,
|
||||
[this](QTableWidgetItem* item) {
|
||||
const int row_count = m_ip_redirections_table->rowCount();
|
||||
const bool is_last_row = item->row() == row_count - 1;
|
||||
if (is_last_row)
|
||||
{
|
||||
if (!item->text().isEmpty())
|
||||
m_ip_redirections_table->insertRow(row_count);
|
||||
}
|
||||
else if (item->column() != COLUMN_DESCRIPTION && item->text().isEmpty())
|
||||
{
|
||||
// Just remove inner rows that have text erased.
|
||||
// This UX could be less weird, but it's good enough for now.
|
||||
m_ip_redirections_table->removeRow(item->row());
|
||||
}
|
||||
});
|
||||
|
||||
LoadConfig();
|
||||
|
||||
QtUtils::AdjustSizeWithinScreen(this);
|
||||
}
|
||||
|
||||
void IPRedirectionsDialog::LoadConfig()
|
||||
{
|
||||
m_ip_redirections_table->setRowCount(0);
|
||||
|
||||
// Add the final empty row.
|
||||
m_ip_redirections_table->insertRow(0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
const auto ip_redirections_str = Config::Get(Config::MAIN_TRIFORCE_IP_REDIRECTIONS);
|
||||
for (auto&& ip_pair : ip_redirections_str | std::views::split(','))
|
||||
{
|
||||
const auto ip_pair_str = std::string_view{ip_pair};
|
||||
const auto parsed = AMMediaboard::ParseIPRedirection(ip_pair_str);
|
||||
if (!parsed.has_value())
|
||||
{
|
||||
ERROR_LOG_FMT(COMMON, "Bad IP redirection string: {}", ip_pair_str);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_ip_redirections_table->insertRow(row);
|
||||
|
||||
m_ip_redirections_table->setItem(row, COLUMN_EMULATED,
|
||||
new QTableWidgetItem(QString::fromUtf8(parsed->original)));
|
||||
m_ip_redirections_table->setItem(row, COLUMN_REAL,
|
||||
new QTableWidgetItem(QString::fromUtf8(parsed->replacement)));
|
||||
m_ip_redirections_table->setItem(row, COLUMN_DESCRIPTION,
|
||||
new QTableWidgetItem(QString::fromUtf8(parsed->description)));
|
||||
|
||||
++row;
|
||||
}
|
||||
}
|
||||
|
||||
void IPRedirectionsDialog::SaveConfig() const
|
||||
{
|
||||
// Ignore our empty row.
|
||||
const int row_count = m_ip_redirections_table->rowCount() - 1;
|
||||
|
||||
std::vector<std::string> replacements(row_count);
|
||||
for (int row = 0; row < row_count; ++row)
|
||||
{
|
||||
auto* const original_item = m_ip_redirections_table->item(row, COLUMN_EMULATED);
|
||||
auto* const replacement_item = m_ip_redirections_table->item(row, COLUMN_REAL);
|
||||
|
||||
// Skip incomplete rows.
|
||||
if (original_item == nullptr || replacement_item == nullptr)
|
||||
continue;
|
||||
|
||||
replacements[row] = fmt::format("{}={}", StripWhitespace(original_item->text().toStdString()),
|
||||
replacement_item->text().toStdString());
|
||||
|
||||
auto* const description_item = m_ip_redirections_table->item(row, COLUMN_DESCRIPTION);
|
||||
if (description_item == nullptr)
|
||||
continue;
|
||||
|
||||
const auto description = description_item->text().toStdString();
|
||||
|
||||
if (!description.empty())
|
||||
(replacements[row] += ' ') += description;
|
||||
}
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_REDIRECTIONS,
|
||||
fmt::format("{}", fmt::join(replacements, ",")));
|
||||
}
|
||||
|
||||
void IPRedirectionsDialog::LoadDefault()
|
||||
{
|
||||
// This alters the config before "OK" is pressed. Bad UX..
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_REDIRECTIONS, Config::DefaultState{});
|
||||
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
void IPRedirectionsDialog::OnClear()
|
||||
{
|
||||
m_ip_redirections_table->setRowCount(0);
|
||||
// Add the final empty row.
|
||||
m_ip_redirections_table->insertRow(0);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class TriforcePane final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TriforcePane();
|
||||
};
|
||||
@@ -28,6 +28,13 @@ enum PadButton
|
||||
PAD_BUTTON_START = 0x1000,
|
||||
};
|
||||
|
||||
enum SwitchButton
|
||||
{
|
||||
SWITCH_TEST = 0x0001,
|
||||
SWITCH_SERVICE = 0x0002,
|
||||
SWITCH_COIN = 0x0004,
|
||||
};
|
||||
|
||||
struct GCPadStatus
|
||||
{
|
||||
u16 button = 0; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
|
||||
@@ -39,6 +46,8 @@ struct GCPadStatus
|
||||
u8 triggerRight = 0; // 0 <= triggerRight <= 255
|
||||
u8 analogA = 0; // 0 <= analogA <= 255
|
||||
u8 analogB = 0; // 0 <= analogB <= 255
|
||||
// Triforce
|
||||
u8 switches = 0;
|
||||
bool isConnected = true;
|
||||
|
||||
static const u8 MAIN_STICK_CENTER_X = 0x80;
|
||||
|
||||
@@ -128,7 +128,6 @@ GameFile::GameFile(std::string path) : m_file_path(std::move(path))
|
||||
m_internal_name = volume->GetInternalName();
|
||||
m_game_id = volume->GetGameID();
|
||||
m_gametdb_id = volume->GetGameTDBID();
|
||||
m_triforce_id = volume->GetTriforceID();
|
||||
m_title_id = volume->GetTitleID().value_or(0);
|
||||
m_maker_id = volume->GetMakerID();
|
||||
m_revision = volume->GetRevision().value_or(0);
|
||||
@@ -309,7 +308,6 @@ void GameFile::DoState(PointerWrap& p)
|
||||
p.Do(m_internal_name);
|
||||
p.Do(m_game_id);
|
||||
p.Do(m_gametdb_id);
|
||||
p.Do(m_triforce_id);
|
||||
p.Do(m_title_id);
|
||||
p.Do(m_maker_id);
|
||||
|
||||
@@ -498,8 +496,7 @@ const std::string& GameFile::GetName(const Core::TitleDatabase& title_database)
|
||||
if (IsModDescriptor())
|
||||
return GetName(Variant::LongAndPossiblyCustom);
|
||||
|
||||
const std::string& database_name =
|
||||
title_database.GetTitleName(m_gametdb_id, m_triforce_id, GetConfigLanguage());
|
||||
const std::string& database_name = title_database.GetTitleName(m_gametdb_id, GetConfigLanguage());
|
||||
return database_name.empty() ? GetName(Variant::LongAndPossiblyCustom) : database_name;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ public:
|
||||
const std::string& GetInternalName() const { return m_internal_name; }
|
||||
const std::string& GetGameID() const { return m_game_id; }
|
||||
const std::string& GetGameTDBID() const { return m_gametdb_id; }
|
||||
std::string GetTriforceID() const { return m_triforce_id; }
|
||||
u64 GetTitleID() const { return m_title_id; }
|
||||
const std::string& GetMakerID() const { return m_maker_id; }
|
||||
u16 GetRevision() const { return m_revision; }
|
||||
@@ -160,7 +159,6 @@ private:
|
||||
std::string m_internal_name;
|
||||
std::string m_game_id;
|
||||
std::string m_gametdb_id;
|
||||
std::string m_triforce_id;
|
||||
u64 m_title_id{};
|
||||
std::string m_maker_id;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
static constexpr u32 CACHE_REVISION = 26; // Last changed in PR 10084
|
||||
static constexpr u32 CACHE_REVISION = 27; // Last changed in PR 13844
|
||||
|
||||
std::vector<std::string> FindAllGamePaths(std::span<const std::string_view> directories_to_scan,
|
||||
bool recursive_scan)
|
||||
|
||||
@@ -280,6 +280,7 @@ void CreateDirectories()
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + USA_DIR DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + EUR_DIR DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + JAP_DIR DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_TRIUSER_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_HIRESTEXTURES_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_GRAPHICSMOD_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_MAPS_IDX));
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Network.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
|
||||
@@ -259,6 +260,155 @@ TEST(StringUtil, CaseInsensitiveContains_OverlappingMatches)
|
||||
EXPECT_TRUE(Common::CaseInsensitiveContains("ababababa", "bABa"));
|
||||
}
|
||||
|
||||
TEST(StringUtil, SplitStringIntoArray)
|
||||
{
|
||||
constexpr auto subject0 = "";
|
||||
EXPECT_FALSE(SplitStringIntoArray<2>(subject0, '.').has_value());
|
||||
|
||||
constexpr auto subject1 = "hello";
|
||||
EXPECT_FALSE(SplitStringIntoArray<2>(subject1, '.').has_value());
|
||||
|
||||
constexpr auto subject2 = "hello.world";
|
||||
const auto split2 = SplitStringIntoArray<2>(subject2, '.');
|
||||
EXPECT_TRUE(split2.has_value() && split2->at(1) == "world");
|
||||
EXPECT_FALSE(SplitStringIntoArray<3>(subject2, '.').has_value());
|
||||
|
||||
constexpr auto subject3 = "hello.universe.";
|
||||
EXPECT_FALSE(SplitStringIntoArray<2>(subject3, '.').has_value());
|
||||
const auto split3 = SplitStringIntoArray<3>(subject3, '.');
|
||||
EXPECT_TRUE(split3.has_value() && split3->at(2) == "");
|
||||
EXPECT_FALSE(SplitStringIntoArray<4>(subject3, '.').has_value());
|
||||
|
||||
constexpr auto subject4 = "=";
|
||||
EXPECT_TRUE(SplitStringIntoArray<2>(subject4, '=').has_value());
|
||||
}
|
||||
|
||||
TEST(StringUtil, StringToIPv4PortRange)
|
||||
{
|
||||
constexpr auto parse = Common::StringToIPv4PortRange;
|
||||
|
||||
constexpr Common::IPAddress ip_network = {192, 168, 0, 0};
|
||||
constexpr Common::IPAddress test_ip = {192, 168, 0, 13};
|
||||
constexpr Common::IPAddress last_ip = {192, 168, 0, 255};
|
||||
|
||||
EXPECT_FALSE(parse("").has_value());
|
||||
EXPECT_FALSE(parse("1.2.3").has_value());
|
||||
EXPECT_FALSE(parse("1.2.3.4-").has_value());
|
||||
EXPECT_FALSE(parse("1.2.3.4.5").has_value());
|
||||
EXPECT_FALSE(parse("1.2.3.256").has_value());
|
||||
EXPECT_FALSE(parse("1.2.3.4/33").has_value());
|
||||
EXPECT_FALSE(parse("1.2.3.4:65536").has_value());
|
||||
EXPECT_FALSE(parse("1.2.3.4-1.2.3.4.5").has_value());
|
||||
EXPECT_FALSE(parse("1.1.1.1:81-80").has_value());
|
||||
EXPECT_FALSE(parse("1.1.1.2-1.1.1.1").has_value());
|
||||
|
||||
EXPECT_TRUE(parse("1.2.3.4").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4:80").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4:80-81").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4/16").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4/16:80").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4/16:80-81").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4-10").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4-10:80").has_value());
|
||||
EXPECT_TRUE(parse("1.2.3.4-10:80-81").has_value());
|
||||
|
||||
{
|
||||
const auto simple_result = parse("192.168.0.13").value();
|
||||
EXPECT_EQ(simple_result.first.ip_address, test_ip);
|
||||
EXPECT_EQ(simple_result.last.ip_address, test_ip);
|
||||
EXPECT_EQ(simple_result.first.GetPortValue(), 0);
|
||||
EXPECT_EQ(simple_result.last.GetPortValue(), u16(-1));
|
||||
}
|
||||
|
||||
{
|
||||
const auto port_result = parse("192.168.0.13:77").value();
|
||||
EXPECT_EQ(port_result.first.ip_address, test_ip);
|
||||
EXPECT_EQ(port_result.last.ip_address, test_ip);
|
||||
EXPECT_EQ(port_result.first.GetPortValue(), 77);
|
||||
EXPECT_EQ(port_result.last.GetPortValue(), 77);
|
||||
}
|
||||
|
||||
{
|
||||
const auto ip_range_result = parse("192.168.0.13-192.168.0.255:123").value();
|
||||
EXPECT_EQ(ip_range_result.first.ip_address, test_ip);
|
||||
EXPECT_EQ(ip_range_result.last.ip_address, last_ip);
|
||||
EXPECT_EQ(ip_range_result.first.GetPortValue(), 123);
|
||||
EXPECT_EQ(ip_range_result.last.GetPortValue(), 123);
|
||||
}
|
||||
|
||||
{
|
||||
const auto port_range_result = parse("192.168.29.151:5000-5010").value();
|
||||
EXPECT_EQ(port_range_result.first.GetPortValue(), 5000);
|
||||
EXPECT_EQ(port_range_result.last.GetPortValue(), 5010);
|
||||
EXPECT_TRUE(port_range_result.IsMatch(parse("192.168.29.151:5008")->first));
|
||||
EXPECT_FALSE(port_range_result.IsMatch(parse("127.0.0.1:5000")->first));
|
||||
}
|
||||
|
||||
{
|
||||
const auto ip_range_result = parse("192.168.0.13-255:123-999").value();
|
||||
EXPECT_EQ(ip_range_result.first.ip_address, test_ip);
|
||||
EXPECT_EQ(ip_range_result.last.ip_address, last_ip);
|
||||
EXPECT_EQ(ip_range_result.first.GetPortValue(), 123);
|
||||
EXPECT_EQ(ip_range_result.last.GetPortValue(), 999);
|
||||
}
|
||||
|
||||
{
|
||||
const auto ip_network_result = parse("192.168.0.13/24:123-999").value();
|
||||
EXPECT_EQ(ip_network_result.first.ip_address, ip_network);
|
||||
EXPECT_EQ(ip_network_result.last.ip_address, last_ip);
|
||||
EXPECT_EQ(ip_network_result.first.GetPortValue(), 123);
|
||||
EXPECT_EQ(ip_network_result.last.GetPortValue(), 999);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(parse("192.168.0.13/24")->IsMatch(parse("192.168.0.99")->first));
|
||||
EXPECT_FALSE(parse("192.168.0.13/24")->IsMatch(parse("192.168.1.99")->first));
|
||||
|
||||
EXPECT_FALSE(parse("192.168.0.13/32")->IsMatch(parse("192.168.0.99")->first));
|
||||
EXPECT_TRUE(parse("192.168.0.13/0")->IsMatch(parse("10.0.0.1")->first));
|
||||
|
||||
EXPECT_TRUE(parse("192.168.0.13")->IsMatch(parse("192.168.0.13:81")->first));
|
||||
EXPECT_FALSE(parse("192.168.0.13:80")->IsMatch(parse("192.168.0.13:81")->first));
|
||||
EXPECT_TRUE(parse("192.168.0.13:80")->IsMatch(parse("192.168.0.13:80")->first));
|
||||
EXPECT_FALSE(parse("192.168.0.13:80-81")->IsMatch(parse("192.168.0.14:81")->first));
|
||||
EXPECT_TRUE(parse("192.168.0.13-14:80-81")->IsMatch(parse("192.168.0.14:81")->first));
|
||||
}
|
||||
|
||||
TEST(StringUtil, IPv4PortRangeToString)
|
||||
{
|
||||
Common::IPv4PortRange subject{
|
||||
.first = {{10, 3, 0, 127}, 0},
|
||||
.last = {{10, 3, 0, 127}, 0},
|
||||
};
|
||||
|
||||
EXPECT_EQ(subject.ToString(), "10.3.0.127");
|
||||
|
||||
subject.last.port = Common::swap16(80); // First port is still zero.
|
||||
EXPECT_EQ(subject.ToString(), "10.3.0.127");
|
||||
|
||||
subject.first.port = Common::swap16(80);
|
||||
EXPECT_EQ(subject.ToString(), "10.3.0.127:80");
|
||||
|
||||
subject.last.port = Common::swap16(88);
|
||||
EXPECT_EQ(subject.ToString(), "10.3.0.127:80-88");
|
||||
|
||||
subject.last.ip_address = {10, 3, 0, 128};
|
||||
EXPECT_EQ(subject.ToString(), "10.3.0.127-128:80-88");
|
||||
|
||||
subject.last.ip_address = {10, 3, 7, 35};
|
||||
EXPECT_EQ(subject.ToString(), "10.3.0.127-10.3.7.35:80-88");
|
||||
|
||||
subject.first.ip_address = {192, 168, 0, 0};
|
||||
subject.last.ip_address = {192, 168, 0, 255};
|
||||
EXPECT_EQ(subject.ToString(), "192.168.0.0/24:80-88");
|
||||
|
||||
subject.last.ip_address = {192, 168, 3, 255};
|
||||
EXPECT_EQ(subject.ToString(), "192.168.0.0/22:80-88");
|
||||
|
||||
subject.first.ip_address = {};
|
||||
subject.last.ip_address = {255, 255, 255, 255};
|
||||
EXPECT_EQ(subject.ToString(), "0.0.0.0/0:80-88");
|
||||
}
|
||||
|
||||
TEST(StringUtil, CharacterEncodingConversion)
|
||||
{
|
||||
// wstring
|
||||
|
||||
Reference in new issue
Block a user