Compare commits

...

14 Commits

Author SHA1 Message Date
JordanTheToaster
5b0c22c343 Mac: Update MoltenVK to v1.4.1 2026-01-06 20:37:29 +01:00
Florin9doi
ea963ffd72 USB: Train Mascon and Master Controller emulation 2026-01-06 20:18:15 +01:00
lightningterror
bd9dcbe441 GS/D3D: Default to DX12 on older GCN amd cards. 2026-01-06 20:09:04 +01:00
PCSX2 Bot
2a1f29c641 [ci skip] Qt: Update Base Translation. 2026-01-06 20:08:18 +01:00
TheLastRar
38883e8df4 GS/DX12: Use cmdlist parameter for read depth transitions 2026-01-06 20:08:02 +01:00
PCSX2 Bot
f971040912 [ci skip] PAD: Update to latest controller database. 2026-01-06 20:07:43 +01:00
Jordan
9aac7e8426 [ci skip] GameDB: CMR 2005 Fixes (#13782)
hotpatch!!!
2026-01-05 17:44:44 -05:00
Ty Lamontagne
96284205a1 [ci skip] Release 2.6 2026-01-04 14:20:16 -05:00
RedPanda4552
4a1d9d31d0 Achievements: Store token to separate ini 2026-01-04 13:56:14 -05:00
Ty
12d6087f2a Translations: Syncing Crowdin translations 2026-01-04 13:55:43 -05:00
Ty
251962c415 Qt: Don't show the dev name if it equals the displayname 2026-01-04 13:49:18 -05:00
Ty
1bdd7d2352 FSUI: Don't show the name if it equals the displayname when listing 2026-01-04 13:49:18 -05:00
SternXD
7b98259ea1 FullscreenUI: Update auto mapping to include device names with display names 2026-01-03 23:59:57 -05:00
Ty
ee8166d1fe Debugger: Check PC instead of cycles when stopping for a breakpoint 2026-01-03 10:14:21 -05:00
60 changed files with 202871 additions and 152863 deletions

View File

@@ -4,6 +4,17 @@ on:
schedule:
- cron: "0 0 * * *" # Every day at 12am UTC.
workflow_dispatch: # As well as manually.
inputs:
stableBuild:
description: 'Build stable version'
required: false
type: boolean
default: false
publish:
description: 'Publish to Flathub'
required: false
type: boolean
default: true
jobs:
@@ -52,8 +63,8 @@ jobs:
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
publish: true
publish: ${{ inputs.publish || true }}
fetchTags: true
stableBuild: false
stableBuild: ${{ inputs.stableBuild || false }}
secrets: inherit

View File

@@ -12,7 +12,7 @@ on:
- master
workflow_dispatch:
inputs:
is_prelease:
is_prerelease:
description: 'Should be a pre-release?'
required: true
default: 'true'
@@ -73,7 +73,7 @@ jobs:
with:
body_path: ./release-notes.md
draft: true
prerelease: ${{ github.event_name != 'workflow_dispatch' || inputs.is_prelease == 'true' }}
prerelease: ${{ github.event_name != 'workflow_dispatch' || inputs.is_prerelease == 'true' }}
tag_name: ${{ steps.tag_version.outputs.new_tag }}
- name: Create a GitHub Release (Push)
@@ -100,7 +100,7 @@ jobs:
cmakeflags: ""
buildAppImage: true
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }}
secrets: inherit
build_linux_flatpak:
@@ -114,9 +114,9 @@ jobs:
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
publish: false
publish: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }} # prerelease builds are published by the cron job
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ inputs.is_prerelease == 'false' }}
secrets: inherit
# Windows
@@ -133,7 +133,7 @@ jobs:
buildSystem: cmake
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }}
secrets: inherit
# MacOS
@@ -147,7 +147,7 @@ jobs:
jobName: "MacOS Build"
artifactPrefixName: "PCSX2-macos-Qt"
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }}
sign_and_notarize: true
secrets: inherit

View File

@@ -47,7 +47,7 @@ LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
MOLTENVK=1.2.9
MOLTENVK=1.4.1
QT=6.10.1
QTAPNG=1.3.0
KDDOCKWIDGETS=2.4.0
@@ -88,7 +88,7 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
@@ -277,7 +277,7 @@ rm -fr "MoltenVK-${MOLTENVK}"
tar xf "v$MOLTENVK.tar.gz"
cd "MoltenVK-${MOLTENVK}"
./fetchDependencies --macos
make macos
make macos MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0 MVK_CONFIG_USE_METAL_PRIVATE_API=1
cp Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib "$INSTALLDIR/lib/"
cd ..

View File

@@ -29,7 +29,7 @@ LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
MOLTENVK=1.2.9
MOLTENVK=1.4.1
QT=6.10.1
QTAPNG=1.3.0
KDDOCKWIDGETS=2.4.0
@@ -69,7 +69,7 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
@@ -225,7 +225,7 @@ cd "MoltenVK-${MOLTENVK}"
sed -i '' 's/xcodebuild "$@"/xcodebuild $XCODEBUILD_EXTRA_ARGS "$@"/g' fetchDependencies
sed -i '' 's/XCODEBUILD :=/XCODEBUILD ?=/g' Makefile
XCODEBUILD_EXTRA_ARGS="VALID_ARCHS=x86_64" ./fetchDependencies --macos
XCODEBUILD="set -o pipefail && xcodebuild VALID_ARCHS=x86_64" make macos
XCODEBUILD="set -o pipefail && xcodebuild VALID_ARCHS=x86_64" make macos MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0 MVK_CONFIG_USE_METAL_PRIVATE_API=1
cp Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib "$INSTALLDIR/lib/"
cd ..

View File

@@ -20894,10 +20894,8 @@ SLES-52636:
gameFixes:
- FullVU0SyncHack # Fixes in-game timer.
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes missing lighting and car reflections.
halfPixelOffset: 1 # Fixes 4 split lines in stage intros.
autoFlush: 1 # Fixes incorrect colors.
alignSprite: 1 # Fixes vertical lines such as in FMVs.
minimumBlendingLevel: 3 # Fixes missing lighting and car reflections.
halfPixelOffset: 4 # Fixes lines in game and FMVs.
SLES-52637:
name: "TOCA Race Driver 2"
region: "PAL-M5"

View File

@@ -245,7 +245,8 @@
03000000b62500000100000000000000,Gametel GT004 01,a:b3,b:b0,dpdown:b10,dpleft:b9,dpright:b8,dpup:b11,leftshoulder:b4,rightshoulder:b5,start:b7,x:b1,y:b2,platform:Windows,
030000008f0e00001411000000000000,Gamo2 Divaller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000a857000000000000,Gator Claw,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000c9110000f055000000000000,GC100XF,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000c21100000791000000000000,Be1 GC101 Controller 1.03,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000c9110000f055000000000000,Be1 GC100XF Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,

View File

@@ -86,6 +86,7 @@ namespace QtHost
//////////////////////////////////////////////////////////////////////////
static QTimer* s_settings_save_timer = nullptr;
static std::unique_ptr<INISettingsInterface> s_base_settings_interface;
static std::unique_ptr<INISettingsInterface> s_secrets_settings_interface;
static bool s_batch_mode = false;
static bool s_nogui_mode = false;
static bool s_start_big_picture_mode = false;
@@ -1307,6 +1308,7 @@ bool QtHost::InitializeConfig()
// Write crash dumps to the data directory, since that'll be accessible for certain.
CrashHandler::SetWriteDirectory(EmuFolders::DataRoot);
// Load main settings ini
const std::string path = Path::Combine(EmuFolders::Settings, "PCSX2.ini");
const bool settings_exists = FileSystem::FileExists(path.c_str());
Console.WriteLnFmt("Loading config from {}.", path);
@@ -1347,6 +1349,29 @@ bool QtHost::InitializeConfig()
SaveSettings();
}
// Layer secrets ini on top
const std::string secrets_path = Path::Combine(EmuFolders::Settings, "secrets.ini");
const bool secrets_settings_exists = FileSystem::FileExists(secrets_path.c_str());
Console.WriteLnFmt("Loading secrets from {}.", secrets_path);
s_secrets_settings_interface = std::make_unique<INISettingsInterface>(std::move(secrets_path));
Host::Internal::SetSecretsSettingsLayer(s_secrets_settings_interface.get());
if (!secrets_settings_exists || !s_secrets_settings_interface->Load())
{
if (!s_base_settings_interface->Save(&error))
{
QMessageBox::critical(
nullptr, QStringLiteral("PCSX2"),
QStringLiteral(
"Failed to save secrets to\n\n%1\n\nThe error was: %2\n\nPlease ensure this directory is writable. You "
"can also try portable mode by creating portable.txt in the same directory you installed PCSX2 into.")
.arg(QString::fromStdString(s_secrets_settings_interface->GetFileName()))
.arg(QString::fromStdString(error.GetDescription())));
return false;
}
}
// Setup wizard was incomplete last time?
s_run_setup_wizard =
s_run_setup_wizard || s_base_settings_interface->GetBoolValue("UI", "SetupWizardIncomplete", false);

View File

@@ -202,7 +202,11 @@ void ControllerBindingWidget::onAutomaticBindingClicked()
for (const QPair<QString, QString>& dev : m_dialog->getDeviceList())
{
// we set it as data, because the device list could get invalidated while the menu is up
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(dev.first).arg(dev.second));
QAction* action;
if(dev.first.compare(dev.second, Qt::CaseInsensitive) == 0)
action = menu.addAction(dev.first);
else
action = menu.addAction(QStringLiteral("%1: %2").arg(dev.first).arg(dev.second));
action->setData(dev.first);
connect(action, &QAction::triggered, this, [this, action]() { doDeviceAutomaticBinding(action->data().toString()); });
added = true;
@@ -1152,7 +1156,11 @@ void USBDeviceWidget::onAutomaticBindingClicked()
for (const QPair<QString, QString>& dev : m_dialog->getDeviceList())
{
// we set it as data, because the device list could get invalidated while the menu is up
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(dev.first).arg(dev.second));
QAction* action;
if(dev.first.compare(dev.second, Qt::CaseInsensitive) == 0)
action = menu.addAction(dev.first);
else
action = menu.addAction(QStringLiteral("%1: %2").arg(dev.first).arg(dev.second));
action->setData(dev.first);
connect(action, &QAction::triggered, this, [this, action]() { doDeviceAutomaticBinding(action->data().toString()); });
added = true;

View File

@@ -88,7 +88,11 @@ ControllerGlobalSettingsWidget::~ControllerGlobalSettingsWidget() = default;
void ControllerGlobalSettingsWidget::addDeviceToList(const QString& identifier, const QString& name)
{
QListWidgetItem* item = new QListWidgetItem();
item->setText(QStringLiteral("%1: %2").arg(identifier).arg(name));
if(identifier.compare(name,Qt::CaseInsensitive) == 0)
item->setText(identifier);
else
item->setText(QStringLiteral("%1: %2").arg(identifier).arg(name));
item->setData(Qt::UserRole, identifier);
m_ui.deviceList->addItem(item);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@
#include "common/MD5Digest.h"
#include "common/Path.h"
#include "common/ScopedGuard.h"
#include "common/SettingsInterface.h"
#include "common/SmallString.h"
#include "common/StringUtil.h"
#include "common/Timer.h"
@@ -439,7 +440,25 @@ bool Achievements::Initialize()
IdentifyGame(VMManager::GetDiscCRC(), VMManager::GetCurrentCRC());
const std::string username = Host::GetBaseStringSettingValue("Achievements", "Username");
const std::string api_token = Host::GetBaseStringSettingValue("Achievements", "Token");
// Check the base settings file to see if the token is defined inside. Move if found.
std::string oldToken = Host::GetBaseStringSettingValue("Achievements", "Token");
if (!oldToken.empty())
{
auto secretsLock = Host::GetSecretsSettingsLock();
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
secretsInterface->SetStringValue("Achievements", "Token", oldToken.c_str());
secretsInterface->Save();
oldToken.clear();
auto baseLock = Host::GetSettingsLock();
SettingsInterface* baseInterface = Host::Internal::GetBaseSettingsLayer();
baseInterface->DeleteValue("Achievements", "Token");
baseInterface->Save();
}
const std::string api_token = Host::GetStringSettingValue("Achievements", "Token");
if (!username.empty() && !api_token.empty())
{
Console.WriteLn("Achievements: Attempting login with user '%s'...", username.c_str());
@@ -1785,9 +1804,12 @@ void Achievements::ClientLoginWithPasswordCallback(int result, const char* error
// Store configuration.
Host::SetBaseStringSettingValue("Achievements", "Username", params->username);
Host::SetBaseStringSettingValue("Achievements", "Token", user->token);
Host::SetBaseStringSettingValue("Achievements", "LoginTimestamp", fmt::format("{}", std::time(nullptr)).c_str());
Host::CommitBaseSettingChanges();
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
secretsInterface->SetStringValue("Achievements", "Token", user->token);
secretsInterface->Save();
ShowLoginSuccess(client);
}
@@ -1887,9 +1909,13 @@ void Achievements::Logout()
Console.WriteLn("Achievements: Clearing credentials...");
Host::RemoveBaseSettingValue("Achievements", "Username");
Host::RemoveBaseSettingValue("Achievements", "Token");
Host::RemoveBaseSettingValue("Achievements", "LoginTimestamp");
Host::CommitBaseSettingChanges();
auto secretsLock = Host::GetSecretsSettingsLock();
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
secretsInterface->DeleteValue("Achievements", "Token");
secretsInterface->Save();
}

View File

@@ -10,9 +10,7 @@
std::vector<BreakPoint> CBreakPoints::breakPoints_;
u32 CBreakPoints::breakSkipFirstAtEE_ = 0;
u64 CBreakPoints::breakSkipFirstTicksEE_ = 0;
u32 CBreakPoints::breakSkipFirstAtIop_ = 0;
u64 CBreakPoints::breakSkipFirstTicksIop_ = 0;
std::vector<MemCheck> CBreakPoints::memChecks_;
std::vector<MemCheck*> CBreakPoints::cleanupMemChecks_;
bool CBreakPoints::breakpointTriggered_ = false;
@@ -393,20 +391,18 @@ void CBreakPoints::SetSkipFirst(BreakPointCpu cpu, u32 pc)
if (cpu == BREAKPOINT_EE)
{
breakSkipFirstAtEE_ = standardizeBreakpointAddress(pc);
breakSkipFirstTicksEE_ = r5900Debug.getCycles();
}
else if (cpu == BREAKPOINT_IOP)
{
breakSkipFirstAtIop_ = pc;
breakSkipFirstTicksIop_ = r3000Debug.getCycles();
}
}
u32 CBreakPoints::CheckSkipFirst(BreakPointCpu cpu, u32 cmpPc)
{
if (cpu == BREAKPOINT_EE && breakSkipFirstTicksEE_ == r5900Debug.getCycles())
if (cpu == BREAKPOINT_EE && breakSkipFirstAtEE_ == r5900Debug.getPC())
return breakSkipFirstAtEE_;
else if (cpu == BREAKPOINT_IOP && breakSkipFirstTicksIop_ == r3000Debug.getCycles())
else if (cpu == BREAKPOINT_IOP && breakSkipFirstAtIop_ == r3000Debug.getPC())
return breakSkipFirstAtIop_;
return 0;
}
@@ -414,9 +410,7 @@ u32 CBreakPoints::CheckSkipFirst(BreakPointCpu cpu, u32 cmpPc)
void CBreakPoints::ClearSkipFirst()
{
breakSkipFirstAtEE_ = 0;
breakSkipFirstTicksEE_ = 0;
breakSkipFirstAtIop_ = 0;
breakSkipFirstTicksIop_ = 0;
}
const std::vector<MemCheck> CBreakPoints::GetMemCheckRanges()

View File

@@ -439,7 +439,9 @@ GSRendererType D3D::GetPreferredRenderer()
if (!feature_level.has_value())
return GSRendererType::DX11;
else if (feature_level == D3D_FEATURE_LEVEL_12_0)
return check_vulkan_supported() ? GSRendererType::VK : GSRendererType::DX11;
return check_vulkan_supported() ? GSRendererType::VK : GSRendererType::DX12;
else if (feature_level == D3D_FEATURE_LEVEL_11_1)
return GSRendererType::DX12;
else
return GSRendererType::DX11;
}

View File

@@ -738,7 +738,7 @@ void GSTexture12::TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RE
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 1, m_resource_state, D3D12_RESOURCE_STATE_DEPTH_WRITE}}},
};
GSDevice12::GetInstance()->GetCommandList()->ResourceBarrier(m_resource_state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
cmdlist->ResourceBarrier(m_resource_state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
}
else if (m_resource_state == (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
@@ -749,7 +749,7 @@ void GSTexture12::TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RE
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 1, D3D12_RESOURCE_STATE_DEPTH_WRITE, state}}},
};
GSDevice12::GetInstance()->GetCommandList()->ResourceBarrier(state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
cmdlist->ResourceBarrier(state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
}
else
{

View File

@@ -26,6 +26,7 @@ namespace Host
const std::string_view context, const std::string_view msg);
static std::mutex s_settings_mutex;
static std::mutex s_secrets_settings_mutex;
static LayeredSettingsInterface s_layered_settings_interface;
static constexpr u32 TRANSLATION_STRING_CACHE_SIZE = 4 * 1024 * 1024;
@@ -164,6 +165,11 @@ std::unique_lock<std::mutex> Host::GetSettingsLock()
return std::unique_lock<std::mutex>(s_settings_mutex);
}
std::unique_lock<std::mutex> Host::GetSecretsSettingsLock()
{
return std::unique_lock<std::mutex>(s_secrets_settings_mutex);
}
SettingsInterface* Host::GetSettingsInterface()
{
return &s_layered_settings_interface;
@@ -352,6 +358,11 @@ SettingsInterface* Host::Internal::GetBaseSettingsLayer()
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE);
}
SettingsInterface* Host::Internal::GetSecretsSettingsLayer()
{
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_SECRETS);
}
SettingsInterface* Host::Internal::GetGameSettingsLayer()
{
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_GAME);
@@ -365,10 +376,17 @@ SettingsInterface* Host::Internal::GetInputSettingsLayer()
void Host::Internal::SetBaseSettingsLayer(SettingsInterface* sif)
{
pxAssertRel(s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE) == nullptr,
"Base layer has not been set");
"Base layer has already been set");
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_BASE, sif);
}
void Host::Internal::SetSecretsSettingsLayer(SettingsInterface* sif)
{
pxAssertRel(s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_SECRETS) == nullptr,
"Secrets layer has already been set");
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_SECRETS, sif);
}
void Host::Internal::SetGameSettingsLayer(SettingsInterface* sif, std::unique_lock<std::mutex>& settings_lock)
{
s_layered_settings_interface.SetLayer(LayeredSettingsInterface::LAYER_GAME, sif);

View File

@@ -134,6 +134,8 @@ namespace Host
/// Direct access to settings interface. Must hold the lock when calling GetSettingsInterface() and while using it.
std::unique_lock<std::mutex> GetSettingsLock();
/// Ditto for secrets file.
std::unique_lock<std::mutex> GetSecretsSettingsLock();
SettingsInterface* GetSettingsInterface();
/// Sets host-specific default settings.
@@ -147,6 +149,9 @@ namespace Host
/// Retrieves the base settings layer. Must call with lock held.
SettingsInterface* GetBaseSettingsLayer();
/// Retrieves the base settings layer. Must call with lock held.
SettingsInterface* GetSecretsSettingsLayer();
/// Retrieves the game settings layer, if present. Must call with lock held.
SettingsInterface* GetGameSettingsLayer();
@@ -156,6 +161,9 @@ namespace Host
/// Sets the base settings layer. Should be called by the host at initialization time.
void SetBaseSettingsLayer(SettingsInterface* sif);
/// Sets the secrets settings layer. Should follow call to SetBaseSettingsLayer.
void SetSecretsSettingsLayer(SettingsInterface* sif);
/// Sets the game settings layer. Called by VMManager when the game changes.
void SetGameSettingsLayer(SettingsInterface* sif, std::unique_lock<std::mutex>& settings_lock);

View File

@@ -3458,8 +3458,11 @@ void FullscreenUI::StartAutomaticBinding(u32 port)
names.reserve(devices.size());
for (auto& [name, display_name] : devices)
{
if(!StringUtil::compareNoCase(name, display_name))
options.emplace_back(fmt::format("{}: {}", name, display_name), false);
else
options.emplace_back(std::move(display_name), false);
names.push_back(std::move(name));
options.emplace_back(std::move(display_name), false);
}
OpenChoiceDialog(FSUI_CSTR("Select Device"), false, std::move(options),
[port, names = std::move(names)](s32 index, const std::string& title, bool checked) {

View File

@@ -15,6 +15,7 @@ public:
LAYER_CMDLINE,
LAYER_GAME,
LAYER_INPUT,
LAYER_SECRETS,
LAYER_BASE,
NUM_LAYERS
};

View File

@@ -37,6 +37,8 @@ namespace usb_pad
TRANSLATE_NOOP("USB", "Type 2"),
TRANSLATE_NOOP("USB", "Shinkansen"),
TRANSLATE_NOOP("USB", "Ryojōhen"),
TRANSLATE_NOOP("USB", "Train Mascon"),
TRANSLATE_NOOP("USB", "Master Controller"),
};
return subtypes;
}
@@ -63,6 +65,14 @@ namespace usb_pad
CID_TC_L = CID_TC_C,
CID_TC_R = CID_TC_D,
// Train Mascon
CID_TC_ATS = CID_TC_D,
CID_TC_CLOSE = CID_TC_CAMERA,
CID_TC_POWER_UP,
CID_TC_POWER_DOWN,
CID_TC_REVERSER_UP,
CID_TC_REVERSER_DOWN,
BUTTONS_OFFSET = CID_TC_B,
};
@@ -110,6 +120,43 @@ namespace usb_pad
return bindings;
}
case TRAIN_MASCON:
{
static constexpr const InputBindingInfo bindings[] = {
{"PowerUp", TRANSLATE_NOOP("USB", "Power Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_UP, GenericInputBinding::R1},
{"PowerDown", TRANSLATE_NOOP("USB", "Power Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_DOWN, GenericInputBinding::L1},
{"ReverserUp", TRANSLATE_NOOP("USB", "Reverser Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_UP, GenericInputBinding::R2},
{"ReverserDown", TRANSLATE_NOOP("USB", "Reverser Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_DOWN, GenericInputBinding::L2},
{"Up", TRANSLATE_NOOP("USB", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, CID_TC_UP, GenericInputBinding::DPadUp},
{"Down", TRANSLATE_NOOP("USB", "D-Pad Down"), ICON_PF_DPAD_DOWN, InputBindingInfo::Type::Button, CID_TC_DOWN, GenericInputBinding::DPadDown},
{"Left", TRANSLATE_NOOP("USB", "D-Pad Left"), ICON_PF_DPAD_LEFT, InputBindingInfo::Type::Button, CID_TC_LEFT, GenericInputBinding::DPadLeft},
{"Right", TRANSLATE_NOOP("USB", "D-Pad Right"), ICON_PF_DPAD_RIGHT, InputBindingInfo::Type::Button, CID_TC_RIGHT, GenericInputBinding::DPadRight},
{"ATS", TRANSLATE_NOOP("USB", "ATS"), nullptr, InputBindingInfo::Type::Button, CID_TC_ATS, GenericInputBinding::Triangle},
{"Close", TRANSLATE_NOOP("USB", "Close"), nullptr, InputBindingInfo::Type::Button, CID_TC_CLOSE, GenericInputBinding::R3},
{"A", TRANSLATE_NOOP("USB", "A Button"), ICON_PF_KEY_A, InputBindingInfo::Type::Button, CID_TC_A, GenericInputBinding::Square},
{"B", TRANSLATE_NOOP("USB", "B Button"), ICON_PF_KEY_B, InputBindingInfo::Type::Button, CID_TC_B, GenericInputBinding::Cross},
{"C", TRANSLATE_NOOP("USB", "C Button"), ICON_PF_KEY_C, InputBindingInfo::Type::Button, CID_TC_C, GenericInputBinding::Circle},
{"Select", TRANSLATE_NOOP("USB", "Select"), ICON_PF_SELECT_SHARE, InputBindingInfo::Type::Button, CID_TC_SELECT, GenericInputBinding::Select},
{"Start", TRANSLATE_NOOP("USB", "Start"), ICON_PF_START, InputBindingInfo::Type::Button, CID_TC_START, GenericInputBinding::Start},
};
return bindings;
}
case MASTER_CONTROLLER:
{
static constexpr const InputBindingInfo bindings[] = {
{"PowerUp", TRANSLATE_NOOP("USB", "Power Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_UP, GenericInputBinding::R1},
{"PowerDown", TRANSLATE_NOOP("USB", "Power Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_POWER_DOWN, GenericInputBinding::L1},
{"ReverserUp", TRANSLATE_NOOP("USB", "Reverser Up"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_UP, GenericInputBinding::R2},
{"ReverserDown", TRANSLATE_NOOP("USB", "Reverser Down"), nullptr, InputBindingInfo::Type::Button, CID_TC_REVERSER_DOWN, GenericInputBinding::L2},
{"S", TRANSLATE_NOOP("USB", "S"), ICON_PF_KEY_S, InputBindingInfo::Type::Button, CID_TC_D, GenericInputBinding::Cross},
{"A", TRANSLATE_NOOP("USB", "A"), ICON_PF_KEY_A, InputBindingInfo::Type::Button, CID_TC_A, GenericInputBinding::Square},
{"B", TRANSLATE_NOOP("USB", "B"), ICON_PF_KEY_B, InputBindingInfo::Type::Button, CID_TC_B, GenericInputBinding::Triangle},
{"C", TRANSLATE_NOOP("USB", "C"), ICON_PF_KEY_C, InputBindingInfo::Type::Button, CID_TC_C, GenericInputBinding::Circle},
};
return bindings;
}
default:
break;
}
@@ -151,21 +198,66 @@ namespace usb_pad
{
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
s->passthrough = USB::GetConfigBool(si, s->port, TypeName(), "Passthrough", false);
switch (s->type)
{
case TRAIN_TYPE2:
case TRAIN_SHINKANSEN:
case TRAIN_RYOJOUHEN:
s->passthrough = USB::GetConfigBool(si, s->port, TypeName(), "Passthrough", false);
break;
case MASTER_CONTROLLER:
s->power_notches = USB::GetConfigInt(si, s->port, TypeName(), "power_notches", 5);
s->brake_notches = USB::GetConfigInt(si, s->port, TypeName(), "brake_notches", 8);
break;
}
}
std::span<const SettingInfo> TrainDevice::Settings(u32 subtype) const
{
static constexpr const SettingInfo passthrough = {
SettingInfo::Type::Boolean,
"Passthrough",
TRANSLATE_NOOP("USB", "Axes Passthrough"),
TRANSLATE_NOOP("USB", "Passes through the unprocessed input axis to the game. Enable if you are using a compatible Densha De Go! controller. Disable if you are using any other joystick."),
"false",
};
static constexpr const SettingInfo info[] = {passthrough};
return info;
switch (subtype)
{
case TRAIN_TYPE2:
case TRAIN_SHINKANSEN:
case TRAIN_RYOJOUHEN:
{
static constexpr const SettingInfo info[] = {
{
.type = SettingInfo::Type::Boolean,
.name = "Passthrough",
.display_name = TRANSLATE_NOOP("USB", "Axes Passthrough"),
.description = TRANSLATE_NOOP("USB", "Passes through the unprocessed input axis to the game. Enable if you are using a compatible Densha De Go! controller. Disable if you are using any other joystick."),
.default_value = "false",
}
};
return info;
}
case MASTER_CONTROLLER:
{
static constexpr const SettingInfo info[] = {
{
.type = SettingInfo::Type::Integer,
.name = "power_notches",
.display_name = TRANSLATE_NOOP("USB", "Power notches"),
.description = TRANSLATE_NOOP("USB", "Selects the number of power notches (3-6)"),
.default_value = "5",
.min_value = "3",
.max_value = "6",
},
{
.type = SettingInfo::Type::Integer,
.name = "brake_notches",
.display_name = TRANSLATE_NOOP("USB", "Brake notches"),
.description = TRANSLATE_NOOP("USB", "Selects the number of brake notches (5-8)"),
.default_value = "8",
.min_value = "5",
.max_value = "8",
}
};
return info;
}
default:
return {};
}
}
static constexpr u32 button_mask(u32 bind_index)
@@ -173,7 +265,7 @@ namespace usb_pad
return (1u << (bind_index - TrainControlID::BUTTONS_OFFSET));
}
static constexpr u8 button_at(u8 value, u32 index)
static constexpr u16 button_at(u16 value, u32 index)
{
return value & button_mask(index);
}
@@ -205,6 +297,10 @@ namespace usb_pad
case CID_TC_SELECT:
case CID_TC_START:
case CID_TC_CAMERA:
case CID_TC_POWER_UP:
case CID_TC_POWER_DOWN:
case CID_TC_REVERSER_UP:
case CID_TC_REVERSER_DOWN:
{
return (button_at(s->data.buttons, bind_index) != 0u) ? 1.0f : 0.0f;
}
@@ -251,14 +347,18 @@ namespace usb_pad
case CID_TC_SELECT:
case CID_TC_START:
case CID_TC_CAMERA:
case CID_TC_POWER_UP:
case CID_TC_POWER_DOWN:
case CID_TC_REVERSER_UP:
case CID_TC_REVERSER_DOWN:
{
const u32 mask = button_mask(bind_index);
if (value >= 0.5f)
s->data.buttons |= mask;
else
s->data.buttons &= ~mask;
break;
}
break;
default:
break;
@@ -452,11 +552,23 @@ namespace usb_pad
return (get_ab(buttons) | (button_at(buttons, CID_TC_CAMERA) >> 4) | ((get_cd(buttons) | get_ss(buttons)) << 1));
}
void TrainDeviceState::UpdateHandles(u8 max_power, u8 max_brake)
{
if (!button_at(prev_buttons, CID_TC_POWER_UP) && button_at(data.buttons, CID_TC_POWER_UP) && handle < max_brake + 1 + max_power)
handle++;
if (!button_at(prev_buttons, CID_TC_POWER_DOWN) && button_at(data.buttons, CID_TC_POWER_DOWN) && handle > 0)
handle--;
if (!button_at(prev_buttons, CID_TC_REVERSER_UP) && button_at(data.buttons, CID_TC_REVERSER_UP) && reverser < 2)
reverser++;
if (!button_at(prev_buttons, CID_TC_REVERSER_DOWN) && button_at(data.buttons, CID_TC_REVERSER_DOWN) && reverser > 0)
reverser--;
}
static void train_handle_data(USBDevice* dev, USBPacket* p)
{
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
if (p->pid != USB_TOKEN_IN || p->ep->nr != 1)
if (s->type < MASTER_CONTROLLER && (p->pid != USB_TOKEN_IN || p->ep->nr != 1))
{
Console.Error("Unhandled TrainController request pid=%d ep=%u", p->pid, p->ep->nr);
p->status = USB_RET_STALL;
@@ -501,6 +613,77 @@ namespace usb_pad
usb_packet_copy(p, &out, sizeof(out));
break;
}
case TRAIN_MASCON:
{
s->UpdateHandles(5, 6);
s->prev_buttons = s->data.buttons;
TrainConData_TrainMascon out = {};
out.one = 0x01;
out.handle = 1 + s->handle;
out.reverser = s->reverser < 2 ? !s->reverser : s->reverser;
out.ats = !!button_at(s->data.buttons, CID_TC_ATS);
out.close = !!button_at(s->data.buttons, CID_TC_CLOSE);
out.button_a_soft = !!button_at(s->data.buttons, CID_TC_A);
out.button_a_hard = !!button_at(s->data.buttons, CID_TC_A);
out.button_b = !!button_at(s->data.buttons, CID_TC_B);
out.button_c = !!button_at(s->data.buttons, CID_TC_C);
out.start = !!button_at(s->data.buttons, CID_TC_START);
out.select = !!button_at(s->data.buttons, CID_TC_SELECT);
out.dpad_up = s->data.hat_up;
out.dpad_down = s->data.hat_down;
out.dpad_left = s->data.hat_left;
out.dpad_right = s->data.hat_right;
usb_packet_copy(p, &out, sizeof(out));
break;
}
case MASTER_CONTROLLER:
{
if (p->ep->nr == 1) // interrupt in
{
p->status = USB_RET_STALL;
break;
}
else if (p->ep->nr == 2) // bulk out
{
// The game sends a reset command after ~1500ms without updates. Resend the status during the next transfer
s->last_handle = -1;
s->last_reverser = -1;
break;
} // else bulk in
s->UpdateHandles(s->power_notches, s->brake_notches);
char data[100];
std::memset(data, 0, sizeof(data));
u8 pos = 0;
if (s->last_handle != s->handle)
{
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_handle[s->handle + 8 - s->brake_notches]);
s->last_handle = s->handle;
}
if (s->last_reverser != s->reverser)
{
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_reverser[s->reverser]);
s->last_reverser = s->reverser;
}
for (int i = 0; i < 4; i++)
{
if (!button_at(s->prev_buttons, BUTTONS_OFFSET + i) && button_at(s->data.buttons, BUTTONS_OFFSET + i))
{
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_button_pressed[i]);
}
if (button_at(s->prev_buttons, BUTTONS_OFFSET + i) && !button_at(s->data.buttons, BUTTONS_OFFSET + i))
{
pos += snprintf(data + pos, sizeof(data) - pos, "%s\x0d", s->mc_button_released[i]);
}
}
s->prev_buttons = s->data.buttons;
usb_packet_copy(p, data, std::min<u16>(p->buffer_size, pos));
break;
}
default:
Console.Error("Unhandled TrainController USB_TOKEN_IN pid=%d ep=%u type=%u", p->pid, p->ep->nr, s->type);
p->status = USB_RET_IOERROR;
@@ -520,25 +703,42 @@ namespace usb_pad
s->desc.str = dct01_desc_strings;
if (usb_desc_parse_dev(dct01_dev_descriptor, sizeof(dct01_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
goto fail;
break;
case TRAIN_SHINKANSEN:
s->desc.str = dct02_desc_strings;
if (usb_desc_parse_dev(dct02_dev_descriptor, sizeof(dct02_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
goto fail;
break;
case TRAIN_RYOJOUHEN:
s->desc.str = dct03_desc_strings;
if (usb_desc_parse_dev(dct03_dev_descriptor, sizeof(dct03_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
goto fail;
break;
case TRAIN_MASCON:
s->desc.str = dct03_desc_strings;
if (usb_desc_parse_dev(train_mascon_dev_descriptor, sizeof(train_mascon_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
if (usb_desc_parse_config(train_mascon_config_descriptor, sizeof(train_mascon_config_descriptor), s->desc_dev) < 0)
goto fail;
break;
case MASTER_CONTROLLER:
s->desc.str = dct03_desc_strings;
if (usb_desc_parse_dev(master_controller_dev_descriptor, sizeof(master_controller_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
if (usb_desc_parse_config(master_controller_config_descriptor, sizeof(master_controller_config_descriptor), s->desc_dev) < 0)
goto fail;
break;
default:
goto fail;
}
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
goto fail;
s->dev.speed = USB_SPEED_FULL;
s->dev.klass.handle_attach = usb_desc_attach;
s->dev.klass.handle_reset = train_handle_reset;

View File

@@ -11,10 +11,11 @@ namespace usb_pad
{
enum TrainDeviceTypes
{
TRAIN_TYPE2, // TCPP20009 or similar
TRAIN_SHINKANSEN, // TCPP20011
TRAIN_RYOJOUHEN, // TCPP20014
TRAIN_COUNT,
TRAIN_TYPE2, // TCPP-20009 or similar
TRAIN_SHINKANSEN, // TCPP-20011
TRAIN_RYOJOUHEN, // TCPP-20014
TRAIN_MASCON, // COTM-02001
MASTER_CONTROLLER, // VOK-00105 or VOK-00106 with OGCW-10001 adapter
};
class TrainDevice final : public DeviceProxy
@@ -39,7 +40,7 @@ namespace usb_pad
u8 control;
u8 brake;
u8 power;
u8 horn;
u8 horn; // pedal
u8 hat;
u8 buttons;
};
@@ -49,7 +50,7 @@ namespace usb_pad
{
u8 brake;
u8 power;
u8 horn;
u8 horn; // pedal
u8 hat;
u8 buttons;
u8 pad;
@@ -60,12 +61,37 @@ namespace usb_pad
{
u8 brake;
u8 power;
u8 horn;
u8 horn; // pedal
u8 hat;
u8 buttons;
u8 pad[3];
};
static_assert(sizeof(TrainConData_Ryojouhen) == 8);
struct TrainConData_TrainMascon
{
u8 one;
u8 handle : 4;
u8 reverser : 4;
u8 ats : 1;
u8 close : 1;
u8 button_a_soft : 1;
u8 button_a_hard : 1;
u8 button_b : 1;
u8 button_c : 1;
u8 : 2;
u8 start : 1;
u8 select : 1;
u8 dpad_up : 1;
u8 dpad_down : 1;
u8 dpad_left : 1;
u8 dpad_right : 1;
u8 : 2;
};
static_assert(sizeof(TrainConData_TrainMascon) == 4);
#pragma pack(pop)
struct TrainDeviceState
@@ -75,6 +101,7 @@ namespace usb_pad
void Reset();
void UpdateHatSwitch() noexcept;
void UpdateHandles(u8 max_power, u8 max_brake);
USBDevice dev{};
USBDesc desc{};
@@ -95,12 +122,24 @@ namespace usb_pad
u8 power; // 255 is fully applied
u8 brake; // 255 is fully applied
u8 hatswitch; // direction
u8 buttons; // active high
u16 buttons; // active high
} data = {};
// Master Controller
const char* mc_handle[16] = {"TSB20", "TSB30", "TSB40", "TSE99", "TSA05", "TSA15", "TSA25", "TSA35", "TSA45", "TSA50", "TSA55", "TSA65", "TSA75", "TSA85", "TSA95", "TSB60"};
const char* mc_reverser[3] = {"TSG00", "TSG50", "TSG99"};
const char* mc_button_pressed[4] = {"TSY99", "TSX99", "TSZ99", "TSK99"};
const char* mc_button_released[4] = {"TSY00", "TSX00", "TSZ00", "TSK00"};
u8 power_notches;
u8 brake_notches;
u16 prev_buttons;
s8 last_handle = -1, handle = 0;
s8 last_reverser = -1, reverser = 1;
};
// Taito Densha Controllers as described at:
// https://marcriera.github.io/ddgo-controller-docs/controllers/usb/
// https://traincontrollerdb.marcriera.cat/hardware/#usb
#define DEFINE_DCT_DEV_DESCRIPTOR(prefix, subclass, product) \
static const uint8_t prefix##_dev_descriptor[] = { \
/* bLength */ USB_DEVICE_DESC_SIZE, \
@@ -185,4 +224,114 @@ namespace usb_pad
// dct03_dev_descriptor
DEFINE_DCT_DEV_DESCRIPTOR(dct03, 0xFF, 0x0007);
// ---- Train Mascon ----
static const uint8_t train_mascon_dev_descriptor[] = {
0x12, // bLength
0x01, // bDescriptorType (Device)
0x10, 0x01, // bcdUSB 1.10
0x00, // bDeviceClass (Use class information in the Interface Descriptors)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
0x08, // bMaxPacketSize0 8
0x06, 0x1C, // idVendor 0x1C06
0xA7, 0x77, // idProduct 0x77A7
0x02, 0x02, // bcdDevice 2.02
0x01, // iManufacturer (String Index)
0x02, // iProduct (String Index)
0x03, // iSerialNumber (String Index)
0x01, // bNumConfigurations 1
};
static const uint8_t train_mascon_config_descriptor[] = {
0x09, // bLength
0x02, // bDescriptorType (Configuration)
0x19, 0x00, // wTotalLength 25
0x01, // bNumInterfaces 1
0x01, // bConfigurationValue
0x04, // iConfiguration (String Index)
0xA0, // bmAttributes Remote Wakeup
0x32, // bMaxPower 100mA
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x00, // bInterfaceNumber 0
0x00, // bAlternateSetting
0x01, // bNumEndpoints 1
0x00, // bInterfaceClass
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0x00, // iInterface (String Index)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x81, // bEndpointAddress (IN/D2H)
0x03, // bmAttributes (Interrupt)
0x08, 0x00, // wMaxPacketSize 8
0x14, // bInterval 20 (unit depends on device speed)
};
// ---- Master Controller ----
// Implements a generic PL2303 adapter.
// Replace with official OGCW-10001 descriptors when available.
static const uint8_t master_controller_dev_descriptor[] = {
0x12, // bLength
0x01, // bDescriptorType (Device)
0x10, 0x01, // bcdUSB 1.10
0x00, // bDeviceClass (Use class information in the Interface Descriptors)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
0x40, // bMaxPacketSize0 64
0x7B, 0x06, // idVendor 0x067B
0x03, 0x23, // idProduct 0x2303
0x00, 0x03, // bcdDevice 3.00
0x01, // iManufacturer (String Index)
0x02, // iProduct (String Index)
0x00, // iSerialNumber (String Index)
0x01, // bNumConfigurations 1
};
static const uint8_t master_controller_config_descriptor[] = {
0x09, // bLength
0x02, // bDescriptorType (Configuration)
0x27, 0x00, // wTotalLength 39
0x01, // bNumInterfaces 1
0x01, // bConfigurationValue
0x00, // iConfiguration (String Index)
0x80, // bmAttributes
0x32, // bMaxPower 100mA
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x00, // bInterfaceNumber 0
0x00, // bAlternateSetting
0x03, // bNumEndpoints 3
0xFF, // bInterfaceClass
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0x00, // iInterface (String Index)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x81, // bEndpointAddress (IN/D2H)
0x03, // bmAttributes (Interrupt)
0x0A, 0x00, // wMaxPacketSize 10
0x01, // bInterval 1 (unit depends on device speed)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x02, // bEndpointAddress (OUT/H2D)
0x02, // bmAttributes (Bulk)
0x40, 0x00, // wMaxPacketSize 64
0x00, // bInterval 0 (unit depends on device speed)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x83, // bEndpointAddress (IN/D2H)
0x02, // bmAttributes (Bulk)
0x40, 0x00, // wMaxPacketSize 64
0x00, // bInterval 0 (unit depends on device speed)
};
} // namespace usb_pad