Compare commits

...

8 Commits

Author SHA1 Message Date
SilentHeII
0f82503cf7 GameDB: Added various memory card filters
Reads Twisted Metal Black for bonus unlockable.

Adds memcardFilters for Shadow Hearts: Covenant

Ignore my previous pull request. I have no idea why it formatted it so obnoxiously.

Anyway, you get extra ingame items if you have a save file from Shadow Hearts 1.

GameIndex changes

Added memcardFilters for Biohazard Outbreak & Biohazard Outbreak File 2 to allow for creation of network configurations.

Added missing titles

Added missing titles to allow games to load their own saves.
2026-01-06 20:56:26 +01:00
Christopher Obbard
33f625a4e2 cmake: only require Qt modules when Qt UI is enabled
The Qt6 CorePrivate/GuiPrivate/WidgetsPrivate components are only needed
for the Qt UI build. Move their find_package() call under ENABLE_QT_UI
to avoid requiring private Qt modules when building without the UI.

Signed-off-by: Christopher Obbard <obbardc@gmail.com>
2026-01-06 20:44:35 +01:00
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
10 changed files with 1641 additions and 1235 deletions

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

@@ -11384,6 +11384,10 @@ SCUS-97197:
name: "War of the Monsters"
region: "NTSC-U"
compat: 5
memcardFilters: # Reads Twisted Metal Black for bonus unlockable.
- "SCUS-97101"
- "SCUS-97179"
- "SCUS-97197"
SCUS-97198:
name: "Sly Cooper and the Thievius Raccoonus"
region: "NTSC-U"
@@ -30583,6 +30587,11 @@ SLES-82030:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLES-82030"
- "SLES-82031"
- "SLES-50677"
- "SLES-50822"
SLES-82031:
name: "Shadow Hearts - Covenant [Disc 2 of 2]"
region: "PAL-M3"
@@ -30590,8 +30599,11 @@ SLES-82031:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLES-82030"
- "SLES-82031"
- "SLES-50677"
- "SLES-50822"
SLES-82032:
name: "Metal Gear Solid 3 - Snake Eater"
region: "PAL-G"
@@ -43473,6 +43485,11 @@ SLPM-65428:
name-en: "BioHazard Outbreak"
region: "NTSC-J"
compat: 5
memcardFilters:
- "SLPM-65428"
- "SLPM-74201"
- "SLPM-65286"
- "BWNETCNF"
SLPM-65429:
name: "ギャラクシーエンジェル Moonlit Lovers [初回限定版ファーストパッケージ]"
name-sort: "ぎゃらくしーえんじぇる Moonlit Lovers [しょかいげんていばんふぁーすとぱっけーじ]"
@@ -44973,6 +44990,8 @@ SLPM-65692:
- "SLPM-65692"
- "SLPM-65428"
- "SLPM-74201"
- "SLPM-65286"
- "BWNETCNF"
SLPM-65693:
name: "ときめきメモリアル3 ~約束のあの場所で~ [コナミ殿堂セレクション]"
name-sort: "ときめきめもりある3 やくそくのあのばしょで [こなみでんどうせれくしょん]"
@@ -53680,6 +53699,11 @@ SLPM-74201:
name-sort: "ばいおはざーど あうとぶれいく [PlayStation2 the Best]"
name-en: "BioHazard Outbreak [PlayStation2 the Best]"
region: "NTSC-J"
memcardFilters:
- "SLPM-65428"
- "SLPM-74201"
- "SLPM-65286"
- "BWNETCNF"
SLPM-74202:
name: "風雲 新撰組 [PlayStation2 the Best]"
name-sort: "ふううん しんせんぐみ [PlayStation2 the Best]"
@@ -58845,6 +58869,11 @@ SLPS-25317:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25317"
- "SLPS-25318"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25318:
name: "シャドウハーツⅡ [DXパック] [ディスク2/2]"
name-sort: "しゃどうはーつ2 [DXぱっく] [でぃすく2/2]"
@@ -58854,8 +58883,11 @@ SLPS-25318:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25317"
- "SLPS-25318"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25319:
name: "ケロケロキング スーパーDX"
name-sort: "けろけろきんぐ すーぱーDX"
@@ -58934,6 +58966,11 @@ SLPS-25334:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25334"
- "SLPS-25335"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25335:
name: "シャドウハーツⅡ [通常版] [ディスク2/2]"
name-sort: "しゃどうはーつ2 [つうじょうばん] [でぃすく2/2]"
@@ -58943,8 +58980,11 @@ SLPS-25335:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25334"
- "SLPS-25335"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25336:
name: "バスランディング3 [Sammy best] [つりコン2+ 同梱版]"
name-sort: "ばすらんでぃんぐ3 [Sammy best] [つりこん2 どうこんばん]"
@@ -63242,6 +63282,11 @@ SLPS-73214:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-73214"
- "SLPS-73215"
- "SLPS-25041"
- "SLPS-73418"
SLPS-73215:
name: "シャドウハーツⅡ ディレクターズカット [PlayStation2 the Best] [ディスク2/2]"
name-sort: "しゃどうはーつ2 でぃれくたーずかっと [PlayStation2 the Best] [でぃすく2/2]"
@@ -63251,8 +63296,11 @@ SLPS-73215:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-73214"
- "SLPS-73215"
- "SLPS-25041"
- "SLPS-73418"
SLPS-73216:
name: "マグナカルタ [PlayStation2 the Best]"
name-sort: "まぐなかるた [PlayStation2 the Best]"
@@ -69321,6 +69369,10 @@ SLUS-21041:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLUS-21041"
- "SLUS-21044"
- "SLUS-20347"
SLUS-21042:
name: "Darkwatch"
region: "NTSC-U"
@@ -69339,8 +69391,10 @@ SLUS-21044:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLUS-21041"
- "SLUS-21044"
- "SLUS-20347"
SLUS-21045:
name: "Conflict - Vietnam"
region: "NTSC-U"

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

@@ -109,14 +109,14 @@ disable_compiler_warnings_for_target(speex)
# Find the Qt components that we need.
if(ENABLE_QT_UI)
find_package(Qt6 6.10.0 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
endif()
if (Qt6_VERSION VERSION_GREATER_EQUAL 6.10.0)
find_package(Qt6 COMPONENTS CorePrivate GuiPrivate WidgetsPrivate REQUIRED)
endif()
if (Qt6_VERSION VERSION_GREATER_EQUAL 6.10.0)
find_package(Qt6 COMPONENTS CorePrivate GuiPrivate WidgetsPrivate REQUIRED)
endif()
# The docking system for the debugger.
# The docking system for the debugger.
find_package(KDDockWidgets-qt6 2.3.0 REQUIRED)
endif()
if(WIN32)
add_subdirectory(3rdparty/rainterface EXCLUDE_FROM_ALL)

File diff suppressed because it is too large Load Diff

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

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