Status for Dolphin Central

Recent events

2026-02-03T23:35:02.985655	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:35:01.945872	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T23:34:37.789140	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'lint', 'pr': 14318, 'success': True, 'pending': False, 'url': 'https://dolphin.ci/#/builders/9/builds/8923', 'description': 'Build succeeded on builder lint', 'type': 'build_status'}
2026-02-03T23:34:37.470745	{'source': 'webserver', 'raw': {'buildid': 157756, 'number': 8923, 'builderid': 9, 'buildrequestid': 157882, 'workerid': 10, 'masterid': 1, 'started_at': 1770161567, 'complete_at': 1770161571, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {'branchname': ['pr-14318', 'Change'], 'shortrev': ['201aa6', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'workername': ['ubuntu-lts', 'Worker'], 'project': ['', 'Build'], 'buildnumber': [8923, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'buildername': ['lint', 'Builder'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'pr_id': [14318, 'Change'], 'builderid': [9, 'Builder'], 'repository': ['', 'Build'], 'codebase': ['', 'Build'], 'basedir': ['/buildbot', 'Worker'], 'builddir': ['/buildbot/lint', 'Worker'], 'got_revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'GitNoBranch']}, 'buildrequest': {'buildrequestid': 157882, 'buildsetid': 36142, 'builderid': 9, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': True, 'results': 0, 'submitted_at': 1770161566, 'complete_at': 1770161571, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 9, 'name': 'lint', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/9/builds/8923'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:18.255649	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-ubu-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/27/builds/8923', 'description': 'Auto build in progress on builder pr-ubu-x64', 'type': 'build_status'}
2026-02-03T23:34:17.798758	{'source': 'webserver', 'raw': {'buildid': 157764, 'number': 8923, 'builderid': 27, 'buildrequestid': 157887, 'workerid': 10, 'masterid': 1, 'started_at': 1770161571, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'buildername': ['pr-ubu-x64', 'Builder'], 'builderid': [27, 'Builder'], 'repository': ['', 'Build'], 'workername': ['ubuntu-lts', 'Worker'], 'buildnumber': [8923, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build']}, 'buildrequest': {'buildrequestid': 157887, 'buildsetid': 36142, 'builderid': 27, 'priority': 0, 'claimed': True, 'claimed_at': 1770161571, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 27, 'name': 'pr-ubu-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/27/builds/8923'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:15.005083	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-freebsd-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/11/builds/8924', 'description': 'Auto build in progress on builder pr-freebsd-x64', 'type': 'build_status'}
2026-02-03T23:34:14.730916	{'source': 'webserver', 'raw': {'buildid': 157762, 'number': 8924, 'builderid': 11, 'buildrequestid': 157883, 'workerid': 1, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'buildername': ['pr-freebsd-x64', 'Builder'], 'builderid': [11, 'Builder'], 'buildnumber': [8924, 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'repository': ['', 'Build'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'workername': ['freebsd', 'Worker'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'branchname': ['pr-14318', 'Change'], 'pr_id': [14318, 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'shortrev': ['201aa6', 'Change']}, 'buildrequest': {'buildrequestid': 157883, 'buildsetid': 36142, 'builderid': 11, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 11, 'name': 'pr-freebsd-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/11/builds/8924'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:14.103886	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-win-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/2/builds/8926', 'description': 'Auto build in progress on builder pr-win-x64', 'type': 'build_status'}
2026-02-03T23:34:13.813813	{'source': 'webserver', 'raw': {'buildid': 157757, 'number': 8926, 'builderid': 2, 'buildrequestid': 157880, 'workerid': 4, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'builderid': [2, 'Builder'], 'workername': ['windows', 'Worker'], 'buildnumber': [8926, 'Build'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'scheduler': ['pr', 'Scheduler'], 'buildername': ['pr-win-x64', 'Builder'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'repository': ['', 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157880, 'buildsetid': 36142, 'builderid': 2, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 2, 'name': 'pr-win-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/2/builds/8926'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:12.999848	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-osx-universal', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/7/builds/8938', 'description': 'Auto build in progress on builder pr-osx-universal', 'type': 'build_status'}
2026-02-03T23:34:12.697269	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-deb-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/26/builds/8922', 'description': 'Auto build in progress on builder pr-deb-x64', 'type': 'build_status'}
2026-02-03T23:34:12.465710	{'source': 'webserver', 'raw': {'buildid': 157760, 'number': 8938, 'builderid': 7, 'buildrequestid': 157881, 'workerid': 8, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'buildnumber': [8938, 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'codebase': ['', 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'project': ['', 'Build'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'builderid': [7, 'Builder'], 'workername': ['osx-m1', 'Worker'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'buildername': ['pr-osx-universal', 'Builder']}, 'buildrequest': {'buildrequestid': 157881, 'buildsetid': 36142, 'builderid': 7, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/7/builds/8938'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:12.373303	{'source': 'webserver', 'raw': {'buildid': 157759, 'number': 8922, 'builderid': 26, 'buildrequestid': 157886, 'workerid': 9, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'scheduler': ['pr', 'Scheduler'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'buildername': ['pr-deb-x64', 'Builder'], 'builderid': [26, 'Builder'], 'workername': ['debian', 'Worker'], 'buildnumber': [8922, 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build']}, 'buildrequest': {'buildrequestid': 157886, 'buildsetid': 36142, 'builderid': 26, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 26, 'name': 'pr-deb-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/26/builds/8922'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:12.263249	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-android', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/22/builds/8926', 'description': 'Auto build in progress on builder pr-android', 'type': 'build_status'}
2026-02-03T23:34:12.003496	{'source': 'webserver', 'raw': {'buildid': 157763, 'number': 8926, 'builderid': 22, 'buildrequestid': 157885, 'workerid': 12, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'pr_id': [14318, 'Change'], 'buildnumber': [8926, 'Build'], 'buildername': ['pr-android', 'Builder'], 'builderid': [22, 'Builder'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'branchname': ['pr-14318', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'shortrev': ['201aa6', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'workername': ['android', 'Worker']}, 'buildrequest': {'buildrequestid': 157885, 'buildsetid': 36142, 'builderid': 22, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 22, 'name': 'pr-android', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/22/builds/8926'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:11.463370	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'lint', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/9/builds/8923', 'description': 'Auto build in progress on builder lint', 'type': 'build_status'}
2026-02-03T23:34:11.174714	{'source': 'webserver', 'raw': {'buildid': 157756, 'number': 8923, 'builderid': 9, 'buildrequestid': 157882, 'workerid': 10, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'branchname': ['pr-14318', 'Change'], 'shortrev': ['201aa6', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'workername': ['ubuntu-lts', 'Worker'], 'project': ['', 'Build'], 'buildnumber': [8923, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'buildername': ['lint', 'Builder'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'pr_id': [14318, 'Change'], 'builderid': [9, 'Builder'], 'repository': ['', 'Build'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157882, 'buildsetid': 36142, 'builderid': 9, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 9, 'name': 'lint', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/9/builds/8923'}, 'type': 'raw_bb_hook'}
2026-02-03T23:33:22.579414	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/43/builds/4069', 'description': 'Auto build in progress on builder pr-flatpak-x64', 'type': 'build_status'}
2026-02-03T23:33:22.248903	{'source': 'webserver', 'raw': {'buildid': 157761, 'number': 4069, 'builderid': 43, 'buildrequestid': 157889, 'workerid': 13, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'branchname': ['pr-14318', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'pr_id': [14318, 'Change'], 'buildername': ['pr-flatpak-x64', 'Builder'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'shortrev': ['201aa6', 'Change'], 'buildnumber': [4069, 'Build'], 'codebase': ['', 'Build'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'builderid': [43, 'Builder'], 'workername': ['altair-flatpak', 'Worker'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'repository': ['', 'Build'], 'project': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157889, 'buildsetid': 36142, 'builderid': 43, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 43, 'name': 'pr-flatpak-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/43/builds/4069'}, 'type': 'raw_bb_hook'}
2026-02-03T23:33:21.847936	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-arm64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/44/builds/4014', 'description': 'Auto build in progress on builder pr-flatpak-arm64', 'type': 'build_status'}
2026-02-03T23:33:21.536353	{'source': 'webserver', 'raw': {'buildid': 157758, 'number': 4014, 'builderid': 44, 'buildrequestid': 157890, 'workerid': 14, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'branchname': ['pr-14318', 'Change'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'buildername': ['pr-flatpak-arm64', 'Builder'], 'builderid': [44, 'Builder'], 'scheduler': ['pr', 'Scheduler'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'workername': ['deneb-flatpak', 'Worker'], 'buildnumber': [4014, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'project': ['', 'Build'], 'repository': ['', 'Build'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157890, 'buildsetid': 36142, 'builderid': 44, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 44, 'name': 'pr-flatpak-arm64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/44/builds/4014'}, 'type': 'raw_bb_hook'}
2026-02-03T23:32:46.871760	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'lint', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:46.584472	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-arm64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:46.272392	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}

Recent 'build_status' events

2026-02-03T23:34:37.789140	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'lint', 'pr': 14318, 'success': True, 'pending': False, 'url': 'https://dolphin.ci/#/builders/9/builds/8923', 'description': 'Build succeeded on builder lint', 'type': 'build_status'}
2026-02-03T23:34:18.255649	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-ubu-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/27/builds/8923', 'description': 'Auto build in progress on builder pr-ubu-x64', 'type': 'build_status'}
2026-02-03T23:34:15.005083	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-freebsd-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/11/builds/8924', 'description': 'Auto build in progress on builder pr-freebsd-x64', 'type': 'build_status'}
2026-02-03T23:34:14.103886	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-win-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/2/builds/8926', 'description': 'Auto build in progress on builder pr-win-x64', 'type': 'build_status'}
2026-02-03T23:34:12.999848	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-osx-universal', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/7/builds/8938', 'description': 'Auto build in progress on builder pr-osx-universal', 'type': 'build_status'}
2026-02-03T23:34:12.697269	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-deb-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/26/builds/8922', 'description': 'Auto build in progress on builder pr-deb-x64', 'type': 'build_status'}
2026-02-03T23:34:12.263249	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-android', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/22/builds/8926', 'description': 'Auto build in progress on builder pr-android', 'type': 'build_status'}
2026-02-03T23:34:11.463370	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'lint', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/9/builds/8923', 'description': 'Auto build in progress on builder lint', 'type': 'build_status'}
2026-02-03T23:33:22.579414	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/43/builds/4069', 'description': 'Auto build in progress on builder pr-flatpak-x64', 'type': 'build_status'}
2026-02-03T23:33:21.847936	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-arm64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/#/builders/44/builds/4014', 'description': 'Auto build in progress on builder pr-flatpak-arm64', 'type': 'build_status'}
2026-02-03T23:32:46.871760	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'lint', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:46.584472	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-arm64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:46.272392	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-flatpak-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:45.948778	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-freebsd-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:45.655160	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-win-dbg-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:45.350306	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-win-arm64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:44.729907	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-win-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:44.450300	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-ubu-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:44.093114	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-osx-universal', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:43.789337	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-deb-dbg-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:43.494387	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-deb-x64', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:43.178418	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'pr-android', 'pr': 14318, 'success': False, 'pending': True, 'url': 'https://dolphin.ci/', 'description': 'Auto build pending', 'type': 'build_status'}
2026-02-03T23:32:42.860903	{'source': 'prbuilder', 'repo': 'dolphin-emu/dolphin', 'hash': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'shortrev': '201aa6', 'service': 'default', 'pr': 14318, 'success': True, 'pending': False, 'url': '', 'description': 'Very basic checks passed, handed off to Buildbot.', 'type': 'build_status'}
2026-02-03T23:03:56.278794	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'shortrev': '72a6ad', 'service': 'pr-android', 'pr': 14218, 'success': True, 'pending': False, 'url': 'https://dolphin.ci/#/builders/22/builds/8925', 'description': 'Build succeeded on builder pr-android', 'type': 'build_status'}
2026-02-03T22:57:43.019054	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'shortrev': '72a6ad', 'service': 'pr-flatpak-x64', 'pr': 14218, 'success': True, 'pending': False, 'url': 'https://dolphin.ci/#/builders/43/builds/4068', 'description': 'Build succeeded on builder pr-flatpak-x64', 'type': 'build_status'}

Recent 'command_message' events

2026-02-03T21:39:22.468786	{'source': 'ircclient', 'who': 'JosJuice', 'what': 'irrawaddy: please rebuild 14317', 'type': 'command_message'}
2026-02-03T21:39:01.984953	{'source': 'ircclient', 'who': 'JosJuice', 'what': 'irrawaddy: please rebuild 13997', 'type': 'command_message'}
2026-02-02T21:19:07.268593	{'source': 'ircclient', 'who': 'JosJuice', 'what': 'irrawaddy: please rebuild 14317', 'type': 'command_message'}

Recent 'gh_commit_comment' events

2026-02-03T16:08:06.874133	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'pizzzza19', 'commit': '128a6454b6247c0c2a2cafb2d91d03f71fc112e0', 'url': 'https://github.com/dolphin-emu/dolphin/commit/128a6454b6247c0c2a2cafb2d91d03f71fc112e0#commitcomment-176299891', 'type': 'gh_commit_comment'}

Recent 'gh_issue_comment' events

2026-02-03T21:10:07.303427	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'created', 'id': 13970, 'title': 'WiimoteReal: Windows improvements.', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843701295', 'safe_author': True, 'body': "> Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI don't know. I haven't experienced that.", 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'id': 3455745903, 'node_id': 'PR_kwDOALCn2M6qnqKV', 'number': 13970, 'title': 'WiimoteReal: Windows improvements.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'closed', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 27, 'created_at': '2025-09-26T04:25:44Z', 'updated_at': '2026-02-03T21:10:04Z', 'closed_at': '2025-10-13T06:54:54Z', 'author_association': 'MEMBER', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13970', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.patch', 'merged_at': '2025-10-13T06:54:54Z'}, 'body': 'Majorly overhauled the Windows Wii remote code.\r\n\r\nThis PR should be ready now.\r\n\r\nStatically link with "Bthprops.lib"\r\nDynamic linking was introduced in https://github.com/dolphin-emu/dolphin/commit/3acf0eb3af98009cc4e00bd9bcf43873dc26c4da to make Dolphin run under Wine.\r\nMy search results imply that Wine has long since added stubs for these functions so I\'m hoping it works fine now?\r\nedit: Wine seems to work fine.\r\n\r\nA 10 year old comment describes a workaround needed for the Toshiba Bluetooth stack.\r\nI\'ve removed the workaround.\r\n\r\nRemoved `HidP_SetOutputReport` fallback (Windows 7 workaround).\r\n\r\nFixed buggy overlapped Read/Write event usage (probably my fault from long ago).\r\n\r\nAdded checks for a Wii remote device name on the HID interface to only attempt connecting to actual Wii remotes devices.\r\nThe existing code just throws Wii remote data at every interface and checks for sane replies.\r\n\r\nAdded a periodic write to test for disconnected remotes in a timely manner.\r\n\r\nChanged the `Refresh` button to not remove authenticated remotes.\r\nSome users found it annoying.\r\nIt now only removes unauthenticated disconnected remotes.\r\n\r\nAdded `Sync` and `Reset` actions to establish and remove pairings.\r\nThe `Refresh` button is now a `QToolButton` with additional actions (only on Windows).\r\n<img width="120" height="177" alt="image" src="https://github.com/user-attachments/assets/16e57431-1eab-4465-a4f8-856188b0f942" />\r\n\r\nPairing a remote with the `Sync` button will enable it to reestablish a connection on any button press.\r\nIt currently shows an ugly animation while the task runs async.\r\n<img width="189" height="72" alt="image" src="https://github.com/user-attachments/assets/076191ce-8312-4cdc-8223-14a4bb9eca2d" />\r\nIt\'s better than nothing and can be visually improved later.\r\n\r\n`Refresh` or `Continuous Scanning` are no longer required to connect paired remotes.\r\nPairing with Windows or the DolphinBar "just works" by assigning a slot to `Real Wii Remote`.\r\n\r\nAdditionally, I am constantly bombarded with ControllerInterface / WiimotePool related deadlocks.\r\nThis seems like an existing issue that fixing properly is probably going to take another PR.\r\nIt seems like it\'s SDL?\r\nedit: SDL\'s DirectInput handling has been disabled in #13982 \r\n\r\nTODO\r\n - [x] Make the `Sync` button not require also clicking `Refresh`.\r\n - [x] Make paired + reconnected Wii remotes work without using `Refresh`\r\n - [x] Test Dolphin with Wine (just in general, not Wii remote functionality)\r\n - [x] Clean up log spam from continuous opening of HID interfaces.\r\n - [x] Make the Sync / Reset buttons display some kind kind of feedback.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/reactions', 'total_count': 4, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 1, 'confused': 0, 'heart': 3, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843701295', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843701295', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'id': 3843701295, 'node_id': 'IC_kwDOALCn2M7lGjov', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T21:10:04Z', 'updated_at': '2026-02-03T21:10:04Z', 'body': "> Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI don't know. I haven't experienced that.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843701295/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-03T21:01:13.944059	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Trihy', 'action': 'edited', 'id': 13970, 'title': 'WiimoteReal: Windows improvements.', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843649310', 'safe_author': False, 'body': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. The wiimote blink 2 or 3 times and turn off. That usually means rejected.", 'raw': {'action': 'edited', 'changes': {'body': {'from': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. "}}, 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'id': 3455745903, 'node_id': 'PR_kwDOALCn2M6qnqKV', 'number': 13970, 'title': 'WiimoteReal: Windows improvements.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'closed', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 26, 'created_at': '2025-09-26T04:25:44Z', 'updated_at': '2026-02-03T20:58:41Z', 'closed_at': '2025-10-13T06:54:54Z', 'author_association': 'MEMBER', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13970', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.patch', 'merged_at': '2025-10-13T06:54:54Z'}, 'body': 'Majorly overhauled the Windows Wii remote code.\r\n\r\nThis PR should be ready now.\r\n\r\nStatically link with "Bthprops.lib"\r\nDynamic linking was introduced in https://github.com/dolphin-emu/dolphin/commit/3acf0eb3af98009cc4e00bd9bcf43873dc26c4da to make Dolphin run under Wine.\r\nMy search results imply that Wine has long since added stubs for these functions so I\'m hoping it works fine now?\r\nedit: Wine seems to work fine.\r\n\r\nA 10 year old comment describes a workaround needed for the Toshiba Bluetooth stack.\r\nI\'ve removed the workaround.\r\n\r\nRemoved `HidP_SetOutputReport` fallback (Windows 7 workaround).\r\n\r\nFixed buggy overlapped Read/Write event usage (probably my fault from long ago).\r\n\r\nAdded checks for a Wii remote device name on the HID interface to only attempt connecting to actual Wii remotes devices.\r\nThe existing code just throws Wii remote data at every interface and checks for sane replies.\r\n\r\nAdded a periodic write to test for disconnected remotes in a timely manner.\r\n\r\nChanged the `Refresh` button to not remove authenticated remotes.\r\nSome users found it annoying.\r\nIt now only removes unauthenticated disconnected remotes.\r\n\r\nAdded `Sync` and `Reset` actions to establish and remove pairings.\r\nThe `Refresh` button is now a `QToolButton` with additional actions (only on Windows).\r\n<img width="120" height="177" alt="image" src="https://github.com/user-attachments/assets/16e57431-1eab-4465-a4f8-856188b0f942" />\r\n\r\nPairing a remote with the `Sync` button will enable it to reestablish a connection on any button press.\r\nIt currently shows an ugly animation while the task runs async.\r\n<img width="189" height="72" alt="image" src="https://github.com/user-attachments/assets/076191ce-8312-4cdc-8223-14a4bb9eca2d" />\r\nIt\'s better than nothing and can be visually improved later.\r\n\r\n`Refresh` or `Continuous Scanning` are no longer required to connect paired remotes.\r\nPairing with Windows or the DolphinBar "just works" by assigning a slot to `Real Wii Remote`.\r\n\r\nAdditionally, I am constantly bombarded with ControllerInterface / WiimotePool related deadlocks.\r\nThis seems like an existing issue that fixing properly is probably going to take another PR.\r\nIt seems like it\'s SDL?\r\nedit: SDL\'s DirectInput handling has been disabled in #13982 \r\n\r\nTODO\r\n - [x] Make the `Sync` button not require also clicking `Refresh`.\r\n - [x] Make paired + reconnected Wii remotes work without using `Refresh`\r\n - [x] Test Dolphin with Wine (just in general, not Wii remote functionality)\r\n - [x] Clean up log spam from continuous opening of HID interfaces.\r\n - [x] Make the Sync / Reset buttons display some kind kind of feedback.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/reactions', 'total_count': 4, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 1, 'confused': 0, 'heart': 3, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843649310', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'id': 3843649310, 'node_id': 'IC_kwDOALCn2M7lGW8e', 'user': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T20:58:41Z', 'updated_at': '2026-02-03T21:01:12Z', 'body': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. The wiimote blink 2 or 3 times and turn off. That usually means rejected.", 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-03T20:58:44.456467	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Trihy', 'action': 'created', 'id': 13970, 'title': 'WiimoteReal: Windows improvements.', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843649310', 'safe_author': False, 'body': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. ", 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'id': 3455745903, 'node_id': 'PR_kwDOALCn2M6qnqKV', 'number': 13970, 'title': 'WiimoteReal: Windows improvements.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'closed', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 26, 'created_at': '2025-09-26T04:25:44Z', 'updated_at': '2026-02-03T20:58:41Z', 'closed_at': '2025-10-13T06:54:54Z', 'author_association': 'MEMBER', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13970', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.patch', 'merged_at': '2025-10-13T06:54:54Z'}, 'body': 'Majorly overhauled the Windows Wii remote code.\r\n\r\nThis PR should be ready now.\r\n\r\nStatically link with "Bthprops.lib"\r\nDynamic linking was introduced in https://github.com/dolphin-emu/dolphin/commit/3acf0eb3af98009cc4e00bd9bcf43873dc26c4da to make Dolphin run under Wine.\r\nMy search results imply that Wine has long since added stubs for these functions so I\'m hoping it works fine now?\r\nedit: Wine seems to work fine.\r\n\r\nA 10 year old comment describes a workaround needed for the Toshiba Bluetooth stack.\r\nI\'ve removed the workaround.\r\n\r\nRemoved `HidP_SetOutputReport` fallback (Windows 7 workaround).\r\n\r\nFixed buggy overlapped Read/Write event usage (probably my fault from long ago).\r\n\r\nAdded checks for a Wii remote device name on the HID interface to only attempt connecting to actual Wii remotes devices.\r\nThe existing code just throws Wii remote data at every interface and checks for sane replies.\r\n\r\nAdded a periodic write to test for disconnected remotes in a timely manner.\r\n\r\nChanged the `Refresh` button to not remove authenticated remotes.\r\nSome users found it annoying.\r\nIt now only removes unauthenticated disconnected remotes.\r\n\r\nAdded `Sync` and `Reset` actions to establish and remove pairings.\r\nThe `Refresh` button is now a `QToolButton` with additional actions (only on Windows).\r\n<img width="120" height="177" alt="image" src="https://github.com/user-attachments/assets/16e57431-1eab-4465-a4f8-856188b0f942" />\r\n\r\nPairing a remote with the `Sync` button will enable it to reestablish a connection on any button press.\r\nIt currently shows an ugly animation while the task runs async.\r\n<img width="189" height="72" alt="image" src="https://github.com/user-attachments/assets/076191ce-8312-4cdc-8223-14a4bb9eca2d" />\r\nIt\'s better than nothing and can be visually improved later.\r\n\r\n`Refresh` or `Continuous Scanning` are no longer required to connect paired remotes.\r\nPairing with Windows or the DolphinBar "just works" by assigning a slot to `Real Wii Remote`.\r\n\r\nAdditionally, I am constantly bombarded with ControllerInterface / WiimotePool related deadlocks.\r\nThis seems like an existing issue that fixing properly is probably going to take another PR.\r\nIt seems like it\'s SDL?\r\nedit: SDL\'s DirectInput handling has been disabled in #13982 \r\n\r\nTODO\r\n - [x] Make the `Sync` button not require also clicking `Refresh`.\r\n - [x] Make paired + reconnected Wii remotes work without using `Refresh`\r\n - [x] Test Dolphin with Wine (just in general, not Wii remote functionality)\r\n - [x] Clean up log spam from continuous opening of HID interfaces.\r\n - [x] Make the Sync / Reset buttons display some kind kind of feedback.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/reactions', 'total_count': 4, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 1, 'confused': 0, 'heart': 3, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843649310', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'id': 3843649310, 'node_id': 'IC_kwDOALCn2M7lGW8e', 'user': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T20:58:41Z', 'updated_at': '2026-02-03T20:58:41Z', 'body': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. ", 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-03T17:22:19.677153	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JMC47', 'action': 'edited', 'id': 13844, 'title': 'Core: Triforce support', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842626451', 'safe_author': True, 'body': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should be considered a work in progress.", 'raw': {'action': 'edited', 'changes': {'body': {'from': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should a work in progress."}}, 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 102, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T17:22:06Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842626451', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842626451, 'node_id': 'IC_kwDOALCn2M7lCdOT', 'user': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T17:22:06Z', 'updated_at': '2026-02-03T17:22:17Z', 'body': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should be considered a work in progress.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-03T17:22:09.068259	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JMC47', 'action': 'created', 'id': 13844, 'title': 'Core: Triforce support', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842626451', 'safe_author': True, 'body': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should a work in progress.", 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 102, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T17:22:06Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842626451', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842626451, 'node_id': 'IC_kwDOALCn2M7lCdOT', 'user': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T17:22:06Z', 'updated_at': '2026-02-03T17:22:06Z', 'body': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should a work in progress.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-03T17:08:19.684924	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'pizzzza19', 'action': 'created', 'id': 13844, 'title': 'Core: Triforce support', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842552882', 'safe_author': False, 'body': 'Tested with manually built exe as guided by official dolphin readme.\nPrevious PR also had a same error. It is a known issue and may have discussed by other people outside this PR.', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 101, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T17:08:17Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842552882', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842552882', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842552882, 'node_id': 'IC_kwDOALCn2M7lCLQy', 'user': {'login': 'pizzzza19', 'id': 183198342, 'node_id': 'U_kgDOCutihg', 'avatar_url': 'https://avatars.githubusercontent.com/u/183198342?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/pizzzza19', 'html_url': 'https://github.com/pizzzza19', 'followers_url': 'https://api.github.com/users/pizzzza19/followers', 'following_url': 'https://api.github.com/users/pizzzza19/following{/other_user}', 'gists_url': 'https://api.github.com/users/pizzzza19/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/pizzzza19/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/pizzzza19/subscriptions', 'organizations_url': 'https://api.github.com/users/pizzzza19/orgs', 'repos_url': 'https://api.github.com/users/pizzzza19/repos', 'events_url': 'https://api.github.com/users/pizzzza19/events{/privacy}', 'received_events_url': 'https://api.github.com/users/pizzzza19/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T17:08:17Z', 'updated_at': '2026-02-03T17:08:17Z', 'body': 'Tested with manually built exe as guided by official dolphin readme.\nPrevious PR also had a same error. It is a known issue and may have discussed by other people outside this PR.', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842552882/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'pizzzza19', 'id': 183198342, 'node_id': 'U_kgDOCutihg', 'avatar_url': 'https://avatars.githubusercontent.com/u/183198342?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/pizzzza19', 'html_url': 'https://github.com/pizzzza19', 'followers_url': 'https://api.github.com/users/pizzzza19/followers', 'following_url': 'https://api.github.com/users/pizzzza19/following{/other_user}', 'gists_url': 'https://api.github.com/users/pizzzza19/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/pizzzza19/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/pizzzza19/subscriptions', 'organizations_url': 'https://api.github.com/users/pizzzza19/orgs', 'repos_url': 'https://api.github.com/users/pizzzza19/repos', 'events_url': 'https://api.github.com/users/pizzzza19/events{/privacy}', 'received_events_url': 'https://api.github.com/users/pizzzza19/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-03T16:31:29.616382	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'created', 'id': 13844, 'title': 'Core: Triforce support', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842350109', 'safe_author': True, 'body': '> Hello I built .exe with your latest code. it is a long journey to add triforce support but there are errors with some ROMs.  \r\n[GEYJ6E] VS2002: Game ID duplicated with JPN and EXP region. JPN rom will be overwritten as EXP mode. (removed Japan team and license logos)  \r\n[GHNJ6E] VS4 VerA (JPN) : Game frozen at demo screen or entering team select screen. This will only happen with JPN rom. It is well known that specific gekko code can skip boot error, but above cannot pass without fixing the main dolphin code.\r\n\r\n@pizzzza19 \r\nDid these games worked at some point with this PR? Do these games display in Dolphin\'s log window (with the highest verbosity enabled) error messages that can hint us regarding which pieces are missing? Does the issue occur with the build provided by [Dolphin\'s buildbot](https://dolphin.ci/#/builders/2/builds/8918)?\r\n\r\n<a href="https://dolphin.ci/#/builders/2/builds/8918"><img width="1427" height="486" alt="Image" src="https://github.com/user-attachments/assets/480e061d-edd0-4836-bdab-e87b46c5d60a" /></a>\r\n\r\n@JMC47 \r\nI don\'t have any Triforce games so you\'re probably more knowledgeable than me regarding the above games.', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 100, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T16:31:27Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842350109', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842350109', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842350109, 'node_id': 'IC_kwDOALCn2M7lBZwd', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T16:31:27Z', 'updated_at': '2026-02-03T16:31:27Z', 'body': '> Hello I built .exe with your latest code. it is a long journey to add triforce support but there are errors with some ROMs.  \r\n[GEYJ6E] VS2002: Game ID duplicated with JPN and EXP region. JPN rom will be overwritten as EXP mode. (removed Japan team and license logos)  \r\n[GHNJ6E] VS4 VerA (JPN) : Game frozen at demo screen or entering team select screen. This will only happen with JPN rom. It is well known that specific gekko code can skip boot error, but above cannot pass without fixing the main dolphin code.\r\n\r\n@pizzzza19 \r\nDid these games worked at some point with this PR? Do these games display in Dolphin\'s log window (with the highest verbosity enabled) error messages that can hint us regarding which pieces are missing? Does the issue occur with the build provided by [Dolphin\'s buildbot](https://dolphin.ci/#/builders/2/builds/8918)?\r\n\r\n<a href="https://dolphin.ci/#/builders/2/builds/8918"><img width="1427" height="486" alt="Image" src="https://github.com/user-attachments/assets/480e061d-edd0-4836-bdab-e87b46c5d60a" /></a>\r\n\r\n@JMC47 \r\nI don\'t have any Triforce games so you\'re probably more knowledgeable than me regarding the above games.', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842350109/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T21:17:44.679741	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Hibyehello', 'action': 'created', 'id': 13991, 'title': "Jit: Emit Branch Watch code only if it's enabled", 'url': 'https://github.com/dolphin-emu/dolphin/pull/13991#issuecomment-3837468646', 'safe_author': False, 'body': 'Tested on an M4 MacBook with no problems', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13991', 'id': 3485022552, 'node_id': 'PR_kwDOALCn2M6sJ1qq', 'number': 13991, 'title': "Jit: Emit Branch Watch code only if it's enabled", 'user': {'login': 'SuperSamus', 'id': 40663462, 'node_id': 'MDQ6VXNlcjQwNjYzNDYy', 'avatar_url': 'https://avatars.githubusercontent.com/u/40663462?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/SuperSamus', 'html_url': 'https://github.com/SuperSamus', 'followers_url': 'https://api.github.com/users/SuperSamus/followers', 'following_url': 'https://api.github.com/users/SuperSamus/following{/other_user}', 'gists_url': 'https://api.github.com/users/SuperSamus/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/SuperSamus/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/SuperSamus/subscriptions', 'organizations_url': 'https://api.github.com/users/SuperSamus/orgs', 'repos_url': 'https://api.github.com/users/SuperSamus/repos', 'events_url': 'https://api.github.com/users/SuperSamus/events{/privacy}', 'received_events_url': 'https://api.github.com/users/SuperSamus/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 5, 'created_at': '2025-10-05T17:58:27Z', 'updated_at': '2026-02-02T21:17:42Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13991', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13991', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13991.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13991.patch', 'merged_at': None}, 'body': 'JIT code related to Branch Watch was emitted if the debugging UI was active: the emitted code would dynamically check whether Branch Watch is active.\r\nHowever, this causes two problems:\r\n1. It decreases performance by just having the debugging UI enabled*\r\n2. It clutters the host assembly in the JIT tab, making it harder to read (unaware readers will wonder what these instructions are for)\r\n\r\nWith this PR, code related to Branch Watch is emitted only if Branch Watch itself is active, fixing the issues above.\r\nThe JIT cache will now be wiped whenever the feature is toggled, causing a slight stutter. However, this isn\'t the kind of feature that is toggled over and over, so IMO it is an acceptable trade-off.\r\n\r\nARM64 is untested.\r\n\r\n#### A note about the debugging UI\r\n\r\nSo, you might wonder what the asterisk is about. It\'s not that the PR doesn\'t improve performance with the debug UI on, because it does.\r\nThe problem is that the performance is still worse than with the debugging UI disabled. I\'m not sure why.[^1]\r\nEven though this performance decrease is intended, *this downside isn\'t communicated anywhere in Dolphin*, and it\'s very possible for someone to unawarely leave the option enabled. (Hi.)\r\nSo... that should be communicated, but where? Only the tooltip of the option, or even in the option title itself `(disables optimizations)`? (Plus the help text in the command line for the `-d` argument.)\r\n\r\nI\'ll leave the discussion for this and on whether to merge the PR (IMO, still worthy due to point 2) to the jury.\r\n\r\n[^1]: I already tested that "reverting" [the disabled BLR optimization](https://github.com/dolphin-emu/dolphin/blob/70bd0943a7a271606367ac129eb39cbdcd659e42/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp#L153-L154) or [the extra checks in the main loop meant for the memory breakpoints](https://github.com/dolphin-emu/dolphin/blob/70bd0943a7a271606367ac129eb39cbdcd659e42/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp#L99-L104) changes basically nothing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3837468646', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13991#issuecomment-3837468646', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991', 'id': 3837468646, 'node_id': 'IC_kwDOALCn2M7kux_m', 'user': {'login': 'Hibyehello', 'id': 36666883, 'node_id': 'MDQ6VXNlcjM2NjY2ODgz', 'avatar_url': 'https://avatars.githubusercontent.com/u/36666883?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Hibyehello', 'html_url': 'https://github.com/Hibyehello', 'followers_url': 'https://api.github.com/users/Hibyehello/followers', 'following_url': 'https://api.github.com/users/Hibyehello/following{/other_user}', 'gists_url': 'https://api.github.com/users/Hibyehello/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Hibyehello/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Hibyehello/subscriptions', 'organizations_url': 'https://api.github.com/users/Hibyehello/orgs', 'repos_url': 'https://api.github.com/users/Hibyehello/repos', 'events_url': 'https://api.github.com/users/Hibyehello/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Hibyehello/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T21:17:41Z', 'updated_at': '2026-02-02T21:17:41Z', 'body': 'Tested on an M4 MacBook with no problems', 'author_association': 'CONTRIBUTOR', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3837468646/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T20:05:56Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14576, 'watchers_count': 14576, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14576, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Hibyehello', 'id': 36666883, 'node_id': 'MDQ6VXNlcjM2NjY2ODgz', 'avatar_url': 'https://avatars.githubusercontent.com/u/36666883?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Hibyehello', 'html_url': 'https://github.com/Hibyehello', 'followers_url': 'https://api.github.com/users/Hibyehello/followers', 'following_url': 'https://api.github.com/users/Hibyehello/following{/other_user}', 'gists_url': 'https://api.github.com/users/Hibyehello/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Hibyehello/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Hibyehello/subscriptions', 'organizations_url': 'https://api.github.com/users/Hibyehello/orgs', 'repos_url': 'https://api.github.com/users/Hibyehello/repos', 'events_url': 'https://api.github.com/users/Hibyehello/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Hibyehello/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T14:13:26.355641	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'dolphin-ci[bot]', 'action': 'deleted', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833918915', 'safe_author': False, 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|vk-lin-mesa|\n|-|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|[🔍 diff](https://fifo.ci/compare/15469060-15426780/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|[🔍 diff](https://fifo.ci/compare/15469081-15426801/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|[🔍 diff](https://fifo.ci/compare/15469098-15426818/)|\n|aeon-charge-attack|-|[🔍 diff](https://fifo.ci/compare/15469066-15426786/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|[🔍 diff](https://fifo.ci/compare/15469103-15426823/)|\n|chibi-robo-fastdepth|-|[🔍 diff](https://fifo.ci/compare/15469042-15426762/)|\n|chibi-robo-zfighting|-|[🔍 diff](https://fifo.ci/compare/15469008-15426728/)|\n|custom-brawl-char|-|[🔍 diff](https://fifo.ci/compare/15469068-15426788/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|[🔍 diff](https://fifo.ci/compare/15469120-15426840/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|[🔍 diff](https://fifo.ci/compare/15469074-15426794/)|\n|fortune-street|-|[🔍 diff](https://fifo.ci/compare/15469027-15426747/)|\n|fortune-street-white-box|-|[🔍 diff](https://fifo.ci/compare/15469029-15426749/)|\n|hotwheels-shadows|-|[🔍 diff](https://fifo.ci/compare/15469142-15426862/)|\n|jd2-fmv|-|[🔍 diff](https://fifo.ci/compare/15469111-15426831/)|\n|jj-awae-mirrored|-|[🔍 diff](https://fifo.ci/compare/15469102-15426822/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|[🔍 diff](https://fifo.ci/compare/15469129-15426849/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|[🔍 diff](https://fifo.ci/compare/15469016-15426736/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|[🔍 diff](https://fifo.ci/compare/15469100-15426820/)|\n|mario-baseball-shadows|-|[🔍 diff](https://fifo.ci/compare/15469095-15426815/)|\n|mario-golf-oob|-|[🔍 diff](https://fifo.ci/compare/15469126-15426846/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|[🔍 diff](https://fifo.ci/compare/15469141-15426861/)|\n|mario-sluggers-bar|-|[🔍 diff](https://fifo.ci/compare/15469032-15426752/)|\n|mario-tennis-menu|-|[🔍 diff](https://fifo.ci/compare/15469031-15426751/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|[🔍 diff](https://fifo.ci/compare/15469036-15426756/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|[🔍 diff](https://fifo.ci/compare/15469082-15426802/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|[🔍 diff](https://fifo.ci/compare/15469051-15426771/)|\n|mkdd-efb|-|[🔍 diff](https://fifo.ci/compare/15469019-15426739/)|\n|mkw-bridge|-|[🔍 diff](https://fifo.ci/compare/15469110-15426830/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|[🔍 diff](https://fifo.ci/compare/15469105-15426825/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|[🔍 diff](https://fifo.ci/compare/15469097-15426817/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|[🔍 diff](https://fifo.ci/compare/15469034-15426754/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|[🔍 diff](https://fifo.ci/compare/15469057-15426777/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|[🔍 diff](https://fifo.ci/compare/15469078-15426798/)|\n|nfsu-purplerect|-|[🔍 diff](https://fifo.ci/compare/15469021-15426741/)|\n|nfsu-reflections|-|[🔍 diff](https://fifo.ci/compare/15469011-15426731/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|[🔍 diff](https://fifo.ci/compare/15469106-15426826/)|\n|nsmbw-coins|-|[🔍 diff](https://fifo.ci/compare/15469064-15426784/)|\n|nsmbw-intro|-|[🔍 diff](https://fifo.ci/compare/15469013-15426733/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|[🔍 diff](https://fifo.ci/compare/15469124-15426844/)|\n|pm-hc-jp|-|[🔍 diff](https://fifo.ci/compare/15469059-15426779/)|\n|quake-gx|-|[🔍 diff](https://fifo.ci/compare/15469130-15426850/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|[🔍 diff](https://fifo.ci/compare/15469039-15426759/)|\n|rs2-zfreeze|-|[🔍 diff](https://fifo.ci/compare/15469022-15426742/)|\n|rs3-bumpmapping|-|[🔍 diff](https://fifo.ci/compare/15469089-15426809/)|\n|sadx-ui|-|[🔍 diff](https://fifo.ci/compare/15469041-15426761/)|\n|sfa-shadows|-|[🔍 diff](https://fifo.ci/compare/15469085-15426805/)|\n|shadow-eyes|-|[🔍 diff](https://fifo.ci/compare/15469116-15426836/)|\n|smb-mirror|-|[🔍 diff](https://fifo.ci/compare/15469109-15426829/)|\n|smg-marioeyes|-|[🔍 diff](https://fifo.ci/compare/15469023-15426743/)|\n|smg-mmg|-|[🔍 diff](https://fifo.ci/compare/15469119-15426839/)|\n|sonic-riders-blur|-|[🔍 diff](https://fifo.ci/compare/15469094-15426814/)|\n|sonic-riders-zg-4p|-|[🔍 diff](https://fifo.ci/compare/15469121-15426841/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|[🔍 diff](https://fifo.ci/compare/15469063-15426783/)|\n|ssbb-mod-lloyd|-|[🔍 diff](https://fifo.ci/compare/15469080-15426800/)|\n|ssbm-pointsize|-|[🔍 diff](https://fifo.ci/compare/15469028-15426748/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|[🔍 diff](https://fifo.ci/compare/15469084-15426804/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|[🔍 diff](https://fifo.ci/compare/15469043-15426763/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|[🔍 diff](https://fifo.ci/compare/15469024-15426744/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|[🔍 diff](https://fifo.ci/compare/15469069-15426789/)|\n|ztp-grass|-|[🔍 diff](https://fifo.ci/compare/15469025-15426745/)|\n|zww-waves|-|[🔍 diff](https://fifo.ci/compare/15469045-15426765/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'raw': {'action': 'deleted', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 7, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T13:47:45Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833918915', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833918915', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3833918915, 'node_id': 'IC_kwDOALCn2M7khPXD', 'user': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T09:18:14Z', 'updated_at': '2026-02-02T09:18:14Z', 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|vk-lin-mesa|\n|-|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|[🔍 diff](https://fifo.ci/compare/15469060-15426780/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|[🔍 diff](https://fifo.ci/compare/15469081-15426801/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|[🔍 diff](https://fifo.ci/compare/15469098-15426818/)|\n|aeon-charge-attack|-|[🔍 diff](https://fifo.ci/compare/15469066-15426786/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|[🔍 diff](https://fifo.ci/compare/15469103-15426823/)|\n|chibi-robo-fastdepth|-|[🔍 diff](https://fifo.ci/compare/15469042-15426762/)|\n|chibi-robo-zfighting|-|[🔍 diff](https://fifo.ci/compare/15469008-15426728/)|\n|custom-brawl-char|-|[🔍 diff](https://fifo.ci/compare/15469068-15426788/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|[🔍 diff](https://fifo.ci/compare/15469120-15426840/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|[🔍 diff](https://fifo.ci/compare/15469074-15426794/)|\n|fortune-street|-|[🔍 diff](https://fifo.ci/compare/15469027-15426747/)|\n|fortune-street-white-box|-|[🔍 diff](https://fifo.ci/compare/15469029-15426749/)|\n|hotwheels-shadows|-|[🔍 diff](https://fifo.ci/compare/15469142-15426862/)|\n|jd2-fmv|-|[🔍 diff](https://fifo.ci/compare/15469111-15426831/)|\n|jj-awae-mirrored|-|[🔍 diff](https://fifo.ci/compare/15469102-15426822/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|[🔍 diff](https://fifo.ci/compare/15469129-15426849/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|[🔍 diff](https://fifo.ci/compare/15469016-15426736/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|[🔍 diff](https://fifo.ci/compare/15469100-15426820/)|\n|mario-baseball-shadows|-|[🔍 diff](https://fifo.ci/compare/15469095-15426815/)|\n|mario-golf-oob|-|[🔍 diff](https://fifo.ci/compare/15469126-15426846/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|[🔍 diff](https://fifo.ci/compare/15469141-15426861/)|\n|mario-sluggers-bar|-|[🔍 diff](https://fifo.ci/compare/15469032-15426752/)|\n|mario-tennis-menu|-|[🔍 diff](https://fifo.ci/compare/15469031-15426751/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|[🔍 diff](https://fifo.ci/compare/15469036-15426756/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|[🔍 diff](https://fifo.ci/compare/15469082-15426802/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|[🔍 diff](https://fifo.ci/compare/15469051-15426771/)|\n|mkdd-efb|-|[🔍 diff](https://fifo.ci/compare/15469019-15426739/)|\n|mkw-bridge|-|[🔍 diff](https://fifo.ci/compare/15469110-15426830/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|[🔍 diff](https://fifo.ci/compare/15469105-15426825/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|[🔍 diff](https://fifo.ci/compare/15469097-15426817/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|[🔍 diff](https://fifo.ci/compare/15469034-15426754/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|[🔍 diff](https://fifo.ci/compare/15469057-15426777/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|[🔍 diff](https://fifo.ci/compare/15469078-15426798/)|\n|nfsu-purplerect|-|[🔍 diff](https://fifo.ci/compare/15469021-15426741/)|\n|nfsu-reflections|-|[🔍 diff](https://fifo.ci/compare/15469011-15426731/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|[🔍 diff](https://fifo.ci/compare/15469106-15426826/)|\n|nsmbw-coins|-|[🔍 diff](https://fifo.ci/compare/15469064-15426784/)|\n|nsmbw-intro|-|[🔍 diff](https://fifo.ci/compare/15469013-15426733/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|[🔍 diff](https://fifo.ci/compare/15469124-15426844/)|\n|pm-hc-jp|-|[🔍 diff](https://fifo.ci/compare/15469059-15426779/)|\n|quake-gx|-|[🔍 diff](https://fifo.ci/compare/15469130-15426850/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|[🔍 diff](https://fifo.ci/compare/15469039-15426759/)|\n|rs2-zfreeze|-|[🔍 diff](https://fifo.ci/compare/15469022-15426742/)|\n|rs3-bumpmapping|-|[🔍 diff](https://fifo.ci/compare/15469089-15426809/)|\n|sadx-ui|-|[🔍 diff](https://fifo.ci/compare/15469041-15426761/)|\n|sfa-shadows|-|[🔍 diff](https://fifo.ci/compare/15469085-15426805/)|\n|shadow-eyes|-|[🔍 diff](https://fifo.ci/compare/15469116-15426836/)|\n|smb-mirror|-|[🔍 diff](https://fifo.ci/compare/15469109-15426829/)|\n|smg-marioeyes|-|[🔍 diff](https://fifo.ci/compare/15469023-15426743/)|\n|smg-mmg|-|[🔍 diff](https://fifo.ci/compare/15469119-15426839/)|\n|sonic-riders-blur|-|[🔍 diff](https://fifo.ci/compare/15469094-15426814/)|\n|sonic-riders-zg-4p|-|[🔍 diff](https://fifo.ci/compare/15469121-15426841/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|[🔍 diff](https://fifo.ci/compare/15469063-15426783/)|\n|ssbb-mod-lloyd|-|[🔍 diff](https://fifo.ci/compare/15469080-15426800/)|\n|ssbm-pointsize|-|[🔍 diff](https://fifo.ci/compare/15469028-15426748/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|[🔍 diff](https://fifo.ci/compare/15469084-15426804/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|[🔍 diff](https://fifo.ci/compare/15469043-15426763/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|[🔍 diff](https://fifo.ci/compare/15469024-15426744/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|[🔍 diff](https://fifo.ci/compare/15469069-15426789/)|\n|ztp-grass|-|[🔍 diff](https://fifo.ci/compare/15469025-15426745/)|\n|zww-waves|-|[🔍 diff](https://fifo.ci/compare/15469045-15426765/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833918915/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': {'id': 49947, 'client_id': 'Iv1.1fcaea7644d8b727', 'slug': 'dolphin-ci', 'node_id': 'MDM6QXBwNDk5NDc=', 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'name': 'Dolphin CI', 'description': 'Continuous Integration setup for [dolphin-emu.org](https://dolphin-emu.org/).', 'external_url': 'https://github.com/dolphin-emu', 'html_url': 'https://github.com/apps/dolphin-ci', 'created_at': '2019-12-26T22:26:07Z', 'updated_at': '2019-12-26T22:33:19Z', 'permissions': {'checks': 'write', 'contents': 'read', 'issues': 'write', 'members': 'read', 'metadata': 'read', 'pull_requests': 'write', 'statuses': 'write'}, 'events': ['check_run', 'commit_comment', 'issue_comment', 'pull_request', 'pull_request_review', 'pull_request_review_comment', 'push']}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T13:00:07Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14575, 'watchers_count': 14575, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14575, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T13:47:45.794718	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'deleted', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3835225593', 'safe_author': True, 'body': '@dolphin-emu-bot rebuild', 'raw': {'action': 'deleted', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 8, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T13:47:33Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3835225593', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3835225593', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3835225593, 'node_id': 'IC_kwDOALCn2M7kmOX5', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T13:47:32Z', 'updated_at': '2026-02-02T13:47:32Z', 'body': '@dolphin-emu-bot rebuild', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3835225593/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T13:00:07Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14575, 'watchers_count': 14575, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14575, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T13:47:35.017369	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'created', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3835225593', 'safe_author': True, 'body': '@dolphin-emu-bot rebuild', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 8, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T13:47:33Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3835225593', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3835225593', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3835225593, 'node_id': 'IC_kwDOALCn2M7kmOX5', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T13:47:32Z', 'updated_at': '2026-02-02T13:47:32Z', 'body': '@dolphin-emu-bot rebuild', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3835225593/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T13:00:07Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14575, 'watchers_count': 14575, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14575, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T09:18:16.840373	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'dolphin-ci[bot]', 'action': 'created', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833918915', 'safe_author': False, 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|vk-lin-mesa|\n|-|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|[🔍 diff](https://fifo.ci/compare/15469060-15426780/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|[🔍 diff](https://fifo.ci/compare/15469081-15426801/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|[🔍 diff](https://fifo.ci/compare/15469098-15426818/)|\n|aeon-charge-attack|-|[🔍 diff](https://fifo.ci/compare/15469066-15426786/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|[🔍 diff](https://fifo.ci/compare/15469103-15426823/)|\n|chibi-robo-fastdepth|-|[🔍 diff](https://fifo.ci/compare/15469042-15426762/)|\n|chibi-robo-zfighting|-|[🔍 diff](https://fifo.ci/compare/15469008-15426728/)|\n|custom-brawl-char|-|[🔍 diff](https://fifo.ci/compare/15469068-15426788/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|[🔍 diff](https://fifo.ci/compare/15469120-15426840/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|[🔍 diff](https://fifo.ci/compare/15469074-15426794/)|\n|fortune-street|-|[🔍 diff](https://fifo.ci/compare/15469027-15426747/)|\n|fortune-street-white-box|-|[🔍 diff](https://fifo.ci/compare/15469029-15426749/)|\n|hotwheels-shadows|-|[🔍 diff](https://fifo.ci/compare/15469142-15426862/)|\n|jd2-fmv|-|[🔍 diff](https://fifo.ci/compare/15469111-15426831/)|\n|jj-awae-mirrored|-|[🔍 diff](https://fifo.ci/compare/15469102-15426822/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|[🔍 diff](https://fifo.ci/compare/15469129-15426849/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|[🔍 diff](https://fifo.ci/compare/15469016-15426736/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|[🔍 diff](https://fifo.ci/compare/15469100-15426820/)|\n|mario-baseball-shadows|-|[🔍 diff](https://fifo.ci/compare/15469095-15426815/)|\n|mario-golf-oob|-|[🔍 diff](https://fifo.ci/compare/15469126-15426846/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|[🔍 diff](https://fifo.ci/compare/15469141-15426861/)|\n|mario-sluggers-bar|-|[🔍 diff](https://fifo.ci/compare/15469032-15426752/)|\n|mario-tennis-menu|-|[🔍 diff](https://fifo.ci/compare/15469031-15426751/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|[🔍 diff](https://fifo.ci/compare/15469036-15426756/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|[🔍 diff](https://fifo.ci/compare/15469082-15426802/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|[🔍 diff](https://fifo.ci/compare/15469051-15426771/)|\n|mkdd-efb|-|[🔍 diff](https://fifo.ci/compare/15469019-15426739/)|\n|mkw-bridge|-|[🔍 diff](https://fifo.ci/compare/15469110-15426830/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|[🔍 diff](https://fifo.ci/compare/15469105-15426825/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|[🔍 diff](https://fifo.ci/compare/15469097-15426817/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|[🔍 diff](https://fifo.ci/compare/15469034-15426754/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|[🔍 diff](https://fifo.ci/compare/15469057-15426777/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|[🔍 diff](https://fifo.ci/compare/15469078-15426798/)|\n|nfsu-purplerect|-|[🔍 diff](https://fifo.ci/compare/15469021-15426741/)|\n|nfsu-reflections|-|[🔍 diff](https://fifo.ci/compare/15469011-15426731/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|[🔍 diff](https://fifo.ci/compare/15469106-15426826/)|\n|nsmbw-coins|-|[🔍 diff](https://fifo.ci/compare/15469064-15426784/)|\n|nsmbw-intro|-|[🔍 diff](https://fifo.ci/compare/15469013-15426733/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|[🔍 diff](https://fifo.ci/compare/15469124-15426844/)|\n|pm-hc-jp|-|[🔍 diff](https://fifo.ci/compare/15469059-15426779/)|\n|quake-gx|-|[🔍 diff](https://fifo.ci/compare/15469130-15426850/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|[🔍 diff](https://fifo.ci/compare/15469039-15426759/)|\n|rs2-zfreeze|-|[🔍 diff](https://fifo.ci/compare/15469022-15426742/)|\n|rs3-bumpmapping|-|[🔍 diff](https://fifo.ci/compare/15469089-15426809/)|\n|sadx-ui|-|[🔍 diff](https://fifo.ci/compare/15469041-15426761/)|\n|sfa-shadows|-|[🔍 diff](https://fifo.ci/compare/15469085-15426805/)|\n|shadow-eyes|-|[🔍 diff](https://fifo.ci/compare/15469116-15426836/)|\n|smb-mirror|-|[🔍 diff](https://fifo.ci/compare/15469109-15426829/)|\n|smg-marioeyes|-|[🔍 diff](https://fifo.ci/compare/15469023-15426743/)|\n|smg-mmg|-|[🔍 diff](https://fifo.ci/compare/15469119-15426839/)|\n|sonic-riders-blur|-|[🔍 diff](https://fifo.ci/compare/15469094-15426814/)|\n|sonic-riders-zg-4p|-|[🔍 diff](https://fifo.ci/compare/15469121-15426841/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|[🔍 diff](https://fifo.ci/compare/15469063-15426783/)|\n|ssbb-mod-lloyd|-|[🔍 diff](https://fifo.ci/compare/15469080-15426800/)|\n|ssbm-pointsize|-|[🔍 diff](https://fifo.ci/compare/15469028-15426748/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|[🔍 diff](https://fifo.ci/compare/15469084-15426804/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|[🔍 diff](https://fifo.ci/compare/15469043-15426763/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|[🔍 diff](https://fifo.ci/compare/15469024-15426744/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|[🔍 diff](https://fifo.ci/compare/15469069-15426789/)|\n|ztp-grass|-|[🔍 diff](https://fifo.ci/compare/15469025-15426745/)|\n|zww-waves|-|[🔍 diff](https://fifo.ci/compare/15469045-15426765/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 7, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T09:18:14Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833918915', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833918915', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3833918915, 'node_id': 'IC_kwDOALCn2M7khPXD', 'user': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T09:18:14Z', 'updated_at': '2026-02-02T09:18:14Z', 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|vk-lin-mesa|\n|-|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|[🔍 diff](https://fifo.ci/compare/15469060-15426780/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|[🔍 diff](https://fifo.ci/compare/15469081-15426801/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|[🔍 diff](https://fifo.ci/compare/15469098-15426818/)|\n|aeon-charge-attack|-|[🔍 diff](https://fifo.ci/compare/15469066-15426786/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|[🔍 diff](https://fifo.ci/compare/15469103-15426823/)|\n|chibi-robo-fastdepth|-|[🔍 diff](https://fifo.ci/compare/15469042-15426762/)|\n|chibi-robo-zfighting|-|[🔍 diff](https://fifo.ci/compare/15469008-15426728/)|\n|custom-brawl-char|-|[🔍 diff](https://fifo.ci/compare/15469068-15426788/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|[🔍 diff](https://fifo.ci/compare/15469120-15426840/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|[🔍 diff](https://fifo.ci/compare/15469074-15426794/)|\n|fortune-street|-|[🔍 diff](https://fifo.ci/compare/15469027-15426747/)|\n|fortune-street-white-box|-|[🔍 diff](https://fifo.ci/compare/15469029-15426749/)|\n|hotwheels-shadows|-|[🔍 diff](https://fifo.ci/compare/15469142-15426862/)|\n|jd2-fmv|-|[🔍 diff](https://fifo.ci/compare/15469111-15426831/)|\n|jj-awae-mirrored|-|[🔍 diff](https://fifo.ci/compare/15469102-15426822/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|[🔍 diff](https://fifo.ci/compare/15469129-15426849/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|[🔍 diff](https://fifo.ci/compare/15469016-15426736/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|[🔍 diff](https://fifo.ci/compare/15469100-15426820/)|\n|mario-baseball-shadows|-|[🔍 diff](https://fifo.ci/compare/15469095-15426815/)|\n|mario-golf-oob|-|[🔍 diff](https://fifo.ci/compare/15469126-15426846/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|[🔍 diff](https://fifo.ci/compare/15469141-15426861/)|\n|mario-sluggers-bar|-|[🔍 diff](https://fifo.ci/compare/15469032-15426752/)|\n|mario-tennis-menu|-|[🔍 diff](https://fifo.ci/compare/15469031-15426751/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|[🔍 diff](https://fifo.ci/compare/15469036-15426756/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|[🔍 diff](https://fifo.ci/compare/15469082-15426802/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|[🔍 diff](https://fifo.ci/compare/15469051-15426771/)|\n|mkdd-efb|-|[🔍 diff](https://fifo.ci/compare/15469019-15426739/)|\n|mkw-bridge|-|[🔍 diff](https://fifo.ci/compare/15469110-15426830/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|[🔍 diff](https://fifo.ci/compare/15469105-15426825/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|[🔍 diff](https://fifo.ci/compare/15469097-15426817/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|[🔍 diff](https://fifo.ci/compare/15469034-15426754/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|[🔍 diff](https://fifo.ci/compare/15469057-15426777/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|[🔍 diff](https://fifo.ci/compare/15469078-15426798/)|\n|nfsu-purplerect|-|[🔍 diff](https://fifo.ci/compare/15469021-15426741/)|\n|nfsu-reflections|-|[🔍 diff](https://fifo.ci/compare/15469011-15426731/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|[🔍 diff](https://fifo.ci/compare/15469106-15426826/)|\n|nsmbw-coins|-|[🔍 diff](https://fifo.ci/compare/15469064-15426784/)|\n|nsmbw-intro|-|[🔍 diff](https://fifo.ci/compare/15469013-15426733/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|[🔍 diff](https://fifo.ci/compare/15469124-15426844/)|\n|pm-hc-jp|-|[🔍 diff](https://fifo.ci/compare/15469059-15426779/)|\n|quake-gx|-|[🔍 diff](https://fifo.ci/compare/15469130-15426850/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|[🔍 diff](https://fifo.ci/compare/15469039-15426759/)|\n|rs2-zfreeze|-|[🔍 diff](https://fifo.ci/compare/15469022-15426742/)|\n|rs3-bumpmapping|-|[🔍 diff](https://fifo.ci/compare/15469089-15426809/)|\n|sadx-ui|-|[🔍 diff](https://fifo.ci/compare/15469041-15426761/)|\n|sfa-shadows|-|[🔍 diff](https://fifo.ci/compare/15469085-15426805/)|\n|shadow-eyes|-|[🔍 diff](https://fifo.ci/compare/15469116-15426836/)|\n|smb-mirror|-|[🔍 diff](https://fifo.ci/compare/15469109-15426829/)|\n|smg-marioeyes|-|[🔍 diff](https://fifo.ci/compare/15469023-15426743/)|\n|smg-mmg|-|[🔍 diff](https://fifo.ci/compare/15469119-15426839/)|\n|sonic-riders-blur|-|[🔍 diff](https://fifo.ci/compare/15469094-15426814/)|\n|sonic-riders-zg-4p|-|[🔍 diff](https://fifo.ci/compare/15469121-15426841/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|[🔍 diff](https://fifo.ci/compare/15469063-15426783/)|\n|ssbb-mod-lloyd|-|[🔍 diff](https://fifo.ci/compare/15469080-15426800/)|\n|ssbm-pointsize|-|[🔍 diff](https://fifo.ci/compare/15469028-15426748/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|[🔍 diff](https://fifo.ci/compare/15469084-15426804/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|[🔍 diff](https://fifo.ci/compare/15469043-15426763/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|[🔍 diff](https://fifo.ci/compare/15469024-15426744/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|[🔍 diff](https://fifo.ci/compare/15469069-15426789/)|\n|ztp-grass|-|[🔍 diff](https://fifo.ci/compare/15469025-15426745/)|\n|zww-waves|-|[🔍 diff](https://fifo.ci/compare/15469045-15426765/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833918915/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': {'id': 49947, 'client_id': 'Iv1.1fcaea7644d8b727', 'slug': 'dolphin-ci', 'node_id': 'MDM6QXBwNDk5NDc=', 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'name': 'Dolphin CI', 'description': 'Continuous Integration setup for [dolphin-emu.org](https://dolphin-emu.org/).', 'external_url': 'https://github.com/dolphin-emu', 'html_url': 'https://github.com/apps/dolphin-ci', 'created_at': '2019-12-26T22:26:07Z', 'updated_at': '2019-12-26T22:33:19Z', 'permissions': {'checks': 'write', 'contents': 'read', 'issues': 'write', 'members': 'read', 'metadata': 'read', 'pull_requests': 'write', 'statuses': 'write'}, 'events': ['check_run', 'commit_comment', 'issue_comment', 'pull_request', 'pull_request_review', 'pull_request_review_comment', 'push']}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T08:38:08Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14574, 'watchers_count': 14574, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14574, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T09:18:14.496051	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'dolphin-ci[bot]', 'action': 'deleted', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833874415', 'safe_author': False, 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|\n|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'raw': {'action': 'deleted', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 7, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T09:07:59Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833874415', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833874415', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3833874415, 'node_id': 'IC_kwDOALCn2M7khEfv', 'user': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T09:07:59Z', 'updated_at': '2026-02-02T09:07:59Z', 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|\n|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833874415/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': {'id': 49947, 'client_id': 'Iv1.1fcaea7644d8b727', 'slug': 'dolphin-ci', 'node_id': 'MDM6QXBwNDk5NDc=', 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'name': 'Dolphin CI', 'description': 'Continuous Integration setup for [dolphin-emu.org](https://dolphin-emu.org/).', 'external_url': 'https://github.com/dolphin-emu', 'html_url': 'https://github.com/apps/dolphin-ci', 'created_at': '2019-12-26T22:26:07Z', 'updated_at': '2019-12-26T22:33:19Z', 'permissions': {'checks': 'write', 'contents': 'read', 'issues': 'write', 'members': 'read', 'metadata': 'read', 'pull_requests': 'write', 'statuses': 'write'}, 'events': ['check_run', 'commit_comment', 'issue_comment', 'pull_request', 'pull_request_review', 'pull_request_review_comment', 'push']}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T08:38:08Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14574, 'watchers_count': 14574, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14574, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T09:08:02.093505	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'dolphin-ci[bot]', 'action': 'created', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833874415', 'safe_author': False, 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|\n|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 7, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T09:07:59Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833874415', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833874415', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3833874415, 'node_id': 'IC_kwDOALCn2M7khEfv', 'user': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T09:07:59Z', 'updated_at': '2026-02-02T09:07:59Z', 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/512972c1cd4a10111412d619e464fc72a964101e/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|\n|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15468920-15426640/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15468941-15426661/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15468958-15426678/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15468963-15426683/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15468980-15426700/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15468934-15426654/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15468989-15426709/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15468876-15426596/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15468960-15426680/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15469001-15426721/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15468896-15426616/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15468942-15426662/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15468911-15426631/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15468965-15426685/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15468957-15426677/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15468894-15426614/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15468917-15426637/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15468938-15426658/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15468966-15426686/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15468984-15426704/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15468899-15426619/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15468923-15426643/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15468944-15426664/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15468903-15426623/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15468884-15426604/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15468929-15426649/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833874415/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': {'id': 49947, 'client_id': 'Iv1.1fcaea7644d8b727', 'slug': 'dolphin-ci', 'node_id': 'MDM6QXBwNDk5NDc=', 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'name': 'Dolphin CI', 'description': 'Continuous Integration setup for [dolphin-emu.org](https://dolphin-emu.org/).', 'external_url': 'https://github.com/dolphin-emu', 'html_url': 'https://github.com/apps/dolphin-ci', 'created_at': '2019-12-26T22:26:07Z', 'updated_at': '2019-12-26T22:33:19Z', 'permissions': {'checks': 'write', 'contents': 'read', 'issues': 'write', 'members': 'read', 'metadata': 'read', 'pull_requests': 'write', 'statuses': 'write'}, 'events': ['check_run', 'commit_comment', 'issue_comment', 'pull_request', 'pull_request_review', 'pull_request_review_comment', 'push']}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T08:38:08Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14574, 'watchers_count': 14574, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14574, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T08:55:22.738308	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'dolphin-ci[bot]', 'action': 'deleted', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3816809833', 'safe_author': False, 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/c79193a2cb21af931e79f9cca7ab3dd592f9fd34/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|vk-lin-mesa|\n|-|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15456460-15426640/)|[🔍 diff](https://fifo.ci/compare/15456600-15426780/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15456481-15426661/)|[🔍 diff](https://fifo.ci/compare/15456621-15426801/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15456498-15426678/)|[🔍 diff](https://fifo.ci/compare/15456638-15426818/)|\n|aeon-charge-attack|-|[🔍 diff](https://fifo.ci/compare/15456606-15426786/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15456503-15426683/)|[🔍 diff](https://fifo.ci/compare/15456643-15426823/)|\n|chibi-robo-fastdepth|-|[🔍 diff](https://fifo.ci/compare/15456582-15426762/)|\n|chibi-robo-zfighting|-|[🔍 diff](https://fifo.ci/compare/15456548-15426728/)|\n|custom-brawl-char|-|[🔍 diff](https://fifo.ci/compare/15456608-15426788/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15456520-15426700/)|[🔍 diff](https://fifo.ci/compare/15456660-15426840/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15456474-15426654/)|[🔍 diff](https://fifo.ci/compare/15456614-15426794/)|\n|fortune-street|-|[🔍 diff](https://fifo.ci/compare/15456567-15426747/)|\n|fortune-street-white-box|-|[🔍 diff](https://fifo.ci/compare/15456569-15426749/)|\n|hotwheels-shadows|-|[🔍 diff](https://fifo.ci/compare/15456682-15426862/)|\n|jd2-fmv|-|[🔍 diff](https://fifo.ci/compare/15456651-15426831/)|\n|jj-awae-mirrored|-|[🔍 diff](https://fifo.ci/compare/15456642-15426822/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15456529-15426709/)|[🔍 diff](https://fifo.ci/compare/15456669-15426849/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15456416-15426596/)|[🔍 diff](https://fifo.ci/compare/15456556-15426736/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15456500-15426680/)|[🔍 diff](https://fifo.ci/compare/15456640-15426820/)|\n|mario-baseball-shadows|-|[🔍 diff](https://fifo.ci/compare/15456635-15426815/)|\n|mario-golf-oob|-|[🔍 diff](https://fifo.ci/compare/15456666-15426846/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15456541-15426721/)|[🔍 diff](https://fifo.ci/compare/15456681-15426861/)|\n|mario-sluggers-bar|-|[🔍 diff](https://fifo.ci/compare/15456572-15426752/)|\n|mario-tennis-menu|-|[🔍 diff](https://fifo.ci/compare/15456571-15426751/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15456436-15426616/)|[🔍 diff](https://fifo.ci/compare/15456576-15426756/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15456482-15426662/)|[🔍 diff](https://fifo.ci/compare/15456622-15426802/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15456451-15426631/)|[🔍 diff](https://fifo.ci/compare/15456591-15426771/)|\n|mkdd-efb|-|[🔍 diff](https://fifo.ci/compare/15456559-15426739/)|\n|mkw-bridge|-|[🔍 diff](https://fifo.ci/compare/15456650-15426830/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15456505-15426685/)|[🔍 diff](https://fifo.ci/compare/15456645-15426825/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15456497-15426677/)|[🔍 diff](https://fifo.ci/compare/15456637-15426817/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15456434-15426614/)|[🔍 diff](https://fifo.ci/compare/15456574-15426754/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15456457-15426637/)|[🔍 diff](https://fifo.ci/compare/15456597-15426777/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15456478-15426658/)|[🔍 diff](https://fifo.ci/compare/15456618-15426798/)|\n|nfsu-purplerect|-|[🔍 diff](https://fifo.ci/compare/15456561-15426741/)|\n|nfsu-reflections|-|[🔍 diff](https://fifo.ci/compare/15456551-15426731/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15456506-15426686/)|[🔍 diff](https://fifo.ci/compare/15456646-15426826/)|\n|nsmbw-coins|-|[🔍 diff](https://fifo.ci/compare/15456604-15426784/)|\n|nsmbw-intro|-|[🔍 diff](https://fifo.ci/compare/15456553-15426733/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15456524-15426704/)|[🔍 diff](https://fifo.ci/compare/15456664-15426844/)|\n|pm-hc-jp|-|[🔍 diff](https://fifo.ci/compare/15456599-15426779/)|\n|quake-gx|-|[🔍 diff](https://fifo.ci/compare/15456670-15426850/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15456439-15426619/)|[🔍 diff](https://fifo.ci/compare/15456579-15426759/)|\n|rs2-zfreeze|-|[🔍 diff](https://fifo.ci/compare/15456562-15426742/)|\n|rs3-bumpmapping|-|[🔍 diff](https://fifo.ci/compare/15456629-15426809/)|\n|sadx-ui|-|[🔍 diff](https://fifo.ci/compare/15456581-15426761/)|\n|sfa-shadows|-|[🔍 diff](https://fifo.ci/compare/15456625-15426805/)|\n|shadow-eyes|-|[🔍 diff](https://fifo.ci/compare/15456656-15426836/)|\n|smb-mirror|-|[🔍 diff](https://fifo.ci/compare/15456649-15426829/)|\n|smg-marioeyes|-|[🔍 diff](https://fifo.ci/compare/15456563-15426743/)|\n|smg-mmg|-|[🔍 diff](https://fifo.ci/compare/15456659-15426839/)|\n|sonic-riders-blur|-|[🔍 diff](https://fifo.ci/compare/15456634-15426814/)|\n|sonic-riders-zg-4p|-|[🔍 diff](https://fifo.ci/compare/15456661-15426841/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15456463-15426643/)|[🔍 diff](https://fifo.ci/compare/15456603-15426783/)|\n|ssbb-mod-lloyd|-|[🔍 diff](https://fifo.ci/compare/15456620-15426800/)|\n|ssbm-pointsize|-|[🔍 diff](https://fifo.ci/compare/15456568-15426748/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15456484-15426664/)|[🔍 diff](https://fifo.ci/compare/15456624-15426804/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15456443-15426623/)|[🔍 diff](https://fifo.ci/compare/15456583-15426763/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15456424-15426604/)|[🔍 diff](https://fifo.ci/compare/15456564-15426744/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15456469-15426649/)|[🔍 diff](https://fifo.ci/compare/15456609-15426789/)|\n|ztp-grass|-|[🔍 diff](https://fifo.ci/compare/15456565-15426745/)|\n|zww-waves|-|[🔍 diff](https://fifo.ci/compare/15456585-15426765/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'raw': {'action': 'deleted', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 7, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T08:27:57Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3816809833', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3816809833', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3816809833, 'node_id': 'IC_kwDOALCn2M7jf-Vp', 'user': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-01-29T10:32:14Z', 'updated_at': '2026-01-29T10:32:14Z', 'body': '[FifoCI](https://fifo.ci/about/) detected that this change impacts graphical             rendering. Here are the [behavior differences](https://fifo.ci/version/c79193a2cb21af931e79f9cca7ab3dd592f9fd34/)             detected by the system:\n\n<details>\n<summary>Detected differences</summary>\n\n||ogl-lin-mesa|vk-lin-mesa|\n|-|-|-|\n|DKCR-Char|[🔍 diff](https://fifo.ci/compare/15456460-15426640/)|[🔍 diff](https://fifo.ci/compare/15456600-15426780/)|\n|DKCR-fast-depth|[🔍 diff](https://fifo.ci/compare/15456481-15426661/)|[🔍 diff](https://fifo.ci/compare/15456621-15426801/)|\n|MaS-LOG-wiimote|[🔍 diff](https://fifo.ci/compare/15456498-15426678/)|[🔍 diff](https://fifo.ci/compare/15456638-15426818/)|\n|aeon-charge-attack|-|[🔍 diff](https://fifo.ci/compare/15456606-15426786/)|\n|burnout2-vehicletextures|[🔍 diff](https://fifo.ci/compare/15456503-15426683/)|[🔍 diff](https://fifo.ci/compare/15456643-15426823/)|\n|chibi-robo-fastdepth|-|[🔍 diff](https://fifo.ci/compare/15456582-15426762/)|\n|chibi-robo-zfighting|-|[🔍 diff](https://fifo.ci/compare/15456548-15426728/)|\n|custom-brawl-char|-|[🔍 diff](https://fifo.ci/compare/15456608-15426788/)|\n|ea-pink|[🔍 diff](https://fifo.ci/compare/15456520-15426700/)|[🔍 diff](https://fifo.ci/compare/15456660-15426840/)|\n|f-zero-rain|[🔍 diff](https://fifo.ci/compare/15456474-15426654/)|[🔍 diff](https://fifo.ci/compare/15456614-15426794/)|\n|fortune-street|-|[🔍 diff](https://fifo.ci/compare/15456567-15426747/)|\n|fortune-street-white-box|-|[🔍 diff](https://fifo.ci/compare/15456569-15426749/)|\n|hotwheels-shadows|-|[🔍 diff](https://fifo.ci/compare/15456682-15426862/)|\n|jd2-fmv|-|[🔍 diff](https://fifo.ci/compare/15456651-15426831/)|\n|jj-awae-mirrored|-|[🔍 diff](https://fifo.ci/compare/15456642-15426822/)|\n|kirby-logicop|[🔍 diff](https://fifo.ci/compare/15456529-15426709/)|[🔍 diff](https://fifo.ci/compare/15456669-15426849/)|\n|kirby-shadows|[🔍 diff](https://fifo.ci/compare/15456416-15426596/)|[🔍 diff](https://fifo.ci/compare/15456556-15426736/)|\n|last-story-shadows|[🔍 diff](https://fifo.ci/compare/15456500-15426680/)|[🔍 diff](https://fifo.ci/compare/15456640-15426820/)|\n|mario-baseball-shadows|-|[🔍 diff](https://fifo.ci/compare/15456635-15426815/)|\n|mario-golf-oob|-|[🔍 diff](https://fifo.ci/compare/15456666-15426846/)|\n|mario-golf-vertex-expansion|[🔍 diff](https://fifo.ci/compare/15456541-15426721/)|[🔍 diff](https://fifo.ci/compare/15456681-15426861/)|\n|mario-sluggers-bar|-|[🔍 diff](https://fifo.ci/compare/15456572-15426752/)|\n|mario-tennis-menu|-|[🔍 diff](https://fifo.ci/compare/15456571-15426751/)|\n|megaman-heat|[🔍 diff](https://fifo.ci/compare/15456436-15426616/)|[🔍 diff](https://fifo.ci/compare/15456576-15426756/)|\n|metroid-visor|[🔍 diff](https://fifo.ci/compare/15456482-15426662/)|[🔍 diff](https://fifo.ci/compare/15456622-15426802/)|\n|milotic-texture|[🔍 diff](https://fifo.ci/compare/15456451-15426631/)|[🔍 diff](https://fifo.ci/compare/15456591-15426771/)|\n|mkdd-efb|-|[🔍 diff](https://fifo.ci/compare/15456559-15426739/)|\n|mkw-bridge|-|[🔍 diff](https://fifo.ci/compare/15456650-15426830/)|\n|mp2-scanner|[🔍 diff](https://fifo.ci/compare/15456505-15426685/)|[🔍 diff](https://fifo.ci/compare/15456645-15426825/)|\n|mp3-bloom|[🔍 diff](https://fifo.ci/compare/15456497-15426677/)|[🔍 diff](https://fifo.ci/compare/15456637-15426817/)|\n|mtennis-zfreeze|[🔍 diff](https://fifo.ci/compare/15456434-15426614/)|[🔍 diff](https://fifo.ci/compare/15456574-15426754/)|\n|nddemo-bumpmapping|[🔍 diff](https://fifo.ci/compare/15456457-15426637/)|[🔍 diff](https://fifo.ci/compare/15456597-15426777/)|\n|nddemo-lighting|[🔍 diff](https://fifo.ci/compare/15456478-15426658/)|[🔍 diff](https://fifo.ci/compare/15456618-15426798/)|\n|nfsu-purplerect|-|[🔍 diff](https://fifo.ci/compare/15456561-15426741/)|\n|nfsu-reflections|-|[🔍 diff](https://fifo.ci/compare/15456551-15426731/)|\n|nhl-slap|[🔍 diff](https://fifo.ci/compare/15456506-15426686/)|[🔍 diff](https://fifo.ci/compare/15456646-15426826/)|\n|nsmbw-coins|-|[🔍 diff](https://fifo.ci/compare/15456604-15426784/)|\n|nsmbw-intro|-|[🔍 diff](https://fifo.ci/compare/15456553-15426733/)|\n|pbr-sfx|[🔍 diff](https://fifo.ci/compare/15456524-15426704/)|[🔍 diff](https://fifo.ci/compare/15456664-15426844/)|\n|pm-hc-jp|-|[🔍 diff](https://fifo.ci/compare/15456599-15426779/)|\n|quake-gx|-|[🔍 diff](https://fifo.ci/compare/15456670-15426850/)|\n|rs2-glass|[🔍 diff](https://fifo.ci/compare/15456439-15426619/)|[🔍 diff](https://fifo.ci/compare/15456579-15426759/)|\n|rs2-zfreeze|-|[🔍 diff](https://fifo.ci/compare/15456562-15426742/)|\n|rs3-bumpmapping|-|[🔍 diff](https://fifo.ci/compare/15456629-15426809/)|\n|sadx-ui|-|[🔍 diff](https://fifo.ci/compare/15456581-15426761/)|\n|sfa-shadows|-|[🔍 diff](https://fifo.ci/compare/15456625-15426805/)|\n|shadow-eyes|-|[🔍 diff](https://fifo.ci/compare/15456656-15426836/)|\n|smb-mirror|-|[🔍 diff](https://fifo.ci/compare/15456649-15426829/)|\n|smg-marioeyes|-|[🔍 diff](https://fifo.ci/compare/15456563-15426743/)|\n|smg-mmg|-|[🔍 diff](https://fifo.ci/compare/15456659-15426839/)|\n|sonic-riders-blur|-|[🔍 diff](https://fifo.ci/compare/15456634-15426814/)|\n|sonic-riders-zg-4p|-|[🔍 diff](https://fifo.ci/compare/15456661-15426841/)|\n|soniccolors-mm|[🔍 diff](https://fifo.ci/compare/15456463-15426643/)|[🔍 diff](https://fifo.ci/compare/15456603-15426783/)|\n|ssbb-mod-lloyd|-|[🔍 diff](https://fifo.ci/compare/15456620-15426800/)|\n|ssbm-pointsize|-|[🔍 diff](https://fifo.ci/compare/15456568-15426748/)|\n|sw3-dt|[🔍 diff](https://fifo.ci/compare/15456484-15426664/)|[🔍 diff](https://fifo.ci/compare/15456624-15426804/)|\n|thps4-shadow|[🔍 diff](https://fifo.ci/compare/15456443-15426623/)|[🔍 diff](https://fifo.ci/compare/15456583-15426763/)|\n|tsp3-pinkgrass|[🔍 diff](https://fifo.ci/compare/15456424-15426604/)|[🔍 diff](https://fifo.ci/compare/15456564-15426744/)|\n|vegas-party-depth|[🔍 diff](https://fifo.ci/compare/15456469-15426649/)|[🔍 diff](https://fifo.ci/compare/15456609-15426789/)|\n|ztp-grass|-|[🔍 diff](https://fifo.ci/compare/15456565-15426745/)|\n|zww-waves|-|[🔍 diff](https://fifo.ci/compare/15456585-15426765/)|\n</details>\n<sub><sup>automated-fifoci-reporter</sup></sub>', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3816809833/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': {'id': 49947, 'client_id': 'Iv1.1fcaea7644d8b727', 'slug': 'dolphin-ci', 'node_id': 'MDM6QXBwNDk5NDc=', 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'name': 'Dolphin CI', 'description': 'Continuous Integration setup for [dolphin-emu.org](https://dolphin-emu.org/).', 'external_url': 'https://github.com/dolphin-emu', 'html_url': 'https://github.com/apps/dolphin-ci', 'created_at': '2019-12-26T22:26:07Z', 'updated_at': '2019-12-26T22:33:19Z', 'permissions': {'checks': 'write', 'contents': 'read', 'issues': 'write', 'members': 'read', 'metadata': 'read', 'pull_requests': 'write', 'statuses': 'write'}, 'events': ['check_run', 'commit_comment', 'issue_comment', 'pull_request', 'pull_request_review', 'pull_request_review_comment', 'push']}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T08:38:08Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14574, 'watchers_count': 14574, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14574, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'dolphin-ci[bot]', 'id': 59266906, 'node_id': 'MDM6Qm90NTkyNjY5MDY=', 'avatar_url': 'https://avatars.githubusercontent.com/in/49947?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D', 'html_url': 'https://github.com/apps/dolphin-ci', 'followers_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/followers', 'following_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/orgs', 'repos_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/repos', 'events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-ci%5Bbot%5D/received_events', 'type': 'Bot', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T08:27:57.825812	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'deleted', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833690255', 'safe_author': True, 'body': '@dolphin-emu-bot rebuild', 'raw': {'action': 'deleted', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 8, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T08:27:47Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833690255', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833690255', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3833690255, 'node_id': 'IC_kwDOALCn2M7kgXiP', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T08:27:47Z', 'updated_at': '2026-02-02T08:27:47Z', 'body': '@dolphin-emu-bot rebuild', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833690255/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T06:22:16Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14574, 'watchers_count': 14574, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14574, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T08:27:49.938572	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'created', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833690255', 'safe_author': True, 'body': '@dolphin-emu-bot rebuild', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 8, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T08:27:47Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833690255', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833690255', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3833690255, 'node_id': 'IC_kwDOALCn2M7kgXiP', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T08:27:47Z', 'updated_at': '2026-02-02T08:27:47Z', 'body': '@dolphin-emu-bot rebuild', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833690255/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T06:22:16Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14574, 'watchers_count': 14574, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14574, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-02T08:13:07.555027	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'cscd98', 'action': 'created', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833617208', 'safe_author': False, 'body': '@cristian64 now ready for testing :+1: (now builds fine with freebsd)', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'id': 3789545274, 'node_id': 'PR_kwDOALCn2M678v8s', 'number': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 7, 'created_at': '2026-01-07T16:57:40Z', 'updated_at': '2026-02-02T08:13:05Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14260', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14260.patch', 'merged_at': None}, 'body': 'This has this https://github.com/mutouyun/cpp-ipc/pull/169 merged in as some new Windows.h includes made it in breaking mingw compatibility, so this fixes that.\r\n\r\nNeeds testing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833617208', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14260#issuecomment-3833617208', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14260', 'id': 3833617208, 'node_id': 'IC_kwDOALCn2M7kgFs4', 'user': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T08:13:05Z', 'updated_at': '2026-02-02T08:13:05Z', 'body': '@cristian64 now ready for testing :+1: (now builds fine with freebsd)', 'author_association': 'CONTRIBUTOR', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3833617208/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T06:22:16Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14574, 'watchers_count': 14574, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14574, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'cscd98', 'id': 1188869, 'node_id': 'MDQ6VXNlcjExODg4Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1188869?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/cscd98', 'html_url': 'https://github.com/cscd98', 'followers_url': 'https://api.github.com/users/cscd98/followers', 'following_url': 'https://api.github.com/users/cscd98/following{/other_user}', 'gists_url': 'https://api.github.com/users/cscd98/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/cscd98/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/cscd98/subscriptions', 'organizations_url': 'https://api.github.com/users/cscd98/orgs', 'repos_url': 'https://api.github.com/users/cscd98/repos', 'events_url': 'https://api.github.com/users/cscd98/events{/privacy}', 'received_events_url': 'https://api.github.com/users/cscd98/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-01T21:48:51.558619	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'created', 'id': 14308, 'title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308#issuecomment-3832120460', 'safe_author': True, 'body': '> To be compliant with RFC 3629, we should also reject all surrogate code points when decoding UTF-8. Looks good other than that.\r\n\r\nDone.', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14308', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14308/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14308/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14308/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308', 'id': 3863094147, 'node_id': 'PR_kwDOALCn2M6_vEBN', 'number': 14308, 'title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 2, 'created_at': '2026-01-28T01:16:38Z', 'updated_at': '2026-02-01T21:48:48Z', 'closed_at': None, 'author_association': 'MEMBER', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14308.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14308.patch', 'merged_at': None}, 'body': "Replaces: #14282.\r\nFixes: https://bugs.dolphin-emu.org/issues/13962\r\n\r\nNote that `std::wstring_convert` throws exceptions on invalid sequences, and it's deprecated, to be removed in C++26.\r\n\r\nThis PR replaces usage of `wstring_convert` with hand-rolled UTF8/16 encoding/decoding.\r\n\r\nI'd usually rather just use an existing libarary (like iconv) but..\r\n- The iconv dependency isn't set up in the Windows build.\r\n- WinAPI's `MultiByteToWideChar` can't output to a `std::u16string` without strict aliasing violations or a memcpy.\r\n- UTF8/16 really isn't overly complicated.\r\n- These hand-rolled versions seem to be 5-10x faster than iconv in my simple tests.\r\n- We have more control over how semi-invalid sequences are handled. [This issue](https://bugs.dolphin-emu.org/issues/12502) will be fixed if QString conversion functions are changed to use the custom decoder.\r\n\r\nIf we wanted to entirely eliminate the iconv dependency, CP1252 would be easy enough to implement, but ShiftJIS looks like it isn't so fun.\r\nEdit: I have these implemented locally if we want to do that. ShiftJIS wasn't so bad.. just some giant tables.", 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14308/reactions', 'total_count': 1, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 1, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14308/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3832120460', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#issuecomment-3832120460', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14308', 'id': 3832120460, 'node_id': 'IC_kwDOALCn2M7kaYSM', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-01T21:48:48Z', 'updated_at': '2026-02-01T21:48:48Z', 'body': '> To be compliant with RFC 3629, we should also reject all surrogate code points when decoding UTF-8. Looks good other than that.\r\n\r\nDone.', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3832120460/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-01T19:55:00Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14572, 'watchers_count': 14572, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14572, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-01T20:27:31.435185	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Dentomologist', 'action': 'edited', 'id': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831860087', 'safe_author': True, 'body': '~~`game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`.~~', 'raw': {'action': 'edited', 'changes': {'body': {'from': '`game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`. '}}, 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'id': 3882697746, 'node_id': 'PR_kwDOALCn2M7Av0U8', 'number': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'user': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 6, 'created_at': '2026-02-01T18:58:25Z', 'updated_at': '2026-02-01T20:22:06Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14315', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.patch', 'merged_at': None}, 'body': 'This PR fixes an issue with wrong param parsing of Riivolution XML files. Usage of `std::string_view` creates corruption and invalid values of the parameters.\r\n<img width="357" height="158" alt="image" src="https://github.com/user-attachments/assets/45d48cae-e21e-4953-80b7-511d8f7ac9e3" />\r\nSimply replacing it with `std::string` fixes the issue.\r\n<img width="359" height="165" alt="image" src="https://github.com/user-attachments/assets/ede15ed1-deef-4af5-94a7-44ab988eaa0b" />\r\n', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/reactions', 'total_count': 1, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 1, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831860087', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831860087', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'id': 3831860087, 'node_id': 'IC_kwDOALCn2M7kZYt3', 'user': {'login': 'Dentomologist', 'id': 73494713, 'node_id': 'MDQ6VXNlcjczNDk0NzEz', 'avatar_url': 'https://avatars.githubusercontent.com/u/73494713?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Dentomologist', 'html_url': 'https://github.com/Dentomologist', 'followers_url': 'https://api.github.com/users/Dentomologist/followers', 'following_url': 'https://api.github.com/users/Dentomologist/following{/other_user}', 'gists_url': 'https://api.github.com/users/Dentomologist/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Dentomologist/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Dentomologist/subscriptions', 'organizations_url': 'https://api.github.com/users/Dentomologist/orgs', 'repos_url': 'https://api.github.com/users/Dentomologist/repos', 'events_url': 'https://api.github.com/users/Dentomologist/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Dentomologist/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-01T19:50:11Z', 'updated_at': '2026-02-01T20:27:29Z', 'body': '~~`game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`.~~', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831860087/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-01T19:55:00Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14572, 'watchers_count': 14572, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14572, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Dentomologist', 'id': 73494713, 'node_id': 'MDQ6VXNlcjczNDk0NzEz', 'avatar_url': 'https://avatars.githubusercontent.com/u/73494713?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Dentomologist', 'html_url': 'https://github.com/Dentomologist', 'followers_url': 'https://api.github.com/users/Dentomologist/followers', 'following_url': 'https://api.github.com/users/Dentomologist/following{/other_user}', 'gists_url': 'https://api.github.com/users/Dentomologist/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Dentomologist/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Dentomologist/subscriptions', 'organizations_url': 'https://api.github.com/users/Dentomologist/orgs', 'repos_url': 'https://api.github.com/users/Dentomologist/repos', 'events_url': 'https://api.github.com/users/Dentomologist/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Dentomologist/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-01T20:22:09.222792	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Dentomologist', 'action': 'created', 'id': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831916842', 'safe_author': True, 'body': "> `std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.\r\n\r\n... whoops, got that mixed up with `std::string::substr`.", 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'id': 3882697746, 'node_id': 'PR_kwDOALCn2M7Av0U8', 'number': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'user': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 6, 'created_at': '2026-02-01T18:58:25Z', 'updated_at': '2026-02-01T20:22:06Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14315', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.patch', 'merged_at': None}, 'body': 'This PR fixes an issue with wrong param parsing of Riivolution XML files. Usage of `std::string_view` creates corruption and invalid values of the parameters.\r\n<img width="357" height="158" alt="image" src="https://github.com/user-attachments/assets/45d48cae-e21e-4953-80b7-511d8f7ac9e3" />\r\nSimply replacing it with `std::string` fixes the issue.\r\n<img width="359" height="165" alt="image" src="https://github.com/user-attachments/assets/ede15ed1-deef-4af5-94a7-44ab988eaa0b" />\r\n', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/reactions', 'total_count': 1, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 1, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831916842', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831916842', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'id': 3831916842, 'node_id': 'IC_kwDOALCn2M7kZmkq', 'user': {'login': 'Dentomologist', 'id': 73494713, 'node_id': 'MDQ6VXNlcjczNDk0NzEz', 'avatar_url': 'https://avatars.githubusercontent.com/u/73494713?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Dentomologist', 'html_url': 'https://github.com/Dentomologist', 'followers_url': 'https://api.github.com/users/Dentomologist/followers', 'following_url': 'https://api.github.com/users/Dentomologist/following{/other_user}', 'gists_url': 'https://api.github.com/users/Dentomologist/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Dentomologist/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Dentomologist/subscriptions', 'organizations_url': 'https://api.github.com/users/Dentomologist/orgs', 'repos_url': 'https://api.github.com/users/Dentomologist/repos', 'events_url': 'https://api.github.com/users/Dentomologist/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Dentomologist/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-01T20:22:06Z', 'updated_at': '2026-02-01T20:22:06Z', 'body': "> `std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.\r\n\r\n... whoops, got that mixed up with `std::string::substr`.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831916842/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-01T19:55:00Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14572, 'watchers_count': 14572, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14572, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Dentomologist', 'id': 73494713, 'node_id': 'MDQ6VXNlcjczNDk0NzEz', 'avatar_url': 'https://avatars.githubusercontent.com/u/73494713?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Dentomologist', 'html_url': 'https://github.com/Dentomologist', 'followers_url': 'https://api.github.com/users/Dentomologist/followers', 'following_url': 'https://api.github.com/users/Dentomologist/following{/other_user}', 'gists_url': 'https://api.github.com/users/Dentomologist/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Dentomologist/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Dentomologist/subscriptions', 'organizations_url': 'https://api.github.com/users/Dentomologist/orgs', 'repos_url': 'https://api.github.com/users/Dentomologist/repos', 'events_url': 'https://api.github.com/users/Dentomologist/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Dentomologist/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-01T20:21:09.791156	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Dentomologist', 'action': 'edited', 'id': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831877200', 'safe_author': True, 'body': "> `game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`.\r\n\r\n`std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.", 'raw': {'action': 'edited', 'changes': {'body': {'from': "> `std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.\r\n\r\n... whoops, got that mixed up with `std::string::substr`."}}, 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'id': 3882697746, 'node_id': 'PR_kwDOALCn2M7Av0U8', 'number': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'user': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 5, 'created_at': '2026-02-01T18:58:25Z', 'updated_at': '2026-02-01T20:12:08Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14315', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.patch', 'merged_at': None}, 'body': 'This PR fixes an issue with wrong param parsing of Riivolution XML files. Usage of `std::string_view` creates corruption and invalid values of the parameters.\r\n<img width="357" height="158" alt="image" src="https://github.com/user-attachments/assets/45d48cae-e21e-4953-80b7-511d8f7ac9e3" />\r\nSimply replacing it with `std::string` fixes the issue.\r\n<img width="359" height="165" alt="image" src="https://github.com/user-attachments/assets/ede15ed1-deef-4af5-94a7-44ab988eaa0b" />\r\n', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/reactions', 'total_count': 1, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 1, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831877200', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831877200', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'id': 3831877200, 'node_id': 'IC_kwDOALCn2M7kZc5Q', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-01T19:59:46Z', 'updated_at': '2026-02-01T20:21:08Z', 'body': "> `game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`.\r\n\r\n`std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831877200/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-01T19:55:00Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14572, 'watchers_count': 14572, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14572, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Dentomologist', 'id': 73494713, 'node_id': 'MDQ6VXNlcjczNDk0NzEz', 'avatar_url': 'https://avatars.githubusercontent.com/u/73494713?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Dentomologist', 'html_url': 'https://github.com/Dentomologist', 'followers_url': 'https://api.github.com/users/Dentomologist/followers', 'following_url': 'https://api.github.com/users/Dentomologist/following{/other_user}', 'gists_url': 'https://api.github.com/users/Dentomologist/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Dentomologist/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Dentomologist/subscriptions', 'organizations_url': 'https://api.github.com/users/Dentomologist/orgs', 'repos_url': 'https://api.github.com/users/Dentomologist/repos', 'events_url': 'https://api.github.com/users/Dentomologist/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Dentomologist/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-01T20:12:10.024975	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Dentomologist', 'action': 'edited', 'id': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831877200', 'safe_author': True, 'body': "> `std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.\r\n\r\n... whoops, got that mixed up with `std::string::substr`.", 'raw': {'action': 'edited', 'changes': {'body': {'from': "> `game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`. \r\n\r\n`std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call."}}, 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'id': 3882697746, 'node_id': 'PR_kwDOALCn2M7Av0U8', 'number': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'user': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 5, 'created_at': '2026-02-01T18:58:25Z', 'updated_at': '2026-02-01T19:59:46Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14315', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.patch', 'merged_at': None}, 'body': 'This PR fixes an issue with wrong param parsing of Riivolution XML files. Usage of `std::string_view` creates corruption and invalid values of the parameters.\r\n<img width="357" height="158" alt="image" src="https://github.com/user-attachments/assets/45d48cae-e21e-4953-80b7-511d8f7ac9e3" />\r\nSimply replacing it with `std::string` fixes the issue.\r\n<img width="359" height="165" alt="image" src="https://github.com/user-attachments/assets/ede15ed1-deef-4af5-94a7-44ab988eaa0b" />\r\n', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/reactions', 'total_count': 1, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 1, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831877200', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831877200', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'id': 3831877200, 'node_id': 'IC_kwDOALCn2M7kZc5Q', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-01T19:59:46Z', 'updated_at': '2026-02-01T20:12:08Z', 'body': "> `std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.\r\n\r\n... whoops, got that mixed up with `std::string::substr`.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831877200/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-01T19:55:00Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14572, 'watchers_count': 14572, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14572, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Dentomologist', 'id': 73494713, 'node_id': 'MDQ6VXNlcjczNDk0NzEz', 'avatar_url': 'https://avatars.githubusercontent.com/u/73494713?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Dentomologist', 'html_url': 'https://github.com/Dentomologist', 'followers_url': 'https://api.github.com/users/Dentomologist/followers', 'following_url': 'https://api.github.com/users/Dentomologist/following{/other_user}', 'gists_url': 'https://api.github.com/users/Dentomologist/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Dentomologist/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Dentomologist/subscriptions', 'organizations_url': 'https://api.github.com/users/Dentomologist/orgs', 'repos_url': 'https://api.github.com/users/Dentomologist/repos', 'events_url': 'https://api.github.com/users/Dentomologist/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Dentomologist/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-01T19:59:48.727527	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831877200', 'safe_author': True, 'body': "> `game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`. \r\n\r\n`std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.", 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'id': 3882697746, 'node_id': 'PR_kwDOALCn2M7Av0U8', 'number': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'user': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 5, 'created_at': '2026-02-01T18:58:25Z', 'updated_at': '2026-02-01T19:59:46Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14315', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.patch', 'merged_at': None}, 'body': 'This PR fixes an issue with wrong param parsing of Riivolution XML files. Usage of `std::string_view` creates corruption and invalid values of the parameters.\r\n<img width="357" height="158" alt="image" src="https://github.com/user-attachments/assets/45d48cae-e21e-4953-80b7-511d8f7ac9e3" />\r\nSimply replacing it with `std::string` fixes the issue.\r\n<img width="359" height="165" alt="image" src="https://github.com/user-attachments/assets/ede15ed1-deef-4af5-94a7-44ab988eaa0b" />\r\n', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/reactions', 'total_count': 1, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 1, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831877200', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831877200', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'id': 3831877200, 'node_id': 'IC_kwDOALCn2M7kZc5Q', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-01T19:59:46Z', 'updated_at': '2026-02-01T19:59:46Z', 'body': "> `game_id_no_region`, `game_region`, and `game_developer` are views into the return value of `substr`, the lifetime of which ends at the end of the expression. Those variables should be `std::string` instead of `std::string_view`. \r\n\r\n`std::string_view::substr` doesn't return an `std::string`, it reuses the same backing memory as the `std::string_view` it was called on. So those variables are views into `game_id`, which is valid for the whole `GeneratePatches` call.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831877200/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-01T19:55:00Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14572, 'watchers_count': 14572, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14572, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}
2026-02-01T19:53:41.054198	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Gabriela-Orzechowska', 'action': 'created', 'id': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831864511', 'safe_author': False, 'body': 'Yeah it seems like std::pair constructor create a string copy which is pretty much immediately destroyed, causing string_view to point to wrong data.\r\n\r\nDid some more checks and found a cleaner solution, keeping string_view, which is just replacing\r\n```\r\n  replacements.emplace_back(std::pair{"{$" + param.first + "}", param.second});\r\n  ```\r\n  with\r\n  ```\r\n  replacements.emplace_back("{$" + param.first + "}", param.second);\r\n  ```', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'id': 3882697746, 'node_id': 'PR_kwDOALCn2M7Av0U8', 'number': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'user': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 4, 'created_at': '2026-02-01T18:58:25Z', 'updated_at': '2026-02-01T19:53:39Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14315', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14315.patch', 'merged_at': None}, 'body': 'This PR fixes an issue with wrong param parsing of Riivolution XML files. Usage of `std::string_view` creates corruption and invalid values of the parameters.\r\n<img width="357" height="158" alt="image" src="https://github.com/user-attachments/assets/45d48cae-e21e-4953-80b7-511d8f7ac9e3" />\r\nSimply replacing it with `std::string` fixes the issue.\r\n<img width="359" height="165" alt="image" src="https://github.com/user-attachments/assets/ede15ed1-deef-4af5-94a7-44ab988eaa0b" />\r\n', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/reactions', 'total_count': 1, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 1, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831864511', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14315#issuecomment-3831864511', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14315', 'id': 3831864511, 'node_id': 'IC_kwDOALCn2M7kZZy_', 'user': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-01T19:53:38Z', 'updated_at': '2026-02-01T19:53:38Z', 'body': 'Yeah it seems like std::pair constructor create a string copy which is pretty much immediately destroyed, causing string_view to point to wrong data.\r\n\r\nDid some more checks and found a cleaner solution, keeping string_view, which is just replacing\r\n```\r\n  replacements.emplace_back(std::pair{"{$" + param.first + "}", param.second});\r\n  ```\r\n  with\r\n  ```\r\n  replacements.emplace_back("{$" + param.first + "}", param.second);\r\n  ```', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3831864511/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-01T17:27:38Z', 'pushed_at': '2026-01-31T23:02:09Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14571, 'watchers_count': 14571, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2967, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2967, 'open_issues': 360, 'watchers': 14571, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Gabriela-Orzechowska', 'id': 85825993, 'node_id': 'MDQ6VXNlcjg1ODI1OTkz', 'avatar_url': 'https://avatars.githubusercontent.com/u/85825993?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Gabriela-Orzechowska', 'html_url': 'https://github.com/Gabriela-Orzechowska', 'followers_url': 'https://api.github.com/users/Gabriela-Orzechowska/followers', 'following_url': 'https://api.github.com/users/Gabriela-Orzechowska/following{/other_user}', 'gists_url': 'https://api.github.com/users/Gabriela-Orzechowska/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Gabriela-Orzechowska/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Gabriela-Orzechowska/subscriptions', 'organizations_url': 'https://api.github.com/users/Gabriela-Orzechowska/orgs', 'repos_url': 'https://api.github.com/users/Gabriela-Orzechowska/repos', 'events_url': 'https://api.github.com/users/Gabriela-Orzechowska/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Gabriela-Orzechowska/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'gh_issue_comment'}

Recent 'gh_pull_request' events

2026-02-03T23:32:40.513235	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'opened', 'id': 14318, 'title': 'Core: Make RunOnCPUThread always non-blocking.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14318', 'head_ref_name': 'RunOnCPUThread-always-non-blocking', 'safe_author': True, 'base_sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'head_sha': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-03T22:50:54.189859	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'closed', 'id': 13594, 'title': 'State: Simplify interthread communication and general cleanups.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13594', 'head_ref_name': 'state-cleanups', 'safe_author': True, 'base_sha': '2a771937cf0a4a45cd466d3eea089a083f73f3c0', 'head_sha': '2322437f96bc53f1edcd9100daedb5b54c6e0fc4', 'merged': True, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-03T22:35:19.459909	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'synchronize', 'id': 14218, 'title': 'Jit64: Make ABI_CallFunction generic', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14218', 'head_ref_name': 'jit64-abi-call-function-parameters', 'safe_author': True, 'base_sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'head_sha': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-03T22:06:12.686110	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'synchronize', 'id': 13768, 'title': 'Core: Create fastmem mappings for page address translation', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13768', 'head_ref_name': 'page-table-fastmem-2', 'safe_author': True, 'base_sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'head_sha': '32856fd15531890d65379376c8cadfc3611b36cb', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-03T22:04:37.178271	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'synchronize', 'id': 13768, 'title': 'Core: Create fastmem mappings for page address translation', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13768', 'head_ref_name': 'page-table-fastmem-2', 'safe_author': True, 'base_sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'head_sha': 'a7301d7d406bc5955303a13e61327c5b17645b7a', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-03T13:20:38.893374	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'bgermann', 'action': 'synchronize', 'id': 13997, 'title': 'Externals: Update minizip-ng to 4.1.0', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13997', 'head_ref_name': 'minizip-ng', 'safe_author': False, 'base_sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'head_sha': 'df85a2c99d667c537a5a896cd81954fb63fab027', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-02T21:07:21.898023	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'opened', 'id': 14317, 'title': 'Optimize JitBaseBlockCache::ErasePhysicalRange', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14317', 'head_ref_name': 'jit-cache-macro-loop', 'safe_author': True, 'base_sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'head_sha': 'c8180071a552720e5436e908afc4eda7e87be08c', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-02T20:05:53.902333	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JMC47', 'action': 'closed', 'id': 14316, 'title': 'ResourcePack: Fix loading resource packs', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14316', 'head_ref_name': 'resourcefix', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'd87e8ab7ff66f3efcedeb5a0ab869b537933aada', 'merged': True, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-02T19:49:56.204599	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'review_requested', 'id': 14316, 'title': 'ResourcePack: Fix loading resource packs', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14316', 'head_ref_name': 'resourcefix', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'd87e8ab7ff66f3efcedeb5a0ab869b537933aada', 'merged': False, 'requested_reviewers': [{'login': 'iwubcode', 'id': 15224722, 'node_id': 'MDQ6VXNlcjE1MjI0NzIy', 'avatar_url': 'https://avatars.githubusercontent.com/u/15224722?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/iwubcode', 'html_url': 'https://github.com/iwubcode', 'followers_url': 'https://api.github.com/users/iwubcode/followers', 'following_url': 'https://api.github.com/users/iwubcode/following{/other_user}', 'gists_url': 'https://api.github.com/users/iwubcode/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/iwubcode/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/iwubcode/subscriptions', 'organizations_url': 'https://api.github.com/users/iwubcode/orgs', 'repos_url': 'https://api.github.com/users/iwubcode/repos', 'events_url': 'https://api.github.com/users/iwubcode/events{/privacy}', 'received_events_url': 'https://api.github.com/users/iwubcode/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}], 'type': 'gh_pull_request'}
2026-02-02T19:47:30.545050	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'synchronize', 'id': 13844, 'title': 'Core: Triforce support', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'head_ref_name': 'master', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': '66380234a68eb4d8106aa3004b0e531a06f5cd24', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-02T19:02:51.844832	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JoshuaVandaele', 'action': 'opened', 'id': 14316, 'title': 'ResourcePack: Fix loading resource packs', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14316', 'head_ref_name': 'resourcefix', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'd87e8ab7ff66f3efcedeb5a0ab869b537933aada', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-02T13:19:20.069306	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'cscd98', 'action': 'synchronize', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'head_ref_name': 'cpp-ipc-update', 'safe_author': False, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'd75794b774d64f50d4bad334660d8f9068f241bd', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-02T08:11:45.865362	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'cscd98', 'action': 'synchronize', 'id': 14260, 'title': 'Externals: Update cpp-ipc to latest for mingw compatibility', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14260', 'head_ref_name': 'cpp-ipc-update', 'safe_author': False, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': '512972c1cd4a10111412d619e464fc72a964101e', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T21:57:37.007539	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'synchronize', 'id': 14308, 'title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308', 'head_ref_name': 'character-encoding', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'f07ac93e55496dbafad9d3d2ad6d36c6f5c016a6', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T21:52:48.036883	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'edited', 'id': 14308, 'title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308', 'head_ref_name': 'character-encoding', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': '21cf436482e458914158b5ed05c1278fa3831c25', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T21:47:29.021412	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'synchronize', 'id': 14308, 'title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308', 'head_ref_name': 'character-encoding', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': '21cf436482e458914158b5ed05c1278fa3831c25', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T18:58:28.030639	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Gabriela-Orzechowska', 'action': 'opened', 'id': 14315, 'title': 'RiivolutionParser: Fix XML Param Parsing', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14315', 'head_ref_name': 'fix_riivo_params', 'safe_author': False, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': '20bc275e97f8facc7a6edc6355acad17ecfcee13', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T12:47:55.922868	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'synchronize', 'id': 14308, 'title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308', 'head_ref_name': 'character-encoding', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'dd6584210e8b2b33ce7ea43cdff5bf36dc1270a4', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T12:44:24.216882	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'synchronize', 'id': 13768, 'title': 'Core: Create fastmem mappings for page address translation', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13768', 'head_ref_name': 'page-table-fastmem-2', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T12:41:55.951659	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'synchronize', 'id': 14308, 'title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308', 'head_ref_name': 'character-encoding', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'cf8dd36ee0593e548c307fae826a44858425db82', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-02-01T05:44:40.208060	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JMC47', 'action': 'opened', 'id': 14314, 'title': 'GameINI: Force Safe Texture Cache to Samurai Shodown Anthology', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14314', 'head_ref_name': 'shodown', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'ac19ef5452a1abcd7bcfc9d166fcdbe28c714ba1', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-01-31T23:30:11.213531	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'synchronize', 'id': 14313, 'title': 'Core/Common: Remove UTF8ToTStr and TStrToUTF8. Replace TCHAR with WCHAR.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14313', 'head_ref_name': 'no-tchar', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'e95ead2b474f4341aebbe1024f1d73ac9a28fce0', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-01-31T23:26:49.502043	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'opened', 'id': 14313, 'title': 'Core/Common: Remove UTF8ToTStr and TStrToUTF8. Replace TCHAR with WCHAR.', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14313', 'head_ref_name': 'no-tchar', 'safe_author': True, 'base_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'head_sha': 'c3c998305790626281c2268f1c035cccfc364a6e', 'merged': False, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-01-31T23:02:12.501094	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Dentomologist', 'action': 'closed', 'id': 14204, 'title': 'Update Comments Based On Hardware Test', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14204', 'head_ref_name': 'update-comment', 'safe_author': True, 'base_sha': '75bc9a474edab83c8751b73939a7136990c3c118', 'head_sha': 'f31f11c0a4b37decebd0e0a349bdc33691fdd657', 'merged': True, 'requested_reviewers': [], 'type': 'gh_pull_request'}
2026-01-31T22:45:21.262733	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'closed', 'id': 14312, 'title': 'WGL: Correctly load wglDestroyPbufferARB extension', 'base_ref_name': 'master', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14312', 'head_ref_name': 'wglDestroyPbufferARB', 'safe_author': True, 'base_sha': 'b1020186519d690a9b64a215f00da7453ab2803b', 'head_sha': 'e6bc8fb342169d9936645fe1fdebe296d92bc44b', 'merged': True, 'requested_reviewers': [], 'type': 'gh_pull_request'}

Recent 'gh_pull_request_comment' events

2026-02-03T04:58:23.579054	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'created', 'id': 14218, 'hash': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14218#discussion_r2757167327', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-03T04:24:35.371305	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'created', 'id': 14140, 'hash': 'd58818f317de389fb1ddded9412a1d9b2a29462e', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2757133526', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-02T17:28:07.901821	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2755469094', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-02-01T22:54:22.629706	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2752074638', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-02-01T19:49:22.122892	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751867162', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T15:40:29.400981	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751386784', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T15:40:29.356305	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751395550', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T15:40:29.269440	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751374597', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T15:40:29.228139	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751296052', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T15:40:29.168946	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751294423', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T15:40:29.123212	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751418497', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T15:40:29.099522	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751389783', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-02-01T11:45:30.419065	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13768, 'hash': '3fdce1e8bd9901e5d852dea23a271ca48434ac37', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13768#discussion_r2751118036', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-01-31T19:02:47.389693	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749868593', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-01-31T19:01:53.469382	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749867718', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-01-31T18:51:16.136805	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749856368', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-01-31T18:42:27.964070	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'edited', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749846854', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-01-31T18:40:52.729042	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749846854', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-01-31T13:41:21.008211	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'edited', 'id': 14308, 'hash': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749544190', 'is_part_of_review': False, 'type': 'gh_pull_request_comment'}
2026-01-31T13:33:17.726485	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749561611', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-01-31T13:33:17.091883	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749561110', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-01-31T13:33:15.688585	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749563661', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-01-31T13:33:15.652154	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749564419', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-01-31T13:33:15.509330	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749562072', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}
2026-01-31T13:33:15.422596	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'created', 'id': 13844, 'hash': '510fec88be973bc870021f2d0347593cdc285864', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749557847', 'is_part_of_review': True, 'type': 'gh_pull_request_comment'}

Recent 'gh_pull_request_review' events

2026-02-03T22:48:25.697637	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'submitted', 'pr_id': 13768, 'pr_title': 'Core: Create fastmem mappings for page address translation', 'state': 'approved', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13768#pullrequestreview-3748007210', 'comments': [], 'type': 'gh_pull_request_review'}
2026-02-03T04:59:38.731134	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'submitted', 'pr_id': 14218, 'pr_title': 'Jit64: Make ABI_CallFunction generic', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14218#pullrequestreview-3742915580', 'comments': [{'id': 2757167327, 'node_id': 'PRRC_kwDOALCn2M6kVwjf', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757167327', 'pull_request_review_id': 3742915580, 'diff_hunk': '@@ -377,6 +423,11 @@ class XEmitter\n   void ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size,\n                               size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp);\n \n+  // This function solves the "parallel moves" problem that\'s common in compilers.\n+  // The arguments are mutated!\n+  void ParallelMoves(RegisterMove* begin, RegisterMove* end, std::array<u8, 16>* source_reg_uses,\n+                     std::array<u8, 16>* destination_reg_uses);', 'path': 'Source/Core/Common/x64Emitter.h', 'position': 84, 'original_position': 84, 'commit_id': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '```suggestion\n  void ParallelMoves(std::span<RegisterMove> pending_moves, std::array<u8, 16>* source_reg_uses,\n                     std::array<u8, 16>* destination_reg_uses);\n```\nI suppose a `span` would be cleaner than two pointers.\n\nThen the call site becomes just:\n```cpp\nParallelMoves(pending_moves, &source_reg_uses, &destination_reg_uses);\n```', 'created_at': '2026-02-03T04:42:30Z', 'updated_at': '2026-02-03T04:55:05Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14218#discussion_r2757167327', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757167327'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14218#discussion_r2757167327'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218'}}, 'original_commit_id': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757167327/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}], 'type': 'gh_pull_request_review'}
2026-02-03T04:07:47.475546	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'submitted', 'pr_id': 13768, 'pr_title': 'Core: Create fastmem mappings for page address translation', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13768#pullrequestreview-3742805702', 'comments': [{'id': 2757073772, 'node_id': 'PRRC_kwDOALCn2M6kVZts', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757073772', 'pull_request_review_id': 3742805702, 'diff_hunk': '@@ -305,6 +319,183 @@ void MemoryManager::UpdateLogicalMemory(const PowerPC::BatTable& dbat_table)\n   }\n }\n \n+void MemoryManager::AddPageTableMapping(u32 logical_address, u32 translated_address, bool writeable)\n+{\n+  if (!m_is_fastmem_arena_initialized)\n+    return;\n+\n+  switch (m_host_page_type)\n+  {\n+  case HostPageType::SmallPages:\n+    return AddHostPageTableMapping(logical_address, translated_address, writeable,\n+                                   PowerPC::HW_PAGE_SIZE);\n+  case HostPageType::LargePages:\n+    return TryAddLargePageTableMapping(logical_address, translated_address, writeable);\n+  default:\n+    return;\n+  }\n+}\n+\n+void MemoryManager::TryAddLargePageTableMapping(u32 logical_address, u32 translated_address,\n+                                                bool writeable)\n+{\n+  const bool add_readable =\n+      TryAddLargePageTableMapping(logical_address, translated_address, m_large_readable_pages);\n+\n+  const bool add_writeable =\n+      writeable &&\n+      TryAddLargePageTableMapping(logical_address, translated_address, m_large_writeable_pages);\n+\n+  if (add_readable || add_writeable)\n+  {\n+    AddHostPageTableMapping(logical_address & ~(m_page_size - 1),\n+                            translated_address & ~(m_page_size - 1), add_writeable, m_page_size);\n+  }\n+}\n+\n+bool MemoryManager::TryAddLargePageTableMapping(u32 logical_address, u32 translated_address,\n+                                                std::map<u32, std::vector<u32>>& map)\n+{\n+  std::vector<u32>& entries = map[logical_address & ~(m_page_size - 1)];\n+\n+  if (entries.empty())\n+    entries = std::vector<u32>(m_guest_pages_per_host_page, INVALID_MAPPING);\n+\n+  entries[(logical_address & (m_page_size - 1)) / PowerPC::HW_PAGE_SIZE] = translated_address;\n+\n+  return CanCreateHostMappingForGuestPages(entries);\n+}\n+\n+bool MemoryManager::CanCreateHostMappingForGuestPages(const std::vector<u32>& entries) const\n+{\n+  const u32 translated_address = entries[0];\n+  if ((translated_address & (m_page_size - 1)) != 0)\n+    return false;\n+\n+  for (size_t i = 1; i < m_guest_pages_per_host_page; ++i)\n+  {\n+    if (entries[i] != translated_address + i * PowerPC::HW_PAGE_SIZE)\n+      return false;\n+  }\n+\n+  return true;\n+}\n+\n+void MemoryManager::AddHostPageTableMapping(u32 logical_address, u32 translated_address,\n+                                            bool writeable, u32 logical_size)\n+{\n+  for (const auto& physical_region : m_physical_regions)\n+  {\n+    if (!physical_region.active)\n+      continue;\n+\n+    u32 mapping_address = physical_region.physical_address;', 'path': 'Source/Core/Core/HW/Memmap.cpp', 'position': 212, 'original_position': 212, 'commit_id': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '```suggestion\n    const u32 mapping_address = physical_region.physical_address;\n```\nIt looks like every variable in this function can be `const`.', 'created_at': '2026-02-03T03:59:35Z', 'updated_at': '2026-02-03T04:07:45Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768#discussion_r2757073772', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757073772'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768#discussion_r2757073772'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}}, 'original_commit_id': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757073772/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2757080594, 'node_id': 'PRRC_kwDOALCn2M6kVbYS', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757080594', 'pull_request_review_id': 3742805702, 'diff_hunk': '@@ -127,6 +127,22 @@ inline u64 swap64(const u8* data)\n   return swap64(value);\n }\n \n+inline void swap16(u8* data, u16 value)', 'path': 'Source/Core/Common/Swap.h', 'position': 4, 'original_position': 4, 'commit_id': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'I think the unique behavior of these new swap functions warrants them having different names instead of creating more overloads.\n\n`WriteSwapped16`, maybe?', 'created_at': '2026-02-03T04:03:04Z', 'updated_at': '2026-02-03T04:07:45Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768#discussion_r2757080594', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757080594'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768#discussion_r2757080594'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}}, 'original_commit_id': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757080594/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}], 'type': 'gh_pull_request_review'}
2026-02-02T20:03:29.296321	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'iwubcode', 'action': 'submitted', 'pr_id': 14316, 'pr_title': 'ResourcePack: Fix loading resource packs', 'state': 'approved', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14316#pullrequestreview-3741520820', 'comments': [], 'type': 'gh_pull_request_review'}
2026-02-02T19:49:46.913162	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'submitted', 'pr_id': 14316, 'pr_title': 'ResourcePack: Fix loading resource packs', 'state': 'approved', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14316#pullrequestreview-3741478957', 'comments': [], 'type': 'gh_pull_request_review'}
2026-02-02T17:28:07.677512	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3740914372', 'comments': [{'id': 2755469094, 'node_id': 'PRRC_kwDOALCn2M6kPR8m', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2755469094', 'pull_request_review_id': 3740914372, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1345, 'original_position': 1345, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "> Can't `length = 1` if `data_in[0] != 0x05`, so `m_card_offset - 2` be unsafe even if incremented by `length`?\r\n\r\nOh, `data_in` is incremented in between getting `length` from `data_in` and the for loop that copies from `data_in` to `m_card_buffer`... Okay, then it's not true that `m_card_buffer[0]` has the same value as `length`, so ignore what I said about it being safe.", 'created_at': '2026-02-02T17:28:05Z', 'updated_at': '2026-02-02T17:28:05Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2755469094', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2755469094'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2755469094'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2755469094/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2751294423}], 'type': 'gh_pull_request_review'}
2026-02-01T22:54:22.565648	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3736773397', 'comments': [{'id': 2752074638, 'node_id': 'PRRC_kwDOALCn2M6kCVOO', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2752074638', 'pull_request_review_id': 3736773397, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1345, 'original_position': 1345, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "> I think `m_card_buffer[0]` contains the same value as `length`. If that's the case, this array offset is safe, but this is hard to reason about. If my analysis is correct, could we do something like replacing `m_card_buffer[0] == 0x02` with `length == 0x02`, or replacing `m_card_offset - 2` with a copy of `m_card_offset` from before we incremented it by `length`?\r\n\r\nCan't `length = 1` if `data_in[0] != 0x05`, so `m_card_offset - 2` be unsafe even if incremented by `length`?\r\n\r\nI'm not sure that `m_card_buffer[0]` is guaranteed to be `length` which was defined above _(before entering the if/else statement)_ as follows:\r\n```C++\r\nconst u32 length = *data_in++;\r\n```\r\nAssuming `data_in` can be arbitrary data, so can `m_card_buffer[0]` and the following:\r\n```C++\r\n// m_card_buffer[m_card_offset = 0] = data_in[0]\r\nfor (u32 i = 0; i < length; ++i)\r\n  m_card_buffer[m_card_offset + i] = data_in[i];\r\n\r\n m_card_offset += length;\r\n```\r\n\r\nMoreover, `m_card_offset` isn't (re)set to 0 before the operation.", 'created_at': '2026-02-01T22:54:20Z', 'updated_at': '2026-02-01T22:54:20Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2752074638', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2752074638'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2752074638'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2752074638/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2751294423}], 'type': 'gh_pull_request_review'}
2026-02-01T19:49:21.914600	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3736546050', 'comments': [{'id': 2751867162, 'node_id': 'PRRC_kwDOALCn2M6kBika', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751867162', 'pull_request_review_id': 3736546050, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                          if (m_card_memory_size > sizeof(m_card_memory))\n+                          {\n+                            ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                          "GC-AM: Command CARD GetState overflow:\\n"\n+                                          " - file name = {}\\n"\n+                                          " - file size = {}\\n"\n+                                          " - card size = {}",\n+                                          card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                            data_in = data_in_end;\n+                            break;\n+                          }\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = static_cast<u32>(File::GetSize(card_filename));\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                        }\n+\n+                        if (m_card_memory_size > sizeof(m_card_memory))\n+                        {\n+                          ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                        "GC-AM: Command CARD Read overflow:\\n"\n+                                        " - file name = {}\\n"\n+                                        " - file size = {}\\n"\n+                                        " - card size = {}",\n+                                        card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                          data_in = data_in_end;\n+                          break;\n+                        }\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+                      else if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Read overflow:\\n"\n+                                      " - requested size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      m_card_read_packet[0] = 0x02;  // SUB CMD\n+                      m_card_read_packet[1] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[2] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[3] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[3] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[4] = 0x30;\n+                      m_card_read_packet[5] = 0x30;\n+\n+                      u32 packet_offset = 6;\n+                      // Data reply\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6);\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7);\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8);\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9);\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+                      if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Write overflow:\\n"\n+                                      " - write size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9);\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode,\n+                                     bitmode, track, m_card_memory_size);\n+\n+                      const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) +\n+                                                      "tricard_" +\n+                                                      SConfig::GetInstance().GetGameID() + ".bin");\n+\n+                      File::IOFile card(card_filename, "wb+");\n+                      card.WriteBytes(m_card_memory, m_card_memory_size);\n+                      card.Close();\n+\n+                      m_card_bit = 2;\n+\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::SetPrintParam:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam");\n+                      break;\n+                    case CARDCommand::WriteInfo:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo");\n+                      break;\n+                    case CARDCommand::Erase:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase");\n+                      break;\n+                    case CARDCommand::Eject:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      break;\n+                    case CARDCommand::SetShutter:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      // Close\n+                      if (m_card_buffer[6] == 0x30)\n+                      {\n+                        m_card_shutter = false;\n+                      }\n+                      // Open\n+                      else if (m_card_buffer[6] == 0x31)\n+                      {\n+                        m_card_shutter = true;\n+                      }\n+                      break;\n+                    default:\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!");\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command);\n+                      // hexdump( m_card_buffer, m_card_offset );\n+                      break;\n+                    }\n+                    m_card_offset = 0;\n+                  }\n+                }\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x32;\n+              data_out[data_offset++] = 0x01;  // len\n+              data_out[data_offset++] = 0x06;  // OK\n+            }\n+          }\n+          else\n+          {\n+            if (!validate_data_in_out(0, 2, "SerialB"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;  // len\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::JVSIOA:\n+        case GCAMCommand::JVSIOB:\n+        {\n+          if (!validate_data_in_out(4, 0, "JVSIO"))\n+            break;\n+\n+          JVSIOMessage message;\n+\n+          static int delay = 0;\n+\n+          const u8* const frame = &data_in[0];\n+          const u8 nr_bytes = frame[3];  // Byte after E0 xx\n+          u32 frame_len = nr_bytes + 3;  // Header(2) + length byte + payload + checksum\n+\n+          u8 jvs_buf[0x80];\n+\n+          frame_len = std::min<u32>(frame_len, sizeof(jvs_buf));\n+\n+          if (!validate_data_in_out(frame_len, 0, "JVSIO"))\n+            break;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GC-AM: Command {:02x} (JVS IO), hexdump:\\n{}",\n+                        gcam_command, HexDump(data_in, frame_len));\n+\n+          memcpy(jvs_buf, frame, frame_len);\n+\n+          // Extract node and payload pointers\n+          u8 node = jvs_buf[2];\n+          u8* jvs_io = jvs_buf + 4;                 // First payload byte\n+          u8* const jvs_end = jvs_buf + frame_len;  // One byte before checksum\n+          u8* const jvs_begin = jvs_io;\n+\n+          message.Start(0);\n+          message.AddData(1);\n+\n+          // Helper to check that iterating over jvs_io n times is safe,\n+          // i.e. *jvs_io++ at most lead to jvs_end\n+          auto validate_jvs_io = [&](u32 n, std::string_view command) -> bool {\n+            if (jvs_io + n > jvs_end)\n+              ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: overflow in {}", command);\n+            else\n+              return true;\n+            ERROR_LOG_FMT(SERIALINTERFACE_JVSIO,\n+                          "Overflow details:\\n"\n+                          " - jvs_io(begin={}, current={}, end={}, n={})\\n"\n+                          " - delay={}, node={}\\n"\n+                          " - frame(begin={}, len={})",\n+                          fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, delay, node,\n+                          fmt::ptr(frame), frame_len);\n+            jvs_io = jvs_end;\n+            return false;\n+          };\n+\n+          // Now iterate over the payload\n+          while (jvs_io < jvs_end)\n+          {\n+            const u8 jvsio_command = *jvs_io++;\n+            DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:node={}, command={:02x}", node,\n+                          jvsio_command);\n+\n+            switch (JVSIOCommand(jvsio_command))\n+            {\n+            case JVSIOCommand::IOID:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+                // Specific version that enables DX mode on AX machines, all this does is enable the\n+                // motion of a chair\n+                message.AddData("SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;");\n+                break;\n+              case FZeroAXMonster:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                message.AddData("namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder");\n+                break;\n+              case VirtuaStriker3:\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                message.AddData("SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551;Ver1.00");\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x10, BoardID");\n+              message.AddData((u32)0);\n+              break;\n+            case JVSIOCommand::CommandRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x11);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x11, CommandRevision");\n+              break;\n+            case JVSIOCommand::JVRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x20);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x12, JVRevision");\n+              break;\n+            case JVSIOCommand::CommunicationVersion:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x10);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x13, CommunicationVersion");\n+              break;\n+\n+            // Slave features:\n+            //\n+            // Inputs:\n+            // 0x01: Switch input:  players,  buttons\n+            // 0x02: Coin input:    slots\n+            // 0x03: Analog input:  channels, bits\n+            // 0x04: Rotary input: channels\n+            // 0x05: Keycode input: 0,0,0 ?\n+            // 0x06: Screen position input: X bits, Y bits, channels\n+            //\n+            // Outputs:\n+            // 0x10: Card system: slots\n+            // 0x11: Medal hopper: channels\n+            // 0x12: GPO-out: slots\n+            // 0x13: Analog output: channels\n+            // 0x14: Character output: width, height, type\n+            // 0x15: Backup\n+            case JVSIOCommand::CheckFunctionality:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // 2 Player (12bit) (p2=paddles), 1 Coin slot, 6 Analog-in\n+                // message.AddData((void *)"\\x01\\x02\\x0C\\x00", 4);\n+                // message.AddData((void *)"\\x02\\x01\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x03\\x06\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x00\\x00\\x00\\x00", 4);\n+                //\n+                // DX Version: 2 Player (22bit) (p2=paddles), 2 Coin slot, 8 Analog-in,\n+                // 22 Driver-out\n+                message.AddData("\\x01\\x02\\x12\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x08\\x0A\\x00", 4);\n+                message.AddData("\\x12\\x16\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker3:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case GekitouProYakyuu:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                // 2 Player (13bit), 1 Coin slot, 4 Analog-in, 1 CARD\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case KeyOfAvalon:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, Touch, 1 CARD, 1 Driver-out\n+                // (Unconfirmed)\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x06\\x10\\x10\\x01", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, 1 CARD, 1 Driver-out\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality");\n+              break;\n+            case JVSIOCommand::MainID:\n+            {\n+              const u8* const main_id = jvs_io;\n+              while (jvs_io < jvs_end && *jvs_io++)\n+              {\n+              }\n+              if (main_id < jvs_io)\n+              {\n+                DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command MainId:\\n{}",\n+                              HexDump(main_id, jvs_io - main_id));\n+              }\n+              message.AddData(StatusOkay);\n+              break;\n+            }\n+            case JVSIOCommand::SwitchesInput:\n+            {\n+              if (!validate_jvs_io(2, "SwitchesInput"))\n+                break;\n+              const u32 player_count = *jvs_io++;\n+              const u32 player_byte_count = *jvs_io++;\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:  Command 0x20, SwitchInputs: {} {}",\n+                            player_count, player_byte_count);\n+\n+              message.AddData(StatusOkay);\n+\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              // Test button\n+              if (pad_status.switches & SWITCH_TEST)\n+              {\n+                // Trying to access the test menu without SegaBoot present will cause a crash\n+                if (AMMediaboard::GetTestMenu())\n+                {\n+                  message.AddData(0x80);\n+                }\n+                else\n+                {\n+                  PanicAlertFmt("Test menu is disabled due missing SegaBoot");\n+                }\n+              }\n+              else\n+              {\n+                message.AddData((u32)0x00);\n+              }\n+\n+              for (u32 i = 0; i < player_count; ++i)\n+              {\n+                u8 player_data[3]{};\n+\n+                // Service button\n+                if (pad_status.switches & SWITCH_SERVICE)\n+                  player_data[0] |= 0x40;\n+\n+                switch (AMMediaboard::GetGameType())\n+                {\n+                // Controller configuration for F-Zero AX (DX)\n+                case FZeroAX:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzdx_seatbelt)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzdx_motion_stop)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzdx_sensor_right)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzdx_sensor_left)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+\n+                    player_data[1] = m_rx_reply << 4;\n+                  }\n+                  break;\n+                // Controller configuration for F-Zero AX MonsterRide\n+                case FZeroAXMonster:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzcc_sensor)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzcc_seatbelt)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzcc_service)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzcc_emergency)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+                  }\n+                  break;\n+                // Controller configuration for Virtua Striker 3 games\n+                case VirtuaStriker3:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x02;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Virtua Striker 4 games\n+                case VirtuaStriker4:\n+                case VirtuaStriker4_2006:\n+                {\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x80;\n+                  // Dash\n+                  if (pad_status.button & PAD_BUTTON_Y)\n+                    player_data[1] |= 0x40;\n+                  // Tactics (U)\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x20;\n+                  // Tactics (M)\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x08;\n+                  // Tactics (D)\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+\n+                  if (i == 0)\n+                  {\n+                    player_data[0] |= 0x10;  // IC-Card Switch ON\n+\n+                    // IC-Card Lock\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[1] |= 0x20;\n+                  }\n+                }\n+                break;\n+                // Controller configuration for Gekitou Pro Yakyuu\n+                case GekitouProYakyuu:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  //  A\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  //  B\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  //  Gekitou\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                    player_data[1] |= 0x80;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Mario Kart and other games\n+                default:\n+                case MarioKartGP:\n+                case MarioKartGP2:\n+                {\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Item button\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x20;\n+                  // VS-Cancel button\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x02;\n+                }\n+                break;\n+                case KeyOfAvalon:\n+                {\n+                  // Debug On\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Switch 1\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x04;\n+                  // Switch 2\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x08;\n+                  // Toggle inserted card\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                  {\n+                    m_ic_card_status ^= ICCARDStatus::NoCard;\n+                  }\n+                }\n+                break;\n+                }\n+\n+                for (u32 j = 0; j < player_byte_count; ++j)\n+                  message.AddData(player_data[j]);\n+              }\n+              break;\n+            }\n+            case JVSIOCommand::CoinInput:\n+            {\n+              if (!validate_jvs_io(1, "CoinInput"))\n+                break;\n+              const u32 slots = *jvs_io++;\n+              message.AddData(StatusOkay);\n+              for (u32 i = 0; i < slots; i++)\n+              {\n+                GCPadStatus pad_status = Pad::GetStatus(i);\n+                if ((pad_status.switches & SWITCH_COIN) && !m_coin_pressed[i])\n+                {\n+                  m_coin[i]++;\n+                }\n+                m_coin_pressed[i] = pad_status.switches & SWITCH_COIN;\n+                message.AddData((m_coin[i] >> 8) & 0x3f);\n+                message.AddData(m_coin[i] & 0xff);\n+              }\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x21, CoinInput: {}", slots);\n+              break;\n+            }\n+            case JVSIOCommand::AnalogInput:\n+            {\n+              if (!validate_jvs_io(1, "AnalogInput"))\n+                break;\n+              message.AddData(StatusOkay);\n+\n+              const u32 analogs = *jvs_io++;\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x22, AnalogInput: {}",\n+                            analogs);\n+\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // Steering\n+                if (m_motor_init == 1)\n+                {\n+                  if (m_motor_force_y > 0)\n+                  {\n+                    message.AddData(0x80 - (m_motor_force_y >> 8));\n+                  }\n+                  else\n+                  {\n+                    message.AddData((m_motor_force_y >> 8));\n+                  }\n+                  message.AddData((u8)0);\n+\n+                  message.AddData(pad_status.stickY);\n+                  message.AddData((u8)0);\n+                }\n+                else\n+                {\n+                  // The center for the Y axis is expected to be 78h this adjusts that\n+                  message.AddData(pad_status.stickX - 12);\n+                  message.AddData((u8)0);\n+\n+                  message.AddData(pad_status.stickY);\n+                  message.AddData((u8)0);\n+                }\n+\n+                // Unused\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+\n+                // Gas\n+                message.AddData(pad_status.triggerRight);\n+                message.AddData((u8)0);\n+\n+                // Brake\n+                message.AddData(pad_status.triggerLeft);\n+                message.AddData((u8)0);\n+\n+                message.AddData((u8)0x80);  // Motion Stop\n+                message.AddData((u8)0);\n+\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+              {\n+                message.AddData(-pad_status.stickY);\n+                message.AddData((u8)0);\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+\n+                pad_status = Pad::GetStatus(1);\n+\n+                message.AddData(-pad_status.stickY);\n+                message.AddData((u8)0);\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+              }\n+              break;\n+              default:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+                // Steering\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+\n+                // Gas\n+                message.AddData(pad_status.triggerRight);\n+                message.AddData((u8)0);\n+\n+                // Brake\n+                message.AddData(pad_status.triggerLeft);\n+                message.AddData((u8)0);\n+                break;\n+              }\n+              break;\n+            }\n+            case JVSIOCommand::PositionInput:\n+            {\n+              if (!validate_jvs_io(1, "PositionInput"))\n+                break;\n+              const u32 channel = *jvs_io++;\n+\n+              const GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              if (pad_status.button & PAD_TRIGGER_R)\n+              {\n+                // Tap at center of screen (~320,240)\n+                message.AddData("\\x01\\x00\\x8C\\x01\\x95",\n+                                5);  // X=320 (0x0140), Y=240 (0x00F0)\n+              }\n+              else\n+              {\n+                message.AddData("\\x01\\xFF\\xFF\\xFF\\xFF", 5);\n+              }\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x25, PositionInput:{}",\n+                            channel);\n+              break;\n+            }\n+            case JVSIOCommand::CoinSubOutput:\n+            {\n+              if (!validate_jvs_io(3, "CoinSubOutput"))\n+                break;\n+              const u32 slot = *jvs_io++;\n+              const u8 coinh = *jvs_io++;\n+              const u8 coinl = *jvs_io++;\n+\n+              m_coin[slot] -= (coinh << 8) | coinl;\n+\n+              message.AddData(StatusOkay);\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x30, CoinSubOutput: {}", slot);\n+              break;\n+            }\n+            case JVSIOCommand::GeneralDriverOutput:\n+            {\n+              if (!validate_jvs_io(1, "GeneralDriverOutput"))\n+                break;\n+              const u32 bytes = *jvs_io++;\n+\n+              if (bytes)\n+              {\n+                message.AddData(StatusOkay);\n+\n+                // The lamps are controlled via this\n+                if (AMMediaboard::GetGameType() == MarioKartGP)\n+                {\n+                  if (!validate_jvs_io(1, "GeneralDriverOutput (MarioKartGP)"))\n+                    break;\n+                  const u32 status = *jvs_io++;\n+                  if (status & 4)\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Item Button ON");\n+                  }\n+                  else\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Item Button OFF");\n+                  }\n+                  if (status & 8)\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Cancel Button ON");\n+                  }\n+                  else\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Cancel Button OFF");\n+                  }\n+                  break;\n+                }\n+\n+                if (!validate_jvs_io(bytes, "GeneralDriverOutput"))\n+                  break;\n+                Common::UniqueBuffer<u8> buf(bytes);\n+\n+                for (u32 i = 0; i < bytes; ++i)\n+                {\n+                  buf[i] = *jvs_io++;', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 2326, 'original_position': 2326, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Fair point. Moreover, `jvs_io` is checked against `bytes` but we don't check that bytes is greater than 2 before accessing the buffer for the info log. Will be addressed.", 'created_at': '2026-02-01T19:49:19Z', 'updated_at': '2026-02-01T19:49:20Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751867162', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751867162'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751867162'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751867162/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2751395550}], 'type': 'gh_pull_request_review'}
2026-02-01T15:40:28.820404	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3735806017', 'comments': [{'id': 2751294423, 'node_id': 'PRRC_kwDOALCn2M6j_WvX', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751294423', 'pull_request_review_id': 3735806017, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1345, 'original_position': 1345, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "I think `m_card_buffer[0]` contains the same value as `length`. If that's the case, this array offset is safe, but this is hard to reason about. If my analysis is correct, could we do something like replacing `m_card_buffer[0] == 0x02` with `length == 0x02`, or replacing `m_card_offset - 2` with a copy of `m_card_offset` from before we incremented it by `length`?", 'created_at': '2026-02-01T14:18:38Z', 'updated_at': '2026-02-01T15:40:26Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751294423', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751294423'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751294423'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751294423/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2751296052, 'node_id': 'PRRC_kwDOALCn2M6j_XI0', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751296052', 'pull_request_review_id': 3735806017, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1345, 'original_position': 1345, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Also, we should probably use `&&` instead of three nested `if`s. The indentation is getting pretty big here.', 'created_at': '2026-02-01T14:20:34Z', 'updated_at': '2026-02-01T15:40:26Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751296052', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751296052'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751296052'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751296052/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2751294423}, {'id': 2751374597, 'node_id': 'PRRC_kwDOALCn2M6j_qUF', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751374597', 'pull_request_review_id': 3735806017, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                          if (m_card_memory_size > sizeof(m_card_memory))\n+                          {\n+                            ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                          "GC-AM: Command CARD GetState overflow:\\n"\n+                                          " - file name = {}\\n"\n+                                          " - file size = {}\\n"\n+                                          " - card size = {}",\n+                                          card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                            data_in = data_in_end;\n+                            break;\n+                          }\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = static_cast<u32>(File::GetSize(card_filename));\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                        }\n+\n+                        if (m_card_memory_size > sizeof(m_card_memory))\n+                        {\n+                          ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                        "GC-AM: Command CARD Read overflow:\\n"\n+                                        " - file name = {}\\n"\n+                                        " - file size = {}\\n"\n+                                        " - card size = {}",\n+                                        card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                          data_in = data_in_end;\n+                          break;\n+                        }\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+                      else if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Read overflow:\\n"\n+                                      " - requested size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      m_card_read_packet[0] = 0x02;  // SUB CMD\n+                      m_card_read_packet[1] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[2] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[3] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[3] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[4] = 0x30;\n+                      m_card_read_packet[5] = 0x30;\n+\n+                      u32 packet_offset = 6;\n+                      // Data reply\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6);\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7);\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8);\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9);\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+                      if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Write overflow:\\n"\n+                                      " - write size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9);\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode,\n+                                     bitmode, track, m_card_memory_size);\n+\n+                      const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) +\n+                                                      "tricard_" +\n+                                                      SConfig::GetInstance().GetGameID() + ".bin");\n+\n+                      File::IOFile card(card_filename, "wb+");\n+                      card.WriteBytes(m_card_memory, m_card_memory_size);\n+                      card.Close();\n+\n+                      m_card_bit = 2;\n+\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::SetPrintParam:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam");\n+                      break;\n+                    case CARDCommand::WriteInfo:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo");\n+                      break;\n+                    case CARDCommand::Erase:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase");\n+                      break;\n+                    case CARDCommand::Eject:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      break;\n+                    case CARDCommand::SetShutter:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      // Close\n+                      if (m_card_buffer[6] == 0x30)\n+                      {\n+                        m_card_shutter = false;\n+                      }\n+                      // Open\n+                      else if (m_card_buffer[6] == 0x31)\n+                      {\n+                        m_card_shutter = true;\n+                      }\n+                      break;\n+                    default:\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!");\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command);\n+                      // hexdump( m_card_buffer, m_card_offset );\n+                      break;\n+                    }\n+                    m_card_offset = 0;\n+                  }\n+                }\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x32;\n+              data_out[data_offset++] = 0x01;  // len\n+              data_out[data_offset++] = 0x06;  // OK\n+            }\n+          }\n+          else\n+          {\n+            if (!validate_data_in_out(0, 2, "SerialB"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;  // len\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::JVSIOA:\n+        case GCAMCommand::JVSIOB:\n+        {\n+          if (!validate_data_in_out(4, 0, "JVSIO"))\n+            break;\n+\n+          JVSIOMessage message;\n+\n+          static int delay = 0;\n+\n+          const u8* const frame = &data_in[0];\n+          const u8 nr_bytes = frame[3];  // Byte after E0 xx\n+          u32 frame_len = nr_bytes + 3;  // Header(2) + length byte + payload + checksum\n+\n+          u8 jvs_buf[0x80];\n+\n+          frame_len = std::min<u32>(frame_len, sizeof(jvs_buf));\n+\n+          if (!validate_data_in_out(frame_len, 0, "JVSIO"))\n+            break;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GC-AM: Command {:02x} (JVS IO), hexdump:\\n{}",\n+                        gcam_command, HexDump(data_in, frame_len));\n+\n+          memcpy(jvs_buf, frame, frame_len);\n+\n+          // Extract node and payload pointers\n+          u8 node = jvs_buf[2];\n+          u8* jvs_io = jvs_buf + 4;                 // First payload byte\n+          u8* const jvs_end = jvs_buf + frame_len;  // One byte before checksum\n+          u8* const jvs_begin = jvs_io;\n+\n+          message.Start(0);\n+          message.AddData(1);\n+\n+          // Helper to check that iterating over jvs_io n times is safe,\n+          // i.e. *jvs_io++ at most lead to jvs_end\n+          auto validate_jvs_io = [&](u32 n, std::string_view command) -> bool {\n+            if (jvs_io + n > jvs_end)\n+              ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: overflow in {}", command);\n+            else\n+              return true;\n+            ERROR_LOG_FMT(SERIALINTERFACE_JVSIO,\n+                          "Overflow details:\\n"\n+                          " - jvs_io(begin={}, current={}, end={}, n={})\\n"\n+                          " - delay={}, node={}\\n"\n+                          " - frame(begin={}, len={})",\n+                          fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, delay, node,\n+                          fmt::ptr(frame), frame_len);\n+            jvs_io = jvs_end;\n+            return false;\n+          };\n+\n+          // Now iterate over the payload\n+          while (jvs_io < jvs_end)\n+          {\n+            const u8 jvsio_command = *jvs_io++;\n+            DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:node={}, command={:02x}", node,\n+                          jvsio_command);\n+\n+            switch (JVSIOCommand(jvsio_command))\n+            {\n+            case JVSIOCommand::IOID:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+                // Specific version that enables DX mode on AX machines, all this does is enable the\n+                // motion of a chair\n+                message.AddData("SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;");\n+                break;\n+              case FZeroAXMonster:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                message.AddData("namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder");\n+                break;\n+              case VirtuaStriker3:\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                message.AddData("SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551;Ver1.00");\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x10, BoardID");\n+              message.AddData((u32)0);\n+              break;\n+            case JVSIOCommand::CommandRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x11);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x11, CommandRevision");\n+              break;\n+            case JVSIOCommand::JVRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x20);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x12, JVRevision");\n+              break;\n+            case JVSIOCommand::CommunicationVersion:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x10);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x13, CommunicationVersion");\n+              break;\n+\n+            // Slave features:\n+            //\n+            // Inputs:\n+            // 0x01: Switch input:  players,  buttons\n+            // 0x02: Coin input:    slots\n+            // 0x03: Analog input:  channels, bits\n+            // 0x04: Rotary input: channels\n+            // 0x05: Keycode input: 0,0,0 ?\n+            // 0x06: Screen position input: X bits, Y bits, channels\n+            //\n+            // Outputs:\n+            // 0x10: Card system: slots\n+            // 0x11: Medal hopper: channels\n+            // 0x12: GPO-out: slots\n+            // 0x13: Analog output: channels\n+            // 0x14: Character output: width, height, type\n+            // 0x15: Backup\n+            case JVSIOCommand::CheckFunctionality:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // 2 Player (12bit) (p2=paddles), 1 Coin slot, 6 Analog-in\n+                // message.AddData((void *)"\\x01\\x02\\x0C\\x00", 4);\n+                // message.AddData((void *)"\\x02\\x01\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x03\\x06\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x00\\x00\\x00\\x00", 4);\n+                //\n+                // DX Version: 2 Player (22bit) (p2=paddles), 2 Coin slot, 8 Analog-in,\n+                // 22 Driver-out\n+                message.AddData("\\x01\\x02\\x12\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x08\\x0A\\x00", 4);\n+                message.AddData("\\x12\\x16\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker3:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case GekitouProYakyuu:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                // 2 Player (13bit), 1 Coin slot, 4 Analog-in, 1 CARD\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case KeyOfAvalon:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, Touch, 1 CARD, 1 Driver-out\n+                // (Unconfirmed)\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x06\\x10\\x10\\x01", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, 1 CARD, 1 Driver-out\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality");\n+              break;\n+            case JVSIOCommand::MainID:\n+            {\n+              const u8* const main_id = jvs_io;\n+              while (jvs_io < jvs_end && *jvs_io++)\n+              {\n+              }\n+              if (main_id < jvs_io)\n+              {\n+                DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command MainId:\\n{}",\n+                              HexDump(main_id, jvs_io - main_id));\n+              }\n+              message.AddData(StatusOkay);\n+              break;\n+            }\n+            case JVSIOCommand::SwitchesInput:\n+            {\n+              if (!validate_jvs_io(2, "SwitchesInput"))\n+                break;\n+              const u32 player_count = *jvs_io++;\n+              const u32 player_byte_count = *jvs_io++;\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:  Command 0x20, SwitchInputs: {} {}",\n+                            player_count, player_byte_count);\n+\n+              message.AddData(StatusOkay);\n+\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              // Test button\n+              if (pad_status.switches & SWITCH_TEST)\n+              {\n+                // Trying to access the test menu without SegaBoot present will cause a crash\n+                if (AMMediaboard::GetTestMenu())\n+                {\n+                  message.AddData(0x80);\n+                }\n+                else\n+                {\n+                  PanicAlertFmt("Test menu is disabled due missing SegaBoot");\n+                }\n+              }\n+              else\n+              {\n+                message.AddData((u32)0x00);\n+              }\n+\n+              for (u32 i = 0; i < player_count; ++i)\n+              {\n+                u8 player_data[3]{};\n+\n+                // Service button\n+                if (pad_status.switches & SWITCH_SERVICE)\n+                  player_data[0] |= 0x40;\n+\n+                switch (AMMediaboard::GetGameType())\n+                {\n+                // Controller configuration for F-Zero AX (DX)\n+                case FZeroAX:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzdx_seatbelt)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzdx_motion_stop)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzdx_sensor_right)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzdx_sensor_left)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+\n+                    player_data[1] = m_rx_reply << 4;\n+                  }\n+                  break;\n+                // Controller configuration for F-Zero AX MonsterRide\n+                case FZeroAXMonster:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzcc_sensor)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzcc_seatbelt)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzcc_service)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzcc_emergency)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+                  }\n+                  break;\n+                // Controller configuration for Virtua Striker 3 games\n+                case VirtuaStriker3:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x02;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Virtua Striker 4 games\n+                case VirtuaStriker4:\n+                case VirtuaStriker4_2006:\n+                {\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x80;\n+                  // Dash\n+                  if (pad_status.button & PAD_BUTTON_Y)\n+                    player_data[1] |= 0x40;\n+                  // Tactics (U)\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x20;\n+                  // Tactics (M)\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x08;\n+                  // Tactics (D)\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+\n+                  if (i == 0)\n+                  {\n+                    player_data[0] |= 0x10;  // IC-Card Switch ON\n+\n+                    // IC-Card Lock\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[1] |= 0x20;\n+                  }\n+                }\n+                break;\n+                // Controller configuration for Gekitou Pro Yakyuu\n+                case GekitouProYakyuu:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  //  A\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  //  B\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  //  Gekitou\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                    player_data[1] |= 0x80;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Mario Kart and other games\n+                default:\n+                case MarioKartGP:\n+                case MarioKartGP2:\n+                {\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Item button\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x20;\n+                  // VS-Cancel button\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x02;\n+                }\n+                break;\n+                case KeyOfAvalon:\n+                {\n+                  // Debug On\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Switch 1\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x04;\n+                  // Switch 2\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x08;\n+                  // Toggle inserted card\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                  {\n+                    m_ic_card_status ^= ICCARDStatus::NoCard;\n+                  }\n+                }\n+                break;\n+                }\n+\n+                for (u32 j = 0; j < player_byte_count; ++j)\n+                  message.AddData(player_data[j]);', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 2127, 'original_position': 2127, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '`player_byte_count` might be larger than the size of `player_data`.', 'created_at': '2026-02-01T15:19:06Z', 'updated_at': '2026-02-01T15:40:26Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751374597', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751374597'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751374597'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751374597/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2751386784, 'node_id': 'PRRC_kwDOALCn2M6j_tSg', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751386784', 'pull_request_review_id': 3735806017, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                          if (m_card_memory_size > sizeof(m_card_memory))\n+                          {\n+                            ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                          "GC-AM: Command CARD GetState overflow:\\n"\n+                                          " - file name = {}\\n"\n+                                          " - file size = {}\\n"\n+                                          " - card size = {}",\n+                                          card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                            data_in = data_in_end;\n+                            break;\n+                          }\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = static_cast<u32>(File::GetSize(card_filename));\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                        }\n+\n+                        if (m_card_memory_size > sizeof(m_card_memory))\n+                        {\n+                          ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                        "GC-AM: Command CARD Read overflow:\\n"\n+                                        " - file name = {}\\n"\n+                                        " - file size = {}\\n"\n+                                        " - card size = {}",\n+                                        card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                          data_in = data_in_end;\n+                          break;\n+                        }\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+                      else if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Read overflow:\\n"\n+                                      " - requested size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      m_card_read_packet[0] = 0x02;  // SUB CMD\n+                      m_card_read_packet[1] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[2] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[3] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[3] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[4] = 0x30;\n+                      m_card_read_packet[5] = 0x30;\n+\n+                      u32 packet_offset = 6;\n+                      // Data reply\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6);\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7);\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8);\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9);\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+                      if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Write overflow:\\n"\n+                                      " - write size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9);\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode,\n+                                     bitmode, track, m_card_memory_size);\n+\n+                      const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) +\n+                                                      "tricard_" +\n+                                                      SConfig::GetInstance().GetGameID() + ".bin");\n+\n+                      File::IOFile card(card_filename, "wb+");\n+                      card.WriteBytes(m_card_memory, m_card_memory_size);\n+                      card.Close();\n+\n+                      m_card_bit = 2;\n+\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::SetPrintParam:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam");\n+                      break;\n+                    case CARDCommand::WriteInfo:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo");\n+                      break;\n+                    case CARDCommand::Erase:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase");\n+                      break;\n+                    case CARDCommand::Eject:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      break;\n+                    case CARDCommand::SetShutter:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      // Close\n+                      if (m_card_buffer[6] == 0x30)\n+                      {\n+                        m_card_shutter = false;\n+                      }\n+                      // Open\n+                      else if (m_card_buffer[6] == 0x31)\n+                      {\n+                        m_card_shutter = true;\n+                      }\n+                      break;\n+                    default:\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!");\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command);\n+                      // hexdump( m_card_buffer, m_card_offset );\n+                      break;\n+                    }\n+                    m_card_offset = 0;\n+                  }\n+                }\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x32;\n+              data_out[data_offset++] = 0x01;  // len\n+              data_out[data_offset++] = 0x06;  // OK\n+            }\n+          }\n+          else\n+          {\n+            if (!validate_data_in_out(0, 2, "SerialB"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;  // len\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::JVSIOA:\n+        case GCAMCommand::JVSIOB:\n+        {\n+          if (!validate_data_in_out(4, 0, "JVSIO"))\n+            break;\n+\n+          JVSIOMessage message;\n+\n+          static int delay = 0;\n+\n+          const u8* const frame = &data_in[0];\n+          const u8 nr_bytes = frame[3];  // Byte after E0 xx\n+          u32 frame_len = nr_bytes + 3;  // Header(2) + length byte + payload + checksum\n+\n+          u8 jvs_buf[0x80];\n+\n+          frame_len = std::min<u32>(frame_len, sizeof(jvs_buf));\n+\n+          if (!validate_data_in_out(frame_len, 0, "JVSIO"))\n+            break;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GC-AM: Command {:02x} (JVS IO), hexdump:\\n{}",\n+                        gcam_command, HexDump(data_in, frame_len));\n+\n+          memcpy(jvs_buf, frame, frame_len);\n+\n+          // Extract node and payload pointers\n+          u8 node = jvs_buf[2];\n+          u8* jvs_io = jvs_buf + 4;                 // First payload byte\n+          u8* const jvs_end = jvs_buf + frame_len;  // One byte before checksum\n+          u8* const jvs_begin = jvs_io;\n+\n+          message.Start(0);\n+          message.AddData(1);\n+\n+          // Helper to check that iterating over jvs_io n times is safe,\n+          // i.e. *jvs_io++ at most lead to jvs_end\n+          auto validate_jvs_io = [&](u32 n, std::string_view command) -> bool {\n+            if (jvs_io + n > jvs_end)\n+              ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: overflow in {}", command);\n+            else\n+              return true;\n+            ERROR_LOG_FMT(SERIALINTERFACE_JVSIO,\n+                          "Overflow details:\\n"\n+                          " - jvs_io(begin={}, current={}, end={}, n={})\\n"\n+                          " - delay={}, node={}\\n"\n+                          " - frame(begin={}, len={})",\n+                          fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, delay, node,\n+                          fmt::ptr(frame), frame_len);\n+            jvs_io = jvs_end;\n+            return false;\n+          };\n+\n+          // Now iterate over the payload\n+          while (jvs_io < jvs_end)\n+          {\n+            const u8 jvsio_command = *jvs_io++;\n+            DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:node={}, command={:02x}", node,\n+                          jvsio_command);\n+\n+            switch (JVSIOCommand(jvsio_command))\n+            {\n+            case JVSIOCommand::IOID:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+                // Specific version that enables DX mode on AX machines, all this does is enable the\n+                // motion of a chair\n+                message.AddData("SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;");\n+                break;\n+              case FZeroAXMonster:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                message.AddData("namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder");\n+                break;\n+              case VirtuaStriker3:\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                message.AddData("SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551;Ver1.00");\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x10, BoardID");\n+              message.AddData((u32)0);\n+              break;\n+            case JVSIOCommand::CommandRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x11);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x11, CommandRevision");\n+              break;\n+            case JVSIOCommand::JVRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x20);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x12, JVRevision");\n+              break;\n+            case JVSIOCommand::CommunicationVersion:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x10);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x13, CommunicationVersion");\n+              break;\n+\n+            // Slave features:\n+            //\n+            // Inputs:\n+            // 0x01: Switch input:  players,  buttons\n+            // 0x02: Coin input:    slots\n+            // 0x03: Analog input:  channels, bits\n+            // 0x04: Rotary input: channels\n+            // 0x05: Keycode input: 0,0,0 ?\n+            // 0x06: Screen position input: X bits, Y bits, channels\n+            //\n+            // Outputs:\n+            // 0x10: Card system: slots\n+            // 0x11: Medal hopper: channels\n+            // 0x12: GPO-out: slots\n+            // 0x13: Analog output: channels\n+            // 0x14: Character output: width, height, type\n+            // 0x15: Backup\n+            case JVSIOCommand::CheckFunctionality:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // 2 Player (12bit) (p2=paddles), 1 Coin slot, 6 Analog-in\n+                // message.AddData((void *)"\\x01\\x02\\x0C\\x00", 4);\n+                // message.AddData((void *)"\\x02\\x01\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x03\\x06\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x00\\x00\\x00\\x00", 4);\n+                //\n+                // DX Version: 2 Player (22bit) (p2=paddles), 2 Coin slot, 8 Analog-in,\n+                // 22 Driver-out\n+                message.AddData("\\x01\\x02\\x12\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x08\\x0A\\x00", 4);\n+                message.AddData("\\x12\\x16\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker3:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case GekitouProYakyuu:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                // 2 Player (13bit), 1 Coin slot, 4 Analog-in, 1 CARD\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case KeyOfAvalon:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, Touch, 1 CARD, 1 Driver-out\n+                // (Unconfirmed)\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x06\\x10\\x10\\x01", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, 1 CARD, 1 Driver-out\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality");\n+              break;\n+            case JVSIOCommand::MainID:\n+            {\n+              const u8* const main_id = jvs_io;\n+              while (jvs_io < jvs_end && *jvs_io++)\n+              {\n+              }\n+              if (main_id < jvs_io)\n+              {\n+                DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command MainId:\\n{}",\n+                              HexDump(main_id, jvs_io - main_id));\n+              }\n+              message.AddData(StatusOkay);\n+              break;\n+            }\n+            case JVSIOCommand::SwitchesInput:\n+            {\n+              if (!validate_jvs_io(2, "SwitchesInput"))\n+                break;\n+              const u32 player_count = *jvs_io++;\n+              const u32 player_byte_count = *jvs_io++;\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:  Command 0x20, SwitchInputs: {} {}",\n+                            player_count, player_byte_count);\n+\n+              message.AddData(StatusOkay);\n+\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              // Test button\n+              if (pad_status.switches & SWITCH_TEST)\n+              {\n+                // Trying to access the test menu without SegaBoot present will cause a crash\n+                if (AMMediaboard::GetTestMenu())\n+                {\n+                  message.AddData(0x80);\n+                }\n+                else\n+                {\n+                  PanicAlertFmt("Test menu is disabled due missing SegaBoot");\n+                }\n+              }\n+              else\n+              {\n+                message.AddData((u32)0x00);\n+              }\n+\n+              for (u32 i = 0; i < player_count; ++i)\n+              {\n+                u8 player_data[3]{};\n+\n+                // Service button\n+                if (pad_status.switches & SWITCH_SERVICE)\n+                  player_data[0] |= 0x40;\n+\n+                switch (AMMediaboard::GetGameType())\n+                {\n+                // Controller configuration for F-Zero AX (DX)\n+                case FZeroAX:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzdx_seatbelt)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzdx_motion_stop)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzdx_sensor_right)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzdx_sensor_left)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+\n+                    player_data[1] = m_rx_reply << 4;\n+                  }\n+                  break;\n+                // Controller configuration for F-Zero AX MonsterRide\n+                case FZeroAXMonster:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzcc_sensor)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzcc_seatbelt)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzcc_service)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzcc_emergency)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+                  }\n+                  break;\n+                // Controller configuration for Virtua Striker 3 games\n+                case VirtuaStriker3:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x02;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Virtua Striker 4 games\n+                case VirtuaStriker4:\n+                case VirtuaStriker4_2006:\n+                {\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x80;\n+                  // Dash\n+                  if (pad_status.button & PAD_BUTTON_Y)\n+                    player_data[1] |= 0x40;\n+                  // Tactics (U)\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x20;\n+                  // Tactics (M)\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x08;\n+                  // Tactics (D)\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+\n+                  if (i == 0)\n+                  {\n+                    player_data[0] |= 0x10;  // IC-Card Switch ON\n+\n+                    // IC-Card Lock\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[1] |= 0x20;\n+                  }\n+                }\n+                break;\n+                // Controller configuration for Gekitou Pro Yakyuu\n+                case GekitouProYakyuu:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  //  A\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  //  B\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  //  Gekitou\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                    player_data[1] |= 0x80;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Mario Kart and other games\n+                default:\n+                case MarioKartGP:\n+                case MarioKartGP2:\n+                {\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Item button\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x20;\n+                  // VS-Cancel button\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x02;\n+                }\n+                break;\n+                case KeyOfAvalon:\n+                {\n+                  // Debug On\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Switch 1\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x04;\n+                  // Switch 2\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x08;\n+                  // Toggle inserted card\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                  {\n+                    m_ic_card_status ^= ICCARDStatus::NoCard;\n+                  }\n+                }\n+                break;\n+                }\n+\n+                for (u32 j = 0; j < player_byte_count; ++j)\n+                  message.AddData(player_data[j]);\n+              }\n+              break;\n+            }\n+            case JVSIOCommand::CoinInput:\n+            {\n+              if (!validate_jvs_io(1, "CoinInput"))\n+                break;\n+              const u32 slots = *jvs_io++;\n+              message.AddData(StatusOkay);\n+              for (u32 i = 0; i < slots; i++)\n+              {\n+                GCPadStatus pad_status = Pad::GetStatus(i);\n+                if ((pad_status.switches & SWITCH_COIN) && !m_coin_pressed[i])\n+                {\n+                  m_coin[i]++;', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 2142, 'original_position': 2142, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '`slots` might be larger than 2, which is the number of elements in `m_coin` and `m_coin_pressed`.', 'created_at': '2026-02-01T15:23:06Z', 'updated_at': '2026-02-01T15:40:26Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751386784', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751386784'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751386784'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751386784/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2751389783, 'node_id': 'PRRC_kwDOALCn2M6j_uBX', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751389783', 'pull_request_review_id': 3735806017, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                          if (m_card_memory_size > sizeof(m_card_memory))\n+                          {\n+                            ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                          "GC-AM: Command CARD GetState overflow:\\n"\n+                                          " - file name = {}\\n"\n+                                          " - file size = {}\\n"\n+                                          " - card size = {}",\n+                                          card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                            data_in = data_in_end;\n+                            break;\n+                          }\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = static_cast<u32>(File::GetSize(card_filename));\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                        }\n+\n+                        if (m_card_memory_size > sizeof(m_card_memory))\n+                        {\n+                          ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                        "GC-AM: Command CARD Read overflow:\\n"\n+                                        " - file name = {}\\n"\n+                                        " - file size = {}\\n"\n+                                        " - card size = {}",\n+                                        card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                          data_in = data_in_end;\n+                          break;\n+                        }\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+                      else if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Read overflow:\\n"\n+                                      " - requested size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      m_card_read_packet[0] = 0x02;  // SUB CMD\n+                      m_card_read_packet[1] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[2] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[3] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[3] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[4] = 0x30;\n+                      m_card_read_packet[5] = 0x30;\n+\n+                      u32 packet_offset = 6;\n+                      // Data reply\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6);\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7);\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8);\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9);\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+                      if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Write overflow:\\n"\n+                                      " - write size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9);\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode,\n+                                     bitmode, track, m_card_memory_size);\n+\n+                      const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) +\n+                                                      "tricard_" +\n+                                                      SConfig::GetInstance().GetGameID() + ".bin");\n+\n+                      File::IOFile card(card_filename, "wb+");\n+                      card.WriteBytes(m_card_memory, m_card_memory_size);\n+                      card.Close();\n+\n+                      m_card_bit = 2;\n+\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::SetPrintParam:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam");\n+                      break;\n+                    case CARDCommand::WriteInfo:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo");\n+                      break;\n+                    case CARDCommand::Erase:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase");\n+                      break;\n+                    case CARDCommand::Eject:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      break;\n+                    case CARDCommand::SetShutter:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      // Close\n+                      if (m_card_buffer[6] == 0x30)\n+                      {\n+                        m_card_shutter = false;\n+                      }\n+                      // Open\n+                      else if (m_card_buffer[6] == 0x31)\n+                      {\n+                        m_card_shutter = true;\n+                      }\n+                      break;\n+                    default:\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!");\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command);\n+                      // hexdump( m_card_buffer, m_card_offset );\n+                      break;\n+                    }\n+                    m_card_offset = 0;\n+                  }\n+                }\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x32;\n+              data_out[data_offset++] = 0x01;  // len\n+              data_out[data_offset++] = 0x06;  // OK\n+            }\n+          }\n+          else\n+          {\n+            if (!validate_data_in_out(0, 2, "SerialB"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;  // len\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::JVSIOA:\n+        case GCAMCommand::JVSIOB:\n+        {\n+          if (!validate_data_in_out(4, 0, "JVSIO"))\n+            break;\n+\n+          JVSIOMessage message;\n+\n+          static int delay = 0;\n+\n+          const u8* const frame = &data_in[0];\n+          const u8 nr_bytes = frame[3];  // Byte after E0 xx\n+          u32 frame_len = nr_bytes + 3;  // Header(2) + length byte + payload + checksum\n+\n+          u8 jvs_buf[0x80];\n+\n+          frame_len = std::min<u32>(frame_len, sizeof(jvs_buf));\n+\n+          if (!validate_data_in_out(frame_len, 0, "JVSIO"))\n+            break;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GC-AM: Command {:02x} (JVS IO), hexdump:\\n{}",\n+                        gcam_command, HexDump(data_in, frame_len));\n+\n+          memcpy(jvs_buf, frame, frame_len);\n+\n+          // Extract node and payload pointers\n+          u8 node = jvs_buf[2];\n+          u8* jvs_io = jvs_buf + 4;                 // First payload byte\n+          u8* const jvs_end = jvs_buf + frame_len;  // One byte before checksum\n+          u8* const jvs_begin = jvs_io;\n+\n+          message.Start(0);\n+          message.AddData(1);\n+\n+          // Helper to check that iterating over jvs_io n times is safe,\n+          // i.e. *jvs_io++ at most lead to jvs_end\n+          auto validate_jvs_io = [&](u32 n, std::string_view command) -> bool {\n+            if (jvs_io + n > jvs_end)\n+              ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: overflow in {}", command);\n+            else\n+              return true;\n+            ERROR_LOG_FMT(SERIALINTERFACE_JVSIO,\n+                          "Overflow details:\\n"\n+                          " - jvs_io(begin={}, current={}, end={}, n={})\\n"\n+                          " - delay={}, node={}\\n"\n+                          " - frame(begin={}, len={})",\n+                          fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, delay, node,\n+                          fmt::ptr(frame), frame_len);\n+            jvs_io = jvs_end;\n+            return false;\n+          };\n+\n+          // Now iterate over the payload\n+          while (jvs_io < jvs_end)\n+          {\n+            const u8 jvsio_command = *jvs_io++;\n+            DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:node={}, command={:02x}", node,\n+                          jvsio_command);\n+\n+            switch (JVSIOCommand(jvsio_command))\n+            {\n+            case JVSIOCommand::IOID:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+                // Specific version that enables DX mode on AX machines, all this does is enable the\n+                // motion of a chair\n+                message.AddData("SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;");\n+                break;\n+              case FZeroAXMonster:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                message.AddData("namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder");\n+                break;\n+              case VirtuaStriker3:\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                message.AddData("SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551;Ver1.00");\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x10, BoardID");\n+              message.AddData((u32)0);\n+              break;\n+            case JVSIOCommand::CommandRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x11);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x11, CommandRevision");\n+              break;\n+            case JVSIOCommand::JVRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x20);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x12, JVRevision");\n+              break;\n+            case JVSIOCommand::CommunicationVersion:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x10);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x13, CommunicationVersion");\n+              break;\n+\n+            // Slave features:\n+            //\n+            // Inputs:\n+            // 0x01: Switch input:  players,  buttons\n+            // 0x02: Coin input:    slots\n+            // 0x03: Analog input:  channels, bits\n+            // 0x04: Rotary input: channels\n+            // 0x05: Keycode input: 0,0,0 ?\n+            // 0x06: Screen position input: X bits, Y bits, channels\n+            //\n+            // Outputs:\n+            // 0x10: Card system: slots\n+            // 0x11: Medal hopper: channels\n+            // 0x12: GPO-out: slots\n+            // 0x13: Analog output: channels\n+            // 0x14: Character output: width, height, type\n+            // 0x15: Backup\n+            case JVSIOCommand::CheckFunctionality:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // 2 Player (12bit) (p2=paddles), 1 Coin slot, 6 Analog-in\n+                // message.AddData((void *)"\\x01\\x02\\x0C\\x00", 4);\n+                // message.AddData((void *)"\\x02\\x01\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x03\\x06\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x00\\x00\\x00\\x00", 4);\n+                //\n+                // DX Version: 2 Player (22bit) (p2=paddles), 2 Coin slot, 8 Analog-in,\n+                // 22 Driver-out\n+                message.AddData("\\x01\\x02\\x12\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x08\\x0A\\x00", 4);\n+                message.AddData("\\x12\\x16\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker3:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case GekitouProYakyuu:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                // 2 Player (13bit), 1 Coin slot, 4 Analog-in, 1 CARD\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case KeyOfAvalon:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, Touch, 1 CARD, 1 Driver-out\n+                // (Unconfirmed)\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x06\\x10\\x10\\x01", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, 1 CARD, 1 Driver-out\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality");\n+              break;\n+            case JVSIOCommand::MainID:\n+            {\n+              const u8* const main_id = jvs_io;\n+              while (jvs_io < jvs_end && *jvs_io++)\n+              {\n+              }\n+              if (main_id < jvs_io)\n+              {\n+                DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command MainId:\\n{}",\n+                              HexDump(main_id, jvs_io - main_id));\n+              }\n+              message.AddData(StatusOkay);\n+              break;\n+            }\n+            case JVSIOCommand::SwitchesInput:\n+            {\n+              if (!validate_jvs_io(2, "SwitchesInput"))\n+                break;\n+              const u32 player_count = *jvs_io++;\n+              const u32 player_byte_count = *jvs_io++;\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:  Command 0x20, SwitchInputs: {} {}",\n+                            player_count, player_byte_count);\n+\n+              message.AddData(StatusOkay);\n+\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              // Test button\n+              if (pad_status.switches & SWITCH_TEST)\n+              {\n+                // Trying to access the test menu without SegaBoot present will cause a crash\n+                if (AMMediaboard::GetTestMenu())\n+                {\n+                  message.AddData(0x80);\n+                }\n+                else\n+                {\n+                  PanicAlertFmt("Test menu is disabled due missing SegaBoot");\n+                }\n+              }\n+              else\n+              {\n+                message.AddData((u32)0x00);\n+              }\n+\n+              for (u32 i = 0; i < player_count; ++i)\n+              {\n+                u8 player_data[3]{};\n+\n+                // Service button\n+                if (pad_status.switches & SWITCH_SERVICE)\n+                  player_data[0] |= 0x40;\n+\n+                switch (AMMediaboard::GetGameType())\n+                {\n+                // Controller configuration for F-Zero AX (DX)\n+                case FZeroAX:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzdx_seatbelt)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzdx_motion_stop)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzdx_sensor_right)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzdx_sensor_left)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+\n+                    player_data[1] = m_rx_reply << 4;\n+                  }\n+                  break;\n+                // Controller configuration for F-Zero AX MonsterRide\n+                case FZeroAXMonster:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzcc_sensor)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzcc_seatbelt)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzcc_service)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzcc_emergency)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+                  }\n+                  break;\n+                // Controller configuration for Virtua Striker 3 games\n+                case VirtuaStriker3:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x02;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Virtua Striker 4 games\n+                case VirtuaStriker4:\n+                case VirtuaStriker4_2006:\n+                {\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x80;\n+                  // Dash\n+                  if (pad_status.button & PAD_BUTTON_Y)\n+                    player_data[1] |= 0x40;\n+                  // Tactics (U)\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x20;\n+                  // Tactics (M)\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x08;\n+                  // Tactics (D)\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+\n+                  if (i == 0)\n+                  {\n+                    player_data[0] |= 0x10;  // IC-Card Switch ON\n+\n+                    // IC-Card Lock\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[1] |= 0x20;\n+                  }\n+                }\n+                break;\n+                // Controller configuration for Gekitou Pro Yakyuu\n+                case GekitouProYakyuu:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  //  A\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  //  B\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  //  Gekitou\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                    player_data[1] |= 0x80;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Mario Kart and other games\n+                default:\n+                case MarioKartGP:\n+                case MarioKartGP2:\n+                {\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Item button\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x20;\n+                  // VS-Cancel button\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x02;\n+                }\n+                break;\n+                case KeyOfAvalon:\n+                {\n+                  // Debug On\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Switch 1\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x04;\n+                  // Switch 2\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x08;\n+                  // Toggle inserted card\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                  {\n+                    m_ic_card_status ^= ICCARDStatus::NoCard;\n+                  }\n+                }\n+                break;\n+                }\n+\n+                for (u32 j = 0; j < player_byte_count; ++j)\n+                  message.AddData(player_data[j]);\n+              }\n+              break;\n+            }\n+            case JVSIOCommand::CoinInput:\n+            {\n+              if (!validate_jvs_io(1, "CoinInput"))\n+                break;\n+              const u32 slots = *jvs_io++;\n+              message.AddData(StatusOkay);\n+              for (u32 i = 0; i < slots; i++)\n+              {\n+                GCPadStatus pad_status = Pad::GetStatus(i);\n+                if ((pad_status.switches & SWITCH_COIN) && !m_coin_pressed[i])\n+                {\n+                  m_coin[i]++;\n+                }\n+                m_coin_pressed[i] = pad_status.switches & SWITCH_COIN;\n+                message.AddData((m_coin[i] >> 8) & 0x3f);\n+                message.AddData(m_coin[i] & 0xff);\n+              }\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x21, CoinInput: {}", slots);\n+              break;\n+            }\n+            case JVSIOCommand::AnalogInput:\n+            {\n+              if (!validate_jvs_io(1, "AnalogInput"))\n+                break;\n+              message.AddData(StatusOkay);\n+\n+              const u32 analogs = *jvs_io++;\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x22, AnalogInput: {}",\n+                            analogs);\n+\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // Steering\n+                if (m_motor_init == 1)\n+                {\n+                  if (m_motor_force_y > 0)\n+                  {\n+                    message.AddData(0x80 - (m_motor_force_y >> 8));\n+                  }\n+                  else\n+                  {\n+                    message.AddData((m_motor_force_y >> 8));\n+                  }\n+                  message.AddData((u8)0);\n+\n+                  message.AddData(pad_status.stickY);\n+                  message.AddData((u8)0);\n+                }\n+                else\n+                {\n+                  // The center for the Y axis is expected to be 78h this adjusts that\n+                  message.AddData(pad_status.stickX - 12);\n+                  message.AddData((u8)0);\n+\n+                  message.AddData(pad_status.stickY);\n+                  message.AddData((u8)0);\n+                }\n+\n+                // Unused\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+\n+                // Gas\n+                message.AddData(pad_status.triggerRight);\n+                message.AddData((u8)0);\n+\n+                // Brake\n+                message.AddData(pad_status.triggerLeft);\n+                message.AddData((u8)0);\n+\n+                message.AddData((u8)0x80);  // Motion Stop\n+                message.AddData((u8)0);\n+\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+              {\n+                message.AddData(-pad_status.stickY);\n+                message.AddData((u8)0);\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+\n+                pad_status = Pad::GetStatus(1);\n+\n+                message.AddData(-pad_status.stickY);\n+                message.AddData((u8)0);\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+              }\n+              break;\n+              default:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+                // Steering\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+\n+                // Gas\n+                message.AddData(pad_status.triggerRight);\n+                message.AddData((u8)0);\n+\n+                // Brake\n+                message.AddData(pad_status.triggerLeft);\n+                message.AddData((u8)0);\n+                break;\n+              }\n+              break;\n+            }\n+            case JVSIOCommand::PositionInput:\n+            {\n+              if (!validate_jvs_io(1, "PositionInput"))\n+                break;\n+              const u32 channel = *jvs_io++;\n+\n+              const GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              if (pad_status.button & PAD_TRIGGER_R)\n+              {\n+                // Tap at center of screen (~320,240)\n+                message.AddData("\\x01\\x00\\x8C\\x01\\x95",\n+                                5);  // X=320 (0x0140), Y=240 (0x00F0)\n+              }\n+              else\n+              {\n+                message.AddData("\\x01\\xFF\\xFF\\xFF\\xFF", 5);\n+              }\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x25, PositionInput:{}",\n+                            channel);\n+              break;\n+            }\n+            case JVSIOCommand::CoinSubOutput:\n+            {\n+              if (!validate_jvs_io(3, "CoinSubOutput"))\n+                break;\n+              const u32 slot = *jvs_io++;\n+              const u8 coinh = *jvs_io++;\n+              const u8 coinl = *jvs_io++;\n+\n+              m_coin[slot] -= (coinh << 8) | coinl;', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 2279, 'original_position': 2279, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Unchecked `slot`.', 'created_at': '2026-02-01T15:24:01Z', 'updated_at': '2026-02-01T15:40:26Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751389783', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751389783'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751389783'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751389783/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2751395550, 'node_id': 'PRRC_kwDOALCn2M6j_vbe', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751395550', 'pull_request_review_id': 3735806017, 'diff_hunk': '@@ -0,0 +1,2708 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      if (buffer_position >= buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})",\n+                      buffer_position, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+\n+      const u32 requested_size = buffer[buffer_position] + 2;\n+      if (requested_size > buffer_length)\n+      {\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer\'s ({})",\n+                      requested_size, buffer_length);\n+        buffer_position = buffer_length;\n+        break;\n+      }\n+      u8* const data_in_end = buffer + requested_size;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (u64{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (u64{m_card_offset} + length > std::size(m_card_buffer))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialB, m_card_buffer overflow:\\n"\n+                              " - m_card_buffer = {}\\n"\n+                              " - m_card_offset = {}\\n"\n+                              " - length = {}",\n+                              fmt::ptr(m_card_buffer), m_card_offset, length);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                          if (m_card_memory_size > sizeof(m_card_memory))\n+                          {\n+                            ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                          "GC-AM: Command CARD GetState overflow:\\n"\n+                                          " - file name = {}\\n"\n+                                          " - file size = {}\\n"\n+                                          " - card size = {}",\n+                                          card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                            data_in = data_in_end;\n+                            break;\n+                          }\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = static_cast<u32>(File::GetSize(card_filename));\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = static_cast<u32>(card.GetSize());\n+                        }\n+\n+                        if (m_card_memory_size > sizeof(m_card_memory))\n+                        {\n+                          ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                        "GC-AM: Command CARD Read overflow:\\n"\n+                                        " - file name = {}\\n"\n+                                        " - file size = {}\\n"\n+                                        " - card size = {}",\n+                                        card_filename, m_card_memory_size, sizeof(m_card_memory));\n+                          data_in = data_in_end;\n+                          break;\n+                        }\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+                      else if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Read overflow:\\n"\n+                                      " - requested size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      m_card_read_packet[0] = 0x02;  // SUB CMD\n+                      m_card_read_packet[1] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[2] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[3] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[3] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[4] = 0x30;\n+                      m_card_read_packet[5] = 0x30;\n+\n+                      u32 packet_offset = 6;\n+                      // Data reply\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6);\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7);\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8);\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9);\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+                      if (m_card_memory_size > sizeof(m_card_memory))\n+                      {\n+                        ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                      "GC-AM: Command CARD Write overflow:\\n"\n+                                      " - write size = {}\\n"\n+                                      " - card size = {}",\n+                                      m_card_memory_size, sizeof(m_card_memory));\n+                        data_in = data_in_end;\n+                        break;\n+                      }\n+\n+                      static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9);\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode,\n+                                     bitmode, track, m_card_memory_size);\n+\n+                      const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) +\n+                                                      "tricard_" +\n+                                                      SConfig::GetInstance().GetGameID() + ".bin");\n+\n+                      File::IOFile card(card_filename, "wb+");\n+                      card.WriteBytes(m_card_memory, m_card_memory_size);\n+                      card.Close();\n+\n+                      m_card_bit = 2;\n+\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::SetPrintParam:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam");\n+                      break;\n+                    case CARDCommand::WriteInfo:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo");\n+                      break;\n+                    case CARDCommand::Erase:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase");\n+                      break;\n+                    case CARDCommand::Eject:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      break;\n+                    case CARDCommand::SetShutter:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter");\n+                      if (AMMediaboard::GetGameType() != FZeroAX)\n+                      {\n+                        m_card_bit = 0;\n+                      }\n+                      // Close\n+                      if (m_card_buffer[6] == 0x30)\n+                      {\n+                        m_card_shutter = false;\n+                      }\n+                      // Open\n+                      else if (m_card_buffer[6] == 0x31)\n+                      {\n+                        m_card_shutter = true;\n+                      }\n+                      break;\n+                    default:\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!");\n+                      ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command);\n+                      // hexdump( m_card_buffer, m_card_offset );\n+                      break;\n+                    }\n+                    m_card_offset = 0;\n+                  }\n+                }\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x32;\n+              data_out[data_offset++] = 0x01;  // len\n+              data_out[data_offset++] = 0x06;  // OK\n+            }\n+          }\n+          else\n+          {\n+            if (!validate_data_in_out(0, 2, "SerialB"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;  // len\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::JVSIOA:\n+        case GCAMCommand::JVSIOB:\n+        {\n+          if (!validate_data_in_out(4, 0, "JVSIO"))\n+            break;\n+\n+          JVSIOMessage message;\n+\n+          static int delay = 0;\n+\n+          const u8* const frame = &data_in[0];\n+          const u8 nr_bytes = frame[3];  // Byte after E0 xx\n+          u32 frame_len = nr_bytes + 3;  // Header(2) + length byte + payload + checksum\n+\n+          u8 jvs_buf[0x80];\n+\n+          frame_len = std::min<u32>(frame_len, sizeof(jvs_buf));\n+\n+          if (!validate_data_in_out(frame_len, 0, "JVSIO"))\n+            break;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GC-AM: Command {:02x} (JVS IO), hexdump:\\n{}",\n+                        gcam_command, HexDump(data_in, frame_len));\n+\n+          memcpy(jvs_buf, frame, frame_len);\n+\n+          // Extract node and payload pointers\n+          u8 node = jvs_buf[2];\n+          u8* jvs_io = jvs_buf + 4;                 // First payload byte\n+          u8* const jvs_end = jvs_buf + frame_len;  // One byte before checksum\n+          u8* const jvs_begin = jvs_io;\n+\n+          message.Start(0);\n+          message.AddData(1);\n+\n+          // Helper to check that iterating over jvs_io n times is safe,\n+          // i.e. *jvs_io++ at most lead to jvs_end\n+          auto validate_jvs_io = [&](u32 n, std::string_view command) -> bool {\n+            if (jvs_io + n > jvs_end)\n+              ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: overflow in {}", command);\n+            else\n+              return true;\n+            ERROR_LOG_FMT(SERIALINTERFACE_JVSIO,\n+                          "Overflow details:\\n"\n+                          " - jvs_io(begin={}, current={}, end={}, n={})\\n"\n+                          " - delay={}, node={}\\n"\n+                          " - frame(begin={}, len={})",\n+                          fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, delay, node,\n+                          fmt::ptr(frame), frame_len);\n+            jvs_io = jvs_end;\n+            return false;\n+          };\n+\n+          // Now iterate over the payload\n+          while (jvs_io < jvs_end)\n+          {\n+            const u8 jvsio_command = *jvs_io++;\n+            DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:node={}, command={:02x}", node,\n+                          jvsio_command);\n+\n+            switch (JVSIOCommand(jvsio_command))\n+            {\n+            case JVSIOCommand::IOID:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+                // Specific version that enables DX mode on AX machines, all this does is enable the\n+                // motion of a chair\n+                message.AddData("SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;");\n+                break;\n+              case FZeroAXMonster:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                message.AddData("namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder");\n+                break;\n+              case VirtuaStriker3:\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                message.AddData("SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551;Ver1.00");\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x10, BoardID");\n+              message.AddData((u32)0);\n+              break;\n+            case JVSIOCommand::CommandRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x11);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x11, CommandRevision");\n+              break;\n+            case JVSIOCommand::JVRevision:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x20);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x12, JVRevision");\n+              break;\n+            case JVSIOCommand::CommunicationVersion:\n+              message.AddData(StatusOkay);\n+              message.AddData(0x10);\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x13, CommunicationVersion");\n+              break;\n+\n+            // Slave features:\n+            //\n+            // Inputs:\n+            // 0x01: Switch input:  players,  buttons\n+            // 0x02: Coin input:    slots\n+            // 0x03: Analog input:  channels, bits\n+            // 0x04: Rotary input: channels\n+            // 0x05: Keycode input: 0,0,0 ?\n+            // 0x06: Screen position input: X bits, Y bits, channels\n+            //\n+            // Outputs:\n+            // 0x10: Card system: slots\n+            // 0x11: Medal hopper: channels\n+            // 0x12: GPO-out: slots\n+            // 0x13: Analog output: channels\n+            // 0x14: Character output: width, height, type\n+            // 0x15: Backup\n+            case JVSIOCommand::CheckFunctionality:\n+              message.AddData(StatusOkay);\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // 2 Player (12bit) (p2=paddles), 1 Coin slot, 6 Analog-in\n+                // message.AddData((void *)"\\x01\\x02\\x0C\\x00", 4);\n+                // message.AddData((void *)"\\x02\\x01\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x03\\x06\\x00\\x00", 4);\n+                // message.AddData((void *)"\\x00\\x00\\x00\\x00", 4);\n+                //\n+                // DX Version: 2 Player (22bit) (p2=paddles), 2 Coin slot, 8 Analog-in,\n+                // 22 Driver-out\n+                message.AddData("\\x01\\x02\\x12\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x08\\x0A\\x00", 4);\n+                message.AddData("\\x12\\x16\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker3:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case GekitouProYakyuu:\n+                // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x02\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x08\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+                // 2 Player (13bit), 1 Coin slot, 4 Analog-in, 1 CARD\n+                message.AddData("\\x01\\x02\\x0D\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x04\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case KeyOfAvalon:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, Touch, 1 CARD, 1 Driver-out\n+                // (Unconfirmed)\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x06\\x10\\x10\\x01", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+              default:\n+                // 1 Player (15bit), 1 Coin slot, 3 Analog-in, 1 CARD, 1 Driver-out\n+                message.AddData("\\x01\\x01\\x0F\\x00", 4);\n+                message.AddData("\\x02\\x01\\x00\\x00", 4);\n+                message.AddData("\\x03\\x03\\x00\\x00", 4);\n+                message.AddData("\\x10\\x01\\x00\\x00", 4);\n+                message.AddData("\\x12\\x01\\x00\\x00", 4);\n+                message.AddData("\\x00\\x00\\x00\\x00", 4);\n+                break;\n+              }\n+              NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality");\n+              break;\n+            case JVSIOCommand::MainID:\n+            {\n+              const u8* const main_id = jvs_io;\n+              while (jvs_io < jvs_end && *jvs_io++)\n+              {\n+              }\n+              if (main_id < jvs_io)\n+              {\n+                DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command MainId:\\n{}",\n+                              HexDump(main_id, jvs_io - main_id));\n+              }\n+              message.AddData(StatusOkay);\n+              break;\n+            }\n+            case JVSIOCommand::SwitchesInput:\n+            {\n+              if (!validate_jvs_io(2, "SwitchesInput"))\n+                break;\n+              const u32 player_count = *jvs_io++;\n+              const u32 player_byte_count = *jvs_io++;\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:  Command 0x20, SwitchInputs: {} {}",\n+                            player_count, player_byte_count);\n+\n+              message.AddData(StatusOkay);\n+\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              // Test button\n+              if (pad_status.switches & SWITCH_TEST)\n+              {\n+                // Trying to access the test menu without SegaBoot present will cause a crash\n+                if (AMMediaboard::GetTestMenu())\n+                {\n+                  message.AddData(0x80);\n+                }\n+                else\n+                {\n+                  PanicAlertFmt("Test menu is disabled due missing SegaBoot");\n+                }\n+              }\n+              else\n+              {\n+                message.AddData((u32)0x00);\n+              }\n+\n+              for (u32 i = 0; i < player_count; ++i)\n+              {\n+                u8 player_data[3]{};\n+\n+                // Service button\n+                if (pad_status.switches & SWITCH_SERVICE)\n+                  player_data[0] |= 0x40;\n+\n+                switch (AMMediaboard::GetGameType())\n+                {\n+                // Controller configuration for F-Zero AX (DX)\n+                case FZeroAX:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzdx_seatbelt)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzdx_motion_stop)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzdx_sensor_right)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzdx_sensor_left)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+\n+                    player_data[1] = m_rx_reply << 4;\n+                  }\n+                  break;\n+                // Controller configuration for F-Zero AX MonsterRide\n+                case FZeroAXMonster:\n+                  if (i == 0)\n+                  {\n+                    if (m_fzcc_sensor)\n+                    {\n+                      player_data[0] |= 0x01;\n+                    }\n+\n+                    // Start\n+                    if (pad_status.button & PAD_BUTTON_START)\n+                      player_data[0] |= 0x80;\n+                    // Boost\n+                    if (pad_status.button & PAD_BUTTON_A)\n+                      player_data[0] |= 0x02;\n+                    // View Change 1\n+                    if (pad_status.button & PAD_BUTTON_RIGHT)\n+                      player_data[0] |= 0x20;\n+                    // View Change 2\n+                    if (pad_status.button & PAD_BUTTON_LEFT)\n+                      player_data[0] |= 0x10;\n+                    // View Change 3\n+                    if (pad_status.button & PAD_BUTTON_UP)\n+                      player_data[0] |= 0x08;\n+                    // View Change 4\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[0] |= 0x04;\n+\n+                    player_data[1] = m_rx_reply & 0xF0;\n+                  }\n+                  else if (i == 1)\n+                  {\n+                    //  Paddle left\n+                    if (pad_status.button & PAD_BUTTON_X)\n+                      player_data[0] |= 0x20;\n+                    //  Paddle right\n+                    if (pad_status.button & PAD_BUTTON_Y)\n+                      player_data[0] |= 0x10;\n+\n+                    if (m_fzcc_seatbelt)\n+                    {\n+                      player_data[0] |= 2;\n+                    }\n+                    if (m_fzcc_service)\n+                    {\n+                      player_data[0] |= 4;\n+                    }\n+                    if (m_fzcc_emergency)\n+                    {\n+                      player_data[0] |= 8;\n+                    }\n+                  }\n+                  break;\n+                // Controller configuration for Virtua Striker 3 games\n+                case VirtuaStriker3:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x02;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Virtua Striker 4 games\n+                case VirtuaStriker4:\n+                case VirtuaStriker4_2006:\n+                {\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Long Pass\n+                  if (pad_status.button & PAD_BUTTON_X)\n+                    player_data[0] |= 0x01;\n+                  // Short Pass\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  // Shoot\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x80;\n+                  // Dash\n+                  if (pad_status.button & PAD_BUTTON_Y)\n+                    player_data[1] |= 0x40;\n+                  // Tactics (U)\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x20;\n+                  // Tactics (M)\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x08;\n+                  // Tactics (D)\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+\n+                  if (i == 0)\n+                  {\n+                    player_data[0] |= 0x10;  // IC-Card Switch ON\n+\n+                    // IC-Card Lock\n+                    if (pad_status.button & PAD_BUTTON_DOWN)\n+                      player_data[1] |= 0x20;\n+                  }\n+                }\n+                break;\n+                // Controller configuration for Gekitou Pro Yakyuu\n+                case GekitouProYakyuu:\n+                  pad_status = Pad::GetStatus(i);\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  //  A\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x01;\n+                  //  B\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x02;\n+                  //  Gekitou\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                    player_data[1] |= 0x80;\n+                  // Left\n+                  if (pad_status.button & PAD_BUTTON_LEFT)\n+                    player_data[0] |= 0x08;\n+                  // Up\n+                  if (pad_status.button & PAD_BUTTON_UP)\n+                    player_data[0] |= 0x20;\n+                  // Right\n+                  if (pad_status.button & PAD_BUTTON_RIGHT)\n+                    player_data[0] |= 0x04;\n+                  // Down\n+                  if (pad_status.button & PAD_BUTTON_DOWN)\n+                    player_data[0] |= 0x10;\n+                  break;\n+                // Controller configuration for Mario Kart and other games\n+                default:\n+                case MarioKartGP:\n+                case MarioKartGP2:\n+                {\n+                  // Start\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Item button\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[1] |= 0x20;\n+                  // VS-Cancel button\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[1] |= 0x02;\n+                }\n+                break;\n+                case KeyOfAvalon:\n+                {\n+                  // Debug On\n+                  if (pad_status.button & PAD_BUTTON_START)\n+                    player_data[0] |= 0x80;\n+                  // Switch 1\n+                  if (pad_status.button & PAD_BUTTON_A)\n+                    player_data[0] |= 0x04;\n+                  // Switch 2\n+                  if (pad_status.button & PAD_BUTTON_B)\n+                    player_data[0] |= 0x08;\n+                  // Toggle inserted card\n+                  if (pad_status.button & PAD_TRIGGER_L)\n+                  {\n+                    m_ic_card_status ^= ICCARDStatus::NoCard;\n+                  }\n+                }\n+                break;\n+                }\n+\n+                for (u32 j = 0; j < player_byte_count; ++j)\n+                  message.AddData(player_data[j]);\n+              }\n+              break;\n+            }\n+            case JVSIOCommand::CoinInput:\n+            {\n+              if (!validate_jvs_io(1, "CoinInput"))\n+                break;\n+              const u32 slots = *jvs_io++;\n+              message.AddData(StatusOkay);\n+              for (u32 i = 0; i < slots; i++)\n+              {\n+                GCPadStatus pad_status = Pad::GetStatus(i);\n+                if ((pad_status.switches & SWITCH_COIN) && !m_coin_pressed[i])\n+                {\n+                  m_coin[i]++;\n+                }\n+                m_coin_pressed[i] = pad_status.switches & SWITCH_COIN;\n+                message.AddData((m_coin[i] >> 8) & 0x3f);\n+                message.AddData(m_coin[i] & 0xff);\n+              }\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x21, CoinInput: {}", slots);\n+              break;\n+            }\n+            case JVSIOCommand::AnalogInput:\n+            {\n+              if (!validate_jvs_io(1, "AnalogInput"))\n+                break;\n+              message.AddData(StatusOkay);\n+\n+              const u32 analogs = *jvs_io++;\n+              GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x22, AnalogInput: {}",\n+                            analogs);\n+\n+              switch (AMMediaboard::GetGameType())\n+              {\n+              case FZeroAX:\n+              case FZeroAXMonster:\n+                // Steering\n+                if (m_motor_init == 1)\n+                {\n+                  if (m_motor_force_y > 0)\n+                  {\n+                    message.AddData(0x80 - (m_motor_force_y >> 8));\n+                  }\n+                  else\n+                  {\n+                    message.AddData((m_motor_force_y >> 8));\n+                  }\n+                  message.AddData((u8)0);\n+\n+                  message.AddData(pad_status.stickY);\n+                  message.AddData((u8)0);\n+                }\n+                else\n+                {\n+                  // The center for the Y axis is expected to be 78h this adjusts that\n+                  message.AddData(pad_status.stickX - 12);\n+                  message.AddData((u8)0);\n+\n+                  message.AddData(pad_status.stickY);\n+                  message.AddData((u8)0);\n+                }\n+\n+                // Unused\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+\n+                // Gas\n+                message.AddData(pad_status.triggerRight);\n+                message.AddData((u8)0);\n+\n+                // Brake\n+                message.AddData(pad_status.triggerLeft);\n+                message.AddData((u8)0);\n+\n+                message.AddData((u8)0x80);  // Motion Stop\n+                message.AddData((u8)0);\n+\n+                message.AddData((u8)0);\n+                message.AddData((u8)0);\n+\n+                break;\n+              case VirtuaStriker4:\n+              case VirtuaStriker4_2006:\n+              {\n+                message.AddData(-pad_status.stickY);\n+                message.AddData((u8)0);\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+\n+                pad_status = Pad::GetStatus(1);\n+\n+                message.AddData(-pad_status.stickY);\n+                message.AddData((u8)0);\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+              }\n+              break;\n+              default:\n+              case MarioKartGP:\n+              case MarioKartGP2:\n+                // Steering\n+                message.AddData(pad_status.stickX);\n+                message.AddData((u8)0);\n+\n+                // Gas\n+                message.AddData(pad_status.triggerRight);\n+                message.AddData((u8)0);\n+\n+                // Brake\n+                message.AddData(pad_status.triggerLeft);\n+                message.AddData((u8)0);\n+                break;\n+              }\n+              break;\n+            }\n+            case JVSIOCommand::PositionInput:\n+            {\n+              if (!validate_jvs_io(1, "PositionInput"))\n+                break;\n+              const u32 channel = *jvs_io++;\n+\n+              const GCPadStatus pad_status = Pad::GetStatus(0);\n+\n+              if (pad_status.button & PAD_TRIGGER_R)\n+              {\n+                // Tap at center of screen (~320,240)\n+                message.AddData("\\x01\\x00\\x8C\\x01\\x95",\n+                                5);  // X=320 (0x0140), Y=240 (0x00F0)\n+              }\n+              else\n+              {\n+                message.AddData("\\x01\\xFF\\xFF\\xFF\\xFF", 5);\n+              }\n+\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x25, PositionInput:{}",\n+                            channel);\n+              break;\n+            }\n+            case JVSIOCommand::CoinSubOutput:\n+            {\n+              if (!validate_jvs_io(3, "CoinSubOutput"))\n+                break;\n+              const u32 slot = *jvs_io++;\n+              const u8 coinh = *jvs_io++;\n+              const u8 coinl = *jvs_io++;\n+\n+              m_coin[slot] -= (coinh << 8) | coinl;\n+\n+              message.AddData(StatusOkay);\n+              DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x30, CoinSubOutput: {}", slot);\n+              break;\n+            }\n+            case JVSIOCommand::GeneralDriverOutput:\n+            {\n+              if (!validate_jvs_io(1, "GeneralDriverOutput"))\n+                break;\n+              const u32 bytes = *jvs_io++;\n+\n+              if (bytes)\n+              {\n+                message.AddData(StatusOkay);\n+\n+                // The lamps are controlled via this\n+                if (AMMediaboard::GetGameType() == MarioKartGP)\n+                {\n+                  if (!validate_jvs_io(1, "GeneralDriverOutput (MarioKartGP)"))\n+                    break;\n+                  const u32 status = *jvs_io++;\n+                  if (status & 4)\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Item Button ON");\n+                  }\n+                  else\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Item Button OFF");\n+                  }\n+                  if (status & 8)\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Cancel Button ON");\n+                  }\n+                  else\n+                  {\n+                    DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Cancel Button OFF");\n+                  }\n+                  break;\n+                }\n+\n+                if (!validate_jvs_io(bytes, "GeneralDriverOutput"))\n+                  break;\n+                Common::UniqueBuffer<u8> buf(bytes);\n+\n+                for (u32 i = 0; i < bytes; ++i)\n+                {\n+                  buf[i] = *jvs_io++;', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 2326, 'original_position': 2326, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Not a security issue, but why are we copying this to a buffer? We should be able to just read the values straight out of `jvs_io`.', 'created_at': '2026-02-01T15:25:43Z', 'updated_at': '2026-02-01T15:40:26Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751395550', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751395550'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751395550'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751395550/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2751418497, 'node_id': 'PRRC_kwDOALCn2M6j_1CB', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751418497', 'pull_request_review_id': 3735806017, 'diff_hunk': '@@ -12,13 +12,46 @@\n #include "DiscIO/DiscUtils.h"\n #include "DiscIO/Enums.h"\n #include "DiscIO/Filesystem.h"\n+#include "DiscIO/VolumeGC.h"\n \n namespace DiscIO\n {\n std::string VolumeDisc::GetGameID(const Partition& partition) const\n {\n   char id[6];\n \n+  if (GetVolumeType() == Platform::Triforce)\n+  {\n+    // Triforce games have their Game ID stored in the boot.id file\n+    const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();\n+\n+    // Construct game ID from the BTID\n+    id[0] = \'G\';\n+\n+    memcpy(id + 1, boot_id->game_id.data() + 2, 2);\n+\n+    switch (GetCountry())\n+    {\n+    default:\n+    case Country::Japan:\n+      id[3] = \'J\';\n+      break;\n+    case Country::Taiwan:\n+      id[3] = \'W\';\n+      break;\n+    case Country::USA:\n+      id[3] = \'E\';\n+      break;\n+    case Country::Europe:\n+      id[3] = \'P\';\n+      break;\n+    }\n+\n+    memcpy(id + 4, GetMakerID().c_str(), 2);', 'path': 'Source/Core/DiscIO/VolumeDisc.cpp', 'position': 39, 'original_position': 39, 'commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '`GetMakerID` can return an empty string.', 'created_at': '2026-02-01T15:34:31Z', 'updated_at': '2026-02-01T15:40:26Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751418497', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751418497'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2751418497'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '5bf8fe93286ebe4f08fb469a44f259a8397c89e0', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751418497/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}], 'type': 'gh_pull_request_review'}
2026-02-01T11:45:30.418183	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 13768, 'pr_title': 'Core: Create fastmem mappings for page address translation', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13768#pullrequestreview-3735600927', 'comments': [{'id': 2751118036, 'node_id': 'PRRC_kwDOALCn2M6j-rrU', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751118036', 'pull_request_review_id': 3735600927, 'diff_hunk': '@@ -305,6 +319,184 @@ void MemoryManager::UpdateLogicalMemory(const PowerPC::BatTable& dbat_table)\n   }\n }\n \n+void MemoryManager::AddPageTableMapping(u32 logical_address, u32 translated_address, bool writeable)\n+{\n+  switch (m_host_page_type)\n+  {\n+  case HostPageType::SmallPages:\n+    return AddHostPageTableMapping(logical_address, translated_address, writeable,\n+                                   PowerPC::HW_PAGE_SIZE);\n+  case HostPageType::LargePages:\n+    return TryAddLargePageTableMapping(logical_address, translated_address, writeable);\n+  default:\n+    return;\n+  }\n+}\n+\n+void MemoryManager::TryAddLargePageTableMapping(u32 logical_address, u32 translated_address,\n+                                                bool writeable)\n+{\n+  const bool add_readable =\n+      TryAddLargePageTableMapping(logical_address, translated_address, m_large_readable_pages);\n+\n+  const bool add_writeable =\n+      writeable &&\n+      TryAddLargePageTableMapping(logical_address, translated_address, m_large_writeable_pages);\n+\n+  if (add_readable || add_writeable)\n+  {\n+    AddHostPageTableMapping(logical_address & ~(m_page_size - 1),\n+                            translated_address & ~(m_page_size - 1), add_writeable, m_page_size);\n+  }\n+}\n+\n+bool MemoryManager::TryAddLargePageTableMapping(u32 logical_address, u32 translated_address,\n+                                                std::map<u32, std::vector<u32>>& map)\n+{\n+  std::vector<u32>& entries = map[logical_address & ~(m_page_size - 1)];\n+\n+  if (entries.empty())\n+    entries = std::vector<u32>(m_guest_pages_per_host_page, INVALID_MAPPING);\n+\n+  entries[(logical_address & (m_page_size - 1)) / PowerPC::HW_PAGE_SIZE] = translated_address;\n+\n+  return CanCreateHostMappingForGuestPages(entries);\n+}\n+\n+bool MemoryManager::CanCreateHostMappingForGuestPages(std::vector<u32>& entries) const\n+{\n+  const u32 translated_address = entries[0];\n+  if ((translated_address & (m_page_size - 1)) != 0)\n+    return false;\n+\n+  for (size_t i = 1; i < m_guest_pages_per_host_page; ++i)\n+  {\n+    if (entries[i] != translated_address + i * PowerPC::HW_PAGE_SIZE)\n+      return false;\n+  }\n+\n+  return true;\n+}\n+\n+void MemoryManager::AddHostPageTableMapping(u32 logical_address, u32 translated_address,\n+                                            bool writeable, u32 logical_size)\n+{\n+  for (const auto& physical_region : m_physical_regions)\n+  {\n+    if (!physical_region.active)\n+      continue;\n+\n+    u32 mapping_address = physical_region.physical_address;\n+    u32 mapping_end = mapping_address + physical_region.size;\n+    u32 intersection_start = std::max(mapping_address, translated_address);\n+    u32 intersection_end = std::min(mapping_end, translated_address + logical_size);\n+    if (intersection_start < intersection_end)\n+    {\n+      // Found an overlapping region; map it.\n+      if (m_is_fastmem_arena_initialized)', 'path': 'Source/Core/Core/HW/Memmap.cpp', 'position': 216, 'original_position': 216, 'commit_id': '3fdce1e8bd9901e5d852dea23a271ca48434ac37', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "It's based on the existing `UpdateDBATMappings` function, where we can't just return early. I'll change it.", 'created_at': '2026-02-01T11:45:28Z', 'updated_at': '2026-02-01T11:45:28Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768#discussion_r2751118036', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751118036'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768#discussion_r2751118036'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}}, 'original_commit_id': '3fdce1e8bd9901e5d852dea23a271ca48434ac37', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2751118036/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2726396883}], 'type': 'gh_pull_request_review'}
2026-01-31T22:44:15.583035	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Dentomologist', 'action': 'submitted', 'pr_id': 14312, 'pr_title': 'WGL: Correctly load wglDestroyPbufferARB extension', 'state': 'approved', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14312#pullrequestreview-3733793408', 'comments': [], 'type': 'gh_pull_request_review'}
2026-01-31T19:02:47.353318	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3733278263', 'comments': [{'id': 2749868593, 'node_id': 'PRRC_kwDOALCn2M6j56ox', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749868593', 'pull_request_review_id': 3733278263, 'diff_hunk': '@@ -0,0 +1,2501 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (data_offset + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                if (cnt > (u32)0x50 - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 2, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = (u32)card.GetSize();\n+\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = (u32)File::GetSize(card_filename);\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+                      u32 packet_offset = 0;\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = (u32)card.GetSize();\n+                        }\n+\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x02;  // SUB CMD\n+                      m_card_read_packet[packet_offset++] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[packet_offset++] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+\n+                      // Data reply\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1498, 'original_position': 1390, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "I admittedly missed that check, but that check only runs if the file exists. You should also check `m_card_memory_size` if the file doesn't exist.", 'created_at': '2026-01-31T19:02:45Z', 'updated_at': '2026-01-31T19:02:45Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749868593', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749868593'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749868593'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '2415f0c1c471b2446a2144a1a2b643d8a77d8cc7', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749868593/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2725629971}], 'type': 'gh_pull_request_review'}
2026-01-31T19:01:53.435137	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3733276394', 'comments': [{'id': 2749867718, 'node_id': 'PRRC_kwDOALCn2M6j56bG', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749867718', 'pull_request_review_id': 3733276394, 'diff_hunk': '@@ -0,0 +1,2501 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (data_offset + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                if (cnt > (u32)0x50 - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 2, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = (u32)card.GetSize();\n+\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = (u32)File::GetSize(card_filename);\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+                      u32 packet_offset = 0;\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = (u32)card.GetSize();\n+                        }\n+\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x02;  // SUB CMD\n+                      m_card_read_packet[packet_offset++] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[packet_offset++] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+\n+                      // Data reply\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1536, 'original_position': 1414, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "My bad. I think I got distracted by the static_assert and didn't read far up enough to see the `m_card_memory_size` check.", 'created_at': '2026-01-31T19:01:49Z', 'updated_at': '2026-01-31T19:01:49Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749867718', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749867718'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749867718'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '2415f0c1c471b2446a2144a1a2b643d8a77d8cc7', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749867718/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2725631348}], 'type': 'gh_pull_request_review'}
2026-01-31T18:51:16.078448	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3733256073', 'comments': [{'id': 2749856368, 'node_id': 'PRRC_kwDOALCn2M6j53pw', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749856368', 'pull_request_review_id': 3733256073, 'diff_hunk': '@@ -0,0 +1,2501 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (data_offset + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                if (cnt > (u32)0x50 - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 2, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = (u32)card.GetSize();\n+\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = (u32)File::GetSize(card_filename);\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+                      u32 packet_offset = 0;\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = (u32)card.GetSize();\n+                        }\n+\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x02;  // SUB CMD\n+                      m_card_read_packet[packet_offset++] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[packet_offset++] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+\n+                      // Data reply\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1498, 'original_position': 1390, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Similar situation here, there is an early exit when `m_card_memory_size` is greater than `sizeof(m_card_memory)`, so the `static_assert` should be enough for the other parameter bound check since `packet_offset` is guaranteed to be `6`. I rewrote part of the code to declare `packet_offset` later to make that clearer.', 'created_at': '2026-01-31T18:51:14Z', 'updated_at': '2026-01-31T18:51:14Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749856368', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749856368'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749856368'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '2415f0c1c471b2446a2144a1a2b643d8a77d8cc7', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749856368/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2725629971}], 'type': 'gh_pull_request_review'}
2026-01-31T18:40:52.590166	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'sepalani', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3733235981', 'comments': [{'id': 2749846854, 'node_id': 'PRRC_kwDOALCn2M6j51VG', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749846854', 'pull_request_review_id': 3733235981, 'diff_hunk': '@@ -0,0 +1,2501 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (data_offset + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                if (cnt > (u32)0x50 - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 2, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = (u32)card.GetSize();\n+\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = (u32)File::GetSize(card_filename);\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+                      u32 packet_offset = 0;\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = (u32)card.GetSize();\n+                        }\n+\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x02;  // SUB CMD\n+                      m_card_read_packet[packet_offset++] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[packet_offset++] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+\n+                      // Data reply\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1536, 'original_position': 1414, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Isn't the `static_assert` + the above check enough?\r\n```C++\r\nif (m_card_memory_size > sizeof(m_card_memory)) // <-- early exit\r\n```\r\n\r\nWon't that imply the following regarding the `static_assert`:\r\n```C++\r\nstatic_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9);\r\n// i.e. worst case since m_card_memory_size <= sizeof(m_card_memory):\r\n// sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9 > sizeof(m_card_memory) >= m_card_memory_size\r\n```\r\n\r\nUnless I'm mistaken, if `m_card_buffer` (offset subtracted) is bigger than `m_card_memory`'s, it shouldn't overflow because `m_card_memory_size` is at most `sizeof(m_card_memory)`. In other words:\r\n - The early exit checks `memcpy`'s source parameter for overflow _(i.e. `m_card_memory_size <= sizeof(m_card_memory)` bytes are available. \r\n - The `static_assert` checks `memcpy`'s destination parameter for overflow (based on the fact that `m_card_memory_size` is at most `sizeof(m_card_memory)` bytes.\r\n\r\nI'm also open to suggestions regarding other and probably better ways to check this. \r\n", 'created_at': '2026-01-31T18:40:50Z', 'updated_at': '2026-01-31T18:40:50Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749846854', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749846854'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749846854'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '2415f0c1c471b2446a2144a1a2b643d8a77d8cc7', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749846854/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2725631348}], 'type': 'gh_pull_request_review'}
2026-01-31T17:12:51.138959	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'iwubcode', 'action': 'submitted', 'pr_id': 14312, 'pr_title': 'WGL: Correctly load wglDestroyPbufferARB extension', 'state': 'approved', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14312#pullrequestreview-3733074436', 'comments': [], 'type': 'gh_pull_request_review'}
2026-01-31T13:33:14.945033	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 13844, 'pr_title': 'Core: Triforce support', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13844#pullrequestreview-3732508353', 'comments': [{'id': 2749545372, 'node_id': 'PRRC_kwDOALCn2M6j4ruc', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749545372', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -1,20 +1,112 @@\n-// Copyright 2013 Dolphin Emulator Project\n+// Copyright 2025 Dolphin Emulator Project\n // SPDX-License-Identifier: GPL-2.0-or-later\n \n #include "Core/HW/EXI/EXI_DeviceBaseboard.h"\n \n-#include "Common/ChunkFile.h"\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n #include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n #include "Common/Logging/Log.h"\n \n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/EXI/EXI_Device.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/Movie.h"\n+#include "Core/PowerPC/PowerPC.h"\n+#include "Core/System.h"\n+\n+static bool s_interrupt_set = false;\n+static u32 s_irq_timer = 0;\n+static u32 s_irq_status = 0;\n+\n+static u16 CheckSum(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, 0);\n+}\n+\n namespace ExpansionInterface\n {\n+\n+void GenerateInterrupt(int flag)\n+{\n+  auto& system = Core::System::GetInstance();\n+\n+  s_interrupt_set = true;\n+  s_irq_timer = 0;\n+  s_irq_status = flag;\n+\n+  system.GetExpansionInterface().UpdateInterrupts();\n+}\n+\n CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system)\n+{\n+  std::string backup_filename(fmt::format("{}tribackup_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                          SConfig::GetInstance().GetGameID()));\n+\n+  m_backup = File::IOFile(backup_filename, "rb+");\n+  if (!m_backup.IsOpen())\n+  {\n+    m_backup = File::IOFile(backup_filename, "wb+");\n+  }\n+\n+  // Some games share the same ID Client/Server\n+  if (!m_backup.IsGood())\n+  {\n+    PanicAlertFmt("Failed to open {}\\nFile might be in use.", backup_filename.c_str());\n+\n+    std::srand(static_cast<u32>(std::time(nullptr)));\n+\n+    backup_filename = fmt::format("{}tribackup_tmp_{}{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                  rand(), SConfig::GetInstance().GetGameID());\n+\n+    m_backup = File::IOFile(backup_filename, "wb+");\n+  }\n+\n+  // Virtua Striker 4 and Gekitou Pro Yakyuu need a higher FIRM version\n+  // Which is read from the backup data?!\n+  if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+      AMMediaboard::GetGameType() == GekitouProYakyuu)\n+  {\n+    if (m_backup.GetSize() >= 0x20C + 0x1F4)\n+    {\n+      Common::UniqueBuffer<u8> data(m_backup.GetSize());', 'path': 'Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp', 'position': 87, 'original_position': 87, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Time of check vs time of use error for `m_backup.GetSize()`. This isn't a security issue that's actually going to get triggered since you need write access to the local file system to do it, but calling `GetSize` only once is good for performance anyway.", 'created_at': '2026-01-31T13:02:11Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749545372', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749545372'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749545372'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749545372/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749551553, 'node_id': 'PRRC_kwDOALCn2M6j4tPB', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749551553', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -0,0 +1,2682 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (std::size_t{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (std::size_t{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 578, 'original_position': 578, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '```suggestion\r\n                if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\r\n```', 'created_at': '2026-01-31T13:11:17Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749551553', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749551553'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749551553'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749551553/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749557847, 'node_id': 'PRRC_kwDOALCn2M6j4uxX', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749557847', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -0,0 +1,2682 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 238, 'original_position': 238, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "This is an older comment, but it hasn't been addressed and GitHub is showing the old comment as outdated, so I'm reposting it.\r\n\r\nYou aren't checking that `buffer_position` is smaller than `128`, and then you aren't checking that `buffer[buffer_position] + 2` is less than or equal to `128`.", 'created_at': '2026-01-31T13:21:16Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749557847', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749557847'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749557847'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749557847/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749561110, 'node_id': 'PRRC_kwDOALCn2M6j4vkW', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749561110', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -0,0 +1,2682 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (std::size_t{data_offset} + n_out > data_out.size())', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 245, 'original_position': 245, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '```suggestion\r\n        else if (u64{data_offset} + n_out > data_out.size())\r\n```', 'created_at': '2026-01-31T13:26:06Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749561110', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749561110'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749561110'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749561110/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749561611, 'node_id': 'PRRC_kwDOALCn2M6j4vsL', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749561611', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -0,0 +1,2682 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (std::size_t{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (std::size_t{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (std::size_t{m_card_read} + read_length > sizeof(m_card_read_packet))', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1161, 'original_position': 1161, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '```suggestion\r\n                if (u64{m_card_read} + read_length > sizeof(m_card_read_packet))\r\n```', 'created_at': '2026-01-31T13:26:57Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749561611', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749561611'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749561611'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749561611/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749562072, 'node_id': 'PRRC_kwDOALCn2M6j4vzY', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749562072', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -0,0 +1,2682 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (std::size_t{data_offset} + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          const u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                if (std::size_t{m_ic_write_offset} + size > sizeof(m_ic_write_buffer))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\\n"\n+                                " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                " - size={}\\n",\n+                                m_ic_write_offset, sizeof(m_ic_write_buffer), size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  if ((page * 8 + count * 8) > sizeof(m_ic_card_data) ||\n+                      (10 + count * 8) > sizeof(m_ic_write_buffer))\n+                  {\n+                    ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                  "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\\n"\n+                                  " - m_ic_card_data(offset={}, size={})\\n"\n+                                  " - m_ic_write_buffer(offset={}, size={})\\n"\n+                                  " - size={}, page={}, count={}\\n",\n+                                  page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer),\n+                                  count * 8, page, count);\n+                    data_in = data_in_end;\n+                    break;\n+                  }\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                    break;\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1;\n+                if (data_offset > reply_buffer_size)\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} > buffer_size={}",\n+                                data_offset, reply_buffer_size);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                if (cnt > reply_buffer_size - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                if (offs + cnt > sizeof(icco.extdata))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_CARD,\n+                                "GC-AM: Command 31 (IC-CARD) Read Pages overflow:"\n+                                " offset={} + count={} > buffer_size={}",\n+                                offs, cnt, sizeof(icco.extdata));\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              if (command_offset + 5 >= std::size(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(\n+                    SERIALINTERFACE_AMBB,\n+                    "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}",\n+                    command_offset + 5, std::size(m_motor_reply));\n+                data_in = data_in_end;\n+                break;\n+              }\n+\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              if (reply_size > sizeof(m_motor_reply))\n+              {\n+                ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                              "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply",\n+                              reply_size);\n+                data_in = data_in_end;\n+                break;\n+              }\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                if (std::size_t{m_card_read} + read_length > sizeof(m_card_read_packet))\n+                {\n+                  ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                "GC-AM: Command SerialB, m_card_read_packet overflow:\\n"\n+                                " - m_card_read_packet = {}\\n"\n+                                " - m_card_read = {}\\n"\n+                                " - read_length = {}",\n+                                fmt::ptr(m_card_read_packet), m_card_read, read_length);\n+                  data_in = data_in_end;\n+                  break;\n+                }\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              if (!validate_data_in_out(length, 0, "SerialB"))\n+                break;\n+              if (std::size_t{m_card_offset} + length > std::size(m_card_buffer))', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1308, 'original_position': 1308, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '```suggestion\r\n              if (u64{m_card_offset} + length > std::size(m_card_buffer))\r\n```', 'created_at': '2026-01-31T13:27:48Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749562072', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749562072'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749562072'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749562072/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749563661, 'node_id': 'PRRC_kwDOALCn2M6j4wMN', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749563661', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -0,0 +1,2501 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (data_offset + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                if (cnt > (u32)0x50 - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 2, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = (u32)card.GetSize();\n+\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = (u32)File::GetSize(card_filename);\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+                      u32 packet_offset = 0;\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = (u32)card.GetSize();\n+                        }\n+\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x02;  // SUB CMD\n+                      m_card_read_packet[packet_offset++] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[packet_offset++] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+\n+                      // Data reply\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1498, 'original_position': 1390, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '`m_card_memory_size` is still not checked.', 'created_at': '2026-01-31T13:30:15Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749563661', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749563661'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749563661'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '2415f0c1c471b2446a2144a1a2b643d8a77d8cc7', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749563661/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2725629971}, {'id': 2749564419, 'node_id': 'PRRC_kwDOALCn2M6j4wYD', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749564419', 'pull_request_review_id': 3732508353, 'diff_hunk': '@@ -0,0 +1,2501 @@\n+// Copyright 2025 Dolphin Emulator Project\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include "Core/HW/SI/SI_DeviceAMBaseboard.h"\n+\n+#include <algorithm>\n+#include <numeric>\n+#include <string>\n+\n+#include <fmt/format.h>\n+\n+#include "Common/Buffer.h"\n+#include "Common/CommonTypes.h"\n+#include "Common/FileUtil.h"\n+#include "Common/IOFile.h"\n+#include "Common/Logging/Log.h"\n+#include "Common/MsgHandler.h"\n+#include "Common/Swap.h"\n+\n+#include "Core/Boot/Boot.h"\n+#include "Core/BootManager.h"\n+#include "Core/Config/MainSettings.h"\n+#include "Core/ConfigManager.h"\n+#include "Core/Core.h"\n+#include "Core/CoreTiming.h"\n+#include "Core/HW/DVD/AMMediaboard.h"\n+#include "Core/HW/DVD/DVDInterface.h"\n+#include "Core/HW/EXI/EXI.h"\n+#include "Core/HW/GCPad.h"\n+#include "Core/HW/MMIO.h"\n+#include "Core/HW/Memmap.h"\n+#include "Core/HW/ProcessorInterface.h"\n+#include "Core/HW/SI/SI.h"\n+#include "Core/HW/SI/SI_Device.h"\n+#include "Core/HW/SI/SI_DeviceGCController.h"\n+#include "Core/HW/SystemTimers.h"\n+#include "Core/Movie.h"\n+#include "Core/NetPlayProto.h"\n+#include "Core/System.h"\n+\n+#include "InputCommon/GCPadStatus.h"\n+\n+namespace SerialInterface\n+{\n+void JVSIOMessage::Start(int node)\n+{\n+  m_last_start = m_pointer;\n+  const u8 header[3] = {0xE0, (u8)node, 0};\n+  m_checksum = 0;\n+  AddData(header, 3, 1);\n+}\n+\n+void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0)\n+{\n+  if (m_pointer + len >= sizeof(m_message))\n+  {\n+    PanicAlertFmt("JVSIOMessage overrun!");\n+    return;\n+  }\n+\n+  while (len--)\n+  {\n+    const u8 c = *dst++;\n+    if (!sync && ((c == 0xE0) || (c == 0xD0)))\n+    {\n+      if (m_pointer + 2 > sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = 0xD0;\n+      m_message[m_pointer++] = c - 1;\n+    }\n+    else\n+    {\n+      if (m_pointer >= sizeof(m_message))\n+      {\n+        PanicAlertFmt("JVSIOMessage overrun!");\n+        break;\n+      }\n+      m_message[m_pointer++] = c;\n+    }\n+\n+    if (!sync)\n+      m_checksum += c;\n+    sync = 0;\n+  }\n+}\n+\n+void JVSIOMessage::AddData(const void* data, size_t len)\n+{\n+  AddData(static_cast<const u8*>(data), len);\n+}\n+\n+void JVSIOMessage::AddData(const char* data)\n+{\n+  AddData(data, strlen(data));\n+}\n+\n+void JVSIOMessage::AddData(u32 n)\n+{\n+  const u8 cs = n;\n+  AddData(&cs, 1);\n+}\n+\n+void JVSIOMessage::End()\n+{\n+  const u32 len = m_pointer - m_last_start;\n+  if (m_last_start + 2 < sizeof(m_message) && len >= 3)\n+  {\n+    m_message[m_last_start + 2] = len - 2;  // assuming len <0xD0\n+    AddData(m_checksum + len - 2);\n+  }\n+  else\n+  {\n+    PanicAlertFmt("JVSIOMessage: Not enough space for checksum!");\n+  }\n+}\n+\n+static constexpr u8 CheckSumXOR(const u8* data, u32 length)\n+{\n+  return std::accumulate(data, data + length, u8{}, std::bit_xor());\n+}\n+\n+static constexpr char s_cdr_program_version[] = {"           Version 1.22,2003/09/19,171-8213B"};\n+static constexpr char s_cdr_boot_version[] = {"           Version 1.04,2003/06/17,171-8213B"};\n+static constexpr u8 s_cdr_card_data[] = {\n+    0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B,\n+    0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00,\n+    0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00,\n+    0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28,\n+    0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00,\n+    0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00};\n+\n+const constexpr u8 s_region_flags[] = "\\x00\\x00\\x30\\x00"\n+                                      //   "\\x01\\xfe\\x00\\x00"  // JAPAN\n+                                      "\\x02\\xfd\\x00\\x00"  // USA\n+                                      //"\\x03\\xfc\\x00\\x00"  // export\n+                                      "\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff";\n+// AM-Baseboard device on SI\n+CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device,\n+                                             int device_number)\n+    : ISIDevice(system, device, device_number)\n+{\n+  // Card ID\n+  m_ic_card_data[0x20] = 0x95;\n+  m_ic_card_data[0x21] = 0x71;\n+\n+  if (AMMediaboard::GetGameType() == KeyOfAvalon)\n+  {\n+    m_ic_card_data[0x22] = 0x26;\n+    m_ic_card_data[0x23] = 0x40;\n+  }\n+  else if (AMMediaboard::GetGameType() == VirtuaStriker4)\n+  {\n+    m_ic_card_data[0x22] = 0x44;\n+    m_ic_card_data[0x23] = 0x00;\n+  }\n+\n+  // Use count\n+  m_ic_card_data[0x28] = 0xFF;\n+  m_ic_card_data[0x29] = 0xFF;\n+}\n+\n+constexpr u32 SI_XFER_LENGTH_MASK = 0x7f;\n+\n+// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127]\n+constexpr s32 ConvertSILengthField(u32 field)\n+{\n+  return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;\n+}\n+\n+void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length)\n+{\n+  iccommand->status = Common::swap16(iccommand->status);\n+\n+  const auto iccommand_data = reinterpret_cast<const u8*>(iccommand);\n+  const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1);\n+\n+  for (u32 i = 0; i < iccommand->pktlen + 1; ++i)\n+  {\n+    buffer[(*length)++] = iccommand_data[i];\n+  }\n+\n+  buffer[(*length)++] = crc;\n+}\n+\n+void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length)\n+{\n+  memcpy(m_last[1], buffer, 0x80);     // Save current buffer\n+  memcpy(buffer, m_last[0], 0x80);     // Load previous buffer\n+  memcpy(m_last[0], m_last[1], 0x80);  // Update history\n+\n+  m_lastptr[1] = *buffer_length;  // Swap lengths\n+  *buffer_length = m_lastptr[0];\n+  m_lastptr[0] = m_lastptr[1];\n+}\n+\n+int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)\n+{\n+  const auto& serial_interface = m_system.GetSerialInterface();\n+  u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength());\n+\n+  // Debug logging\n+  ISIDevice::RunBuffer(buffer, buffer_length);\n+\n+  u32 buffer_position = 0;\n+  while (buffer_position < buffer_length)\n+  {\n+    BaseBoardCommand command = static_cast<BaseBoardCommand>(buffer[buffer_position]);\n+    buffer_position++;\n+\n+    switch (command)\n+    {\n+    case BaseBoardCommand::GCAM_Reset:  // Returns ID and dip switches\n+    {\n+      const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100);\n+      std::memcpy(buffer, &id, sizeof(id));\n+      return sizeof(id);\n+    }\n+    break;\n+    case BaseBoardCommand::GCAM_Command:\n+    {\n+      u32 checksum = 0;\n+      for (u32 i = 0; i < buffer_length; ++i)\n+        checksum += buffer[i];\n+\n+      std::array<u8, 0x80> data_out{};\n+      u32 data_offset = 0;\n+\n+      static u32 dip_switch_1 = 0xFE;\n+      static u32 dip_switch_0 = 0xFF;\n+\n+      data_out[data_offset++] = 1;\n+      data_out[data_offset++] = 1;\n+\n+      u8* data_in = buffer + 2;\n+      u8* const data_in_end = buffer + buffer[buffer_position] + 2;\n+\n+      // Helper to check that iterating over data n times is safe,\n+      // i.e. *data++ at most lead to data.end()\n+      auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool {\n+        if (data_in + n_in > data_in_end)\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command);\n+        else if (data_offset + n_out > data_out.size())\n+          ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command);\n+        else\n+          return true;\n+        ERROR_LOG_FMT(SERIALINTERFACE_AMBB,\n+                      "Overflow details:\\n"\n+                      " - data_in(begin={}, current={}, end={}, n_in={})\\n"\n+                      " - data_out(offset={}, size={}, n_out={})\\n"\n+                      " - buffer(position={}, length={})",\n+                      fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in,\n+                      data_offset, data_out.size(), n_out, buffer_position, buffer_length);\n+        data_in = data_in_end;\n+        return false;\n+      };\n+\n+      while (data_in < data_in_end)\n+      {\n+        const u32 gcam_command = *data_in++;\n+        switch (GCAMCommand(gcam_command))\n+        {\n+        case GCAMCommand::StatusSwitches:\n+        {\n+          if (!validate_data_in_out(1, 4, "StatusSwitches"))\n+            break;\n+\n+          const u8 status = *data_in++;\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)",\n+                        status);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x2;\n+\n+          // We read Test/Service from the JVS-I/O SwitchesInput instead\n+          //\n+          // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number);\n+          // baseboard test/service switches\n+          // if (pad_status.button & PAD_BUTTON_Y)\t// Test\n+          //  dip_switch_0 &= ~0x80;\n+          // if (pad_status.button & PAD_BUTTON_X)\t// Service\n+          //  dip_switch_0 &= ~0x40;\n+\n+          // Horizontal Scanning Frequency switch\n+          // Required for F-Zero AX booting via Sega Boot\n+          if (AMMediaboard::GetGameType() == FZeroAX ||\n+              AMMediaboard::GetGameType() == FZeroAXMonster)\n+          {\n+            dip_switch_0 &= ~0x20;\n+          }\n+\n+          // Disable camera in MKGP1/2\n+          if (AMMediaboard::GetGameType() == MarioKartGP ||\n+              AMMediaboard::GetGameType() == MarioKartGP2)\n+          {\n+            dip_switch_0 &= ~0x10;\n+          }\n+\n+          data_out[data_offset++] = dip_switch_0;\n+          data_out[data_offset++] = dip_switch_1;\n+          break;\n+        }\n+        case GCAMCommand::SerialNumber:\n+        {\n+          if (!validate_data_in_out(1, 18, "SerialNumber"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 16;\n+          memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16);\n+\n+          data_offset += 16;\n+          break;\n+        }\n+        case GCAMCommand::Unknown_12:\n+          if (!validate_data_in_out(2, 2, "Unknown_12"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_14:\n+          if (!validate_data_in_out(2, 2, "Unknown_14"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0],\n+                         data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::FirmVersion:\n+          if (!validate_data_in_out(1, 4, "FirmVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 00.26\n+          data_out[data_offset++] = 0x00;\n+          data_out[data_offset++] = 0x26;\n+          break;\n+        case GCAMCommand::FPGAVersion:\n+          if (!validate_data_in_out(1, 4, "FPGAVersion"))\n+            break;\n+\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)",\n+                         *data_in);\n+          data_in++;\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x02;\n+          // 07.06\n+          data_out[data_offset++] = 0x07;\n+          data_out[data_offset++] = 0x06;\n+          break;\n+        case GCAMCommand::RegionSettings:\n+        {\n+          if (!validate_data_in_out(5, 0x16, "RegionSettings"))\n+            break;\n+\n+          // Used by SegaBoot for region checks (dev mode skips this check)\n+          // In some games this also controls the displayed language\n+          NOTICE_LOG_FMT(SERIALINTERFACE_AMBB,\n+                         "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)",\n+                         data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x14;\n+\n+          for (int i = 0; i < 0x14; ++i)\n+            data_out[data_offset++] = s_region_flags[i];\n+\n+          data_in += 5;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends three bytes even though size is set to two\n+        case GCAMCommand::Unknown_21:\n+        {\n+          if (!validate_data_in_out(4, 0, "Unknown_21"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}",\n+                        data_in[0], data_in[1], data_in[2], data_in[3]);\n+          data_in += 4;\n+        }\n+        break;\n+        // No reply\n+        // Note: Always sends six bytes\n+        case GCAMCommand::Unknown_22:\n+        {\n+          if (!validate_data_in_out(7, 0, "Unknown_22"))\n+            break;\n+\n+          DEBUG_LOG_FMT(\n+              SERIALINTERFACE_AMBB,\n+              "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}",\n+              data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]);\n+\n+          const u32 in_size = data_in[0] + 1;\n+          if (!validate_data_in_out(in_size, 0, "Unknown_22"))\n+            break;\n+          data_in += in_size;\n+        }\n+        break;\n+        case GCAMCommand::Unknown_23:\n+          if (!validate_data_in_out(2, 2, "Unknown_23"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::Unknown_24:\n+          if (!validate_data_in_out(2, 2, "Unknown_24"))\n+            break;\n+\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0],\n+                        data_in[1]);\n+\n+          data_out[data_offset++] = gcam_command;\n+          data_out[data_offset++] = 0x00;\n+\n+          data_in += 2;\n+          break;\n+        case GCAMCommand::SerialA:\n+        {\n+          if (!validate_data_in_out(1, 0, "SerialA"))\n+            break;\n+\n+          u32 length = *data_in++;\n+          if (length)\n+          {\n+            if (!validate_data_in_out(length, 0, "SerialA"))\n+              break;\n+\n+            INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\\n{}",\n+                         length, HexDump(data_in, length));\n+\n+            // Serial - Wheel\n+            if (AMMediaboard::GetGameType() == MarioKartGP ||\n+                AMMediaboard::GetGameType() == MarioKartGP2)\n+            {\n+              if (!validate_data_in_out(10, 2, "SerialA (Wheel)"))\n+                break;\n+\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} "\n+                           "{:02x} {:02x} {:02x} {:02x}",\n+                           data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5],\n+                           data_in[6], data_in[7], data_in[8], data_in[9]);\n+\n+              data_out[data_offset++] = gcam_command;\n+              data_out[data_offset++] = 0x03;\n+\n+              switch (m_wheel_init)\n+              {\n+              case 0:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'E\';  // Error\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'0\';\n+                m_wheel_init++;\n+                break;\n+              case 1:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power Off\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'6\';\n+                // Only turn on when a wheel is connected\n+                if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                {\n+                  m_wheel_init++;\n+                }\n+                break;\n+              case 2:\n+                if (!validate_data_in_out(0, 3, "SerialA (Wheel)"))\n+                  break;\n+                data_out[data_offset++] = \'C\';  // Power On\n+                data_out[data_offset++] = \'0\';\n+                data_out[data_offset++] = \'1\';\n+                break;\n+              default:\n+                break;\n+              }\n+\n+              // u16 CenteringForce= ptr(6);\n+              // u16 FrictionForce = ptr(8);\n+              // u16 Roll          = ptr(10);\n+              if (!validate_data_in_out(length, 0, "SerialA (Wheel)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial - Unknown\n+            if (AMMediaboard::GetGameType() == GekitouProYakyuu)\n+            {\n+              if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)"))\n+                break;\n+              const u32 serial_command = Common::BitCastPtr<u32>(data_in);\n+\n+              if (serial_command == 0x00001000)\n+              {\n+                if (!validate_data_in_out(0, 5, "SerialA (Unknown)"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                data_out[data_offset++] = 0x03;\n+                data_out[data_offset++] = 1;\n+                data_out[data_offset++] = 2;\n+                data_out[data_offset++] = 3;\n+              }\n+\n+              if (!validate_data_in_out(length, 0, "SerialA (Unknown)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+\n+            // Serial IC-CARD / Serial Deck Reader\n+            if (AMMediaboard::GetGameType() == VirtuaStriker4 ||\n+                AMMediaboard::GetGameType() == VirtuaStriker4_2006 ||\n+                AMMediaboard::GetGameType() == KeyOfAvalon)\n+            {\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              u32 serial_command = data_in[1];\n+\n+              ICCommand icco;\n+\n+              // Set default reply\n+              icco.pktcmd = gcam_command;\n+              icco.pktlen = 7;\n+              icco.fixed = 0x10;\n+              icco.command = serial_command;\n+              icco.flag = 0;\n+              icco.length = 2;\n+              icco.status = 0;\n+              icco.extlen = 0;\n+\n+              // Check for rest of data from the write pages command\n+              if (m_ic_write_size && m_ic_write_offset)\n+              {\n+                const u32 size = data_in[1];\n+\n+                if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2));\n+\n+                INFO_LOG_FMT(\n+                    SERIALINTERFACE_CARD,\n+                    "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}",\n+                    m_ic_write_offset, m_ic_write_size, size);\n+\n+                memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size);\n+\n+                m_ic_write_offset += size;\n+\n+                if (m_ic_write_offset > m_ic_write_size)\n+                {\n+                  m_ic_write_offset = 0;\n+\n+                  const u16 page = m_ic_write_buffer[5];\n+                  const u16 count = m_ic_write_buffer[7];\n+\n+                  memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8);\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+\n+                  icco.command = WritePages;\n+\n+                  ICCardSendReply(&icco, data_out.data(), &data_offset);\n+                }\n+                if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                data_in += length;\n+                break;\n+              }\n+\n+              switch (ICCARDCommand(serial_command))\n+              {\n+              case ICCARDCommand::GetStatus:\n+                icco.status = m_ic_card_state;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state);\n+                break;\n+              case ICCARDCommand::SetBaudrate:\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate");\n+                break;\n+              case ICCARDCommand::FieldOn:\n+                m_ic_card_state |= 0x10;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On");\n+                break;\n+              case ICCARDCommand::InsertCheck:\n+                icco.status = m_ic_card_status;\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status);\n+                break;\n+              case ICCARDCommand::AntiCollision:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Card ID\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = 0x00;\n+                icco.extdata[2] = 0x54;\n+                icco.extdata[3] = 0x4D;\n+                icco.extdata[4] = 0x50;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision");\n+                break;\n+              case ICCARDCommand::SelectCard:\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                // Session\n+                icco.extdata[0] = 0x00;\n+                icco.extdata[1] = m_ic_card_session;\n+                icco.extdata[2] = 0x00;\n+                icco.extdata[3] = 0x00;\n+                icco.extdata[4] = 0x00;\n+                icco.extdata[5] = 0x00;\n+                icco.extdata[6] = 0x00;\n+                icco.extdata[7] = 0x00;\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}",\n+                             m_ic_card_session);\n+                break;\n+              case ICCARDCommand::ReadPage:\n+              case ICCARDCommand::ReadUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 8;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + page * 8, 8);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::WritePage:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 8) & 0xFF;  // 255 is max page\n+\n+                // Write only one page\n+                if (page == 4)\n+                {\n+                  icco.status = 0x80;\n+                }\n+                else\n+                {\n+                  if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_card_data + page * 8, data_in + 10, 8);\n+                }\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}",\n+                             page);\n+                break;\n+              }\n+              case ICCARDCommand::DecreaseUseCount:\n+              {\n+                if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+\n+                icco.extlen = 2;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                auto ic_card_data = Common::BitCastPtr<u16>(m_ic_card_data + 0x28);\n+                ic_card_data = ic_card_data - 1;\n+\n+                // Counter\n+                icco.extdata[0] = m_ic_card_data[0x28];\n+                icco.extdata[1] = m_ic_card_data[0x29];\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page);\n+                break;\n+              }\n+              case ICCARDCommand::ReadPages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                const u32 offs = page * 8;\n+                u32 cnt = count * 8;\n+\n+                // Limit read size to not overwrite the reply buffer\n+                if (cnt > (u32)0x50 - data_offset)\n+                {\n+                  cnt = 5 * 8;\n+                }\n+\n+                icco.extlen = cnt;\n+                icco.length += icco.extlen;\n+                icco.pktlen += icco.extlen;\n+\n+                memcpy(icco.extdata, m_ic_card_data + offs, cnt);\n+\n+                INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                             "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count);\n+                break;\n+              }\n+              case ICCARDCommand::WritePages:\n+              {\n+                if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)"))\n+                  break;\n+                const u16 pksize = length;\n+                const u16 size = Common::swap16(data_in + 2);\n+                const u16 page = Common::swap16(data_in + 6) & 0xFF;  // 255 is max page\n+                const u16 count = Common::swap16(data_in + 8);\n+\n+                // We got a complete packet\n+                if (pksize - 5 == size)\n+                {\n+                  if (page == 4)  // Read Only Page, must return error\n+                  {\n+                    icco.status = 0x80;\n+                  }\n+                  else\n+                  {\n+                    if (page * 8 + count * 8 > sizeof(m_ic_card_data))\n+                    {\n+                      ERROR_LOG_FMT(\n+                          SERIALINTERFACE_CARD,\n+                          "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})",\n+                          page, count, size);\n+                    }\n+                    else\n+                    {\n+                      if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)"))\n+                        break;\n+                      memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8);\n+                    }\n+                  }\n+\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page,\n+                               count, size);\n+                }\n+                // VirtuaStriker 4 splits the writes over multiple packets\n+                else\n+                {\n+                  if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)"))\n+                    break;\n+                  memcpy(m_ic_write_buffer, data_in + 2, pksize);\n+                  m_ic_write_offset += pksize;\n+                  m_ic_write_size = size;\n+                }\n+                break;\n+              }\n+              default:\n+                // Handle Deck Reader commands\n+                if (!validate_data_in_out(1, 0, "SerialA (DECK READER)"))\n+                  break;\n+                serial_command = data_in[0];\n+                icco.command = serial_command;\n+                icco.flag = 0;\n+                switch (CDReaderCommand(serial_command))\n+                {\n+                case CDReaderCommand::ProgramVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_program_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_program_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::BootVersion:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Version");\n+\n+                  icco.extlen = (u32)strlen(s_cdr_boot_version);\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_boot_version, icco.extlen);\n+                  break;\n+                case CDReaderCommand::ShutterGet:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Get");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0;\n+                  icco.extdata[1] = 0;\n+                  icco.extdata[2] = 0;\n+                  icco.extdata[3] = 0;\n+                  break;\n+                case CDReaderCommand::CameraCheck:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Camera Check");\n+\n+                  icco.extlen = 6;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  icco.extdata[4] = 0x45;\n+                  icco.extdata[5] = 0x29;\n+                  break;\n+                case CDReaderCommand::ProgramChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Program Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::BootChecksum:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Boot Checksum");\n+\n+                  icco.extlen = 4;\n+                  icco.length += icco.extlen;\n+                  icco.pktlen += icco.extlen;\n+\n+                  icco.extdata[0] = 0x23;\n+                  icco.extdata[1] = 0x28;\n+                  icco.extdata[2] = 0x45;\n+                  icco.extdata[3] = 0x29;\n+                  break;\n+                case CDReaderCommand::SelfTest:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test");\n+                  icco.flag = 0x00;\n+                  break;\n+                case CDReaderCommand::SensLock:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock");\n+                  icco.flag = 0x01;\n+                  break;\n+                case CDReaderCommand::SensCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card");\n+                  break;\n+                case CDReaderCommand::ShutterCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (DECK READER) Shutter Card");\n+                  break;\n+                case CDReaderCommand::ReadCard:\n+                  INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card");\n+\n+                  icco.fixed = 0xAA;\n+                  icco.flag = 0xAA;\n+                  icco.extlen = sizeof(s_cdr_card_data);\n+                  icco.length = 0x72;\n+                  icco.status = Common::swap16(icco.extlen);\n+\n+                  icco.pktlen += icco.extlen;\n+\n+                  memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data));\n+\n+                  break;\n+                default:\n+                  if (!validate_data_in_out(14, 0, "SerialA (DECK READER)"))\n+                    break;\n+                  WARN_LOG_FMT(SERIALINTERFACE_CARD,\n+                               "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} "\n+                               "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",\n+                               data_in[2], data_in[3], data_in[4], data_in[5], data_in[6],\n+                               data_in[7], data_in[8], data_in[9], data_in[10], data_in[11],\n+                               data_in[12], data_in[13]);\n+                  break;\n+                }\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)"))\n+                break;\n+              ICCardSendReply(&icco, data_out.data(), &data_offset);\n+\n+              if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)"))\n+                break;\n+              data_in += length;\n+              break;\n+            }\n+          }\n+\n+          u32 command_offset = 0;\n+          while (command_offset < length)\n+          {\n+            // All commands are OR\'d with 0x80\n+            // Last byte is checksum which we don\'t care about\n+            if (!validate_data_in_out(command_offset + 4, 0, "SerialA"))\n+              break;\n+            const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000;\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                           "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})",\n+                           length, (serial_command >> 8) & 0xFFFF, serial_command >> 24);\n+            }\n+            else\n+            {\n+              INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}",\n+                           serial_command);\n+\n+              if (serial_command == 0x801000)\n+              {\n+                if (!validate_data_in_out(0, 4, "SerialA"))\n+                  break;\n+                data_out[data_offset++] = 0x31;\n+                data_out[data_offset++] = 0x02;\n+                data_out[data_offset++] = 0xFF;\n+                data_out[data_offset++] = 0x01;\n+              }\n+            }\n+\n+            command_offset += sizeof(u32);\n+\n+            if (AMMediaboard::GetGameType() == FZeroAX ||\n+                AMMediaboard::GetGameType() == FZeroAXMonster)\n+            {\n+              // Status\n+              m_motor_reply[command_offset + 2] = 0;\n+              m_motor_reply[command_offset + 3] = 0;\n+\n+              // Error\n+              m_motor_reply[command_offset + 4] = 0;\n+\n+              switch (serial_command >> 24)\n+              {\n+              case 0:\n+              case 1:  // Set Maximum?\n+              case 2:\n+                break;\n+\n+              // 0x00-0x40: left\n+              // 0x40-0x80: right\n+              case 4:  // Move Steering Wheel\n+                // Left\n+                if (serial_command & 0x010000)\n+                {\n+                  m_motor_force_y = -((s16)serial_command & 0xFF00);\n+                }\n+                else  // Right\n+                {\n+                  m_motor_force_y = (serial_command - 0x4000) & 0xFF00;\n+                }\n+\n+                m_motor_force_y *= 2;\n+\n+                // FFB\n+                if (m_motor_init == 2)\n+                {\n+                  if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING)\n+                  {\n+                    const GCPadStatus pad_status = Pad::GetStatus(1);\n+                    if (pad_status.isConnected)\n+                    {\n+                      const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f;\n+\n+                      Pad::Rumble(1, mapped_strength);\n+                      INFO_LOG_FMT(SERIALINTERFACE_AMBB,\n+                                   "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}",\n+                                   mapped_strength);\n+                    }\n+                  }\n+                }\n+                break;\n+              case 6:  // nice\n+              case 9:\n+              default:\n+                break;\n+              // Switch back to normal controls\n+              case 7:\n+                m_motor_init = 2;\n+                break;\n+              // Reset\n+              case 0x7F:\n+                m_motor_init = 1;\n+                memset(m_motor_reply, 0, sizeof(m_motor_reply));\n+                break;\n+              }\n+\n+              // Checksum\n+              m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^\n+                                                  m_motor_reply[command_offset + 3] ^\n+                                                  m_motor_reply[command_offset + 4];\n+            }\n+          }\n+\n+          if (length == 0)\n+          {\n+            if (!validate_data_in_out(length, 2, "SerialA"))\n+              break;\n+            data_out[data_offset++] = gcam_command;\n+            data_out[data_offset++] = 0x00;\n+          }\n+          else\n+          {\n+            if (m_motor_init)\n+            {\n+              // Motor\n+              m_motor_reply[0] = gcam_command;\n+              m_motor_reply[1] = length;  // Same out as in size\n+\n+              const u32 reply_size = m_motor_reply[1] + 2;\n+              if (!validate_data_in_out(length, reply_size, "SerialA"))\n+                break;\n+\n+              memcpy(data_out.data() + data_offset, m_motor_reply, reply_size);\n+              data_offset += reply_size;\n+            }\n+          }\n+\n+          if (!validate_data_in_out(length, 0, "SerialA"))\n+          {\n+            break;\n+          }\n+          data_in += length;\n+          break;\n+        }\n+        case GCAMCommand::SerialB:\n+        {\n+          DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");\n+          if (!validate_data_in_out(1, 0, "SerialB"))\n+            break;\n+          const u32 length = *data_in++;\n+          if (!validate_data_in_out(length, 0, "SerialB"))\n+            break;\n+          if (length)\n+          {\n+            // Send Card Reply\n+            if (length == 1 && data_in[0] == 0x05)\n+            {\n+              if (m_card_read_length)\n+              {\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = gcam_command;\n+                u32 read_length = m_card_read_length - m_card_read;\n+\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  read_length = std::min<u32>(read_length, 0x2F);\n+                }\n+\n+                if (!validate_data_in_out(0, 1, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = read_length;  // 0x2F (max size per packet)\n+\n+                if (!validate_data_in_out(0, read_length, "SerialB"))\n+                  break;\n+                memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read,\n+                       read_length);\n+\n+                data_offset += read_length;\n+                m_card_read += read_length;\n+\n+                if (m_card_read >= m_card_read_length)\n+                  m_card_read_length = 0;\n+\n+                data_in += length;\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 5, "SerialB"))\n+                break;\n+\n+              data_out[data_offset++] = gcam_command;\n+              const u32 command_length_offset = data_offset;\n+              data_out[data_offset++] = 0x00;  // len\n+\n+              data_out[data_offset++] = 0x02;\n+              const u32 checksum_start = data_offset;\n+\n+              data_out[data_offset++] = 0x00;  // 0x00 len\n+\n+              data_out[data_offset++] = m_card_command;  // 0x01 command\n+\n+              switch (CARDCommand(m_card_command))\n+              {\n+              case CARDCommand::Init:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::GetState:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x20 | m_card_bit;  // 0x02\n+\n+                // bit 0: Please take your card\n+                // bit 1: Endless waiting causes UNK_E to be called\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Read:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x53;  // 0x03\n+                break;\n+              case CARDCommand::IsPresent:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x22;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Write:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::SetPrintParam:\n+              case CARDCommand::RegisterFont:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::WriteInfo:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Erase:\n+                // TODO: CARDCommand::Erase is not handled.\n+                break;\n+              case CARDCommand::Eject:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                if (AMMediaboard::GetGameType() == FZeroAX)\n+                {\n+                  data_out[data_offset++] = 0x01;  // 0x02\n+                }\n+                else\n+                {\n+                  data_out[data_offset++] = 0x31;  // 0x02\n+                }\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::Clean:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              case CARDCommand::Load:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x02;  // 0x02\n+                data_out[data_offset++] = 0x30;  // 0x03\n+                break;\n+              case CARDCommand::SetShutter:\n+                if (!validate_data_in_out(0, 2, "SerialB"))\n+                  break;\n+                data_out[data_offset++] = 0x00;  // 0x02\n+                data_out[data_offset++] = 0x00;  // 0x03\n+                break;\n+              }\n+\n+              if (!validate_data_in_out(0, 3, "SerialB"))\n+                break;\n+              data_out[data_offset++] = 0x30;  // 0x04\n+              data_out[data_offset++] = 0x00;  // 0x05\n+\n+              data_out[data_offset++] = 0x03;  // 0x06\n+\n+              data_out[checksum_start] = data_offset - checksum_start;  // 0x00 len\n+\n+              if (!validate_data_in_out(0, 1, "SerialB"))\n+                break;\n+              data_out[data_offset] = 0;  // 0x07\n+              for (u32 i = 0; i < data_out[checksum_start]; ++i)\n+                data_out[data_offset] ^= data_out[checksum_start + i];\n+\n+              if (!validate_data_in_out(0, 2, "SerialB"))\n+                break;\n+              data_offset++;\n+\n+              data_out[command_length_offset] = data_out[checksum_start] + 2;\n+            }\n+            else\n+            {\n+              for (u32 i = 0; i < length; ++i)\n+                m_card_buffer[m_card_offset + i] = data_in[i];\n+\n+              m_card_offset += length;\n+\n+              // Check if we got a complete command\n+              if (m_card_buffer[0] == 0x02)\n+              {\n+                if (m_card_buffer[1] == m_card_offset - 2)\n+                {\n+                  if (m_card_buffer[m_card_offset - 2] == 0x03)\n+                  {\n+                    m_card_command = m_card_buffer[2];\n+\n+                    switch (CARDCommand(m_card_command))\n+                    {\n+                    case CARDCommand::Init:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init");\n+\n+                      m_card_write_length = 0;\n+                      m_card_bit = 0;\n+                      m_card_memory_size = 0;\n+                      m_card_state_call_count = 0;\n+                      break;\n+                    case CARDCommand::GetState:\n+                    {\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})",\n+                                     m_card_bit);\n+\n+                      if (m_card_memory_size == 0)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          File::IOFile card(card_filename, "rb+");\n+                          m_card_memory_size = (u32)card.GetSize();\n+\n+                          card.ReadBytes(m_card_memory, m_card_memory_size);\n+                          card.Close();\n+\n+                          m_card_is_inserted = true;\n+                        }\n+                      }\n+\n+                      if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size)\n+                      {\n+                        m_card_state_call_count++;\n+                        if (m_card_state_call_count > 10)\n+                        {\n+                          if (m_card_bit & 2)\n+                            m_card_bit &= ~2u;\n+                          else\n+                            m_card_bit |= 2;\n+\n+                          m_card_state_call_count = 0;\n+                        }\n+                      }\n+\n+                      if (m_card_clean == 1)\n+                      {\n+                        m_card_clean = 2;\n+                      }\n+                      else if (m_card_clean == 2)\n+                      {\n+                        const std::string card_filename(\n+                            fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                        SConfig::GetInstance().GetGameID()));\n+\n+                        if (File::Exists(card_filename))\n+                        {\n+                          m_card_memory_size = (u32)File::GetSize(card_filename);\n+                          if (m_card_memory_size)\n+                          {\n+                            if (AMMediaboard::GetGameType() == FZeroAX)\n+                            {\n+                              m_card_bit = 2;\n+                            }\n+                            else\n+                            {\n+                              m_card_bit = 1;\n+                            }\n+                          }\n+                        }\n+                        m_card_clean = 0;\n+                      }\n+                      break;\n+                    }\n+                    case CARDCommand::IsPresent:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent");\n+                      break;\n+                    case CARDCommand::RegisterFont:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont");\n+                      break;\n+                    case CARDCommand::Load:\n+                    {\n+                      u8 mode = m_card_buffer[6];\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})",\n+                                     mode);\n+                      break;\n+                    }\n+                    case CARDCommand::Clean:\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean");\n+                      m_card_clean = 1;\n+                      break;\n+                    case CARDCommand::Read:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      NOTICE_LOG_FMT(SERIALINTERFACE_CARD,\n+                                     "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode,\n+                                     bitmode, track);\n+\n+                      // Prepare read packet\n+                      memset(m_card_read_packet, 0, 0xDB);\n+                      u32 packet_offset = 0;\n+\n+                      const std::string card_filename(\n+                          fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX),\n+                                      SConfig::GetInstance().GetGameID()));\n+\n+                      if (File::Exists(card_filename))\n+                      {\n+                        File::IOFile card(card_filename, "rb+");\n+                        if (m_card_memory_size == 0)\n+                        {\n+                          m_card_memory_size = (u32)card.GetSize();\n+                        }\n+\n+                        card.ReadBytes(m_card_memory, m_card_memory_size);\n+                        card.Close();\n+\n+                        m_card_is_inserted = true;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x02;  // SUB CMD\n+                      m_card_read_packet[packet_offset++] = 0x00;  // SUB CMDLen\n+\n+                      m_card_read_packet[packet_offset++] = 0x33;  // CARD CMD\n+\n+                      if (m_card_is_inserted)  // CARD Status\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x31;\n+                      }\n+                      else\n+                      {\n+                        m_card_read_packet[packet_offset++] = 0x30;\n+                      }\n+\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+                      m_card_read_packet[packet_offset++] = 0x30;\n+\n+                      // Data reply\n+                      memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size);\n+                      packet_offset += m_card_memory_size;\n+\n+                      m_card_read_packet[packet_offset++] = 0x03;\n+\n+                      m_card_read_packet[1] = packet_offset - 1;  // SUB CMDLen\n+\n+                      for (u32 i = 0; i < packet_offset - 1; ++i)\n+                        m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i];\n+\n+                      packet_offset++;\n+\n+                      m_card_read_length = packet_offset;\n+                      m_card_read = 0;\n+                      break;\n+                    }\n+                    case CARDCommand::Write:\n+                    {\n+                      const u8 mode = m_card_buffer[6];\n+                      const u8 bitmode = m_card_buffer[7];\n+                      const u8 track = m_card_buffer[8];\n+\n+                      m_card_memory_size = m_card_buffer[1] - 9;\n+\n+                      memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size);', 'path': 'Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp', 'position': 1536, 'original_position': 1414, 'commit_id': '510fec88be973bc870021f2d0347593cdc285864', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '`m_card_memory_size` is still not checked.', 'created_at': '2026-01-31T13:31:17Z', 'updated_at': '2026-01-31T13:33:12Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749564419', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749564419'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13844#discussion_r2749564419'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844'}}, 'original_commit_id': '2415f0c1c471b2446a2144a1a2b643d8a77d8cc7', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749564419/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2725631348}], 'type': 'gh_pull_request_review'}
2026-01-31T13:00:47.796532	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'submitted', 'pr_id': 14308, 'pr_title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308#pullrequestreview-3732505040', 'comments': [{'id': 2749544190, 'node_id': 'PRRC_kwDOALCn2M6j4rb-', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749544190', 'pull_request_review_id': 3732505040, 'diff_hunk': '@@ -391,6 +390,189 @@ size_t StringUTF8CodePointCount(std::string_view str)\n   return str.size() - std::ranges::count_if(str, [](char c) -> bool { return (c & 0xC0) == 0x80; });\n }\n \n+enum class DecodeError\n+{\n+  End,\n+  IncompleteSequence,\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 1)\n+class UTF8Decoder\n+{\n+public:\n+  constexpr explicit UTF8Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u8 first_byte = *m_ptr;\n+    ++m_ptr;\n+\n+    // This is either ASCII or an invalid byte that we will interpret as\n+    //  ISO/IEC 8859-1 or "Extended ASCII".\n+    //  In particular, this handles 0xa0 (NO-BREAK SPACE).', 'path': 'Source/Core/Common/StringUtil.cpp', 'position': 47, 'original_position': 47, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Yeah.. you're not wrong.\r\n\r\nAlso, I just re-tested the locale issue and it seems like it fixed itself somehow?\r\nI haven't figured out how yet.. fmt might be translating the `0xa0` to `0x20` somewhere?\r\n", 'created_at': '2026-01-31T13:00:45Z', 'updated_at': '2026-01-31T13:00:45Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749544190', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749544190'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749544190'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749544190/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2749402975}], 'type': 'gh_pull_request_review'}
2026-01-31T11:36:10.006954	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 14308, 'pr_title': 'StringUtil: Make UTF16ToUTF8 and UTF8ToUTF16 use custom encoding/decoding implementation.', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14308#pullrequestreview-3732310342', 'comments': [{'id': 2749402975, 'node_id': 'PRRC_kwDOALCn2M6j4I9f', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749402975', 'pull_request_review_id': 3732310342, 'diff_hunk': '@@ -391,6 +390,189 @@ size_t StringUTF8CodePointCount(std::string_view str)\n   return str.size() - std::ranges::count_if(str, [](char c) -> bool { return (c & 0xC0) == 0x80; });\n }\n \n+enum class DecodeError\n+{\n+  End,\n+  IncompleteSequence,\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 1)\n+class UTF8Decoder\n+{\n+public:\n+  constexpr explicit UTF8Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u8 first_byte = *m_ptr;\n+    ++m_ptr;\n+\n+    // This is either ASCII or an invalid byte that we will interpret as\n+    //  ISO/IEC 8859-1 or "Extended ASCII".\n+    //  In particular, this handles 0xa0 (NO-BREAK SPACE).', 'path': 'Source/Core/Common/StringUtil.cpp', 'position': 47, 'original_position': 47, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'This is in my opinion not desirable behavior. If the input isn\'t valid UTF-8, we shouldn\'t "guess" what we think it might be.\n\nIf you want this because of https://bugs.dolphin-emu.org/issues/12502, I would prefer having special handling for this in the code that deals with file size formatting, not code that can be triggered any time Dolphin decodes UTF-8.', 'created_at': '2026-01-31T11:10:16Z', 'updated_at': '2026-01-31T11:36:07Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749402975', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749402975'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749402975'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749402975/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749404099, 'node_id': 'PRRC_kwDOALCn2M6j4JPD', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749404099', 'pull_request_review_id': 3732310342, 'diff_hunk': '@@ -391,6 +390,189 @@ size_t StringUTF8CodePointCount(std::string_view str)\n   return str.size() - std::ranges::count_if(str, [](char c) -> bool { return (c & 0xC0) == 0x80; });\n }\n \n+enum class DecodeError\n+{\n+  End,\n+  IncompleteSequence,\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 1)\n+class UTF8Decoder\n+{\n+public:\n+  constexpr explicit UTF8Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u8 first_byte = *m_ptr;\n+    ++m_ptr;\n+\n+    // This is either ASCII or an invalid byte that we will interpret as\n+    //  ISO/IEC 8859-1 or "Extended ASCII".\n+    //  In particular, this handles 0xa0 (NO-BREAK SPACE).\n+    if (first_byte < 0xc0u)\n+      return first_byte;\n+\n+    u32 byte_count = std::countl_one(first_byte);\n+\n+    // Remove leading 1s.\n+    u32 current_value = u8(first_byte << byte_count) >> byte_count;\n+\n+    while (--byte_count != 0)\n+    {\n+      if (m_ptr == m_end_ptr)\n+        return std::unexpected{DecodeError::IncompleteSequence};\n+\n+      current_value = (current_value << 6u) | (u8(*m_ptr) & 0x3fu);\n+      ++m_ptr;\n+    }\n+\n+    return current_value;', 'path': 'Source/Core/Common/StringUtil.cpp', 'position': 65, 'original_position': 65, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Code points larger than U+10FFFF should be rejected.', 'created_at': '2026-01-31T11:12:13Z', 'updated_at': '2026-01-31T11:36:07Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749404099', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749404099'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749404099'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749404099/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749406314, 'node_id': 'PRRC_kwDOALCn2M6j4Jxq', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749406314', 'pull_request_review_id': 3732310342, 'diff_hunk': '@@ -391,6 +390,189 @@ size_t StringUTF8CodePointCount(std::string_view str)\n   return str.size() - std::ranges::count_if(str, [](char c) -> bool { return (c & 0xC0) == 0x80; });\n }\n \n+enum class DecodeError\n+{\n+  End,\n+  IncompleteSequence,\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 1)\n+class UTF8Decoder\n+{\n+public:\n+  constexpr explicit UTF8Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u8 first_byte = *m_ptr;\n+    ++m_ptr;\n+\n+    // This is either ASCII or an invalid byte that we will interpret as\n+    //  ISO/IEC 8859-1 or "Extended ASCII".\n+    //  In particular, this handles 0xa0 (NO-BREAK SPACE).\n+    if (first_byte < 0xc0u)\n+      return first_byte;\n+\n+    u32 byte_count = std::countl_one(first_byte);\n+\n+    // Remove leading 1s.\n+    u32 current_value = u8(first_byte << byte_count) >> byte_count;\n+\n+    while (--byte_count != 0)\n+    {\n+      if (m_ptr == m_end_ptr)\n+        return std::unexpected{DecodeError::IncompleteSequence};\n+\n+      current_value = (current_value << 6u) | (u8(*m_ptr) & 0x3fu);', 'path': 'Source/Core/Common/StringUtil.cpp', 'position': 61, 'original_position': 61, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Overlong encodings should be rejected (i.e. if `current_value` previously was 0, you should return an error).', 'created_at': '2026-01-31T11:16:38Z', 'updated_at': '2026-01-31T11:36:08Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749406314', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749406314'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749406314'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749406314/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749407669, 'node_id': 'PRRC_kwDOALCn2M6j4KG1', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749407669', 'pull_request_review_id': 3732310342, 'diff_hunk': '@@ -391,6 +390,189 @@ size_t StringUTF8CodePointCount(std::string_view str)\n   return str.size() - std::ranges::count_if(str, [](char c) -> bool { return (c & 0xC0) == 0x80; });\n }\n \n+enum class DecodeError\n+{\n+  End,\n+  IncompleteSequence,\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 1)\n+class UTF8Decoder\n+{\n+public:\n+  constexpr explicit UTF8Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u8 first_byte = *m_ptr;\n+    ++m_ptr;\n+\n+    // This is either ASCII or an invalid byte that we will interpret as\n+    //  ISO/IEC 8859-1 or "Extended ASCII".\n+    //  In particular, this handles 0xa0 (NO-BREAK SPACE).\n+    if (first_byte < 0xc0u)\n+      return first_byte;\n+\n+    u32 byte_count = std::countl_one(first_byte);\n+\n+    // Remove leading 1s.\n+    u32 current_value = u8(first_byte << byte_count) >> byte_count;\n+\n+    while (--byte_count != 0)\n+    {\n+      if (m_ptr == m_end_ptr)\n+        return std::unexpected{DecodeError::IncompleteSequence};\n+\n+      current_value = (current_value << 6u) | (u8(*m_ptr) & 0x3fu);\n+      ++m_ptr;\n+    }\n+\n+    return current_value;\n+  }\n+\n+private:\n+  const CharType* m_ptr;\n+  const CharType* const m_end_ptr;\n+};\n+\n+class UTF8Encoder\n+{\n+public:\n+  static constexpr u32 GetMaxUnitsPerCodePoint() { return 4; }\n+\n+  // `ptr` should point to at least 4 bytes.\n+  // Returns the number of written code units (bytes).\n+  template <std::integral CharType>\n+  requires(sizeof(CharType) == 1)\n+  constexpr u32 operator()(char32_t code_point, CharType* ptr)\n+  {\n+    u32 value = code_point;\n+    if (value < 0x80u)\n+    {\n+      *ptr = u8(value);\n+      return 1;\n+    }\n+\n+    u32 additional_bytes = 1;\n+    if (value < 0x800u)\n+    {\n+      *ptr = u8(0xc0u | (value >> 6u));\n+    }\n+    else if (value < 0x10000u)\n+    {\n+      additional_bytes = 2;\n+      *ptr = u8(0xe0u | (value >> 12u));\n+    }\n+    else\n+    {\n+      additional_bytes = 3;\n+      *ptr = u8(0xf0u | (value >> 18u));\n+    }\n+\n+    for (u32 i = additional_bytes; i != 0; --i)\n+    {\n+      ptr[i] = u8(0x80u | (value & 0x3fu));\n+      value >>= 6;\n+    }\n+\n+    return 1 + additional_bytes;\n+  }\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 2)\n+class UTF16Decoder\n+{\n+public:\n+  constexpr explicit UTF16Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u32 current_value = u16(*m_ptr);\n+    ++m_ptr;\n+\n+    if (current_value < 0xd800u || current_value > 0xd8ffu)\n+      return current_value;\n+\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::IncompleteSequence};\n+\n+    // We assume the surrogate pair is valid.', 'path': 'Source/Core/Common/StringUtil.cpp', 'position': 141, 'original_position': 141, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Don't assume this.", 'created_at': '2026-01-31T11:19:11Z', 'updated_at': '2026-01-31T11:36:08Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749407669', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749407669'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749407669'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749407669/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749408809, 'node_id': 'PRRC_kwDOALCn2M6j4KYp', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749408809', 'pull_request_review_id': 3732310342, 'diff_hunk': '@@ -391,6 +390,189 @@ size_t StringUTF8CodePointCount(std::string_view str)\n   return str.size() - std::ranges::count_if(str, [](char c) -> bool { return (c & 0xC0) == 0x80; });\n }\n \n+enum class DecodeError\n+{\n+  End,\n+  IncompleteSequence,\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 1)\n+class UTF8Decoder\n+{\n+public:\n+  constexpr explicit UTF8Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u8 first_byte = *m_ptr;\n+    ++m_ptr;\n+\n+    // This is either ASCII or an invalid byte that we will interpret as\n+    //  ISO/IEC 8859-1 or "Extended ASCII".\n+    //  In particular, this handles 0xa0 (NO-BREAK SPACE).\n+    if (first_byte < 0xc0u)\n+      return first_byte;\n+\n+    u32 byte_count = std::countl_one(first_byte);\n+\n+    // Remove leading 1s.\n+    u32 current_value = u8(first_byte << byte_count) >> byte_count;\n+\n+    while (--byte_count != 0)\n+    {\n+      if (m_ptr == m_end_ptr)\n+        return std::unexpected{DecodeError::IncompleteSequence};\n+\n+      current_value = (current_value << 6u) | (u8(*m_ptr) & 0x3fu);\n+      ++m_ptr;\n+    }\n+\n+    return current_value;\n+  }\n+\n+private:\n+  const CharType* m_ptr;\n+  const CharType* const m_end_ptr;\n+};\n+\n+class UTF8Encoder\n+{\n+public:\n+  static constexpr u32 GetMaxUnitsPerCodePoint() { return 4; }\n+\n+  // `ptr` should point to at least 4 bytes.\n+  // Returns the number of written code units (bytes).\n+  template <std::integral CharType>\n+  requires(sizeof(CharType) == 1)\n+  constexpr u32 operator()(char32_t code_point, CharType* ptr)\n+  {\n+    u32 value = code_point;\n+    if (value < 0x80u)\n+    {\n+      *ptr = u8(value);\n+      return 1;\n+    }\n+\n+    u32 additional_bytes = 1;\n+    if (value < 0x800u)\n+    {\n+      *ptr = u8(0xc0u | (value >> 6u));\n+    }\n+    else if (value < 0x10000u)\n+    {\n+      additional_bytes = 2;\n+      *ptr = u8(0xe0u | (value >> 12u));\n+    }\n+    else\n+    {\n+      additional_bytes = 3;\n+      *ptr = u8(0xf0u | (value >> 18u));\n+    }\n+\n+    for (u32 i = additional_bytes; i != 0; --i)\n+    {\n+      ptr[i] = u8(0x80u | (value & 0x3fu));\n+      value >>= 6;\n+    }\n+\n+    return 1 + additional_bytes;\n+  }\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 2)\n+class UTF16Decoder\n+{\n+public:\n+  constexpr explicit UTF16Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u32 current_value = u16(*m_ptr);\n+    ++m_ptr;\n+\n+    if (current_value < 0xd800u || current_value > 0xd8ffu)\n+      return current_value;\n+\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::IncompleteSequence};\n+\n+    // We assume the surrogate pair is valid.\n+    const u32 low_surrogate = u16(*m_ptr);\n+    ++m_ptr;\n+    return ((current_value - 0xd800u) << 10u) + (low_surrogate - 0xdc00u) + 0x10000u;\n+  }\n+\n+private:\n+  const CharType* m_ptr;\n+  const CharType* const m_end_ptr;\n+};\n+\n+class UTF16Encoder\n+{\n+public:\n+  static constexpr u32 GetMaxUnitsPerCodePoint() { return 2; }\n+\n+  // `ptr` should point to at least 2 code units.\n+  // Returns the number of written code units.\n+  template <std::integral CharType>\n+  requires(sizeof(CharType) == 2)\n+  constexpr u32 operator()(char32_t code_point, CharType* ptr)\n+  {\n+    if (code_point < 0x10000u)\n+    {\n+      *ptr = u16(code_point);\n+      return 1;\n+    }\n+\n+    // Create surrogate pair.\n+    const u32 value = code_point - 0x10000u;', 'path': 'Source/Core/Common/StringUtil.cpp', 'position': 170, 'original_position': 170, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "I would suggest including a check so code points larger than U+10FFFF are rejected. Though... If this is only used internally by `ReEncodeString`, and we don't expose UTF-32 as an option, I guess it could be fine to not check it.", 'created_at': '2026-01-31T11:21:04Z', 'updated_at': '2026-01-31T11:36:08Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749408809', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749408809'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749408809'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749408809/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749409210, 'node_id': 'PRRC_kwDOALCn2M6j4Ke6', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749409210', 'pull_request_review_id': 3732310342, 'diff_hunk': '@@ -261,6 +261,14 @@ TEST(StringUtil, CaseInsensitiveContains_OverlappingMatches)\n \n TEST(StringUtil, CharacterEncodingConversion)\n {\n+  // Invalid sequences shouldn\'t crash or throw.\n+  WStringToUTF8(L"\\xdbff\\xdbff");\n+  UTF16ToUTF8(u"\\xdbff\\xdbff");\n+  UTF16ToUTF8(u"\\xdbff");\n+  UTF8ToUTF16("\\x80");\n+  UTF8ToUTF16("\\xc2");\n+  UTF8ToSHIFTJIS("\\x80");', 'path': 'Source/UnitTests/Common/StringUtilTest.cpp', 'position': 10, 'original_position': 10, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Please check the return value of all of these.', 'created_at': '2026-01-31T11:21:56Z', 'updated_at': '2026-01-31T11:36:08Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749409210', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749409210'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749409210'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749409210/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749415165, 'node_id': 'PRRC_kwDOALCn2M6j4L79', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749415165', 'pull_request_review_id': 3732310342, 'diff_hunk': '', 'path': 'Source/UnitTests/Common/StringUtilTest.cpp', 'position': 1, 'original_position': 1, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Now that we have hand-rolled implementations, we should test more cases. I would suggest:\n\n* Code point between U+0000 and U+007F (already covered here)\n* Code point between U+0080 and U+00FF\n* Code point between U+0800 and U+D7FF\n* High unmatched surrogate half, U+D800 to U+DBFF (already covered by your "invalid sequences" test cases, but only tested in one direction)\n* Low unmatched surrogate half, U+DC00 to U+DFFF\n* Code point between U+E000 and U+FFFF\n* Code point between U+10000 and U+1FFFF (already covered here)\n\nPlus all the edge cases I\'ve left review comments on.', 'created_at': '2026-01-31T11:30:12Z', 'updated_at': '2026-01-31T11:36:08Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749415165', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749415165'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749415165'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749415165/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, {'id': 2749417504, 'node_id': 'PRRC_kwDOALCn2M6j4Mgg', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749417504', 'pull_request_review_id': 3732310342, 'diff_hunk': '@@ -391,6 +390,189 @@ size_t StringUTF8CodePointCount(std::string_view str)\n   return str.size() - std::ranges::count_if(str, [](char c) -> bool { return (c & 0xC0) == 0x80; });\n }\n \n+enum class DecodeError\n+{\n+  End,\n+  IncompleteSequence,\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 1)\n+class UTF8Decoder\n+{\n+public:\n+  constexpr explicit UTF8Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u8 first_byte = *m_ptr;\n+    ++m_ptr;\n+\n+    // This is either ASCII or an invalid byte that we will interpret as\n+    //  ISO/IEC 8859-1 or "Extended ASCII".\n+    //  In particular, this handles 0xa0 (NO-BREAK SPACE).\n+    if (first_byte < 0xc0u)\n+      return first_byte;\n+\n+    u32 byte_count = std::countl_one(first_byte);\n+\n+    // Remove leading 1s.\n+    u32 current_value = u8(first_byte << byte_count) >> byte_count;\n+\n+    while (--byte_count != 0)\n+    {\n+      if (m_ptr == m_end_ptr)\n+        return std::unexpected{DecodeError::IncompleteSequence};\n+\n+      current_value = (current_value << 6u) | (u8(*m_ptr) & 0x3fu);\n+      ++m_ptr;\n+    }\n+\n+    return current_value;\n+  }\n+\n+private:\n+  const CharType* m_ptr;\n+  const CharType* const m_end_ptr;\n+};\n+\n+class UTF8Encoder\n+{\n+public:\n+  static constexpr u32 GetMaxUnitsPerCodePoint() { return 4; }\n+\n+  // `ptr` should point to at least 4 bytes.\n+  // Returns the number of written code units (bytes).\n+  template <std::integral CharType>\n+  requires(sizeof(CharType) == 1)\n+  constexpr u32 operator()(char32_t code_point, CharType* ptr)\n+  {\n+    u32 value = code_point;\n+    if (value < 0x80u)\n+    {\n+      *ptr = u8(value);\n+      return 1;\n+    }\n+\n+    u32 additional_bytes = 1;\n+    if (value < 0x800u)\n+    {\n+      *ptr = u8(0xc0u | (value >> 6u));\n+    }\n+    else if (value < 0x10000u)\n+    {\n+      additional_bytes = 2;\n+      *ptr = u8(0xe0u | (value >> 12u));\n+    }\n+    else\n+    {\n+      additional_bytes = 3;\n+      *ptr = u8(0xf0u | (value >> 18u));\n+    }\n+\n+    for (u32 i = additional_bytes; i != 0; --i)\n+    {\n+      ptr[i] = u8(0x80u | (value & 0x3fu));\n+      value >>= 6;\n+    }\n+\n+    return 1 + additional_bytes;\n+  }\n+};\n+\n+template <std::integral CharType>\n+requires(sizeof(CharType) == 2)\n+class UTF16Decoder\n+{\n+public:\n+  constexpr explicit UTF16Decoder(std::span<const CharType> chars)\n+      : m_ptr{chars.data()}, m_end_ptr{m_ptr + chars.size()}\n+  {\n+  }\n+\n+  constexpr std::expected<char32_t, DecodeError> operator()()\n+  {\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::End};\n+\n+    const u32 current_value = u16(*m_ptr);\n+    ++m_ptr;\n+\n+    if (current_value < 0xd800u || current_value > 0xd8ffu)\n+      return current_value;\n+\n+    if (m_ptr == m_end_ptr)\n+      return std::unexpected{DecodeError::IncompleteSequence};\n+\n+    // We assume the surrogate pair is valid.\n+    const u32 low_surrogate = u16(*m_ptr);\n+    ++m_ptr;\n+    return ((current_value - 0xd800u) << 10u) + (low_surrogate - 0xdc00u) + 0x10000u;\n+  }\n+\n+private:\n+  const CharType* m_ptr;\n+  const CharType* const m_end_ptr;\n+};\n+\n+class UTF16Encoder\n+{\n+public:\n+  static constexpr u32 GetMaxUnitsPerCodePoint() { return 2; }\n+\n+  // `ptr` should point to at least 2 code units.\n+  // Returns the number of written code units.\n+  template <std::integral CharType>\n+  requires(sizeof(CharType) == 2)\n+  constexpr u32 operator()(char32_t code_point, CharType* ptr)\n+  {\n+    if (code_point < 0x10000u)\n+    {\n+      *ptr = u16(code_point);\n+      return 1;\n+    }\n+\n+    // Create surrogate pair.\n+    const u32 value = code_point - 0x10000u;\n+    ptr[0] = u16((value >> 10u) + 0xd800u);\n+    ptr[1] = u16((value & 0x3ffu) + 0xdc00u);\n+    return 2;\n+  }\n+};\n+\n+template <typename Decoder, typename Encoder, typename ResultCharType, typename InputCharType>\n+static constexpr std::basic_string<ResultCharType>\n+ReEncodeString(std::basic_string_view<InputCharType> input)\n+{\n+  Decoder decoder{input};\n+  Encoder encoder;\n+\n+  const auto max_code_units = input.size() * encoder.GetMaxUnitsPerCodePoint();\n+\n+  std::basic_string<ResultCharType> result;\n+  result.resize_and_overwrite(max_code_units, [&](ResultCharType* buf, std::size_t) {\n+    std::size_t position = 0;\n+    while (true)\n+    {\n+      const auto code_point = decoder();\n+      if (!code_point.has_value())\n+        break;', 'path': 'Source/Core/Common/StringUtil.cpp', 'position': 193, 'original_position': 193, 'commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "It's common practice to replace a decoding failure with a question mark or U+FFFD, not to stop trying to decode the string.", 'created_at': '2026-01-31T11:33:23Z', 'updated_at': '2026-01-31T11:36:08Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749417504', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749417504'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14308#discussion_r2749417504'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14308'}}, 'original_commit_id': 'b89301e80b16df4559224d21f11bcbdf314140b1', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749417504/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}], 'type': 'gh_pull_request_review'}
2026-01-31T11:06:02.576127	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'JosJuice', 'action': 'submitted', 'pr_id': 14311, 'pr_title': 'RetroAchievements - Fix Aliases', 'state': 'approved', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14311#pullrequestreview-3732308741', 'comments': [], 'type': 'gh_pull_request_review'}
2026-01-31T10:21:21.648609	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'oltolm', 'action': 'submitted', 'pr_id': 14003, 'pr_title': 'mingw: enable compilation using mingw compiler', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14003#pullrequestreview-3732221231', 'comments': [{'id': 2749340303, 'node_id': 'PRRC_kwDOALCn2M6j35qP', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749340303', 'pull_request_review_id': 3732221231, 'diff_hunk': '@@ -153,7 +153,8 @@ void Destroy()\n     {\n       // print out alive objects, but only if we actually have pending references\n       // note this will also print out internal live objects to the debug console\n-      s_debug->ReportLiveDeviceObjects(D3D11_RLDO_SUMMARY | D3D11_RLDO_DETAIL);', 'path': 'Source/Core/VideoBackends/D3D/D3DBase.cpp', 'position': 4, 'original_position': 4, 'commit_id': '1bdc317e942ddb4309f03e82d22c90bf6532b410', 'user': {'login': 'oltolm', 'id': 1674229, 'node_id': 'MDQ6VXNlcjE2NzQyMjk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1674229?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/oltolm', 'html_url': 'https://github.com/oltolm', 'followers_url': 'https://api.github.com/users/oltolm/followers', 'following_url': 'https://api.github.com/users/oltolm/following{/other_user}', 'gists_url': 'https://api.github.com/users/oltolm/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/oltolm/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/oltolm/subscriptions', 'organizations_url': 'https://api.github.com/users/oltolm/orgs', 'repos_url': 'https://api.github.com/users/oltolm/repos', 'events_url': 'https://api.github.com/users/oltolm/events{/privacy}', 'received_events_url': 'https://api.github.com/users/oltolm/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'The latest version of mingw-w64 available today in MSYS2 has added `DEFINE_ENUM_FLAG_OPERATORS(D3D11_RLDO_FLAGS)` to the headers, so that this is not necessary anymore.', 'created_at': '2026-01-31T10:21:18Z', 'updated_at': '2026-01-31T10:21:18Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14003#discussion_r2749340303', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14003', 'author_association': 'CONTRIBUTOR', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749340303'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14003#discussion_r2749340303'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14003'}}, 'original_commit_id': '1bdc317e942ddb4309f03e82d22c90bf6532b410', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749340303/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}], 'type': 'gh_pull_request_review'}
2026-01-31T07:55:55.714075	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'submitted', 'pr_id': 14140, 'pr_title': 'VideoCommon: add a graphics mod feature to modify EFBs with a custom material, enhance bloom', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14140#pullrequestreview-3732007325', 'comments': [{'id': 2749173567, 'node_id': 'PRRC_kwDOALCn2M6j3Q8_', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749173567', 'pull_request_review_id': 3732007325, 'diff_hunk': '@@ -93,7 +95,11 @@ class MaterialResource final : public Resource\n   // Note: asset cache owns the asset, we access as a reference\n   MaterialAsset* m_material_asset = nullptr;\n \n-  GXPipelineUid m_uid;\n+  // If provided, denotes this material will be used\n+  // as a custom draw material, if not provided\n+  // denotes this will be used as an efb post\n+  // processing material', 'path': 'Source/Core/VideoCommon/Resources/MaterialResource.h', 'position': 24, 'original_position': 24, 'commit_id': '354b85dddf3c0beb229e700602421c46ce59b4fe', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "`processing material` all on it's own line was the main weirdness, but I suppose this works.\r\n```cpp\r\n// If provided, denotes this material will be used as a custom draw material.\r\n// If not provided, denotes this will be used as an efb post processing material.\r\n```", 'created_at': '2026-01-31T07:55:53Z', 'updated_at': '2026-01-31T07:55:54Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2749173567', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749173567'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2749173567'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140'}}, 'original_commit_id': '354b85dddf3c0beb229e700602421c46ce59b4fe', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749173567/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2749167260}], 'type': 'gh_pull_request_review'}
2026-01-31T07:46:08.840044	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'iwubcode', 'action': 'submitted', 'pr_id': 14140, 'pr_title': 'VideoCommon: add a graphics mod feature to modify EFBs with a custom material, enhance bloom', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14140#pullrequestreview-3732001360', 'comments': [{'id': 2749168366, 'node_id': 'PRRC_kwDOALCn2M6j3Pru', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749168366', 'pull_request_review_id': 3732001360, 'diff_hunk': '@@ -93,7 +95,11 @@ class MaterialResource final : public Resource\n   // Note: asset cache owns the asset, we access as a reference\n   MaterialAsset* m_material_asset = nullptr;\n \n-  GXPipelineUid m_uid;\n+  // If provided, denotes this material will be used\n+  // as a custom draw material, if not provided\n+  // denotes this will be used as an efb post\n+  // processing material', 'path': 'Source/Core/VideoCommon/Resources/MaterialResource.h', 'position': 24, 'original_position': 24, 'commit_id': '354b85dddf3c0beb229e700602421c46ce59b4fe', 'user': {'login': 'iwubcode', 'id': 15224722, 'node_id': 'MDQ6VXNlcjE1MjI0NzIy', 'avatar_url': 'https://avatars.githubusercontent.com/u/15224722?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/iwubcode', 'html_url': 'https://github.com/iwubcode', 'followers_url': 'https://api.github.com/users/iwubcode/followers', 'following_url': 'https://api.github.com/users/iwubcode/following{/other_user}', 'gists_url': 'https://api.github.com/users/iwubcode/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/iwubcode/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/iwubcode/subscriptions', 'organizations_url': 'https://api.github.com/users/iwubcode/orgs', 'repos_url': 'https://api.github.com/users/iwubcode/repos', 'events_url': 'https://api.github.com/users/iwubcode/events{/privacy}', 'received_events_url': 'https://api.github.com/users/iwubcode/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Any suggestions on how to fix?', 'created_at': '2026-01-31T07:46:06Z', 'updated_at': '2026-01-31T07:46:07Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2749168366', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749168366'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2749168366'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140'}}, 'original_commit_id': '354b85dddf3c0beb229e700602421c46ce59b4fe', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749168366/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2749167260}], 'type': 'gh_pull_request_review'}
2026-01-31T07:44:08.950768	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'jordan-woyak', 'action': 'submitted', 'pr_id': 14140, 'pr_title': 'VideoCommon: add a graphics mod feature to modify EFBs with a custom material, enhance bloom', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/14140#pullrequestreview-3731999385', 'comments': [{'id': 2749167260, 'node_id': 'PRRC_kwDOALCn2M6j3Pac', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749167260', 'pull_request_review_id': 3731999385, 'diff_hunk': '@@ -93,7 +95,11 @@ class MaterialResource final : public Resource\n   // Note: asset cache owns the asset, we access as a reference\n   MaterialAsset* m_material_asset = nullptr;\n \n-  GXPipelineUid m_uid;\n+  // If provided, denotes this material will be used\n+  // as a custom draw material, if not provided\n+  // denotes this will be used as an efb post\n+  // processing material', 'path': 'Source/Core/VideoCommon/Resources/MaterialResource.h', 'position': 24, 'original_position': 24, 'commit_id': '354b85dddf3c0beb229e700602421c46ce59b4fe', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Nitpicky, but this comment spans multiple lines weirdly. :P', 'created_at': '2026-01-31T07:44:06Z', 'updated_at': '2026-01-31T07:44:06Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2749167260', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749167260'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2749167260'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140'}}, 'original_commit_id': '354b85dddf3c0beb229e700602421c46ce59b4fe', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2749167260/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}], 'type': 'gh_pull_request_review'}
2026-01-31T04:24:31.897282	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'author': 'Geotale', 'action': 'submitted', 'pr_id': 13872, 'pr_title': 'Improve Interpreted Paired Move Accuracy', 'state': 'commented', 'url': 'https://github.com/dolphin-emu/dolphin/pull/13872#pullrequestreview-3731483886', 'comments': [{'id': 2748848295, 'node_id': 'PRRC_kwDOALCn2M6j2Bin', 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2748848295', 'pull_request_review_id': 3731483886, 'diff_hunk': '@@ -139,51 +139,79 @@ double ApproximateReciprocalSquareRoot(double val)\n }\n \n const std::array<BaseAndDec, 32> fres_expected = {{\n-    {0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340}, {0x638800, 0x313},\n-    {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0}, {0x4cc800, 0x27f}, {0x47ca00, 0x261},\n-    {0x430800, 0x245}, {0x3e8000, 0x22a}, {0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5},\n-    {0x2e4a00, 0x1d1}, {0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b},\n-    {0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b}, {0x124400, 0x143},\n-    {0x0fbe00, 0x143}, {0x0d3800, 0x12d}, {0x0ade00, 0x12d}, {0x088400, 0x11a}, {0x065000, 0x11a},\n-    {0x041c00, 0x108}, {0x020c00, 0x106},\n+    {0xfff000, -0x3e1}, {0xf07000, -0x3a7}, {0xe1d400, -0x371}, {0xd41000, -0x340},\n+    {0xc71000, -0x313}, {0xbac400, -0x2ea}, {0xaf2000, -0x2c4}, {0xa41000, -0x2a0},\n+    {0x999000, -0x27f}, {0x8f9400, -0x261}, {0x861000, -0x245}, {0x7d0000, -0x22a},\n+    {0x745800, -0x212}, {0x6c1000, -0x1fb}, {0x642800, -0x1e5}, {0x5c9400, -0x1d1},\n+    {0x555000, -0x1be}, {0x4e5800, -0x1ac}, {0x47ac00, -0x19b}, {0x413c00, -0x18b},\n+    {0x3b1000, -0x17c}, {0x352000, -0x16e}, {0x2f5c00, -0x15b}, {0x29f000, -0x15b},\n+    {0x248800, -0x143}, {0x1f7c00, -0x143}, {0x1a7000, -0x12d}, {0x15bc00, -0x12d},\n+    {0x110800, -0x11a}, {0x0ca000, -0x11a}, {0x083800, -0x108}, {0x041800, -0x106},\n }};\n \n // Used by fres and ps_res.\n-double ApproximateReciprocal(double val)\n+double ApproximateReciprocal(const UReg_FPSCR& fpscr, double val)\n {\n-  s64 integral = std::bit_cast<s64>(val);\n-  const s64 mantissa = integral & ((1LL << 52) - 1);\n-  const s64 sign = integral & (1ULL << 63);\n-  s64 exponent = integral & (0x7FFLL << 52);\n+  const u64 integral = std::bit_cast<u64>(val);\n+\n+  // Convert into a float when possible\n+  const u64 signless = integral & ~DOUBLE_SIGN;\n+  const u32 mantissa =\n+      static_cast<u32>((integral & DOUBLE_FRAC) >> (DOUBLE_FRAC_WIDTH - FLOAT_FRAC_WIDTH));\n+  const u32 sign = static_cast<u32>((integral >> 32) & FLOAT_SIGN);\n+  const s32 exponent = static_cast<s32>((integral & DOUBLE_EXP) >> DOUBLE_FRAC_WIDTH) - 0x380;\n+\n+  // The largest floats possible just return 0\n+  const u64 huge_float = fpscr.NI ? 0x47d0000000000000ULL : 0x4940000000000000ULL;\n \n   // Special case 0\n-  if (mantissa == 0 && exponent == 0)\n+  if (signless == 0)\n     return std::copysign(std::numeric_limits<double>::infinity(), val);\n \n-  // Special case NaN-ish numbers\n-  if (exponent == (0x7FFLL << 52))\n+  // Special case huge or NaN-ish numbers\n+  if (signless >= huge_float)\n   {\n-    if (mantissa == 0)\n+    if (!std::isnan(val))\n       return std::copysign(0.0, val);\n     return MakeQuiet(val);\n   }\n \n   // Special case small inputs\n-  if (exponent < (895LL << 52))\n+  if (exponent < -1)\n     return std::copysign(std::numeric_limits<float>::max(), val);\n \n-  // Special case large inputs\n-  if (exponent >= (1149LL << 52))\n-    return std::copysign(0.0, val);\n+  const s32 new_exponent = 253 - exponent;\n \n-  exponent = (0x7FDLL << 52) - exponent;\n-\n-  const int i = static_cast<int>(mantissa >> 37);\n+  const u32 i = static_cast<u32>(mantissa >> 8);\n   const auto& entry = fres_expected[i / 1024];\n-  integral = sign | exponent;\n-  integral |= static_cast<s64>(entry.m_base - (entry.m_dec * (i % 1024) + 1) / 2) << 29;\n+  const u32 new_mantissa = static_cast<u32>(entry.m_base + entry.m_dec * (i % 1024)) / 2;\n \n-  return std::bit_cast<double>(integral);\n+  u32 result = sign | (static_cast<u32>(new_exponent) << FLOAT_FRAC_WIDTH) | new_mantissa;\n+  if (new_exponent <= 0)\n+  {\n+    // Result is subnormal so format it properly!\n+    if (fpscr.NI)\n+    {\n+      // Flush to 0 if inexact\n+      result = sign;\n+    }\n+    else\n+    {\n+      // Shift by the exponent amount\n+      u32 shift = 1 + static_cast<u32>(-new_exponent);\n+      result = sign | (((1 << FLOAT_FRAC_WIDTH) | new_mantissa) >> shift);\n+    }\n+  }\n+  return static_cast<double>(std::bit_cast<float>(result));\n+}\n+\n+// Variation of `ApproximateReciprocal`, operating on the bits rather than the raw value\n+u64 ApproximateReciprocalBits(const UReg_FPSCR& fpscr, u64 integral)', 'path': 'Source/Core/Core/FloatUtils.cpp', 'position': 1, 'original_position': 130, 'commit_id': 'b8aa7daea89b49f92a0b083307c89ba10a9ad9e5', 'user': {'login': 'Geotale', 'id': 72356786, 'node_id': 'MDQ6VXNlcjcyMzU2Nzg2', 'avatar_url': 'https://avatars.githubusercontent.com/u/72356786?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Geotale', 'html_url': 'https://github.com/Geotale', 'followers_url': 'https://api.github.com/users/Geotale/followers', 'following_url': 'https://api.github.com/users/Geotale/following{/other_user}', 'gists_url': 'https://api.github.com/users/Geotale/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Geotale/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Geotale/subscriptions', 'organizations_url': 'https://api.github.com/users/Geotale/orgs', 'repos_url': 'https://api.github.com/users/Geotale/repos', 'events_url': 'https://api.github.com/users/Geotale/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Geotale/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Done ^^b This also gives rise to another optimization which could be done... in the future', 'created_at': '2026-01-31T04:24:29Z', 'updated_at': '2026-01-31T04:24:30Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13872#discussion_r2748848295', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13872', 'author_association': 'MEMBER', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2748848295'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13872#discussion_r2748848295'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13872'}}, 'original_commit_id': 'b8aa7daea89b49f92a0b083307c89ba10a9ad9e5', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2748848295/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'in_reply_to_id': 2724581105}], 'type': 'gh_pull_request_review'}

Recent 'gh_push' events

2026-02-03T22:50:54.523610	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'pusher': 'jordan-woyak', 'before_sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'after_sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'commits': [{'author': {'name': 'JosJuice', 'email': 'josjuice@gmail.com', 'date': '2025-04-26T10:33:01+02:00', 'username': 'JosJuice'}, 'distinct': False, 'added': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt', 'Source/Android/jni/ActivityTracker.cpp', 'Source/Core/UICommon/UICommon.cpp', 'Source/Core/UICommon/UICommon.h'], 'removed': [], 'message': 'Android: Detect when native code should flush unsaved data', 'url': 'https://github.com/dolphin-emu/dolphin/commit/0ba32f7add97daf1c5ea52cf77b75e81d862676f', 'hash': '0ba32f7add97daf1c5ea52cf77b75e81d862676f'}, {'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2025-04-26T17:18:56-05:00', 'username': 'jordan-woyak'}, 'distinct': False, 'added': [], 'modified': ['Source/Core/UICommon/UICommon.cpp', 'Source/Core/UICommon/UICommon.h'], 'removed': [], 'message': 'Core: Add a HookableEvent for UICommon::FlushUnsavedData.', 'url': 'https://github.com/dolphin-emu/dolphin/commit/b0652925faa54b9bfd5a17cfed8870a2b1f2759b', 'hash': 'b0652925faa54b9bfd5a17cfed8870a2b1f2759b'}, {'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2025-10-19T20:31:22-05:00', 'username': 'jordan-woyak'}, 'distinct': False, 'added': ['Source/Core/Common/TransferableSharedMutex.h'], 'modified': ['Source/Core/Common/CMakeLists.txt', 'Source/Core/DolphinLib.props', 'Source/UnitTests/Common/MutexTest.cpp'], 'removed': [], 'message': 'Common: Add TransferableSharedMutex class and unit tests.', 'url': 'https://github.com/dolphin-emu/dolphin/commit/1d9e475123346c0e83289e788bdfad9f3345b512', 'hash': '1d9e475123346c0e83289e788bdfad9f3345b512'}, {'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2025-11-02T22:21:15-06:00', 'username': 'jordan-woyak'}, 'distinct': False, 'added': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt', 'Source/Android/jni/MainAndroid.cpp', 'Source/Core/Core/State.cpp', 'Source/Core/Core/State.h'], 'removed': [], 'message': 'State: Simplify interthread communication and cleanups. Save/Load calls are now always non-blocking for the caller, but appropriately block the CPU thread as needed.', 'url': 'https://github.com/dolphin-emu/dolphin/commit/2322437f96bc53f1edcd9100daedb5b54c6e0fc4', 'hash': '2322437f96bc53f1edcd9100daedb5b54c6e0fc4'}, {'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-02-03T16:50:52-06:00', 'username': 'jordan-woyak'}, 'distinct': True, 'added': ['Source/Core/Common/TransferableSharedMutex.h'], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt', 'Source/Android/jni/ActivityTracker.cpp', 'Source/Android/jni/MainAndroid.cpp', 'Source/Core/Common/CMakeLists.txt', 'Source/Core/Core/State.cpp', 'Source/Core/Core/State.h', 'Source/Core/DolphinLib.props', 'Source/Core/UICommon/UICommon.cpp', 'Source/Core/UICommon/UICommon.h', 'Source/UnitTests/Common/MutexTest.cpp'], 'removed': [], 'message': 'Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'url': 'https://github.com/dolphin-emu/dolphin/commit/dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'hash': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}
2026-02-03T04:26:15.538287	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'pusher': 'jordan-woyak', 'before_sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'after_sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'commits': [{'author': {'name': 'JMC47', 'email': 'JMC4789@gmail.com', 'date': '2026-02-01T00:42:27-05:00', 'username': 'JMC47'}, 'distinct': False, 'added': [], 'modified': ['Data/Sys/GameSettings/RSS.ini'], 'removed': [], 'message': 'GameINI: Force Safe Texture Cache to Samurai Shodown Anthology', 'url': 'https://github.com/dolphin-emu/dolphin/commit/ac19ef5452a1abcd7bcfc9d166fcdbe28c714ba1', 'hash': 'ac19ef5452a1abcd7bcfc9d166fcdbe28c714ba1'}, {'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-02-02T22:26:13-06:00', 'username': 'jordan-woyak'}, 'distinct': True, 'added': [], 'modified': ['Data/Sys/GameSettings/RSS.ini'], 'removed': [], 'message': 'Merge pull request #14314 from JMC47/shodown\n\nGameINI: Force Safe Texture Cache to Samurai Shodown Anthology', 'url': 'https://github.com/dolphin-emu/dolphin/commit/eac7b290423efea867c2c7f3f2b2792d05418bdd', 'hash': 'eac7b290423efea867c2c7f3f2b2792d05418bdd'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}
2026-02-02T20:05:53.999965	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'pusher': 'JMC47', 'before_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'after_sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'commits': [{'author': {'name': 'Joshua Vandaële', 'email': 'joshua@vandaele.software', 'date': '2026-02-02T20:01:58+01:00', 'username': 'JoshuaVandaele'}, 'distinct': False, 'added': [], 'modified': ['Source/Core/UICommon/ResourcePack/ResourcePack.cpp'], 'removed': [], 'message': "ResourcePack: Fix loading resource packs\n\nThis is something I missed when updating minizip, this mistake made it so resource packs didn't find any textures", 'url': 'https://github.com/dolphin-emu/dolphin/commit/d87e8ab7ff66f3efcedeb5a0ab869b537933aada', 'hash': 'd87e8ab7ff66f3efcedeb5a0ab869b537933aada'}, {'author': {'name': 'JMC47', 'email': 'JMC4789@gmail.com', 'date': '2026-02-02T15:05:51-05:00', 'username': 'JMC47'}, 'distinct': True, 'added': [], 'modified': ['Source/Core/UICommon/ResourcePack/ResourcePack.cpp'], 'removed': [], 'message': 'Merge pull request #14316 from JoshuaVandaele/resourcefix\n\nResourcePack: Fix loading resource packs', 'url': 'https://github.com/dolphin-emu/dolphin/commit/c7de9162765452fc2787f3ba4f0092394067d603', 'hash': 'c7de9162765452fc2787f3ba4f0092394067d603'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}
2026-01-31T23:02:12.428260	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'pusher': 'Dentomologist', 'before_sha': '16276928ccb6d14e32cd33cde993f70503f0e838', 'after_sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'commits': [{'author': {'name': 'Nuh Uh', 'email': '72356786+Geotale@users.noreply.github.com', 'date': '2025-12-10T13:40:24-06:00', 'username': 'Geotale'}, 'distinct': False, 'added': [], 'modified': ['Source/Core/VideoCommon/UberShaderPixel.cpp'], 'removed': [], 'message': 'Update Comments Based On Hardware Test\n\nChecked on hardware that this bias was not added because I had assumed the other way around would be true, forgot to ask about making a PR for this when I initially had done so', 'url': 'https://github.com/dolphin-emu/dolphin/commit/f31f11c0a4b37decebd0e0a349bdc33691fdd657', 'hash': 'f31f11c0a4b37decebd0e0a349bdc33691fdd657'}, {'author': {'name': 'Dentomologist', 'email': 'dentomologist@gmail.com', 'date': '2026-01-31T15:02:09-08:00', 'username': 'Dentomologist'}, 'distinct': True, 'added': [], 'modified': ['Source/Core/VideoCommon/UberShaderPixel.cpp'], 'removed': [], 'message': 'Merge pull request #14204 from Geotale/update-comment\n\nUpdate Comments Based On Hardware Test', 'url': 'https://github.com/dolphin-emu/dolphin/commit/6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'hash': '6711d77b9901ed0a4418203db49ed4d8576e6fe0'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}
2026-01-31T22:45:21.300900	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'pusher': 'jordan-woyak', 'before_sha': 'b1020186519d690a9b64a215f00da7453ab2803b', 'after_sha': '16276928ccb6d14e32cd33cde993f70503f0e838', 'commits': [{'author': {'name': 'Joshua Vandaële', 'email': 'joshua@vandaele.software', 'date': '2026-01-31T10:36:55+01:00', 'username': 'JoshuaVandaele'}, 'distinct': False, 'added': [], 'modified': ['Source/Core/Common/GL/GLInterface/WGL.cpp'], 'removed': [], 'message': 'WGL: Correctly load wglDestroyPbufferARB extension', 'url': 'https://github.com/dolphin-emu/dolphin/commit/e6bc8fb342169d9936645fe1fdebe296d92bc44b', 'hash': 'e6bc8fb342169d9936645fe1fdebe296d92bc44b'}, {'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-01-31T16:45:19-06:00', 'username': 'jordan-woyak'}, 'distinct': True, 'added': [], 'modified': ['Source/Core/Common/GL/GLInterface/WGL.cpp'], 'removed': [], 'message': 'Merge pull request #14312 from JoshuaVandaele/wglDestroyPbufferARB\n\nWGL: Correctly load wglDestroyPbufferARB extension', 'url': 'https://github.com/dolphin-emu/dolphin/commit/16276928ccb6d14e32cd33cde993f70503f0e838', 'hash': '16276928ccb6d14e32cd33cde993f70503f0e838'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}
2026-01-30T19:51:11.032299	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'pusher': 'JMC47', 'before_sha': 'bb7e01167346d0e3e4bab426a7b87968d715ee5e', 'after_sha': 'b1020186519d690a9b64a215f00da7453ab2803b', 'commits': [{'author': {'name': 'Sam Belliveau', 'email': 'sam.belliveau@gmail.com', 'date': '2026-01-27T18:48:22-05:00', 'username': 'Sam-Belliveau'}, 'distinct': False, 'added': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt', 'Source/Android/app/src/main/res/values/strings.xml', 'Source/Core/AudioCommon/Mixer.cpp', 'Source/Core/AudioCommon/Mixer.h', 'Source/Core/Core/Config/MainSettings.cpp', 'Source/Core/Core/Config/MainSettings.h', 'Source/Core/DolphinQt/Settings/AudioPane.cpp', 'Source/Core/DolphinQt/Settings/AudioPane.h'], 'removed': [], 'message': 'feat: Add an option to preserve audio pitch when emulation speed changes, integrating it into core configuration and both Qt and Android UIs.', 'url': 'https://github.com/dolphin-emu/dolphin/commit/51c8f18b73727afaea0991e10789998eaacbe487', 'hash': '51c8f18b73727afaea0991e10789998eaacbe487'}, {'author': {'name': 'JMC47', 'email': 'JMC4789@gmail.com', 'date': '2026-01-30T14:51:08-05:00', 'username': 'JMC47'}, 'distinct': True, 'added': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt', 'Source/Android/app/src/main/res/values/strings.xml', 'Source/Core/AudioCommon/Mixer.cpp', 'Source/Core/AudioCommon/Mixer.h', 'Source/Core/Core/Config/MainSettings.cpp', 'Source/Core/Core/Config/MainSettings.h', 'Source/Core/DolphinQt/Settings/AudioPane.cpp', 'Source/Core/DolphinQt/Settings/AudioPane.h'], 'removed': [], 'message': 'Merge pull request #14307 from Sam-Belliveau/granular-audio-stretching\n\nAdd an option to preserve audio pitch when emulation speed changes', 'url': 'https://github.com/dolphin-emu/dolphin/commit/b1020186519d690a9b64a215f00da7453ab2803b', 'hash': 'b1020186519d690a9b64a215f00da7453ab2803b'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}
2026-01-29T22:05:09.716660	{'source': 'ghhookparser', 'repo': 'dolphin-emu/sadm', 'pusher': 'OatmealDome', 'before_sha': '5b6d8c9aa277c5d4c7e23f32eb81f52d26d2f693', 'after_sha': 'aa075152f04a441758db4e72a0468ee2d85df930', 'commits': [{'author': {'name': 'OatmealDome', 'email': 'julian@oatmealdome.me', 'date': '2026-01-29T08:25:10-05:00', 'username': 'OatmealDome'}, 'distinct': True, 'added': [], 'modified': ['flake.lock'], 'removed': [], 'message': "flake.lock: Update\n\nFlake lock file updates:\n\n• Updated input 'nixpkgs':\n    'github:NixOS/nixpkgs/078d69f03934859a181e81ba987c2bb033eebfc5?narHash=sha256-9yA/LIuAVQq0lXelrZPjLuLVuZdm03p8tfmHhnDIkms%3D' (2026-01-22)\n  → 'github:NixOS/nixpkgs/fa83fd837f3098e3e678e6cf017b2b36102c7211?narHash=sha256-e7VO/kGLgRMbWtpBqdWl0uFg8Y2XWFMdz0uUJvlML8o%3D' (2026-01-28)\n• Updated input 'rust-overlay':\n    'github:oxalica/rust-overlay/22da29e7f3d8cff75009cbbcf992c7cb66920cfd?narHash=sha256-gu6oZ86zLudBZMq8LL1qdtYt/S69GV5keQVXdvBrVSU%3D' (2026-01-24)\n  → 'github:oxalica/rust-overlay/8b94aae763a09749cc153bab0b14e6ad8fc95494?narHash=sha256-Yq4uj%2BRjiE2Bw7C%2B7Mojdiw0kTh9xwJxUo6IjofZV%2Bc%3D' (2026-01-29)", 'url': 'https://github.com/dolphin-emu/sadm/commit/908bde4502959bcc48ef61df34d9005cd203ccda', 'hash': '908bde4502959bcc48ef61df34d9005cd203ccda'}, {'author': {'name': 'OatmealDome', 'email': 'julian@oatmealdome.me', 'date': '2026-01-29T08:25:30-05:00', 'username': 'OatmealDome'}, 'distinct': True, 'added': [], 'modified': ['roles/bug-tracker/default.nix'], 'removed': [], 'message': 'Revert "bug-tracker: Manually patch Redmine with nixpkgs PR 483401"\n\nThis reverts commit 82503abd8f7894e2350676085139de08aa3bde74.', 'url': 'https://github.com/dolphin-emu/sadm/commit/aa075152f04a441758db4e72a0468ee2d85df930', 'hash': 'aa075152f04a441758db4e72a0468ee2d85df930'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}
2026-01-27T23:32:28.150875	{'source': 'ghhookparser', 'repo': 'dolphin-emu/dolphin', 'pusher': 'jordan-woyak', 'before_sha': '10b9da8cd451875e011ca754ce8c5ff37cff4c0b', 'after_sha': 'bb7e01167346d0e3e4bab426a7b87968d715ee5e', 'commits': [{'author': {'name': 'Ballai Péter', 'email': 'ballaipeterlorant@gmail.com', 'date': '2026-01-18T00:53:08+01:00', 'username': 'droidpeti'}, 'distinct': False, 'added': [], 'modified': ['Data/Sys/GameSettings/GMSE01.ini'], 'removed': [], 'message': 'GMSE01: Added author credits for Gecko Codes', 'url': 'https://github.com/dolphin-emu/dolphin/commit/0afdfd914672695e75fe77b3a1bd4435fc69cc0f', 'hash': '0afdfd914672695e75fe77b3a1bd4435fc69cc0f'}, {'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-01-27T17:32:25-06:00', 'username': 'jordan-woyak'}, 'distinct': True, 'added': [], 'modified': ['Data/Sys/GameSettings/GMSE01.ini'], 'removed': [], 'message': 'Merge pull request #14254 from droidpeti/master\n\nGMSE01: Added credits to Gecko codes for Super Mario Sunshine (USA)', 'url': 'https://github.com/dolphin-emu/dolphin/commit/bb7e01167346d0e3e4bab426a7b87968d715ee5e', 'hash': 'bb7e01167346d0e3e4bab426a7b87968d715ee5e'}], 'base_ref_name': None, 'ref_name': 'master', 'ref_type': 'heads', 'created': False, 'deleted': False, 'forced': False, 'type': 'gh_push'}

Recent 'internal_log' events

2026-02-03T23:35:02.985655	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:35:01.945872	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T23:32:40.922058	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/buildbot.py', 'lineno': 131, 'msg': 'PR %s mergeable: %s (%s)', 'args': "(14318, None, 'unknown')", 'type': 'internal_log'}
2026-02-03T23:30:01.845839	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:30:00.803572	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T23:27:32.175125	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/core-developers', 'AdmiralCurtiss,linkmauve,delroth,jordan-woyak,Tilka,JosJuice,spycrab,OatmealDome,leoetlino,CrossVR,iwubcode,degasus,phire,lioncash,Pokechu22,Dentomologist,skidau,Helios747,hrydgard,Parlane')", 'type': 'internal_log'}
2026-02-03T23:27:31.962733	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'core-developers')", 'type': 'internal_log'}
2026-02-03T23:25:00.704569	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:24:59.266770	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T23:19:59.166814	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:19:58.121168	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T23:17:31.862672	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/core-developers', 'AdmiralCurtiss,linkmauve,delroth,jordan-woyak,Tilka,JosJuice,spycrab,OatmealDome,leoetlino,CrossVR,iwubcode,degasus,phire,lioncash,Pokechu22,Dentomologist,skidau,Helios747,hrydgard,Parlane')", 'type': 'internal_log'}
2026-02-03T23:17:31.637845	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'core-developers')", 'type': 'internal_log'}
2026-02-03T23:14:58.021237	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:14:56.919765	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T23:09:56.820171	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:09:55.606760	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T23:07:31.538382	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/core-developers', 'AdmiralCurtiss,linkmauve,delroth,jordan-woyak,Tilka,JosJuice,spycrab,OatmealDome,leoetlino,CrossVR,iwubcode,degasus,phire,lioncash,Pokechu22,Dentomologist,skidau,Helios747,hrydgard,Parlane')", 'type': 'internal_log'}
2026-02-03T23:07:31.308754	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'core-developers')", 'type': 'internal_log'}
2026-02-03T23:04:55.506748	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T23:04:54.346320	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T22:59:54.246236	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/trusted-developers', 'marcan,CelestialAmber,aroulin,nullgemm,NanoByte011,mitaclaw,booto,mandar1jn,merryhime,FioraAeterna,Tilka,Sam-Belliveau,endrift,magcius,LPFaint99,moncefmechri,Stevoisiak,rlnilsen,archshift,hdcmeta,mrgreywater,iwubcode,unknownbrackets,RachelBryk,Zopolis4,lioncash,adamdmoss,SirMangler,MayImilae,comex,hthh,yourWaifu,Geotale,phire,RisingFog,AdmiralCurtiss,deReeperJosh,aldelaro5,linkmauve,glennricster,Starsam80,gwicks,smurf3tte,Ebola16,zackhow,Orphis,meffij,Gamer64ytb,JosJuice,Hydr8gon,vladfi1,DacoTaco,cristian64,PatrickFerry,mathieui,ligfx,TryTwo,JMC47,SuperSamus,EmptyChaos,rukai,zopieux,OrN,CasualPokePlayer,sepalani,ColinDTaylor,Pokechu22,neobrain,Techjar,Alcaro,kamiyo,jjdelvalle,delroth,CookiePLMonster,jloehr,Linktothepast,riking,BhaaLseN,OatmealDome,corwin-mcknight,bentley,orbea,TellowKrinkle,Filoppi,jezze,Sintendo,mimimi085181,spxtr,tygyh,galop1n,K0bin,Phatcat,Buddybenj,krnlyng,hrydgard,Parlane,nickbeth,mahdihijazi,ShimmerGlass,jordan-woyak,noahpistilli,JordanTheToaster,Simonx22,Ziek,randomstuff,mmastrac,spycrab,JoshuaVandaele,Tinob,leoetlino,CrossVR,LillyJadeKatrin,degasus,shonumi,dreamsyntax,CrystalGamma,skylersaleh,kayru,mbc07,magumagu,Dentomologist,hackbar,skidau,LAGonauta,malleoz,Helios747,crediar,Lobsterzelda')", 'type': 'internal_log'}
2026-02-03T22:59:52.854289	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'trusted-developers')", 'type': 'internal_log'}
2026-02-03T22:57:31.208904	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 30, 'msg': 'New GH %s: %s', 'args': "('dolphin-emu/core-developers', 'AdmiralCurtiss,linkmauve,delroth,jordan-woyak,Tilka,JosJuice,spycrab,OatmealDome,leoetlino,CrossVR,iwubcode,degasus,phire,lioncash,Pokechu22,Dentomologist,skidau,Helios747,hrydgard,Parlane')", 'type': 'internal_log'}
2026-02-03T22:57:30.981835	{'source': 'logging', 'level': 'INFO', 'pathname': '/nix/store/pszlv4r8dapp8a8nw5qjpiz4q8py1bfg-central-env/lib/python3.10/site-packages/central/github/authz.py', 'lineno': 21, 'msg': 'Refreshing list of trusted users (from %s/%s)', 'args': "('dolphin-emu', 'core-developers')", 'type': 'internal_log'}

Recent 'issue' events

2026-02-03T22:35:18.876310	{'source': 'redmine', 'new': False, 'update': 2, 'issue': 13966, 'title': 'Just Dance (SDNP41) will not launch if Progressive Scan is enabled', 'author': 'Someone7272', 'type': 'issue'}
2026-02-03T19:29:56.821630	{'source': 'redmine', 'new': False, 'update': 4, 'issue': 13707, 'title': '.bca data file support for Wii games (Feature Request)', 'author': 'P-chan', 'type': 'issue'}
2026-02-03T04:28:25.991986	{'source': 'redmine', 'new': False, 'update': 2, 'issue': 13971, 'title': 'Samurai Shodown Anthology needs texture cache setting in game INI', 'author': 'Billiard26', 'type': 'issue'}
2026-02-01T13:49:55.259998	{'source': 'redmine', 'new': False, 'update': 3, 'issue': 13970, 'title': 'Majoras Mask (VC) Sound Disappears; Also Softlock; No Savestate before bug', 'author': 'master0fdisaster1', 'type': 'issue'}
2026-01-31T21:27:01.119661	{'source': 'redmine', 'new': False, 'update': 4, 'issue': 13865, 'title': 'fmadds does not match hardware', 'author': 'JMC4789', 'type': 'issue'}
2026-01-31T21:10:38.620105	{'source': 'redmine', 'new': False, 'update': 3, 'issue': 13865, 'title': 'fmadds does not match hardware', 'author': 'JMC4789', 'type': 'issue'}
2026-01-31T15:00:43.901629	{'source': 'redmine', 'new': True, 'update': 0, 'issue': 13971, 'title': 'Samurai Shodown Anthology needs texture cache setting in game INI', 'author': 'JosJuice', 'type': 'issue'}
2026-01-30T05:15:01.064699	{'source': 'redmine', 'new': False, 'update': 6, 'issue': 7743, 'title': "Various Action Replay Codes and Gecko Codes don't work", 'author': 'Billiard26', 'type': 'issue'}

Recent 'new_dev_version' events

2026-02-03T22:50:56.732271	{'source': 'repomanager', 'hash': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'branch': 'master', 'shortrev': '2512-272', 'author': 'Jordan Woyak', 'message': 'Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'url': 'https://github.com/dolphin-emu/dolphin/commit/dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'type': 'new_dev_version'}
2026-02-03T04:26:17.092079	{'source': 'repomanager', 'hash': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'branch': 'master', 'shortrev': '2512-267', 'author': 'Jordan Woyak', 'message': 'Merge pull request #14314 from JMC47/shodown\n\nGameINI: Force Safe Texture Cache to Samurai Shodown Anthology', 'url': 'https://github.com/dolphin-emu/dolphin/commit/eac7b290423efea867c2c7f3f2b2792d05418bdd', 'type': 'new_dev_version'}
2026-02-02T20:05:56.081402	{'source': 'repomanager', 'hash': 'c7de9162765452fc2787f3ba4f0092394067d603', 'branch': 'master', 'shortrev': '2512-265', 'author': 'JMC47', 'message': 'Merge pull request #14316 from JoshuaVandaele/resourcefix\n\nResourcePack: Fix loading resource packs', 'url': 'https://github.com/dolphin-emu/dolphin/commit/c7de9162765452fc2787f3ba4f0092394067d603', 'type': 'new_dev_version'}
2026-01-31T23:02:14.045612	{'source': 'repomanager', 'hash': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'branch': 'master', 'shortrev': '2512-263', 'author': 'Dentomologist', 'message': 'Merge pull request #14204 from Geotale/update-comment\n\nUpdate Comments Based On Hardware Test', 'url': 'https://github.com/dolphin-emu/dolphin/commit/6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'type': 'new_dev_version'}
2026-01-31T22:45:22.874684	{'source': 'repomanager', 'hash': '16276928ccb6d14e32cd33cde993f70503f0e838', 'branch': 'master', 'shortrev': '2512-261', 'author': 'Jordan Woyak', 'message': 'Merge pull request #14312 from JoshuaVandaele/wglDestroyPbufferARB\n\nWGL: Correctly load wglDestroyPbufferARB extension', 'url': 'https://github.com/dolphin-emu/dolphin/commit/16276928ccb6d14e32cd33cde993f70503f0e838', 'type': 'new_dev_version'}
2026-01-30T19:51:12.531401	{'source': 'repomanager', 'hash': 'b1020186519d690a9b64a215f00da7453ab2803b', 'branch': 'master', 'shortrev': '2512-259', 'author': 'JMC47', 'message': 'Merge pull request #14307 from Sam-Belliveau/granular-audio-stretching\n\nAdd an option to preserve audio pitch when emulation speed changes', 'url': 'https://github.com/dolphin-emu/dolphin/commit/b1020186519d690a9b64a215f00da7453ab2803b', 'type': 'new_dev_version'}
2026-01-27T23:32:30.190426	{'source': 'repomanager', 'hash': 'bb7e01167346d0e3e4bab426a7b87968d715ee5e', 'branch': 'master', 'shortrev': '2512-257', 'author': 'Jordan Woyak', 'message': 'Merge pull request #14254 from droidpeti/master\n\nGMSE01: Added credits to Gecko codes for Super Mario Sunshine (USA)', 'url': 'https://github.com/dolphin-emu/dolphin/commit/bb7e01167346d0e3e4bab426a7b87968d715ee5e', 'type': 'new_dev_version'}

Recent 'notification' events

2026-02-03T23:32:40.514260	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303j\ufeffordan-woyak\x03 opened pull request #14318: Core: Make RunOnCPUThread always non-blocking. (\x0306master\x03...\x0306RunOnCPUThread-always-non-blocking\x03): \x1f\x0302https://dolp.in/pr14318\x03\x1f', 'type': 'notification'}
2026-02-03T22:53:33.532870	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] build for #14218 \x0304failed\x03 on builders [pr-win-dbg-x64]: \x1f\x0302https://dolphin.ci/#/builders/31/builds/8927\x03\x1f', 'type': 'notification'}
2026-02-03T22:50:54.524181	{'source': 'notifications', 'msg': 'dd2b94 by \x0303J\ufeffordan Woyak\x03 [\x03091\x03|\x030913\x03|\x03040\x03] \x1f\x0302https://dolp.in/rdd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f\x03\x1f Merge pull request #13594 from jordan-woyak/state-cleanups', 'type': 'notification'}
2026-02-03T22:50:54.524010	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x1f\x03] \x0303j\ufeffordan-woyak\x03 pushed \x021\x02 new commit to \x0306master\x03', 'type': 'notification'}
2026-02-03T22:50:54.190465	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303j\ufeffordan-woyak\x03 merged pull request #13594: State: Simplify interthread communication and general cleanups. (\x0306master\x03...\x0306state-cleanups\x03): \x1f\x0302https://dolp.in/pr13594\x03\x1f', 'type': 'notification'}
2026-02-03T22:48:25.698070	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303j\ufeffordan-woyak\x03 approved pull request #13768 (Core: Create fastmem mappings for page address translation): \x1f\x0302https://dolp.in/pr13768#pullrequestreview-3748007210\x03\x1f', 'type': 'notification'}
2026-02-03T22:35:19.460599	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303J\ufeffosJuice\x03 synchronized pull request #14218: Jit64: Make ABI_CallFunction generic (\x0306master\x03...\x0306jit64-abi-call-function-parameters\x03): \x1f\x0302https://dolp.in/pr14218\x03\x1f', 'type': 'notification'}
2026-02-03T22:35:18.877874	{'source': 'notifications', 'msg': 'Update 2 to issue 13966 ("Just Dance (SDNP41) will not launch if Progressive Scan is enabled") by \x0303S\ufeffomeone7272\x03 - \x1f\x0302https://dolp.in/i13966/2\x03\x1f', 'type': 'notification'}
2026-02-03T22:24:04.646254	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] build for #13768 \x0304failed\x03 on builders [pr-android]: \x1f\x0302https://dolphin.ci/#/builders/22/builds/8923\x03\x1f', 'type': 'notification'}
2026-02-03T22:19:09.569348	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] build for #13768 \x0304failed\x03 on builders [pr-win-dbg-x64]: \x1f\x0302https://dolphin.ci/#/builders/31/builds/8925\x03\x1f', 'type': 'notification'}
2026-02-03T22:15:59.569813	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] build for #13768 \x0304failed\x03 on builders [pr-deb-dbg-x64]: \x1f\x0302https://dolphin.ci/#/builders/1/builds/8923\x03\x1f', 'type': 'notification'}
2026-02-03T22:15:01.768998	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] build for #13768 \x0304failed\x03 on builders [pr-freebsd-x64]: \x1f\x0302https://dolphin.ci/#/builders/11/builds/8921\x03\x1f', 'type': 'notification'}
2026-02-03T22:13:43.248862	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] build for #13768 \x0304failed\x03 on builders [pr-win-arm64]: \x1f\x0302https://dolphin.ci/#/builders/13/builds/8918\x03\x1f', 'type': 'notification'}
2026-02-03T22:06:12.690596	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303J\ufeffosJuice\x03 synchronized pull request #13768: Core: Create fastmem mappings for page address translation (\x0306master\x03...\x0306page-table-fastmem-2\x03): \x1f\x0302https://dolp.in/pr13768\x03\x1f', 'type': 'notification'}
2026-02-03T22:05:19.920482	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] build for #13768 \x0304failed\x03 on builders [lint]: \x1f\x0302https://dolphin.ci/#/builders/9/builds/8920\x03\x1f', 'type': 'notification'}
2026-02-03T22:04:37.179304	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303J\ufeffosJuice\x03 synchronized pull request #13768: Core: Create fastmem mappings for page address translation (\x0306master\x03...\x0306page-table-fastmem-2\x03): \x1f\x0302https://dolp.in/pr13768\x03\x1f', 'type': 'notification'}
2026-02-03T21:10:07.303726	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303j\ufeffordan-woyak\x03 commented on #13970 (WiimoteReal: Windows improvements.): \x1f\x0302https://dolp.in/pr13970#issuecomment-3843701295\x03\x1f', 'type': 'notification'}
2026-02-03T21:01:13.944377	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303T\ufeffrihy\x03 edited a comment on #13970 (WiimoteReal: Windows improvements.): \x1f\x0302https://dolp.in/pr13970#issuecomment-3843649310\x03\x1f', 'type': 'notification'}
2026-02-03T20:58:44.456747	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303T\ufeffrihy\x03 commented on #13970 (WiimoteReal: Windows improvements.): \x1f\x0302https://dolp.in/pr13970#issuecomment-3843649310\x03\x1f', 'type': 'notification'}
2026-02-03T19:29:56.821900	{'source': 'notifications', 'msg': 'Update 4 to issue 13707 (".bca data file support for Wii games (Feature Request)") by \x0303P\ufeff-chan\x03 - \x1f\x0302https://dolp.in/i13707/4\x03\x1f', 'type': 'notification'}
2026-02-03T17:22:19.677400	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303J\ufeffMC47\x03 edited a comment on #13844 (Core: Triforce support): \x1f\x0302https://dolp.in/pr13844#issuecomment-3842626451\x03\x1f', 'type': 'notification'}
2026-02-03T17:22:09.068538	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303J\ufeffMC47\x03 commented on #13844 (Core: Triforce support): \x1f\x0302https://dolp.in/pr13844#issuecomment-3842626451\x03\x1f', 'type': 'notification'}
2026-02-03T17:08:19.685165	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303p\ufeffizzzza19\x03 commented on #13844 (Core: Triforce support): \x1f\x0302https://dolp.in/pr13844#issuecomment-3842552882\x03\x1f', 'type': 'notification'}
2026-02-03T16:31:29.616745	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303s\ufeffepalani\x03 commented on #13844 (Core: Triforce support): \x1f\x0302https://dolp.in/pr13844#issuecomment-3842350109\x03\x1f', 'type': 'notification'}
2026-02-03T16:08:06.874370	{'source': 'notifications', 'msg': '[\x1f\x0313dolphin-emu/dolphin\x03\x1f] \x0303p\ufeffizzzza19\x03 commented on commit 128a6454b6247c0c2a2cafb2d91d03f71fc112e0: \x1f\x0302https://dolp.in/r128a6454b6247c0c2a2cafb2d91d03f71fc112e0#commitcomment-176299891\x03\x1f', 'type': 'notification'}

Recent 'pull_request_fifoci_status' events

2026-02-03T22:57:32.343620	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '32856fd15531890d65379376c8cadfc3611b36cb', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 13768, 'type': 'pull_request_fifoci_status'}
2026-02-03T22:31:51.810969	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'c8180071a552720e5436e908afc4eda7e87be08c', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 14317, 'type': 'pull_request_fifoci_status'}
2026-02-03T22:13:31.207061	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'df85a2c99d667c537a5a896cd81954fb63fab027', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 13997, 'type': 'pull_request_fifoci_status'}
2026-02-03T21:54:32.835079	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'df85a2c99d667c537a5a896cd81954fb63fab027', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 13997, 'type': 'pull_request_fifoci_status'}
2026-02-02T22:27:35.708352	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'c8180071a552720e5436e908afc4eda7e87be08c', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 14317, 'type': 'pull_request_fifoci_status'}
2026-02-02T22:21:33.739375	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'c8180071a552720e5436e908afc4eda7e87be08c', 'service': 'pr-fifoci-vk-lin-mesa', 'pr': 14317, 'type': 'pull_request_fifoci_status'}
2026-02-02T22:11:18.938675	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'c8180071a552720e5436e908afc4eda7e87be08c', 'service': 'pr-fifoci-vk-lin-mesa', 'pr': 14317, 'type': 'pull_request_fifoci_status'}
2026-02-02T22:00:49.874018	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'c8180071a552720e5436e908afc4eda7e87be08c', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 14317, 'type': 'pull_request_fifoci_status'}
2026-02-02T21:48:53.656103	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'c8180071a552720e5436e908afc4eda7e87be08c', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 14317, 'type': 'pull_request_fifoci_status'}
2026-02-02T21:34:23.333363	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'c8180071a552720e5436e908afc4eda7e87be08c', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 14317, 'type': 'pull_request_fifoci_status'}
2026-02-02T21:18:18.371595	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '66380234a68eb4d8106aa3004b0e531a06f5cd24', 'service': 'pr-fifoci-vk-lin-mesa', 'pr': 13844, 'type': 'pull_request_fifoci_status'}
2026-02-02T21:01:42.103917	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '66380234a68eb4d8106aa3004b0e531a06f5cd24', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 13844, 'type': 'pull_request_fifoci_status'}
2026-02-02T20:17:41.642487	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '66380234a68eb4d8106aa3004b0e531a06f5cd24', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 13844, 'type': 'pull_request_fifoci_status'}
2026-02-02T19:36:41.663910	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'd87e8ab7ff66f3efcedeb5a0ab869b537933aada', 'service': 'pr-fifoci-vk-lin-mesa', 'pr': 14316, 'type': 'pull_request_fifoci_status'}
2026-02-02T19:26:23.873938	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'd87e8ab7ff66f3efcedeb5a0ab869b537933aada', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 14316, 'type': 'pull_request_fifoci_status'}
2026-02-02T19:12:28.666753	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'd87e8ab7ff66f3efcedeb5a0ab869b537933aada', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 14316, 'type': 'pull_request_fifoci_status'}
2026-02-02T14:36:19.207571	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'd75794b774d64f50d4bad334660d8f9068f241bd', 'service': 'pr-fifoci-vk-lin-mesa', 'pr': 14260, 'type': 'pull_request_fifoci_status'}
2026-02-02T14:26:00.718968	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'd75794b774d64f50d4bad334660d8f9068f241bd', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 14260, 'type': 'pull_request_fifoci_status'}
2026-02-02T14:13:25.995721	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'd75794b774d64f50d4bad334660d8f9068f241bd', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 14260, 'type': 'pull_request_fifoci_status'}
2026-02-02T09:18:14.519048	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '512972c1cd4a10111412d619e464fc72a964101e', 'service': 'pr-fifoci-vk-lin-mesa', 'pr': 14260, 'type': 'pull_request_fifoci_status'}
2026-02-02T09:07:59.865610	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '512972c1cd4a10111412d619e464fc72a964101e', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 14260, 'type': 'pull_request_fifoci_status'}
2026-02-02T08:55:22.417124	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': '512972c1cd4a10111412d619e464fc72a964101e', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 14260, 'type': 'pull_request_fifoci_status'}
2026-02-01T22:40:56.371197	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'f07ac93e55496dbafad9d3d2ad6d36c6f5c016a6', 'service': 'pr-fifoci-sw-lin-mesa', 'pr': 14308, 'type': 'pull_request_fifoci_status'}
2026-02-01T22:34:41.356263	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'f07ac93e55496dbafad9d3d2ad6d36c6f5c016a6', 'service': 'pr-fifoci-vk-lin-mesa', 'pr': 14308, 'type': 'pull_request_fifoci_status'}
2026-02-01T22:24:08.767167	{'source': 'buildbot', 'repo': 'dolphin-emu/dolphin', 'hash': 'f07ac93e55496dbafad9d3d2ad6d36c6f5c016a6', 'service': 'pr-fifoci-ogl-lin-mesa', 'pr': 14308, 'type': 'pull_request_fifoci_status'}

Recent 'raw_bb_hook' events

2026-02-03T23:34:37.470745	{'source': 'webserver', 'raw': {'buildid': 157756, 'number': 8923, 'builderid': 9, 'buildrequestid': 157882, 'workerid': 10, 'masterid': 1, 'started_at': 1770161567, 'complete_at': 1770161571, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {'branchname': ['pr-14318', 'Change'], 'shortrev': ['201aa6', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'workername': ['ubuntu-lts', 'Worker'], 'project': ['', 'Build'], 'buildnumber': [8923, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'buildername': ['lint', 'Builder'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'pr_id': [14318, 'Change'], 'builderid': [9, 'Builder'], 'repository': ['', 'Build'], 'codebase': ['', 'Build'], 'basedir': ['/buildbot', 'Worker'], 'builddir': ['/buildbot/lint', 'Worker'], 'got_revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'GitNoBranch']}, 'buildrequest': {'buildrequestid': 157882, 'buildsetid': 36142, 'builderid': 9, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': True, 'results': 0, 'submitted_at': 1770161566, 'complete_at': 1770161571, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 9, 'name': 'lint', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/9/builds/8923'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:17.798758	{'source': 'webserver', 'raw': {'buildid': 157764, 'number': 8923, 'builderid': 27, 'buildrequestid': 157887, 'workerid': 10, 'masterid': 1, 'started_at': 1770161571, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'buildername': ['pr-ubu-x64', 'Builder'], 'builderid': [27, 'Builder'], 'repository': ['', 'Build'], 'workername': ['ubuntu-lts', 'Worker'], 'buildnumber': [8923, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build']}, 'buildrequest': {'buildrequestid': 157887, 'buildsetid': 36142, 'builderid': 27, 'priority': 0, 'claimed': True, 'claimed_at': 1770161571, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 27, 'name': 'pr-ubu-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/27/builds/8923'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:14.730916	{'source': 'webserver', 'raw': {'buildid': 157762, 'number': 8924, 'builderid': 11, 'buildrequestid': 157883, 'workerid': 1, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'buildername': ['pr-freebsd-x64', 'Builder'], 'builderid': [11, 'Builder'], 'buildnumber': [8924, 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'repository': ['', 'Build'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'workername': ['freebsd', 'Worker'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'branchname': ['pr-14318', 'Change'], 'pr_id': [14318, 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'shortrev': ['201aa6', 'Change']}, 'buildrequest': {'buildrequestid': 157883, 'buildsetid': 36142, 'builderid': 11, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 11, 'name': 'pr-freebsd-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/11/builds/8924'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:13.813813	{'source': 'webserver', 'raw': {'buildid': 157757, 'number': 8926, 'builderid': 2, 'buildrequestid': 157880, 'workerid': 4, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'builderid': [2, 'Builder'], 'workername': ['windows', 'Worker'], 'buildnumber': [8926, 'Build'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'scheduler': ['pr', 'Scheduler'], 'buildername': ['pr-win-x64', 'Builder'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'repository': ['', 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157880, 'buildsetid': 36142, 'builderid': 2, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 2, 'name': 'pr-win-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/2/builds/8926'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:12.465710	{'source': 'webserver', 'raw': {'buildid': 157760, 'number': 8938, 'builderid': 7, 'buildrequestid': 157881, 'workerid': 8, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'buildnumber': [8938, 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'codebase': ['', 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'project': ['', 'Build'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'builderid': [7, 'Builder'], 'workername': ['osx-m1', 'Worker'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'buildername': ['pr-osx-universal', 'Builder']}, 'buildrequest': {'buildrequestid': 157881, 'buildsetid': 36142, 'builderid': 7, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/7/builds/8938'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:12.373303	{'source': 'webserver', 'raw': {'buildid': 157759, 'number': 8922, 'builderid': 26, 'buildrequestid': 157886, 'workerid': 9, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'scheduler': ['pr', 'Scheduler'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'branchname': ['pr-14318', 'Change'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'buildername': ['pr-deb-x64', 'Builder'], 'builderid': [26, 'Builder'], 'workername': ['debian', 'Worker'], 'buildnumber': [8922, 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build']}, 'buildrequest': {'buildrequestid': 157886, 'buildsetid': 36142, 'builderid': 26, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 26, 'name': 'pr-deb-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/26/builds/8922'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:12.003496	{'source': 'webserver', 'raw': {'buildid': 157763, 'number': 8926, 'builderid': 22, 'buildrequestid': 157885, 'workerid': 12, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'pr_id': [14318, 'Change'], 'buildnumber': [8926, 'Build'], 'buildername': ['pr-android', 'Builder'], 'builderid': [22, 'Builder'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'branchname': ['pr-14318', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'codebase': ['', 'Build'], 'project': ['', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'shortrev': ['201aa6', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'workername': ['android', 'Worker']}, 'buildrequest': {'buildrequestid': 157885, 'buildsetid': 36142, 'builderid': 22, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 22, 'name': 'pr-android', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/22/builds/8926'}, 'type': 'raw_bb_hook'}
2026-02-03T23:34:11.174714	{'source': 'webserver', 'raw': {'buildid': 157756, 'number': 8923, 'builderid': 9, 'buildrequestid': 157882, 'workerid': 10, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'branchname': ['pr-14318', 'Change'], 'shortrev': ['201aa6', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'workername': ['ubuntu-lts', 'Worker'], 'project': ['', 'Build'], 'buildnumber': [8923, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'buildername': ['lint', 'Builder'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'pr_id': [14318, 'Change'], 'builderid': [9, 'Builder'], 'repository': ['', 'Build'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157882, 'buildsetid': 36142, 'builderid': 9, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 9, 'name': 'lint', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/9/builds/8923'}, 'type': 'raw_bb_hook'}
2026-02-03T23:33:22.248903	{'source': 'webserver', 'raw': {'buildid': 157761, 'number': 4069, 'builderid': 43, 'buildrequestid': 157889, 'workerid': 13, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'branchname': ['pr-14318', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'pr_id': [14318, 'Change'], 'buildername': ['pr-flatpak-x64', 'Builder'], 'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'branch': ['refs/pull/14318/head', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'shortrev': ['201aa6', 'Change'], 'buildnumber': [4069, 'Build'], 'codebase': ['', 'Build'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'scheduler': ['pr', 'Scheduler'], 'builderid': [43, 'Builder'], 'workername': ['altair-flatpak', 'Worker'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'repository': ['', 'Build'], 'project': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157889, 'buildsetid': 36142, 'builderid': 43, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 43, 'name': 'pr-flatpak-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/43/builds/4069'}, 'type': 'raw_bb_hook'}
2026-02-03T23:33:21.536353	{'source': 'webserver', 'raw': {'buildid': 157758, 'number': 4014, 'builderid': 44, 'buildrequestid': 157890, 'workerid': 14, 'masterid': 1, 'started_at': 1770161567, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'baserev': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Change'], 'headrev': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Change'], 'branchname': ['pr-14318', 'Change'], 'revision': ['201aa659066b41a2cfe89b615213bcb093fcbe79', 'Build'], 'buildername': ['pr-flatpak-arm64', 'Builder'], 'builderid': [44, 'Builder'], 'scheduler': ['pr', 'Scheduler'], 'shortrev': ['201aa6', 'Change'], 'pr_id': [14318, 'Change'], 'repo': ['dolphin-emu/dolphin', 'Change'], 'workername': ['deneb-flatpak', 'Worker'], 'buildnumber': [4014, 'Build'], 'branch': ['refs/pull/14318/head', 'Build'], 'owners': [['Central (on behalf of: jordan-woyak)'], 'Build'], 'project': ['', 'Build'], 'repository': ['', 'Build'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157890, 'buildsetid': 36142, 'builderid': 44, 'priority': 0, 'claimed': True, 'claimed_at': 1770161567, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770161566, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36142, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'pr' triggered this build", 'submitted_at': 1770161566, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10816, 'branch': 'refs/pull/14318/head', 'revision': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770161566, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 44, 'name': 'pr-flatpak-arm64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/44/builds/4014'}, 'type': 'raw_bb_hook'}
2026-02-03T23:30:41.230012	{'source': 'webserver', 'raw': {'buildid': 157755, 'number': 7798, 'builderid': 10, 'buildrequestid': 157863, 'workerid': 5, 'masterid': 1, 'started_at': 1770161440, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'pr_id': [14218, 'Trigger'], 'buildnumber': [7798, 'Build'], 'project': ['', 'Build'], 'branchname': ['pr-14218', 'Trigger'], 'shortrev': ['72a6ad', 'Trigger'], 'builderid': [10, 'Builder'], 'repository': ['', 'Build'], 'headrev': ['72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'Trigger'], 'buildername': ['pr-fifoci-ogl-lin-mesa', 'Builder'], 'revision': ['72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'Build'], 'scheduler': ['pr-fifoci-lin', 'Scheduler'], 'branch': ['refs/pull/14218/head', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'baserev': ['eac7b290423efea867c2c7f3f2b2792d05418bdd', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'workername': ['altair-fifoci', 'Worker'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157863, 'buildsetid': 36139, 'builderid': 10, 'priority': 0, 'claimed': True, 'claimed_at': 1770161440, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770158877, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36139, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-lin' triggered this build", 'submitted_at': 1770158877, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157729, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10814, 'branch': 'refs/pull/14218/head', 'revision': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770158126, 'patch': None}]}, 'parentbuild': {'buildid': 157729, 'number': 8922, 'builderid': 27, 'buildrequestid': 157857, 'workerid': 10, 'masterid': 1, 'started_at': 1770158131, 'complete_at': 1770158877, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 27, 'name': 'pr-ubu-x64', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 10, 'name': 'pr-fifoci-ogl-lin-mesa', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/10/builds/7798'}, 'type': 'raw_bb_hook'}
2026-02-03T23:30:40.764575	{'source': 'webserver', 'raw': {'buildid': 157754, 'number': 2115, 'builderid': 5, 'buildrequestid': 157868, 'workerid': 5, 'masterid': 1, 'started_at': 1770160645, 'complete_at': 1770161440, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {'owners': [['Jordan Woyak'], 'Build'], 'shortrev': ['2512-272', 'Change'], 'basedir': ['/var/lib/fifoci-worker/worker', 'Worker'], 'warnings-count': [0, 'WarningCountingShellCommand'], 'author': ['Jordan Woyak', 'Change'], 'scheduler': ['dev', 'Scheduler'], 'builderid': [5, 'Builder'], 'buildnumber': [2115, 'Build'], 'revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Build'], 'codebase': ['', 'Build'], 'description': ['Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'Change'], 'builddir': ['/var/lib/fifoci-worker/worker/fifoci-ogl-lin-mesa', 'Worker'], 'branchname': ['master', 'Change'], 'buildername': ['fifoci-ogl-lin-mesa', 'Builder'], 'workername': ['altair-fifoci', 'Worker'], 'branch': ['master', 'Build'], 'repository': ['', 'Build'], 'project': ['', 'Build'], 'got_revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'GitNoBranch']}, 'buildrequest': {'buildrequestid': 157868, 'buildsetid': 36141, 'builderid': 5, 'priority': 0, 'claimed': True, 'claimed_at': 1770160645, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770159056, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36141, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'dev' triggered this build", 'submitted_at': 1770159056, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10815, 'branch': 'master', 'revision': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770159056, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 5, 'name': 'fifoci-ogl-lin-mesa', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/5/builds/2115'}, 'type': 'raw_bb_hook'}
2026-02-03T23:25:18.058501	{'source': 'webserver', 'raw': {'buildid': 157746, 'number': 1136, 'builderid': 39, 'buildrequestid': 157875, 'workerid': 12, 'masterid': 1, 'started_at': 1770159835, 'complete_at': 1770161117, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {'builderid': [39, 'Builder'], 'branch': ['master', 'Build'], 'codebase': ['', 'Build'], 'branchname': ['master', 'Change'], 'description': ['Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'Change'], 'buildername': ['dev-android', 'Builder'], 'buildnumber': [1136, 'Build'], 'repository': ['', 'Build'], 'got_revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'GitNoBranch'], 'shortrev': ['2512-272', 'Change'], 'scheduler': ['dev', 'Scheduler'], 'workername': ['android', 'Worker'], 'revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Build'], 'project': ['', 'Build'], 'author': ['Jordan Woyak', 'Change'], 'basedir': ['/buildbot', 'Worker'], 'owners': [['Jordan Woyak'], 'Build'], 'builddir': ['/buildbot/dev-android', 'Worker']}, 'buildrequest': {'buildrequestid': 157875, 'buildsetid': 36141, 'builderid': 39, 'priority': 0, 'claimed': True, 'claimed_at': 1770159835, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770159056, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36141, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'dev' triggered this build", 'submitted_at': 1770159056, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10815, 'branch': 'master', 'revision': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770159056, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 39, 'name': 'dev-android', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/39/builds/1136'}, 'type': 'raw_bb_hook'}
2026-02-03T23:17:25.574672	{'source': 'webserver', 'raw': {'buildid': 157754, 'number': 2115, 'builderid': 5, 'buildrequestid': 157868, 'workerid': 5, 'masterid': 1, 'started_at': 1770160645, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'shortrev': ['2512-272', 'Change'], 'author': ['Jordan Woyak', 'Change'], 'scheduler': ['dev', 'Scheduler'], 'builderid': [5, 'Builder'], 'buildnumber': [2115, 'Build'], 'revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Build'], 'codebase': ['', 'Build'], 'description': ['Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'Change'], 'branchname': ['master', 'Change'], 'buildername': ['fifoci-ogl-lin-mesa', 'Builder'], 'workername': ['altair-fifoci', 'Worker'], 'branch': ['master', 'Build'], 'repository': ['', 'Build'], 'project': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157868, 'buildsetid': 36141, 'builderid': 5, 'priority': 0, 'claimed': True, 'claimed_at': 1770160645, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770159056, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36141, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'dev' triggered this build", 'submitted_at': 1770159056, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10815, 'branch': 'master', 'revision': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770159056, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 5, 'name': 'fifoci-ogl-lin-mesa', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/5/builds/2115'}, 'type': 'raw_bb_hook'}
2026-02-03T23:17:25.445573	{'source': 'webserver', 'raw': {'buildid': 157747, 'number': 979, 'builderid': 40, 'buildrequestid': 157876, 'workerid': 5, 'masterid': 1, 'started_at': 1770159903, 'complete_at': 1770160645, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {'buildername': ['fifoci-vk-lin-mesa', 'Builder'], 'revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Build'], 'builddir': ['/var/lib/fifoci-worker/worker/fifoci-vk-lin-mesa', 'Worker'], 'author': ['Jordan Woyak', 'Change'], 'workername': ['altair-fifoci', 'Worker'], 'codebase': ['', 'Build'], 'description': ['Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'Change'], 'buildnumber': [979, 'Build'], 'project': ['', 'Build'], 'basedir': ['/var/lib/fifoci-worker/worker', 'Worker'], 'shortrev': ['2512-272', 'Change'], 'builderid': [40, 'Builder'], 'repository': ['', 'Build'], 'got_revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'GitNoBranch'], 'warnings-count': [0, 'WarningCountingShellCommand'], 'branchname': ['master', 'Change'], 'scheduler': ['dev', 'Scheduler'], 'branch': ['master', 'Build'], 'owners': [['Jordan Woyak'], 'Build']}, 'buildrequest': {'buildrequestid': 157876, 'buildsetid': 36141, 'builderid': 40, 'priority': 0, 'claimed': True, 'claimed_at': 1770159903, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770159056, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36141, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'dev' triggered this build", 'submitted_at': 1770159056, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10815, 'branch': 'master', 'revision': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770159056, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 40, 'name': 'fifoci-vk-lin-mesa', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/40/builds/979'}, 'type': 'raw_bb_hook'}
2026-02-03T23:11:10.293481	{'source': 'webserver', 'raw': {'buildid': 157753, 'number': 7626, 'builderid': 19, 'buildrequestid': 157866, 'workerid': 8, 'masterid': 1, 'started_at': 1770160251, 'complete_at': 1770160270, 'locks_duration_s': 0, 'complete': True, 'state_string': 'failed build (failure)', 'results': 2, 'properties': {'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'branch': ['refs/pull/14218/head', 'Build'], 'got_revision': ['ca98b3030bcc70e67158ce899a5589aa2ec65d32', 'GitNoBranch'], 'shortrev': ['72a6ad', 'Trigger'], 'builderid': [19, 'Builder'], 'repository': ['', 'Build'], 'basedir': ['/Users/administrator/buildbot-worker/osx_m1', 'Worker'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'revision': ['72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'builddir': ['/Users/administrator/buildbot-worker/osx_m1/pr-fifoci-mvk-osx-m1', 'Worker'], 'warnings-count': [1, 'WarningCountingShellCommand'], 'baserev': ['eac7b290423efea867c2c7f3f2b2792d05418bdd', 'Change'], 'pr_id': [14218, 'Trigger'], 'workername': ['osx-m1', 'Worker'], 'codebase': ['', 'Build'], 'branchname': ['pr-14218', 'Trigger'], 'headrev': ['72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'Trigger'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'buildnumber': [7626, 'Build'], 'project': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157866, 'buildsetid': 36140, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160251, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770158985, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36140, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770158985, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157728, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10814, 'branch': 'refs/pull/14218/head', 'revision': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770158126, 'patch': None}]}, 'parentbuild': {'buildid': 157728, 'number': 8937, 'builderid': 7, 'buildrequestid': 157851, 'workerid': 8, 'masterid': 1, 'started_at': 1770158129, 'complete_at': 1770158985, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7626'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:51.821221	{'source': 'webserver', 'raw': {'buildid': 157753, 'number': 7626, 'builderid': 19, 'buildrequestid': 157866, 'workerid': 8, 'masterid': 1, 'started_at': 1770160251, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'branch': ['refs/pull/14218/head', 'Build'], 'shortrev': ['72a6ad', 'Trigger'], 'builderid': [19, 'Builder'], 'repository': ['', 'Build'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'revision': ['72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'baserev': ['eac7b290423efea867c2c7f3f2b2792d05418bdd', 'Change'], 'pr_id': [14218, 'Trigger'], 'workername': ['osx-m1', 'Worker'], 'codebase': ['', 'Build'], 'branchname': ['pr-14218', 'Trigger'], 'headrev': ['72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'Trigger'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'buildnumber': [7626, 'Build'], 'project': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157866, 'buildsetid': 36140, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160251, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770158985, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36140, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770158985, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157728, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10814, 'branch': 'refs/pull/14218/head', 'revision': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770158126, 'patch': None}]}, 'parentbuild': {'buildid': 157728, 'number': 8937, 'builderid': 7, 'buildrequestid': 157851, 'workerid': 8, 'masterid': 1, 'started_at': 1770158129, 'complete_at': 1770158985, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7626'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:51.525337	{'source': 'webserver', 'raw': {'buildid': 157752, 'number': 7625, 'builderid': 19, 'buildrequestid': 157861, 'workerid': 8, 'masterid': 1, 'started_at': 1770160237, 'complete_at': 1770160251, 'locks_duration_s': 0, 'complete': True, 'state_string': 'failed build (failure)', 'results': 2, 'properties': {'headrev': ['32856fd15531890d65379376c8cadfc3611b36cb', 'Trigger'], 'pr_id': [13768, 'Trigger'], 'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'buildnumber': [7625, 'Build'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'basedir': ['/Users/administrator/buildbot-worker/osx_m1', 'Worker'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'project': ['', 'Build'], 'got_revision': ['ca98b3030bcc70e67158ce899a5589aa2ec65d32', 'GitNoBranch'], 'branchname': ['pr-13768', 'Trigger'], 'shortrev': ['32856f', 'Trigger'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'builderid': [19, 'Builder'], 'branch': ['refs/pull/13768/head', 'Build'], 'codebase': ['', 'Build'], 'warnings-count': [1, 'WarningCountingShellCommand'], 'baserev': ['eac7b290423efea867c2c7f3f2b2792d05418bdd', 'Change'], 'workername': ['osx-m1', 'Worker'], 'revision': ['32856fd15531890d65379376c8cadfc3611b36cb', 'Build'], 'builddir': ['/Users/administrator/buildbot-worker/osx_m1/pr-fifoci-mvk-osx-m1', 'Worker']}, 'buildrequest': {'buildrequestid': 157861, 'buildsetid': 36137, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160237, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770158129, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36137, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770158129, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157716, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10813, 'branch': 'refs/pull/13768/head', 'revision': '32856fd15531890d65379376c8cadfc3611b36cb', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770156379, 'patch': None}]}, 'parentbuild': {'buildid': 157716, 'number': 8936, 'builderid': 7, 'buildrequestid': 157831, 'workerid': 8, 'masterid': 1, 'started_at': 1770157334, 'complete_at': 1770158129, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7625'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:46.791175	{'source': 'webserver', 'raw': {'buildid': 157750, 'number': 7623, 'builderid': 19, 'buildrequestid': 157827, 'workerid': 8, 'masterid': 1, 'started_at': 1770160221, 'complete_at': 1770160236, 'locks_duration_s': 0, 'complete': True, 'state_string': 'failed build (failure)', 'results': 2, 'properties': {'branchname': ['pr-14317', 'Trigger'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'buildnumber': [7623, 'Build'], 'project': ['', 'Build'], 'builddir': ['/Users/administrator/buildbot-worker/osx_m1/pr-fifoci-mvk-osx-m1', 'Worker'], 'pr_id': [14317, 'Trigger'], 'workername': ['osx-m1', 'Worker'], 'codebase': ['', 'Build'], 'baserev': ['c7de9162765452fc2787f3ba4f0092394067d603', 'Change'], 'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'branch': ['refs/pull/14317/head', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'got_revision': ['ca98b3030bcc70e67158ce899a5589aa2ec65d32', 'GitNoBranch'], 'warnings-count': [1, 'WarningCountingShellCommand'], 'shortrev': ['c81800', 'Trigger'], 'builderid': [19, 'Builder'], 'repository': ['', 'Build'], 'headrev': ['c8180071a552720e5436e908afc4eda7e87be08c', 'Trigger'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'revision': ['c8180071a552720e5436e908afc4eda7e87be08c', 'Build'], 'basedir': ['/Users/administrator/buildbot-worker/osx_m1', 'Worker']}, 'buildrequest': {'buildrequestid': 157827, 'buildsetid': 36129, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160221, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770156358, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36129, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770156358, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157688, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10809, 'branch': 'refs/pull/14317/head', 'revision': 'c8180071a552720e5436e908afc4eda7e87be08c', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770066448, 'patch': None}]}, 'parentbuild': {'buildid': 157688, 'number': 8934, 'builderid': 7, 'buildrequestid': 157797, 'workerid': 8, 'masterid': 1, 'started_at': 1770155559, 'complete_at': 1770156358, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7623'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:38.030214	{'source': 'webserver', 'raw': {'buildid': 157752, 'number': 7625, 'builderid': 19, 'buildrequestid': 157861, 'workerid': 8, 'masterid': 1, 'started_at': 1770160237, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['32856fd15531890d65379376c8cadfc3611b36cb', 'Trigger'], 'pr_id': [13768, 'Trigger'], 'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'buildnumber': [7625, 'Build'], 'repository': ['', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'project': ['', 'Build'], 'branchname': ['pr-13768', 'Trigger'], 'shortrev': ['32856f', 'Trigger'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'builderid': [19, 'Builder'], 'branch': ['refs/pull/13768/head', 'Build'], 'codebase': ['', 'Build'], 'baserev': ['eac7b290423efea867c2c7f3f2b2792d05418bdd', 'Change'], 'workername': ['osx-m1', 'Worker'], 'revision': ['32856fd15531890d65379376c8cadfc3611b36cb', 'Build']}, 'buildrequest': {'buildrequestid': 157861, 'buildsetid': 36137, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160237, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770158129, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36137, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770158129, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157716, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10813, 'branch': 'refs/pull/13768/head', 'revision': '32856fd15531890d65379376c8cadfc3611b36cb', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770156379, 'patch': None}]}, 'parentbuild': {'buildid': 157716, 'number': 8936, 'builderid': 7, 'buildrequestid': 157831, 'workerid': 8, 'masterid': 1, 'started_at': 1770157334, 'complete_at': 1770158129, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7625'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:37.899701	{'source': 'webserver', 'raw': {'buildid': 157751, 'number': 7624, 'builderid': 19, 'buildrequestid': 157847, 'workerid': 8, 'masterid': 1, 'started_at': 1770160236, 'complete_at': 1770160237, 'locks_duration_s': 0, 'complete': True, 'state_string': 'failed update (failure)', 'results': 2, 'properties': {'headrev': ['a7301d7d406bc5955303a13e61327c5b17645b7a', 'Trigger'], 'pr_id': [13768, 'Trigger'], 'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'builderid': [19, 'Builder'], 'buildnumber': [7624, 'Build'], 'baserev': ['eac7b290423efea867c2c7f3f2b2792d05418bdd', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'repository': ['', 'Build'], 'project': ['', 'Build'], 'branchname': ['pr-13768', 'Trigger'], 'shortrev': ['a7301d', 'Trigger'], 'branch': ['refs/pull/13768/head', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'basedir': ['/Users/administrator/buildbot-worker/osx_m1', 'Worker'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'workername': ['osx-m1', 'Worker'], 'builddir': ['/Users/administrator/buildbot-worker/osx_m1/pr-fifoci-mvk-osx-m1', 'Worker'], 'revision': ['a7301d7d406bc5955303a13e61327c5b17645b7a', 'Build'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157847, 'buildsetid': 36135, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160236, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770157334, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36135, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770157334, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157701, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10812, 'branch': 'refs/pull/13768/head', 'revision': 'a7301d7d406bc5955303a13e61327c5b17645b7a', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770156283, 'patch': None}]}, 'parentbuild': {'buildid': 157701, 'number': 8935, 'builderid': 7, 'buildrequestid': 157817, 'workerid': 8, 'masterid': 1, 'started_at': 1770156358, 'complete_at': 1770157334, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7624'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:36.371211	{'source': 'webserver', 'raw': {'buildid': 157751, 'number': 7624, 'builderid': 19, 'buildrequestid': 157847, 'workerid': 8, 'masterid': 1, 'started_at': 1770160236, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'headrev': ['a7301d7d406bc5955303a13e61327c5b17645b7a', 'Trigger'], 'pr_id': [13768, 'Trigger'], 'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'builderid': [19, 'Builder'], 'buildnumber': [7624, 'Build'], 'baserev': ['eac7b290423efea867c2c7f3f2b2792d05418bdd', 'Change'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'repository': ['', 'Build'], 'project': ['', 'Build'], 'branchname': ['pr-13768', 'Trigger'], 'shortrev': ['a7301d', 'Trigger'], 'branch': ['refs/pull/13768/head', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'workername': ['osx-m1', 'Worker'], 'revision': ['a7301d7d406bc5955303a13e61327c5b17645b7a', 'Build'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157847, 'buildsetid': 36135, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160236, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770157334, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36135, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770157334, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157701, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10812, 'branch': 'refs/pull/13768/head', 'revision': 'a7301d7d406bc5955303a13e61327c5b17645b7a', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770156283, 'patch': None}]}, 'parentbuild': {'buildid': 157701, 'number': 8935, 'builderid': 7, 'buildrequestid': 157817, 'workerid': 8, 'masterid': 1, 'started_at': 1770156358, 'complete_at': 1770157334, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7624'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:31.848837	{'source': 'webserver', 'raw': {'buildid': 157750, 'number': 7623, 'builderid': 19, 'buildrequestid': 157827, 'workerid': 8, 'masterid': 1, 'started_at': 1770160221, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'branchname': ['pr-14317', 'Trigger'], 'repo': ['dolphin-emu/dolphin', 'Trigger'], 'buildnumber': [7623, 'Build'], 'project': ['', 'Build'], 'pr_id': [14317, 'Trigger'], 'workername': ['osx-m1', 'Worker'], 'codebase': ['', 'Build'], 'baserev': ['c7de9162765452fc2787f3ba4f0092394067d603', 'Change'], 'scheduler': ['pr-fifoci-osx', 'Scheduler'], 'branch': ['refs/pull/14317/head', 'Build'], 'owners': [['Central (on behalf of: JosJuice)'], 'Build'], 'shortrev': ['c81800', 'Trigger'], 'builderid': [19, 'Builder'], 'repository': ['', 'Build'], 'headrev': ['c8180071a552720e5436e908afc4eda7e87be08c', 'Trigger'], 'buildername': ['pr-fifoci-mvk-osx-m1', 'Builder'], 'revision': ['c8180071a552720e5436e908afc4eda7e87be08c', 'Build']}, 'buildrequest': {'buildrequestid': 157827, 'buildsetid': 36129, 'builderid': 19, 'priority': 0, 'claimed': True, 'claimed_at': 1770160221, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770156358, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36129, 'external_idstring': None, 'reason': "The Triggerable scheduler named 'pr-fifoci-osx' triggered this build", 'submitted_at': 1770156358, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': 157688, 'parent_relationship': 'Triggered from', 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10809, 'branch': 'refs/pull/14317/head', 'revision': 'c8180071a552720e5436e908afc4eda7e87be08c', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770066448, 'patch': None}]}, 'parentbuild': {'buildid': 157688, 'number': 8934, 'builderid': 7, 'buildrequestid': 157797, 'workerid': 8, 'masterid': 1, 'started_at': 1770155559, 'complete_at': 1770156358, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {}}, 'parentbuilder': {'builderid': 7, 'name': 'pr-osx-universal', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'builder': {'builderid': 19, 'name': 'pr-fifoci-mvk-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/19/builds/7623'}, 'type': 'raw_bb_hook'}
2026-02-03T23:10:21.372530	{'source': 'webserver', 'raw': {'buildid': 157749, 'number': 2168, 'builderid': 17, 'buildrequestid': 157870, 'workerid': 8, 'masterid': 1, 'started_at': 1770160124, 'complete_at': 1770160221, 'locks_duration_s': 0, 'complete': True, 'state_string': 'build successful', 'results': 0, 'properties': {'shortrev': ['2512-272', 'Change'], 'description': ['Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'Change'], 'buildername': ['fifoci-mtl-osx-m1', 'Builder'], 'workername': ['osx-m1', 'Worker'], 'branchname': ['master', 'Change'], 'author': ['Jordan Woyak', 'Change'], 'scheduler': ['dev', 'Scheduler'], 'builderid': [17, 'Builder'], 'buildnumber': [2168, 'Build'], 'project': ['', 'Build'], 'basedir': ['/Users/administrator/buildbot-worker/osx_m1', 'Worker'], 'warnings-count': [5, 'WarningCountingShellCommand'], 'branch': ['master', 'Build'], 'builddir': ['/Users/administrator/buildbot-worker/osx_m1/fifoci-mtl-osx-m1', 'Worker'], 'repository': ['', 'Build'], 'owners': [['Jordan Woyak'], 'Build'], 'revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Build'], 'codebase': ['', 'Build'], 'got_revision': ['ca98b3030bcc70e67158ce899a5589aa2ec65d32', 'GitNoBranch']}, 'buildrequest': {'buildrequestid': 157870, 'buildsetid': 36141, 'builderid': 17, 'priority': 0, 'claimed': True, 'claimed_at': 1770160124, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770159056, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36141, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'dev' triggered this build", 'submitted_at': 1770159056, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10815, 'branch': 'master', 'revision': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770159056, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 17, 'name': 'fifoci-mtl-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/17/builds/2168'}, 'type': 'raw_bb_hook'}
2026-02-03T23:08:44.615332	{'source': 'webserver', 'raw': {'buildid': 157749, 'number': 2168, 'builderid': 17, 'buildrequestid': 157870, 'workerid': 8, 'masterid': 1, 'started_at': 1770160124, 'complete_at': None, 'locks_duration_s': 0, 'complete': False, 'state_string': 'starting', 'results': None, 'properties': {'shortrev': ['2512-272', 'Change'], 'description': ['Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'Change'], 'buildername': ['fifoci-mtl-osx-m1', 'Builder'], 'workername': ['osx-m1', 'Worker'], 'branchname': ['master', 'Change'], 'author': ['Jordan Woyak', 'Change'], 'scheduler': ['dev', 'Scheduler'], 'builderid': [17, 'Builder'], 'buildnumber': [2168, 'Build'], 'project': ['', 'Build'], 'branch': ['master', 'Build'], 'repository': ['', 'Build'], 'revision': ['dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'Build'], 'codebase': ['', 'Build']}, 'buildrequest': {'buildrequestid': 157870, 'buildsetid': 36141, 'builderid': 17, 'priority': 0, 'claimed': True, 'claimed_at': 1770160124, 'claimed_by_masterid': 1, 'complete': False, 'results': -1, 'submitted_at': 1770159056, 'complete_at': None, 'waited_for': False, 'properties': None}, 'buildset': {'bsid': 36141, 'external_idstring': None, 'reason': "The AnyBranchScheduler scheduler named 'dev' triggered this build", 'submitted_at': 1770159056, 'complete': False, 'complete_at': None, 'results': -1, 'parent_buildid': None, 'parent_relationship': None, 'rebuilt_buildid': None, 'sourcestamps': [{'ssid': 10815, 'branch': 'master', 'revision': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'project': '', 'repository': '', 'codebase': '', 'created_at': 1770159056, 'patch': None}]}, 'parentbuild': None, 'parentbuilder': None, 'builder': {'builderid': 17, 'name': 'fifoci-mtl-osx-m1', 'masterids': [1], 'description': None, 'description_format': None, 'description_html': None, 'projectid': None, 'tags': []}, 'url': 'https://dolphin.ci/#/builders/17/builds/2168'}, 'type': 'raw_bb_hook'}

Recent 'raw_gh_hook' events

2026-02-03T23:32:40.513253	{'source': 'webserver', 'gh_type': 'pull_request', 'raw': {'action': 'opened', 'number': 14318, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14318', 'id': 3243403084, 'node_id': 'PR_kwDOALCn2M7BUmdM', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14318', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14318.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14318.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14318', 'number': 14318, 'state': 'open', 'locked': False, 'title': 'Core: Make RunOnCPUThread always non-blocking.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'Followup to #14127.\r\n\r\nAll `RunOnCPUThread` calls are already non-blocking.\r\nThis PR removes the `wait_for_completion` parameter.\r\n\r\nThe existing `CPUThreadGuard` is a cleaner way to run code on the CPU thread in a blocking manner.', 'created_at': '2026-02-03T23:32:37Z', 'updated_at': '2026-02-03T23:32:37Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': None, 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14318/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14318/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14318/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/201aa659066b41a2cfe89b615213bcb093fcbe79', 'head': {'label': 'jordan-woyak:RunOnCPUThread-always-non-blocking', 'ref': 'RunOnCPUThread-always-non-blocking', 'sha': '201aa659066b41a2cfe89b615213bcb093fcbe79', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 16818408, 'node_id': 'MDEwOlJlcG9zaXRvcnkxNjgxODQwOA==', 'name': 'dolphin', 'full_name': 'jordan-woyak/dolphin', 'private': False, 'owner': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/jordan-woyak/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/jordan-woyak/dolphin', 'forks_url': 'https://api.github.com/repos/jordan-woyak/dolphin/forks', 'keys_url': 'https://api.github.com/repos/jordan-woyak/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/jordan-woyak/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/jordan-woyak/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/jordan-woyak/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/jordan-woyak/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/jordan-woyak/dolphin/events', 'assignees_url': 'https://api.github.com/repos/jordan-woyak/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/jordan-woyak/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/jordan-woyak/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/jordan-woyak/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/jordan-woyak/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/jordan-woyak/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/jordan-woyak/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/jordan-woyak/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/jordan-woyak/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/jordan-woyak/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/jordan-woyak/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/jordan-woyak/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/jordan-woyak/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/jordan-woyak/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/jordan-woyak/dolphin/merges', 'archive_url': 'https://api.github.com/repos/jordan-woyak/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/jordan-woyak/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/jordan-woyak/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/jordan-woyak/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/jordan-woyak/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/jordan-woyak/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/jordan-woyak/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/jordan-woyak/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/jordan-woyak/dolphin/deployments', 'created_at': '2014-02-13T21:50:38Z', 'updated_at': '2026-01-31T21:37:22Z', 'pushed_at': '2026-02-03T23:30:37Z', 'git_url': 'git://github.com/jordan-woyak/dolphin.git', 'ssh_url': 'git@github.com:jordan-woyak/dolphin.git', 'clone_url': 'https://github.com/jordan-woyak/dolphin.git', 'svn_url': 'https://github.com/jordan-woyak/dolphin', 'homepage': None, 'size': 510087, 'stargazers_count': 4, 'watchers_count': 4, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': False, 'has_pages': False, 'has_discussions': False, 'forks_count': 5, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 2, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 5, 'open_issues': 2, 'watchers': 4, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': True, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T22:50:57Z', 'pushed_at': '2026-02-03T22:50:52Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14318'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14318'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14318'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14318/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14318/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14318/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/201aa659066b41a2cfe89b615213bcb093fcbe79'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None, 'merged': False, 'mergeable': None, 'rebaseable': None, 'mergeable_state': 'unknown', 'merged_by': None, 'comments': 0, 'review_comments': 0, 'maintainer_can_modify': True, 'commits': 1, 'additions': 102, 'deletions': 149, 'changed_files': 4}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T22:50:57Z', 'pushed_at': '2026-02-03T22:50:52Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T22:50:55.572873	{'source': 'webserver', 'gh_type': 'check_suite', 'raw': {'action': 'requested', 'check_suite': {'id': 56318013676, 'node_id': 'CS_kwDOALCn2M8AAAANHNCw7A', 'head_branch': 'master', 'head_sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'status': 'queued', 'conclusion': None, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/check-suites/56318013676', 'before': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'after': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'pull_requests': [{'url': 'https://api.github.com/repos/MarioPartyNetplay/Dolphin-MPN/pulls/191', 'id': 3229846782, 'number': 191, 'head': {'ref': 'master', 'sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': 'bb679f420d6ea68263904009bebf3e8905f5c8e1', 'repo': {'id': 623850328, 'url': 'https://api.github.com/repos/MarioPartyNetplay/Dolphin-MPN', 'name': 'Dolphin-MPN'}}}, {'url': 'https://api.github.com/repos/Faster-Brawl/dolphin/pulls/5', 'id': 1222541325, 'number': 5, 'head': {'ref': 'master', 'sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': 'eb9e232680dfdc04ec2adab05dbc530b1e707fc9', 'repo': {'id': 577809188, 'url': 'https://api.github.com/repos/Faster-Brawl/dolphin', 'name': 'dolphin'}}}, {'url': 'https://api.github.com/repos/dirextric-auto/dolphin/pulls/1', 'id': 571486662, 'number': 1, 'head': {'ref': 'master', 'sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': '1e71904cb9930eccc036e2ce2360beaa746befa7', 'repo': {'id': 337848960, 'url': 'https://api.github.com/repos/dirextric-auto/dolphin', 'name': 'dolphin'}}}, {'url': 'https://api.github.com/repos/zurgeg/dolphin-vr-no-ovr/pulls/1', 'id': 518053317, 'number': 1, 'head': {'ref': 'master', 'sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'VR-Hydra', 'sha': 'ea9f95955e5335cc238b2f9c5a0a6dfdc8698aec', 'repo': {'id': 311469400, 'url': 'https://api.github.com/repos/zurgeg/dolphin-vr-no-ovr', 'name': 'dolphin-vr-no-ovr'}}}, {'url': 'https://api.github.com/repos/weihuoya/dolphin/pulls/1', 'id': 400884538, 'number': 1, 'head': {'ref': 'master', 'sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': '0f4c971326ae9389b3ad55b0fefacb708d148f4d', 'repo': {'id': 143011855, 'url': 'https://api.github.com/repos/weihuoya/dolphin', 'name': 'dolphin'}}}], 'app': {'id': 49947, 'client_id': 'Iv1.1fcaea7644d8b727', 'slug': 'dolphin-ci', 'node_id': 'MDM6QXBwNDk5NDc=', 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'name': 'Dolphin CI', 'description': 'Continuous Integration setup for [dolphin-emu.org](https://dolphin-emu.org/).', 'external_url': 'https://github.com/dolphin-emu', 'html_url': 'https://github.com/apps/dolphin-ci', 'created_at': '2019-12-26T22:26:07Z', 'updated_at': '2019-12-26T22:33:19Z', 'permissions': {'checks': 'write', 'contents': 'read', 'issues': 'write', 'members': 'read', 'metadata': 'read', 'pull_requests': 'write', 'statuses': 'write'}, 'events': ['check_run', 'commit_comment', 'issue_comment', 'pull_request', 'pull_request_review', 'pull_request_review_comment', 'push']}, 'created_at': '2026-02-03T22:50:54Z', 'updated_at': '2026-02-03T22:50:54Z', 'rerequestable': True, 'runs_rerequestable': True, 'latest_check_runs_count': 0, 'check_runs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/check-suites/56318013676/check-runs', 'head_commit': {'id': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'tree_id': '5355c7878fc942b9b9ed79f2b9ca09923c5a8557', 'message': 'Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'timestamp': '2026-02-03T22:50:52Z', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com'}, 'committer': {'name': 'GitHub', 'email': 'noreply@github.com'}}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T22:50:52Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 359, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 359, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T22:50:54.523623	{'source': 'webserver', 'gh_type': 'push', 'raw': {'ref': 'refs/heads/master', 'before': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'after': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'name': 'dolphin-emu', 'email': None, 'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': 1374484077, 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': 1770159052, 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 359, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 359, 'watchers': 14582, 'default_branch': 'master', 'stargazers': 14582, 'master_branch': 'master', 'organization': 'dolphin-emu', 'custom_properties': {}}, 'pusher': {'name': 'jordan-woyak', 'email': 'jordan.woyak@gmail.com'}, 'forced': False, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}, 'created': False, 'deleted': False, 'base_ref': None, 'compare': 'https://github.com/dolphin-emu/dolphin/compare/eac7b290423e...dd2b94cd4a71', 'commits': [{'id': '0ba32f7add97daf1c5ea52cf77b75e81d862676f', 'tree_id': '88a4225db26349f7b326a0b597725cc2f6ab4299', 'distinct': False, 'message': 'Android: Detect when native code should flush unsaved data', 'timestamp': '2026-01-19T21:56:09-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/0ba32f7add97daf1c5ea52cf77b75e81d862676f', 'author': {'name': 'JosJuice', 'email': 'josjuice@gmail.com', 'date': '2025-04-26T10:33:01+02:00', 'username': 'JosJuice'}, 'committer': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-01-19T21:56:09-06:00', 'username': 'jordan-woyak'}, 'added': [], 'removed': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt', 'Source/Android/jni/ActivityTracker.cpp', 'Source/Core/UICommon/UICommon.cpp', 'Source/Core/UICommon/UICommon.h']}, {'id': 'b0652925faa54b9bfd5a17cfed8870a2b1f2759b', 'tree_id': '20229c7e3b6735332e4f0938d442c76c720b186c', 'distinct': False, 'message': 'Core: Add a HookableEvent for UICommon::FlushUnsavedData.', 'timestamp': '2026-01-19T21:56:09-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/b0652925faa54b9bfd5a17cfed8870a2b1f2759b', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2025-04-26T17:18:56-05:00', 'username': 'jordan-woyak'}, 'committer': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-01-19T21:56:09-06:00', 'username': 'jordan-woyak'}, 'added': [], 'removed': [], 'modified': ['Source/Core/UICommon/UICommon.cpp', 'Source/Core/UICommon/UICommon.h']}, {'id': '1d9e475123346c0e83289e788bdfad9f3345b512', 'tree_id': 'b81f11362e50f3c1379a757dc655556bdc4749c8', 'distinct': False, 'message': 'Common: Add TransferableSharedMutex class and unit tests.', 'timestamp': '2026-01-19T21:56:59-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/1d9e475123346c0e83289e788bdfad9f3345b512', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2025-10-19T20:31:22-05:00', 'username': 'jordan-woyak'}, 'committer': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-01-19T21:56:59-06:00', 'username': 'jordan-woyak'}, 'added': ['Source/Core/Common/TransferableSharedMutex.h'], 'removed': [], 'modified': ['Source/Core/Common/CMakeLists.txt', 'Source/Core/DolphinLib.props', 'Source/UnitTests/Common/MutexTest.cpp']}, {'id': '2322437f96bc53f1edcd9100daedb5b54c6e0fc4', 'tree_id': '256962a703953a1eb54f978c4bc0fe3e41054806', 'distinct': False, 'message': 'State: Simplify interthread communication and cleanups. Save/Load calls are now always non-blocking for the caller, but appropriately block the CPU thread as needed.', 'timestamp': '2026-01-19T21:56:59-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/2322437f96bc53f1edcd9100daedb5b54c6e0fc4', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2025-11-02T22:21:15-06:00', 'username': 'jordan-woyak'}, 'committer': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-01-19T21:56:59-06:00', 'username': 'jordan-woyak'}, 'added': [], 'removed': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt', 'Source/Android/jni/MainAndroid.cpp', 'Source/Core/Core/State.cpp', 'Source/Core/Core/State.h']}, {'id': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'tree_id': '5355c7878fc942b9b9ed79f2b9ca09923c5a8557', 'distinct': True, 'message': 'Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'timestamp': '2026-02-03T16:50:52-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-02-03T16:50:52-06:00', 'username': 'jordan-woyak'}, 'committer': {'name': 'GitHub', 'email': 'noreply@github.com', 'date': '2026-02-03T16:50:52-06:00', 'username': 'web-flow'}, 'added': ['Source/Core/Common/TransferableSharedMutex.h'], 'removed': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt', 'Source/Android/jni/ActivityTracker.cpp', 'Source/Android/jni/MainAndroid.cpp', 'Source/Core/Common/CMakeLists.txt', 'Source/Core/Core/State.cpp', 'Source/Core/Core/State.h', 'Source/Core/DolphinLib.props', 'Source/Core/UICommon/UICommon.cpp', 'Source/Core/UICommon/UICommon.h', 'Source/UnitTests/Common/MutexTest.cpp']}], 'head_commit': {'id': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'tree_id': '5355c7878fc942b9b9ed79f2b9ca09923c5a8557', 'distinct': True, 'message': 'Merge pull request #13594 from jordan-woyak/state-cleanups\n\nState: Simplify interthread communication and general cleanups.', 'timestamp': '2026-02-03T16:50:52-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-02-03T16:50:52-06:00', 'username': 'jordan-woyak'}, 'committer': {'name': 'GitHub', 'email': 'noreply@github.com', 'date': '2026-02-03T16:50:52-06:00', 'username': 'web-flow'}, 'added': ['Source/Core/Common/TransferableSharedMutex.h'], 'removed': [], 'modified': ['Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt', 'Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt', 'Source/Android/jni/ActivityTracker.cpp', 'Source/Android/jni/MainAndroid.cpp', 'Source/Core/Common/CMakeLists.txt', 'Source/Core/Core/State.cpp', 'Source/Core/Core/State.h', 'Source/Core/DolphinLib.props', 'Source/Core/UICommon/UICommon.cpp', 'Source/Core/UICommon/UICommon.h', 'Source/UnitTests/Common/MutexTest.cpp']}}, 'type': 'raw_gh_hook'}
2026-02-03T22:50:54.189879	{'source': 'webserver', 'gh_type': 'pull_request', 'raw': {'action': 'closed', 'number': 13594, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13594', 'id': 2482865278, 'node_id': 'PR_kwDOALCn2M6T_YR-', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13594', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13594.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13594.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13594', 'number': 13594, 'state': 'closed', 'locked': False, 'title': 'State: Simplify interthread communication and general cleanups.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '### Simplified interthread communication\r\nThere was a lot of hard-to-validate interthread communication going on in State.cpp.\r\nIt\'s now less complicated.\r\ne.g. there were four `std::mutex`, now there\'s one mutex.\r\n\r\n### Made saves not block the UI\r\nThere\'s no longer a `wait` parameter on Save functions.\r\nThe calls are now always non-blocking for the UI thread.\r\nWaiting on completion of writes will happen automatically when attempting to read them later.\r\n\r\n### Fixed write-followed-by-read race\r\nFixed the race when saving and immediately trying to use the written data.\r\nSave followed by Load will now make Core wait.\r\nIf the UI tries to read states (for timestamps), it block appropriately. (it doesn\'t block until you open the state menu)\r\n\r\nBecause the tasks generally start on the UI thread, move to the Core thread, and finally a worker thread, I\'ve created a `TransferableSharedMutex` class to track the completion of these tasks.\r\nI\'ve added unit tests for the new class.\r\nA normal `std::shared_mutex` can\'t be locked on one thread and unlocked on another.\r\n\r\n### Made saves marginally faster\r\nThe explicit "Measure" step of `PointerWrap` is now skipped by just doing "Write" on a buffer sized 110% of the last state.\r\n`PointerWrap` conveniently switches to "Measure" mode on buffer overrun.\r\nOn would-be overrun that becomes the measurement for a secondary real "Write".\r\nThat saves like ~0.3ms per state save here. :)\r\n\r\n### Wait for state save flush on Android process termination\r\nThanks to Jos for providing the means to connect this to the android side of things.', 'created_at': '2025-04-26T04:59:35Z', 'updated_at': '2026-02-03T22:50:52Z', 'closed_at': '2026-02-03T22:50:52Z', 'merged_at': '2026-02-03T22:50:52Z', 'merge_commit_sha': 'dd2b94cd4a71fb6abda72f58d27434ef2d8b0a5f', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13594/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13594/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13594/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/2322437f96bc53f1edcd9100daedb5b54c6e0fc4', 'head': {'label': 'jordan-woyak:state-cleanups', 'ref': 'state-cleanups', 'sha': '2322437f96bc53f1edcd9100daedb5b54c6e0fc4', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 16818408, 'node_id': 'MDEwOlJlcG9zaXRvcnkxNjgxODQwOA==', 'name': 'dolphin', 'full_name': 'jordan-woyak/dolphin', 'private': False, 'owner': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/jordan-woyak/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/jordan-woyak/dolphin', 'forks_url': 'https://api.github.com/repos/jordan-woyak/dolphin/forks', 'keys_url': 'https://api.github.com/repos/jordan-woyak/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/jordan-woyak/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/jordan-woyak/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/jordan-woyak/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/jordan-woyak/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/jordan-woyak/dolphin/events', 'assignees_url': 'https://api.github.com/repos/jordan-woyak/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/jordan-woyak/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/jordan-woyak/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/jordan-woyak/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/jordan-woyak/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/jordan-woyak/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/jordan-woyak/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/jordan-woyak/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/jordan-woyak/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/jordan-woyak/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/jordan-woyak/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/jordan-woyak/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/jordan-woyak/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/jordan-woyak/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/jordan-woyak/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/jordan-woyak/dolphin/merges', 'archive_url': 'https://api.github.com/repos/jordan-woyak/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/jordan-woyak/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/jordan-woyak/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/jordan-woyak/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/jordan-woyak/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/jordan-woyak/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/jordan-woyak/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/jordan-woyak/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/jordan-woyak/dolphin/deployments', 'created_at': '2014-02-13T21:50:38Z', 'updated_at': '2026-01-31T21:37:22Z', 'pushed_at': '2026-02-01T21:57:35Z', 'git_url': 'git://github.com/jordan-woyak/dolphin.git', 'ssh_url': 'git@github.com:jordan-woyak/dolphin.git', 'clone_url': 'https://github.com/jordan-woyak/dolphin.git', 'svn_url': 'https://github.com/jordan-woyak/dolphin', 'homepage': None, 'size': 510087, 'stargazers_count': 4, 'watchers_count': 4, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': False, 'has_pages': False, 'has_discussions': False, 'forks_count': 5, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 2, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 5, 'open_issues': 2, 'watchers': 4, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': True, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': '2a771937cf0a4a45cd466d3eea089a083f73f3c0', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 359, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 359, 'watchers': 14582, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13594'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13594'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13594'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13594/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13594/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13594/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/2322437f96bc53f1edcd9100daedb5b54c6e0fc4'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None, 'merged': True, 'mergeable': None, 'rebaseable': None, 'mergeable_state': 'unknown', 'merged_by': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'comments': 0, 'review_comments': 41, 'maintainer_can_modify': False, 'commits': 4, 'additions': 582, 'deletions': 375, 'changed_files': 14}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 359, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 359, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T22:48:25.697653	{'source': 'webserver', 'gh_type': 'pull_request_review', 'raw': {'action': 'submitted', 'review': {'id': 3748007210, 'node_id': 'PRR_kwDOALCn2M7fZg0q', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Code LGTM from a C++ standpoint. I can't say anything about the JIT code. Tested.", 'commit_id': '32856fd15531890d65379376c8cadfc3611b36cb', 'submitted_at': '2026-02-03T22:48:22Z', 'state': 'approved', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768#pullrequestreview-3748007210', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', '_links': {'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768#pullrequestreview-3748007210'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}}, 'updated_at': '2026-02-03T22:48:22Z', 'author_association': 'MEMBER'}, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', 'id': 2608905795, 'node_id': 'PR_kwDOALCn2M6bgL5D', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768', 'number': 13768, 'state': 'open', 'locked': False, 'title': 'Core: Create fastmem mappings for page address translation', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Previously we've only been setting up fastmem mappings for block address translation, but now we also do it for page address translation. This increases performance when games access memory using page tables, but decreases performance when games set up page tables.\r\n\r\nThe tlbie instruction is used as an indication that the mappings need to be updated.\r\n\r\nThere is the accuracy downside that the TLB is now effectively infinitely large. No games are known to be affected by this, and you still get the old, more accurate behavior if Enable Write-Back Cache is on.\r\n\r\nLeft to do:\r\n\r\n- [x] Support for host page sizes larger than 4K\r\n- [x] Support for host page sizes larger than 4K is untested\r\n- [x] macOS is untested\r\n- [x] Rogue Squadron 3 is super slow due to the pessimistic setting of R and C bits (I guess the game is heavily swapping? DoJit is running very often)\r\n- [x] Unit tests\r\n- [x] Unit tests fail on the Ubuntu and Debian runners", 'created_at': '2025-06-21T09:20:23Z', 'updated_at': '2026-02-03T22:48:22Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': '991d76dcffb09f237a458ac540055d13ae1335ad', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/32856fd15531890d65379376c8cadfc3611b36cb', 'head': {'label': 'JosJuice:page-table-fastmem-2', 'ref': 'page-table-fastmem-2', 'sha': '32856fd15531890d65379376c8cadfc3611b36cb', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-03T22:35:15Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509732, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/32856fd15531890d65379376c8cadfc3611b36cb'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T22:35:19.459931	{'source': 'webserver', 'gh_type': 'pull_request', 'raw': {'action': 'synchronize', 'number': 14218, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218', 'id': 3099870143, 'node_id': 'PR_kwDOALCn2M64xEO_', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14218', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14218.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14218.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218', 'number': 14218, 'state': 'open', 'locked': False, 'title': 'Jit64: Make ABI_CallFunction generic', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'This replaces all the hand-coded variants of ABI_CallFunction with a single generic function. It handles all existing cases, plus many new cases that would have been annoying to hand-code.\r\n\r\nThe implementation is based on our AArch64 ABI_CallFunction, but adapted for x64. The most notable differences are support for memory operands and a new way for callers to specify the sizes of operands.', 'created_at': '2025-12-14T13:50:33Z', 'updated_at': '2026-02-03T22:35:17Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': 'f86a133381f5a524545ebeff9efa0a63c20ca31f', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'head': {'label': 'JosJuice:jit64-abi-call-function-parameters', 'ref': 'jit64-abi-call-function-parameters', 'sha': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-03T22:35:15Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509732, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14218'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/72a6ad6fac50dccf5f37921497b92e75e84bbf40'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None, 'merged': False, 'mergeable': None, 'rebaseable': None, 'mergeable_state': 'unknown', 'merged_by': None, 'comments': 1, 'review_comments': 6, 'maintainer_can_modify': True, 'commits': 1, 'additions': 793, 'deletions': 298, 'changed_files': 22}, 'before': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'after': '72a6ad6fac50dccf5f37921497b92e75e84bbf40', 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T22:06:12.686146	{'source': 'webserver', 'gh_type': 'pull_request', 'raw': {'action': 'synchronize', 'number': 13768, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', 'id': 2608905795, 'node_id': 'PR_kwDOALCn2M6bgL5D', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768', 'number': 13768, 'state': 'open', 'locked': False, 'title': 'Core: Create fastmem mappings for page address translation', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Previously we've only been setting up fastmem mappings for block address translation, but now we also do it for page address translation. This increases performance when games access memory using page tables, but decreases performance when games set up page tables.\r\n\r\nThe tlbie instruction is used as an indication that the mappings need to be updated.\r\n\r\nThere is the accuracy downside that the TLB is now effectively infinitely large. No games are known to be affected by this, and you still get the old, more accurate behavior if Enable Write-Back Cache is on.\r\n\r\nLeft to do:\r\n\r\n- [x] Support for host page sizes larger than 4K\r\n- [x] Support for host page sizes larger than 4K is untested\r\n- [x] macOS is untested\r\n- [x] Rogue Squadron 3 is super slow due to the pessimistic setting of R and C bits (I guess the game is heavily swapping? DoJit is running very often)\r\n- [x] Unit tests\r\n- [x] Unit tests fail on the Ubuntu and Debian runners", 'created_at': '2025-06-21T09:20:23Z', 'updated_at': '2026-02-03T22:06:10Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': '52a7548520f780d3f393f825e772cba718f412db', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/32856fd15531890d65379376c8cadfc3611b36cb', 'head': {'label': 'JosJuice:page-table-fastmem-2', 'ref': 'page-table-fastmem-2', 'sha': '32856fd15531890d65379376c8cadfc3611b36cb', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-03T22:06:09Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509732, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/32856fd15531890d65379376c8cadfc3611b36cb'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None, 'merged': False, 'mergeable': None, 'rebaseable': None, 'mergeable_state': 'unknown', 'merged_by': None, 'comments': 23, 'review_comments': 14, 'maintainer_can_modify': True, 'commits': 10, 'additions': 2043, 'deletions': 153, 'changed_files': 33}, 'before': 'a7301d7d406bc5955303a13e61327c5b17645b7a', 'after': '32856fd15531890d65379376c8cadfc3611b36cb', 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T22:04:37.178288	{'source': 'webserver', 'gh_type': 'pull_request', 'raw': {'action': 'synchronize', 'number': 13768, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', 'id': 2608905795, 'node_id': 'PR_kwDOALCn2M6bgL5D', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768', 'number': 13768, 'state': 'open', 'locked': False, 'title': 'Core: Create fastmem mappings for page address translation', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Previously we've only been setting up fastmem mappings for block address translation, but now we also do it for page address translation. This increases performance when games access memory using page tables, but decreases performance when games set up page tables.\r\n\r\nThe tlbie instruction is used as an indication that the mappings need to be updated.\r\n\r\nThere is the accuracy downside that the TLB is now effectively infinitely large. No games are known to be affected by this, and you still get the old, more accurate behavior if Enable Write-Back Cache is on.\r\n\r\nLeft to do:\r\n\r\n- [x] Support for host page sizes larger than 4K\r\n- [x] Support for host page sizes larger than 4K is untested\r\n- [x] macOS is untested\r\n- [x] Rogue Squadron 3 is super slow due to the pessimistic setting of R and C bits (I guess the game is heavily swapping? DoJit is running very often)\r\n- [x] Unit tests\r\n- [x] Unit tests fail on the Ubuntu and Debian runners", 'created_at': '2025-06-21T09:20:23Z', 'updated_at': '2026-02-03T22:04:35Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': '92006fa2785886984eace093208386946a3d5c9f', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/a7301d7d406bc5955303a13e61327c5b17645b7a', 'head': {'label': 'JosJuice:page-table-fastmem-2', 'ref': 'page-table-fastmem-2', 'sha': 'a7301d7d406bc5955303a13e61327c5b17645b7a', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-03T22:04:34Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509732, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/a7301d7d406bc5955303a13e61327c5b17645b7a'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None, 'merged': False, 'mergeable': None, 'rebaseable': None, 'mergeable_state': 'unknown', 'merged_by': None, 'comments': 23, 'review_comments': 14, 'maintainer_can_modify': True, 'commits': 10, 'additions': 2042, 'deletions': 153, 'changed_files': 33}, 'before': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'after': 'a7301d7d406bc5955303a13e61327c5b17645b7a', 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T21:10:07.303456	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'id': 3455745903, 'node_id': 'PR_kwDOALCn2M6qnqKV', 'number': 13970, 'title': 'WiimoteReal: Windows improvements.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'closed', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 27, 'created_at': '2025-09-26T04:25:44Z', 'updated_at': '2026-02-03T21:10:04Z', 'closed_at': '2025-10-13T06:54:54Z', 'author_association': 'MEMBER', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13970', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.patch', 'merged_at': '2025-10-13T06:54:54Z'}, 'body': 'Majorly overhauled the Windows Wii remote code.\r\n\r\nThis PR should be ready now.\r\n\r\nStatically link with "Bthprops.lib"\r\nDynamic linking was introduced in https://github.com/dolphin-emu/dolphin/commit/3acf0eb3af98009cc4e00bd9bcf43873dc26c4da to make Dolphin run under Wine.\r\nMy search results imply that Wine has long since added stubs for these functions so I\'m hoping it works fine now?\r\nedit: Wine seems to work fine.\r\n\r\nA 10 year old comment describes a workaround needed for the Toshiba Bluetooth stack.\r\nI\'ve removed the workaround.\r\n\r\nRemoved `HidP_SetOutputReport` fallback (Windows 7 workaround).\r\n\r\nFixed buggy overlapped Read/Write event usage (probably my fault from long ago).\r\n\r\nAdded checks for a Wii remote device name on the HID interface to only attempt connecting to actual Wii remotes devices.\r\nThe existing code just throws Wii remote data at every interface and checks for sane replies.\r\n\r\nAdded a periodic write to test for disconnected remotes in a timely manner.\r\n\r\nChanged the `Refresh` button to not remove authenticated remotes.\r\nSome users found it annoying.\r\nIt now only removes unauthenticated disconnected remotes.\r\n\r\nAdded `Sync` and `Reset` actions to establish and remove pairings.\r\nThe `Refresh` button is now a `QToolButton` with additional actions (only on Windows).\r\n<img width="120" height="177" alt="image" src="https://github.com/user-attachments/assets/16e57431-1eab-4465-a4f8-856188b0f942" />\r\n\r\nPairing a remote with the `Sync` button will enable it to reestablish a connection on any button press.\r\nIt currently shows an ugly animation while the task runs async.\r\n<img width="189" height="72" alt="image" src="https://github.com/user-attachments/assets/076191ce-8312-4cdc-8223-14a4bb9eca2d" />\r\nIt\'s better than nothing and can be visually improved later.\r\n\r\n`Refresh` or `Continuous Scanning` are no longer required to connect paired remotes.\r\nPairing with Windows or the DolphinBar "just works" by assigning a slot to `Real Wii Remote`.\r\n\r\nAdditionally, I am constantly bombarded with ControllerInterface / WiimotePool related deadlocks.\r\nThis seems like an existing issue that fixing properly is probably going to take another PR.\r\nIt seems like it\'s SDL?\r\nedit: SDL\'s DirectInput handling has been disabled in #13982 \r\n\r\nTODO\r\n - [x] Make the `Sync` button not require also clicking `Refresh`.\r\n - [x] Make paired + reconnected Wii remotes work without using `Refresh`\r\n - [x] Test Dolphin with Wine (just in general, not Wii remote functionality)\r\n - [x] Clean up log spam from continuous opening of HID interfaces.\r\n - [x] Make the Sync / Reset buttons display some kind kind of feedback.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/reactions', 'total_count': 4, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 1, 'confused': 0, 'heart': 3, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843701295', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843701295', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'id': 3843701295, 'node_id': 'IC_kwDOALCn2M7lGjov', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T21:10:04Z', 'updated_at': '2026-02-03T21:10:04Z', 'body': "> Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI don't know. I haven't experienced that.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843701295/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T21:01:13.944102	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'edited', 'changes': {'body': {'from': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. "}}, 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'id': 3455745903, 'node_id': 'PR_kwDOALCn2M6qnqKV', 'number': 13970, 'title': 'WiimoteReal: Windows improvements.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'closed', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 26, 'created_at': '2025-09-26T04:25:44Z', 'updated_at': '2026-02-03T20:58:41Z', 'closed_at': '2025-10-13T06:54:54Z', 'author_association': 'MEMBER', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13970', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.patch', 'merged_at': '2025-10-13T06:54:54Z'}, 'body': 'Majorly overhauled the Windows Wii remote code.\r\n\r\nThis PR should be ready now.\r\n\r\nStatically link with "Bthprops.lib"\r\nDynamic linking was introduced in https://github.com/dolphin-emu/dolphin/commit/3acf0eb3af98009cc4e00bd9bcf43873dc26c4da to make Dolphin run under Wine.\r\nMy search results imply that Wine has long since added stubs for these functions so I\'m hoping it works fine now?\r\nedit: Wine seems to work fine.\r\n\r\nA 10 year old comment describes a workaround needed for the Toshiba Bluetooth stack.\r\nI\'ve removed the workaround.\r\n\r\nRemoved `HidP_SetOutputReport` fallback (Windows 7 workaround).\r\n\r\nFixed buggy overlapped Read/Write event usage (probably my fault from long ago).\r\n\r\nAdded checks for a Wii remote device name on the HID interface to only attempt connecting to actual Wii remotes devices.\r\nThe existing code just throws Wii remote data at every interface and checks for sane replies.\r\n\r\nAdded a periodic write to test for disconnected remotes in a timely manner.\r\n\r\nChanged the `Refresh` button to not remove authenticated remotes.\r\nSome users found it annoying.\r\nIt now only removes unauthenticated disconnected remotes.\r\n\r\nAdded `Sync` and `Reset` actions to establish and remove pairings.\r\nThe `Refresh` button is now a `QToolButton` with additional actions (only on Windows).\r\n<img width="120" height="177" alt="image" src="https://github.com/user-attachments/assets/16e57431-1eab-4465-a4f8-856188b0f942" />\r\n\r\nPairing a remote with the `Sync` button will enable it to reestablish a connection on any button press.\r\nIt currently shows an ugly animation while the task runs async.\r\n<img width="189" height="72" alt="image" src="https://github.com/user-attachments/assets/076191ce-8312-4cdc-8223-14a4bb9eca2d" />\r\nIt\'s better than nothing and can be visually improved later.\r\n\r\n`Refresh` or `Continuous Scanning` are no longer required to connect paired remotes.\r\nPairing with Windows or the DolphinBar "just works" by assigning a slot to `Real Wii Remote`.\r\n\r\nAdditionally, I am constantly bombarded with ControllerInterface / WiimotePool related deadlocks.\r\nThis seems like an existing issue that fixing properly is probably going to take another PR.\r\nIt seems like it\'s SDL?\r\nedit: SDL\'s DirectInput handling has been disabled in #13982 \r\n\r\nTODO\r\n - [x] Make the `Sync` button not require also clicking `Refresh`.\r\n - [x] Make paired + reconnected Wii remotes work without using `Refresh`\r\n - [x] Test Dolphin with Wine (just in general, not Wii remote functionality)\r\n - [x] Clean up log spam from continuous opening of HID interfaces.\r\n - [x] Make the Sync / Reset buttons display some kind kind of feedback.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/reactions', 'total_count': 4, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 1, 'confused': 0, 'heart': 3, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843649310', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'id': 3843649310, 'node_id': 'IC_kwDOALCn2M7lGW8e', 'user': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T20:58:41Z', 'updated_at': '2026-02-03T21:01:12Z', 'body': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. The wiimote blink 2 or 3 times and turn off. That usually means rejected.", 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T20:58:44.456501	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'id': 3455745903, 'node_id': 'PR_kwDOALCn2M6qnqKV', 'number': 13970, 'title': 'WiimoteReal: Windows improvements.', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'closed', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 26, 'created_at': '2025-09-26T04:25:44Z', 'updated_at': '2026-02-03T20:58:41Z', 'closed_at': '2025-10-13T06:54:54Z', 'author_association': 'MEMBER', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13970', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13970.patch', 'merged_at': '2025-10-13T06:54:54Z'}, 'body': 'Majorly overhauled the Windows Wii remote code.\r\n\r\nThis PR should be ready now.\r\n\r\nStatically link with "Bthprops.lib"\r\nDynamic linking was introduced in https://github.com/dolphin-emu/dolphin/commit/3acf0eb3af98009cc4e00bd9bcf43873dc26c4da to make Dolphin run under Wine.\r\nMy search results imply that Wine has long since added stubs for these functions so I\'m hoping it works fine now?\r\nedit: Wine seems to work fine.\r\n\r\nA 10 year old comment describes a workaround needed for the Toshiba Bluetooth stack.\r\nI\'ve removed the workaround.\r\n\r\nRemoved `HidP_SetOutputReport` fallback (Windows 7 workaround).\r\n\r\nFixed buggy overlapped Read/Write event usage (probably my fault from long ago).\r\n\r\nAdded checks for a Wii remote device name on the HID interface to only attempt connecting to actual Wii remotes devices.\r\nThe existing code just throws Wii remote data at every interface and checks for sane replies.\r\n\r\nAdded a periodic write to test for disconnected remotes in a timely manner.\r\n\r\nChanged the `Refresh` button to not remove authenticated remotes.\r\nSome users found it annoying.\r\nIt now only removes unauthenticated disconnected remotes.\r\n\r\nAdded `Sync` and `Reset` actions to establish and remove pairings.\r\nThe `Refresh` button is now a `QToolButton` with additional actions (only on Windows).\r\n<img width="120" height="177" alt="image" src="https://github.com/user-attachments/assets/16e57431-1eab-4465-a4f8-856188b0f942" />\r\n\r\nPairing a remote with the `Sync` button will enable it to reestablish a connection on any button press.\r\nIt currently shows an ugly animation while the task runs async.\r\n<img width="189" height="72" alt="image" src="https://github.com/user-attachments/assets/076191ce-8312-4cdc-8223-14a4bb9eca2d" />\r\nIt\'s better than nothing and can be visually improved later.\r\n\r\n`Refresh` or `Continuous Scanning` are no longer required to connect paired remotes.\r\nPairing with Windows or the DolphinBar "just works" by assigning a slot to `Real Wii Remote`.\r\n\r\nAdditionally, I am constantly bombarded with ControllerInterface / WiimotePool related deadlocks.\r\nThis seems like an existing issue that fixing properly is probably going to take another PR.\r\nIt seems like it\'s SDL?\r\nedit: SDL\'s DirectInput handling has been disabled in #13982 \r\n\r\nTODO\r\n - [x] Make the `Sync` button not require also clicking `Refresh`.\r\n - [x] Make paired + reconnected Wii remotes work without using `Refresh`\r\n - [x] Test Dolphin with Wine (just in general, not Wii remote functionality)\r\n - [x] Clean up log spam from continuous opening of HID interfaces.\r\n - [x] Make the Sync / Reset buttons display some kind kind of feedback.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/reactions', 'total_count': 4, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 1, 'confused': 0, 'heart': 3, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13970#issuecomment-3843649310', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13970', 'id': 3843649310, 'node_id': 'IC_kwDOALCn2M7lGW8e', 'user': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T20:58:41Z', 'updated_at': '2026-02-03T20:58:41Z', 'body': "Do you know why some bt dongles rejects the reconnection? Probably some security feature on authentication that wiimotes doesn't have.\r\n\r\nI been testing many built in BT and found the mediatek to have this problem. There are others. Some can fe fixed using another driver\r\n\r\nSo seems to be BT driver related. But maybe the BT dongle expect another response and we can change that. ", 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3843649310/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T18:03:13Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14582, 'watchers_count': 14582, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14582, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Trihy', 'id': 107453916, 'node_id': 'U_kgDOBmed3A', 'avatar_url': 'https://avatars.githubusercontent.com/u/107453916?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Trihy', 'html_url': 'https://github.com/Trihy', 'followers_url': 'https://api.github.com/users/Trihy/followers', 'following_url': 'https://api.github.com/users/Trihy/following{/other_user}', 'gists_url': 'https://api.github.com/users/Trihy/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Trihy/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Trihy/subscriptions', 'organizations_url': 'https://api.github.com/users/Trihy/orgs', 'repos_url': 'https://api.github.com/users/Trihy/repos', 'events_url': 'https://api.github.com/users/Trihy/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Trihy/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T17:22:19.677183	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'edited', 'changes': {'body': {'from': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should a work in progress."}}, 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 102, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T17:22:06Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842626451', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842626451, 'node_id': 'IC_kwDOALCn2M7lCdOT', 'user': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T17:22:06Z', 'updated_at': '2026-02-03T17:22:17Z', 'body': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should be considered a work in progress.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T17:22:09.068291	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 102, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T17:22:06Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842626451', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842626451, 'node_id': 'IC_kwDOALCn2M7lCdOT', 'user': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T17:22:06Z', 'updated_at': '2026-02-03T17:22:06Z', 'body': "All revisions of all games may not be supported initially.  We'll continue to work on it as we go, but support should a work in progress.", 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842626451/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T17:08:19.684953	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 101, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T17:08:17Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842552882', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842552882', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842552882, 'node_id': 'IC_kwDOALCn2M7lCLQy', 'user': {'login': 'pizzzza19', 'id': 183198342, 'node_id': 'U_kgDOCutihg', 'avatar_url': 'https://avatars.githubusercontent.com/u/183198342?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/pizzzza19', 'html_url': 'https://github.com/pizzzza19', 'followers_url': 'https://api.github.com/users/pizzzza19/followers', 'following_url': 'https://api.github.com/users/pizzzza19/following{/other_user}', 'gists_url': 'https://api.github.com/users/pizzzza19/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/pizzzza19/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/pizzzza19/subscriptions', 'organizations_url': 'https://api.github.com/users/pizzzza19/orgs', 'repos_url': 'https://api.github.com/users/pizzzza19/repos', 'events_url': 'https://api.github.com/users/pizzzza19/events{/privacy}', 'received_events_url': 'https://api.github.com/users/pizzzza19/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T17:08:17Z', 'updated_at': '2026-02-03T17:08:17Z', 'body': 'Tested with manually built exe as guided by official dolphin readme.\nPrevious PR also had a same error. It is a known issue and may have discussed by other people outside this PR.', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842552882/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'pizzzza19', 'id': 183198342, 'node_id': 'U_kgDOCutihg', 'avatar_url': 'https://avatars.githubusercontent.com/u/183198342?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/pizzzza19', 'html_url': 'https://github.com/pizzzza19', 'followers_url': 'https://api.github.com/users/pizzzza19/followers', 'following_url': 'https://api.github.com/users/pizzzza19/following{/other_user}', 'gists_url': 'https://api.github.com/users/pizzzza19/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/pizzzza19/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/pizzzza19/subscriptions', 'organizations_url': 'https://api.github.com/users/pizzzza19/orgs', 'repos_url': 'https://api.github.com/users/pizzzza19/repos', 'events_url': 'https://api.github.com/users/pizzzza19/events{/privacy}', 'received_events_url': 'https://api.github.com/users/pizzzza19/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T16:31:29.616413	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'id': 3286480944, 'node_id': 'PR_kwDOALCn2M6h2r1s', 'number': 13844, 'title': 'Core: Triforce support', 'user': {'login': 'crediar', 'id': 145270593, 'node_id': 'U_kgDOCKinQQ', 'avatar_url': 'https://avatars.githubusercontent.com/u/145270593?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/crediar', 'html_url': 'https://github.com/crediar', 'followers_url': 'https://api.github.com/users/crediar/followers', 'following_url': 'https://api.github.com/users/crediar/following{/other_user}', 'gists_url': 'https://api.github.com/users/crediar/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/crediar/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/crediar/subscriptions', 'organizations_url': 'https://api.github.com/users/crediar/orgs', 'repos_url': 'https://api.github.com/users/crediar/repos', 'events_url': 'https://api.github.com/users/crediar/events{/privacy}', 'received_events_url': 'https://api.github.com/users/crediar/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 100, 'created_at': '2025-08-02T23:06:09Z', 'updated_at': '2026-02-03T16:31:27Z', 'closed_at': None, 'author_association': 'NONE', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13844', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13844.patch', 'merged_at': None}, 'body': 'All the required changes to make Triforce games bootable.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/reactions', 'total_count': 35, '+1': 4, '-1': 0, 'laugh': 0, 'hooray': 24, 'confused': 0, 'heart': 2, 'rocket': 5, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842350109', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13844#issuecomment-3842350109', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13844', 'id': 3842350109, 'node_id': 'IC_kwDOALCn2M7lBZwd', 'user': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-03T16:31:27Z', 'updated_at': '2026-02-03T16:31:27Z', 'body': '> Hello I built .exe with your latest code. it is a long journey to add triforce support but there are errors with some ROMs.  \r\n[GEYJ6E] VS2002: Game ID duplicated with JPN and EXP region. JPN rom will be overwritten as EXP mode. (removed Japan team and license logos)  \r\n[GHNJ6E] VS4 VerA (JPN) : Game frozen at demo screen or entering team select screen. This will only happen with JPN rom. It is well known that specific gekko code can skip boot error, but above cannot pass without fixing the main dolphin code.\r\n\r\n@pizzzza19 \r\nDid these games worked at some point with this PR? Do these games display in Dolphin\'s log window (with the highest verbosity enabled) error messages that can hint us regarding which pieces are missing? Does the issue occur with the build provided by [Dolphin\'s buildbot](https://dolphin.ci/#/builders/2/builds/8918)?\r\n\r\n<a href="https://dolphin.ci/#/builders/2/builds/8918"><img width="1427" height="486" alt="Image" src="https://github.com/user-attachments/assets/480e061d-edd0-4836-bdab-e87b46c5d60a" /></a>\r\n\r\n@JMC47 \r\nI don\'t have any Triforce games so you\'re probably more knowledgeable than me regarding the above games.', 'author_association': 'MEMBER', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3842350109/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T16:16:52Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14581, 'watchers_count': 14581, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14581, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'sepalani', 'id': 7890055, 'node_id': 'MDQ6VXNlcjc4OTAwNTU=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7890055?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/sepalani', 'html_url': 'https://github.com/sepalani', 'followers_url': 'https://api.github.com/users/sepalani/followers', 'following_url': 'https://api.github.com/users/sepalani/following{/other_user}', 'gists_url': 'https://api.github.com/users/sepalani/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/sepalani/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/sepalani/subscriptions', 'organizations_url': 'https://api.github.com/users/sepalani/orgs', 'repos_url': 'https://api.github.com/users/sepalani/repos', 'events_url': 'https://api.github.com/users/sepalani/events{/privacy}', 'received_events_url': 'https://api.github.com/users/sepalani/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T16:08:06.874146	{'source': 'webserver', 'gh_type': 'commit_comment', 'raw': {'action': 'created', 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments/176299891', 'html_url': 'https://github.com/dolphin-emu/dolphin/commit/128a6454b6247c0c2a2cafb2d91d03f71fc112e0#commitcomment-176299891', 'id': 176299891, 'node_id': 'CC_kwDOALCn2M4Kgh9z', 'user': {'login': 'pizzzza19', 'id': 183198342, 'node_id': 'U_kgDOCutihg', 'avatar_url': 'https://avatars.githubusercontent.com/u/183198342?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/pizzzza19', 'html_url': 'https://github.com/pizzzza19', 'followers_url': 'https://api.github.com/users/pizzzza19/followers', 'following_url': 'https://api.github.com/users/pizzzza19/following{/other_user}', 'gists_url': 'https://api.github.com/users/pizzzza19/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/pizzzza19/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/pizzzza19/subscriptions', 'organizations_url': 'https://api.github.com/users/pizzzza19/orgs', 'repos_url': 'https://api.github.com/users/pizzzza19/repos', 'events_url': 'https://api.github.com/users/pizzzza19/events{/privacy}', 'received_events_url': 'https://api.github.com/users/pizzzza19/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'position': None, 'line': None, 'path': None, 'commit_id': '128a6454b6247c0c2a2cafb2d91d03f71fc112e0', 'created_at': '2026-02-03T16:08:04Z', 'updated_at': '2026-02-03T16:08:04Z', 'body': 'Hello I built .exe with your latest code.\r\nit is a long journey to add triforce support but there are errors with some ROMs.\r\n[GEYJ6E] VS2002: Game ID duplicated with JPN and EXP region. JPN rom will be overwritten as EXP mode. (removed Japan team and license logos)\r\n[GHNJ6E] VS4 VerA (JPN) : Game frozen at demo screen or entering team select screen. This will only happen with JPN rom.\r\nIt is well known that specific gekko code can skip boot error, but above cannot pass without fixing the main dolphin code.', 'author_association': 'NONE', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments/176299891/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T15:41:29Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14580, 'watchers_count': 14580, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14580, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'pizzzza19', 'id': 183198342, 'node_id': 'U_kgDOCutihg', 'avatar_url': 'https://avatars.githubusercontent.com/u/183198342?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/pizzzza19', 'html_url': 'https://github.com/pizzzza19', 'followers_url': 'https://api.github.com/users/pizzzza19/followers', 'following_url': 'https://api.github.com/users/pizzzza19/following{/other_user}', 'gists_url': 'https://api.github.com/users/pizzzza19/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/pizzzza19/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/pizzzza19/subscriptions', 'organizations_url': 'https://api.github.com/users/pizzzza19/orgs', 'repos_url': 'https://api.github.com/users/pizzzza19/repos', 'events_url': 'https://api.github.com/users/pizzzza19/events{/privacy}', 'received_events_url': 'https://api.github.com/users/pizzzza19/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T13:20:38.893387	{'source': 'webserver', 'gh_type': 'pull_request', 'raw': {'action': 'synchronize', 'number': 13997, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13997', 'id': 2893849083, 'node_id': 'PR_kwDOALCn2M6sfKH7', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13997', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13997.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13997.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13997', 'number': 13997, 'state': 'open', 'locked': False, 'title': 'Externals: Update minizip-ng to 4.1.0', 'user': {'login': 'bgermann', 'id': 9329107, 'node_id': 'MDQ6VXNlcjkzMjkxMDc=', 'avatar_url': 'https://avatars.githubusercontent.com/u/9329107?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/bgermann', 'html_url': 'https://github.com/bgermann', 'followers_url': 'https://api.github.com/users/bgermann/followers', 'following_url': 'https://api.github.com/users/bgermann/following{/other_user}', 'gists_url': 'https://api.github.com/users/bgermann/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/bgermann/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/bgermann/subscriptions', 'organizations_url': 'https://api.github.com/users/bgermann/orgs', 'repos_url': 'https://api.github.com/users/bgermann/repos', 'events_url': 'https://api.github.com/users/bgermann/events{/privacy}', 'received_events_url': 'https://api.github.com/users/bgermann/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'This includes removals of non-distributable files.', 'created_at': '2025-10-07T12:39:37Z', 'updated_at': '2026-02-03T13:20:36Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': '0f5aa622248621b8a16127a67f76c51c88e8f718', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13997/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13997/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13997/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/df85a2c99d667c537a5a896cd81954fb63fab027', 'head': {'label': 'bgermann:minizip-ng', 'ref': 'minizip-ng', 'sha': 'df85a2c99d667c537a5a896cd81954fb63fab027', 'user': {'login': 'bgermann', 'id': 9329107, 'node_id': 'MDQ6VXNlcjkzMjkxMDc=', 'avatar_url': 'https://avatars.githubusercontent.com/u/9329107?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/bgermann', 'html_url': 'https://github.com/bgermann', 'followers_url': 'https://api.github.com/users/bgermann/followers', 'following_url': 'https://api.github.com/users/bgermann/following{/other_user}', 'gists_url': 'https://api.github.com/users/bgermann/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/bgermann/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/bgermann/subscriptions', 'organizations_url': 'https://api.github.com/users/bgermann/orgs', 'repos_url': 'https://api.github.com/users/bgermann/repos', 'events_url': 'https://api.github.com/users/bgermann/events{/privacy}', 'received_events_url': 'https://api.github.com/users/bgermann/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 1071493499, 'node_id': 'R_kgDOP92xew', 'name': 'dolphin', 'full_name': 'bgermann/dolphin', 'private': False, 'owner': {'login': 'bgermann', 'id': 9329107, 'node_id': 'MDQ6VXNlcjkzMjkxMDc=', 'avatar_url': 'https://avatars.githubusercontent.com/u/9329107?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/bgermann', 'html_url': 'https://github.com/bgermann', 'followers_url': 'https://api.github.com/users/bgermann/followers', 'following_url': 'https://api.github.com/users/bgermann/following{/other_user}', 'gists_url': 'https://api.github.com/users/bgermann/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/bgermann/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/bgermann/subscriptions', 'organizations_url': 'https://api.github.com/users/bgermann/orgs', 'repos_url': 'https://api.github.com/users/bgermann/repos', 'events_url': 'https://api.github.com/users/bgermann/events{/privacy}', 'received_events_url': 'https://api.github.com/users/bgermann/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/bgermann/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/bgermann/dolphin', 'forks_url': 'https://api.github.com/repos/bgermann/dolphin/forks', 'keys_url': 'https://api.github.com/repos/bgermann/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/bgermann/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/bgermann/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/bgermann/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/bgermann/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/bgermann/dolphin/events', 'assignees_url': 'https://api.github.com/repos/bgermann/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/bgermann/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/bgermann/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/bgermann/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/bgermann/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/bgermann/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/bgermann/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/bgermann/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/bgermann/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/bgermann/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/bgermann/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/bgermann/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/bgermann/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/bgermann/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/bgermann/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/bgermann/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/bgermann/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/bgermann/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/bgermann/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/bgermann/dolphin/merges', 'archive_url': 'https://api.github.com/repos/bgermann/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/bgermann/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/bgermann/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/bgermann/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/bgermann/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/bgermann/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/bgermann/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/bgermann/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/bgermann/dolphin/deployments', 'created_at': '2025-10-07T12:28:54Z', 'updated_at': '2026-01-29T08:36:46Z', 'pushed_at': '2026-02-03T13:20:35Z', 'git_url': 'git://github.com/bgermann/dolphin.git', 'ssh_url': 'git@github.com:bgermann/dolphin.git', 'clone_url': 'https://github.com/bgermann/dolphin.git', 'svn_url': 'https://github.com/bgermann/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 506471, 'stargazers_count': 0, 'watchers_count': 0, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 0, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 0, 'open_issues': 0, 'watchers': 0, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T12:36:07Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14579, 'watchers_count': 14579, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14579, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13997'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13997'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13997'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13997/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13997/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13997/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/df85a2c99d667c537a5a896cd81954fb63fab027'}}, 'author_association': 'NONE', 'auto_merge': None, 'active_lock_reason': None, 'merged': False, 'mergeable': None, 'rebaseable': None, 'mergeable_state': 'unknown', 'merged_by': None, 'comments': 3, 'review_comments': 0, 'maintainer_can_modify': True, 'commits': 1, 'additions': 1, 'deletions': 1, 'changed_files': 1}, 'before': '5a875f6cbebf015e5948e7c99f4277982e85e8a5', 'after': 'df85a2c99d667c537a5a896cd81954fb63fab027', 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T12:36:07Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544014, 'stargazers_count': 14579, 'watchers_count': 14579, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2965, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2965, 'open_issues': 360, 'watchers': 14579, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'bgermann', 'id': 9329107, 'node_id': 'MDQ6VXNlcjkzMjkxMDc=', 'avatar_url': 'https://avatars.githubusercontent.com/u/9329107?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/bgermann', 'html_url': 'https://github.com/bgermann', 'followers_url': 'https://api.github.com/users/bgermann/followers', 'following_url': 'https://api.github.com/users/bgermann/following{/other_user}', 'gists_url': 'https://api.github.com/users/bgermann/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/bgermann/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/bgermann/subscriptions', 'organizations_url': 'https://api.github.com/users/bgermann/orgs', 'repos_url': 'https://api.github.com/users/bgermann/repos', 'events_url': 'https://api.github.com/users/bgermann/events{/privacy}', 'received_events_url': 'https://api.github.com/users/bgermann/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T04:59:38.731148	{'source': 'webserver', 'gh_type': 'pull_request_review', 'raw': {'action': 'submitted', 'review': {'id': 3742915580, 'node_id': 'PRR_kwDOALCn2M7fGFv8', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': None, 'commit_id': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'submitted_at': '2026-02-03T04:55:05Z', 'state': 'commented', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14218#pullrequestreview-3742915580', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218', '_links': {'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14218#pullrequestreview-3742915580'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218'}}, 'updated_at': '2026-02-03T04:55:05Z', 'author_association': 'MEMBER'}, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218', 'id': 3099870143, 'node_id': 'PR_kwDOALCn2M64xEO_', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14218', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14218.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14218.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218', 'number': 14218, 'state': 'open', 'locked': False, 'title': 'Jit64: Make ABI_CallFunction generic', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'This replaces all the hand-coded variants of ABI_CallFunction with a single generic function. It handles all existing cases, plus many new cases that would have been annoying to hand-code.\r\n\r\nThe implementation is based on our AArch64 ABI_CallFunction, but adapted for x64. The most notable differences are support for memory operands and a new way for callers to specify the sizes of operands.', 'created_at': '2025-12-14T13:50:33Z', 'updated_at': '2026-02-03T04:55:05Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': 'f86a133381f5a524545ebeff9efa0a63c20ca31f', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'head': {'label': 'JosJuice:jit64-abi-call-function-parameters', 'ref': 'jit64-abi-call-function-parameters', 'sha': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-02T21:07:04Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509732, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'a32779340e39392750de791c80d9773ff613c841', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T04:26:20Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14577, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14218'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/41c26ea85f34764a41e9f4e79211cf9fe6bad48c'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T04:26:20Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14577, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T04:58:23.579068	{'source': 'webserver', 'gh_type': 'pull_request_review_comment', 'raw': {'action': 'created', 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757167327', 'pull_request_review_id': 3742915580, 'id': 2757167327, 'node_id': 'PRRC_kwDOALCn2M6kVwjf', 'diff_hunk': '@@ -377,6 +423,11 @@ class XEmitter\n   void ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size,\n                               size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp);\n \n+  // This function solves the "parallel moves" problem that\'s common in compilers.\n+  // The arguments are mutated!\n+  void ParallelMoves(RegisterMove* begin, RegisterMove* end, std::array<u8, 16>* source_reg_uses,\n+                     std::array<u8, 16>* destination_reg_uses);', 'path': 'Source/Core/Common/x64Emitter.h', 'commit_id': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'original_commit_id': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': '```suggestion\n  void ParallelMoves(std::span<RegisterMove> pending_moves, std::array<u8, 16>* source_reg_uses,\n                     std::array<u8, 16>* destination_reg_uses);\n```\nI suppose a `span` would be cleaner than two pointers.\n\nThen the call site becomes just:\n```cpp\nParallelMoves(pending_moves, &source_reg_uses, &destination_reg_uses);\n```', 'created_at': '2026-02-03T04:42:30Z', 'updated_at': '2026-02-03T04:55:05Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14218#discussion_r2757167327', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757167327'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14218#discussion_r2757167327'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218'}}, 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757167327/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'start_line': 428, 'original_start_line': 428, 'start_side': 'RIGHT', 'line': 429, 'original_line': 429, 'side': 'RIGHT', 'author_association': 'MEMBER', 'original_position': 84, 'position': 84, 'subject_type': 'line'}, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218', 'id': 3099870143, 'node_id': 'PR_kwDOALCn2M64xEO_', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14218', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14218.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14218.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218', 'number': 14218, 'state': 'open', 'locked': False, 'title': 'Jit64: Make ABI_CallFunction generic', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'This replaces all the hand-coded variants of ABI_CallFunction with a single generic function. It handles all existing cases, plus many new cases that would have been annoying to hand-code.\r\n\r\nThe implementation is based on our AArch64 ABI_CallFunction, but adapted for x64. The most notable differences are support for memory operands and a new way for callers to specify the sizes of operands.', 'created_at': '2025-12-14T13:50:33Z', 'updated_at': '2026-02-03T04:55:05Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': 'f86a133381f5a524545ebeff9efa0a63c20ca31f', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'head': {'label': 'JosJuice:jit64-abi-call-function-parameters', 'ref': 'jit64-abi-call-function-parameters', 'sha': '41c26ea85f34764a41e9f4e79211cf9fe6bad48c', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-02T21:07:04Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509732, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'a32779340e39392750de791c80d9773ff613c841', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T04:26:20Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14577, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14218'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14218/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14218/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/41c26ea85f34764a41e9f4e79211cf9fe6bad48c'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-03T04:26:20Z', 'pushed_at': '2026-02-03T04:26:13Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14577, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T04:26:15.538299	{'source': 'webserver', 'gh_type': 'push', 'raw': {'ref': 'refs/heads/master', 'before': 'c7de9162765452fc2787f3ba4f0092394067d603', 'after': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'name': 'dolphin-emu', 'email': None, 'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': 1374484077, 'updated_at': '2026-02-02T23:15:04Z', 'pushed_at': 1770092773, 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14577, 'default_branch': 'master', 'stargazers': 14577, 'master_branch': 'master', 'organization': 'dolphin-emu', 'custom_properties': {}}, 'pusher': {'name': 'jordan-woyak', 'email': 'jordan.woyak@gmail.com'}, 'forced': False, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}, 'created': False, 'deleted': False, 'base_ref': None, 'compare': 'https://github.com/dolphin-emu/dolphin/compare/c7de91627654...eac7b290423e', 'commits': [{'id': 'ac19ef5452a1abcd7bcfc9d166fcdbe28c714ba1', 'tree_id': 'f1dea370112a71e24e5c6623e7424e66e70b5661', 'distinct': False, 'message': 'GameINI: Force Safe Texture Cache to Samurai Shodown Anthology', 'timestamp': '2026-02-01T00:42:27-05:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/ac19ef5452a1abcd7bcfc9d166fcdbe28c714ba1', 'author': {'name': 'JMC47', 'email': 'JMC4789@gmail.com', 'date': '2026-02-01T00:42:27-05:00', 'username': 'JMC47'}, 'committer': {'name': 'JMC47', 'email': 'JMC4789@gmail.com', 'date': '2026-02-01T00:42:27-05:00', 'username': 'JMC47'}, 'added': [], 'removed': [], 'modified': ['Data/Sys/GameSettings/RSS.ini']}, {'id': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'tree_id': 'ec7ff444f3da935c70f0745b24158930233504d7', 'distinct': True, 'message': 'Merge pull request #14314 from JMC47/shodown\n\nGameINI: Force Safe Texture Cache to Samurai Shodown Anthology', 'timestamp': '2026-02-02T22:26:13-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/eac7b290423efea867c2c7f3f2b2792d05418bdd', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-02-02T22:26:13-06:00', 'username': 'jordan-woyak'}, 'committer': {'name': 'GitHub', 'email': 'noreply@github.com', 'date': '2026-02-02T22:26:13-06:00', 'username': 'web-flow'}, 'added': [], 'removed': [], 'modified': ['Data/Sys/GameSettings/RSS.ini']}], 'head_commit': {'id': 'eac7b290423efea867c2c7f3f2b2792d05418bdd', 'tree_id': 'ec7ff444f3da935c70f0745b24158930233504d7', 'distinct': True, 'message': 'Merge pull request #14314 from JMC47/shodown\n\nGameINI: Force Safe Texture Cache to Samurai Shodown Anthology', 'timestamp': '2026-02-02T22:26:13-06:00', 'url': 'https://github.com/dolphin-emu/dolphin/commit/eac7b290423efea867c2c7f3f2b2792d05418bdd', 'author': {'name': 'Jordan Woyak', 'email': 'jordan.woyak@gmail.com', 'date': '2026-02-02T22:26:13-06:00', 'username': 'jordan-woyak'}, 'committer': {'name': 'GitHub', 'email': 'noreply@github.com', 'date': '2026-02-02T22:26:13-06:00', 'username': 'web-flow'}, 'added': [], 'removed': [], 'modified': ['Data/Sys/GameSettings/RSS.ini']}}, 'type': 'raw_gh_hook'}
2026-02-03T04:24:35.371322	{'source': 'webserver', 'gh_type': 'pull_request_review_comment', 'raw': {'action': 'created', 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757133526', 'pull_request_review_id': 3742877805, 'id': 2757133526, 'node_id': 'PRRC_kwDOALCn2M6kVoTW', 'diff_hunk': '@@ -33,6 +34,11 @@ struct EFB\n   u32* scaled_height;\n };\n \n+struct PostEFB\n+{\n+  VideoCommon::MaterialResource** material;', 'path': 'Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h', 'commit_id': 'd58818f317de389fb1ddded9412a1d9b2a29462e', 'original_commit_id': 'd58818f317de389fb1ddded9412a1d9b2a29462e', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'This double pointer seems unnecessary.\nI think it would be less confusing as a `MaterialResource*`.\n\nSince `AfterEFB` takes a `PostEFB*` it can assign the `material` member there, then the caller can inspect `efb->material` afterwards.\n\nUnless you have some future plans where this `MaterialResource**` makes more sense?', 'created_at': '2026-02-03T04:24:30Z', 'updated_at': '2026-02-03T04:24:33Z', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2757133526', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140', '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757133526'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14140#discussion_r2757133526'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140'}}, 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments/2757133526/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'start_line': None, 'original_start_line': None, 'start_side': None, 'line': 39, 'original_line': 39, 'side': 'RIGHT', 'author_association': 'MEMBER', 'original_position': 23, 'position': 23, 'subject_type': 'line'}, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140', 'id': 3020230967, 'node_id': 'PR_kwDOALCn2M60BRE3', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14140', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14140.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14140.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14140', 'number': 14140, 'state': 'open', 'locked': False, 'title': 'VideoCommon: add a graphics mod feature to modify EFBs with a custom material, enhance bloom', 'user': {'login': 'iwubcode', 'id': 15224722, 'node_id': 'MDQ6VXNlcjE1MjI0NzIy', 'avatar_url': 'https://avatars.githubusercontent.com/u/15224722?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/iwubcode', 'html_url': 'https://github.com/iwubcode', 'followers_url': 'https://api.github.com/users/iwubcode/followers', 'following_url': 'https://api.github.com/users/iwubcode/following{/other_user}', 'gists_url': 'https://api.github.com/users/iwubcode/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/iwubcode/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/iwubcode/subscriptions', 'organizations_url': 'https://api.github.com/users/iwubcode/orgs', 'repos_url': 'https://api.github.com/users/iwubcode/repos', 'events_url': 'https://api.github.com/users/iwubcode/events{/privacy}', 'received_events_url': 'https://api.github.com/users/iwubcode/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "( this builds on #14043 )\r\n\r\nThis PR adds a feature to modify EFBs created using a custom Dolphin material.  This is the same feature I have been building for custom draw shaders and this will eventually form the base for the new post processing system.\r\n\r\nStory time below...\r\n\r\nIf you recall, targeting by EFB is prone to error.  One example of this is in Mario Galaxy 2 where targeting bloom actually targets the DOF during level selection.  This is because Dolphin can't discern between the two effects, it only has so much information to leverage.\r\n\r\nMy answer to this was Graphics Mods 2.0 (unreleased), by targeting the draw call (not the EFB), I was able to easily pick what effect I actually intended to modify.  I originally planned to provide a new enhanced bloom system with that feature.  However, what I found was that while I could target with higher accuracy and correctly replace that draw with a custom effect, doing so generically was a much bigger challenge.  Games had different ways of using the bloom EFB copy and that meant I needed to support those various scenarios.  Discouraged, I let the idea disappear from my mind...\r\n\r\nFellow developer @TryTwo has maintained a custom fork for a long time.  The fork supports modifying bloom at the EFB level.  While it is arguably less accurate than Graphics Mods 1.0, it provides some really nice features as well.  The first is a way to blur the bloom.  The second, is a nice UI that allows you to control the spread of the blur and the brightness.  Many users have adopted this feature due to Graphics Mods 1.0 having noticeable shimmering when playing some games.**\r\n\r\nA couple months ago, I started working on a separate feature.  I won't go into great depth about that today but my hope is it will allow for us to get the accuracy at the draw level (with Graphics Mods 2.0) while still using the EFB for modification.  It is still being developed and I can't say for sure it will work but it gave me new hope.  Because of that, I opted to add the ability to modify the EFB copies, similar to TryTwo's fork.\r\n\r\nWhat you get, is the ability to target EFBs and provide a custom material to draw over the EFB with.  This is how the bloom is performed (done in two passes).\r\n\r\nI started with a modified version of @TryTwo 's implementation of the blur shader but I may investigate other blur shaders as part of this PR or part of the future.  Fellow developer phire originally discussed a Gaussian blur and I think that would be nice from a performance standpoint as well.\r\n\r\nThis adds more games to the list of bloom enabled games.  These have been lightly tested:\r\n\r\n* [Conduit 2](https://wiki.dolphin-emu.org/index.php?title=Conduit_2)\r\n* [De Blob](https://wiki.dolphin-emu.org/index.php?title=De_Blob)\r\n* [De Blob 2](https://wiki.dolphin-emu.org/index.php?title=De_Blob_2)\r\n* [Epic Mickey](https://wiki.dolphin-emu.org/index.php?title=Epic_Mickey)\r\n* [Epic Mickey 2](https://wiki.dolphin-emu.org/index.php?title=Epic_Mickey_2:_The_Power_of_Two)\r\n* [Go Vacation](https://wiki.dolphin-emu.org/index.php?title=Go_Vacation)\r\n* [Lego Batman](https://wiki.dolphin-emu.org/index.php?title=Lego_Batman:_The_Videogame)\r\n* [Lord of the Rings: Aragorn's Quest](https://wiki.dolphin-emu.org/index.php?title=The_Lord_of_the_Rings:_Aragorn%27s_Quest)\r\n* [LostWinds](https://wiki.dolphin-emu.org/index.php?title=LostWinds)\r\n* [LostWinds: Winter of the Melodias](https://wiki.dolphin-emu.org/index.php?title=LostWinds:_Winter_of_the_Melodias)\r\n* [Metroid: Other M](https://wiki.dolphin-emu.org/index.php?title=Metroid:_Other_M)\r\n* [Metroid Prime: Trilogy](https://wiki.dolphin-emu.org/index.php?title=Metroid_Prime:_Trilogy)\r\n* [Overlord: Dark Legend](https://wiki.dolphin-emu.org/index.php?title=Overlord:_Dark_Legend) ( from the [forum](https://forums.dolphin-emu.org/Thread-wii-overlord-dark-legend?pid=538496#pid538496) )\r\n* [Sonic Unleashed](https://wiki.dolphin-emu.org/index.php?title=Sonic_Unleashed) ( from PR #13402 )\r\n* [Spectrobes: Origins](https://wiki.dolphin-emu.org/index.php?title=Spectrobes:_Origins)\r\n* [Spyborgs](https://wiki.dolphin-emu.org/index.php?title=Spyborgs)\r\n* [Takt of Magic](https://wiki.dolphin-emu.org/index.php?title=Takt_of_Magic)\r\n* [Zangeki no Reginleiv](https://wiki.dolphin-emu.org/index.php?title=Zangeki_no_Reginleiv)\r\n\r\nThere were other games I wanted to add but they ran into accuracy issues when testing.\r\n\r\n\r\n<sub>** Admiral's PR (9860) from 4 years ago explained well why shimmering might occur:</sub>\r\n\r\n> <sub>This has become known as 'shimmering' artifacts in enthusiast circles and happens because the individual columns of source pixels are unevenly sized -- some are 3 pixels wide, some are 2 wide -- so a pattern moving across them stands out poorly.</sub>", 'created_at': '2025-11-18T06:39:36Z', 'updated_at': '2026-02-03T04:24:33Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': '73f80d68bec195e6e3511a357d0e064e708262d3', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14140/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/d58818f317de389fb1ddded9412a1d9b2a29462e', 'head': {'label': 'iwubcode:blurry_bloom', 'ref': 'blurry_bloom', 'sha': 'd58818f317de389fb1ddded9412a1d9b2a29462e', 'user': {'login': 'iwubcode', 'id': 15224722, 'node_id': 'MDQ6VXNlcjE1MjI0NzIy', 'avatar_url': 'https://avatars.githubusercontent.com/u/15224722?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/iwubcode', 'html_url': 'https://github.com/iwubcode', 'followers_url': 'https://api.github.com/users/iwubcode/followers', 'following_url': 'https://api.github.com/users/iwubcode/following{/other_user}', 'gists_url': 'https://api.github.com/users/iwubcode/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/iwubcode/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/iwubcode/subscriptions', 'organizations_url': 'https://api.github.com/users/iwubcode/orgs', 'repos_url': 'https://api.github.com/users/iwubcode/repos', 'events_url': 'https://api.github.com/users/iwubcode/events{/privacy}', 'received_events_url': 'https://api.github.com/users/iwubcode/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 77596353, 'node_id': 'MDEwOlJlcG9zaXRvcnk3NzU5NjM1Mw==', 'name': 'dolphin', 'full_name': 'iwubcode/dolphin', 'private': False, 'owner': {'login': 'iwubcode', 'id': 15224722, 'node_id': 'MDQ6VXNlcjE1MjI0NzIy', 'avatar_url': 'https://avatars.githubusercontent.com/u/15224722?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/iwubcode', 'html_url': 'https://github.com/iwubcode', 'followers_url': 'https://api.github.com/users/iwubcode/followers', 'following_url': 'https://api.github.com/users/iwubcode/following{/other_user}', 'gists_url': 'https://api.github.com/users/iwubcode/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/iwubcode/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/iwubcode/subscriptions', 'organizations_url': 'https://api.github.com/users/iwubcode/orgs', 'repos_url': 'https://api.github.com/users/iwubcode/repos', 'events_url': 'https://api.github.com/users/iwubcode/events{/privacy}', 'received_events_url': 'https://api.github.com/users/iwubcode/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/iwubcode/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/iwubcode/dolphin', 'forks_url': 'https://api.github.com/repos/iwubcode/dolphin/forks', 'keys_url': 'https://api.github.com/repos/iwubcode/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/iwubcode/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/iwubcode/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/iwubcode/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/iwubcode/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/iwubcode/dolphin/events', 'assignees_url': 'https://api.github.com/repos/iwubcode/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/iwubcode/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/iwubcode/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/iwubcode/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/iwubcode/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/iwubcode/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/iwubcode/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/iwubcode/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/iwubcode/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/iwubcode/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/iwubcode/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/iwubcode/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/iwubcode/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/iwubcode/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/iwubcode/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/iwubcode/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/iwubcode/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/iwubcode/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/iwubcode/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/iwubcode/dolphin/merges', 'archive_url': 'https://api.github.com/repos/iwubcode/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/iwubcode/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/iwubcode/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/iwubcode/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/iwubcode/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/iwubcode/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/iwubcode/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/iwubcode/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/iwubcode/dolphin/deployments', 'created_at': '2016-12-29T08:26:39Z', 'updated_at': '2025-12-24T07:50:59Z', 'pushed_at': '2026-01-31T08:01:51Z', 'git_url': 'git://github.com/iwubcode/dolphin.git', 'ssh_url': 'git@github.com:iwubcode/dolphin.git', 'clone_url': 'https://github.com/iwubcode/dolphin.git', 'svn_url': 'https://github.com/iwubcode/dolphin', 'homepage': 'https://dolphin-emu.org/', 'size': 507925, 'stargazers_count': 1, 'watchers_count': 1, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 3, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 1, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 3, 'open_issues': 1, 'watchers': 1, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'b1020186519d690a9b64a215f00da7453ab2803b', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T23:15:04Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14577, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14140'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14140'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14140/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14140/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/d58818f317de389fb1ddded9412a1d9b2a29462e'}}, 'author_association': 'CONTRIBUTOR', 'auto_merge': None, 'active_lock_reason': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T23:15:04Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14577, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-03T04:07:47.475567	{'source': 'webserver', 'gh_type': 'pull_request_review', 'raw': {'action': 'submitted', 'review': {'id': 3742805702, 'node_id': 'PRR_kwDOALCn2M7fFq7G', 'user': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': None, 'commit_id': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'submitted_at': '2026-02-03T04:07:45Z', 'state': 'commented', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768#pullrequestreview-3742805702', 'pull_request_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', '_links': {'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768#pullrequestreview-3742805702'}, 'pull_request': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}}, 'updated_at': '2026-02-03T04:07:45Z', 'author_association': 'MEMBER'}, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768', 'id': 2608905795, 'node_id': 'PR_kwDOALCn2M6bgL5D', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13768', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13768.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768', 'number': 13768, 'state': 'open', 'locked': False, 'title': 'Core: Create fastmem mappings for page address translation', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': "Previously we've only been setting up fastmem mappings for block address translation, but now we also do it for page address translation. This increases performance when games access memory using page tables, but decreases performance when games set up page tables.\r\n\r\nThe tlbie instruction is used as an indication that the mappings need to be updated.\r\n\r\nThere is the accuracy downside that the TLB is now effectively infinitely large. No games are known to be affected by this, and you still get the old, more accurate behavior if Enable Write-Back Cache is on.\r\n\r\nLeft to do:\r\n\r\n- [x] Support for host page sizes larger than 4K\r\n- [x] Support for host page sizes larger than 4K is untested\r\n- [x] macOS is untested\r\n- [x] Rogue Squadron 3 is super slow due to the pessimistic setting of R and C bits (I guess the game is heavily swapping? DoJit is running very often)\r\n- [x] Unit tests\r\n- [x] Unit tests fail on the Ubuntu and Debian runners", 'created_at': '2025-06-21T09:20:23Z', 'updated_at': '2026-02-03T04:07:45Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': 'b8643856cdcf8d4227ad32709bada837d257e972', 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'head': {'label': 'JosJuice:page-table-fastmem-2', 'ref': 'page-table-fastmem-2', 'sha': 'e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-02T21:07:04Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509732, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T23:15:04Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14577, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/13768'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13768/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13768/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/e88ee42d345e0b2a85cc6bd44c7258eb8be3c6b8'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T23:15:04Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14577, 'watchers_count': 14577, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14577, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'jordan-woyak', 'id': 1768214, 'node_id': 'MDQ6VXNlcjE3NjgyMTQ=', 'avatar_url': 'https://avatars.githubusercontent.com/u/1768214?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/jordan-woyak', 'html_url': 'https://github.com/jordan-woyak', 'followers_url': 'https://api.github.com/users/jordan-woyak/followers', 'following_url': 'https://api.github.com/users/jordan-woyak/following{/other_user}', 'gists_url': 'https://api.github.com/users/jordan-woyak/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/jordan-woyak/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/jordan-woyak/subscriptions', 'organizations_url': 'https://api.github.com/users/jordan-woyak/orgs', 'repos_url': 'https://api.github.com/users/jordan-woyak/repos', 'events_url': 'https://api.github.com/users/jordan-woyak/events{/privacy}', 'received_events_url': 'https://api.github.com/users/jordan-woyak/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-02T21:17:44.679800	{'source': 'webserver', 'gh_type': 'issue_comment', 'raw': {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991', 'repository_url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/labels{/name}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/comments', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/events', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13991', 'id': 3485022552, 'node_id': 'PR_kwDOALCn2M6sJ1qq', 'number': 13991, 'title': "Jit: Emit Branch Watch code only if it's enabled", 'user': {'login': 'SuperSamus', 'id': 40663462, 'node_id': 'MDQ6VXNlcjQwNjYzNDYy', 'avatar_url': 'https://avatars.githubusercontent.com/u/40663462?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/SuperSamus', 'html_url': 'https://github.com/SuperSamus', 'followers_url': 'https://api.github.com/users/SuperSamus/followers', 'following_url': 'https://api.github.com/users/SuperSamus/following{/other_user}', 'gists_url': 'https://api.github.com/users/SuperSamus/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/SuperSamus/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/SuperSamus/subscriptions', 'organizations_url': 'https://api.github.com/users/SuperSamus/orgs', 'repos_url': 'https://api.github.com/users/SuperSamus/repos', 'events_url': 'https://api.github.com/users/SuperSamus/events{/privacy}', 'received_events_url': 'https://api.github.com/users/SuperSamus/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'labels': [], 'state': 'open', 'locked': False, 'assignee': None, 'assignees': [], 'milestone': None, 'comments': 5, 'created_at': '2025-10-05T17:58:27Z', 'updated_at': '2026-02-02T21:17:42Z', 'closed_at': None, 'author_association': 'CONTRIBUTOR', 'type': None, 'active_lock_reason': None, 'draft': False, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/13991', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13991', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/13991.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/13991.patch', 'merged_at': None}, 'body': 'JIT code related to Branch Watch was emitted if the debugging UI was active: the emitted code would dynamically check whether Branch Watch is active.\r\nHowever, this causes two problems:\r\n1. It decreases performance by just having the debugging UI enabled*\r\n2. It clutters the host assembly in the JIT tab, making it harder to read (unaware readers will wonder what these instructions are for)\r\n\r\nWith this PR, code related to Branch Watch is emitted only if Branch Watch itself is active, fixing the issues above.\r\nThe JIT cache will now be wiped whenever the feature is toggled, causing a slight stutter. However, this isn\'t the kind of feature that is toggled over and over, so IMO it is an acceptable trade-off.\r\n\r\nARM64 is untested.\r\n\r\n#### A note about the debugging UI\r\n\r\nSo, you might wonder what the asterisk is about. It\'s not that the PR doesn\'t improve performance with the debug UI on, because it does.\r\nThe problem is that the performance is still worse than with the debugging UI disabled. I\'m not sure why.[^1]\r\nEven though this performance decrease is intended, *this downside isn\'t communicated anywhere in Dolphin*, and it\'s very possible for someone to unawarely leave the option enabled. (Hi.)\r\nSo... that should be communicated, but where? Only the tooltip of the option, or even in the option title itself `(disables optimizations)`? (Plus the help text in the command line for the `-d` argument.)\r\n\r\nI\'ll leave the discussion for this and on whether to merge the PR (IMO, still worthy due to point 2) to the jury.\r\n\r\n[^1]: I already tested that "reverting" [the disabled BLR optimization](https://github.com/dolphin-emu/dolphin/blob/70bd0943a7a271606367ac129eb39cbdcd659e42/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp#L153-L154) or [the extra checks in the main loop meant for the memory breakpoints](https://github.com/dolphin-emu/dolphin/blob/70bd0943a7a271606367ac129eb39cbdcd659e42/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp#L99-L104) changes basically nothing.', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'timeline_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991/timeline', 'performed_via_github_app': None, 'state_reason': None}, 'comment': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3837468646', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/13991#issuecomment-3837468646', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/13991', 'id': 3837468646, 'node_id': 'IC_kwDOALCn2M7kux_m', 'user': {'login': 'Hibyehello', 'id': 36666883, 'node_id': 'MDQ6VXNlcjM2NjY2ODgz', 'avatar_url': 'https://avatars.githubusercontent.com/u/36666883?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Hibyehello', 'html_url': 'https://github.com/Hibyehello', 'followers_url': 'https://api.github.com/users/Hibyehello/followers', 'following_url': 'https://api.github.com/users/Hibyehello/following{/other_user}', 'gists_url': 'https://api.github.com/users/Hibyehello/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Hibyehello/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Hibyehello/subscriptions', 'organizations_url': 'https://api.github.com/users/Hibyehello/orgs', 'repos_url': 'https://api.github.com/users/Hibyehello/repos', 'events_url': 'https://api.github.com/users/Hibyehello/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Hibyehello/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'created_at': '2026-02-02T21:17:41Z', 'updated_at': '2026-02-02T21:17:41Z', 'body': 'Tested on an M4 MacBook with no problems', 'author_association': 'CONTRIBUTOR', 'reactions': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments/3837468646/reactions', 'total_count': 0, '+1': 0, '-1': 0, 'laugh': 0, 'hooray': 0, 'confused': 0, 'heart': 0, 'rocket': 0, 'eyes': 0}, 'performed_via_github_app': None}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T20:05:56Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14576, 'watchers_count': 14576, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14576, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'Hibyehello', 'id': 36666883, 'node_id': 'MDQ6VXNlcjM2NjY2ODgz', 'avatar_url': 'https://avatars.githubusercontent.com/u/36666883?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/Hibyehello', 'html_url': 'https://github.com/Hibyehello', 'followers_url': 'https://api.github.com/users/Hibyehello/followers', 'following_url': 'https://api.github.com/users/Hibyehello/following{/other_user}', 'gists_url': 'https://api.github.com/users/Hibyehello/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/Hibyehello/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/Hibyehello/subscriptions', 'organizations_url': 'https://api.github.com/users/Hibyehello/orgs', 'repos_url': 'https://api.github.com/users/Hibyehello/repos', 'events_url': 'https://api.github.com/users/Hibyehello/events{/privacy}', 'received_events_url': 'https://api.github.com/users/Hibyehello/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-02T21:07:21.898039	{'source': 'webserver', 'gh_type': 'pull_request', 'raw': {'action': 'opened', 'number': 14317, 'pull_request': {'url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14317', 'id': 3238193063, 'node_id': 'PR_kwDOALCn2M7BAuen', 'html_url': 'https://github.com/dolphin-emu/dolphin/pull/14317', 'diff_url': 'https://github.com/dolphin-emu/dolphin/pull/14317.diff', 'patch_url': 'https://github.com/dolphin-emu/dolphin/pull/14317.patch', 'issue_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14317', 'number': 14317, 'state': 'open', 'locked': False, 'title': 'Optimize JitBaseBlockCache::ErasePhysicalRange', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'body': 'This speeds up switching to and from cockpit view in Rogue Squadron 2 by maybe 5%. This scenario spends most time jitting, with jitting taking up 10x more time than block invalidation, but the time spent in block invalidation is non-trivial and I was able to find this low-hanging fruit there.', 'created_at': '2026-02-02T21:07:19Z', 'updated_at': '2026-02-02T21:07:19Z', 'closed_at': None, 'merged_at': None, 'merge_commit_sha': None, 'assignee': None, 'assignees': [], 'requested_reviewers': [], 'requested_teams': [], 'labels': [], 'milestone': None, 'draft': False, 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14317/commits', 'review_comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14317/comments', 'review_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14317/comments', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/c8180071a552720e5436e908afc4eda7e87be08c', 'head': {'label': 'JosJuice:jit-cache-macro-loop', 'ref': 'jit-cache-macro-loop', 'sha': 'c8180071a552720e5436e908afc4eda7e87be08c', 'user': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 26057138, 'node_id': 'MDEwOlJlcG9zaXRvcnkyNjA1NzEzOA==', 'name': 'dolphin', 'full_name': 'JosJuice/dolphin', 'private': False, 'owner': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/JosJuice/dolphin', 'description': 'Dolphin is a GameCube/Wii emulator, allowing you to play games for these two platforms on PC, with improvements.', 'fork': True, 'url': 'https://api.github.com/repos/JosJuice/dolphin', 'forks_url': 'https://api.github.com/repos/JosJuice/dolphin/forks', 'keys_url': 'https://api.github.com/repos/JosJuice/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/JosJuice/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/JosJuice/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/JosJuice/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/JosJuice/dolphin/events', 'assignees_url': 'https://api.github.com/repos/JosJuice/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/JosJuice/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/JosJuice/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/JosJuice/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/JosJuice/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/JosJuice/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/JosJuice/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/JosJuice/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/JosJuice/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/JosJuice/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/JosJuice/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/JosJuice/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/JosJuice/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/JosJuice/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/JosJuice/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/JosJuice/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/JosJuice/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/JosJuice/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/JosJuice/dolphin/merges', 'archive_url': 'https://api.github.com/repos/JosJuice/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/JosJuice/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/JosJuice/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/JosJuice/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/JosJuice/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/JosJuice/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/JosJuice/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/JosJuice/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/JosJuice/dolphin/deployments', 'created_at': '2014-11-01T17:12:40Z', 'updated_at': '2026-02-01T11:39:14Z', 'pushed_at': '2026-02-02T21:07:04Z', 'git_url': 'git://github.com/JosJuice/dolphin.git', 'ssh_url': 'git@github.com:JosJuice/dolphin.git', 'clone_url': 'https://github.com/JosJuice/dolphin.git', 'svn_url': 'https://github.com/JosJuice/dolphin', 'homepage': None, 'size': 509721, 'stargazers_count': 6, 'watchers_count': 6, 'language': 'C++', 'has_issues': False, 'has_projects': True, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 1, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 0, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': [], 'visibility': 'public', 'forks': 1, 'open_issues': 0, 'watchers': 6, 'default_branch': 'master', 'allow_squash_merge': True, 'allow_merge_commit': True, 'allow_rebase_merge': True, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, 'base': {'label': 'dolphin-emu:master', 'ref': 'master', 'sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'user': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'repo': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T20:05:56Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14576, 'watchers_count': 14576, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14576, 'default_branch': 'master', 'allow_squash_merge': False, 'allow_merge_commit': True, 'allow_rebase_merge': False, 'allow_auto_merge': False, 'delete_branch_on_merge': False, 'allow_update_branch': False, 'use_squash_pr_title_as_default': False, 'squash_merge_commit_message': 'COMMIT_MESSAGES', 'squash_merge_commit_title': 'COMMIT_OR_PR_TITLE', 'merge_commit_message': 'PR_TITLE', 'merge_commit_title': 'MERGE_MESSAGE'}}, '_links': {'self': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14317'}, 'html': {'href': 'https://github.com/dolphin-emu/dolphin/pull/14317'}, 'issue': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14317'}, 'comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/14317/comments'}, 'review_comments': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14317/comments'}, 'review_comment': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/comments{/number}'}, 'commits': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls/14317/commits'}, 'statuses': {'href': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/c8180071a552720e5436e908afc4eda7e87be08c'}}, 'author_association': 'MEMBER', 'auto_merge': None, 'active_lock_reason': None, 'merged': False, 'mergeable': None, 'rebaseable': None, 'mergeable_state': 'unknown', 'merged_by': None, 'comments': 0, 'review_comments': 0, 'maintainer_can_modify': True, 'commits': 1, 'additions': 10, 'deletions': 2, 'changed_files': 1}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T20:05:56Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14576, 'watchers_count': 14576, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 361, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 361, 'watchers': 14576, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JosJuice', 'id': 6716818, 'node_id': 'MDQ6VXNlcjY3MTY4MTg=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6716818?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JosJuice', 'html_url': 'https://github.com/JosJuice', 'followers_url': 'https://api.github.com/users/JosJuice/followers', 'following_url': 'https://api.github.com/users/JosJuice/following{/other_user}', 'gists_url': 'https://api.github.com/users/JosJuice/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JosJuice/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JosJuice/subscriptions', 'organizations_url': 'https://api.github.com/users/JosJuice/orgs', 'repos_url': 'https://api.github.com/users/JosJuice/repos', 'events_url': 'https://api.github.com/users/JosJuice/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JosJuice/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}
2026-02-02T20:05:54.683640	{'source': 'webserver', 'gh_type': 'check_suite', 'raw': {'action': 'requested', 'check_suite': {'id': 56183190815, 'node_id': 'CS_kwDOALCn2M8AAAANFMd1Hw', 'head_branch': 'master', 'head_sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'status': 'queued', 'conclusion': None, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin/check-suites/56183190815', 'before': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'after': 'c7de9162765452fc2787f3ba4f0092394067d603', 'pull_requests': [{'url': 'https://api.github.com/repos/MarioPartyNetplay/Dolphin-MPN/pulls/191', 'id': 3229846782, 'number': 191, 'head': {'ref': 'master', 'sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': 'bb679f420d6ea68263904009bebf3e8905f5c8e1', 'repo': {'id': 623850328, 'url': 'https://api.github.com/repos/MarioPartyNetplay/Dolphin-MPN', 'name': 'Dolphin-MPN'}}}, {'url': 'https://api.github.com/repos/Faster-Brawl/dolphin/pulls/5', 'id': 1222541325, 'number': 5, 'head': {'ref': 'master', 'sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': 'eb9e232680dfdc04ec2adab05dbc530b1e707fc9', 'repo': {'id': 577809188, 'url': 'https://api.github.com/repos/Faster-Brawl/dolphin', 'name': 'dolphin'}}}, {'url': 'https://api.github.com/repos/dirextric-auto/dolphin/pulls/1', 'id': 571486662, 'number': 1, 'head': {'ref': 'master', 'sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': '1e71904cb9930eccc036e2ce2360beaa746befa7', 'repo': {'id': 337848960, 'url': 'https://api.github.com/repos/dirextric-auto/dolphin', 'name': 'dolphin'}}}, {'url': 'https://api.github.com/repos/zurgeg/dolphin-vr-no-ovr/pulls/1', 'id': 518053317, 'number': 1, 'head': {'ref': 'master', 'sha': 'c7de9162765452fc2787f3ba4f0092394067d603', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'VR-Hydra', 'sha': 'ea9f95955e5335cc238b2f9c5a0a6dfdc8698aec', 'repo': {'id': 311469400, 'url': 'https://api.github.com/repos/zurgeg/dolphin-vr-no-ovr', 'name': 'dolphin-vr-no-ovr'}}}, {'url': 'https://api.github.com/repos/weihuoya/dolphin/pulls/1', 'id': 400884538, 'number': 1, 'head': {'ref': 'master', 'sha': '6711d77b9901ed0a4418203db49ed4d8576e6fe0', 'repo': {'id': 11577304, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'name': 'dolphin'}}, 'base': {'ref': 'master', 'sha': '0f4c971326ae9389b3ad55b0fefacb708d148f4d', 'repo': {'id': 143011855, 'url': 'https://api.github.com/repos/weihuoya/dolphin', 'name': 'dolphin'}}}], 'app': {'id': 49947, 'client_id': 'Iv1.1fcaea7644d8b727', 'slug': 'dolphin-ci', 'node_id': 'MDM6QXBwNDk5NDc=', 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'name': 'Dolphin CI', 'description': 'Continuous Integration setup for [dolphin-emu.org](https://dolphin-emu.org/).', 'external_url': 'https://github.com/dolphin-emu', 'html_url': 'https://github.com/apps/dolphin-ci', 'created_at': '2019-12-26T22:26:07Z', 'updated_at': '2019-12-26T22:33:19Z', 'permissions': {'checks': 'write', 'contents': 'read', 'issues': 'write', 'members': 'read', 'metadata': 'read', 'pull_requests': 'write', 'statuses': 'write'}, 'events': ['check_run', 'commit_comment', 'issue_comment', 'pull_request', 'pull_request_review', 'pull_request_review_comment', 'push']}, 'created_at': '2026-02-02T20:05:53Z', 'updated_at': '2026-02-02T20:05:53Z', 'rerequestable': True, 'runs_rerequestable': True, 'latest_check_runs_count': 0, 'check_runs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/check-suites/56183190815/check-runs', 'head_commit': {'id': 'c7de9162765452fc2787f3ba4f0092394067d603', 'tree_id': 'df1616469bb5667243b07405978fffcc84476e14', 'message': 'Merge pull request #14316 from JoshuaVandaele/resourcefix\n\nResourcePack: Fix loading resource packs', 'timestamp': '2026-02-02T20:05:51Z', 'author': {'name': 'JMC47', 'email': 'JMC4789@gmail.com'}, 'committer': {'name': 'GitHub', 'email': 'noreply@github.com'}}}, 'repository': {'id': 11577304, 'node_id': 'MDEwOlJlcG9zaXRvcnkxMTU3NzMwNA==', 'name': 'dolphin', 'full_name': 'dolphin-emu/dolphin', 'private': False, 'owner': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/dolphin-emu', 'html_url': 'https://github.com/dolphin-emu', 'followers_url': 'https://api.github.com/users/dolphin-emu/followers', 'following_url': 'https://api.github.com/users/dolphin-emu/following{/other_user}', 'gists_url': 'https://api.github.com/users/dolphin-emu/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/dolphin-emu/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/dolphin-emu/subscriptions', 'organizations_url': 'https://api.github.com/users/dolphin-emu/orgs', 'repos_url': 'https://api.github.com/users/dolphin-emu/repos', 'events_url': 'https://api.github.com/users/dolphin-emu/events{/privacy}', 'received_events_url': 'https://api.github.com/users/dolphin-emu/received_events', 'type': 'Organization', 'user_view_type': 'public', 'site_admin': False}, 'html_url': 'https://github.com/dolphin-emu/dolphin', 'description': 'Dolphin is a GameCube / Wii emulator, allowing you to play games for these two platforms on PC with improvements.', 'fork': False, 'url': 'https://api.github.com/repos/dolphin-emu/dolphin', 'forks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/forks', 'keys_url': 'https://api.github.com/repos/dolphin-emu/dolphin/keys{/key_id}', 'collaborators_url': 'https://api.github.com/repos/dolphin-emu/dolphin/collaborators{/collaborator}', 'teams_url': 'https://api.github.com/repos/dolphin-emu/dolphin/teams', 'hooks_url': 'https://api.github.com/repos/dolphin-emu/dolphin/hooks', 'issue_events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/events{/number}', 'events_url': 'https://api.github.com/repos/dolphin-emu/dolphin/events', 'assignees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/assignees{/user}', 'branches_url': 'https://api.github.com/repos/dolphin-emu/dolphin/branches{/branch}', 'tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/tags', 'blobs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/blobs{/sha}', 'git_tags_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/tags{/sha}', 'git_refs_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/refs{/sha}', 'trees_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/trees{/sha}', 'statuses_url': 'https://api.github.com/repos/dolphin-emu/dolphin/statuses/{sha}', 'languages_url': 'https://api.github.com/repos/dolphin-emu/dolphin/languages', 'stargazers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/stargazers', 'contributors_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contributors', 'subscribers_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscribers', 'subscription_url': 'https://api.github.com/repos/dolphin-emu/dolphin/subscription', 'commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/commits{/sha}', 'git_commits_url': 'https://api.github.com/repos/dolphin-emu/dolphin/git/commits{/sha}', 'comments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/comments{/number}', 'issue_comment_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues/comments{/number}', 'contents_url': 'https://api.github.com/repos/dolphin-emu/dolphin/contents/{+path}', 'compare_url': 'https://api.github.com/repos/dolphin-emu/dolphin/compare/{base}...{head}', 'merges_url': 'https://api.github.com/repos/dolphin-emu/dolphin/merges', 'archive_url': 'https://api.github.com/repos/dolphin-emu/dolphin/{archive_format}{/ref}', 'downloads_url': 'https://api.github.com/repos/dolphin-emu/dolphin/downloads', 'issues_url': 'https://api.github.com/repos/dolphin-emu/dolphin/issues{/number}', 'pulls_url': 'https://api.github.com/repos/dolphin-emu/dolphin/pulls{/number}', 'milestones_url': 'https://api.github.com/repos/dolphin-emu/dolphin/milestones{/number}', 'notifications_url': 'https://api.github.com/repos/dolphin-emu/dolphin/notifications{?since,all,participating}', 'labels_url': 'https://api.github.com/repos/dolphin-emu/dolphin/labels{/name}', 'releases_url': 'https://api.github.com/repos/dolphin-emu/dolphin/releases{/id}', 'deployments_url': 'https://api.github.com/repos/dolphin-emu/dolphin/deployments', 'created_at': '2013-07-22T09:07:57Z', 'updated_at': '2026-02-02T18:49:39Z', 'pushed_at': '2026-02-02T20:05:51Z', 'git_url': 'git://github.com/dolphin-emu/dolphin.git', 'ssh_url': 'git@github.com:dolphin-emu/dolphin.git', 'clone_url': 'https://github.com/dolphin-emu/dolphin.git', 'svn_url': 'https://github.com/dolphin-emu/dolphin', 'homepage': 'https://dolphin-emu.org', 'size': 544012, 'stargazers_count': 14576, 'watchers_count': 14576, 'language': 'C++', 'has_issues': False, 'has_projects': False, 'has_downloads': True, 'has_wiki': True, 'has_pages': False, 'has_discussions': False, 'forks_count': 2966, 'mirror_url': None, 'archived': False, 'disabled': False, 'open_issues_count': 360, 'license': {'key': 'other', 'name': 'Other', 'spdx_id': 'NOASSERTION', 'url': None, 'node_id': 'MDc6TGljZW5zZTA='}, 'allow_forking': True, 'is_template': False, 'web_commit_signoff_required': False, 'topics': ['cpp', 'emulation', 'emulator', 'gamecube', 'wii'], 'visibility': 'public', 'forks': 2966, 'open_issues': 360, 'watchers': 14576, 'default_branch': 'master', 'custom_properties': {}}, 'organization': {'login': 'dolphin-emu', 'id': 5050316, 'node_id': 'MDEyOk9yZ2FuaXphdGlvbjUwNTAzMTY=', 'url': 'https://api.github.com/orgs/dolphin-emu', 'repos_url': 'https://api.github.com/orgs/dolphin-emu/repos', 'events_url': 'https://api.github.com/orgs/dolphin-emu/events', 'hooks_url': 'https://api.github.com/orgs/dolphin-emu/hooks', 'issues_url': 'https://api.github.com/orgs/dolphin-emu/issues', 'members_url': 'https://api.github.com/orgs/dolphin-emu/members{/member}', 'public_members_url': 'https://api.github.com/orgs/dolphin-emu/public_members{/member}', 'avatar_url': 'https://avatars.githubusercontent.com/u/5050316?v=4', 'description': ''}, 'sender': {'login': 'JMC47', 'id': 6598209, 'node_id': 'MDQ6VXNlcjY1OTgyMDk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/6598209?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/JMC47', 'html_url': 'https://github.com/JMC47', 'followers_url': 'https://api.github.com/users/JMC47/followers', 'following_url': 'https://api.github.com/users/JMC47/following{/other_user}', 'gists_url': 'https://api.github.com/users/JMC47/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/JMC47/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/JMC47/subscriptions', 'organizations_url': 'https://api.github.com/users/JMC47/orgs', 'repos_url': 'https://api.github.com/users/JMC47/repos', 'events_url': 'https://api.github.com/users/JMC47/events{/privacy}', 'received_events_url': 'https://api.github.com/users/JMC47/received_events', 'type': 'User', 'user_view_type': 'public', 'site_admin': False}, 'installation': {'id': 35437981, 'node_id': 'MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMzU0Mzc5ODE='}}, 'type': 'raw_gh_hook'}