This commit is contained in:
crediar 2025-08-11 17:55:14 -05:00 committed by GitHub
commit b4bcaf316c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 5515 additions and 171 deletions

View file

@ -0,0 +1,4 @@
# SBEY - Virtua Striker 2002 (Export, Japan, Type 3)
[Core]
FPRF = True

View file

@ -0,0 +1,5 @@
# SBGG - F-ZERO AX
[Core]
FPRF = True
CPUThread = True

View file

@ -0,0 +1,5 @@
# SBHA - F-ZERO AX (Monster)
[Core]
FPRF = True
CPUThread = True

View file

@ -0,0 +1,4 @@
# SBHN - VIRTUA STRIKER 4 VER.A
[Core]
FPRF = True

View file

@ -0,0 +1,4 @@
# SBHZ - VIRTUA STRIKER 4 (Asia)
[Core]
FPRF = True

View file

@ -0,0 +1,4 @@
# SBJA - VIRTUA STRIKER 4 (Export)
[Core]
FPRF = True

View file

@ -0,0 +1,4 @@
# SBLK - VIRTUA STRIKER 4 Ver.2006 (Japan)
[Core]
FPRF = True

View file

@ -0,0 +1,4 @@
# SBLL - VIRTUA STRIKER 4 Ver.2006 (Export)
[Core]
FPRF = True

View file

@ -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"
@ -156,6 +158,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"

View file

@ -844,6 +844,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;

View file

@ -28,7 +28,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

View file

@ -28,6 +28,7 @@ enum class LogType : int
DSP_MAIL,
DSPINTERFACE,
DVDINTERFACE,
DVDINTERFACE_AMMB,
DYNA_REC,
EXPANSIONINTERFACE,
FILEMON,
@ -59,6 +60,9 @@ enum class LogType : int
PROCESSORINTERFACE,
POWERPC,
SERIALINTERFACE,
SERIALINTERFACE_AMBB,
SERIALINTERFACE_CARD,
SERIALINTERFACE_JVSIO,
SP1,
SYMBOLS,
VIDEO,

View file

@ -113,6 +113,7 @@ 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::DVDINTERFACE_AMMB] = {"DVD_AMMB", "AMMB Interface"};
m_log[LogType::DYNA_REC] = {"JIT", "JIT Dynamic Recompiler"};
m_log[LogType::EXPANSIONINTERFACE] = {"EXI", "Expansion Interface"};
m_log[LogType::FILEMON] = {"FileMon", "File Monitor"};
@ -144,6 +145,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"};

View file

@ -37,6 +37,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"
@ -374,7 +375,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;
@ -408,6 +410,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;
@ -458,6 +461,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;
PowerPC::MSRUpdated(ppc_state);
@ -491,6 +502,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)));

View file

@ -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"
@ -227,12 +228,20 @@ 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();
u32 dsize = volume.GetDataSize();
memory.Memset(0, 0, 12);
// Load game into RAM, like on the actual Triforce
u8* dimm_disc = AMMediaboard::InitDIMM(dsize);
volume.Read(0, dsize, dimm_disc, DiscIO::PARTITION_NONE);
// Triforce disc register obfucation
AMMediaboard::InitKeys(memory.Read_U32(0), memory.Read_U32(4), memory.Read_U32(8));
AMMediaboard::FirmwareMap(false);
}
return true;

View file

@ -183,6 +183,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
@ -276,6 +278,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

View file

@ -20,6 +20,7 @@
#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"
@ -27,6 +28,7 @@
#include "Core/HW/Memmap.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "Core/USBUtils.h"
#include "DiscIO/Enums.h"
#include "VideoCommon/VideoBackendBase.h"
@ -623,9 +625,17 @@ DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
const char* GetDirectoryForRegion(DiscIO::Region region, RegionDirectoryStyle style)
{
auto& system = Core::System::GetInstance();
if (region == DiscIO::Region::Unknown)
region = ToGameCubeRegion(Config::Get(Config::MAIN_FALLBACK_REGION));
// Triforce IPL
if (system.IsTriforce())
{
return DEV_DIR;
}
switch (region)
{
case DiscIO::Region::NTSC_J:

View file

@ -126,12 +126,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);
@ -147,7 +141,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,
@ -156,14 +150,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());
}
}
@ -181,28 +175,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;
@ -235,7 +226,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();

View file

@ -57,7 +57,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.
@ -67,7 +66,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();
@ -122,15 +120,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;

View file

@ -52,6 +52,7 @@
#include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DSP.h"
#include "Core/HW/DVD/AMMediaboard.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/GBAPad.h"
#include "Core/HW/GCKeyboard.h"
@ -299,6 +300,13 @@ void Stop(Core::System& system) // - Hammertime!
system.GetFifo().ExitGpuLoop();
}
const ExpansionInterface::EXIDeviceType type = Config::Get(Config::MAIN_SERIAL_PORT_1);
if ((type == ExpansionInterface::EXIDeviceType::Baseboard))
{
AMMediaboard::Shutdown();
}
}
void DeclareAsCPUThread()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,234 @@
// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
class PointerWrap;
namespace Core
{
class System;
}
namespace File
{
class IOFile;
}
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,
};
#define SocketCheck(x) (x <= 0x3F ? x : 0)
namespace AMMediaboard
{
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
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,
DIMMCommandExecute2 = 0x88000000,
NetworkCommandAddress = 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,
};
void Init(void);
void FirmwareMap(bool on);
u8* InitDIMM(u32 size);
void InitKeys(u32 KeyA, u32 KeyB, u32 KeyC);
u32 ExecuteCommand(std::array<u32, 3>& DICMDBUF, u32* DIIMMBUF, u32 Address, u32 Length);
u32 GetGameType(void);
u32 GetMediaType(void);
bool GetTestMenu(void);
void Shutdown(void);
}; // namespace AMMediaboard

View file

@ -27,6 +27,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"
@ -282,6 +283,16 @@ 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;
m_DICFG.Hex |= 8; /* The Triforce IPL checks this bit
to set the physical memory to either 50MB(unset) or 24MB(set) */
}
}
// Resets state on the MN102 chip in the drive itself, but not the DI registers exposed on the
@ -311,7 +322,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
@ -697,12 +708,16 @@ bool DVDInterface::CheckReadPreconditions()
SetDriveError(DriveError::MotorStopped);
return false;
}
if (m_drive_state == DriveState::DiscIdNotRead)
{
ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read.");
SetDriveError(DriveError::NoDiscID);
return false;
}
// SegaBoot doesn't read the Disc ID
if (!m_system.IsTriforce())
if (m_drive_state == DriveState::DiscIdNotRead)
{
ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read.");
SetDriveError(DriveError::NoDiscID);
return false;
}
return true;
}
@ -753,12 +768,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;
}

View file

@ -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));

View file

@ -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);

View file

@ -2,20 +2,132 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/EXI/EXI_DeviceBaseboard.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include <fmt/format.h>
#include "Common/Buffer.h"
#include "Common/CommonTypes.h"
#include "Common/Config/Config.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/IniFile.h"
#include "Common/Logging/Log.h"
#include "Core/Boot/Boot.h"
#include "Core/BootManager.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h"
#include "Core/ConfigLoaders/BaseConfigLoader.h"
#include "Core/ConfigLoaders/NetPlayConfigLoader.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/DVD/AMMediaboard.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/HW/Sram.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/Movie.h"
#include "Core/NetPlayProto.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "Core/WiiRoot.h"
#include "DiscIO/Enums.h"
bool g_interrupt_set = false;
u32 g_irq_timer = 0;
u32 g_irq_status = 0;
static u16 CheckSum(u8* Data, u32 Length)
{
u16 check = 0;
for (u32 i = 0; i < Length; i++)
{
check += Data[i];
}
return check;
}
namespace ExpansionInterface
{
CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system)
void GenerateInterrupt(int flag)
{
auto& system = Core::System::GetInstance();
g_interrupt_set = true;
g_irq_timer = 0;
g_irq_status = flag;
system.GetExpansionInterface().UpdateInterrupts();
}
CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system), m_position(0)
{
std::string backup_Filename(File::GetUserPath(D_TRIUSER_IDX) + "tribackup_" +
SConfig::GetInstance().GetGameID().c_str() + ".bin");
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 = File::GetUserPath(D_TRIUSER_IDX) + "tribackup_tmp_" + std::to_string(rand()) +
SConfig::GetInstance().GetGameID().c_str() + ".bin";
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() == VirtuaStriker4 ||
AMMediaboard::GetGameType() == GekitouProYakyuu)
{
if (m_backup.GetSize() != 0)
{
Common::UniqueBuffer<u8> data(m_backup.GetSize());
m_backup.ReadBytes(data.data(), data.size());
// Set FIRM version
reinterpret_cast<u16&>(data[0x12]) = 0x1703;
reinterpret_cast<u16&>(data[0x212]) = 0x1703;
// Update checksum
reinterpret_cast<u16&>(data[0x0A]) = Common::swap16(CheckSum(&data[0xC], 0x1F4));
reinterpret_cast<u16&>(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()
{
m_backup.Close();
}
void CEXIBaseboard::SetCS(int cs)
{
DEBUG_LOG_FMT(SP1, "AM-BB: ChipSelect={}", cs);
if (cs)
m_position = 0;
}
@ -25,33 +137,199 @@ bool CEXIBaseboard::IsPresent() const
return true;
}
void CEXIBaseboard::TransferByte(u8& byte)
bool CEXIBaseboard::IsInterruptSet()
{
if (m_position == 0)
if (g_interrupt_set)
{
m_command = byte;
DEBUG_LOG_FMT(SP1, "AM-BB: IRQ");
if (++g_irq_timer > 12)
g_interrupt_set = false;
return 1;
}
else
{
switch (m_command)
return 0;
}
}
void CEXIBaseboard::DMAWrite(u32 addr, u32 size)
{
auto& system = Core::System::GetInstance();
auto& memory = system.GetMemory();
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Write: {:08x} {:x}", addr, size);
m_backup.Seek(m_backoffset, File::SeekOrigin::Begin);
m_backup.WriteBytes(memory.GetSpanForAddress(addr).data(), size);
m_backup.Flush();
}
void CEXIBaseboard::DMARead(u32 addr, u32 size)
{
auto& system = Core::System::GetInstance();
auto& memory = system.GetMemory();
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Read: {:08x} {:x}", addr, size);
m_backup.Seek(m_backoffset, File::SeekOrigin::Begin);
m_backup.Flush();
m_backup.ReadBytes(memory.GetSpanForAddress(addr).data(), size);
}
void CEXIBaseboard::TransferByte(u8& _byte)
{
DEBUG_LOG_FMT(SP1, "AM-BB: > {:02x}", _byte);
if (m_position < 4)
{
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)
{
case 0x00:
{
static constexpr std::array<u8, 4> ID = {0x06, 0x04, 0x10, 0x00};
byte = ID[(m_position - 2) & 3];
break;
if (checksum & bit)
checksum ^= check;
check >>= 1;
bit >>= 1;
}
default:
ERROR_LOG_FMT(EXPANSIONINTERFACE, "EXI BASEBOARD: Unhandled command {:#x} {:#x}", m_command,
m_position);
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_backoffset = (m_command[1] << 8) | m_command[2];
DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupOffsetSet:{:04x}", m_backoffset);
m_backup.Seek(m_backoffset, File::SeekOrigin::Begin);
_byte = 0x01;
break;
case BackupWrite:
DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupWrite:{:04x}-{:02x}", m_backoffset, 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_backoffset);
_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, g_irq_status);
_byte = 0x04;
break;
case WriteISR:
NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteISR :{:02x} {:02x}", m_command[1], m_command[2]);
g_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))
{
g_interrupt_set = true;
g_irq_timer = 0;
g_irq_status = 0x02;
}
if ((m_command[1] == 2) && (m_command[2] == 1))
{
g_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.Flush();
m_backup.ReadBytes(&_byte, 1);
break;
case DMAOffsetLengthSet:
_byte = 0x01;
break;
// 2 byte out
case ReadISR:
if (m_position == 6)
{
_byte = g_irq_status;
g_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
{
_byte = 0xFF;
}
}
DEBUG_LOG_FMT(SP1, "AM-BB < {:02x}", _byte);
m_position++;
}
void CEXIBaseboard::DoState(PointerWrap& p)
{
p.Do(m_position);
p.Do(g_interrupt_set);
p.Do(m_command);
}
} // namespace ExpansionInterface

View file

@ -3,25 +3,65 @@
#pragma once
#include <SFML/Network.hpp>
#include <deque>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include "Common/CommonTypes.h"
#include "Common/Flag.h"
#include "Common/IOFile.h"
#include "Core/HW/EXI/EXI_Device.h"
class PointerWrap;
namespace Core
{
class System;
}
namespace ExpansionInterface
{
void GenerateInterrupt(int flag);
class CEXIBaseboard : public IEXIDevice
{
public:
CEXIBaseboard(Core::System& system);
void SetCS(int cs) override;
explicit CEXIBaseboard(Core::System& system);
virtual ~CEXIBaseboard();
void SetCS(int _iCS) 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,
void TransferByte(u8& byte) override;
DMAOffsetLengthSet = 0x05,
ReadISR = 0x82,
WriteISR = 0x83,
ReadIMR = 0x86,
WriteIMR = 0x87,
WriteLANCNT = 0xFF,
};
u32 m_position;
u32 m_backup_dma_offset;
u32 m_backup_dma_length;
u8 m_command[4];
u16 m_backoffset;
File::IOFile m_backup;
protected:
void TransferByte(u8& _uByte) override;
};
} // namespace ExpansionInterface

View file

@ -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[] = {PAD_SWITCH_TEST, PAD_SWITCH_SERVICE, PAD_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::DoNotTranslate, 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);
@ -202,6 +215,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

View file

@ -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 = "Test";
static constexpr const char* SERVICE_BUTTON = "Service";
static constexpr const char* COIN_BUTTON = "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;

View file

@ -115,7 +115,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);

View file

@ -56,6 +56,7 @@ public:
void RemoveEvent(int device_number);
void UpdateDevices();
u32 GetInLength(void) const { return m_com_csr.INLNGTH; }
void RemoveDevice(int device_number);
void AddDevice(SIDevices device, int device_number);

View file

@ -16,6 +16,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"
@ -139,6 +140,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;
@ -181,6 +183,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);

View file

@ -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

View file

@ -0,0 +1,304 @@
// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Core/HW/GCPad.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:
u32 m_ptr, m_last_start, m_csum;
u8 m_msg[0x80];
JVSIOMessage();
void Start(int node);
void AddData(const u8* dst, size_t len, int sync);
void AddData(const void* data, size_t len);
void AddData(const char* data);
void AddData(u32 n);
void End();
}; // end class JVSIOMessage
// triforce (GC-AM) baseboard
class CSIDevice_AMBaseboard : public ISIDevice
{
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 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,
};
union ICCommand
{
u8 data[81 + 4 + 4 + 4];
struct
{
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;
};
};
u16 m_coin[2];
u32 m_coin_pressed[2];
u8 m_ic_card_data[2048];
u16 m_ic_card_state;
/*
0 - OK
8000 - no card
800E - ???
other- bad card
*/
u16 m_ic_card_status;
u16 m_ic_card_session;
u8 m_ic_write_buffer[512];
u32 m_ic_write_offset;
u32 m_ic_write_size;
u8 m_card_memory[0xD0];
u8 m_card_read_packet[0xDB];
u8 m_card_buffer[0x100];
u32 m_card_memory_size;
u32 m_card_is_inserted;
u32 m_card_command;
u32 m_card_clean;
u32 m_card_write_length;
u32 m_card_wrote;
u32 m_card_read_length;
u32 m_card_read;
u32 m_card_bit;
u32 m_card_shutter;
u32 m_card_state_call_count;
u8 m_card_offset;
u32 m_wheelinit;
u32 m_motorinit;
u8 m_motorreply[64];
s16 m_motorforce_x;
// F-Zero AX (DX)
u32 m_fzdx_seatbelt;
u32 m_fzdx_motion_stop;
u32 m_fzdx_sensor_right;
u32 m_fzdx_sensor_left;
u8 m_rx_reply;
// F-Zero AX (CyCraft)
u32 m_fzcc_seatbelt;
u32 m_fzcc_sensor;
u32 m_fzcc_emergency;
u32 m_fzcc_service;
void ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length);
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;
};
enum EButtonCombo
{
COMBO_NONE = 0,
COMBO_ORIGIN,
COMBO_RESET
};
// 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;
public:
// constructor
CSIDevice_AMBaseboard(Core::System& system, SIDevices device, int device_number);
// run the SI Buffer
int RunBuffer(u8* buffer, int request_length) override;
// 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);
protected:
void SetOrigin(const GCPadStatus& pad_status);
};
} // namespace SerialInterface

View file

@ -3,8 +3,11 @@
#pragma once
#include <SFML/Network.hpp>
#include <array>
#include "Common/CommonTypes.h"
#include "Common/Flag.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/SI/SI_Device.h"
#include "InputCommon/GCPadStatus.h"

View file

@ -163,7 +163,19 @@ 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;
if (region == DiscIO::Region::NTSC_J)
{
m_dtv_status.ntsc_j = false;
}
/*
Triforce IPL requires the DTV NTSC-J flag to be set.
*/
if (m_system.IsTriforce())
{
m_dtv_status.ntsc_j = true;
}
m_fb_width.Hex = 0;
m_border_hblank.Hex = 0;

View file

@ -54,6 +54,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"
@ -1891,6 +1892,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);
@ -1898,8 +1901,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);
@ -1917,7 +1918,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
{
@ -2780,6 +2782,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);
@ -2852,3 +2864,12 @@ int SerialInterface::CSIDevice_GCController::NetPlay_InGamePadToLocalPad(int num
return numPAD;
}
int SerialInterface::CSIDevice_AMBaseboard::NetPlay_InGamePadToLocalPad(int numPAD)
{
std::lock_guard lk(NetPlay::crit_netplay_client);
if (NetPlay::netplay_client)
return NetPlay::netplay_client->InGamePadToLocalPad(numPAD);
return numPAD;
}

View file

@ -94,32 +94,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;
@ -152,12 +128,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);

View file

@ -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;

View file

@ -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,7 @@ 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)
};
// Languages 0 - 9 match Nintendo's Wii language numbering.
@ -66,7 +66,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);

View file

@ -96,7 +96,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;

View file

@ -20,6 +20,20 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const
{
char id[6];
// Triforce games have their Game ID stored in the boot.id file
const FileSystem* file_system = GetFileSystem(partition);
if (file_system)
{
std::unique_ptr<FileInfo> file_info = file_system->FindFileInfo("boot.id");
if (file_info && !file_info->IsDirectory())
{
if (Read(file_info->GetOffset() + 0x30, sizeof(id), reinterpret_cast<u8*>(id), partition))
{
return DecodeString(id);
}
}
}
if (!Read(0, sizeof(id), reinterpret_cast<u8*>(id), partition))
return std::string();
@ -67,6 +81,28 @@ 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
const FileSystem* file_system = GetFileSystem(partition);
if (file_system)
{
std::unique_ptr<FileInfo> file_info = file_system->FindFileInfo("boot.id");
if (file_info && !file_info->IsDirectory())
{
u8* bootid_buffer = new u8[file_info->GetTotalSize()];
if (Read(file_info->GetOffset(), file_info->GetTotalSize(), bootid_buffer, partition))
{
memcpy(name, bootid_buffer + 0x80, 0x20);
delete[] bootid_buffer;
return DecodeString(name);
}
// Fall back to normal title from header
delete[] bootid_buffer;
}
}
if (!Read(0x20, sizeof(name), reinterpret_cast<u8*>(&name), partition))
return std::string();

View file

@ -29,6 +29,8 @@
namespace DiscIO
{
Region g_triforce_region;
VolumeGC::VolumeGC(std::unique_ptr<BlobReader> reader)
: m_reader(std::move(reader)), m_is_triforce(false)
{
@ -40,6 +42,7 @@ VolumeGC::VolumeGC(std::unique_ptr<BlobReader> reader)
};
m_converted_banner = [this] { return LoadBannerFile(); };
g_triforce_region = Region::Unknown;
constexpr u32 BTID_MAGIC = 0x44495442;
auto tmp_fs = GetFileSystem(PARTITION_NONE);
@ -54,7 +57,22 @@ VolumeGC::VolumeGC(std::unique_ptr<BlobReader> reader)
if (file_size >= 4 && triforce_header.magic == BTID_MAGIC)
{
m_is_triforce = true;
m_triforce_id = triforce_header.id;
// Load region from the file
switch (triforce_header.region)
{
default:
case 0x02: // JAPAN
case 0x08: // ASIA
g_triforce_region = Region::NTSC_J;
break;
case 0x0E: // USA
g_triforce_region = Region::NTSC_U;
break;
case 0x0C: // EXPORT
g_triforce_region = Region::PAL;
break;
}
}
}
}
@ -93,16 +111,11 @@ 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 (g_triforce_region != Region::Unknown)
return g_triforce_region;
return RegionCodeToRegion(m_reader->ReadSwapped<u32>(0x458));
}
@ -187,6 +200,14 @@ 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));

View file

@ -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;
@ -82,6 +81,13 @@ private:
u32 magic; // "BTID"
u32 padding[11];
std::array<char, 4> id;
u32 padding_b;
u8 region;
u8 padding_c[0x27];
std::array<char, 32> maker;
std::array<char, 32> name;
u32 padding_d[0x10];
char credits_text[8][32];
};
struct ConvertedGCBanner
@ -113,7 +119,6 @@ private:
std::unique_ptr<BlobReader> m_reader;
bool m_is_triforce;
std::array<char, 4> m_triforce_id;
};
} // namespace DiscIO

View file

@ -70,20 +70,24 @@
<ItemGroup>
<Text Include="$(BuildInfoTemplate)" />
</ItemGroup>
<UsingTask TaskName="GetProductVersion"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ItemGroup>
<ClCompile Include="Core\HW\DVD\AMMediaboard.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceAMBaseboard.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Core\HW\DVD\AMMediaboard.h" />
<ClInclude Include="Core\HW\SI\SI_DeviceAMBaseboard.h" />
</ItemGroup>
<UsingTask TaskName="GetProductVersion" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<ProductVersion ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.Diagnostics"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
<Using Namespace="System.Diagnostics" />
<Code Type="Fragment" Language="cs"><![CDATA[
ProductVersion = FileVersionInfo.GetVersionInfo(Path).ProductVersion;
]]>
</Code>
]]></Code>
</Task>
</UsingTask>
<Target Name="WriteBuildInfo" AfterTargets="Build" Inputs="$(BuildInfoTemplate)" Outputs="$(BuildInfoOutput)">
@ -91,10 +95,6 @@
<Output PropertyName="VCToolsProductVersion" TaskParameter="ProductVersion" />
</GetProductVersion>
<Message Text="VCToolsProductVersion $(VCToolsProductVersion)" Importance="High" />
<WriteLinesToFile
File="$(BuildInfoOutput)"
Lines="$([System.IO.File]::ReadAllText($(BuildInfoTemplate)).Replace('${VC_TOOLS_VERSION}', $(VCToolsProductVersion)))"
Overwrite="true"
/>
<WriteLinesToFile File="$(BuildInfoOutput)" Lines="$([System.IO.File]::ReadAllText($(BuildInfoTemplate)).Replace('${VC_TOOLS_VERSION}', $(VCToolsProductVersion)))" Overwrite="true" />
</Target>
</Project>

View file

@ -43,6 +43,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)
@ -133,6 +134,7 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index)
case SerialInterface::SIDEVICE_NONE:
case SerialInterface::SIDEVICE_GC_GBA:
return;
case SerialInterface::SIDEVICE_AM_BASEBOARD:
case SerialInterface::SIDEVICE_GC_CONTROLLER:
type = MappingWindow::Type::MAPPING_GCPAD;
break;

View file

@ -123,12 +123,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);

View file

@ -8,6 +8,10 @@
#include "Core/HW/GCPad.h"
#include "Core/HW/GCPadEmu.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_DeviceAMBaseboard.h"
#include "Core/Config/MainSettings.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/InputConfig.h"
@ -23,8 +27,15 @@ void GCPadEmu::CreateMainLayout()
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 (Config::Get(Config::GetInfoForSIDevice(0)) ==
SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD)
{
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);

View file

@ -24,6 +24,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"
@ -437,8 +440,17 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
case Type::MAPPING_GC_DANCEMAT:
case Type::MAPPING_GCPAD:
widget = new GCPadEmu(this);
setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1));
AddWidget(tr("GameCube Controller"), widget);
if (Config::Get(Config::GetInfoForSIDevice(GetPort())) ==
SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD)
{
setWindowTitle(tr("Triforce Baseboard at Port %1").arg(GetPort() + 1));
AddWidget(tr("Triforce Baseboard"), widget);
}
else
{
setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1));
AddWidget(tr("GameCube Controller"), widget);
}
break;
case Type::MAPPING_GC_MICROPHONE:
widget = new GCMicrophone(this);

View file

@ -1145,6 +1145,43 @@ void MainWindow::StartGame(std::unique_ptr<BootParameters>&& parameters)
if (!NKitWarningDialog::ShowUnlessDisabled())
return;
}
/*
When booting Triforce games, we need to ensure that the hardware is set up correctly.
*/
const auto volume_type =
std::get<BootParameters::Disc>(parameters->parameters).volume->GetVolumeType();
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;
if (volume_type == DiscIO::Platform::Triforce)
{
if (!triforce_hardware_sp1 || !triforce_hardware_port_1)
{
ModalMessageBox::critical(
this, tr("Error"),
tr("To boot a Triforce game, SP1 and Port 1 must be set to Triforce Baseboard."),
QMessageBox::Ok);
HideRenderWidget();
return;
}
}
else
{
/*
Some Triforce tools don't include a boot.id file, but they can still be launched.
*/
if (triforce_hardware_sp1 || triforce_hardware_port_1)
{
ModalMessageBox::warning(
this, tr("Warning"),
tr("Non-Triforce games cannot be booted with Triforce hardware attached."),
QMessageBox::Ok);
}
}
}
// If we're running, only start a new game once we've stopped the last.

View file

@ -310,6 +310,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();
@ -1105,6 +1108,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);

View file

@ -217,6 +217,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;

View file

@ -138,15 +138,10 @@ void GameCubePane::CreateWidgets()
}
// Add SP1 devices
for (const auto device : {
EXIDeviceType::None,
EXIDeviceType::Dummy,
EXIDeviceType::Ethernet,
EXIDeviceType::EthernetXLink,
EXIDeviceType::EthernetTapServer,
EXIDeviceType::EthernetBuiltIn,
EXIDeviceType::ModemTapServer,
})
for (const auto device :
{EXIDeviceType::None, EXIDeviceType::Dummy, EXIDeviceType::Ethernet,
EXIDeviceType::EthernetXLink, EXIDeviceType::EthernetTapServer,
EXIDeviceType::EthernetBuiltIn, EXIDeviceType::ModemTapServer, EXIDeviceType::Baseboard})
{
m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()),
static_cast<int>(device));
@ -414,6 +409,10 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
dialog.exec();
return;
}
case ExpansionInterface::EXIDeviceType::Baseboard:
{
return;
}
default:
PanicAlertFmt("Unknown settings pressed for {}", device);
return;

View file

@ -28,6 +28,13 @@ enum PadButton
PAD_BUTTON_START = 0x1000,
};
enum SwitchButton
{
PAD_SWITCH_TEST = 0x0001,
PAD_SWITCH_SERVICE = 0x0002,
PAD_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;

View file

@ -131,7 +131,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);
@ -312,7 +311,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);
@ -501,8 +499,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;
}
@ -529,6 +526,15 @@ const std::string& GameFile::GetMaker(Variant variant) const
if (!maker.empty())
return maker;
// Triforce games use a default header, so all have RELSAB as the ID
// The actual information is stored within the boot.id file
// TODO: use maker name to set company IDs
if (m_game_id[0] == 'S' && m_game_id[1] == 'B')
{
return DiscIO::GetCompanyFromID("6E"); // SEGA
}
if (m_game_id.size() >= 6)
return DiscIO::GetCompanyFromID(m_maker_id);

View file

@ -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;

View file

@ -26,7 +26,7 @@
namespace UICommon
{
static constexpr u32 CACHE_REVISION = 26; // Last changed in PR 10084
static constexpr u32 CACHE_REVISION = 27; // Last changed in PR 10084
std::vector<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
bool recursive_scan)

View file

@ -269,6 +269,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_MAILLOGS_IDX));

View file

@ -25,7 +25,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Languages", "..\Languages\L
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LZO", "..\Externals\LZO\LZO.vcxproj", "{AB993F38-C31D-4897-B139-A620C42BC565}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lz4", "..\Externals\LZ4\LZ4.vcxproj", "{9092C5CC-3E71-41B3-BF68-4A7BDD8A5476}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LZ4", "..\Externals\lz4\LZ4.vcxproj", "{9092C5CC-3E71-41B3-BF68-4A7BDD8A5476}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniupnpc", "..\Externals\miniupnpc\miniupnpc.vcxproj", "{31643FDB-1BB8-4965-9DE7-000FC88D35AE}"
EndProject
@ -186,7 +186,6 @@ Global
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|ARM64.ActiveCfg = Debug|ARM64
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|ARM64.Build.0 = Debug|ARM64
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.ActiveCfg = Debug|x64
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.Build.0 = Debug|x64
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|ARM64.ActiveCfg = Release|ARM64
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|ARM64.Build.0 = Release|ARM64
{31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.ActiveCfg = Release|x64