Compare commits

..

150 commits

Author SHA1 Message Date
ergo720
dd36dd598c
Merge pull request #2484 from ergo720/update_sdl
Updated SDL submodule to version 2.30.11
2025-03-31 13:08:49 +02:00
ergo720
87634a2e27 Updated SDL submodule to version 2.30.11 2025-03-30 10:27:23 +02:00
Luke Usher
6f32d89545
Merge pull request #2474 from Margen67/build
cmake: Replace /Ob2 with /Ob3
2024-12-23 08:55:15 +00:00
Margen67
ec0c288bc4 cmake: Replace /Ob2 with /Ob3
See https://learn.microsoft.com/en-us/cpp/build/reference/ob-inline-function-expansion
2024-12-17 00:34:09 -08:00
RadWolfie
8bfbcb56fd
Merge pull request #2473 from Margen67/w11
Disable rounded corners on Windows 11
2024-12-17 01:59:01 -06:00
Margen67
8965d2443b Remove rounded corners on Windows 11 2024-12-16 23:48:10 -08:00
RadWolfie
b33ed95c5b
Merge pull request #2472 from Margen67/ci2
CI: Upgrade actions
2024-12-16 22:15:06 -06:00
Margen67
8ee17b512c CI: Update actions 2024-12-16 19:39:48 -08:00
RadWolfie
50334cbc31
Merge pull request #2469 from Margen67/subhook
Replace subhook with working mirror
2024-12-13 14:52:09 -06:00
Margen67
41454b8c26 Replace subhook with working mirror 2024-12-13 12:41:38 -08:00
Luke Usher
204dcf8801
Merge pull request #2462 from RadWolfie/file-minor-fixes
File minor fixes
2024-08-25 10:21:29 +01:00
RadWolfie
77c63ceec3 kernel: fix dashupdate titles attempt delete new files
NOTE: Partition2.bin needs to be emulated in order for copy files to partition and fatx metadata.
2024-08-18 08:39:16 -05:00
RadWolfie
2cfaba893e kernel: fix Exhibition Demo discs problem for copy soundtracks onto hdd (require force santion) 2024-08-18 08:39:16 -05:00
Luke Usher
17b0cb81d4 CI: Specify minimum platform and SDK version 2024-07-08 15:19:54 +01:00
RadWolfie
daa6a816ff Merge experimental chihiro branch 2024-07-05 12:19:54 -05:00
Luke Usher
6caf3ea679 chihiro: prevent JVS register updates from being missed due to long delays
This really needs a better solution, but for now, this will do.
2024-07-05 12:19:54 -05:00
Luke Usher
9a58823b70 chihiro: emulate a chihiro system when boot.id is present 2024-07-05 12:19:54 -05:00
Luke Usher
3edd8d168b chihiro: fix an issue where media board detection failed due to instant response time 2024-07-05 12:19:54 -05:00
RadWolfie
f894d31332 Cleanly rebase chihiro-work on develop
Co-authored-by: Luke Usher <luke.usher@outlook.com>
Co-authored-by: wutno <aaron@installgentoo.net>
Co-authored-by: RadWolfie <RadWolfie@users.noreply.github.com>
2024-07-05 12:19:54 -05:00
RadWolfie
9241bec768 Merge ergo720 less_busy_loops branch 2024-07-05 12:19:53 -05:00
RadWolfie
c50a0c5c7d Merge EmuX86 passive branch 2024-07-05 12:18:57 -05:00
Luke Usher
87bab04932 EmuX86: Let invalid memory accesses trigger a warning rather than a fatal error
This seems to resolve most regressions we have had in recent history.
2024-07-05 12:18:54 -05:00
ergo720
ad6769bbf3 Never change the thread priority on the host and the disable boost flag too
This fixes almost all the games that were broken in this branch
2024-07-05 10:58:05 -05:00
ergo720
0e63131fc3 Use a DPC for expired timers + don't execute NV2A DPCs from the timer thread to avoid the exception overhead 2024-07-05 10:58:05 -05:00
ergo720
889040c56a Fixed an issue in WaitApc where the wait block was not removed when using a zero timeout or when satisfied by a user APC + properly lock the wait block operations to avoid a race between SatisfyWait and KiTimerExpiration 2024-07-05 10:58:05 -05:00
ergo720
86542c9f2e Implemented PTIMER alarm interrupt of NV2A + fixed a bug in timer_init
This fixes DOAU showing the dirty disk error in PAL50 mode
2024-07-05 10:58:05 -05:00
ergo720
c9edbd1003 Fixed wrong nv2a clock frequency
This is accessed by DOAU via PTIMER only in PAL50 mode
2024-07-05 10:58:05 -05:00
ergo720
ebb122f2a0 Fixed a bug in KeTickCount + check all timer indices when we are late in KiClockIsr
This almost completely fixes the slowness in Panzer Dragoon Orta
2024-07-05 10:58:04 -05:00
ergo720
c158a472ff Make sure to reset WaitStatus when a new wait starts
This fixes an issue in Panzer Dragoon Orta, where KeDelayExecutionThread would return X_STATUS_TIMEOUT | X_STATUS_USER_APC
2024-07-05 10:58:04 -05:00
ergo720
6961d1c7a1 Make sure that GetNativeHandle succeeds before attempting to get the native handle
This fixes a sporadic crash in Panzer Dragoon Orta, where the title calls KeSetBasePriorityThread on a thread that has already terminated
2024-07-05 10:58:04 -05:00
ergo720
2f7cfe7e95 Fixed a bug in KiInsertTimerTable + log all objects being waited on in NtWaitForMultipleObjectsEx
This fixes a crash in Metal Slug 3
2024-07-05 10:58:04 -05:00
ergo720
46d0173673 Account for partial milliseconds in KiClockIsr
This fixes the slowness in The lord of the rings: the third era
2024-07-05 10:58:04 -05:00
ergo720
c7b028b3e7 Fixed a race condition in WaitApc + removed wrong InsertTailList for ktimers used during a timeout
This fixes almost all broken games in this branch. Still broken: PDO: 1 fps vs 10 fps, DOA3: freezes after title screen, Lord of the rings The third era: Unable to determine default Xbox backbuffer error???
2024-07-05 10:58:04 -05:00
ergo720
3d12edc77d Always create a wait object even when we satisfy the wait on the host side + fixed a bug in KiWaitTestNoYield
This fixes an occasionl freeze in Steel Battalion + the slowness in JSRF
2024-07-05 10:58:04 -05:00
ergo720
08ab4b9164 Revert to using the host to do thread suspension 2024-07-05 10:58:04 -05:00
ergo720
4fca5c7007 Hack: <= thread priority instead of >= 2024-07-05 10:58:04 -05:00
ergo720
e26f20108a Setup a KTIMER for the other functions using WaitApc too 2024-07-05 10:58:04 -05:00
ergo720
8475124e5b Restore single interrupt loop in update_non_periodic_events 2024-07-05 10:58:03 -05:00
ergo720
9b2ae106e5 Place nvnet in its own thread 2024-07-05 10:58:03 -05:00
ergo720
b3bfeca3a8 Use get_now directly in system_events instead of qpc 2024-07-05 10:58:03 -05:00
ergo720
b77a13b708 Adjust KeSystemTime when the host system time is changed by the user 2024-07-05 10:58:03 -05:00
ergo720
1b5e111ae3 Account for delays between calls to KiClockIsr
This fixes the slowness in the dashboard
2024-07-05 10:58:03 -05:00
ergo720
1504a75a46 Raise priority of system events thread 2024-07-05 10:58:03 -05:00
ergo720
87496ab873 Removed delta amount added to KeSystemTime 2024-07-05 10:58:03 -05:00
ergo720
5b37a7ec21 Fixed thread order initialization when a thread starts suspended 2024-07-05 10:58:03 -05:00
ergo720
639f42c318 Make sure to hold the DPC lock until the DPC list has been emptied
This fixes a crash in Lord of the rings: The fellowship of the ring
2024-07-05 10:58:03 -05:00
ergo720
8d92992a6b Implemented kernel unwait routines + updated/fixed KeWaitForMultipleObjects and KeWaitForSingleObject 2024-07-05 10:58:02 -05:00
ergo720
7323eed73e Only change the priority of a thread if it is being set above normal 2024-07-05 10:58:02 -05:00
ergo720
b47c1f195c Unpatch D3DDevice_BlockUntilVerticalBlank and D3DDevice_SetVerticalBlankCallback 2024-07-05 10:58:02 -05:00
ergo720
7c73bfc525 Avoid triggering multiple gpu interrupts outside the vblank 2024-07-05 10:58:02 -05:00
ergo720
1b4a3bb54f Moved position of ObfDereferenceObject in NtSuspendThread 2024-07-05 10:58:02 -05:00
ergo720
750d202fa8 Removed scaling hack in KeInterruptTime and KeTickCount + added yield in system_events routine
This fixes the stuttering in Halo 2, Metal slug 3, JSRF and restores PDO, PSO to the same state as in master
2024-07-05 10:58:02 -05:00
ergo720
e7bca5e1bf Implemented suspend/resume kernel Nt routines with the corresponding Ke routines 2024-07-05 10:58:01 -05:00
ergo720
937ab9e1c2 Fixed a bug in KeSetBasePriorityThread 2024-07-05 10:58:01 -05:00
ergo720
8006f55cf3 Merge many different periodic events in a single thread, instead of each having its own busy loop
This merges vblank, ohci's eof, pit interrupt, dsound sync and async workers, nvnet packet processing and system interrupt
2024-07-05 10:58:01 -05:00
ergo720
1828ddfd6f Merge lle and hle vblank routines in a single thread 2024-07-05 10:58:00 -05:00
ergo720
bc42cfaa6b Removed unnecessary lock in the interrupt thread 2024-07-05 10:58:00 -05:00
PatrickvL
b1235b7733 Merge pull request #2458 from LukeUsher/fix-compilation-vs2022 2024-07-05 10:41:18 -05:00
Luke Usher
1615ecc976 fix the build on vs2022 17.9.1 2024-05-22 12:46:03 +01:00
Luke Usher
0007d20b03
Merge pull request #2445 from medievil1/fog-stuff
fog stuff
2024-05-22 12:42:07 +01:00
RadWolfie
bfb10092c0
Merge pull request #2451 from RadWolfie/fix-hardware-model
Fix hardware model conversions + use respective hardware model based on console type
2024-02-08 12:48:46 -06:00
Luke Usher
f5b4878245
Merge pull request #2452 from medievil1/my-master
fix incorrect setting in pixel shader template
2024-02-04 19:47:12 +00:00
RadWolfie
4174fbc23f review remarks 2024-02-04 13:29:40 -06:00
medievil1
0560ed6955 change clamp to saturate in shader templates
per discussions
2024-02-03 14:46:54 -05:00
medievil1
875015164c fix incorrect setting in pixel shader template
it should be normal2, not normal 3...
PS_TEXTUREMODES_DOT_RFLCT_DIFF(ts) works in conjunction with #define PS_TEXTUREMODES_DOT_RFLCT_SPEC(ts)(after) and PS_TEXTUREMODES_DOTPRODUCT(ts)(before)
dotproduct uses it's own product/normal, dot_rflct uses dotproduct and it's own, and dot_spec uses the previous 2 and it's own ... this corrects light reflection on the floor in Halo
2024-02-03 14:38:16 -05:00
medievil1
aedb5ba87b address review comment
remove PB pr sampling adjustments
2024-02-03 12:08:00 -05:00
RadWolfie
e5dcdebe7f device: corrected conversions based on hardware model 2024-02-03 02:45:37 -06:00
medievil1
ad0b8340da change flow of fog table/enable
per Jack, change flow to :
if fog is disabled, avoid table code and just set fogFactor to 1
2024-01-24 00:34:47 -05:00
medievil1
7298b6c4dc address review comment
fix inadvertently not changing pixel shader pass to template c reg info
2024-01-23 21:45:15 -05:00
medievil1
b20db36e15 fix template formatting
hopefully it all matches now

Revert "fix template formatting"

This reverts commit 79aa4436ef330e8754755044ce3ebf9549c557ae.

fix template formatting

hopefully it all matches now
3rd times a charm  hopefully...lol
2024-01-21 14:52:36 -05:00
medievil1
a03d50df56 add FF fog move to Pixelshader 2024-01-20 15:14:02 -05:00
medievil1
0e25897f77 address review comments 2024-01-19 00:56:03 -05:00
medievil1
c6049b768c address review comments 2024-01-18 09:30:07 -05:00
medievil1
399baccadb fog stuff
fix no fog issue which was incorrectly passing data into iFog instead of 1 when fog was disabled

testing a move to pixel shader

adjust passthrough template

for move to pixel shader

remove printf
2024-01-18 01:29:15 -05:00
ergo720
3edc388abf
Merge pull request #2444 from RadWolfie/update-time-api
Fix RtlTimeFieldsToTime and RtlTimeToTimeFields implements
2024-01-15 15:38:03 +01:00
RadWolfie
796e8d2beb kernel: change 1000 to MSECSPERSEC 2024-01-15 05:19:57 -06:00
RadWolfie
282c5f5622 kernel: RtlTimeToTimeFields no longer need to be logged 2024-01-13 16:25:58 -06:00
RadWolfie
8e5b27d054 kernel: fix RtlTimeToTimeFields implement to match test results 2024-01-13 16:22:05 -06:00
RadWolfie
b64a3b6faa kernel: fix comment typo in RtlTimeToTimeFields 2024-01-13 16:18:23 -06:00
RadWolfie
d64e172c9f kernel: RtlTimeFieldsToTime no longer need to be logged 2024-01-05 01:26:08 -06:00
RadWolfie
d6b96b8ea1 kernel: clean up RtlTimeFieldsToTime bad indents 2024-01-05 01:19:10 -06:00
RadWolfie
131b330a85 kernel: Change RtlTimeFieldsToTime's Time format into more readable math operation 2024-01-05 01:17:43 -06:00
RadWolfie
bf6193202a kernel: split RtlTimeFieldsToTime range check into their own if statements for clear reading
Plus fixed a bug for leap year's day range
2024-01-05 01:15:15 -06:00
RadWolfie
06c28a847e Revert "Merge pull request #2441 from ergo720/time_fix"
This reverts commit 8cc9c73f58.
2024-01-05 00:33:34 -06:00
PatrickvL
8cc9c73f58
Merge pull request #2441 from ergo720/time_fix 2023-12-30 09:53:52 +01:00
ergo720
2452965580 Fixed a bug in RtlTimeFieldsToTime 2023-12-29 14:00:15 +01:00
PatrickvL
eddc14e151
Merge pull request #2439 from NZJenkins/hotload_shaders 2023-12-29 11:43:42 +01:00
Luke Usher
31ff15ba1d
Merge pull request #2440 from RadWolfie/fix-xkts-issues
Fix Xbox Kernel Test Suite issues discovered
2023-12-21 15:29:44 +00:00
RadWolfie
4d9151ca26 rtl: remove unnecessary double setter in RtlInitUnicodeString 2023-12-20 09:31:35 -06:00
RadWolfie
1f1d1ac631 rtl: fix RtlCompareString and RtlCompareUnicodeString to match with kernel test suite 2023-12-17 20:50:46 -06:00
RadWolfie
e5043dbc05 fix UNICODE_STRING's Buffer variable type issue 2023-12-17 20:49:10 -06:00
RadWolfie
a2fb41856d kernel: fix RtlWalkFrameChain according to xbox kernel test suite failed test 2023-12-17 19:02:20 -06:00
RadWolfie
b09d3ca69a kernel: update RtlAnsiStringToUnicodeString to include error log returns 2023-12-17 11:41:12 -06:00
Anthony
684d3338f2 Always copy hlsl files if they changed in the sources
Require files to be explicitly declared in CXBXR_HEADER_EMU in order to be copied
2023-11-18 11:34:05 +13:00
Anthony
ae140bb6bf Copy and install hlsl files in the cxbx project 2023-11-16 23:00:18 +13:00
Anthony
a5b8f15a14 reformat todo 2023-11-12 20:04:39 +13:00
Anthony
2c8a764fc7 Save a backup copy of hlsl files 2023-11-12 19:56:20 +13:00
Anthony
605271245c review comments 2023-11-11 22:19:46 +13:00
Anthony
93e36f7be3 Rename shaderhlsl to shadersources
and updateshaders to update
2023-11-11 01:08:34 +13:00
Anthony
a7bc6a307d fixup remove unused variable 2023-11-11 01:08:34 +13:00
Anthony
7ad047bcea reduce crashing if the shaders are broken and get hotloaded 2023-11-11 01:08:34 +13:00
Anthony
0f21e25d7d Support hotloading pixelshaders 2023-11-11 01:08:34 +13:00
Anthony
79884bdf3d Move shader hlsl management into Shader.cpp
- g_ShaderHlsl keeps track of hlsl
- VS and PS source their hlsl from g_ShaderHlsl
2023-11-11 01:08:34 +13:00
Anthony
260e2fb7c8 Ensure filewatcher is closed if something goes wrong
and avoid creating multiple watchers
2023-11-11 01:08:34 +13:00
Anthony
4d221c3c81 tidy vertex shader loading 2023-11-11 01:08:34 +13:00
Anthony
712d3bee2f move passthrough shader to a file 2023-11-11 01:08:34 +13:00
Anthony
3cd551d827 Reload/recompile vertex shaders hlsl if they change 2023-11-11 01:08:34 +13:00
Anthony
397f33143d Rename VertexShaderSource to VertexShaderCache
because it was a weird name
2023-11-11 01:01:04 +13:00
ergo720
c7e75d7c5c
Merge pull request #2432 from Margen67/ci
CI: Upgrade checkout to v4
2023-10-21 16:19:43 +02:00
NZJenkins
4808be65c4
Slightly reduce build time (#2437) 2023-10-16 08:51:56 +02:00
Margen67
def10ff466
CI: Upgrade checkout to v4 2023-09-20 01:28:42 -07:00
Luke Usher
e1ea10c4cb
Merge pull request #2428 from jackchentwkh/fix_pushbuffer_subroutine
fix COMMAND_TYPE_CALL pushbuffer command handling
2023-09-03 21:45:52 +01:00
Jack Chen
67f21d5c30 reset subr_active flag to indicate we've returned from any COMMAND_TYPE_CALL command.
NV2A used COMMAND_TYPE_JUMP_LONG to return from COMMAND_TYPE_CALL.
Otogi uses lot's of COMMAND_TYPE_CALL, this should inprove the pushbuffer handling.
2023-09-03 20:42:07 +08:00
PatrickvL
971318a89a
Merge pull request #2426 from Margen67/ci
CI: Fix output
2023-07-27 13:56:36 +02:00
Margen67
b62d39da7d CI: Fix output
set-output is deprecated: https://github.blog/changelog/2023-07-24-github-actions-update-on-save-state-and-set-output-commands/
2023-07-25 16:46:37 -07:00
Luke Usher
6c530fbf86
Merge pull request #2418 from RadWolfie/fix-reboot-non-ansii-path
kernel: fix non-ansii file path conversion for reboot process
2023-03-13 12:41:17 +00:00
RadWolfie
a8f6d0496e kernel: fix non-ansii file path conversion for reboot process 2023-03-08 18:17:07 -06:00
Luke Usher
6389cb6524
Merge pull request #2417 from RadWolfie/d3d-update
Update XbSymbolDatabase fix and add LTCG patch missing
2023-03-06 14:50:23 +00:00
RadWolfie
ef3439e46f d3d8: fix GTA: SA bug and add LTCG patch for D3DDevice_DrawVertices variant
Plus other variant LTCG patches that only does logging. And symbol renames.
2023-03-06 05:47:39 -06:00
ergo720
ed8a6124e4
Merge pull request #2416 from ergo720/dpc_recursion_fix
Dpc recursion fix
2023-03-02 23:32:30 +01:00
ergo720
b1bd9dd5d0
Merge pull request #2414 from RadWolfie/fix-hacked-ob-handle-return
kernel: fix hacked windows handle check to bypass special handle of current process
2023-03-02 23:04:40 +01:00
ergo720
062752e1a7 Review remarks 2023-03-02 22:53:18 +01:00
ergo720
99ab34ac82 Fixed dpc recursion bug 2023-03-02 14:59:10 +01:00
RadWolfie
8e0df988a8 kernel: fix hacked windows handle check to bypass special handle of current process 2023-03-01 17:04:42 -06:00
ergo720
58041c95b4 Clean up unused dpc event member variable 2023-03-01 22:43:42 +01:00
RadWolfie
827a3212f8
Merge pull request #2413 from ergo720/priority_thread
Fixed a bug in KeSetDisableBoostThread
2023-03-01 10:14:34 -06:00
ergo720
d0890d588d Fixed a bug in KeSetDisableBoostThread 2023-03-01 17:10:24 +01:00
Luke Usher
9e9d3f390f
Merge pull request #2412 from ergo720/priority_thread
Fix a priority bug in KeQueryBasePriorityThread and KeSetBasePriorityThread
2023-03-01 16:04:57 +00:00
ergo720
4821a72b6f Set/query the priority of the requested thread, instead of the current one in KeQueryBasePriorityThread and KeSetBasePriorityThread 2023-03-01 16:48:30 +01:00
Luke Usher
bf1483ae56
Merge pull request #2411 from RadWolfie/update-xbsdb
lib: sync XbSymbolDatabase
2023-03-01 09:15:15 +00:00
RadWolfie
b2f05b8b0b lib: sync XbSymbolDatabase 2023-02-27 04:37:02 -06:00
ergo720
111728f170
Merge pull request #2408 from LukeUsher/avoid-region-patching
cxbxkrnl: avoid region patching loaded titles
2023-02-10 18:35:28 +01:00
ergo720
ce05ea1397
Merge pull request #2409 from RadWolfie/fix-emulation-status
Fix false positive emulation is either still running or stopped
2023-02-10 18:17:12 +01:00
RadWolfie
f4488c0270 fix false positive emulation is either still running or stopped 2023-02-10 07:16:12 -06:00
Luke Usher
65a5ad6591 cxbxkrnl: avoid region patching loaded titles
While it sounds ideal from a UX standpoint, region patching does break a number of
titles that would otherwise work.

Instead, show a warning that it may not be compatible with instructions on how to
configure region settings in eeprom.

Allow the user to attempt to run the title anyway, if the game does not do it's own
region checking, it will most likely just work regardless.
2023-02-07 22:08:31 +00:00
RadWolfie
0b695637ce
Merge pull request #2406 from LukeUsher/fix-xbe-version-reporting
xbe: fix version number formatting
2022-11-20 06:24:51 -06:00
Luke Usher
fe9a706a8e xbe: fix version number formatting 2022-11-19 16:06:12 +00:00
Luke Usher
8ac5d14cd2
Merge pull request #2401 from medievil1/new-master
correct hemisphere formula
2022-10-13 14:39:59 +01:00
Luke Usher
628323218a
Merge pull request #2404 from CookiePLMonster/remove-int32x32To64
Remove Int32x32To64 from the code
2022-10-06 22:41:51 +01:00
Silent
f7c09ddc4f
Remove Int32x32To64 as it's potentially harmful 2022-10-06 23:07:37 +02:00
ergo720
09e744ecc4
Merge pull request #2403 from RadWolfie/libusb-fixes
HOTFIX: Fix LibusbDevice's initialization process
2022-10-06 00:36:31 +02:00
RadWolfie
a37124e2dc readme: create a link to libusb's driver installation section to find suggested driver 2022-10-05 17:09:17 -05:00
RadWolfie
5f58ae918c input: have libusb_claim_interface's return actually give err number than comparsion check for non-zero 2022-10-05 16:46:17 -05:00
RadWolfie
f17b7f7fa6 input: check for error from libusb_open and report as device invalid 2022-10-05 16:40:39 -05:00
RadWolfie
4dccf6d5b9 input: don't override device's vendor id, expected to do comparsion instead 2022-10-05 06:42:56 -05:00
medievil1
6bbe6cefe8 added notes
changed a couple things around and added notes

correct hemisphere formula

correct one entry

make twointoone shorter

Per PatrickvL's suggestion and code
2022-09-25 19:32:25 -04:00
PatrickvL
547c3ae663
Merge pull request #2398 from LukeUsher/xxh3-hash 2022-09-24 07:26:34 +02:00
PatrickvL
c594e34ac5
Merge pull request #2400 from jarupxx/dialog 2022-09-24 07:24:38 +02:00
jarupxx
caae99952c Fixed a Folder select dialog 2022-09-24 04:51:55 +09:00
Luke Usher
aeeb67dc6a hasher: use xxh3 exclusively 2022-09-14 13:51:05 +01:00
105 changed files with 4544 additions and 2206 deletions

112
.github/labeler.yml vendored
View file

@ -1,75 +1,111 @@
# Labels are in alphabetical order.
cmake:
- 'CMake*'
- '**/CMakeLists.txt'
- '**/*.cmake'
- changed-files:
- any-glob-to-any-file:
- 'CMake*'
- '**/CMakeLists.txt'
- '**/*.cmake'
cpu-emulation:
- 'src/devices/x86/**'
- changed-files:
- any-glob-to-any-file:
- 'src/devices/x86/**'
deployment:
- '*.yml'
- '.github/workflows/CI.yml'
- changed-files:
- any-glob-to-any-file:
- '*.yml'
- '.github/workflows/CI.yml'
file-system:
- 'src/core/kernel/support/EmuFile*'
- changed-files:
- any-glob-to-any-file:
- 'src/core/kernel/support/EmuFile*'
graphics:
- 'src/core/hle/D3D8/**'
- 'src/core/hle/XGRAPHIC/**'
- 'src/devices/video/**'
- 'src/gui/*Video*'
- changed-files:
- any-glob-to-any-file:
- 'src/core/hle/D3D8/**'
- 'src/core/hle/XGRAPHIC/**'
- 'src/devices/video/**'
- 'src/gui/*Video*'
HLE:
- 'src/core/hle/**'
- 'src/core/kernel/**'
- changed-files:
- any-glob-to-any-file:
- 'src/core/hle/**'
- 'src/core/kernel/**'
informational:
- '**/*Logging*'
- '**/*Logging*/**'
- '**/README.md'
- changed-files:
- any-glob-to-any-file:
- '**/*Logging*'
- '**/*Logging*/**'
- '**/README.md'
input:
- 'src/common/input/**'
- 'src/core/hle/XAPI/input/**'
- 'src/devices/usb/**'
- 'src/gui/controllers/**'
- 'src/gui/*Input*'
- changed-files:
- any-glob-to-any-file:
- 'src/common/input/**'
- 'src/core/hle/XAPI/input/**'
- 'src/devices/usb/**'
- 'src/gui/controllers/**'
- 'src/gui/*Input*'
kernel:
- 'src/core/kernel/**'
- changed-files:
- any-glob-to-any-file:
- 'src/core/kernel/**'
LLE:
- 'src/devices/**'
- changed-files:
- any-glob-to-any-file:
- 'src/devices/**'
memory:
- 'src/core/kernel/memory-manager/**'
- changed-files:
- any-glob-to-any-file:
- 'src/core/kernel/memory-manager/**'
networking:
- 'src/core/hle/XONLINE/**'
- 'src/devices/network/**'
- 'src/gui/*Network*'
- changed-files:
- any-glob-to-any-file:
- 'src/core/hle/XONLINE/**'
- 'src/devices/network/**'
- 'src/gui/*Network*'
sound:
- 'src/common/audio/**'
- 'src/core/hle/DSOUND/**'
- 'src/core/hle/XACTENG/**'
- 'src/devices/audio/**'
- 'src/gui/*Audio*'
- changed-files:
- any-glob-to-any-file:
- 'src/common/audio/**'
- 'src/core/hle/DSOUND/**'
- 'src/core/hle/XACTENG/**'
- 'src/devices/audio/**'
- 'src/gui/*Audio*'
modules:
- 'import/**'
- changed-files:
- any-glob-to-any-file:
- 'import/**'
threading:
- 'src/core/kernel/support/EmuFS*'
- changed-files:
- any-glob-to-any-file:
- 'src/core/kernel/support/EmuFS*'
timing:
- 'src/common/Timer*'
- changed-files:
- any-glob-to-any-file:
- 'src/common/Timer*'
user interface:
- 'src/core/common/imgui/*'
- 'src/gui/**'
- changed-files:
- any-glob-to-any-file:
- 'src/core/common/imgui/*'
- 'src/gui/**'
xbdm:
- 'src/common/xbdm/**'
- changed-files:
- any-glob-to-any-file:
- 'src/common/xbdm/**'

View file

@ -30,19 +30,21 @@ jobs:
fail-fast: false
matrix:
configuration: [Release, Debug]
vsver: [2022]
vsver: [2019]
winver: [7]
sdkver: [10.0.22621.0]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Generate CMake files
run: cmake -B build -A Win32
run: cmake -B build -A Win32,version=${{ matrix.sdkver }} -DCMAKE_SYSTEM_VERSION=${{ matrix.winver }}
- name: Build
run: cmake --build build --config ${{ matrix.configuration }} -j $env:NUMBER_OF_PROCESSORS
- name: Prepare artifacts
if: matrix.configuration == 'Release'
run: cmake --install build --config ${{ matrix.configuration }} --prefix artifacts
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: matrix.configuration == 'Release'
with:
name: CxbxReloaded-${{ matrix.configuration }}-VS${{ matrix.vsver }}
@ -57,9 +59,9 @@ jobs:
needs: build-windows
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Re-zip artifacts
@ -72,7 +74,7 @@ jobs:
exit 1
fi
done
echo "::set-output name=tag_name::CI-${GITHUB_SHA::7}"
echo "tag_name=CI-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -7,9 +7,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Automatically close issues that don't follow the issue template
uses: ergo720/auto-close-issues@v1.0.4
uses: ergo720/auto-close-issues@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-close-message: "@${issue.user.login}: your issue has been automatically closed because it does not follow the issue template." # optional property

View file

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Labeler
uses: actions/labeler@v4
uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
sync-labels: true

6
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "import/subhook"]
path = import/subhook
url = https://github.com/Zeex/subhook.git
url = https://github.com/Cxbx-Reloaded/subhook.git
shallow = true
[submodule "import/cs_x86"]
path = import/cs_x86
@ -43,3 +43,7 @@
[submodule "import/nv2a_vsh_cpu"]
path = import/nv2a_vsh_cpu
url = https://github.com/abaire/nv2a_vsh_cpu.git
[submodule "import/mio"]
path = import/mio
url = https://github.com/mandreyel/mio.git
shadow = true

View file

@ -22,6 +22,8 @@ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/XbSymbolDatabase")
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/SDL2" EXCLUDE_FROM_ALL)
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/mio" EXCLUDE_FROM_ALL)
# Cxbx-Reloaded projects
set(CXBXR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR})
@ -77,7 +79,6 @@ file (GLOB CXBXR_HEADER_COMMON
"${CXBXR_ROOT_DIR}/src/common/util/cliConfig.hpp"
"${CXBXR_ROOT_DIR}/src/common/util/cliConverter.hpp"
"${CXBXR_ROOT_DIR}/src/common/util/CPUID.h"
"${CXBXR_ROOT_DIR}/src/common/util/crc32c.h"
"${CXBXR_ROOT_DIR}/src/common/util/CxbxUtil.h"
"${CXBXR_ROOT_DIR}/src/common/util/std_extend.hpp"
"${CXBXR_ROOT_DIR}/src/common/util/strConverter.hpp"
@ -144,6 +145,7 @@ file (GLOB CXBXR_HEADER_EMU
"${CXBXR_ROOT_DIR}/src/core/common/video/RenderBase.hpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/CxbxPixelShaderTemplate.hlsl"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/CxbxVertexShaderTemplate.hlsl"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/CxbxVertexShaderPassthrough.hlsl"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/Direct3D9.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsl"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsli"
@ -152,7 +154,7 @@ file (GLOB CXBXR_HEADER_EMU
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/PixelShader.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/Shader.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShader.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShaderSource.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShaderCache.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/WalkIndexBuffer.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/FixedFunctionState.h"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/ResourceTracker.h"
@ -252,9 +254,7 @@ file (GLOB CXBXR_SOURCE_COMMON
"${CXBXR_ROOT_DIR}/src/common/Settings.cpp"
"${CXBXR_ROOT_DIR}/src/common/util/cliConfig.cpp"
"${CXBXR_ROOT_DIR}/src/common/util/cliConverter.cpp"
"${CXBXR_ROOT_DIR}/src/common/util/crc32c.cpp"
"${CXBXR_ROOT_DIR}/src/common/util/CxbxUtil.cpp"
"${CXBXR_ROOT_DIR}/src/common/util/hasher.cpp"
"${CXBXR_ROOT_DIR}/src/common/win32/EmuShared.cpp"
"${CXBXR_ROOT_DIR}/src/common/win32/InlineFunc.cpp"
"${CXBXR_ROOT_DIR}/src/common/win32/IPCWindows.cpp"
@ -327,7 +327,7 @@ file (GLOB CXBXR_SOURCE_EMU
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/Shader.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/TextureStates.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShader.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShaderSource.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/VertexShaderCache.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/Direct3D9/WalkIndexBuffer.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/FixedFunctionState.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/D3D8/ResourceTracker.cpp"
@ -348,6 +348,7 @@ file (GLOB CXBXR_SOURCE_EMU
"${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/common/XbInternalDSVoice.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/common/XbInternalStruct.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/Intercept.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/JVS/JVS.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/Patches.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/XACTENG/XactEng.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/XGRAPHIC/XGraphic.cpp"
@ -378,6 +379,8 @@ file (GLOB CXBXR_SOURCE_EMU
"${CXBXR_ROOT_DIR}/src/core/kernel/support/NativeHandle.cpp"
"${CXBXR_ROOT_DIR}/src/core/kernel/support/PatchRdtsc.cpp"
"${CXBXR_ROOT_DIR}/src/devices/ADM1032Device.cpp"
"${CXBXR_ROOT_DIR}/src/devices/Chihiro/JvsIO.cpp"
"${CXBXR_ROOT_DIR}/src/devices/Chihiro/MediaBoard.cpp"
"${CXBXR_ROOT_DIR}/src/devices/EEPROMDevice.cpp"
"${CXBXR_ROOT_DIR}/src/devices/network/NVNetDevice.cpp"
"${CXBXR_ROOT_DIR}/src/devices/MCPXDevice.cpp"
@ -471,14 +474,19 @@ install(FILES ${cxbxr_INSTALL_files}
DESTINATION bin
)
install(FILES
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/CxbxPixelShaderTemplate.hlsl"
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsl"
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsli"
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli"
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl"
DESTINATION bin/hlsl
# Copy HLSL files to the output directory, which are loaded at runtime
set(CXBXR_HLSL_FILES ${CXBXR_HEADER_EMU})
list(FILTER CXBXR_HLSL_FILES INCLUDE REGEX ".*/src/core/hle/D3D8/Direct3D9/[^/]+\.hlsli?")
add_custom_command(
TARGET misc-batch POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>/hlsl
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CXBXR_HLSL_FILES} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>/hlsl"
# These files can be edited.
# Create backup copies for convenience of restoring original shader behaviour.
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>/hlsl/backup
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CXBXR_HLSL_FILES} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>/hlsl/backup"
)
install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>/hlsl DESTINATION bin)
set(cxbxr_GLEW_DLL "${CMAKE_SOURCE_DIR}/import/glew-2.0.0/bin/Release/Win32/glew32.dll")

View file

@ -17,7 +17,7 @@ Cxbx-Reloaded is an emulator for running Microsoft Xbox (and eventually, Chihiro
* [32-bit (x86) Visual C++ 2022 Redistributable](https://aka.ms/vs/17/release/vc_redist.x86.exe)
* [Npcap *(used for network emulation)*](https://nmap.org/npcap/#download)
* Make sure to enable winpcap compatibility mode.
* WinUSB compliant driver
* [WinUSB compliant driver](https://github.com/libusb/libusb/wiki/Windows#Driver_Installation)
* *Optional, only needed for USB pass-through of original Xbox and Steel Battalion controllers.*
### Wine

2
import/SDL2 vendored

@ -1 +1 @@
Subproject commit b424665e0899769b200231ba943353a5fee1b6b6
Subproject commit fa24d868ac2f8fd558e4e914c9863411245db8fd

@ -1 +1 @@
Subproject commit 75ce58fa8d135ef0a75bee729cde9542eda393b6
Subproject commit 774111351210e6f340246d6fb32741b09708f381

1
import/mio vendored Submodule

@ -0,0 +1 @@
Subproject commit 3f86a95c0784d73ce6815237ec33ed25f233b643

View file

@ -42,8 +42,11 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
LTC_NO_PRNGS
LTC_NO_MISC
LTC_NO_PROTOTYPES
# Enable Chihiro work
CHIHIRO_WORK
)
# Reference: https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically
add_compile_options(
# Catch synchronous (C++) exceptions only
@ -65,7 +68,7 @@ XXH_INLINE_ALL
)
file (GLOB RESOURCES
"${CXBXR_ROOT_DIR}/CONTRIBUTORS"
"${CXBXR_ROOT_DIR}/COPYING"
"${CXBXR_ROOT_DIR}/README.md"
@ -87,7 +90,7 @@ source_group(TREE ${CXBXR_ROOT_DIR}/import PREFIX import FILES
${CXBXR_SOURCE_EMU_IMPORT}
)
source_group(TREE ${CXBXR_ROOT_DIR}/src PREFIX source FILES
source_group(TREE ${CXBXR_ROOT_DIR}/src PREFIX source FILES
${CXBXR_SOURCE_GUIv1}
${CXBXR_SOURCE_COMMON}
)
@ -118,7 +121,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# Reference: https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically
# /Zi = create a PDB file without affecting optimization
# /Ob2 = Controls inline expansion of functions.
# /Ob3 = Controls inline expansion of functions.
# /Oi = Generate intrinsic functions
# /Ot = In favor of using fast code than small code
# /GL = Whole program optimization
@ -129,7 +132,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# Set optimization options for release build
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} \
/Zi \
/Ob2 \
/Ob3 \
/Oi \
/Ot \
/GL \
@ -139,7 +142,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
/Qpar \
"
)
# disable optimization for CxbxKrnl.cpp file
set_source_files_properties(
${CXBXR_KRNL_CPP} PROPERTIES COMPILE_FLAGS "/Od /GL-"
@ -147,7 +150,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
endif()
# Windows libraries
set(WINS_LIB
set(WINS_LIB
legacy_stdio_definitions
d3d9
d3dcompiler
@ -167,6 +170,7 @@ set(WINS_LIB
comctl32
XINPUT9_1_0
Iphlpapi
Dwmapi
)
target_link_libraries(cxbx
@ -176,6 +180,7 @@ target_link_libraries(cxbx
SDL2
imgui
libusb
mio::mio_min_winapi
${WINS_LIB}
)

View file

@ -48,6 +48,9 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# Use inline XXHash version
XXH_INLINE_ALL
# Enable Chihiro work
CHIHIRO_WORK
)
add_compile_options(
/EHs
@ -128,7 +131,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# Set optimization options for release build
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} \
/Zi \
/Ob2 \
/Ob3 \
/Oi \
/Ot \
/GL \
@ -141,7 +144,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
endif()
# Windows libraries
set(WINS_LIB
set(WINS_LIB
legacy_stdio_definitions
d3d9
d3dcompiler
@ -172,6 +175,7 @@ target_link_libraries(cxbxr-emu
imgui
libusb
nv2a_vsh_emulator
mio::mio_min_winapi
${WINS_LIB}
)

View file

@ -27,14 +27,3 @@ message("Runtime Build Directory: ${TargetRunTimeDir}")
# Copy glew32.dll to build type's folder.
set(CXBXR_GLEW_DLL "${CMAKE_SOURCE_DIR}/import/glew-2.0.0/bin/Release/Win32/glew32.dll")
file(COPY ${CXBXR_GLEW_DLL} DESTINATION ${TargetRunTimeDir})
# Copy certain HLSL files to the output directory, which we will load at runtime
set(CXBXR_HLSL_FILES
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShaderState.hlsli"
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionVertexShader.hlsl"
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsli"
"${CMAKE_SOURCE_DIR}/src/core/hle/D3D8/Direct3D9/FixedFunctionPixelShader.hlsl"
)
set(HlslOutputDir ${TargetRunTimeDir}/hlsl)
file(MAKE_DIRECTORY ${HlslOutputDir})
file(COPY ${CXBXR_HLSL_FILES} DESTINATION ${HlslOutputDir})

View file

@ -84,6 +84,4 @@ extern volatile bool g_bPrintfOn;
#define CxbxSetThreadName(Name)
#endif
#include <filesystem>
#endif

View file

@ -25,6 +25,7 @@
#define LOG_PREFIX CXBXR_MODULE::FILE
#define LOG_PREFIX_INIT CXBXR_MODULE::INIT
#include <filesystem>
#include "common/cxbxr.hpp"
#include "Settings.hpp"
#include "EmuShared.h"

View file

@ -31,6 +31,8 @@ extern std::string g_DataFilePath;
extern std::string g_DiskBasePath;
extern std::string g_MuBasePath;
#include <filesystem>
//TODO: Possible move CxbxResolveHostToFullPath inline function someplace else if become useful elsewhere.
// Let filesystem library clean it up for us, including resolve host's symbolic link path.
// Since internal kernel do translate to full path than preserved host symoblic link path.

View file

@ -49,8 +49,9 @@ void ipc_send_gui_update(IPC_UPDATE_GUI command, const unsigned int value);
// ******************************************************************
typedef enum class _IPC_UPDATE_KERNEL {
CONFIG_LOGGING_SYNC = 0
, CONFIG_INPUT_SYNC
CONFIG_LOGGING_SYNC = 0,
CONFIG_INPUT_SYNC,
CONFIG_CHANGE_TIME
} IPC_UPDATE_KERNEL;
void ipc_send_kernel_update(IPC_UPDATE_KERNEL command, const int value, const unsigned int hwnd);

View file

@ -26,7 +26,6 @@
// ******************************************************************
#ifndef SETTINGS_HPP
#define SETTINGS_HPP
#include "Cxbx.h"
#include "SimpleIni.h"
#include "common\input\InputManager.h"

View file

@ -25,19 +25,45 @@
// *
// ******************************************************************
#ifdef _WIN32
#include <core\kernel\exports\xboxkrnl.h>
#include <windows.h>
#endif
#include <thread>
#include <vector>
#include <mutex>
#include <array>
#include "Timer.h"
#include "common\util\CxbxUtil.h"
#include "core\kernel\support\EmuFS.h"
#include "core/kernel/exports/EmuKrnlPs.hpp"
#ifdef __linux__
#include <time.h>
#endif
#include "core\kernel\exports\EmuKrnlPs.hpp"
#include "core\kernel\exports\EmuKrnl.h"
#include "devices\Xbox.h"
#include "devices\usb\OHCI.h"
#include "core\hle\DSOUND\DirectSound\DirectSoundGlobal.hpp"
static std::atomic_uint64_t last_qpc; // last time when QPC was called
static std::atomic_uint64_t exec_time; // total execution time in us since the emulation started
static uint64_t pit_last; // last time when the pit time was updated
static uint64_t pit_last_qpc; // last QPC time of the pit
// The frequency of the high resolution clock of the host, and the start time
int64_t HostQPCFrequency, HostQPCStartTime;
void timer_init()
{
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER *>(&HostQPCFrequency));
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&HostQPCStartTime));
pit_last_qpc = last_qpc = HostQPCStartTime;
pit_last = get_now();
// Synchronize xbox system time with host time
LARGE_INTEGER HostSystemTime;
GetSystemTimeAsFileTime((LPFILETIME)&HostSystemTime);
xbox::KeSystemTime.High2Time = HostSystemTime.u.HighPart;
xbox::KeSystemTime.LowPart = HostSystemTime.u.LowPart;
xbox::KeSystemTime.High1Time = HostSystemTime.u.HighPart;
}
// More precise sleep, but with increased CPU usage
void SleepPrecise(std::chrono::steady_clock::time_point targetTime)
@ -69,174 +95,83 @@ void SleepPrecise(std::chrono::steady_clock::time_point targetTime)
}
}
// Virtual clocks will probably become useful once LLE CPU is implemented, but for now we don't need them.
// See the QEMUClockType QEMU_CLOCK_VIRTUAL of XQEMU for more info.
#define CLOCK_REALTIME 0
//#define CLOCK_VIRTUALTIME 1
// Vector storing all the timers created
static std::vector<TimerObject*> TimerList;
// The frequency of the high resolution clock of the host, and the start time
int64_t HostQPCFrequency, HostQPCStartTime;
// Lock to acquire when accessing TimerList
std::mutex TimerMtx;
// Returns the current time of the timer
uint64_t GetTime_NS(TimerObject* Timer)
// NOTE: the pit device is not implemented right now, so we put this here
static uint64_t pit_next(uint64_t now)
{
#ifdef _WIN32
uint64_t Ret = Timer_GetScaledPerformanceCounter(SCALE_S_IN_NS);
#elif __linux__
static struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
uint64_t Ret = Muldiv64(ts.tv_sec, SCALE_S_IN_NS, 1) + ts.tv_nsec;
#else
#error "Unsupported OS"
#endif
return Ret;
constexpr uint64_t pit_period = 1000;
uint64_t next = pit_last + pit_period;
if (now >= next) {
xbox::KiClockIsr(now - pit_last);
pit_last = get_now();
return pit_period;
}
return pit_last + pit_period - now; // time remaining until next clock interrupt
}
// Calculates the next expire time of the timer
static inline uint64_t GetNextExpireTime(TimerObject* Timer)
static void update_non_periodic_events()
{
return GetTime_NS(Timer) + Timer->ExpireTime_MS.load();
// update dsound
dsound_worker();
// check for hw interrupts
for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) {
// If the interrupt is pending and connected, process it
if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) {
HalSystemInterrupts[i].Trigger(EmuInterruptList[i]);
}
}
}
// Deallocates the memory of the timer
void Timer_Destroy(TimerObject* Timer)
uint64_t get_now()
{
unsigned int index, i;
std::lock_guard<std::mutex>lock(TimerMtx);
index = TimerList.size();
for (i = 0; i < index; i++) {
if (Timer == TimerList[i]) {
index = i;
}
}
assert(index != TimerList.size());
delete Timer;
TimerList.erase(TimerList.begin() + index);
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
uint64_t elapsed_us = now.QuadPart - last_qpc;
last_qpc = now.QuadPart;
elapsed_us *= 1000000;
elapsed_us /= HostQPCFrequency;
exec_time += elapsed_us;
return exec_time;
}
void Timer_Shutdown()
static uint64_t get_next(uint64_t now)
{
unsigned i, iXboxThreads = 0;
TimerMtx.lock();
for (i = 0; i < TimerList.size(); i++) {
TimerObject* Timer = TimerList[i];
// We only need to terminate host threads.
if (!Timer->IsXboxTimer) {
Timer_Exit(Timer);
}
// If the thread is xbox, we need to increment for while statement check
else {
iXboxThreads++;
}
}
// Only perform wait for host threads, otherwise xbox threads are
// already handled within xbox kernel for shutdown process. See CxbxrKrnlSuspendThreads function.
int counter = 0;
while (iXboxThreads != TimerList.size()) {
if (counter >= 8) {
break;
}
TimerMtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
TimerMtx.lock();
counter++;
}
TimerList.clear();
TimerMtx.unlock();
std::array<uint64_t, 5> next = {
pit_next(now),
g_NV2A->vblank_next(now),
g_NV2A->ptimer_next(now),
g_USB0->m_HostController->OHCI_next(now),
dsound_next(now)
};
return *std::min_element(next.begin(), next.end());
}
// Thread that runs the timer
void NTAPI ClockThread(void *TimerArg)
xbox::void_xt NTAPI system_events(xbox::PVOID arg)
{
TimerObject *Timer = static_cast<TimerObject *>(TimerArg);
if (!Timer->Name.empty()) {
CxbxSetThreadName(Timer->Name.c_str());
}
if (!Timer->IsXboxTimer) {
g_AffinityPolicy->SetAffinityOther();
}
// Testing shows that, if this thread has the same priority of the other xbox threads, it can take tens, even hundreds of ms to complete a single loop.
// So we increase its priority to above normal, so that it scheduled more often
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
uint64_t NewExpireTime = GetNextExpireTime(Timer);
// Always run this thread at dpc level to prevent it from ever executing APCs/DPCs
xbox::KeRaiseIrqlToDpcLevel();
while (true) {
if (GetTime_NS(Timer) > NewExpireTime) {
if (Timer->Exit.load()) {
Timer_Destroy(Timer);
return;
const uint64_t last_time = get_now();
const uint64_t nearest_next = get_next(last_time);
while (true) {
update_non_periodic_events();
uint64_t elapsed_us = get_now() - last_time;
if (elapsed_us >= nearest_next) {
break;
}
Timer->Callback(Timer->Opaque);
NewExpireTime = GetNextExpireTime(Timer);
std::this_thread::yield();
}
Sleep(1); // prevent burning the cpu
}
}
// Changes the expire time of a timer
void Timer_ChangeExpireTime(TimerObject* Timer, uint64_t Expire_ms)
{
Timer->ExpireTime_MS.store(Expire_ms);
}
// Destroys the timer
void Timer_Exit(TimerObject* Timer)
{
Timer->Exit.store(true);
}
// Allocates the memory for the timer object
TimerObject* Timer_Create(TimerCB Callback, void* Arg, std::string Name, bool IsXboxTimer)
{
std::lock_guard<std::mutex>lock(TimerMtx);
TimerObject* pTimer = new TimerObject;
pTimer->Type = CLOCK_REALTIME;
pTimer->Callback = Callback;
pTimer->ExpireTime_MS.store(0);
pTimer->Exit.store(false);
pTimer->Opaque = Arg;
pTimer->Name = Name.empty() ? "Unnamed thread" : std::move(Name);
pTimer->IsXboxTimer = IsXboxTimer;
TimerList.emplace_back(pTimer);
return pTimer;
}
// Starts the timer
// Expire_MS must be expressed in NS
void Timer_Start(TimerObject* Timer, uint64_t Expire_MS)
{
Timer->ExpireTime_MS.store(Expire_MS);
if (Timer->IsXboxTimer) {
xbox::HANDLE hThread;
CxbxrCreateThread(&hThread, xbox::zeroptr, ClockThread, Timer, FALSE);
}
else {
std::thread(ClockThread, Timer).detach();
}
}
// Retrives the frequency of the high resolution clock of the host
void Timer_Init()
{
#ifdef _WIN32
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&HostQPCFrequency));
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&HostQPCStartTime));
#elif __linux__
ClockFrequency = 0;
#else
#error "Unsupported OS"
#endif
}
int64_t Timer_GetScaledPerformanceCounter(int64_t Period)
{
LARGE_INTEGER currentQPC;

View file

@ -40,33 +40,12 @@
#define SCALE_MS_IN_US 1000
#define SCALE_US_IN_US 1
/* typedef of the timer object and the callback function */
typedef void(*TimerCB)(void*);
typedef struct _TimerObject
{
int Type; // timer type
std::atomic_uint64_t ExpireTime_MS; // when the timer expires (ms)
std::atomic_bool Exit; // indicates that the timer should be destroyed
TimerCB Callback; // function to call when the timer expires
void* Opaque; // opaque argument to pass to the callback
std::string Name; // the name of the timer thread (if any)
bool IsXboxTimer; // indicates that the timer should run on the Xbox CPU
}
TimerObject;
extern int64_t HostQPCFrequency;
/* Timer exported functions */
TimerObject* Timer_Create(TimerCB Callback, void* Arg, std::string Name, bool IsXboxTimer);
void Timer_Start(TimerObject* Timer, uint64_t Expire_MS);
void Timer_Exit(TimerObject* Timer);
void Timer_ChangeExpireTime(TimerObject* Timer, uint64_t Expire_ms);
uint64_t GetTime_NS(TimerObject* Timer);
void Timer_Init();
void Timer_Shutdown();
void timer_init();
uint64_t get_now();
int64_t Timer_GetScaledPerformanceCounter(int64_t Period);
void SleepPrecise(std::chrono::steady_clock::time_point targetTime);
#endif

View file

@ -121,9 +121,6 @@ bool HandleFirstLaunch()
}
// NOTE: Require to be after g_renderbase's shutdown process.
// Next thing we need to do is shutdown our timer threads.
Timer_Shutdown();
// NOTE: Must be last step of shutdown process and before CxbxUnlockFilePath call!
// Shutdown the memory manager
g_VMManager.Shutdown();

View file

@ -24,7 +24,6 @@
// ******************************************************************
#pragma once
#include <filesystem>
#include <string>
#include <optional>

View file

@ -37,6 +37,7 @@
#include "DInputKeyboardMouse.h"
#include "InputManager.h"
#include "core\kernel\support\Emu.h"
#include <algorithm>
// Unfortunately, sdl doesn't seem to be able to capture keyboard/mouse input from windows it didn't create (we currently use
// win32 for that). So unless we create sdl windows, we will have to keep dinput around to handle keyboard/mouse input.

View file

@ -25,6 +25,7 @@
// *
// ******************************************************************
#include <algorithm>
#include "Button.h"
#include "InputManager.h"
#include "layout_xbox_device.h"

View file

@ -170,7 +170,7 @@ namespace Libusb
}
else {
for (size_t i = 0; i < ARRAY_SIZE(SupportedDevices_VidPid); ++i) {
if ((Desc->idVendor = SupportedDevices_VidPid[i][0]) && (Desc->idProduct == SupportedDevices_VidPid[i][1])) {
if ((Desc->idVendor == SupportedDevices_VidPid[i][0]) && (Desc->idProduct == SupportedDevices_VidPid[i][1])) {
m_Type = XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER;
m_UcType = XINPUT_DEVTYPE_GAMEPAD;
m_UcSubType = XINPUT_DEVSUBTYPE_GC_GAMEPAD;
@ -185,9 +185,18 @@ namespace Libusb
if (m_Type == XBOX_INPUT_DEVICE::DEVICE_INVALID) { return; }
// Duke, S and SBC have 1 configuration, 1 interface and 2 endpoints (input and output) and use the default alternate setting zero.
// The code below assumes that third-party controllers follow suit.
if (libusb_open(Dev, &m_hDev) == 0) {
// check if we are able to open device through libusb
if (int err = libusb_open(Dev, &m_hDev)) {
// Couldn't open device, create an error log report then don't use it.
EmuLog(LOG_LEVEL::ERROR2, "Unable to open original xbox device \"%s\" (%hX:%hX), libusb's error was: %s",
m_Name.c_str(), Desc->idVendor, Desc->idProduct, libusb_strerror(err));
m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID;
return;
}
// If we are able to open device, continue with query process.
else {
// Duke, S and SBC have 1 configuration, 1 interface and 2 endpoints (input and output) and use the default alternate setting zero.
// The code below assumes that third-party controllers follow suit.
libusb_config_descriptor *ConfDesc;
if (libusb_get_active_config_descriptor(Dev, &ConfDesc) == 0) {
if (ConfDesc->bNumInterfaces == 1) {
@ -211,7 +220,7 @@ namespace Libusb
}
}
EmuLog(LOG_LEVEL::INFO, "Out endpoint %s", m_HasEndpointOut ? "present" : "not present");
if (int err = libusb_claim_interface(m_hDev, m_IfaceNum) != 0) {
if (int err = libusb_claim_interface(m_hDev, m_IfaceNum)) {
EmuLog(LOG_LEVEL::INFO, "Rejected device %s because libusb could not claim its interface. The error was: %s",
m_Name.c_str(), libusb_strerror(err));
m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID;

View file

@ -35,6 +35,7 @@
#define LOG_PREFIX CXBXR_MODULE::SDL
#include <assert.h>
#include <algorithm>
#include <thread>
#include "core\kernel\support\Emu.h"
#include "SdlJoystick.h"

View file

@ -26,12 +26,15 @@
#ifndef CXBXUTIL_H
#define CXBXUTIL_H
#include <algorithm>
#include <stdexcept>
#include "xbox_types.h"
#include "Cxbx.h"
#include <stdint.h>
#include <assert.h>
#include <string>
#include <type_traits>
#include <vector>
#include "std_extend.hpp" // for ARRAY_SIZE
/* This is a linux struct for vectored I/O. See readv() and writev() */

View file

@ -1,335 +0,0 @@
/*
Copyright (c) 2013 - 2014, 2016 Mark Adler, Robert Vazan, Max Vysokikh
This software is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "crc32c.h"
#include <intrin.h>
#include <algorithm>
#define POLY 0x82f63b78
#define LONG_SHIFT 8192
#define SHORT_SHIFT 256
typedef const uint8_t *buffer;
static uint32_t table[16][256];
static uint32_t long_shifts[4][256];
static uint32_t short_shifts[4][256];
static bool _tableInitialized;
void calculate_table();
/* Table-driven software version as a fall-back. This is about 15 times slower
than using the hardware instructions. This assumes little-endian integers,
as is the case on Intel processors that the assembler code here is for. */
extern "C" CRC32C_API uint32_t crc32c_append_sw(uint32_t crci, buffer input, size_t length)
{
buffer next = input;
#ifdef _M_X64
uint64_t crc;
#else
uint32_t crc;
#endif
crc = crci ^ 0xffffffff;
#ifdef _M_X64
while (length && ((uintptr_t)next & 7) != 0)
{
crc = table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
--length;
}
while (length >= 16)
{
crc ^= *(uint64_t *)next;
uint64_t high = *(uint64_t *)(next + 8);
crc = table[15][crc & 0xff]
^ table[14][(crc >> 8) & 0xff]
^ table[13][(crc >> 16) & 0xff]
^ table[12][(crc >> 24) & 0xff]
^ table[11][(crc >> 32) & 0xff]
^ table[10][(crc >> 40) & 0xff]
^ table[9][(crc >> 48) & 0xff]
^ table[8][crc >> 56]
^ table[7][high & 0xff]
^ table[6][(high >> 8) & 0xff]
^ table[5][(high >> 16) & 0xff]
^ table[4][(high >> 24) & 0xff]
^ table[3][(high >> 32) & 0xff]
^ table[2][(high >> 40) & 0xff]
^ table[1][(high >> 48) & 0xff]
^ table[0][high >> 56];
next += 16;
length -= 16;
}
#else
while (length && ((uintptr_t)next & 3) != 0)
{
crc = table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
--length;
}
while (length >= 12)
{
crc ^= *(uint32_t *)next;
uint32_t high = *(uint32_t *)(next + 4);
uint32_t high2 = *(uint32_t *)(next + 8);
crc = table[11][crc & 0xff]
^ table[10][(crc >> 8) & 0xff]
^ table[9][(crc >> 16) & 0xff]
^ table[8][crc >> 24]
^ table[7][high & 0xff]
^ table[6][(high >> 8) & 0xff]
^ table[5][(high >> 16) & 0xff]
^ table[4][high >> 24]
^ table[3][high2 & 0xff]
^ table[2][(high2 >> 8) & 0xff]
^ table[1][(high2 >> 16) & 0xff]
^ table[0][high2 >> 24];
next += 12;
length -= 12;
}
#endif
while (length)
{
crc = table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
--length;
}
return (uint32_t)crc ^ 0xffffffff;
}
/* Apply the zeros operator table to crc. */
static inline uint32_t shift_crc(uint32_t shift_table[][256], uint32_t crc)
{
return shift_table[0][crc & 0xff]
^ shift_table[1][(crc >> 8) & 0xff]
^ shift_table[2][(crc >> 16) & 0xff]
^ shift_table[3][crc >> 24];
}
/* Compute CRC-32C using the Intel hardware instruction. */
extern "C" CRC32C_API uint32_t crc32c_append_hw(uint32_t crc, buffer buf, size_t len)
{
buffer next = buf;
buffer end;
#ifdef _M_X64
uint64_t crc0, crc1, crc2; /* need to be 64 bits for crc32q */
#else
uint32_t crc0, crc1, crc2;
#endif
/* pre-process the crc */
crc0 = crc ^ 0xffffffff;
/* compute the crc for up to seven leading bytes to bring the data pointer
to an eight-byte boundary */
while (len && ((uintptr_t)next & 7) != 0)
{
crc0 = _mm_crc32_u8(static_cast<uint32_t>(crc0), *next);
++next;
--len;
}
#ifdef _M_X64
/* compute the crc on sets of LONG_SHIFT*3 bytes, executing three independent crc
instructions, each on LONG_SHIFT bytes -- this is optimized for the Nehalem,
Westmere, Sandy Bridge, and Ivy Bridge architectures, which have a
throughput of one crc per cycle, but a latency of three cycles */
while (len >= 3 * LONG_SHIFT)
{
crc1 = 0;
crc2 = 0;
end = next + LONG_SHIFT;
do
{
crc0 = _mm_crc32_u64(crc0, *reinterpret_cast<const uint64_t *>(next));
crc1 = _mm_crc32_u64(crc1, *reinterpret_cast<const uint64_t *>(next + LONG_SHIFT));
crc2 = _mm_crc32_u64(crc2, *reinterpret_cast<const uint64_t *>(next + 2 * LONG_SHIFT));
next += 8;
} while (next < end);
crc0 = shift_crc(long_shifts, static_cast<uint32_t>(crc0)) ^ crc1;
crc0 = shift_crc(long_shifts, static_cast<uint32_t>(crc0)) ^ crc2;
next += 2 * LONG_SHIFT;
len -= 3 * LONG_SHIFT;
}
/* do the same thing, but now on SHORT_SHIFT*3 blocks for the remaining data less
than a LONG_SHIFT*3 block */
while (len >= 3 * SHORT_SHIFT)
{
crc1 = 0;
crc2 = 0;
end = next + SHORT_SHIFT;
do
{
crc0 = _mm_crc32_u64(crc0, *reinterpret_cast<const uint64_t *>(next));
crc1 = _mm_crc32_u64(crc1, *reinterpret_cast<const uint64_t *>(next + SHORT_SHIFT));
crc2 = _mm_crc32_u64(crc2, *reinterpret_cast<const uint64_t *>(next + 2 * SHORT_SHIFT));
next += 8;
} while (next < end);
crc0 = shift_crc(short_shifts, static_cast<uint32_t>(crc0)) ^ crc1;
crc0 = shift_crc(short_shifts, static_cast<uint32_t>(crc0)) ^ crc2;
next += 2 * SHORT_SHIFT;
len -= 3 * SHORT_SHIFT;
}
/* compute the crc on the remaining eight-byte units less than a SHORT_SHIFT*3
block */
end = next + (len - (len & 7));
while (next < end)
{
crc0 = _mm_crc32_u64(crc0, *reinterpret_cast<const uint64_t *>(next));
next += 8;
}
#else
/* compute the crc on sets of LONG_SHIFT*3 bytes, executing three independent crc
instructions, each on LONG_SHIFT bytes -- this is optimized for the Nehalem,
Westmere, Sandy Bridge, and Ivy Bridge architectures, which have a
throughput of one crc per cycle, but a latency of three cycles */
while (len >= 3 * LONG_SHIFT)
{
crc1 = 0;
crc2 = 0;
end = next + LONG_SHIFT;
do
{
crc0 = _mm_crc32_u32(crc0, *reinterpret_cast<const uint32_t *>(next));
crc1 = _mm_crc32_u32(crc1, *reinterpret_cast<const uint32_t *>(next + LONG_SHIFT));
crc2 = _mm_crc32_u32(crc2, *reinterpret_cast<const uint32_t *>(next + 2 * LONG_SHIFT));
next += 4;
} while (next < end);
crc0 = shift_crc(long_shifts, static_cast<uint32_t>(crc0)) ^ crc1;
crc0 = shift_crc(long_shifts, static_cast<uint32_t>(crc0)) ^ crc2;
next += 2 * LONG_SHIFT;
len -= 3 * LONG_SHIFT;
}
/* do the same thing, but now on SHORT_SHIFT*3 blocks for the remaining data less
than a LONG_SHIFT*3 block */
while (len >= 3 * SHORT_SHIFT)
{
crc1 = 0;
crc2 = 0;
end = next + SHORT_SHIFT;
do
{
crc0 = _mm_crc32_u32(crc0, *reinterpret_cast<const uint32_t *>(next));
crc1 = _mm_crc32_u32(crc1, *reinterpret_cast<const uint32_t *>(next + SHORT_SHIFT));
crc2 = _mm_crc32_u32(crc2, *reinterpret_cast<const uint32_t *>(next + 2 * SHORT_SHIFT));
next += 4;
} while (next < end);
crc0 = shift_crc(short_shifts, static_cast<uint32_t>(crc0)) ^ crc1;
crc0 = shift_crc(short_shifts, static_cast<uint32_t>(crc0)) ^ crc2;
next += 2 * SHORT_SHIFT;
len -= 3 * SHORT_SHIFT;
}
/* compute the crc on the remaining eight-byte units less than a SHORT_SHIFT*3
block */
end = next + (len - (len & 7));
while (next < end)
{
crc0 = _mm_crc32_u32(crc0, *reinterpret_cast<const uint32_t *>(next));
next += 4;
}
#endif
len &= 7;
/* compute the crc for up to seven trailing bytes */
while (len)
{
crc0 = _mm_crc32_u8(static_cast<uint32_t>(crc0), *next);
++next;
--len;
}
/* return a post-processed crc */
return static_cast<uint32_t>(crc0) ^ 0xffffffff;
}
extern "C" CRC32C_API int crc32c_hw_available()
{
int info[4];
__cpuid(info, 1);
return (info[2] & (1 << 20)) != 0;
}
void calculate_table()
{
for(int i = 0; i < 256; i++)
{
uint32_t res = (uint32_t)i;
for(int t = 0; t < 16; t++) {
for (int k = 0; k < 8; k++) res = (res & 1) == 1 ? POLY ^ (res >> 1) : (res >> 1);
table[t][i] = res;
}
}
_tableInitialized = true;
}
void calculate_table_hw()
{
for(int i = 0; i < 256; i++)
{
uint32_t res = (uint32_t)i;
for (int k = 0; k < 8 * (SHORT_SHIFT - 4); k++) res = (res & 1) == 1 ? POLY ^ (res >> 1) : (res >> 1);
for(int t = 0; t < 4; t++) {
for (int k = 0; k < 8; k++) res = (res & 1) == 1 ? POLY ^ (res >> 1) : (res >> 1);
short_shifts[3 - t][i] = res;
}
for (int k = 0; k < 8 * (LONG_SHIFT - 4 - SHORT_SHIFT); k++) res = (res & 1) == 1 ? POLY ^ (res >> 1) : (res >> 1);
for(int t = 0; t < 4; t++) {
for (int k = 0; k < 8; k++) res = (res & 1) == 1 ? POLY ^ (res >> 1) : (res >> 1);
long_shifts[3 - t][i] = res;
}
}
}
uint32_t (*append_func)(uint32_t, buffer, size_t);
void __crc32_init()
{
if (append_func == NULL)
{
// somebody can call sw version directly, so, precalculate table for this version
calculate_table();
if (crc32c_hw_available()) {
calculate_table_hw();
append_func = crc32c_append_hw;
} else {
append_func = crc32c_append_sw;
}
}
}
extern "C" CRC32C_API uint32_t crc32c_append(uint32_t crc, buffer input, size_t length)
{
if (append_func == NULL) {
__crc32_init();
}
return append_func(crc, input, length);
}

View file

@ -1,35 +0,0 @@
#ifndef CRC32C_H
#define CRC32C_H
#define CRC32C_API
#include <stdint.h>
/*
Computes CRC-32C (Castagnoli) checksum. Uses Intel's CRC32 instruction if it is available.
Otherwise it uses a very fast software fallback.
*/
extern "C" CRC32C_API uint32_t crc32c_append(
uint32_t crc, // Initial CRC value. Typically it's 0.
// You can supply non-trivial initial value here.
// Initial value can be used to chain CRC from multiple buffers.
const uint8_t *input, // Data to be put through the CRC algorithm.
size_t length); // Length of the data in the input buffer.
/*
Software fallback version of CRC-32C (Castagnoli) checksum.
*/
extern "C" CRC32C_API uint32_t crc32c_append_sw(uint32_t crc, const uint8_t *input, size_t length);
/*
Hardware version of CRC-32C (Castagnoli) checksum. Will fail, if CPU does not support related instructions. Use a crc32c_append version instead of.
*/
extern "C" CRC32C_API uint32_t crc32c_append_hw(uint32_t crc, const uint8_t *input, size_t length);
/*
Checks is hardware version of CRC-32C is available.
*/
extern "C" CRC32C_API int crc32c_hw_available();
#endif

View file

@ -1,42 +0,0 @@
#include "hasher.h"
#include "xxhash.h"
#include "crc32c.h"
#include <cstdio>
enum {
HASH_NONE = 0,
HASH_XXH3,
HASH_CRC32C
};
static int g_HashAlgorithm = HASH_NONE;
void InitHasher()
{
// Detect the best hashing algorithm to use for the host machine
// TODO/Future Improvement: This could be expanded to support even more hash algorithims
// And we could hash a random buffer to calculate the fastest hash to use on a given host
printf("Selecting hash algorithm: ");
if (crc32c_hw_available()) {
printf("CRC32C\n");
g_HashAlgorithm = HASH_CRC32C;
} else {
printf("XXH3\n");
g_HashAlgorithm = HASH_XXH3;
}
}
uint64_t ComputeHash(const void* data, size_t len)
{
if (g_HashAlgorithm == HASH_NONE) {
InitHasher();
}
switch (g_HashAlgorithm) {
case HASH_XXH3: return XXH3_64bits(data, len);
case HASH_CRC32C: return crc32c_append(0, (uint8_t*)data, len);
}
return 0;
}

View file

@ -27,8 +27,7 @@
#ifndef _HASHER_H
#define _HASHER_H
#include <stdint.h>
uint64_t ComputeHash(const void* data, size_t len);
#include "xxhash.h"
#define ComputeHash XXH3_64bits
#endif

View file

@ -101,6 +101,10 @@ void ipc_send_kernel_update(IPC_UPDATE_KERNEL command, const int value, const un
cmdParam = ID_SYNC_CONFIG_INPUT;
break;
case IPC_UPDATE_KERNEL::CONFIG_CHANGE_TIME:
cmdParam = ID_SYNC_TIME_CHANGE;
break;
default:
cmdParam = 0;
break;

View file

@ -72,7 +72,7 @@ Xbe::Xbe(const char *x_szFilename)
// This is necessary because CxbxInitWindow internally calls g_AffinityPolicy->SetAffinityOther. If we are launched directly from the command line and the dashboard
// cannot be opened, we will crash below because g_AffinityPolicy will be empty
g_AffinityPolicy = AffinityPolicy::InitPolicy();
CxbxInitWindow(false);
CxbxInitWindow();
ULONG FatalErrorCode = FATAL_ERROR_XBE_DASH_GENERIC;
@ -732,25 +732,29 @@ void Xbe::PurgeBadChar(std::string& s, const std::string& illegalChars)
}
}
const char *Xbe::GameRegionToString()
const char *Xbe::GameRegionToString(uint32_t dwRegionFlags)
{
if (!dwRegionFlags) {
dwRegionFlags = m_Certificate.dwGameRegion;
}
const char *Region_text[] = {
"Unknown", "NTSC", "JAP", "NTSC+JAP",
"PAL", "PAL+NTSC", "PAL+JAP", "Region Free",
"DEBUG", "NTSC (DEBUG)", "JAP (DEBUG)", "NTSC+JAP (DEBUG)",
"PAL (DEBUG)", "PAL+NTSC (DEBUG)", "PAL+JAP (DEBUG)", "Region Free (DEBUG)"
"Unknown", "NTSC", "JAPAN", "NTSC+JAPAN",
"PAL", "PAL+NTSC", "PAL+JAPAN", "Region Free",
"DEBUG", "NTSC (DEBUG)", "JAPAN (DEBUG)", "NTSC+JAPAN (DEBUG)",
"PAL (DEBUG)", "PAL+NTSC (DEBUG)", "PAL+JAPAN (DEBUG)", "Region Free (DEBUG)"
};
const uint32_t all_regions = XBEIMAGE_GAME_REGION_NA |
XBEIMAGE_GAME_REGION_JAPAN |
XBEIMAGE_GAME_REGION_RESTOFWORLD |
XBEIMAGE_GAME_REGION_MANUFACTURING;
if(m_Certificate.dwGameRegion & ~all_regions) {
if(dwRegionFlags & ~all_regions) {
return "REGION ERROR";
}
uint8_t index = (m_Certificate.dwGameRegion & XBEIMAGE_GAME_REGION_MANUFACTURING) ? 0x8 : 0;
index |= (m_Certificate.dwGameRegion & 0x7);
uint8_t index = (dwRegionFlags & XBEIMAGE_GAME_REGION_MANUFACTURING) ? 0x8 : 0;
index |= (dwRegionFlags & 0x7);
return Region_text[index];
}
@ -825,5 +829,15 @@ XbeType Xbe::GetXbeType()
return XbeType::xtRetail;
}
uint32_t Xbe::GetDiscVersion()
{
return m_Certificate.dwVersion & 0xFF;
}
uint32_t Xbe::GetPatchVersion()
{
return (m_Certificate.dwVersion & 0xFFFFFF00) >> 8;
}
template auto Xbe::FindSection<true>(const char *zsSectionName);
template auto Xbe::FindSection<false>(const char *zsSectionName);

View file

@ -81,10 +81,13 @@ class Xbe : public Error
void PurgeBadChar(std::string& s, const std::string& illegalChars = "\\/:?\"<>|");
// Convert game region field to string
const char *GameRegionToString();
const char *GameRegionToString(uint32_t dwRegionFlags = 0);
XbeType GetXbeType();
uint32_t GetDiscVersion();
uint32_t GetPatchVersion();
// Xbe header
#include "AlignPrefix1.h"
struct Header
@ -157,7 +160,7 @@ class Xbe : public Error
uint32_t dwAllowedMedia; // 0x009C - allowed media types
uint32_t dwGameRegion; // 0x00A0 - game region
uint32_t dwGameRatings; // 0x00A4 - game ratings
uint32_t dwDiskNumber; // 0x00A8 - disk number
uint32_t dwDiscNumber; // 0x00A8 - disc number
uint32_t dwVersion; // 0x00AC - version
uint8_t bzLanKey[16]; // 0x00B0 - lan key
uint8_t bzSignatureKey[16]; // 0x00C0 - signature key

View file

@ -58,6 +58,7 @@ std::string DumpInformation(Xbe* Xbe_object)
}
#define SSTREAM_SET_HEX(stream_name) stream_name << std::setfill('0') << std::uppercase << std::hex;
#define SSTREAM_SET_DEC(stream_name) stream_name << std::setfill('0') << std::uppercase << std::dec;
std::string FormatTitleId(uint32_t title_id)
{
@ -359,8 +360,12 @@ std::string XbePrinter::GenMediaInfo()
text << "Allowed Media : 0x" << std::setw(8) << Xbe_certificate->dwAllowedMedia << " (" << AllowedMediaToString() << ")\n";
text << "Game Region : 0x" << std::setw(8) << Xbe_certificate->dwGameRegion << " (" << Xbe_to_print->GameRegionToString() << ")\n";
text << "Game Ratings : 0x" << std::setw(8) << Xbe_certificate->dwGameRatings << " (" << GameRatingToString() << ")\n";
text << "Disk Number : 0x" << std::setw(8) << Xbe_certificate->dwDiskNumber << "\n";
text << "Version : 1." << std::dec << std::setw(2) << Xbe_certificate->dwVersion << "\n";
text << "Disc Number : 0x" << std::setw(8) << Xbe_certificate->dwDiscNumber << "\n";
text << "Version : 0x" << std::setw(8) << Xbe_certificate->dwVersion << "\n";
SSTREAM_SET_DEC(text);
text << "Disc Version : " << std::setw(0) << Xbe_to_print->GetDiscVersion() << "\n";
text << "Patch Version : " << std::setw(0) << Xbe_to_print->GetPatchVersion() << "\n";
return text.str();
}

View file

@ -1,6 +1,3 @@
// This starts the raw string (comment to get syntax highlighting, UNCOMMENT to compile) :
R"DELIMITER(
struct PS_INPUT // Declared identical to vertex shader output (see VS_OUTPUT)
{
float2 iPos : VPOS; // Screen space x,y pixel location
@ -54,7 +51,8 @@ uniform const float4 FC1 : register(c17); // Note : Maps to PSH_XBOX_CONSTANT_FC
uniform const float4 BEM[4] : register(c19); // Note : PSH_XBOX_CONSTANT_BEM for 4 texture stages
uniform const float4 LUM[4] : register(c23); // Note : PSH_XBOX_CONSTANT_LUM for 4 texture stages
uniform const float FRONTFACE_FACTOR : register(c27); // Note : PSH_XBOX_CONSTANT_LUM for 4 texture stages
uniform const float4 FOGINFO : register(c28);
uniform const float FOGENABLE : register(c29);
#define CM_LT(c) if(c < 0) clip(-1); // = PS_COMPAREMODE_[RSTQ]_LT
#define CM_GE(c) if(c >= 0) clip(-1); // = PS_COMPAREMODE_[RSTQ]_GE
@ -92,10 +90,9 @@ uniform const float FRONTFACE_FACTOR : register(c27); // Note : PSH_XBOX_CONSTA
#define PS_FINALCOMBINERSETTING_CLAMP_SUM
#endif
)DELIMITER", /* This terminates the 1st raw string within the 16380 single-byte characters limit. // */
// See https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2026?f1url=%3FappId%3DDev15IDEF1%26l%3DEN-US%26k%3Dk(C2026)%26rd%3Dtrue&view=vs-2019
// Second raw string :
R"DELIMITER(
// Hardcoded state will be inserted here
// <HARDCODED STATE GOES HERE>
// End hardcoded state
// PS_COMBINERCOUNT_UNIQUE_C0 steers whether for C0 to use combiner stage-specific constants c0_0 .. c0_7, or c0_0 for all stages
#ifdef PS_COMBINERCOUNT_UNIQUE_C0
@ -173,10 +170,6 @@ R"DELIMITER(
// HLSL : https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-lerp
// lerp(x, y, s ) x*(1-s ) + y*s == x + s(y-x)
// lerp(s2, s1, s0) s2*(1-s0) + s1*s0
)DELIMITER", /* This terminates the 1st raw string within the 16380 single-byte characters limit. // */
// See https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2026?f1url=%3FappId%3DDev15IDEF1%26l%3DEN-US%26k%3Dk(C2026)%26rd%3Dtrue&view=vs-2019
// Second raw string :
R"DELIMITER(
float m21d(const float input)
{
@ -205,20 +198,39 @@ float m21(const float input)
return (float)tmp / 127; // -128 scales to -1.007874016, 0 scales to 0.0, 127 scales to 1.0
}
float hls(float input) // 0..65535 range
{
float tmp = (float)(input);
tmp = (input < 32768) ? tmp / 32767 : (tmp - 65536) / 32767; // -1..1
return (float)tmp;
}
float hlu(float input) // 0..65535 range
{
return (float)input / 65535; // 0..1
}
float p2(float input) // power of 2
{
return input * input;
}
// Note : each component seems already in range [0..1], but two must be combined into one
#define TwoIntoOne(a,b) (((a * 255) * 256) + (b * 255)) / 255 // TODO : Verify whether this works at all !
#define TwoIntoOne(a,b) (((a * 256) + b) * 255)
#define CalcHiLo(in) H = TwoIntoOne(in.x, in.y); L = TwoIntoOne(in.z, in.w) // TODO : Verify whether this works at all !
// Dot mappings over the output value of a (4 component 8 bit unsigned) texture stage register into a (3 component float) vector value, for use in a dot product calculation:
#define PS_DOTMAPPING_ZERO_TO_ONE(in) dm = in.rgb // :r8g8b8a8->(r,g,b): 0x00=>0, 0xff=>1 thus : output = (input / 0xff )
#define PS_DOTMAPPING_MINUS1_TO_1_D3D(in) dm = float3(m21d(in.x), m21d(in.y), m21d(in.z)) // :r8g8b8a8->(r,g,b): 0x00=>-128/127, 0x01=>-1, 0x80=>0, 0xff=>1 thus : output = ((input - 0x100 ) / 0x7f )
#define PS_DOTMAPPING_MINUS1_TO_1_GL(in) dm = float3(m21g(in.x), m21g(in.y), m21g(in.z)) // :r8g8b8a8->(r,g,b): 0x80=>-1, 0x00=>0, 0x7f=>1 thus : output = (input < 0x80 ) ? (input / 0x7f ) : ((input - 0x100 ) / 0x80 ) (see https://en.wikipedia.org/wiki/Two's_complement)
#define PS_DOTMAPPING_MINUS1_TO_1(in) dm = float3(m21(in.x), m21(in.y), m21(in.z)) // :r8g8b8a8->(r,g,b): 0x80=>-128/127, ?0x81=>-1, 0x00=>0, 0x7f=>1 thus : output = (input < 0x80 ) ? (input / 0x7f ) : ((input - 0x100 ) / 0x7f ) (see https://en.wikipedia.org/wiki/Two's_complement)
#define PS_DOTMAPPING_MINUS1_TO_1(in) dm = float3(m21( in.x), m21( in.y), m21( in.z)) // :r8g8b8a8->(r,g,b): 0x80=>-128/127, ?0x81=>-1, 0x00=>0, 0x7f=>1 thus : output = (input < 0x80 ) ? (input / 0x7f ) : ((input - 0x100 ) / 0x7f ) (see https://en.wikipedia.org/wiki/Two's_complement)
#define PS_DOTMAPPING_HILO_1(in) CalcHiLo(in); dm = float3(H, L, 1) // :H16L16 ->(H,L,1): 0x0000=>0, 0xffff=>1 thus : output = (input / 0xffff)
#define PS_DOTMAPPING_HILO_HEMISPHERE_D3D(in) CalcHiLo(in); dm = float3(H, L, sqrt(1-(H*H)-(L*L))) // :H16L16 ->(H,L,sqrt(1-H^2-L^2)):? 0x8000=>-1, 0x0000=>0, 0x7fff=32767/32768 thus : output = ((input - 0x10000) / 0x7fff)
#define PS_DOTMAPPING_HILO_HEMISPHERE_GL(in) CalcHiLo(in); dm = float3(H, L, sqrt(1-(H*H)-(L*L))) // :H16L16 ->(H,L,sqrt(1-H^2-L^2)):? 0x8000=>-1, 0x0000=>0, 0x7fff=>1 thus : output = (input < 0x8000) ? (input / 0x7fff) : ((input - 0x10000) / 0x8000)
#define PS_DOTMAPPING_HILO_HEMISPHERE(in) CalcHiLo(in); dm = float3(H, L, sqrt(1-(H*H)-(L*L))) // :H16L16 ->(H,L,sqrt(1-H^2-L^2)): 0x8000=>-32768/32767, 0x8001=>-1, 0x0000=>0, 0x7fff=>1 thus : output = (input < 0x8000) ? (input / 0x7fff) : ((input - 0x10000) / 0x7fff)
#define PS_DOTMAPPING_HILO_1(in) CalcHiLo(in); dm = float3(hlu(H), hlu(L), 1) // :H16L16 ->(H,L,1): 0x0000=>0, 0xffff=>1 thus : output = (input / 0xffff)
#define PS_DOTMAPPING_HILO_HEMISPHERE_D3D(in) CalcHiLo(in); dm = float3(hls(H), hls(L), sqrt(1-p2(H)-p2(L))) // :H16L16 ->(H,L,sqrt(1-H^2-L^2)):? 0x8000=>-1, 0x0000=>0, 0x7fff=32767/32768 thus : output = ((input - 0x10000) / 0x7fff)
#define PS_DOTMAPPING_HILO_HEMISPHERE_GL(in) CalcHiLo(in); dm = float3(hls(H), hls(L), sqrt(1-p2(H)-p2(L))) // :H16L16 ->(H,L,sqrt(1-H^2-L^2)):? 0x8000=>-1, 0x0000=>0, 0x7fff=>1 thus : output = (input < 0x8000) ? (input / 0x7fff) : ((input - 0x10000) / 0x8000)
#define PS_DOTMAPPING_HILO_HEMISPHERE(in) CalcHiLo(in); dm = float3(hls(H), hls(L), sqrt(1-p2(H)-p2(L))) // :H16L16 ->(H,L,sqrt(1-H^2-L^2)): 0x8000=>-32768/32767, 0x8001=>-1, 0x0000=>0, 0x7fff=>1 thus : output = (input < 0x8000) ? (input / 0x7fff) : ((input - 0x10000) / 0x7fff)
// Declare one sampler per each {Sampler Type, Texture Stage} combination
// TODO : Generate sampler status?
@ -312,7 +324,7 @@ float3 DoBumpEnv(const float4 TexCoord, const float4 BumpEnvMat, const float4 sr
/*--23 texbrdf */ #define PS_TEXTUREMODES_BRDF(ts) s = Brdf(ts); v = Sample3D(ts, s); t[ts] = v // TODO : Test (t[ts-2] is 16 bit eyePhi,eyeSigma; t[ts-1] is lightPhi,lightSigma)
/*--23 texm3x2tex */ #define PS_TEXTUREMODES_DOT_ST(ts) CalcDot(ts); n = Normal2(ts); s = n; v = Sample2D(ts, s); t[ts] = v // TODO : Test
/*--23 texm3x2depth */ #define PS_TEXTUREMODES_DOT_ZW(ts) CalcDot(ts); n = Normal2(ts); if (n.y==0) v=1;else v = n.x / n.y; t[ts] = v // TODO : Make depth-check use result of division, but how?
/*--2- texm3x3diff */ #define PS_TEXTUREMODES_DOT_RFLCT_DIFF(ts) CalcDot(ts); n = Normal3(ts); s = n; v = Sample6F(ts, s); t[ts] = v // TODO : Test
/*--2- texm3x3diff */ #define PS_TEXTUREMODES_DOT_RFLCT_DIFF(ts) CalcDot(ts); n = Normal2(ts); s = n; v = Sample6F(ts, s); t[ts] = v // TODO : Test
/*---3 texm3x3vspec */ #define PS_TEXTUREMODES_DOT_RFLCT_SPEC(ts) CalcDot(ts); n = Normal3(ts); s = Reflect(n, Eye); v = Sample6F(ts, s); t[ts] = v // TODO : Test
/*---3 texm3x3tex */ #define PS_TEXTUREMODES_DOT_STR_3D(ts) CalcDot(ts); n = Normal3(ts); s = n; v = Sample3D(ts, s); t[ts] = v // TODO : Test
/*---3 texm3x3tex */ #define PS_TEXTUREMODES_DOT_STR_CUBE(ts) CalcDot(ts); n = Normal3(ts); s = n; v = Sample6F(ts, s); t[ts] = v // TODO : Test
@ -325,6 +337,34 @@ float3 DoBumpEnv(const float4 TexCoord, const float4 BumpEnvMat, const float4 sr
PS_OUTPUT main(const PS_INPUT xIn)
{
// fogging
const float fogDepth = xIn.iFog.x; // Don't abs this value! Test-case : DolphinClassic xdk sampl
const int fogTableMode = FOGINFO.x;
const float fogDensity = FOGINFO.y;
const float fogStart = FOGINFO.z;
const float fogEnd = FOGINFO.w;
const int FOG_TABLE_NONE = 0;
const int FOG_TABLE_EXP = 1;
const int FOG_TABLE_EXP2 = 2;
const int FOG_TABLE_LINEAR = 3;
float fogFactor;
if(FOGENABLE == 0){
fogFactor = 1;
}
else{
if(fogTableMode == FOG_TABLE_NONE)
fogFactor = fogDepth;
if(fogTableMode == FOG_TABLE_EXP)
fogFactor = 1 / exp(fogDepth * fogDensity); // 1 / e^(d * density)
if(fogTableMode == FOG_TABLE_EXP2)
fogFactor = 1 / exp(pow(fogDepth * fogDensity, 2)); // 1 / e^((d * density)^2)
if(fogTableMode == FOG_TABLE_LINEAR)
fogFactor = (fogEnd - fogDepth) / (fogEnd - fogStart);
}
// Local constants
const float4 zero = 0;
const float4 half = 0.5; // = s_negbias(zero)
@ -358,12 +398,11 @@ PS_OUTPUT main(const PS_INPUT xIn)
// Note : VFACE/FrontFace has been unreliable, investigate again if some test-case shows bland colors
v0 = isFrontFace ? xIn.iD0 : xIn.iB0; // Diffuse front/back
v1 = isFrontFace ? xIn.iD1 : xIn.iB1; // Specular front/back
fog = float4(c_fog.rgb, xIn.iFog); // color from PSH_XBOX_CONSTANT_FOG, alpha from vertex shader output / pixel shader input
fog = float4(c_fog.rgb, saturate(fogFactor)); // color from PSH_XBOX_CONSTANT_FOG, alpha from vertex shader output / pixel shader input
// Xbox shader program
)DELIMITER", /* This terminates the 2nd raw string within the 16380 single-byte characters limit. // */
// Third and last raw string, the footer :
R"DELIMITER(
// Xbox shader program will be inserted here
// <XBOX SHADER PROGRAM GOES HERE>
// End Xbox shader program
// Copy r0.rgba to output
PS_OUTPUT xOut;
@ -372,5 +411,3 @@ R"DELIMITER(
return xOut;
}
// End of pixel shader footer)DELIMITER" /* This terminates the footer raw string" // */

View file

@ -0,0 +1,108 @@
// Xbox HLSL pretransformed vertex shader
// Default values for vertex registers, and whether to use them
uniform float4 vRegisterDefaultValues[16] : register(c192);
uniform float4 vRegisterDefaultFlagsPacked[4] : register(c208);
uniform float4 xboxScreenspaceScale : register(c212);
uniform float4 xboxScreenspaceOffset : register(c213);
uniform float4 xboxTextureScale[4] : register(c214);
// Parameters for mapping the shader's fog output value to a fog factor
uniform float4 CxbxFogInfo: register(c218); // = CXBX_D3DVS_CONSTREG_FOGINFO
struct VS_INPUT
{
float4 v[16] : TEXCOORD;
};
// Output registers
struct VS_OUTPUT
{
float4 oPos : POSITION; // Homogeneous clip space position
float4 oD0 : COLOR0; // Primary color (front-facing)
float4 oD1 : COLOR1; // Secondary color (front-facing)
float oFog : FOG; // Fog coordinate
float oPts : PSIZE; // Point size
float4 oB0 : TEXCOORD4; // Back-facing primary color
float4 oB1 : TEXCOORD5; // Back-facing secondary color
float4 oT0 : TEXCOORD0; // Texture coordinate set 0
float4 oT1 : TEXCOORD1; // Texture coordinate set 1
float4 oT2 : TEXCOORD2; // Texture coordinate set 2
float4 oT3 : TEXCOORD3; // Texture coordinate set 3
};
float4 reverseScreenspaceTransform(float4 oPos)
{
// Scale screenspace coordinates (0 to viewport width/height) to -1 to +1 range
// On Xbox, oPos should contain the vertex position in screenspace
// We need to reverse this transformation
// Conventionally, each Xbox Vertex Shader includes instructions like this
// mul oPos.xyz, r12, c-38
// +rcc r1.x, r12.w
// mad oPos.xyz, r12, r1.x, c-37
// where c-37 and c-38 are reserved transform values
// Reverse screenspace offset
oPos -= xboxScreenspaceOffset;
// Reverse screenspace scale
oPos /= xboxScreenspaceScale;
// Ensure w is nonzero
if(oPos.w == 0) oPos.w = 1;
// Reverse perspective divide
oPos.xyz *= oPos.w;
return oPos;
}
VS_OUTPUT main(const VS_INPUT xIn)
{
// Input registers
float4 v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15;
// Unpack 16 flags from 4 float4 constant registers
float vRegisterDefaultFlags[16] = (float[16])vRegisterDefaultFlagsPacked;
// Initialize input registers from the vertex buffer data
// Or use the register's default value (which can be changed by the title)
#define init_v(i) v##i = lerp(xIn.v[i], vRegisterDefaultValues[i], vRegisterDefaultFlags[i]);
// Note : unroll manually instead of for-loop, because of the ## concatenation
init_v( 0); init_v( 1); init_v( 2); init_v( 3);
init_v( 4); init_v( 5); init_v( 6); init_v( 7);
init_v( 8); init_v( 9); init_v(10); init_v(11);
init_v(12); init_v(13); init_v(14); init_v(15);
// For passthrough, map output variables to their corresponding input registers
float4 oPos = v0;
float4 oD0 = v3;
float4 oD1 = v4;
float4 oFog = v5;
float4 oPts = v6;
float4 oB0 = v7;
float4 oB1 = v8;
float4 oT0 = v9;
float4 oT1 = v10;
float4 oT2 = v11;
float4 oT3 = v12;
// Copy variables to output struct
VS_OUTPUT xOut;
xOut.oPos = reverseScreenspaceTransform(oPos);
xOut.oD0 = saturate(oD0);
xOut.oD1 = saturate(oD1);
xOut.oFog = oFog.x; // Note : Xbox clamps fog in pixel shader
xOut.oPts = oPts.x;
xOut.oB0 = saturate(oB0);
xOut.oB1 = saturate(oB1);
// Scale textures (TODO: or should we apply this to the input register values?)
xOut.oT0 = oT0 / xboxTextureScale[0];
xOut.oT1 = oT1 / xboxTextureScale[1];
xOut.oT2 = oT2 / xboxTextureScale[2];
xOut.oT3 = oT3 / xboxTextureScale[3];
return xOut;
}

View file

@ -1,6 +1,3 @@
// This starts the raw string (comment to get syntax highlighting, UNCOMMENT to compile) :
R"DELIMITER(// Xbox HLSL vertex shader (template populated at runtime)
struct VS_INPUT
{
float4 v[16] : TEXCOORD;
@ -326,40 +323,17 @@ VS_OUTPUT main(const VS_INPUT xIn)
// Temp variable for paired VS instruction
float4 temp;
// Xbox shader program)DELIMITER", /* This terminates the header raw string" // */
R"DELIMITER(
// Xbox shader program will be inserted here
// <XBOX SHADER PROGRAM GOES HERE>
// End Xbox shader program
// Copy variables to output struct
VS_OUTPUT xOut;
// Fogging
// TODO deduplicate
const float fogDepth = oFog.x; // Don't abs this value! Test-case : DolphinClassic xdk sample
const float fogTableMode = CxbxFogInfo.x;
const float fogDensity = CxbxFogInfo.y;
const float fogStart = CxbxFogInfo.z;
const float fogEnd = CxbxFogInfo.w;
const float FOG_TABLE_NONE = 0;
const float FOG_TABLE_EXP = 1;
const float FOG_TABLE_EXP2 = 2;
const float FOG_TABLE_LINEAR = 3;
float fogFactor;
if(fogTableMode == FOG_TABLE_NONE)
fogFactor = fogDepth;
if(fogTableMode == FOG_TABLE_EXP)
fogFactor = 1 / exp(fogDepth * fogDensity); /* / 1 / e^(d * density)*/
if(fogTableMode == FOG_TABLE_EXP2)
fogFactor = 1 / exp(pow(fogDepth * fogDensity, 2)); /* / 1 / e^((d * density)^2)*/
if(fogTableMode == FOG_TABLE_LINEAR)
fogFactor = (fogEnd - fogDepth) / (fogEnd - fogStart);
xOut.oPos = reverseScreenspaceTransform(oPos);
xOut.oD0 = saturate(oD0);
xOut.oD1 = saturate(oD1);
xOut.oFog = fogFactor; // Note : Xbox clamps fog in pixel shader -> *NEEDS TESTING* /was oFog.x
xOut.oFog = oFog.x; // Note : Xbox clamps fog in pixel shader -> *NEEDS TESTING* /was oFog.x
xOut.oPts = oPts.x;
xOut.oB0 = saturate(oB0);
xOut.oB1 = saturate(oB1);
@ -371,5 +345,3 @@ R"DELIMITER(
return xOut;
}
// End of vertex shader footer)DELIMITER" /* This terminates the footer raw string" // */

View file

@ -38,10 +38,12 @@
#include "core\kernel\support\Emu.h"
#include "core\kernel\support\EmuFS.h"
#include "core\kernel\support\NativeHandle.h"
#include "core\kernel\exports\EmuKrnlKe.h"
#include "EmuShared.h"
#include "..\FixedFunctionState.h"
#include "core\hle\D3D8\ResourceTracker.h"
#include "core\hle\D3D8\Direct3D9\Direct3D9.h" // For LPDIRECTDRAWSURFACE7
#include "core\hle\D3D8\Direct3D9\Shader.h" // For InitShaderHotloading
#include "core\hle\D3D8\XbVertexBuffer.h"
#include "core\hle\D3D8\XbVertexShader.h"
#include "core\hle\D3D8\XbPixelShader.h" // For DxbxUpdateActivePixelShader
@ -62,7 +64,7 @@
#include "common\input\DInputKeyboardMouse.h"
#include "common\input\InputManager.h"
#include "common/util/strConverter.hpp" // for utf8_to_utf16
#include "VertexShaderSource.h"
#include "VertexShaderCache.h"
#include "Timer.h"
#include <imgui.h>
@ -159,8 +161,6 @@ static IDirect3DQuery *g_pHostQueryWaitForIdle = nullptr;
static IDirect3DQuery *g_pHostQueryCallbackEvent = nullptr;
static int g_RenderUpscaleFactor = 1;
static std::condition_variable g_VBConditionVariable; // Used in BlockUntilVerticalBlank
static std::mutex g_VBConditionMutex; // Used in BlockUntilVerticalBlank
static DWORD g_VBLastSwap = 0;
static xbox::dword_xt g_Xbox_PresentationInterval_Default = D3DPRESENT_INTERVAL_IMMEDIATE;
@ -168,7 +168,6 @@ static xbox::dword_xt g_Xbox_PresentationInterval_Default = D3
static xbox::X_D3DSWAPDATA g_Xbox_SwapData = {0}; // current swap information
static xbox::X_D3DSWAPCALLBACK g_pXbox_SwapCallback = xbox::zeroptr; // Swap/Present callback routine
static xbox::X_D3DVBLANKDATA g_Xbox_VBlankData = {0}; // current vertical blank information
static xbox::X_D3DVBLANKCALLBACK g_pXbox_VerticalBlankCallback = xbox::zeroptr; // Vertical-Blank callback routine
xbox::X_D3DSurface *g_pXbox_BackBufferSurface = xbox::zeroptr;
static xbox::X_D3DSurface *g_pXbox_DefaultDepthStencilSurface = xbox::zeroptr;
@ -228,7 +227,6 @@ static xbox::dword_xt *g_Xbox_D3DDevice; // TODO: This should b
// Static Function(s)
static DWORD WINAPI EmuRenderWindow(LPVOID);
static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static xbox::void_xt NTAPI EmuUpdateTickCount(xbox::PVOID Arg);
static inline void EmuVerifyResourceIsRegistered(xbox::X_D3DResource *pResource, DWORD D3DUsage, int iTextureStage, DWORD dwSize);
static void UpdateCurrentMSpFAndFPS(); // Used for benchmarking/fps count
static void CxbxImpl_SetRenderTarget(xbox::X_D3DSurface *pRenderTarget, xbox::X_D3DSurface *pNewZStencil);
@ -320,15 +318,17 @@ g_EmuCDPD;
/*XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_LoadVertexShader_4, (xbox::dword_xt) );*/\
XB_MACRO(xbox::hresult_xt, WINAPI, D3DDevice_PersistDisplay, (xbox::void_xt) ); \
XB_MACRO(xbox::hresult_xt, WINAPI, D3DDevice_Reset, (xbox::X_D3DPRESENT_PARAMETERS*) ); \
XB_MACRO(xbox::hresult_xt, WINAPI, D3DDevice_Reset_0__LTCG_edi1, () ); \
XB_MACRO(xbox::hresult_xt, WINAPI, D3DDevice_Reset_0__LTCG_ebx1, () ); \
/*XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SelectVertexShader, (xbox::dword_xt, xbox::dword_xt) );*/\
/*XB_MACRO(xbox::void_xt, __stdcall, D3DDevice_SelectVertexShader_0, () );*/\
/*XB_MACRO(xbox::void_xt, __stdcall, D3DDevice_SelectVertexShader_4, (xbox::dword_xt) );*/\
/*XB_MACRO(xbox::void_xt, __stdcall, D3DDevice_SelectVertexShader_0__LTCG_eax1_ebx2, () );*/\
/*XB_MACRO(xbox::void_xt, __stdcall, D3DDevice_SelectVertexShader_4__LTCG_eax1, (xbox::dword_xt) );*/\
/*XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetGammaRamp, (xbox::dword_xt, CONST X_D3DGAMMARAMP*) );*/\
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetIndices, (xbox::X_D3DIndexBuffer*, xbox::uint_xt) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetIndices_4, (xbox::uint_xt) ); \
XB_MACRO(xbox::hresult_xt, WINAPI, D3DDevice_SetLight, (xbox::dword_xt, CONST xbox::X_D3DLIGHT8*) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetPixelShader, (xbox::dword_xt) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetPixelShader_0, () ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetPixelShader_0__LTCG_eax_handle, () ); \
XB_MACRO(xbox::void_xt, __fastcall, D3DDevice_SetRenderState_Simple, (xbox::dword_xt, xbox::dword_xt) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetRenderTarget, (xbox::X_D3DSurface*, xbox::X_D3DSurface*) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetRenderTarget_0, () ); \
@ -339,7 +339,7 @@ g_EmuCDPD;
XB_MACRO(xbox::void_xt, __fastcall, D3DDevice_SetStreamSource_8__LTCG_edx_StreamNumber, (void*, xbox::uint_xt, xbox::X_D3DVertexBuffer*, xbox::uint_xt) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetTexture, (xbox::dword_xt, xbox::X_D3DBaseTexture*) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetTexture_4__LTCG_eax_pTexture, (xbox::dword_xt) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetTexture_4, (xbox::X_D3DBaseTexture*) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetTexture_4__LTCG_eax_Stage, (xbox::X_D3DBaseTexture*) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetPalette, (xbox::dword_xt, xbox::X_D3DPalette*) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetPalette_4, (xbox::X_D3DPalette*) ); \
XB_MACRO(xbox::void_xt, WINAPI, D3DDevice_SetVertexShader, (xbox::dword_xt) ); \
@ -626,25 +626,13 @@ const char *D3DErrorString(HRESULT hResult)
return buffer;
}
void CxbxInitWindow(bool bFullInit)
void CxbxInitWindow()
{
g_EmuShared->GetVideoSettings(&g_XBVideo);
if(g_XBVideo.bFullScreen)
CxbxKrnl_hEmuParent = NULL;
// create timing thread
if (bFullInit && !bLLE_GPU)
{
xbox::HANDLE hThread;
xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, EmuUpdateTickCount, xbox::zeroptr, FALSE);
// We set the priority of this thread a bit higher, to assure reliable timing :
auto nativeHandle = GetNativeHandle(hThread);
assert(nativeHandle);
SetThreadPriority(*nativeHandle, THREAD_PRIORITY_ABOVE_NORMAL);
g_AffinityPolicy->SetAffinityOther(*nativeHandle);
}
/* TODO : Port this Dxbx code :
// create vblank handling thread
{
@ -680,6 +668,10 @@ void CxbxInitWindow(bool bFullInit)
g_renderbase->SetWindowRelease([] {
ImGui_ImplWin32_Shutdown();
});
(void) g_ShaderSources.Update();
g_ShaderSources.InitShaderHotloading();
}
void DrawUEM(HWND hWnd)
@ -1699,6 +1691,13 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
}
break;
case ID_SYNC_TIME_CHANGE:
{
// Sent by the GUI when it detects WM_TIMECHANGE
xbox::KeSystemTimeChanged.test_and_set();
}
break;
default:
break;
}
@ -1716,6 +1715,14 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
}
break;
case WM_TIMECHANGE:
{
// NOTE: this is only received if the loader was launched from the command line without the GUI
xbox::KeSystemTimeChanged.test_and_set();
return DefWindowProc(hWnd, msg, wParam, lParam);
}
break;
case WM_SYSKEYDOWN:
{
if(wParam == VK_RETURN)
@ -1923,47 +1930,25 @@ std::chrono::steady_clock::time_point GetNextVBlankTime()
return steady_clock::now() + duration_cast<steady_clock::duration>(ms);
}
// timing thread procedure
static xbox::void_xt NTAPI EmuUpdateTickCount(xbox::PVOID Arg)
void hle_vblank()
{
CxbxSetThreadName("Cxbx Timing Thread");
// Note: This whole code block can be removed once NV2A interrupts are implemented
// And Both Swap and Present can be ran unpatched
// Once that is in place, MiniPort + Direct3D will handle this on it's own!
g_Xbox_VBlankData.VBlank++;
EmuLog(LOG_LEVEL::DEBUG, "Timing thread is running.");
// TODO: Fixme. This may not be right...
g_Xbox_SwapData.SwapVBlank = 1;
auto nextVBlankTime = GetNextVBlankTime();
g_Xbox_VBlankData.Swap = 0;
while(true)
{
// Wait for VBlank
// Note: This whole code block can be removed once NV2A interrupts are implemented
// And Both Swap and Present can be ran unpatched
// Once that is in place, MiniPort + Direct3D will handle this on it's own!
SleepPrecise(nextVBlankTime);
nextVBlankTime = GetNextVBlankTime();
// TODO: This can't be accurate...
g_Xbox_SwapData.TimeUntilSwapVBlank = 0;
// Increment the VBlank Counter and Wake all threads there were waiting for the VBlank to occur
std::unique_lock<std::mutex> lk(g_VBConditionMutex);
g_Xbox_VBlankData.VBlank++;
g_VBConditionVariable.notify_all();
// TODO: Fixme. This may not be right...
g_Xbox_SwapData.SwapVBlank = 1;
if(g_pXbox_VerticalBlankCallback != xbox::zeroptr)
{
g_pXbox_VerticalBlankCallback(&g_Xbox_VBlankData);
}
g_Xbox_VBlankData.Swap = 0;
// TODO: This can't be accurate...
g_Xbox_SwapData.TimeUntilSwapVBlank = 0;
// TODO: Recalculate this for PAL version if necessary.
// Also, we should check the D3DPRESENT_INTERVAL value for accurracy.
// g_Xbox_SwapData.TimeBetweenSwapVBlanks = 1/60;
g_Xbox_SwapData.TimeBetweenSwapVBlanks = 0;
}
// TODO: Recalculate this for PAL version if necessary.
// Also, we should check the D3DPRESENT_INTERVAL value for accurracy.
// g_Xbox_SwapData.TimeBetweenSwapVBlanks = 1/60;
g_Xbox_SwapData.TimeBetweenSwapVBlanks = 0;
}
void UpdateDepthStencilFlags(IDirect3DSurface *pDepthStencilSurface)
@ -2271,7 +2256,7 @@ static void CreateDefaultD3D9Device
DrawInitialBlackScreen();
// Set up cache
g_VertexShaderSource.ResetD3DDevice(g_pD3DDevice);
g_VertexShaderCache.ResetD3DDevice(g_pD3DDevice);
// Set up ImGui's render backend
ImGui_ImplDX9_Init(g_pD3DDevice);
@ -2813,10 +2798,13 @@ void Direct3D_CreateDevice_End
{
#if 0 // Unused :
// Set g_Xbox_D3DDevice to point to the Xbox D3D Device
auto it = g_SymbolAddresses.find("D3DDEVICE");
auto it = g_SymbolAddresses.find("D3D_g_pDevice");
if (it != g_SymbolAddresses.end()) {
g_Xbox_D3DDevice = (DWORD*)it->second;
}
else {
EmuLog(LOG_LEVEL::ERROR2, "D3D_g_pDevice was not found!");
}
#endif
UpdateHostBackBufferDesc();
@ -3153,6 +3141,12 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(Direct3D_CreateDevice)
Direct3D_CreateDevice_Start(pPresentationParameters);
// HACK: Disable Software VertexProcessing (Fixes CreateDevice failure in Chihiro titles)
if (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) {
BehaviorFlags &= ~D3DCREATE_SOFTWARE_VERTEXPROCESSING;
BehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
// Only then call Xbox CreateDevice function
hresult_xt hRet = XB_TRMP(Direct3D_CreateDevice)(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);
@ -3164,30 +3158,41 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(Direct3D_CreateDevice)
// ******************************************************************
// * patch: D3DDevice_Reset
// ******************************************************************
xbox::hresult_xt WINAPI xbox::EMUPATCH(D3DDevice_Reset)
(
X_D3DPRESENT_PARAMETERS* pPresentationParameters
)
{
LOG_FUNC_ONE_ARG(pPresentationParameters)
static void CxbxImpl_Reset(xbox::X_D3DPRESENT_PARAMETERS* pPresentationParameters)
{
// Unlike the host version of Reset, The Xbox version does not actually reset the entire device
// Instead, it simply re-creates the backbuffer with a new configuration
// Store the new multisampling configuration
SetXboxMultiSampleType(pPresentationParameters->MultiSampleType);
// Update scaling aspect ratio
SetAspectRatioScale(pPresentationParameters);
// Update scaling aspect ratio
SetAspectRatioScale(pPresentationParameters);
// Since Reset will call create a new backbuffer surface, we can clear our current association
// NOTE: We don't actually free the Xbox data, the Xbox side will do this for us when we call the trampoline below.
// We must not reset the values to nullptr, since the XDK will re-use the same addresses for the data headers
// (they are members of the Direct3DDevice object). if we overwrite then, the reference to the xbox backbuffer will be lost
// and we'll get a black screen.
// and we'll get a black screen.
FreeHostResource(GetHostResourceKey(g_pXbox_BackBufferSurface));
FreeHostResource(GetHostResourceKey(g_pXbox_DefaultDepthStencilSurface));
// Below requirement for patched function(s) in order to function properly.
// Perform xbox's D3DDevice_Reset call.
// Perform CxbxImpl_SetRenderTarget call.
}
xbox::hresult_xt WINAPI xbox::EMUPATCH(D3DDevice_Reset)
(
X_D3DPRESENT_PARAMETERS* pPresentationParameters
)
{
LOG_FUNC_ONE_ARG(pPresentationParameters);
CxbxImpl_Reset(pPresentationParameters);
// Call the Xbox Reset function to do the rest of the work for us
hresult_xt hRet = XB_TRMP(D3DDevice_Reset)(pPresentationParameters);
@ -3198,6 +3203,70 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(D3DDevice_Reset)
return hRet;
}
static void D3DDevice_Reset_0__LTCG_edi1(xbox::X_D3DPRESENT_PARAMETERS* pPresentationParameters)
{
LOG_FUNC_ONE_ARG(pPresentationParameters);
}
__declspec(naked) xbox::hresult_xt WINAPI xbox::EMUPATCH(D3DDevice_Reset_0__LTCG_edi1)()
{
X_D3DPRESENT_PARAMETERS* pPresentationParameters;
__asm {
LTCG_PROLOGUE
mov pPresentationParameters, edi
}
// Log
D3DDevice_Reset_0__LTCG_edi1(pPresentationParameters);
CxbxImpl_Reset(pPresentationParameters);
// Call the Xbox version of DestroyResource
__asm {
mov edi, pPresentationParameters
call XB_TRMP(D3DDevice_Reset_0__LTCG_edi1)
}
CxbxImpl_SetRenderTarget(g_pXbox_RenderTarget, g_pXbox_DepthStencil);
__asm {
LTCG_EPILOGUE
ret
}
}
static void D3DDevice_Reset_0__LTCG_ebx1(xbox::X_D3DPRESENT_PARAMETERS* pPresentationParameters)
{
LOG_FUNC_ONE_ARG(pPresentationParameters);
}
__declspec(naked) xbox::hresult_xt WINAPI xbox::EMUPATCH(D3DDevice_Reset_0__LTCG_ebx1)()
{
X_D3DPRESENT_PARAMETERS* pPresentationParameters;
__asm {
LTCG_PROLOGUE
mov pPresentationParameters, ebx
}
// Log
D3DDevice_Reset_0__LTCG_ebx1(pPresentationParameters);
CxbxImpl_Reset(pPresentationParameters);
// Call the Xbox version of DestroyResource
__asm {
mov ebx, pPresentationParameters
call XB_TRMP(D3DDevice_Reset_0__LTCG_ebx1)
}
CxbxImpl_SetRenderTarget(g_pXbox_RenderTarget, g_pXbox_DepthStencil);
__asm {
LTCG_EPILOGUE
ret
}
}
// ******************************************************************
// * patch: D3DDevice_GetDisplayFieldStatus
@ -3237,12 +3306,12 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_GetDisplayFieldStatus)(X_D3DFIELD_
}
// ******************************************************************
// * patch: D3DDevice_BeginPush
// * patch: D3DDevice_BeginPush_4
// TODO: Find a test case and verify this
// Starting from XDK 4531, this changed to 1 parameter only.
// Is this definition incorrect, or did it change at some point?
// ******************************************************************
xbox::PDWORD WINAPI xbox::EMUPATCH(D3DDevice_BeginPush)(dword_xt Count)
xbox::PDWORD WINAPI xbox::EMUPATCH(D3DDevice_BeginPush_4)(dword_xt Count)
{
LOG_FUNC_ONE_ARG(Count);
@ -3260,11 +3329,11 @@ xbox::PDWORD WINAPI xbox::EMUPATCH(D3DDevice_BeginPush)(dword_xt Count)
}
// ******************************************************************
// * patch: D3DDevice_BeginPush2
// * patch: D3DDevice_BeginPush_8
// TODO: Find a test case and verify this: RalliSport Challenge XDK 4134
// For XDK before 4531
// ******************************************************************
xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BeginPush2)(dword_xt Count, dword_xt** ppPush)
xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BeginPush_8)(dword_xt Count, dword_xt** ppPush)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Count)
@ -3587,7 +3656,7 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_LoadVertexShader)
}
// Overload for logging
static void D3DDevice_SelectVertexShader_0
static void D3DDevice_SelectVertexShader_0__LTCG_eax1_ebx2
(
xbox::dword_xt Handle,
xbox::dword_xt Address
@ -3602,7 +3671,7 @@ static void D3DDevice_SelectVertexShader_0
// LTCG specific D3DDevice_SelectVertexShader function...
// This uses a custom calling convention where parameter is passed in EAX, EBX
// Test-case: Star Wars - Battlefront
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SelectVertexShader_0)
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SelectVertexShader_0__LTCG_eax1_ebx2)
(
)
{
@ -3615,7 +3684,7 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SelectVertexShad
}
// Log
D3DDevice_SelectVertexShader_0(Handle, Address);
D3DDevice_SelectVertexShader_0__LTCG_eax1_ebx2(Handle, Address);
CxbxImpl_SelectVertexShader(Handle, Address);
@ -3626,7 +3695,7 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SelectVertexShad
}
// Overload for logging
static void D3DDevice_SelectVertexShader_4
static void D3DDevice_SelectVertexShader_4__LTCG_eax1
(
xbox::dword_xt Handle,
xbox::dword_xt Address
@ -3641,7 +3710,7 @@ static void D3DDevice_SelectVertexShader_4
// LTCG specific D3DDevice_SelectVertexShader function...
// This uses a custom calling convention where parameter is passed in EAX
// Test-case: Aggressive Inline
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SelectVertexShader_4)
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SelectVertexShader_4__LTCG_eax1)
(
dword_xt Address
)
@ -3653,7 +3722,7 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SelectVertexShad
}
// Log
D3DDevice_SelectVertexShader_4(Handle, Address);
D3DDevice_SelectVertexShader_4__LTCG_eax1(Handle, Address);
CxbxImpl_SelectVertexShader(Handle, Address);
@ -4371,7 +4440,7 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTexture_4__LT
}
// Overload for logging
static void D3DDevice_SetTexture_4
static void D3DDevice_SetTexture_4__LTCG_eax_Stage
(
xbox::dword_xt Stage,
xbox::X_D3DBaseTexture *pTexture
@ -4386,7 +4455,7 @@ static void D3DDevice_SetTexture_4
// LTCG specific D3DDevice_SetTexture function...
// This uses a custom calling convention where Stage is passed in EAX
// Test-case: Metal Wolf Chaos
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTexture_4)
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTexture_4__LTCG_eax_Stage)
(
X_D3DBaseTexture *pTexture
)
@ -4398,13 +4467,13 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetTexture_4)
}
// Log
D3DDevice_SetTexture_4(Stage, pTexture);
D3DDevice_SetTexture_4__LTCG_eax_Stage(Stage, pTexture);
// Call the Xbox implementation of this function, to properly handle reference counting for us
__asm {
mov eax, Stage
push pTexture
call XB_TRMP(D3DDevice_SetTexture_4)
call XB_TRMP(D3DDevice_SetTexture_4__LTCG_eax_Stage)
}
g_pXbox_SetTexture[Stage] = pTexture;
@ -6456,31 +6525,6 @@ xbox::bool_xt WINAPI xbox::EMUPATCH(D3DDevice_GetOverlayUpdateStatus)()
return TRUE;
}
// ******************************************************************
// * patch: D3DDevice_BlockUntilVerticalBlank
// ******************************************************************
xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank)()
{
LOG_FUNC();
std::unique_lock<std::mutex> lk(g_VBConditionMutex);
g_VBConditionVariable.wait(lk);
}
// ******************************************************************
// * patch: D3DDevice_SetVerticalBlankCallback
// ******************************************************************
xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback)
(
X_D3DVBLANKCALLBACK pCallback
)
{
LOG_FUNC_ONE_ARG(pCallback);
g_pXbox_VerticalBlankCallback = pCallback;
}
// ******************************************************************
// * patch: D3DDevice_SetRenderState_Simple
// ******************************************************************
@ -7452,11 +7496,11 @@ void CxbxUpdateHostVertexShaderConstants()
}
// Placed this here until we find a better place
const uint32_t fogTableMode = XboxRenderStates.GetXboxRenderState(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGTABLEMODE);
const float fogTableMode = XboxRenderStates.GetXboxRenderState(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGTABLEMODE);
const float fogDensity = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGDENSITY);
const float fogStart = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGSTART);
const float fogEnd = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGEND);
float fogStuff[4] = { (float)fogTableMode, fogDensity, fogStart, fogEnd };
float fogStuff[4] = {fogTableMode, fogDensity, fogStart, fogEnd};
g_pD3DDevice->SetVertexShaderConstantF(CXBX_D3DVS_CONSTREG_FOGINFO, fogStuff, 1);
}
@ -7712,7 +7756,7 @@ xbox::void_xt CxbxImpl_SetPixelShader(xbox::dword_xt Handle)
}
// Overload for logging
static void D3DDevice_SetPixelShader_0
static void D3DDevice_SetPixelShader_0__LTCG_eax_handle
(
xbox::dword_xt Handle
)
@ -7725,7 +7769,7 @@ static void D3DDevice_SetPixelShader_0
// Test-case: Metal Wolf Chaos
// Test-case: Lord of the Rings: The Third Age
// Test-case: Midtown Madness 3
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetPixelShader_0)()
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetPixelShader_0__LTCG_eax_handle)()
{
dword_xt Handle;
__asm {
@ -7734,11 +7778,11 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetPixelShader_0
}
// Log
D3DDevice_SetPixelShader_0(Handle);
D3DDevice_SetPixelShader_0__LTCG_eax_handle(Handle);
__asm {
mov eax, Handle
call XB_TRMP(D3DDevice_SetPixelShader_0)
call XB_TRMP(D3DDevice_SetPixelShader_0__LTCG_eax_handle)
}
CxbxImpl_SetPixelShader(Handle);
@ -7771,7 +7815,7 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetPixelShader)
// This uses a custom calling convention where parameter is passed in ECX, EAX and Stack
// Test Case: Conker
// ******************************************************************
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVertices_4)
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVertices_4__LTCG_ecx2_eax3)
(
X_D3DPRIMITIVETYPE PrimitiveType
)
@ -7792,6 +7836,30 @@ __declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVertices_4)
}
}
// ******************************************************************
// * patch: D3DDevice_DrawVertices_8__LTCG_eax3
// LTCG specific D3DDevice_DrawVertices function...
// ******************************************************************
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVertices_8__LTCG_eax3)
(
X_D3DPRIMITIVETYPE PrimitiveType,
uint_xt StartVertex
)
{
uint_xt VertexCount;
__asm {
LTCG_PROLOGUE
mov VertexCount, eax
}
EMUPATCH(D3DDevice_DrawVertices)(PrimitiveType, StartVertex, VertexCount);
__asm {
LTCG_EPILOGUE
ret 4
}
}
// ******************************************************************
// * patch: D3DDevice_DrawVertices
// ******************************************************************
@ -7815,7 +7883,7 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVertices)
return;
}
// TODO : Call unpatched CDevice_SetStateVB(0);
// TODO : Call unpatched CDevice_SetStateVB[_8](0);
CxbxUpdateNativeD3DResources();
@ -7953,7 +8021,7 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVerticesUP)
// LTCG specific D3DDevice_DrawVerticesUP function...
// This uses a custom calling convention where pVertexStreamZeroData is passed in EBX
// Test-case: NASCAR Heat 20002
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVerticesUP_12)
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawVerticesUP_12__LTCG_ebx3)
(
X_D3DPRIMITIVETYPE PrimitiveType,
uint_xt VertexCount,
@ -7999,7 +8067,7 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_DrawIndexedVertices)
return;
}
// TODO : Call unpatched CDevice_SetStateVB(g_Xbox_BaseVertexIndex);
// TODO : Call unpatched CDevice_SetStateVB[_8](g_Xbox_BaseVertexIndex);
CxbxUpdateNativeD3DResources();
@ -9008,28 +9076,84 @@ xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_GetProjectionViewportMatrix)
// ******************************************************************
// * patch: CDevice_SetStateVB (D3D::CDevice::SetStateVB)
// ******************************************************************
xbox::void_xt WINAPI xbox::EMUPATCH(CDevice_SetStateVB)(ulong_xt Unknown1 )
xbox::void_xt WINAPI xbox::EMUPATCH(CDevice_SetStateVB)(ulong_xt Unknown1)
{
LOG_FUNC_ONE_ARG(Unknown1);
addr_xt _this;
__asm mov _this, ecx;
LOG_FUNC_BEGIN
LOG_FUNC_ARG(_this)
LOG_FUNC_ARG(Unknown1)
LOG_FUNC_END;
// TODO: Anything?
// __asm int 3;
LOG_UNIMPLEMENTED();
LOG_UNIMPLEMENTED();
}
xbox::void_xt WINAPI xbox::EMUPATCH(CDevice_SetStateVB_8)(addr_xt _this, ulong_xt Unknown1)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(_this)
LOG_FUNC_ARG(Unknown1)
LOG_FUNC_END;
// TODO: Anything?
// __asm int 3;
LOG_UNIMPLEMENTED();
}
// ******************************************************************
// * patch: CDevice_SetStateUP (D3D::CDevice::SetStateUP)
// ******************************************************************
xbox::void_xt WINAPI xbox::EMUPATCH(CDevice_SetStateUP)()
xbox::void_xt CxbxrImpl_CDevice_SetStateUP(xbox::addr_xt _this)
{
LOG_FUNC();
LOG_UNIMPLEMENTED();
// TODO: Anything?
// __asm int 3;
//__asm int 3;
}
xbox::void_xt WINAPI xbox::EMUPATCH(CDevice_SetStateUP)()
{
addr_xt _this;
__asm mov _this, ecx;
LOG_FUNC_ONE_ARG(_this);
CxbxrImpl_CDevice_SetStateUP(_this);
}
xbox::void_xt WINAPI xbox::EMUPATCH(CDevice_SetStateUP_4)(xbox::addr_xt _this)
{
LOG_FUNC_ONE_ARG(_this);
CxbxrImpl_CDevice_SetStateUP(_this);
}
static void CDevice_SetStateUP_0__LTCG_esi1(xbox::addr_xt _this)
{
LOG_FUNC_ONE_ARG(_this);
}
__declspec(naked) xbox::void_xt WINAPI xbox::EMUPATCH(CDevice_SetStateUP_0__LTCG_esi1)()
{
addr_xt _this;
__asm {
LTCG_PROLOGUE
mov _this, esi
}
// Log
CDevice_SetStateUP_0__LTCG_esi1(_this);
CxbxrImpl_CDevice_SetStateUP(_this);
__asm {
LTCG_EPILOGUE
ret
}
}
// ******************************************************************

View file

@ -4256,3 +4256,27 @@ HRESULT WINAPI XTL::EMUPATCH(D3DDevice_GetVertexShaderInput)
return 0;
}
// ******************************************************************
// * patch: D3DDevice_BlockUntilVerticalBlank
// ******************************************************************
xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank)()
{
LOG_FUNC();
std::unique_lock<std::mutex> lk(g_VBConditionMutex);
g_VBConditionVariable.wait(lk);
}
// ******************************************************************
// * patch: D3DDevice_SetVerticalBlankCallback
// ******************************************************************
xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback)
(
X_D3DVBLANKCALLBACK pCallback
)
{
LOG_FUNC_ONE_ARG(pCallback);
g_pXbox_VerticalBlankCallback = pCallback;
}

View file

@ -40,7 +40,7 @@
void LookupTrampolinesD3D();
// initialize render window
extern void CxbxInitWindow(bool bFullInit);
extern void CxbxInitWindow();
void CxbxUpdateNativeD3DResources();
@ -144,14 +144,14 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_GetDisplayFieldStatus)
);
// ******************************************************************
// * patch: D3DDevice_BeginPush
// * patch: D3DDevice_BeginPush_4
// ******************************************************************
xbox::PDWORD WINAPI EMUPATCH(D3DDevice_BeginPush)(dword_xt Count);
xbox::PDWORD WINAPI EMUPATCH(D3DDevice_BeginPush_4)(dword_xt Count);
// ******************************************************************
// * patch: D3DDevice_BeginPush2 //two arg version for xdk before 4531
// * patch: D3DDevice_BeginPush_8
// ******************************************************************
xbox::void_xt WINAPI EMUPATCH(D3DDevice_BeginPush2)(dword_xt Count, dword_xt **ppPush);
xbox::void_xt WINAPI EMUPATCH(D3DDevice_BeginPush_8)(dword_xt Count, dword_xt **ppPush);
// ******************************************************************
// * patch: D3DDevice_EndPush
@ -213,8 +213,8 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_SelectVertexShader)
dword_xt Address
);
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SelectVertexShader_0)();
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SelectVertexShader_4)
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SelectVertexShader_0__LTCG_eax1_ebx2)();
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SelectVertexShader_4__LTCG_eax1)
(
dword_xt Address
);
@ -359,6 +359,10 @@ xbox::hresult_xt WINAPI EMUPATCH(D3DDevice_Reset)
X_D3DPRESENT_PARAMETERS *pPresentationParameters
);
xbox::hresult_xt WINAPI EMUPATCH(D3DDevice_Reset_0__LTCG_edi1)();
xbox::hresult_xt WINAPI EMUPATCH(D3DDevice_Reset_0__LTCG_ebx1)();
// ******************************************************************
// * patch: D3DDevice_GetRenderTarget
// ******************************************************************
@ -505,7 +509,7 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetPixelShader)
dword_xt Handle
);
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetPixelShader_0)();
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetPixelShader_0__LTCG_eax_handle)();
// ******************************************************************
// * patch: D3DDevice_CreateTexture2
@ -611,7 +615,7 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetTexture_4__LTCG_eax_pTexture)
dword_xt Stage
);
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetTexture_4)
xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetTexture_4__LTCG_eax_Stage)
(
X_D3DBaseTexture *pTexture
);
@ -1390,18 +1394,27 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_SetVertexShader_0)();
xbox::void_xt WINAPI EMUPATCH(D3DDevice_DrawVertices)
(
X_D3DPRIMITIVETYPE PrimitiveType,
uint_xt StartVertex,
uint_xt VertexCount
uint_xt StartVertex,
uint_xt VertexCount
);
// ******************************************************************
// * patch: D3DDevice_DrawVertices_4
// * patch: D3DDevice_DrawVertices_4__LTCG_ecx2_eax3
// ******************************************************************
xbox::void_xt WINAPI EMUPATCH(D3DDevice_DrawVertices_4)
xbox::void_xt WINAPI EMUPATCH(D3DDevice_DrawVertices_4__LTCG_ecx2_eax3)
(
X_D3DPRIMITIVETYPE PrimitiveType
);
// ******************************************************************
// * patch: D3DDevice_DrawVertices_8__LTCG_eax3
// ******************************************************************
xbox::void_xt WINAPI EMUPATCH(D3DDevice_DrawVertices_8__LTCG_eax3)
(
X_D3DPRIMITIVETYPE PrimitiveType,
uint_xt StartVertex
);
// ******************************************************************
// * patch: D3DDevice_DrawVerticesUP
// ******************************************************************
@ -1413,11 +1426,11 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_DrawVerticesUP)
uint_xt VertexStreamZeroStride
);
xbox::void_xt WINAPI EMUPATCH(D3DDevice_DrawVerticesUP_12)
xbox::void_xt WINAPI EMUPATCH(D3DDevice_DrawVerticesUP_12__LTCG_ebx3)
(
X_D3DPRIMITIVETYPE PrimitiveType,
uint_xt VertexCount,
uint_xt VertexStreamZeroStride
uint_xt VertexCount,
uint_xt VertexStreamZeroStride
);
@ -1897,12 +1910,15 @@ xbox::void_xt WINAPI EMUPATCH(D3DDevice_GetTexture)
// ******************************************************************
// * patch: CDevice_SetStateVB (D3D::CDevice::SetStateVB)
// ******************************************************************
xbox::void_xt WINAPI EMUPATCH(CDevice_SetStateVB)( xbox::ulong_xt Unknown1 );
xbox::void_xt WINAPI EMUPATCH(CDevice_SetStateVB)(xbox::ulong_xt Unknown1);
xbox::void_xt WINAPI EMUPATCH(CDevice_SetStateVB_8)(xbox::addr_xt _this, xbox::ulong_xt Unknown1);
// ******************************************************************
// * patch: CDevice_SetStateUP (D3D::CDevice::SetStateUP)
// ******************************************************************
xbox::void_xt WINAPI EMUPATCH(CDevice_SetStateUP)();
xbox::void_xt WINAPI EMUPATCH(CDevice_SetStateUP_4)(xbox::addr_xt _this);
xbox::void_xt WINAPI EMUPATCH(CDevice_SetStateUP_0__LTCG_esi1)();
// ******************************************************************
// * patch: D3DDevice_SetStipple

View file

@ -237,6 +237,24 @@ TextureArgs ExecuteTextureStage(
float4 main(const PS_INPUT input) : COLOR {
// Calculate the fog factor
float fogFactor;
if (state.FogEnable == 0){
fogFactor = 1;
}
else{
if (state.FogTableMode == FOG_TABLE_NONE)
fogFactor = input.iFog;
if (state.FogTableMode == FOG_TABLE_EXP)
fogFactor = 1 / exp(input.iFog * state.FogDensity); // 1 / e^(d * density)
if (state.FogTableMode == FOG_TABLE_EXP2)
fogFactor = 1 / exp(pow(input.iFog * state.FogDensity, 2)); // 1 / e^((d * density)^2)
if (state.FogTableMode == FOG_TABLE_LINEAR)
fogFactor = (state.FogEnd - input.iFog) / (state.FogEnd - state.FogStart); // (end - d) / (end - start)
if (state.FogEnable == 0)
fogFactor = 1;
}
TexCoords = input.iT;
// Each stage is passed and returns
@ -284,7 +302,7 @@ float4 main(const PS_INPUT input) : COLOR {
// Add fog if enabled
if (state.FogEnable) {
ctx.CURRENT.rgb = lerp(state.FogColor.rgb, ctx.CURRENT.rgb, saturate(input.iFog));
ctx.CURRENT.rgb = lerp(state.FogColor.rgb, ctx.CURRENT.rgb, saturate(fogFactor));
}
// Add specular if enabled

View file

@ -63,10 +63,16 @@ namespace FixedFunctionPixelShader {
const float X_D3DTA_COMPLEMENT = 0x00000010; // take 1.0 - x (read modifier)
const float X_D3DTA_ALPHAREPLICATE = 0x00000020; // replicate alpha to color components (read modifier)
const int SAMPLE_NONE = 0;
const int SAMPLE_2D = 1;
const int SAMPLE_3D = 2;
const int SAMPLE_CUBE = 3;
const int SAMPLE_NONE = 0;
const int SAMPLE_2D = 1;
const int SAMPLE_3D = 2;
const int SAMPLE_CUBE = 3;
// https://docs.microsoft.com/en-us/windows/win32/direct3d9/fog-formulas
const float FOG_TABLE_NONE = 0;
const float FOG_TABLE_EXP = 1;
const float FOG_TABLE_EXP2 = 2;
const float FOG_TABLE_LINEAR = 3;
// This state is passed to the shader
struct PsTextureStageState {
@ -125,7 +131,11 @@ namespace FixedFunctionPixelShader {
alignas(16) float SpecularEnable;
alignas(16) float FogEnable;
alignas(16) float3 FogColor;
};
alignas(16) float FogTableMode;
alignas(16) float FogDensity;
alignas(16) float FogStart;
alignas(16) float FogEnd;
};
#ifdef __cplusplus
} // FixedFunctionPixelShader namespace
#endif

View file

@ -288,20 +288,8 @@ float DoFog(const VS_INPUT xIn)
fogDepth = abs(Projection.Position.z);
if (state.Fog.DepthMode == FixedFunctionVertexShader::FOG_DEPTH_W)
fogDepth = Projection.Position.w;
// Calculate the fog factor
// Some of this might be better done in the pixel shader?
float fogFactor;
if (state.Fog.TableMode == FixedFunctionVertexShader::FOG_TABLE_NONE)
fogFactor = fogDepth;
if (state.Fog.TableMode == FixedFunctionVertexShader::FOG_TABLE_EXP)
fogFactor = 1 / exp(fogDepth * state.Fog.Density); // 1 / e^(d * density)
if (state.Fog.TableMode == FixedFunctionVertexShader::FOG_TABLE_EXP2)
fogFactor = 1 / exp(pow(fogDepth * state.Fog.Density, 2)); // 1 / e^((d * density)^2)
if (state.Fog.TableMode == FixedFunctionVertexShader::FOG_TABLE_LINEAR)
fogFactor = (state.Fog.End - fogDepth) / (state.Fog.End - state.Fog.Start); // (end - d) / (end - start)
return fogFactor;
return fogDepth;
}
float4 DoTexCoord(const uint stage, const VS_INPUT xIn)
@ -351,9 +339,9 @@ float4 DoTexCoord(const uint stage, const VS_INPUT xIn)
// TODO move alongside the texture transformation when it stops angering the HLSL compiler
const float componentCount = state.TexCoordComponentCount[texCoordIndex];
if (componentCount == 1)
texCoord.yzw = float3(1, 0, 0);
texCoord.yzw = float3(0, 0, 1);
if (componentCount == 2)
texCoord.zw = float2(1, 0);
texCoord.zw = float2(0, 1);
if (componentCount == 3)
texCoord.w = 1;
} // Generate texture coordinates
@ -390,14 +378,15 @@ float4 DoTexCoord(const uint stage, const VS_INPUT xIn)
// Test case: ProjectedTexture sample, which uses 3 coordinates
// We'll need to implement the divide when D3D stops handling it for us?
// https://docs.microsoft.com/en-us/windows/win32/direct3d9/d3dtexturetransformflags
// padding makes no differences in LLE. LLE works with ProjectedTexture sample without padding, but to be devided by w is necessary.
if (projected)
{
if (countFlag == 1)
texCoord.yzw = texCoord.x;
if (countFlag == 2)
texCoord.zw = texCoord.y;
if (countFlag == 3)
texCoord.w = texCoord.z;
//if (countFlag == 1)
//texCoord.yz = texCoord.x;
//if (countFlag == 2)
//texCoord.z = texCoord.y;
//texCoord.xyzw = texCoord.xyzw / texCoord.w;
}
return texCoord;

View file

@ -289,12 +289,7 @@ bool IsTextureSampled(DecodedRegisterCombiner* pShader, int reg)
void BuildShader(DecodedRegisterCombiner* pShader, std::stringstream& hlsl)
{
// Include HLSL header and footer as raw strings :
static const std::string hlsl_template[4] = {
#include "core\hle\D3D8\Direct3D9\CxbxPixelShaderTemplate.hlsl"
};
hlsl << hlsl_template[0]; // Start with the HLSL template header
hlsl << g_ShaderSources.pixelShaderTemplateHlsl[0]; // Start with the HLSL template header
hlsl << "\n#define ALPHAKILL {"
<< (pShader->AlphaKill[0] ? "true, " : "false, ")
@ -341,9 +336,9 @@ void BuildShader(DecodedRegisterCombiner* pShader, std::stringstream& hlsl)
OutputDefineFlag(hlsl, pShader->FinalCombiner.ComplementV1, "PS_FINALCOMBINERSETTING_COMPLEMENT_V1");
OutputDefineFlag(hlsl, pShader->FinalCombiner.ComplementR0, "PS_FINALCOMBINERSETTING_COMPLEMENT_R0");
OutputDefineFlag(hlsl, pShader->FinalCombiner.ClampSum, "PS_FINALCOMBINERSETTING_CLAMP_SUM");
hlsl << '\n';
hlsl << hlsl_template[1];
hlsl << hlsl_template[2];
hlsl << g_ShaderSources.pixelShaderTemplateHlsl[1];
// Generate all four texture stages
for (unsigned i = 0; i < PSH_XBOX_MAX_T_REGISTER_COUNT; i++) {
@ -390,7 +385,7 @@ void BuildShader(DecodedRegisterCombiner* pShader, std::stringstream& hlsl)
FinalCombinerStageHlsl(hlsl, pShader->FinalCombiner, pShader->hasFinalCombiner);
hlsl << hlsl_template[3]; // Finish with the HLSL template footer
hlsl << g_ShaderSources.pixelShaderTemplateHlsl[2]; // Finish with the HLSL template footer
}
// recompile xbox pixel shader function

View file

@ -36,20 +36,14 @@ void SetXboxMultiSampleType(xbox::X_D3DMULTISAMPLE_TYPE value);
bool XboxRenderStateConverter::Init()
{
if (g_SymbolAddresses.find("D3DDeferredRenderState") != g_SymbolAddresses.end()) {
D3D__RenderState = (uint32_t*)g_SymbolAddresses["D3DDeferredRenderState"];
// Get render state
if (g_SymbolAddresses.find("D3D_g_RenderState") != g_SymbolAddresses.end()) {
D3D__RenderState = (uint32_t*)g_SymbolAddresses["D3D_g_RenderState"];
} else {
EmuLog(LOG_LEVEL::ERROR2, "D3D_g_RenderState was not found!");
return false;
}
// At this point, D3D__RenderState points to the first Deferred render state
// Do a little magic to verify that it's correct, then count back to determine the
// start offset of the entire structure
VerifyAndFixDeferredRenderStateOffset();
// Now use the verified Deferred offset to derive the D3D__RenderState offset
DeriveRenderStateOffsetFromDeferredRenderStateOffset();
// Build a mapping of Cxbx Render State indexes to indexes within the current XDK
BuildRenderStateMappingTable();
@ -70,54 +64,6 @@ bool IsRenderStateAvailableInCurrentXboxD3D8Lib(RenderStateInfo& aRenderStateInf
return bIsRenderStateAvailable;
}
void XboxRenderStateConverter::VerifyAndFixDeferredRenderStateOffset()
{
DWORD CullModeOffset = g_SymbolAddresses["D3DRS_CULLMODE"];
// If we found a valid CullMode offset, verify the symbol location
if (CullModeOffset == 0) {
EmuLog(LOG_LEVEL::WARNING, "D3DRS_CULLMODE could not be found. Please update the XbSymbolDatabase submodule");
return;
}
// Calculate index of D3DRS_CULLMODE for this XDK. We start counting from the first deferred state (D3DRS_FOGENABLE)
DWORD CullModeIndex = 0;
for (int i = xbox::X_D3DRS_DEFERRED_FIRST; i < xbox::X_D3DRS_CULLMODE; i++) {
auto RenderStateInfo = GetDxbxRenderStateInfo(i);
if (IsRenderStateAvailableInCurrentXboxD3D8Lib(RenderStateInfo)) {
CullModeIndex++;
}
}
// If the offset was incorrect, calculate the correct offset, log it, and fix it
if ((DWORD)(&D3D__RenderState[CullModeIndex]) != CullModeOffset) {
DWORD CorrectOffset = CullModeOffset - (CullModeIndex * sizeof(DWORD));
EmuLog(LOG_LEVEL::WARNING, "EmuD3DDeferredRenderState returned by XboxSymbolDatabase (0x%08X) was incorrect. Correcting to be 0x%08X.\nPlease file an issue with the XbSymbolDatabase project", D3D__RenderState, CorrectOffset);
D3D__RenderState = (uint32_t*)CorrectOffset;
}
}
void XboxRenderStateConverter::DeriveRenderStateOffsetFromDeferredRenderStateOffset()
{
// When this function is called. D3D__RenderState actually points to the first deferred render state
// (this is X_D3DRS_FOGENABLE). We can count back from this using our RenderStateInfo table to find
// the start of D3D__RenderStates.
// Count the number of render states (for this XDK) between 0 and the first deferred render state (D3DRS_FOGENABLE)
int FirstDeferredRenderStateOffset = 0;
for (unsigned int RenderState = xbox::X_D3DRS_FIRST; RenderState < xbox::X_D3DRS_DEFERRED_FIRST; RenderState++) {
// if the current renderstate exists in this XDK version, count it
auto RenderStateInfo = GetDxbxRenderStateInfo(RenderState);
if (IsRenderStateAvailableInCurrentXboxD3D8Lib(RenderStateInfo)) {
FirstDeferredRenderStateOffset++;
}
}
// At this point, FirstDeferredRenderStateOffset should point to the index of D3DRS_FOGENABLE for the given XDK
// This will be correct as long as our table DxbxRenderStateInfo is correct
// We can get the correct 0 offset by using a negative index
D3D__RenderState = &D3D__RenderState[-FirstDeferredRenderStateOffset];
}
void XboxRenderStateConverter::BuildRenderStateMappingTable()
{
EmuLog(LOG_LEVEL::INFO, "Building Cxbx to XDK Render State Mapping Table");

View file

@ -29,10 +29,18 @@
#include <d3dcompiler.h>
#include "Shader.h"
#include "common/FilePaths.hpp" // For szFilePath_CxbxReloaded_Exe
#include "core\kernel\init\CxbxKrnl.h" // LOG_TEST_CASE
#include "core\kernel\support\Emu.h" // EmuLog
#include <filesystem>
#include <fstream>
#include <array>
#include <thread>
//#include <sstream>
ShaderSources g_ShaderSources;
std::string DebugPrependLineNumbers(std::string shaderString) {
std::stringstream shader(shaderString);
auto debugShader = std::stringstream();
@ -140,3 +148,173 @@ extern HRESULT EmuCompileShader
return hRet;
}
std::ifstream OpenWithRetry(const std::string& path) {
auto fstream = std::ifstream(path);
int failures = 0;
while (fstream.fail()) {
Sleep(50);
fstream = std::ifstream(path);
if (failures++ > 10) {
// crash?
CxbxrAbort("Error opening shader file: %s", path);
break;
}
}
return fstream;
}
int ShaderSources::Update() {
int versionOnDisk = shaderVersionOnDisk;
if (shaderVersionLoadedFromDisk != versionOnDisk) {
LoadShadersFromDisk();
shaderVersionLoadedFromDisk = versionOnDisk;
}
return shaderVersionLoadedFromDisk;
}
void ShaderSources::LoadShadersFromDisk() {
const auto hlslDir = std::filesystem::path(szFilePath_CxbxReloaded_Exe)
.parent_path()
.append("hlsl");
// Pixel Shader Template
{
std::stringstream tmp;
auto dir = hlslDir;
dir.append("CxbxPixelShaderTemplate.hlsl");
tmp << OpenWithRetry(dir.string()).rdbuf();
std::string hlsl = tmp.str();
// Split the HLSL file on insertion points
std::array<std::string, 2> insertionPoints = {
"// <HARDCODED STATE GOES HERE>\n",
"// <XBOX SHADER PROGRAM GOES HERE>\n",
};
int pos = 0;
for (int i = 0; i < insertionPoints.size(); i++) {
auto insertionPoint = insertionPoints[i];
auto index = hlsl.find(insertionPoint, pos);
if (index == std::string::npos) {
// Handle broken shaders
this->pixelShaderTemplateHlsl[i] = "";
}
else {
this->pixelShaderTemplateHlsl[i] = hlsl.substr(pos, index - pos);
pos = index + insertionPoint.length();
}
}
this->pixelShaderTemplateHlsl[insertionPoints.size()] = hlsl.substr(pos);
}
// Fixed Function Pixel Shader
{
auto dir = hlslDir;
this->fixedFunctionPixelShaderPath = dir.append("FixedFunctionPixelShader.hlsl").string();
std::stringstream tmp;
tmp << OpenWithRetry(this->fixedFunctionPixelShaderPath).rdbuf();
this->fixedFunctionPixelShaderHlsl = tmp.str();
}
// Vertex Shader Template
{
std::stringstream tmp;
auto dir = hlslDir;
dir.append("CxbxVertexShaderTemplate.hlsl");
tmp << OpenWithRetry(dir.string()).rdbuf();
std::string hlsl = tmp.str();
const std::string insertionPoint = "// <XBOX SHADER PROGRAM GOES HERE>\n";
auto index = hlsl.find(insertionPoint);
if (index == std::string::npos) {
// Handle broken shaders
this->vertexShaderTemplateHlsl[0] = hlsl;
this->vertexShaderTemplateHlsl[1] = "";
}
else
{
this->vertexShaderTemplateHlsl[0] = hlsl.substr(0, index);
this->vertexShaderTemplateHlsl[1] = hlsl.substr(index + insertionPoint.length());
}
}
// Fixed Function Vertex Shader
{
auto dir = hlslDir;
this->fixedFunctionVertexShaderPath = dir.append("FixedFunctionVertexShader.hlsl").string();
std::stringstream tmp;
tmp << OpenWithRetry(this->fixedFunctionVertexShaderPath).rdbuf();
this->fixedFunctionVertexShaderHlsl = tmp.str();
}
// Passthrough Vertex Shader
{
auto dir = hlslDir;
this->vertexShaderPassthroughPath = dir.append("CxbxVertexShaderPassthrough.hlsl").string();
std::stringstream tmp;
tmp << OpenWithRetry(this->vertexShaderPassthroughPath).rdbuf();
this->vertexShaderPassthroughHlsl = tmp.str();
}
}
void ShaderSources::InitShaderHotloading() {
static std::jthread fsWatcherThread;
if (fsWatcherThread.joinable()) {
EmuLog(LOG_LEVEL::ERROR2, "Ignoring request to start shader file watcher - it has already been started.");
return;
}
EmuLog(LOG_LEVEL::DEBUG, "Starting shader file watcher...");
fsWatcherThread = std::jthread([]{
// Determine the filename and directory for the fixed function shader
char cxbxExePath[MAX_PATH];
GetModuleFileName(GetModuleHandle(nullptr), cxbxExePath, MAX_PATH);
auto hlslDir = std::filesystem::path(cxbxExePath).parent_path().append("hlsl/");
HANDLE changeHandle = FindFirstChangeNotification(hlslDir.string().c_str(), false, FILE_NOTIFY_CHANGE_LAST_WRITE);
if (changeHandle == INVALID_HANDLE_VALUE) {
DWORD errorCode = GetLastError();
EmuLog(LOG_LEVEL::ERROR2, "Error initializing shader file watcher: %d", errorCode);
return 1;
}
while (true) {
if (FindNextChangeNotification(changeHandle)) {
WaitForSingleObject(changeHandle, INFINITE);
// Wait for changes to stop..
// Will usually be at least two - one for the file and one for the directory
while (true) {
FindNextChangeNotification(changeHandle);
if (WaitForSingleObject(changeHandle, 100) == WAIT_TIMEOUT) {
break;
}
}
EmuLog(LOG_LEVEL::DEBUG, "Change detected in shader folder");
g_ShaderSources.shaderVersionOnDisk++;
}
else {
EmuLog(LOG_LEVEL::ERROR2, "Shader filewatcher failed to get the next notification");
break;
}
}
EmuLog(LOG_LEVEL::DEBUG, "Shader file watcher exiting...");
// until there is a way to disable hotloading
// this is always an error
FindCloseChangeNotification(changeHandle);
return 1;
});
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <string> // std::string
#include <d3dcompiler.h> // ID3DBlob (via d3d9.h > d3d11shader.h > d3dcommon.h)
@ -10,3 +11,38 @@ extern HRESULT EmuCompileShader
ID3DBlob** ppHostShader,
const char* pSourceName = nullptr
);
struct ShaderSources {
// Pixel Shader
std::string pixelShaderTemplateHlsl[3];
std::string fixedFunctionPixelShaderHlsl;
std::string fixedFunctionPixelShaderPath;
// Vertex Shader
std::string vertexShaderTemplateHlsl[2];
std::string fixedFunctionVertexShaderHlsl;
std::string fixedFunctionVertexShaderPath;
std::string vertexShaderPassthroughHlsl;
std::string vertexShaderPassthroughPath;
// Load shaders from disk (if out-of-date)
// and return the current loaded shader version
int Update();
// Start a thread to watch for changes in the shader folder
void InitShaderHotloading();
private:
void LoadShadersFromDisk();
// counts upwards on every change detected to the shader source files at runtime
std::atomic_int shaderVersionOnDisk = 0;
// current loaded shader version
// Initialized to < shaderVersionOnDisk
int shaderVersionLoadedFromDisk = -1;
};
extern ShaderSources g_ShaderSources;

View file

@ -79,11 +79,12 @@ TextureStateInfo CxbxTextureStateInfo[] = {
bool XboxTextureStateConverter::Init(XboxRenderStateConverter* pState)
{
// Deferred states start at 0, this means that D3DDeferredTextureState IS D3D__TextureState
// Deferred states start at 0, this means that D3D_g_DeferredTextureState IS D3D__TextureState
// No further works is required to derive the offset
if (g_SymbolAddresses.find("D3DDeferredTextureState") != g_SymbolAddresses.end()) {
D3D__TextureState = (uint32_t*)g_SymbolAddresses["D3DDeferredTextureState"];
if (g_SymbolAddresses.find("D3D_g_DeferredTextureState") != g_SymbolAddresses.end()) {
D3D__TextureState = (uint32_t*)g_SymbolAddresses["D3D_g_DeferredTextureState"];
} else {
EmuLog(LOG_LEVEL::ERROR2, "D3D_g_DeferredTextureState was not found!");
return false;
}

View file

@ -4,9 +4,7 @@
#include "VertexShader.h" // EmuCompileVertexShader
#include "core\kernel\init\CxbxKrnl.h" // implicit CxbxKrnl_Xbe used in LOG_TEST_CASE
#include "core\kernel\support\Emu.h" // LOG_TEST_CASE (via Logging.h)
#include "common/FilePaths.hpp" // For szFilePath_CxbxReloaded_Exe
#include <fstream>
#include <sstream> // std::stringstream
extern const char* g_vs_model = vs_model_3_0;
@ -290,26 +288,23 @@ extern HRESULT EmuCompileVertexShader
ID3DBlob** ppHostShader
)
{
// Include HLSL header and footer as raw strings :
static std::string hlsl_template[2] = {
#include "core\hle\D3D8\Direct3D9\CxbxVertexShaderTemplate.hlsl"
};
auto hlsl_stream = std::stringstream();
hlsl_stream << hlsl_template[0]; // Start with the HLSL template header
assert(pIntermediateShader->Instructions.size() > 0);
BuildShader(pIntermediateShader, hlsl_stream);
hlsl_stream << hlsl_template[1]; // Finish with the HLSL template footer
// Combine the shader template with the shader program
auto hlsl_stream = std::stringstream();
hlsl_stream << g_ShaderSources.vertexShaderTemplateHlsl[0]; // Start with the HLSL template header
BuildShader(pIntermediateShader, hlsl_stream);
hlsl_stream << g_ShaderSources.vertexShaderTemplateHlsl[1]; // Finish with the HLSL template footer
std::string hlsl_str = hlsl_stream.str();
HRESULT hRet = EmuCompileShader(hlsl_str, g_vs_model, ppHostShader, "CxbxVertexShaderTemplate.hlsl");
const char* notionalSourceName = "CxbxVertexShaderTemplate.hlsl";
HRESULT hRet = EmuCompileShader(hlsl_str, g_vs_model, ppHostShader, notionalSourceName);
if (FAILED(hRet) && (g_vs_model != vs_model_3_0)) {
// If the shader failed in the default vertex shader model, retry in vs_model_3_0
// This allows shaders too large for 2_a to be compiled (Test Case: Shenmue 2)
EmuLog(LOG_LEVEL::WARNING, "Shader compile failed. Retrying with shader model 3.0");
hRet = EmuCompileShader(hlsl_str, vs_model_3_0, ppHostShader, "CxbxVertexShaderTemplate.hlsl");
hRet = EmuCompileShader(hlsl_str, vs_model_3_0, ppHostShader, notionalSourceName);
}
return hRet;
@ -317,174 +312,10 @@ extern HRESULT EmuCompileVertexShader
extern void EmuCompileFixedFunction(ID3DBlob** ppHostShader)
{
static ID3DBlob* pShader = nullptr;
// TODO does this need to be thread safe?
if (pShader == nullptr) {
// Determine the filename and directory for the fixed function shader
auto hlslDir = std::filesystem::path(szFilePath_CxbxReloaded_Exe)
.parent_path()
.append("hlsl");
auto sourceFile = hlslDir.append("FixedFunctionVertexShader.hlsl").string();
// Load the shader into a string
std::ifstream hlslStream(sourceFile);
std::stringstream hlsl;
hlsl << hlslStream.rdbuf();
// Compile the shader
EmuCompileShader(hlsl.str(), g_vs_model, &pShader, sourceFile.c_str());
}
*ppHostShader = pShader;
EmuCompileShader(g_ShaderSources.fixedFunctionVertexShaderHlsl, g_vs_model, ppHostShader, g_ShaderSources.fixedFunctionVertexShaderPath.c_str());
};
static ID3DBlob* pPassthroughShader = nullptr;
extern HRESULT EmuCompileXboxPassthrough(ID3DBlob** ppHostShader)
extern void EmuCompileXboxPassthrough(ID3DBlob** ppHostShader)
{
// TODO does this need to be thread safe?
if (pPassthroughShader == nullptr) {
auto hlsl =
R"(
// Xbox HLSL pretransformed vertex shader
// Default values for vertex registers, and whether to use them
uniform float4 vRegisterDefaultValues[16] : register(c192);
uniform float4 vRegisterDefaultFlagsPacked[4] : register(c208);
uniform float4 xboxScreenspaceScale : register(c212);
uniform float4 xboxScreenspaceOffset : register(c213);
uniform float4 xboxTextureScale[4] : register(c214);
// Parameters for mapping the shader's fog output value to a fog factor
uniform float4 CxbxFogInfo: register(c218); // = CXBX_D3DVS_CONSTREG_FOGINFO
struct VS_INPUT
{
float4 v[16] : TEXCOORD;
};
// Output registers
struct VS_OUTPUT
{
float4 oPos : POSITION; // Homogeneous clip space position
float4 oD0 : COLOR0; // Primary color (front-facing)
float4 oD1 : COLOR1; // Secondary color (front-facing)
float oFog : FOG; // Fog coordinate
float oPts : PSIZE; // Point size
float4 oB0 : TEXCOORD4; // Back-facing primary color
float4 oB1 : TEXCOORD5; // Back-facing secondary color
float4 oT0 : TEXCOORD0; // Texture coordinate set 0
float4 oT1 : TEXCOORD1; // Texture coordinate set 1
float4 oT2 : TEXCOORD2; // Texture coordinate set 2
float4 oT3 : TEXCOORD3; // Texture coordinate set 3
};
float4 reverseScreenspaceTransform(float4 oPos)
{
// Scale screenspace coordinates (0 to viewport width/height) to -1 to +1 range
// On Xbox, oPos should contain the vertex position in screenspace
// We need to reverse this transformation
// Conventionally, each Xbox Vertex Shader includes instructions like this
// mul oPos.xyz, r12, c-38
// +rcc r1.x, r12.w
// mad oPos.xyz, r12, r1.x, c-37
// where c-37 and c-38 are reserved transform values
// Reverse screenspace offset
oPos -= xboxScreenspaceOffset;
// Reverse screenspace scale
oPos /= xboxScreenspaceScale;
// Ensure w is nonzero
if(oPos.w == 0) oPos.w = 1;
// Reverse perspective divide
oPos.xyz *= oPos.w;
return oPos;
}
VS_OUTPUT main(const VS_INPUT xIn)
{
// Input registers
float4 v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15;
// Unpack 16 flags from 4 float4 constant registers
float vRegisterDefaultFlags[16] = (float[16])vRegisterDefaultFlagsPacked;
// Initialize input registers from the vertex buffer data
// Or use the register's default value (which can be changed by the title)
#define init_v(i) v##i = lerp(xIn.v[i], vRegisterDefaultValues[i], vRegisterDefaultFlags[i]);
// Note : unroll manually instead of for-loop, because of the ## concatenation
init_v( 0); init_v( 1); init_v( 2); init_v( 3);
init_v( 4); init_v( 5); init_v( 6); init_v( 7);
init_v( 8); init_v( 9); init_v(10); init_v(11);
init_v(12); init_v(13); init_v(14); init_v(15);
// For passthrough, map output variables to their corresponding input registers
float4 oPos = v0;
float4 oD0 = v3;
float4 oD1 = v4;
float4 oFog = v5;
float4 oPts = v6;
float4 oB0 = v7;
float4 oB1 = v8;
float4 oT0 = v9;
float4 oT1 = v10;
float4 oT2 = v11;
float4 oT3 = v12;
// Copy variables to output struct
VS_OUTPUT xOut;
// Fogging
// TODO deduplicate
const float fogDepth = abs(oFog.x);
const float fogTableMode = CxbxFogInfo.x;
const float fogDensity = CxbxFogInfo.y;
const float fogStart = CxbxFogInfo.z;
const float fogEnd = CxbxFogInfo.w;
const float FOG_TABLE_NONE = 0;
const float FOG_TABLE_EXP = 1;
const float FOG_TABLE_EXP2 = 2;
const float FOG_TABLE_LINEAR = 3;
float fogFactor;
if(fogTableMode == FOG_TABLE_NONE)
fogFactor = fogDepth;
if(fogTableMode == FOG_TABLE_EXP)
fogFactor = 1 / exp(fogDepth * fogDensity); /* / 1 / e^(d * density)*/
if(fogTableMode == FOG_TABLE_EXP2)
fogFactor = 1 / exp(pow(fogDepth * fogDensity, 2)); /* / 1 / e^((d * density)^2)*/
if(fogTableMode == FOG_TABLE_LINEAR)
fogFactor = (fogEnd - fogDepth) / (fogEnd - fogStart);
xOut.oPos = reverseScreenspaceTransform(oPos);
xOut.oD0 = saturate(oD0);
xOut.oD1 = saturate(oD1);
xOut.oFog = fogFactor; // Note : Xbox clamps fog in pixel shader
xOut.oPts = oPts.x;
xOut.oB0 = saturate(oB0);
xOut.oB1 = saturate(oB1);
// Scale textures (TODO : or should we apply this to the input register values?)
xOut.oT0 = oT0 / xboxTextureScale[0];
xOut.oT1 = oT1 / xboxTextureScale[1];
xOut.oT2 = oT2 / xboxTextureScale[2];
xOut.oT3 = oT3 / xboxTextureScale[3];
return xOut;
}
)";
EmuCompileShader(hlsl, g_vs_model, &pPassthroughShader, "passthrough.hlsl");
}
*ppHostShader = pPassthroughShader;
return 0;
EmuCompileShader(g_ShaderSources.vertexShaderPassthroughHlsl, g_vs_model, ppHostShader, g_ShaderSources.vertexShaderPassthroughPath.c_str());
}

View file

@ -21,5 +21,5 @@ extern HRESULT EmuCompileVertexShader
extern void EmuCompileFixedFunction(ID3DBlob** ppHostShader);
extern HRESULT EmuCompileXboxPassthrough(ID3DBlob** ppHostShader);
extern void EmuCompileXboxPassthrough(ID3DBlob** ppHostShader);

View file

@ -1,15 +1,16 @@
#define LOG_PREFIX CXBXR_MODULE::VSHCACHE
#include "VertexShaderSource.h"
#include "VertexShaderCache.h"
#include "core/kernel/init/CxbxKrnl.h"
#include "util/hasher.h"
#include "core/kernel/support/Emu.h"
VertexShaderSource g_VertexShaderSource = VertexShaderSource();
VertexShaderCache g_VertexShaderCache = VertexShaderCache();
// FIXME : This should really be released and created in step with the D3D device lifecycle rather than being a thing on its own
// (And the ResetD3DDevice method should be removed)
ID3DBlob* AsyncCreateVertexShader(IntermediateVertexShader intermediateShader, ShaderKey key) {
ID3DBlob* pCompiledShader;
@ -25,7 +26,7 @@ ID3DBlob* AsyncCreateVertexShader(IntermediateVertexShader intermediateShader, S
// Find a shader
// Return true if the shader was found
bool VertexShaderSource::_FindShader(ShaderKey key, LazyVertexShader** ppLazyShader) {
bool VertexShaderCache::_FindShader(ShaderKey key, LazyVertexShader** ppLazyShader) {
auto it = cache.find(key);
if (it == cache.end()) {
// We didn't find anything! Was CreateShader called?
@ -39,7 +40,7 @@ ID3DBlob* AsyncCreateVertexShader(IntermediateVertexShader intermediateShader, S
// Create a new shader
// If the shader was already created, just increase its reference count
ShaderKey VertexShaderSource::CreateShader(const xbox::dword_xt* pXboxFunction, DWORD *pXboxFunctionSize) {
ShaderKey VertexShaderCache::CreateShader(const xbox::dword_xt* pXboxFunction, DWORD *pXboxFunctionSize) {
IntermediateVertexShader intermediateShader;
*pXboxFunctionSize = GetVshFunctionSize(pXboxFunction);
@ -86,7 +87,7 @@ ShaderKey VertexShaderSource::CreateShader(const xbox::dword_xt* pXboxFunction,
}
// Get a shader using the given key
IDirect3DVertexShader* VertexShaderSource::GetShader(ShaderKey key)
IDirect3DVertexShader* VertexShaderCache::GetShader(ShaderKey key)
{
LazyVertexShader* pLazyShader = nullptr;
@ -113,6 +114,12 @@ IDirect3DVertexShader* VertexShaderSource::GetShader(ShaderKey key)
EmuLog(LOG_LEVEL::DEBUG, "Waiting for shader %llx...", key);
pCompiledShader = pLazyShader->compileResult.get();
if (!pCompiledShader) {
EmuLog(LOG_LEVEL::ERROR2, "Failed to compile vertex shader for %llx", key);
pLazyShader->isReady = true;
return nullptr;
}
// Create the shader
auto hRet = pD3DDevice->CreateVertexShader
(
@ -145,7 +152,7 @@ IDirect3DVertexShader* VertexShaderSource::GetShader(ShaderKey key)
}
// Release a shader. Doesn't actually release any resources for now
void VertexShaderSource::ReleaseShader(ShaderKey key)
void VertexShaderCache::ReleaseShader(ShaderKey key)
{
// For now, don't bother releasing any shaders
LazyVertexShader* pLazyShader;
@ -165,8 +172,25 @@ void VertexShaderSource::ReleaseShader(ShaderKey key)
}
}
void VertexShaderSource::ResetD3DDevice(IDirect3DDevice9* newDevice)
void VertexShaderCache::ResetD3DDevice(IDirect3DDevice9* newDevice)
{
EmuLog(LOG_LEVEL::DEBUG, "Resetting D3D device");
cache.clear();
this->pD3DDevice = newDevice;
}
void VertexShaderCache::Clear()
{
for (auto& x : cache) {
if (!x.second.isReady) {
auto pBlob = x.second.compileResult.get();
if (pBlob) {
pBlob->Release();
}
}
else if(x.second.pHostVertexShader) {
x.second.pHostVertexShader->Release();
}
}
cache.clear();
}

View file

@ -8,7 +8,7 @@
typedef uint64_t ShaderKey;
// Manages creation and caching of vertex shaders
class VertexShaderSource {
class VertexShaderCache {
public:
ShaderKey CreateShader(const xbox::dword_xt* pXboxFunction, DWORD* pXboxFunctionSize);
@ -16,6 +16,7 @@ public:
void ReleaseShader(ShaderKey key);
void ResetD3DDevice(IDirect3DDevice9* pD3DDevice);
void Clear();
// TODO
// WriteCacheToDisk
@ -42,6 +43,6 @@ private:
bool _FindShader(ShaderKey key, LazyVertexShader** ppLazyShader);
};
extern VertexShaderSource g_VertexShaderSource;
extern VertexShaderCache g_VertexShaderCache;
#endif

View file

@ -39,6 +39,7 @@
#include "core\kernel\support\Emu.h"
#include "core\hle\D3D8\Direct3D9\Direct3D9.h" // For g_pD3DDevice, g_pXbox_PixelShader
#include "core\hle\D3D8\Direct3D9\Shader.h" // For g_ShaderSources
#include "core\hle\D3D8\XbPixelShader.h"
#include "core\hle\D3D8\Direct3D9\PixelShader.h" // EmuCompilePixelShader
#include "core\hle\D3D8\XbD3D8Logging.h" // For D3DErrorString()
@ -51,6 +52,7 @@
#include <assert.h> // assert()
#include <process.h>
#include <locale.h>
#include <filesystem>
#include <fstream>
#include <sstream>
@ -659,35 +661,12 @@ constexpr int PSH_XBOX_CONSTANT_BEM = PSH_XBOX_CONSTANT_FOG + 1; // = 19..22
constexpr int PSH_XBOX_CONSTANT_LUM = PSH_XBOX_CONSTANT_BEM + 4; // = 23..26
// Which winding order to consider as the front face
constexpr int PSH_XBOX_CONSTANT_FRONTFACE_FACTOR = PSH_XBOX_CONSTANT_LUM + 4; // = 27
// Fog table information {Table mode, density, start and end}
constexpr int CXBX_D3DPS_CONSTREG_FOGINFO = PSH_XBOX_CONSTANT_FRONTFACE_FACTOR + 1; // = 28
//Fog enable flag
constexpr int PSH_XBOX_CONSTANT_FOGENABLE = CXBX_D3DPS_CONSTREG_FOGINFO + 1; // = 29
// This concludes the set of constants that need to be set on host :
constexpr int PSH_XBOX_CONSTANT_MAX = PSH_XBOX_CONSTANT_FRONTFACE_FACTOR + 1; // = 28
std::string GetFixedFunctionShaderTemplate() {
static bool loaded = false;
static std::string hlslString;
// TODO does this need to be thread safe?
if (!loaded) {
loaded = true;
// Determine the filename and directory for the fixed function shader
// TODO make this a relative path so we guarantee an LPCSTR for D3DCompile
auto hlslDir = std::filesystem::path(szFilePath_CxbxReloaded_Exe)
.parent_path()
.append("hlsl");
auto sourceFile = hlslDir.append("FixedFunctionPixelShader.hlsl").string();
// Load the shader into a string
std::ifstream hlslStream(sourceFile);
std::stringstream hlsl;
hlsl << hlslStream.rdbuf();
hlslString = hlsl.str();
}
return hlslString;
}
constexpr int PSH_XBOX_CONSTANT_MAX = PSH_XBOX_CONSTANT_FOGENABLE + 1; // = 30
std::string_view GetD3DTOPString(int d3dtop) {
static constexpr std::string_view opToString[] = {
@ -789,6 +768,21 @@ IDirect3DPixelShader9* GetFixedFunctionShader()
// TODO move this cache elsewhere - and flush it when the device is released!
static std::unordered_map<uint64_t, IDirect3DPixelShader9*> ffPsCache = {};
// Support hotloading hlsl
static int pixelShaderVersion = -1;
int shaderVersion = g_ShaderSources.Update();
if (pixelShaderVersion != shaderVersion) {
pixelShaderVersion = shaderVersion;
g_pD3DDevice->SetPixelShader(nullptr);
for (auto& hostShader : ffPsCache) {
if (hostShader.second)
hostShader.second->Release();
}
ffPsCache.clear();
}
// Create a key from state that will be baked in to the shader
PsTextureHardcodedState states[4] = {};
int sampleType[4] = { SAMPLE_NONE, SAMPLE_NONE, SAMPLE_NONE, SAMPLE_NONE };
@ -871,68 +865,74 @@ IDirect3DPixelShader9* GetFixedFunctionShader()
}
// Build and compile a new shader
auto hlslTemplate = GetFixedFunctionShaderTemplate();
std::string hlslTemplate = g_ShaderSources.fixedFunctionPixelShaderHlsl;
// In D3D9 it seems we need to know hardcode if we're doing a 2D or 3D lookup
const std::string sampleTypePattern = "TEXTURE_SAMPLE_TYPE;";
auto sampleTypeReplace = hlslTemplate.find(sampleTypePattern);
std::string finalShader = hlslTemplate;
static constexpr std::string_view typeToString[] = {
"SAMPLE_NONE",
"SAMPLE_2D",
"SAMPLE_3D",
"SAMPLE_CUBE"
};
if (sampleTypeReplace != std::string::npos) {
static constexpr std::string_view typeToString[] = {
"SAMPLE_NONE",
"SAMPLE_2D",
"SAMPLE_3D",
"SAMPLE_CUBE"
};
std::stringstream sampleTypeString;
sampleTypeString << "{"
<< typeToString[sampleType[0]] << ", "
<< typeToString[sampleType[1]] << ", "
<< typeToString[sampleType[2]] << ", "
<< typeToString[sampleType[3]] << "};";
std::stringstream sampleTypeString;
sampleTypeString << "{"
<< typeToString[sampleType[0]] << ", "
<< typeToString[sampleType[1]] << ", "
<< typeToString[sampleType[2]] << ", "
<< typeToString[sampleType[3]] << "};";
auto finalShader = hlslTemplate.replace(sampleTypeReplace, sampleTypePattern.size(), sampleTypeString.str());
finalShader = hlslTemplate.replace(sampleTypeReplace, sampleTypePattern.size(), sampleTypeString.str());
}
// Hardcode the texture stage operations and arguments
// So the shader handles exactly one combination of values
const std::string stageDef = "// STAGE DEFINITIONS";
auto stageDefInsert = finalShader.find(stageDef) + stageDef.size();
auto stageDefInsert = finalShader.find(stageDef);
if (stageDefInsert != std::string::npos) {
stageDefInsert += stageDef.size();
std::stringstream stageSetup;
stageSetup << '\n';
std::stringstream stageSetup;
stageSetup << '\n';
for (int i = 0; i < 4; i++) {
for (int i = 0; i < 4; i++) {
#ifdef ENABLE_FF_ALPHAKILL
// Even when a stage is disabled, we still have to fully initialize it's values, to prevent
// "error X4000: variable 'stages' used without having been completely initialized"
// Even when a stage is disabled, we still have to fully initialize it's values, to prevent
// "error X4000: variable 'stages' used without having been completely initialized"
#else
// The stage is initialized to be disabled
// We don't have to output anything
if (states[i].COLOROP == X_D3DTOP_DISABLE)
continue;
// The stage is initialized to be disabled
// We don't have to output anything
if (states[i].COLOROP == X_D3DTOP_DISABLE)
continue;
#endif
std::string target = "stages[" + std::to_string(i) + "].";
std::string target = "stages[" + std::to_string(i) + "].";
auto s = states[i];
stageSetup << target << "COLOROP = " << GetD3DTOPString(s.COLOROP) << ";\n";
auto s = states[i];
stageSetup << target << "COLOROP = " << GetD3DTOPString(s.COLOROP) << ";\n";
stageSetup << target << "COLORARG0 = " << GetD3DTASumString(s.COLORARG0) << ";\n";
stageSetup << target << "COLORARG1 = " << GetD3DTASumString(s.COLORARG1) << ";\n";
stageSetup << target << "COLORARG2 = " << GetD3DTASumString(s.COLORARG2) << ";\n";
stageSetup << target << "COLORARG0 = " << GetD3DTASumString(s.COLORARG0) << ";\n";
stageSetup << target << "COLORARG1 = " << GetD3DTASumString(s.COLORARG1) << ";\n";
stageSetup << target << "COLORARG2 = " << GetD3DTASumString(s.COLORARG2) << ";\n";
stageSetup << target << "ALPHAOP = " << GetD3DTOPString(s.ALPHAOP) << ";\n";
stageSetup << target << "ALPHAOP = " << GetD3DTOPString(s.ALPHAOP) << ";\n";
stageSetup << target << "ALPHAARG0 = " << GetD3DTASumString(s.ALPHAARG0) << ";\n";
stageSetup << target << "ALPHAARG1 = " << GetD3DTASumString(s.ALPHAARG1) << ";\n";
stageSetup << target << "ALPHAARG2 = " << GetD3DTASumString(s.ALPHAARG2) << ";\n";
stageSetup << target << "ALPHAARG0 = " << GetD3DTASumString(s.ALPHAARG0) << ";\n";
stageSetup << target << "ALPHAARG1 = " << GetD3DTASumString(s.ALPHAARG1) << ";\n";
stageSetup << target << "ALPHAARG2 = " << GetD3DTASumString(s.ALPHAARG2) << ";\n";
stageSetup << target << "RESULTARG = " << GetD3DTASumString(s.RESULTARG, false) << ";\n";
stageSetup << '\n';
stageSetup << target << "RESULTARG = " << GetD3DTASumString(s.RESULTARG, false) << ";\n";
stageSetup << '\n';
}
finalShader = finalShader.insert(stageDefInsert, stageSetup.str());
}
finalShader = finalShader.insert(stageDefInsert, stageSetup.str());
// Compile the shader
ID3DBlob* pShaderBlob;
@ -944,12 +944,15 @@ IDirect3DPixelShader9* GetFixedFunctionShader()
auto pseudoSourceFile = hlslDir.append(pseudoFileName).string();
EmuCompileShader(finalShader, "ps_3_0", &pShaderBlob, pseudoSourceFile.c_str());
// Create shader object for the device
IDirect3DPixelShader9* pShader = nullptr;
auto hRet = g_pD3DDevice->CreatePixelShader((DWORD*)pShaderBlob->GetBufferPointer(), &pShader);
if (hRet != S_OK)
CxbxrAbort("Failed to compile fixed function pixel shader");
pShaderBlob->Release();
if (pShaderBlob) {
// Create shader object for the device
auto hRet = g_pD3DDevice->CreatePixelShader((DWORD*)pShaderBlob->GetBufferPointer(), &pShader);
if (hRet != S_OK) {
EmuLog(LOG_LEVEL::ERROR2, "Failed to compile fixed function pixel shader");
}
pShaderBlob->Release();
}
// Insert the shader into the cache
ffPsCache[key] = pShader;
@ -972,7 +975,10 @@ void UpdateFixedFunctionPixelShaderState()
ffPsState.SpecularEnable = XboxRenderStates.GetXboxRenderState(xbox::X_D3DRS_SPECULARENABLE);
ffPsState.FogEnable = XboxRenderStates.GetXboxRenderState(xbox::X_D3DRS_FOGENABLE);
ffPsState.FogColor = (D3DXVECTOR3)((D3DXCOLOR)XboxRenderStates.GetXboxRenderState(xbox::X_D3DRS_FOGCOLOR));
ffPsState.FogTableMode = XboxRenderStates.GetXboxRenderState(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGTABLEMODE);
ffPsState.FogDensity = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGDENSITY);
ffPsState.FogStart = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGSTART);
ffPsState.FogEnd = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGEND);
// Texture state
for (int i = 0; i < xbox::X_D3DTS_STAGECOUNT; i++) {
@ -1029,6 +1035,21 @@ void DxbxUpdateActivePixelShader() // NOPATCH
// Fetch all other values that are used in the IsEquivalent check :
CompletePSDef.SnapshotRuntimeVariables();
// Support hotloading hlsl
static int pixelShaderVersion = -1;
int shaderVersion = g_ShaderSources.Update();
if (pixelShaderVersion != shaderVersion) {
pixelShaderVersion = shaderVersion;
g_pD3DDevice->SetPixelShader(nullptr);
for (auto& hostShader : g_RecompiledPixelShaders) {
if (hostShader.ConvertedPixelShader)
hostShader.ConvertedPixelShader->Release();
}
g_RecompiledPixelShaders.clear();
}
// Now, see if we already have a shader compiled for this definition :
// TODO : Change g_RecompiledPixelShaders into an unordered_map, hash just the identifying PSDef members, and add cache eviction (clearing host resources when pruning)
const PSH_RECOMPILED_SHADER* RecompiledPixelShader = nullptr;
@ -1150,7 +1171,18 @@ void DxbxUpdateActivePixelShader() // NOPATCH
frontfaceFactor = cwFrontface ? 1.0 : -1.0;
}
fColor[PSH_XBOX_CONSTANT_FRONTFACE_FACTOR].r = frontfaceFactor;
float fogEnable = XboxRenderStates.GetXboxRenderState(xbox::X_D3DRS_FOGENABLE) > 0;
const float fogTableMode = XboxRenderStates.GetXboxRenderState(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGTABLEMODE);
const float fogDensity = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGDENSITY);
const float fogStart = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGSTART);
const float fogEnd = XboxRenderStates.GetXboxRenderStateAsFloat(xbox::_X_D3DRENDERSTATETYPE::X_D3DRS_FOGEND);
fColor[CXBX_D3DPS_CONSTREG_FOGINFO].r = fogTableMode;
fColor[CXBX_D3DPS_CONSTREG_FOGINFO].g = fogDensity;
fColor[CXBX_D3DPS_CONSTREG_FOGINFO].b = fogStart;
fColor[CXBX_D3DPS_CONSTREG_FOGINFO].a = fogEnd;
fColor[PSH_XBOX_CONSTANT_FOGENABLE].r = fogEnable;
// Assume all constants are in use (this is much easier than tracking them for no other purpose than to skip a few here)
// Read the color from the corresponding render state slot :
// Set all host constant values using a single call:

View file

@ -469,12 +469,15 @@ extern void EmuExecutePushBufferRaw
LOG_TEST_CASE("Pushbuffer COMMAND_TYPE_JUMP_LONG");
dma_get_jmp_shadow = dma_get;
dma_get = (uint32_t *)(CONTIGUOUS_MEMORY_BASE | (word & COMMAND_WORD_MASK_JUMP_LONG));
//NV2A uses COMMAND_TYPE_JUMP_LONG as return for COMMAND_TYPE_CALL. we clear the subr_active here to indicate we have returned from COMMAND_TYPE_CALL.
subr_active = false;
continue; // while
case COMMAND_TYPE_CALL: // Note : NV2A return is said not to work?
if (subr_active) {
LOG_TEST_CASE("Pushbuffer COMMAND_TYPE_CALL while another call was active!");
// TODO : throw DMA_PUSHER(CALL_SUBR_ACTIVE);
return; // For now, don't even attempt to run through
// For now, don't even attempt to run through, this should never happened, if it happened, the pgraph handler will go crazy.
CxbxrAbort("Pushbuffer COMMAND_TYPE_CALL called without return!");
}
else {
LOG_TEST_CASE("Pushbuffer COMMAND_TYPE_CALL");

View file

@ -34,7 +34,8 @@
#include "core\kernel\support\Emu.h"
#include "core\hle\D3D8\Direct3D9\Direct3D9.h" // For g_Xbox_VertexShader_Handle
#include "core\hle\D3D8\Direct3D9\RenderStates.h" // For XboxRenderStateConverter
#include "core\hle\D3D8\Direct3D9\VertexShaderSource.h" // For g_VertexShaderSource
#include "core\hle\D3D8\Direct3D9\VertexShaderCache.h" // For g_VertexShaderCache
#include "core\hle\D3D8\Direct3D9\Shader.h" // For g_ShaderSources
#include "core\hle\D3D8\XbVertexBuffer.h" // For CxbxImpl_SetVertexData4f
#include "core\hle\D3D8\XbVertexShader.h"
#include "core\hle\D3D8\XbD3D8Logging.h" // For DEBUG_D3DRESULT
@ -49,6 +50,7 @@
#include <unordered_map>
#include <array>
#include <bitset>
#include <filesystem>
// External symbols :
extern xbox::X_STREAMINPUT g_Xbox_SetStreamSource[X_VSH_MAX_STREAMS]; // Declared in XbVertexBuffer.cpp
@ -1124,10 +1126,49 @@ IDirect3DVertexDeclaration* CxbxCreateHostVertexDeclaration(D3DVERTEXELEMENT *pD
return pHostVertexDeclaration;
}
static IDirect3DVertexShader* passthroughshader;
IDirect3DVertexShader* InitShader(void (*compileFunc)(ID3DBlob**), const char* label) {
IDirect3DVertexShader* shader = nullptr;
ID3DBlob* pBlob = nullptr;
compileFunc(&pBlob);
if (pBlob) {
HRESULT hRet = g_pD3DDevice->CreateVertexShader((DWORD*)pBlob->GetBufferPointer(), &shader);
pBlob->Release();
if (FAILED(hRet)) CxbxrAbort("Failed to create shader: %s", label);
}
return shader;
}
void CxbxUpdateHostVertexShader()
{
extern bool g_bUsePassthroughHLSL; // TMP glue
// TODO: move d3d9 state to VertexShader.cpp
static IDirect3DVertexShader* fixedFunctionShader = nullptr; // TODO: move to shader cache
static IDirect3DVertexShader* passthroughShader = nullptr;
static int vertexShaderVersion = -1;
int shaderVersion = g_ShaderSources.Update();
if (vertexShaderVersion != shaderVersion) {
vertexShaderVersion = shaderVersion;
g_pD3DDevice->SetVertexShader(nullptr);
EmuLog(LOG_LEVEL::INFO, "Loading vertex shaders...");
g_VertexShaderCache.Clear();
if (fixedFunctionShader) {
fixedFunctionShader->Release();
fixedFunctionShader = nullptr;
}
fixedFunctionShader = InitShader(EmuCompileFixedFunction, "Fixed Function Vertex Shader");
if (passthroughShader) {
passthroughShader->Release();
passthroughShader = nullptr;
}
passthroughShader = InitShader(EmuCompileXboxPassthrough, "Passthrough Vertex Shader");
}
// TODO Call this when state is dirty
// Rather than every time state changes
@ -1135,43 +1176,20 @@ void CxbxUpdateHostVertexShader()
LOG_INIT; // Allows use of DEBUG_D3DRESULT
if (g_Xbox_VertexShaderMode == VertexShaderMode::FixedFunction) {
IDirect3DVertexShader* fixedFunctionShader = nullptr;
HRESULT hRet;
if (g_UseFixedFunctionVertexShader) {
static IDirect3DVertexShader* ffHlsl = nullptr;
if (ffHlsl == nullptr) {
ID3DBlob* pBlob = nullptr;
EmuCompileFixedFunction(&pBlob);
if (pBlob) {
hRet = g_pD3DDevice->CreateVertexShader((DWORD*)pBlob->GetBufferPointer(), &ffHlsl);
if (FAILED(hRet)) CxbxrAbort("Failed to create fixed-function shader");
}
}
fixedFunctionShader = ffHlsl;
}
hRet = g_pD3DDevice->SetVertexShader(fixedFunctionShader);
HRESULT hRet = g_pD3DDevice->SetVertexShader(fixedFunctionShader);
if (FAILED(hRet)) CxbxrAbort("Failed to set fixed-function shader");
}
else if (g_Xbox_VertexShaderMode == VertexShaderMode::Passthrough && g_bUsePassthroughHLSL) {
if (passthroughshader == nullptr) {
ID3DBlob* pBlob = nullptr;
EmuCompileXboxPassthrough(&pBlob);
if (pBlob) {
g_pD3DDevice->CreateVertexShader((DWORD*)pBlob->GetBufferPointer(), &passthroughshader);
}
}
HRESULT hRet = g_pD3DDevice->SetVertexShader(passthroughshader);
HRESULT hRet = g_pD3DDevice->SetVertexShader(passthroughShader);
if (FAILED(hRet)) CxbxrAbort("Failed to set passthrough shader");
}
else {
auto pTokens = GetCxbxVertexShaderSlotPtr(g_Xbox_VertexShader_FunctionSlots_StartAddress);
assert(pTokens);
// Create a vertex shader from the tokens
DWORD shaderSize;
auto VertexShaderKey = g_VertexShaderSource.CreateShader(pTokens, &shaderSize);
IDirect3DVertexShader* pHostVertexShader = g_VertexShaderSource.GetShader(VertexShaderKey);
auto VertexShaderKey = g_VertexShaderCache.CreateShader(pTokens, &shaderSize);
IDirect3DVertexShader* pHostVertexShader = g_VertexShaderCache.GetShader(VertexShaderKey);
HRESULT hRet = g_pD3DDevice->SetVertexShader(pHostVertexShader);
DEBUG_D3DRESULT(hRet, "g_pD3DDevice->SetVertexShader");
}
@ -1559,7 +1577,7 @@ void CxbxImpl_DeleteVertexShader(DWORD Handle)
RegisterCxbxVertexDeclaration(pCxbxVertexDeclaration->Key, nullptr); // Remove from cache (which will free present pCxbxVertexDeclaration)
// Release the host vertex shader
g_VertexShaderSource.ReleaseShader(pCxbxVertexShader->Key);
g_VertexShaderCache.ReleaseShader(pCxbxVertexShader->Key);
#endif
}

View file

@ -53,6 +53,7 @@
// TODO: Move these to LLE APUDevice once we have one!
static constexpr uint32_t APU_TIMER_FREQUENCY = 48000;
static uint64_t dsound_last;
uint32_t GetAPUTime()
{
@ -85,10 +86,6 @@ uint32_t GetAPUTime()
there is chance of failure which contain value greater than 0.
*/
// Managed memory xbox audio variables
static std::thread dsound_thread;
static void dsound_thread_worker(LPVOID);
#include "DirectSoundInline.hpp"
#ifdef __cplusplus
@ -98,6 +95,7 @@ extern "C" {
void CxbxInitAudio()
{
g_EmuShared->GetAudioSettings(&g_XBAudio);
dsound_last = get_now();
}
#ifdef __cplusplus
@ -125,10 +123,6 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(DirectSoundCreate)
HRESULT hRet = DS_OK;
if (!initialized) {
dsound_thread = std::thread(dsound_thread_worker, nullptr);
}
// Set this flag when this function is called
g_bDSoundCreateCalled = TRUE;
@ -454,51 +448,51 @@ void StreamBufferAudio(xbox::XbHybridDSBuffer* pHybridBuffer, float msToCopy) {
}
}
static void dsound_thread_worker(LPVOID nullPtr)
void dsound_async_worker()
{
g_AffinityPolicy->SetAffinityOther();
DSoundMutexGuardLock;
const int dsStreamInterval = 300;
int waitCounter = 0;
xbox::LARGE_INTEGER getTime;
xbox::KeQuerySystemTime(&getTime);
DirectSoundDoWork_Stream(getTime);
}
while (true) {
// FIXME time this loop more accurately
// and account for variation in the length of Sleep calls
void dsound_worker()
{
// Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often
// unless console is open with logging enabled. This is the cause of stopping intro videos often.
// Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often
// unless console is open with logging enabled. This is the cause of stopping intro videos often.
Sleep(g_dsBufferStreaming.streamInterval);
waitCounter += g_dsBufferStreaming.streamInterval;
// Enforce mutex guard lock only occur inside below bracket for proper compile build.
DSoundMutexGuardLock;
// Enforce mutex guard lock only occur inside below bracket for proper compile build.
{
DSoundMutexGuardLock;
if (waitCounter > dsStreamInterval) {
waitCounter = 0;
// For Async process purpose only
xbox::LARGE_INTEGER getTime;
xbox::KeQuerySystemTime(&getTime);
DirectSoundDoWork_Stream(getTime);
// Stream sound buffer audio
// because the title may change the content of sound buffers at any time
for (auto& pBuffer : g_pDSoundBufferCache) {
// Avoid expensive calls to DirectSound on buffers unless they've been played at least once
// Since some titles create a large amount of buffers, but only use a few
if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) {
DWORD status;
HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status);
if (hRet == 0 && status & DSBSTATUS_PLAYING) {
auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead;
StreamBufferAudio(pBuffer, streamMs);
}
}
}
}
// Stream sound buffer audio
// because the title may change the content of sound buffers at any time
for (auto& pBuffer : g_pDSoundBufferCache) {
// Avoid expensive calls to DirectSound on buffers unless they've been played at least once
// Since some titles create a large amount of buffers, but only use a few
if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) {
DWORD status;
HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status);
if (hRet == 0 && status & DSBSTATUS_PLAYING) {
auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead;
StreamBufferAudio(pBuffer, streamMs);
}
}
}
}
uint64_t dsound_next(uint64_t now)
{
constexpr uint64_t dsound_period = 300 * 1000;
uint64_t next = dsound_last + dsound_period;
if (now >= next) {
dsound_async_worker();
dsound_last = get_now();
return dsound_period;
}
return dsound_last + dsound_period - now; // time remaining until next dsound async event
}
// Kismet given name for RadWolfie's experiment major issue in the mutt.

View file

@ -81,3 +81,6 @@ extern DsBufferStreaming g_dsBufferStreaming;
extern void DirectSoundDoWork_Buffer(xbox::LARGE_INTEGER& time);
extern void DirectSoundDoWork_Stream(xbox::LARGE_INTEGER& time);
extern void dsound_async_worker();
extern void dsound_worker();
extern uint64_t dsound_next(uint64_t now);

View file

@ -34,6 +34,7 @@
#include <cmath>
#include <iomanip> // For std::setfill and std::setw
#include <filesystem>
#include "core\kernel\init\CxbxKrnl.h"
#include "core\kernel\support\Emu.h"
@ -205,6 +206,7 @@ void CDECL EmuOutputMessage(xb_output_message mFlag,
void CDECL EmuRegisterSymbol(const char* library_str,
uint32_t library_flag,
uint32_t xref_index,
const char* symbol_str,
uint32_t func_addr,
uint32_t revision)
@ -438,22 +440,6 @@ void EmuHLEIntercept(Xbe::Header *pXbeHeader)
<< " -> " << functionName << "\n";
std::printf(output.str().c_str());
}
// Fix up Render state and Texture States
if (g_SymbolAddresses.find("D3DDeferredRenderState") == g_SymbolAddresses.end()
|| g_SymbolAddresses["D3DDeferredRenderState"] == 0) {
EmuLog(LOG_LEVEL::WARNING, "EmuD3DDeferredRenderState was not found!");
}
if (g_SymbolAddresses.find("D3DDeferredTextureState") == g_SymbolAddresses.end()
|| g_SymbolAddresses["D3DDeferredTextureState"] == 0) {
EmuLog(LOG_LEVEL::WARNING, "EmuD3DDeferredTextureState was not found!");
}
if (g_SymbolAddresses.find("D3DDEVICE") == g_SymbolAddresses.end()
|| g_SymbolAddresses["D3DDEVICE"] == 0) {
EmuLog(LOG_LEVEL::WARNING, "D3DDEVICE was not found!");
}
}
}

670
src/core/hle/JVS/JVS.cpp Normal file
View file

@ -0,0 +1,670 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// ******************************************************************
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them under the terms of the GNU General Public
// * License as published by the Free Software Foundation; either
// * version 2 of the license, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2019 Luke Usher <luke.usher@outlook.com>
// *
// * All rights reserved
// *
// ******************************************************************
#define _XBOXKRNL_DEFEXTRN_
#define LOG_PREFIX CXBXR_MODULE::JVS
#undef FIELD_OFFSET // prevent macro redefinition warnings
#include "EmuShared.h"
#include "common\Logging.h"
#include "common\FilePaths.hpp"
#include "core\kernel\init\CxbxKrnl.h"
#include "core\kernel\support\Emu.h"
#include "core\hle\JVS\JVS.h"
#include "core\hle\Intercept.hpp"
#include "devices\chihiro\JvsIo.h"
#include "devices\Xbox.h"
#include <thread>
#pragma warning(disable:4244) // Silence mio compiler warnings
#include <mio/mmap.hpp>
#pragma warning(default:4244)
// Global variables used to store JVS related firmware/eeproms
mio::mmap_sink g_BaseBoardQcFirmware; // QC Microcontroller firmware
mio::mmap_sink g_BaseBoardScFirmware; // SC Microcontroller firmware
mio::mmap_sink g_BaseBoardEeprom; // Config EEPROM
mio::mmap_sink g_BaseBoardBackupMemory; // Backup Memory (high-scores, etc)
typedef struct _baseboard_state_t {
// Switch 1: Horizontal Display, On = Vertical Display
// Switch 2-3: D3D Resolution Configuraton
// Switch 4: 0 = Hardware Vertex Processing, 1 = Software Vertex processing (Causes D3D to fail).. Horizontal frequency?
// Switch 5: Unknown
// Switch 6-8: Connected AV Pack flag
bool DipSwitch[8];
bool TestButton;
bool ServiceButton;
uint8_t JvsSense;
void Reset()
{
// TODO: Make this configurable
DipSwitch[0] = false;
DipSwitch[1] = false;
DipSwitch[2] = true;
DipSwitch[3] = true;
DipSwitch[4] = false;
DipSwitch[5] = true;
DipSwitch[6] = true;
DipSwitch[7] = true;
TestButton = false;
ServiceButton = false;
JvsSense = 0;
}
uint8_t GetAvPack()
{
uint8_t avpack = 0;
// Dip Switches 6,7,8 combine to form the Av Pack ID
// TODO: Verify the order, these might need to be reversed
avpack &= ~((DipSwitch[5] ? 1 : 0) << 2);
avpack &= ~((DipSwitch[6] ? 1 : 0) << 1);
avpack &= ~ (DipSwitch[7] ? 1 : 0);
return avpack;
}
uint8_t GetPINSA()
{
uint8_t PINSA = 0b11101111; // 1 = Off, 0 = On
// Dip Switches 1-3 are set on PINSA bits 0-2
PINSA &= ~ (DipSwitch[0] ? 1 : 0);
PINSA &= ~((DipSwitch[1] ? 1 : 0) << 1);
PINSA &= ~((DipSwitch[2] ? 1 : 0) << 2);
// Bit 3 is currently unknown, so we don't modify that bit
// Dip Switches 4,5 are set on bits 4,5
PINSA &= ~((DipSwitch[3] ? 1 : 0) << 4);
PINSA &= ~((DipSwitch[4] ? 1 : 0) << 5);
// Bit 6 = Test, Bit 7 = Service
PINSA &= ~((TestButton ? 1 : 0) << 6);
PINSA &= ~((ServiceButton ? 1 : 0) << 7);
return PINSA;
}
uint8_t GetPINSB()
{
// PINSB bits 0-1 represent the JVS Sense line
return JvsSense;
}
} baseboard_state_t;
baseboard_state_t ChihiroBaseBoardState = {};
DWORD* g_pPINSA = nullptr; // Qc PINSA Register: Contains Filter Board DIP Switches + Test/Service buttons
DWORD* g_pPINSB = nullptr; // Qc PINSB Register: Contains JVS Sense Pin state
bool JVS_LoadFile(std::string path, mio::mmap_sink& data)
{
FILE* fp = fopen(path.c_str(), "rb");
if (fp == nullptr) {
return false;
}
std::error_code error;
data = mio::make_mmap_sink(path, error);
if (error) {
return false;
}
return true;
}
void JvsInputThread()
{
g_AffinityPolicy->SetAffinityOther(GetCurrentThread());
while (true) {
// This thread is responsible for reading the emulated Baseboard state
// and setting the correct internal variables
ChihiroBaseBoardState.TestButton = GetAsyncKeyState(VK_F1);
ChihiroBaseBoardState.ServiceButton = GetAsyncKeyState(VK_F2);
// Call into the Jvs I/O board update function
g_pJvsIo->Update();
if (g_pPINSA != nullptr) {
*g_pPINSA = ChihiroBaseBoardState.GetPINSA();
}
if (g_pPINSB != nullptr) {
*g_pPINSB = ChihiroBaseBoardState.GetPINSB();
}
}
}
void JVS_Init()
{
// Init Jvs IO board
g_pJvsIo = new JvsIo(&ChihiroBaseBoardState.JvsSense);
std::string romPath = g_DataFilePath + std::string("\\EmuDisk\\Chihiro");
std::string baseBoardQcFirmwarePath = "ic10_g24lc64.bin";
std::string baseBoardScFirmwarePath = "pc20_g24lc64.bin";
std::string baseBoardEepromPath = "ic11_24lc024.bin";
std::string baseBoardBackupRamPath = "backup_ram.bin";
if (!JVS_LoadFile((romPath + "\\" + baseBoardQcFirmwarePath).c_str(), g_BaseBoardQcFirmware)) {
CxbxrAbort("Failed to load base board firmware: %s", baseBoardQcFirmwarePath.c_str());
}
if (!JVS_LoadFile((romPath + "\\" + baseBoardScFirmwarePath).c_str(), g_BaseBoardScFirmware)) {
CxbxrAbort("Failed to load base board qc firmware: %s", baseBoardScFirmwarePath.c_str());
}
if (!JVS_LoadFile((romPath + "\\" + baseBoardEepromPath).c_str(), g_BaseBoardEeprom)) {
CxbxrAbort("Failed to load base board EEPROM: %s", baseBoardEepromPath.c_str());
}
// backup ram is a special case, we can create it automatically if it doesn't exist
if (!std::filesystem::exists(romPath + "\\" + baseBoardBackupRamPath)) {
FILE *fp = fopen((romPath + "\\" + baseBoardBackupRamPath).c_str(), "w");
if (fp == nullptr) {
CxbxrAbort("Could not create Backup File: %s", baseBoardBackupRamPath.c_str());
}
// Create 128kb empty file for backup ram
fseek(fp, (128 * 1024) - 1, SEEK_SET);
fputc('\0', fp);
fclose(fp);
}
if (!JVS_LoadFile((romPath + "\\" + baseBoardBackupRamPath).c_str(), g_BaseBoardBackupMemory)) {
CxbxrAbort("Failed to load base board BACKUP RAM: %s", baseBoardBackupRamPath.c_str());
}
// Determine which version of JVS_SendCommand this title is using and derive the offset
// TODO: Extract this into a function and also locate PINSB
static int JvsSendCommandVersion = -1;
g_pPINSA = nullptr;
g_pPINSB = nullptr;
auto JvsSendCommandOffset1 = (uintptr_t)GetXboxSymbolPointer("JVS_SendCommand");
auto JvsSendCommandOffset2 = (uintptr_t)GetXboxSymbolPointer("JVS_SendCommand2");
auto JvsSendCommandOffset3 = (uintptr_t)GetXboxSymbolPointer("JVS_SendCommand3");
if (JvsSendCommandOffset1) {
JvsSendCommandVersion = 1;
g_pPINSA = *(DWORD**)(JvsSendCommandOffset1 + 0x2A0);
g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8);
}
if (JvsSendCommandOffset2) {
JvsSendCommandVersion = 2;
g_pPINSA = *(DWORD**)(JvsSendCommandOffset2 + 0x312);
g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8);
}
if (JvsSendCommandOffset3) {
JvsSendCommandVersion = 3;
g_pPINSA = *(DWORD**)(JvsSendCommandOffset3 + 0x307);
g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8);
if ((DWORD)g_pPINSA > XBE_MAX_VA) {
// This was invalid, we must have the other varient of SendCommand3 (SEGABOOT)
g_pPINSA = *(DWORD**)(JvsSendCommandOffset3 + 0x302);
g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8);
}
}
// Set state to a sane initial default
ChihiroBaseBoardState.Reset();
// Auto-Patch Chihiro Region Flag to match the desired game
uint8_t &region = (uint8_t &)g_BaseBoardQcFirmware[0x1F00];
auto regionFlags = g_MediaBoard->GetBootId().regionFlags;
// The region of the system can be converted to a game region flag by doing 1 << region
// This gives a bitmask that can be ANDed with the BootID region flags to check the games support
if ((regionFlags & (1 << region)) == 0) {
// The region was not compatible, so we need to patch the region flag
// This avoids "Error 05: This game is not acceptable by main board."
// We use USA,EXPORT,JAPAN to make sure mutiple-language games default to English first
if (regionFlags & MB_CHIHIRO_REGION_FLAG_USA) {
region = 2;
}
else if (regionFlags & MB_CHIHIRO_REGION_FLAG_EXPORT) {
region = 3;
}
else if (regionFlags & MB_CHIHIRO_REGION_FLAG_JAPAN) {
region = 1;
}
}
// Spawn the Chihiro/JVS Input Thread
std::thread(JvsInputThread).detach();
}
DWORD WINAPI xbox::EMUPATCH(JVS_SendCommand)
(
DWORD a1,
DWORD Command,
DWORD a3,
DWORD Length,
DWORD a5,
DWORD a6,
DWORD a7,
DWORD a8
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(a1)
LOG_FUNC_ARG(Command)
LOG_FUNC_ARG(a3)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(a5)
LOG_FUNC_ARG(a6)
LOG_FUNC_ARG(a7)
LOG_FUNC_ARG(a8)
LOG_FUNC_END;
LOG_UNIMPLEMENTED();
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsBACKUP_Read)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy((void*)Buffer, &g_BaseBoardBackupMemory[Offset], Length);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsBACKUP_Write)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy(&g_BaseBoardBackupMemory[Offset], (void*)Buffer, Length);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsEEPROM_Read)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG_OUT(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy((void*)Buffer, &g_BaseBoardEeprom[Offset], Length);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsEEPROM_Write)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG_OUT(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy(&g_BaseBoardEeprom[Offset], (void*)Buffer, Length);
std::error_code error;
g_BaseBoardEeprom.sync(error);
if (error) {
EmuLog(LOG_LEVEL::WARNING, "Couldn't sync EEPROM to disk");
}
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsFirmwareDownload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG_OUT(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy((void*)Buffer, &g_BaseBoardQcFirmware[Offset], Length);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsFirmwareUpload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy(&g_BaseBoardQcFirmware[Offset], (void*)Buffer, Length);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsNodeReceivePacket)
(
PUCHAR Buffer,
PDWORD Length,
DWORD a3
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG_OUT(Buffer)
LOG_FUNC_ARG_OUT(Length)
LOG_FUNC_ARG(a3)
LOG_FUNC_END
// Receive the packet from the connected IO board
uint8_t DeviceId = g_pJvsIo->GetDeviceId();
// TODO : "Number of packets received" below might imply multiple packets might need receiving here...
uint16_t payloadSize = (uint16_t)g_pJvsIo->ReceivePacket(&Buffer[6]);
if (payloadSize > 0) {
Buffer[0] = 0; // Empty header byte, ignored
Buffer[1] = 1; // Number of packets received
Buffer[2] = DeviceId;
Buffer[3] = 0; // Unused
*Length = payloadSize + 6;
// Write the payload size header field
*((uint16_t*)&Buffer[4]) = payloadSize; // Packet Length (bytes 4-5)
// TODO : Prevent little/big endian issues here by explicitly setting Buffer[4] and Buffer[5]
}
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsNodeSendPacket)
(
PUCHAR Buffer,
DWORD Length,
DWORD a3
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Buffer)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(a3)
LOG_FUNC_END
// Buffer contains two opening bytes, '00' and 'XX', where XX is the number of JVS packets to send
// Each JVS packet is prepended with a '00' byte, the rest of the packet is as-per the JVS I/O standard.
// Ignore Buffer[0] (should be 0x00)
unsigned packetCount = Buffer[1];
uint8_t* packetPtr = &Buffer[2]; // First JVS packet starts at offset 2;
for (unsigned i = 0; i < packetCount; i++) {
// Skip the separator byte (should be 0x00)
packetPtr++;
// Send the packet to the connected I/O board
size_t bytes = g_pJvsIo->SendPacket(packetPtr);
// Set packetPtr to the next packet
packetPtr += bytes;
}
RETURN(0);
}
// Binary Coded Decimal to Decimal conversion
uint8_t BcdToUint8(uint8_t value)
{
return value - 6 * (value >> 4);
}
uint8_t Uint8ToBcd(uint8_t value)
{
return value + 6 * (value / 10);
}
DWORD WINAPI xbox::EMUPATCH(JvsRTC_Read)
(
DWORD a1,
DWORD a2,
JvsRTCTime* pTime,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(a1)
LOG_FUNC_ARG(a2)
LOG_FUNC_ARG_OUT(time)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
time_t hostTime;
struct tm* hostTimeInfo;
time(&hostTime);
hostTimeInfo = localtime(&hostTime);
memset(pTime, 0, sizeof(JvsRTCTime));
pTime->day = Uint8ToBcd(hostTimeInfo->tm_mday);
pTime->month = Uint8ToBcd(hostTimeInfo->tm_mon + 1); // Chihiro month counter stats at 1
pTime->year = Uint8ToBcd(hostTimeInfo->tm_year - 100); // Chihiro starts counting from year 2000
pTime->hour = Uint8ToBcd(hostTimeInfo->tm_hour);
pTime->minute = Uint8ToBcd(hostTimeInfo->tm_min);
pTime->second = Uint8ToBcd(hostTimeInfo->tm_sec);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsRTC_Write)
(
DWORD a1,
DWORD a2,
JvsRTCTime* pTime,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(a1)
LOG_FUNC_ARG(a2)
LOG_FUNC_ARG_OUT(time)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
LOG_UNIMPLEMENTED();
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsScFirmwareDownload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG_OUT(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy((void*)Buffer, &g_BaseBoardScFirmware[Offset], Length);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsScFirmwareUpload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Offset)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(Buffer)
LOG_FUNC_ARG(a4)
LOG_FUNC_END
memcpy(&g_BaseBoardScFirmware[Offset], (void*)Buffer, Length);
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsScReceiveMidi)
(
DWORD a1,
DWORD a2,
DWORD a3
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(a1)
LOG_FUNC_ARG(a2)
LOG_FUNC_ARG(a3)
LOG_FUNC_END
LOG_UNIMPLEMENTED();
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsScSendMidi)
(
DWORD a1,
DWORD a2,
DWORD a3
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(a1)
LOG_FUNC_ARG(a2)
LOG_FUNC_ARG(a3)
LOG_FUNC_END
LOG_UNIMPLEMENTED();
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsScReceiveRs323c)
(
PUCHAR Buffer,
DWORD Length,
DWORD a3
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Buffer)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(a3)
LOG_FUNC_END
LOG_UNIMPLEMENTED();
RETURN(0);
}
DWORD WINAPI xbox::EMUPATCH(JvsScSendRs323c)
(
PUCHAR Buffer,
DWORD Length,
DWORD a3
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Buffer)
LOG_FUNC_ARG(Length)
LOG_FUNC_ARG(a3)
LOG_FUNC_END
LOG_UNIMPLEMENTED();
RETURN(0);
}

182
src/core/hle/JVS/JVS.h Normal file
View file

@ -0,0 +1,182 @@
// ******************************************************************
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them under the terms of the GNU General Public
// * License as published by the Free Software Foundation; either
// * version 2 of the license, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2019 Luke Usher <luke.usher@outlook.com>
// *
// * All rights reserved
// *
// ******************************************************************
#ifndef JVS_H
#define JVS_H
// Used by CxbxKrnl to setup JVS roms
void JVS_Init();
#include "core\hle\XAPI\Xapi.h" // For EMUPATCH
namespace xbox {
DWORD WINAPI EMUPATCH(JVS_SendCommand)
(
DWORD a1,
DWORD Command,
DWORD a3,
DWORD Length,
DWORD a5,
DWORD a6,
DWORD a7,
DWORD a8
);
DWORD WINAPI EMUPATCH(JvsBACKUP_Read)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsBACKUP_Write)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsEEPROM_Read)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsEEPROM_Write)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsFirmwareDownload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsFirmwareUpload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsNodeReceivePacket)
(
PUCHAR Buffer,
PDWORD Length,
DWORD a3
);
DWORD WINAPI EMUPATCH(JvsNodeSendPacket)
(
PUCHAR Buffer,
DWORD Length,
DWORD a3
);
typedef struct {
UCHAR second;
UCHAR minute;
UCHAR hour;
UCHAR unused_2;
UCHAR day;
UCHAR month;
UCHAR year;
UCHAR unused_1;
} JvsRTCTime;
DWORD WINAPI EMUPATCH(JvsRTC_Read)
(
DWORD a1,
DWORD a2,
JvsRTCTime *time,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsRTC_Write)
(
DWORD a1,
DWORD a2,
JvsRTCTime *time,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsScFirmwareDownload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsScFirmwareUpload)
(
DWORD Offset,
DWORD Length,
PUCHAR Buffer,
DWORD a4
);
DWORD WINAPI EMUPATCH(JvsScReceiveMidi)
(
DWORD a1,
DWORD a2,
DWORD a3
);
DWORD WINAPI EMUPATCH(JvsScSendMidi)
(
DWORD a1,
DWORD a2,
DWORD a3
);
DWORD WINAPI EMUPATCH(JvsScReceiveRs323c)
(
PUCHAR Buffer,
DWORD Length,
DWORD a3
);
DWORD WINAPI EMUPATCH(JvsScSendRs323c)
(
PUCHAR Buffer,
DWORD Length,
DWORD a3
);
}
#endif

View file

@ -29,6 +29,7 @@
#include "core\kernel\init\CxbxKrnl.h"
#include "core\kernel\support\Emu.h"
#include "core\hle\D3D8\Direct3D9/Direct3D9.h"
#include "core\hle\JVS\JVS.h"
#include "core\hle\DSOUND\DirectSound\DirectSound.hpp"
#include "Patches.hpp"
#include "Intercept.hpp"
@ -57,13 +58,16 @@ const uint32_t PATCH_IS_FIBER = 1 << 4;
std::map<const std::string, const xbox_patch_t> g_PatchTable = {
// Direct3D
PATCH_ENTRY("CDevice_SetStateUP", xbox::EMUPATCH(CDevice_SetStateUP), PATCH_HLE_D3D),
PATCH_ENTRY("CDevice_SetStateUP_4", xbox::EMUPATCH(CDevice_SetStateUP_4), PATCH_HLE_D3D),
PATCH_ENTRY("CDevice_SetStateUP_0__LTCG_esi1", xbox::EMUPATCH(CDevice_SetStateUP_0__LTCG_esi1), PATCH_HLE_D3D),
PATCH_ENTRY("CDevice_SetStateVB", xbox::EMUPATCH(CDevice_SetStateVB), PATCH_HLE_D3D),
PATCH_ENTRY("CDevice_SetStateVB_8", xbox::EMUPATCH(CDevice_SetStateVB_8), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Begin", xbox::EMUPATCH(D3DDevice_Begin), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BeginPush", xbox::EMUPATCH(D3DDevice_BeginPush), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BeginPush2", xbox::EMUPATCH(D3DDevice_BeginPush2), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BeginPush_4", xbox::EMUPATCH(D3DDevice_BeginPush_4), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BeginPush_8", xbox::EMUPATCH(D3DDevice_BeginPush_8), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BeginVisibilityTest", xbox::EMUPATCH(D3DDevice_BeginVisibilityTest), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BlockOnFence", xbox::EMUPATCH(D3DDevice_BlockOnFence), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BlockUntilVerticalBlank", xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank), PATCH_HLE_D3D),
//PATCH_ENTRY("D3DDevice_BlockUntilVerticalBlank", xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Clear", xbox::EMUPATCH(D3DDevice_Clear), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_CopyRects", xbox::EMUPATCH(D3DDevice_CopyRects), PATCH_HLE_D3D),
// PATCH_ENTRY("D3DDevice_CreateVertexShader", xbox::EMUPATCH(D3DDevice_CreateVertexShader), PATCH_HLE_D3D),
@ -74,9 +78,10 @@ std::map<const std::string, const xbox_patch_t> g_PatchTable = {
PATCH_ENTRY("D3DDevice_DrawRectPatch", xbox::EMUPATCH(D3DDevice_DrawRectPatch), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawTriPatch", xbox::EMUPATCH(D3DDevice_DrawTriPatch), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawVertices", xbox::EMUPATCH(D3DDevice_DrawVertices), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawVertices_4", xbox::EMUPATCH(D3DDevice_DrawVertices_4), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawVertices_4__LTCG_ecx2_eax3", xbox::EMUPATCH(D3DDevice_DrawVertices_4__LTCG_ecx2_eax3), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawVertices_8__LTCG_eax3", xbox::EMUPATCH(D3DDevice_DrawVertices_8__LTCG_eax3), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawVerticesUP", xbox::EMUPATCH(D3DDevice_DrawVerticesUP), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawVerticesUP_12", xbox::EMUPATCH(D3DDevice_DrawVerticesUP_12), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_DrawVerticesUP_12__LTCG_ebx3", xbox::EMUPATCH(D3DDevice_DrawVerticesUP_12__LTCG_ebx3), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_EnableOverlay", xbox::EMUPATCH(D3DDevice_EnableOverlay), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_End", xbox::EMUPATCH(D3DDevice_End), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_EndPush", xbox::EMUPATCH(D3DDevice_EndPush), PATCH_HLE_D3D),
@ -118,12 +123,14 @@ std::map<const std::string, const xbox_patch_t> g_PatchTable = {
PATCH_ENTRY("D3DDevice_Present", xbox::EMUPATCH(D3DDevice_Present), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_PrimeVertexCache", xbox::EMUPATCH(D3DDevice_PrimeVertexCache), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Reset", xbox::EMUPATCH(D3DDevice_Reset), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Reset_0__LTCG_edi1", xbox::EMUPATCH(D3DDevice_Reset_0__LTCG_edi1), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Reset_0__LTCG_ebx1", xbox::EMUPATCH(D3DDevice_Reset_0__LTCG_ebx1), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_RunPushBuffer", xbox::EMUPATCH(D3DDevice_RunPushBuffer), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_RunVertexStateShader", xbox::EMUPATCH(D3DDevice_RunVertexStateShader), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SelectVertexShader", xbox::EMUPATCH(D3DDevice_SelectVertexShader), PATCH_HLE_D3D),
//PATCH_ENTRY("D3DDevice_SelectVertexShaderDirect", xbox::EMUPATCH(D3DDevice_SelectVertexShaderDirect), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SelectVertexShader_0", xbox::EMUPATCH(D3DDevice_SelectVertexShader_0), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SelectVertexShader_4", xbox::EMUPATCH(D3DDevice_SelectVertexShader_4), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SelectVertexShader_0__LTCG_eax1_ebx2", xbox::EMUPATCH(D3DDevice_SelectVertexShader_0__LTCG_eax1_ebx2), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SelectVertexShader_4__LTCG_eax1", xbox::EMUPATCH(D3DDevice_SelectVertexShader_4__LTCG_eax1), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetBackBufferScale", xbox::EMUPATCH(D3DDevice_SetBackBufferScale), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetDepthClipPlanes", xbox::EMUPATCH(D3DDevice_SetDepthClipPlanes), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetFlickerFilter", xbox::EMUPATCH(D3DDevice_SetFlickerFilter), PATCH_HLE_D3D),
@ -138,7 +145,7 @@ std::map<const std::string, const xbox_patch_t> g_PatchTable = {
PATCH_ENTRY("D3DDevice_SetPalette_4", xbox::EMUPATCH(D3DDevice_SetPalette_4), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetPixelShader", xbox::EMUPATCH(D3DDevice_SetPixelShader), PATCH_HLE_D3D),
//PATCH_ENTRY("D3DDevice_SetPixelShaderConstant_4", xbox::EMUPATCH(D3DDevice_SetPixelShaderConstant_4), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetPixelShader_0", xbox::EMUPATCH(D3DDevice_SetPixelShader_0), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetPixelShader_0__LTCG_eax_handle", xbox::EMUPATCH(D3DDevice_SetPixelShader_0__LTCG_eax_handle), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetRenderState_Simple", xbox::EMUPATCH(D3DDevice_SetRenderState_Simple), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetRenderTarget", xbox::EMUPATCH(D3DDevice_SetRenderTarget), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetRenderTargetFast", xbox::EMUPATCH(D3DDevice_SetRenderTargetFast), PATCH_HLE_D3D),
@ -156,7 +163,7 @@ std::map<const std::string, const xbox_patch_t> g_PatchTable = {
PATCH_ENTRY("D3DDevice_SetSwapCallback", xbox::EMUPATCH(D3DDevice_SetSwapCallback), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetTexture", xbox::EMUPATCH(D3DDevice_SetTexture), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetTexture_4__LTCG_eax_pTexture", xbox::EMUPATCH(D3DDevice_SetTexture_4__LTCG_eax_pTexture), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetTexture_4", xbox::EMUPATCH(D3DDevice_SetTexture_4), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetTexture_4__LTCG_eax_Stage", xbox::EMUPATCH(D3DDevice_SetTexture_4__LTCG_eax_Stage), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetTransform", xbox::EMUPATCH(D3DDevice_SetTransform), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetTransform_0__LTCG_eax1_edx2", xbox::EMUPATCH(D3DDevice_SetTransform_0__LTCG_eax1_edx2), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetVertexData2f", xbox::EMUPATCH(D3DDevice_SetVertexData2f), PATCH_HLE_D3D),
@ -177,7 +184,7 @@ std::map<const std::string, const xbox_patch_t> g_PatchTable = {
PATCH_ENTRY("D3DDevice_SetVertexShaderConstant_8", xbox::EMUPATCH(D3DDevice_SetVertexShaderConstant_8), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetVertexShaderInput", xbox::EMUPATCH(D3DDevice_SetVertexShaderInput), PATCH_HLE_D3D),
//PATCH_ENTRY("D3DDevice_SetVertexShaderInputDirect", xbox::EMUPATCH(D3DDevice_SetVertexShaderInputDirect), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetVerticalBlankCallback", xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback), PATCH_HLE_D3D),
//PATCH_ENTRY("D3DDevice_SetVerticalBlankCallback", xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetViewport", xbox::EMUPATCH(D3DDevice_SetViewport), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Swap", xbox::EMUPATCH(D3DDevice_Swap), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Swap_0", xbox::EMUPATCH(D3DDevice_Swap_0), PATCH_HLE_D3D),
@ -369,6 +376,54 @@ std::map<const std::string, const xbox_patch_t> g_PatchTable = {
//PATCH_ENTRY("timeSetEvent", xbox::EMUPATCH(timeSetEvent), PATCH_ALWAYS),
PATCH_ENTRY("XReadMUMetaData", xbox::EMUPATCH(XReadMUMetaData), PATCH_ALWAYS),
PATCH_ENTRY("XUnmountMU", xbox::EMUPATCH(XUnmountMU), PATCH_ALWAYS),
// JVS Functions
PATCH_ENTRY("JVS_SendCommand", xbox::EMUPATCH(JVS_SendCommand), PATCH_ALWAYS),
PATCH_ENTRY("JVS_SendCommand2", xbox::EMUPATCH(JVS_SendCommand), PATCH_ALWAYS),
PATCH_ENTRY("JVS_SendCommand3", xbox::EMUPATCH(JVS_SendCommand), PATCH_ALWAYS),
PATCH_ENTRY("JvsBACKUP_Read", xbox::EMUPATCH(JvsBACKUP_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsBACKUP_Read2", xbox::EMUPATCH(JvsBACKUP_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsBACKUP_Read3", xbox::EMUPATCH(JvsBACKUP_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsBACKUP_Write", xbox::EMUPATCH(JvsBACKUP_Write), PATCH_ALWAYS),
PATCH_ENTRY("JvsBACKUP_Write2", xbox::EMUPATCH(JvsBACKUP_Write), PATCH_ALWAYS),
PATCH_ENTRY("JvsEEPROM_Read", xbox::EMUPATCH(JvsEEPROM_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsEEPROM_Read2", xbox::EMUPATCH(JvsEEPROM_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsEEPROM_Read3", xbox::EMUPATCH(JvsEEPROM_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsEEPROM_Write", xbox::EMUPATCH(JvsEEPROM_Write), PATCH_ALWAYS),
PATCH_ENTRY("JvsEEPROM_Write2", xbox::EMUPATCH(JvsEEPROM_Write), PATCH_ALWAYS),
PATCH_ENTRY("JvsEEPROM_Write3", xbox::EMUPATCH(JvsEEPROM_Write), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareDownload", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareDownload2", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareDownload3", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareDownload4", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareUpload", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareUpload2", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareUpload3", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS),
PATCH_ENTRY("JvsFirmwareUpload4", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS),
PATCH_ENTRY("JvsNodeReceivePacket", xbox::EMUPATCH(JvsNodeReceivePacket), PATCH_ALWAYS),
PATCH_ENTRY("JvsNodeReceivePacket2", xbox::EMUPATCH(JvsNodeReceivePacket), PATCH_ALWAYS),
PATCH_ENTRY("JvsNodeSendPacket", xbox::EMUPATCH(JvsNodeSendPacket), PATCH_ALWAYS),
PATCH_ENTRY("JvsNodeSendPacket2", xbox::EMUPATCH(JvsNodeSendPacket), PATCH_ALWAYS),
PATCH_ENTRY("JvsRTC_Read", xbox::EMUPATCH(JvsRTC_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsRTC_Read2", xbox::EMUPATCH(JvsRTC_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsRTC_Read3", xbox::EMUPATCH(JvsRTC_Read), PATCH_ALWAYS),
PATCH_ENTRY("JvsRTC_Write", xbox::EMUPATCH(JvsRTC_Write), PATCH_ALWAYS),
PATCH_ENTRY("JvsRTC_Write2", xbox::EMUPATCH(JvsRTC_Write), PATCH_ALWAYS),
PATCH_ENTRY("JvsScFirmwareDownload", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsScFirmwareDownload2", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsScFirmwareDownload3", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsScFirmwareDownload4", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS),
PATCH_ENTRY("JvsScFirmwareUpload", xbox::EMUPATCH(JvsScFirmwareUpload), PATCH_ALWAYS),
PATCH_ENTRY("JvsScFirmwareUpload2", xbox::EMUPATCH(JvsScFirmwareUpload), PATCH_ALWAYS),
PATCH_ENTRY("JvsScFirmwareUpload3", xbox::EMUPATCH(JvsScFirmwareUpload), PATCH_ALWAYS),
PATCH_ENTRY("JvsScReceiveMidi", xbox::EMUPATCH(JvsScReceiveMidi), PATCH_ALWAYS),
PATCH_ENTRY("JvsScReceiveMidi2", xbox::EMUPATCH(JvsScReceiveMidi), PATCH_ALWAYS),
PATCH_ENTRY("JvsScReceiveRs323c", xbox::EMUPATCH(JvsScReceiveRs323c), PATCH_ALWAYS),
PATCH_ENTRY("JvsScReceiveRs323c2", xbox::EMUPATCH(JvsScReceiveRs323c), PATCH_ALWAYS),
PATCH_ENTRY("JvsScSendMidi", xbox::EMUPATCH(JvsScSendMidi), PATCH_ALWAYS),
PATCH_ENTRY("JvsScSendMidi2", xbox::EMUPATCH(JvsScSendMidi), PATCH_ALWAYS),
PATCH_ENTRY("JvsScSendRs323c", xbox::EMUPATCH(JvsScSendRs323c), PATCH_ALWAYS),
PATCH_ENTRY("JvsScSendRs323c2", xbox::EMUPATCH(JvsScSendRs323c), PATCH_ALWAYS),
};
std::unordered_map<std::string, subhook::Hook> g_FunctionHooks;

View file

@ -399,91 +399,34 @@ void DestructHleInputDevice(DeviceState *dev)
void SetupXboxDeviceTypes()
{
// If we don't yet have the offset to gDeviceType_Gamepad, work it out!
if (g_DeviceType_Gamepad == nullptr) {
// First, attempt to find GetTypeInformation
auto typeInformation = g_SymbolAddresses.find("GetTypeInformation");
if (typeInformation != g_SymbolAddresses.end() && typeInformation->second != xbox::zero) {
EmuLog(LOG_LEVEL::INFO, "Deriving XDEVICE_TYPE_GAMEPAD from DeviceTable (via GetTypeInformation)");
// Read the offset values of the device table structure from GetTypeInformation
xbox::addr_xt deviceTableStartOffset = *(uint32_t*)((uint32_t)typeInformation->second + 0x01);
xbox::addr_xt deviceTableEndOffset = *(uint32_t*)((uint32_t)typeInformation->second + 0x09);
// Calculate the number of device entires in the table
size_t deviceTableEntryCount = (deviceTableEndOffset - deviceTableStartOffset) / sizeof(uint32_t);
EmuLog(LOG_LEVEL::INFO, "DeviceTableStart: 0x%08X", deviceTableStartOffset);
EmuLog(LOG_LEVEL::INFO, "DeviceTableEnd: 0x%08X", deviceTableEndOffset);
EmuLog(LOG_LEVEL::INFO, "DeviceTable Entires: %u", deviceTableEntryCount);
// Sanity check: Where all these device offsets within Xbox memory
if ((deviceTableStartOffset >= g_SystemMaxMemory) || (deviceTableEndOffset >= g_SystemMaxMemory)) {
CxbxrAbort("DeviceTable Location is outside of Xbox Memory range");
}
// Iterate through the table until we find gamepad
xbox::PXID_TYPE_INFORMATION* deviceTable = (xbox::PXID_TYPE_INFORMATION*)(deviceTableStartOffset);
for (unsigned int i = 0; i < deviceTableEntryCount; i++) {
// Skip empty table entries
if (deviceTable[i] == nullptr) {
continue;
}
EmuLog(LOG_LEVEL::INFO, "----------------------------------------");
EmuLog(LOG_LEVEL::INFO, "DeviceTable[%u]->ucType = %d", i, deviceTable[i]->ucType);
switch (deviceTable[i]->ucType) {
case XINPUT_DEVTYPE_GAMEPAD:
g_DeviceType_Gamepad = deviceTable[i]->XppType;
EmuLog(LOG_LEVEL::INFO, "DeviceTable[%u]->XppType = 0x%08X (XDEVICE_TYPE_GAMEPAD)", i, (uintptr_t)g_DeviceType_Gamepad);
break;
case XINPUT_DEVTYPE_STEELBATTALION:
g_DeviceType_SBC = deviceTable[i]->XppType;
EmuLog(LOG_LEVEL::INFO, "DeviceTable[%u]->XppType = 0x%08X (XDEVICE_TYPE_STEELBATTALION)", i, (uintptr_t)g_DeviceType_SBC);
break;
default:
EmuLog(LOG_LEVEL::WARNING, "DeviceTable[%u]->XppType = 0x%08X (Unknown device type)", i, (uintptr_t)deviceTable[i]->XppType);
continue;
}
}
} else {
// XDKs without GetTypeInformation have the GamePad address hardcoded in XInputOpen
// Only the earliest XDKs use this code path, and the offset never changed between them
// so this works well for us.
void* XInputOpenAddr = (void*)g_SymbolAddresses["XInputOpen"];
if (XInputOpenAddr != nullptr) {
EmuLog(LOG_LEVEL::INFO, "Deriving XDEVICE_TYPE_GAMEPAD from XInputOpen (0x%08X)", (uintptr_t)XInputOpenAddr);
g_DeviceType_Gamepad = *(xbox::PXPP_DEVICE_TYPE*)((uint32_t)XInputOpenAddr + 0x0B);
}
}
if (g_DeviceType_Gamepad == nullptr) {
EmuLog(LOG_LEVEL::WARNING, "XDEVICE_TYPE_GAMEPAD was not found");
return;
}
EmuLog(LOG_LEVEL::INFO, "XDEVICE_TYPE_GAMEPAD found at 0x%08X", (uintptr_t)g_DeviceType_Gamepad);
// Get address to xpp type's devices
if (xbox::addr_xt gamepad_xpp_type = g_SymbolAddresses["g_DeviceType_Gamepad"]) {
g_DeviceType_Gamepad = reinterpret_cast<xbox::PXPP_DEVICE_TYPE>(gamepad_xpp_type);
}
#if 0 // Not implemented
if (xbox::addr_xt ir_dongle_xpp_type = g_SymbolAddresses["g_DeviceType_IRDongle"]) {
g_DeviceType_IRDongle = reinterpret_cast<xbox::PXPP_DEVICE_TYPE>(ir_dongle_xpp_type);
}
if (xbox::addr_xt keyboard_xpp_type = g_SymbolAddresses["g_DeviceType_Keyboard"]) {
g_DeviceType_Keyboard = reinterpret_cast<xbox::PXPP_DEVICE_TYPE>(keyboard_xpp_type);
}
if (xbox::addr_xt mouse_xpp_type = g_SymbolAddresses["g_DeviceType_Mouse"]) {
g_DeviceType_Mouse = reinterpret_cast<xbox::PXPP_DEVICE_TYPE>(mouse_xpp_type);
}
#endif
if (xbox::addr_xt sbc_xpp_type = g_SymbolAddresses["g_DeviceType_SBC"]) {
g_DeviceType_SBC = reinterpret_cast<xbox::PXPP_DEVICE_TYPE>(sbc_xpp_type);
}
if (xbox::addr_xt mu_xpp_type = g_SymbolAddresses["g_DeviceType_MU"]) {
g_DeviceType_MU = reinterpret_cast<xbox::PXPP_DEVICE_TYPE>(mu_xpp_type);
EmuLog(LOG_LEVEL::INFO, "XDEVICE_TYPE_MEMORY_UNIT found at 0x%08X", reinterpret_cast<uintptr_t>(g_DeviceType_MU));
}
else {
EmuLog(LOG_LEVEL::INFO, "XDEVICE_TYPE_MEMORY_UNIT was not found by XbSymbolDatabase");
}
// Get additional variables relative to Memory Unit
if (xbox::addr_xt xapi_mounted_mu = g_SymbolAddresses["g_XapiMountedMUs"]) {
g_XapiMountedMUs = reinterpret_cast<xbox::ulong_xt *>(xapi_mounted_mu);
EmuLog(LOG_LEVEL::INFO, "XapiMountedMUs found at 0x%08X", reinterpret_cast<uintptr_t>(g_XapiMountedMUs));
g_XapiAltLett_MU = reinterpret_cast<xbox::char_xt *>(g_XapiMountedMUs - 1);
EmuLog(LOG_LEVEL::INFO, "XapiAltLett_MU found at 0x%08X", reinterpret_cast<uintptr_t>(g_XapiAltLett_MU));
g_XapiMountedMUs = reinterpret_cast<xbox::ulong_xt*>(xapi_mounted_mu);
}
else {
EmuLog(LOG_LEVEL::INFO, "XapiMountedMUs was not found by XbSymbolDatabase");
if (xbox::addr_xt xapi_alt_lett_mu = g_SymbolAddresses["g_XapiAltLett_MU"]) {
g_XapiAltLett_MU = reinterpret_cast<xbox::char_xt *>(xapi_alt_lett_mu);
}
}
@ -1017,15 +960,41 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait)
NewTime.QuadPart += (static_cast<xbox::ulonglong_xt>(dwMilliseconds) * CLOCK_TIME_INCREMENT);
}
xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional<DWORD> {
PKTHREAD kThread = KeGetCurrentThread();
kThread->WaitStatus = X_STATUS_SUCCESS;
if (!AddWaitObject(kThread, Timeout)) {
RETURN(WAIT_TIMEOUT);
}
xbox::ntstatus_xt status = WaitApc<true>([hObjectToSignal, hObjectToWaitOn, bAlertable](xbox::PKTHREAD kThread) -> std::optional<DWORD> {
DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable);
if (dwRet == WAIT_TIMEOUT) {
return std::nullopt;
}
return std::make_optional<DWORD>(dwRet);
}, Timeout, bAlertable, UserMode);
// If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added
// to the thread
xbox::ntstatus_xt Status;
switch (dwRet)
{
case WAIT_ABANDONED: Status = X_STATUS_ABANDONED; break;
case WAIT_IO_COMPLETION: Status = X_STATUS_USER_APC; break;
case WAIT_OBJECT_0: Status = X_STATUS_SUCCESS; break;
default: Status = X_STATUS_INVALID_HANDLE;
}
xbox::KiUnwaitThreadAndLock(kThread, Status, 0);
return std::make_optional<ntstatus_xt>(kThread->WaitStatus);
}, Timeout, bAlertable, UserMode, kThread);
RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret);
xbox::dword_xt ret;
switch (status)
{
case X_STATUS_ABANDONED: ret = WAIT_ABANDONED; break;
case X_STATUS_USER_APC: ret = WAIT_IO_COMPLETION; break;
case X_STATUS_SUCCESS: ret = WAIT_OBJECT_0; break;
case X_STATUS_TIMEOUT: ret = WAIT_TIMEOUT; break;
default: ret = WAIT_FAILED;
}
RETURN(ret);
}
// ******************************************************************

View file

@ -605,6 +605,9 @@ XBSYSAPI EXPORTNUM(352) void_xt NTAPI RtlRip
PCHAR Message
);
void_xt RtlInitSystem();
extern RTL_CRITICAL_SECTION NtSystemTimeCritSec;
}
#endif

View file

@ -98,6 +98,8 @@ typedef void* LPSECURITY_ATTRIBUTES;
#define X_STATUS_FILE_IS_A_DIRECTORY 0xC00000BAL
#define X_STATUS_END_OF_FILE 0xC0000011L
#define X_STATUS_INVALID_PAGE_PROTECTION 0xC0000045L
#define X_STATUS_SUSPEND_COUNT_EXCEEDED 0xC000004AL
#define X_STATUS_THREAD_IS_TERMINATING 0xC000004BL
#define X_STATUS_CONFLICTING_ADDRESSES 0xC0000018L
#define X_STATUS_UNABLE_TO_FREE_VM 0xC000001AL
#define X_STATUS_FREE_VM_NOT_AT_BASE 0xC000009FL
@ -266,7 +268,7 @@ typedef struct _UNICODE_STRING
{
ushort_xt Length;
ushort_xt MaximumLength;
ushort_xt *Buffer;
wchar_xt *Buffer;
}
UNICODE_STRING, *PUNICODE_STRING;
@ -1945,7 +1947,7 @@ typedef struct _KTHREAD
/* 0x56/86 */ char_xt WaitNext;
/* 0x57/87 */ char_xt WaitReason;
/* 0x58/88 */ PKWAIT_BLOCK WaitBlockList;
/* 0x5C/92 */ LIST_ENTRY WaitListEntry;
/* 0x5C/92 */ LIST_ENTRY WaitListEntry; // Used to place the thread in the ready list of the scheduler
/* 0x64/100 */ ulong_xt WaitTime;
/* 0x68/104 */ ulong_xt KernelApcDisable;
/* 0x6C/108 */ ulong_xt Quantum;
@ -1969,6 +1971,8 @@ typedef struct _KTHREAD
}
KTHREAD, *PKTHREAD, *RESTRICTED_POINTER PRKTHREAD;
#define X_MAXIMUM_SUSPEND_COUNT 0x7F
// ******************************************************************
// * ETHREAD
// ******************************************************************
@ -2081,6 +2085,11 @@ typedef enum _XC_VALUE_INDEX
}
XC_VALUE_INDEX, *PXC_VALUE_INDEX;
#define XBOX_HW_FLAG_INTERNAL_USB_HUB 0x00000001
#define XBOX_HW_FLAG_DEVKIT_KERNEL 0x00000002
#define XBOX_480P_MACROVISION_ENABLED 0x00000004
#define XBOX_HW_FLAG_ARCADE 0x00000008
// ******************************************************************
// * XBOX_HARDWARE_INFO
// ******************************************************************

View file

@ -85,6 +85,8 @@ void InsertTailList(xbox::PLIST_ENTRY pListHead, xbox::PLIST_ENTRY pEntry)
//#define RemoveEntryList(e) do { PLIST_ENTRY f = (e)->Flink, b = (e)->Blink; f->Blink = b; b->Flink = f; (e)->Flink = (e)->Blink = NULL; } while (0)
// Returns TRUE if the list has become empty after removing the element, FALSE otherwise.
// NOTE: this function is a mess. _EX_Flink and _EX_Flink should never be nullptr, and it should never be called on a detached element either. Try to fix
// the bugs in the caller instead of trying to handle it here with these hacks
xbox::boolean_xt RemoveEntryList(xbox::PLIST_ENTRY pEntry)
{
xbox::PLIST_ENTRY _EX_Flink = pEntry->Flink;
@ -140,8 +142,6 @@ void RestoreInterruptMode(bool value)
g_bInterruptsEnabled = value;
}
extern void ExecuteDpcQueue();
void KiUnexpectedInterrupt()
{
xbox::KeBugCheck(TRAP_CAUSE_UNKNOWN); // see
@ -158,7 +158,10 @@ void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql)
xbox::KiExecuteKernelApc();
break;
case DISPATCH_LEVEL: // = 2
ExecuteDpcQueue();
// This can be recursively called by KiUnlockDispatcherDatabase and KfLowerIrql, so avoid calling DPCs again if the current one has queued yet another one
if (!IsDpcActive()) { // Avoid KeIsExecutingDpc(), as that logs
ExecuteDpcQueue();
}
break;
case APC_LEVEL | DISPATCH_LEVEL: // = 3
KiUnexpectedInterrupt();
@ -175,6 +178,33 @@ void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql)
HalInterruptRequestRegister ^= (1 << SoftwareIrql);
}
bool AddWaitObject(xbox::PKTHREAD kThread, xbox::PLARGE_INTEGER Timeout)
{
// Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work
xbox::KiTimerLock();
xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock;
kThread->WaitBlockList = WaitBlock;
xbox::PKTIMER Timer = &kThread->Timer;
WaitBlock->NextWaitBlock = WaitBlock;
Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry;
if (Timeout && Timeout->QuadPart) {
// Setup a timer so that KiTimerExpiration can discover the timeout and yield to us.
// Otherwise, we will only be able to discover the timeout when Windows decides to schedule us again, and testing shows that
// tends to happen much later than the due time
if (xbox::KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
// Sanity check: set WaitBlockList to nullptr so that we can catch the case where a waiter starts a new wait but forgets to setup a new wait block. This
// way, we will crash instead of silently using the pointer to the old block
kThread->WaitBlockList = xbox::zeroptr;
xbox::KiTimerUnlock();
return false;
}
}
kThread->State = xbox::Waiting;
xbox::KiTimerUnlock();
return true;
}
// This masks have been verified to be correct against a kernel dump
const DWORD IrqlMasks[] = {
0xFFFFFFFE, // IRQL 0
@ -450,7 +480,8 @@ XBSYSAPI EXPORTNUM(163) xbox::void_xt FASTCALL xbox::KiUnlockDispatcherDatabase
LOG_FUNC_ONE_ARG_TYPE(KIRQL_TYPE, OldIrql);
// Wrong, this should only happen when OldIrql >= DISPATCH_LEVEL
if (!(KeGetCurrentPrcb()->DpcRoutineActive)) { // Avoid KeIsExecutingDpc(), as that logs
// Checking DpcRoutineActive doesn't work because our Prcb is per-thread instead of being per-processor
if (!IsDpcActive()) { // Avoid KeIsExecutingDpc(), as that logs
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
}

View file

@ -52,8 +52,8 @@ xbox::PLIST_ENTRY RemoveTailList(xbox::PLIST_ENTRY pListHead);
extern xbox::LAUNCH_DATA_PAGE DefaultLaunchDataPage;
extern xbox::PKINTERRUPT EmuInterruptList[MAX_BUS_INTERRUPT_LEVEL + 1];
inline std::condition_variable g_InterruptSignal;
inline std::atomic_bool g_AnyInterruptAsserted = false;
// Indicates to disable/enable all interrupts when cli and sti instructions are executed
inline std::atomic_bool g_bEnableAllInterrupts = true;
class HalSystemInterrupt {
public:
@ -64,8 +64,6 @@ public:
}
m_Asserted = state;
g_AnyInterruptAsserted = true;
g_InterruptSignal.notify_one();
};
void Enable() {
@ -110,17 +108,18 @@ extern HalSystemInterrupt HalSystemInterrupts[MAX_BUS_INTERRUPT_LEVEL + 1];
bool DisableInterrupts();
void RestoreInterruptMode(bool value);
void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql);
bool AddWaitObject(xbox::PKTHREAD kThread, xbox::PLARGE_INTEGER Timeout);
template<typename T>
std::optional<xbox::ntstatus_xt> SatisfyWait(T &&Lambda, xbox::PETHREAD eThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
std::optional<xbox::ntstatus_xt> SatisfyWait(T &&Lambda, xbox::PKTHREAD kThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
{
if (const auto ret = Lambda()) {
if (const auto ret = Lambda(kThread)) {
return ret;
}
xbox::KiApcListMtx.lock();
bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]);
bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]);
bool EmptyKernel = IsListEmpty(&kThread->ApcState.ApcListHead[xbox::KernelMode]);
bool EmptyUser = IsListEmpty(&kThread->ApcState.ApcListHead[xbox::UserMode]);
xbox::KiApcListMtx.unlock();
if (EmptyKernel == false) {
@ -131,56 +130,65 @@ std::optional<xbox::ntstatus_xt> SatisfyWait(T &&Lambda, xbox::PETHREAD eThread,
(Alertable == TRUE) &&
(WaitMode == xbox::UserMode)) {
xbox::KiExecuteUserApc();
return X_STATUS_USER_APC;
xbox::KiUnwaitThreadAndLock(kThread, X_STATUS_USER_APC, 0);
return kThread->WaitStatus;
}
return std::nullopt;
}
template<typename T>
xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
template<bool host_wait, typename T>
xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode, xbox::PKTHREAD kThread)
{
// NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should
// also interrupt the wait
xbox::PETHREAD eThread = reinterpret_cast<xbox::PETHREAD>(EmuKeGetPcr()->Prcb->CurrentThread);
// NOTE1: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should
// also interrupt the wait.
xbox::ntstatus_xt status;
if (Timeout == nullptr) {
// No timout specified, so this is an infinite wait until an alert, a user apc or the object(s) become(s) signalled
while (true) {
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) {
status = *ret;
break;
}
std::this_thread::yield();
}
}
else if (Timeout->QuadPart == 0) {
assert(host_wait);
// A zero timeout means that we only have to check the conditions once and then return immediately if they are not satisfied
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) {
status = *ret;
}
else {
return X_STATUS_TIMEOUT;
// If the wait failed, then always remove the wait block. Note that this can only happen with host waits, since guest waits never call us at all
// when Timeout->QuadPart == 0. Test case: Halo 2 (sporadically when playing the intro video)
xbox::KiUnwaitThreadAndLock(kThread, X_STATUS_TIMEOUT, 0);
status = kThread->WaitStatus;
}
}
else {
// A non-zero timeout means we have to check the conditions until we reach the requested time
xbox::LARGE_INTEGER ExpireTime, DueTime, NewTime;
xbox::ulonglong_xt Now;
ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart; // either positive, negative, but not NULL
xbox::PLARGE_INTEGER AbsoluteExpireTime = xbox::KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime, &Now);
while (Now <= static_cast<xbox::ulonglong_xt>(AbsoluteExpireTime->QuadPart)) {
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
while (true) {
if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) {
status = *ret;
break;
}
if (host_wait && (kThread->State == xbox::Ready)) {
status = kThread->WaitStatus;
break;
}
std::this_thread::yield();
Now = xbox::KeQueryInterruptTime();
}
return X_STATUS_TIMEOUT;
}
if constexpr (host_wait) {
kThread->State = xbox::Running;
}
return status;
}
#endif

View file

@ -43,7 +43,6 @@
#include "common\EmuEEPROM.h" // For EEPROM
#include "devices\Xbox.h" // For g_SMBus, SMBUS_ADDRESS_SYSTEM_MICRO_CONTROLLER
#include "devices\SMCDevice.h" // For SMC_COMMAND_SCRATCH
#include "common/util/strConverter.hpp" // for utf16_to_ascii
#include "core\kernel\memory-manager\VMManager.h"
#include <algorithm> // for std::replace
@ -583,6 +582,7 @@ XBSYSAPI EXPORTNUM(49) xbox::void_xt DECLSPEC_NORETURN NTAPI xbox::HalReturnToFi
case ReturnFirmwareFatal:
{
xbox::HalWriteSMBusValue(SMBUS_ADDRESS_SYSTEM_MICRO_CONTROLLER, SMC_COMMAND_SCRATCH, 0, SMC_SCRATCH_DISPLAY_FATAL_ERROR);
is_reboot = true;
g_VMManager.SavePersistentMemory();

View file

@ -248,6 +248,14 @@ XBSYSAPI EXPORTNUM(66) xbox::ntstatus_xt NTAPI xbox::IoCreateFile
LOG_FUNC_ARG(Options)
LOG_FUNC_END;
// If we are emulating the Chihiro, we need to hook mbcom, so return an easily identifable handle
if (g_bIsChihiro) {
if (strncmp(ObjectAttributes->ObjectName->Buffer, DriveMbcom.c_str(), DriveMbcom.length()) == 0) {
*FileHandle = CHIHIRO_MBCOM_HANDLE;
return X_STATUS_SUCCESS;
}
}
NativeObjectAttributes nativeObjectAttributes;
// If we are NOT accessing a directory, and we match a partition path, we need to redirect to a partition.bin file
@ -273,6 +281,17 @@ XBSYSAPI EXPORTNUM(66) xbox::ntstatus_xt NTAPI xbox::IoCreateFile
// Force ShareAccess to all
ShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
// Force set DELETE permission flag if write attributes flag is set.
// Testcase: dashupdate.xbe (4928, untested with other versions but newer dashupdate did not call for deletion on fail).
if (DesiredAccess & FILE_WRITE_ATTRIBUTES) {
DesiredAccess |= DELETE;
}
// Force sanitize before call to NtDll::NtCreateFile
// Testcase:
// * Exhibition Demo discs - Attempt to create music folder fail internally which then show unable to copy soundtrack dialog.
FileAttributes &= FILE_ATTRIBUTE_VALID_FLAGS;
if (SUCCEEDED(ret))
{
// redirect to NtCreateFile

View file

@ -96,11 +96,13 @@ namespace NtDll
// TODO : Move towards thread-simulation based Dpc emulation
typedef struct _DpcData {
CRITICAL_SECTION Lock;
HANDLE DpcEvent;
std::atomic_flag IsDpcActive;
std::atomic_flag IsDpcPending;
xbox::LIST_ENTRY DpcQueue; // TODO : Use KeGetCurrentPrcb()->DpcListHead instead
} DpcData;
DpcData g_DpcData = { 0 }; // Note : g_DpcData is initialized in InitDpcThread()
DpcData g_DpcData = { 0 }; // Note : g_DpcData is initialized in InitDpcData()
std::atomic_flag xbox::KeSystemTimeChanged;
xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value)
{
@ -130,6 +132,33 @@ xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value)
break; \
}
xbox::void_xt xbox::KeResumeThreadEx
(
IN PKTHREAD Thread
)
{
// This is only to be used to synchronize new thread creation with the thread that spawned it
Thread->SuspendSemaphore.Header.SignalState = 1;
KiWaitListLock();
KiWaitTest(&Thread->SuspendSemaphore, 0);
}
xbox::void_xt xbox::KeSuspendThreadEx
(
IN PKTHREAD Thread
)
{
// This is only to be used to synchronize new thread creation with the thread that spawned it
Thread->SuspendSemaphore.Header.SignalState = 0;
KiInsertQueueApc(&Thread->SuspendApc, 0);
}
xbox::void_xt xbox::KeWaitForDpc()
{
g_DpcData.IsDpcPending.wait(false);
}
// ******************************************************************
// * EmuKeGetPcr()
@ -166,7 +195,7 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime
)
{
KIRQL OldIrql, OldIrql2;
LARGE_INTEGER DeltaTime, HostTime;
LARGE_INTEGER DeltaTime;
PLIST_ENTRY ListHead, NextEntry;
PKTIMER Timer;
LIST_ENTRY TempList, TempList2;
@ -184,10 +213,6 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime
/* Query the system time now */
KeQuerySystemTime(OldTime);
/* Surely, we won't set the system time here, but we CAN remember a delta to the host system time */
HostTime.QuadPart = OldTime->QuadPart - HostSystemTimeDelta.load();
HostSystemTimeDelta = NewTime->QuadPart - HostTime.QuadPart;
/* Calculate the difference between the new and the old time */
DeltaTime.QuadPart = NewTime->QuadPart - OldTime->QuadPart;
@ -246,7 +271,7 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime
}
}
/* Process expired timers. This releases the dispatcher and timer locks */
/* Process expired timers. This releases the dispatcher and timer locks, then it yields */
KiTimerListExpire(&TempList2, OldIrql);
}
@ -460,8 +485,11 @@ void ExecuteDpcQueue()
// Mark it as no longer linked into the DpcQueue
pkdpc->Inserted = FALSE;
// Set DpcRoutineActive to support KeIsExecutingDpc:
g_DpcData.IsDpcActive.test_and_set();
KeGetCurrentPrcb()->DpcRoutineActive = TRUE; // Experimental
EmuLog(LOG_LEVEL::DEBUG, "Global DpcQueue, calling DPC at 0x%.8X", pkdpc->DeferredRoutine);
LeaveCriticalSection(&(g_DpcData.Lock));
EmuLog(LOG_LEVEL::DEBUG, "Global DpcQueue, calling DPC object 0x%.8X at 0x%.8X", pkdpc, pkdpc->DeferredRoutine);
// Call the Deferred Procedure :
pkdpc->DeferredRoutine(
@ -470,23 +498,30 @@ void ExecuteDpcQueue()
pkdpc->SystemArgument1,
pkdpc->SystemArgument2);
EnterCriticalSection(&(g_DpcData.Lock));
KeGetCurrentPrcb()->DpcRoutineActive = FALSE; // Experimental
g_DpcData.IsDpcActive.clear();
}
g_DpcData.IsDpcPending.clear();
// Assert(g_DpcData._dwThreadId == GetCurrentThreadId());
// Assert(g_DpcData._dwDpcThreadId == g_DpcData._dwThreadId);
// g_DpcData._dwDpcThreadId = 0;
LeaveCriticalSection(&(g_DpcData.Lock));
}
void InitDpcThread()
void InitDpcData()
{
DWORD dwThreadId = 0;
// Let's initialize the Dpc handling thread too,
// here for now (should be called by our caller)
InitializeCriticalSection(&(g_DpcData.Lock));
InitializeListHead(&(g_DpcData.DpcQueue));
EmuLogEx(CXBXR_MODULE::INIT, LOG_LEVEL::DEBUG, "Creating DPC event\n");
g_DpcData.DpcEvent = CreateEvent(/*lpEventAttributes=*/nullptr, /*bManualReset=*/FALSE, /*bInitialState=*/FALSE, /*lpName=*/nullptr);
}
bool IsDpcActive()
{
return g_DpcData.IsDpcActive.test();
}
static constexpr uint32_t XBOX_TSC_FREQUENCY = 733333333; // Xbox Time Stamp Counter Frequency = 733333333 (CPU Clock)
@ -498,13 +533,6 @@ ULONGLONG CxbxGetPerformanceCounter(bool acpi)
return Timer_GetScaledPerformanceCounter(period);
}
void CxbxInitPerformanceCounters()
{
// Let's initialize the Dpc handling thread too,
// here for now (should be called by our caller)
InitDpcThread();
}
// ******************************************************************
// * 0x005C - KeAlertResumeThread()
// ******************************************************************
@ -715,7 +743,13 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread
// We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well
// Test case: Metal Slug 3
xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional<ntstatus_xt> {
PKTHREAD kThread = KeGetCurrentThread();
kThread->WaitStatus = X_STATUS_SUCCESS;
if (!AddWaitObject(kThread, Interval)) {
RETURN(X_STATUS_TIMEOUT);
}
xbox::ntstatus_xt ret = WaitApc<true>([Alertable](xbox::PKTHREAD kThread) -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime);
@ -723,8 +757,11 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread
if (Status >= 0 && Status != STATUS_ALERTED && Status != STATUS_USER_APC) {
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, Interval, Alertable, WaitMode);
// If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added
// to the thread. Test case: Steel Battalion
xbox::KiUnwaitThreadAndLock(kThread, Status, 0);
return std::make_optional<ntstatus_xt>(kThread->WaitStatus);
}, Interval, Alertable, WaitMode, kThread);
if (ret == X_STATUS_TIMEOUT) {
// NOTE: this function considers a timeout a success
@ -1206,35 +1243,9 @@ XBSYSAPI EXPORTNUM(118) xbox::boolean_xt NTAPI xbox::KeInsertQueueApc
Apc->SystemArgument1 = SystemArgument1;
Apc->SystemArgument2 = SystemArgument2;
if (Apc->Inserted) {
KfLowerIrql(OldIrql);
RETURN(FALSE);
}
else {
KiApcListMtx.lock();
InsertTailList(&kThread->ApcState.ApcListHead[Apc->ApcMode], &Apc->ApcListEntry);
Apc->Inserted = TRUE;
KiApcListMtx.unlock();
// We can only attempt to execute the queued apc right away if it is been inserted in the current thread, because otherwise the KTHREAD
// in the fs selector will not be correct
if (kThread == KeGetCurrentThread()) {
if (Apc->ApcMode == KernelMode) { // kernel apc
// NOTE: this is wrong, we should check the thread state instead of just signaling the kernel apc, but we currently
// don't set the appropriate state in kthread
kThread->ApcState.KernelApcPending = TRUE;
KiExecuteKernelApc();
}
else if ((kThread->WaitMode == UserMode) && (kThread->Alertable)) { // user apc
// NOTE: this should also check the thread state
kThread->ApcState.UserApcPending = TRUE;
KiExecuteUserApc();
}
}
KfLowerIrql(OldIrql);
RETURN(TRUE);
}
boolean_xt result = KiInsertQueueApc(Apc, Increment);
KfLowerIrql(OldIrql);
RETURN(result);
}
}
@ -1266,16 +1277,25 @@ XBSYSAPI EXPORTNUM(119) xbox::boolean_xt NTAPI xbox::KeInsertQueueDpc
Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2;
InsertTailList(&(g_DpcData.DpcQueue), &(Dpc->DpcListEntry));
LeaveCriticalSection(&(g_DpcData.Lock));
g_DpcData.IsDpcPending.test_and_set();
g_DpcData.IsDpcPending.notify_one();
// TODO : Instead of DpcQueue, add the DPC to KeGetCurrentPrcb()->DpcListHead
// Signal the Dpc handling code there's work to do
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
if (!IsDpcActive()) {
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
}
// OpenXbox has this instead:
// if (!pKPRCB->DpcRoutineActive && !pKPRCB->DpcInterruptRequested) {
// pKPRCB->DpcInterruptRequested = TRUE;
}
else {
LeaveCriticalSection(&(g_DpcData.Lock));
}
// Thread-safety is no longer required anymore
LeaveCriticalSection(&(g_DpcData.Lock));
// TODO : Instead, enable interrupts - use KeLowerIrql(OldIrql) ?
RETURN(NeedsInsertion);
@ -1289,7 +1309,12 @@ XBSYSAPI EXPORTNUM(121) xbox::boolean_xt NTAPI xbox::KeIsExecutingDpc
{
LOG_FUNC();
#if 0
// This is the correct implementation, but it doesn't work because our Prcb is per-thread instead of being per-processor
BOOLEAN ret = (BOOLEAN)KeGetCurrentPrcb()->DpcRoutineActive;
#else
BOOLEAN ret = (BOOLEAN)IsDpcActive();
#endif
RETURN(ret);
}
@ -1346,13 +1371,14 @@ XBSYSAPI EXPORTNUM(123) xbox::long_xt NTAPI xbox::KePulseEvent
}
LONG OldState = Event->Header.SignalState;
KiWaitListLock();
if ((OldState == 0) && (IsListEmpty(&Event->Header.WaitListHead) == FALSE)) {
Event->Header.SignalState = 1;
// TODO: KiWaitTest(Event, Increment);
// For now, we just sleep to give other threads time to wake
// KiWaitTest and related functions require correct thread scheduling to implement first
// This will have to wait until CPU emulation at v1.0
Sleep(1);
KiWaitTest(Event, Increment);
std::this_thread::yield();
}
else {
KiWaitListUnlock();
}
Event->Header.SignalState = 0;
@ -1378,9 +1404,7 @@ XBSYSAPI EXPORTNUM(124) xbox::long_xt NTAPI xbox::KeQueryBasePriorityThread
KIRQL OldIrql;
KiLockDispatcherDatabase(&OldIrql);
// It cannot fail because all thread handles are created by ob
const auto& nativeHandle = GetNativeHandle<true>(PspGetCurrentThread()->UniqueThread);
long_xt ret = GetThreadPriority(*nativeHandle);
long_xt ret = Thread->Priority;
KiUnlockDispatcherDatabase(OldIrql);
@ -1533,12 +1557,14 @@ XBSYSAPI EXPORTNUM(132) xbox::long_xt NTAPI xbox::KeReleaseSemaphore
}
Semaphore->Header.SignalState = adjusted_signalstate;
//TODO: Implement KiWaitTest
#if 0
KiWaitListLock();
if ((initial_state == 0) && (IsListEmpty(&Semaphore->Header.WaitListHead) == FALSE)) {
KiWaitTest(&Semaphore->Header, Increment);
std::this_thread::yield();
}
else {
KiWaitListUnlock();
}
#endif
if (Wait) {
PKTHREAD current_thread = KeGetCurrentThread();
@ -1752,11 +1778,29 @@ XBSYSAPI EXPORTNUM(140) xbox::ulong_xt NTAPI xbox::KeResumeThread
{
LOG_FUNC_ONE_ARG(Thread);
NTSTATUS ret = X_STATUS_SUCCESS;
KIRQL OldIrql;
KiLockDispatcherDatabase(&OldIrql);
LOG_UNIMPLEMENTED();
char_xt OldCount = Thread->SuspendCount;
if (OldCount != 0) {
--Thread->SuspendCount;
if (Thread->SuspendCount == 0) {
#if 0
++Thread->SuspendSemaphore.Header.SignalState;
KiWaitListLock();
KiWaitTest(&Thread->SuspendSemaphore, 0);
std::this_thread::yield();
#else
if (const auto &nativeHandle = GetNativeHandle<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread)) {
ResumeThread(*nativeHandle);
}
#endif
}
}
RETURN(ret);
KiUnlockDispatcherDatabase(OldIrql);
RETURN(OldCount);
}
XBSYSAPI EXPORTNUM(141) xbox::PLIST_ENTRY NTAPI xbox::KeRundownQueue
@ -1798,25 +1842,15 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG_OUT(Thread)
LOG_FUNC_ARG_OUT(Priority)
LOG_FUNC_ARG(Thread)
LOG_FUNC_ARG(Priority)
LOG_FUNC_END;
KIRQL oldIRQL;
KiLockDispatcherDatabase(&oldIRQL);
// It cannot fail because all thread handles are created by ob
const auto &nativeHandle = GetNativeHandle<true>(PspGetCurrentThread()->UniqueThread);
LONG ret = GetThreadPriority(*nativeHandle);
// This would work normally, but it will slow down the emulation,
// don't do that if the priority is higher then normal (so our own)!
if (Priority <= THREAD_PRIORITY_NORMAL) {
BOOL result = SetThreadPriority(*nativeHandle, Priority);
if (!result) {
EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str());
}
}
Thread->Priority = Priority;
long_xt ret = Thread->Priority;
KiUnlockDispatcherDatabase(oldIRQL);
@ -1837,17 +1871,9 @@ XBSYSAPI EXPORTNUM(144) xbox::boolean_xt NTAPI xbox::KeSetDisableBoostThread
KIRQL oldIRQL;
KiLockDispatcherDatabase(&oldIRQL);
// It cannot fail because all thread handles are created by ob
const auto &nativeHandle = GetNativeHandle<true>(PspGetCurrentThread()->UniqueThread);
boolean_xt prevDisableBoost = Thread->DisableBoost;
Thread->DisableBoost = (CHAR)Disable;
BOOL bRet = SetThreadPriorityBoost(*nativeHandle, Disable);
if (!bRet) {
EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost failed: %s", WinError2Str().c_str());
}
KiUnlockDispatcherDatabase(oldIRQL);
RETURN(prevDisableBoost);
@ -1882,7 +1908,9 @@ XBSYSAPI EXPORTNUM(145) xbox::long_xt NTAPI xbox::KeSetEvent
}
LONG OldState = Event->Header.SignalState;
KiWaitListLock();
if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) {
KiWaitListUnlock();
Event->Header.SignalState = 1;
} else {
PKWAIT_BLOCK WaitBlock = CONTAINING_RECORD(Event->Header.WaitListHead.Flink, KWAIT_BLOCK, WaitListEntry);
@ -1890,16 +1918,14 @@ XBSYSAPI EXPORTNUM(145) xbox::long_xt NTAPI xbox::KeSetEvent
(WaitBlock->WaitType != WaitAny)) {
if (OldState == 0) {
Event->Header.SignalState = 1;
// TODO: KiWaitTest(Event, Increment);
// For now, we just sleep to give other threads time to wake
// See KePulseEvent
Sleep(1);
KiWaitTest(Event, Increment);
}
else {
KiWaitListUnlock();
}
} else {
// TODO: KiUnwaitThread(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment);
// For now, we just sleep to give other threads time to wake
// See KePulseEvent
Sleep(1);
KiUnwaitThread(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment);
KiWaitListUnlock();
}
}
@ -1936,6 +1962,7 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority
return;
}
KiWaitListLock();
if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) {
Event->Header.SignalState = 1;
} else {
@ -1946,11 +1973,9 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority
}
WaitThread->Quantum = WaitThread->ApcState.Process->ThreadQuantum;
// TODO: KiUnwaitThread(WaitThread, X_STATUS_SUCCESS, 1);
// For now, we just sleep to give other threads time to wake
// See KePulseEvent
Sleep(1);
KiUnwaitThread(WaitThread, X_STATUS_SUCCESS, 1);
}
KiWaitListUnlock();
KiUnlockDispatcherDatabase(OldIrql);
}
@ -1982,8 +2007,8 @@ XBSYSAPI EXPORTNUM(148) xbox::boolean_xt NTAPI xbox::KeSetPriorityThread
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG_OUT(Thread)
LOG_FUNC_ARG_OUT(Priority)
LOG_FUNC_ARG(Thread)
LOG_FUNC_ARG(Priority)
LOG_FUNC_END;
LOG_UNIMPLEMENTED();
@ -2096,11 +2121,38 @@ XBSYSAPI EXPORTNUM(152) xbox::ulong_xt NTAPI xbox::KeSuspendThread
{
LOG_FUNC_ONE_ARG(Thread);
NTSTATUS ret = X_STATUS_SUCCESS;
KIRQL OldIrql;
KiLockDispatcherDatabase(&OldIrql);
LOG_UNIMPLEMENTED();
char_xt OldCount = Thread->SuspendCount;
if (OldCount == X_MAXIMUM_SUSPEND_COUNT) {
KiUnlockDispatcherDatabase(OldIrql);
RETURN(X_STATUS_SUSPEND_COUNT_EXCEEDED);
}
RETURN(ret);
if (Thread->ApcState.ApcQueueable == TRUE) {
++Thread->SuspendCount;
if (OldCount == 0) {
#if 0
if (KiInsertQueueApc(&Thread->SuspendApc, 0) == FALSE) {
--Thread->SuspendSemaphore.Header.SignalState;
}
#else
// JSRF creates a thread at 0x0013BC30 and then it attempts to continuously suspend/resume it. Unfortunately, this thread performs a never ending loop (and
// terminates if it ever exit the loop), and never calls any kernel functions in the middle. This means that our suspend APC will never be executed and so
// we cannot suspend such thread. Thus, we will always have to rely on the host to do the suspension, as long as we do direct execution. Note that this is
// a general issue for all kernel APCs too.
if (const auto &nativeHandle = GetNativeHandle<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread)) {
SuspendThread(*nativeHandle);
}
#endif
}
}
KiUnlockDispatcherDatabase(OldIrql);
RETURN(OldCount);
}
// ******************************************************************
@ -2196,15 +2248,13 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
// Wait Loop
// This loop ends
PLARGE_INTEGER OriginalTime = Timeout;
LARGE_INTEGER DueTime, NewTime;
KWAIT_BLOCK StackWaitBlock;
PKWAIT_BLOCK WaitBlock = &StackWaitBlock;
PKWAIT_BLOCK WaitBlock;
BOOLEAN WaitSatisfied;
NTSTATUS WaitStatus;
PKMUTANT ObjectMutant;
// Hack variable (remove this when the thread scheduler is here)
bool timeout_set = false;
do {
Thread->WaitBlockList = WaitBlockArray;
// Check if we need to let an APC run. This should immediately trigger APC interrupt via a call to UnlockDispatcherDatabase
if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
KiUnlockDispatcherDatabase(Thread->WaitIrql);
@ -2263,7 +2313,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
// Check if the wait can be satisfied immediately
if ((WaitType == WaitAll) && (WaitSatisfied)) {
WaitBlock->NextWaitBlock = &WaitBlockArray[0];
KiWaitSatisfyAll(WaitBlock);
KiWaitSatisfyAllAndLock(WaitBlock);
WaitStatus = (NTSTATUS)Thread->WaitStatus;
goto NoWait;
}
@ -2278,36 +2328,20 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
goto NoWait;
}
// Setup a timer for the thread but only once (for now)
if (!timeout_set) {
KiTimerLock();
PKTIMER Timer = &Thread->Timer;
PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock;
WaitBlock->NextWaitBlock = WaitTimer;
Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;
WaitTimer->NextWaitBlock = WaitBlock;
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
KiTimerUnlock();
goto NoWait;
}
// Boring, ensure that we only set the thread timer once. Otherwise, this will cause to insert the same
// thread timer over and over in the timer list, which will prevent KiTimerExpiration from removing these
// duplicated timers and thus it will attempt to endlessly remove the same unremoved timers, causing a deadlock.
// This can be removed once KiSwapThread and the kernel/user APCs are implemented
timeout_set = true;
DueTime.QuadPart = Timer->DueTime.QuadPart;
// Setup a timer for the thread
KiTimerLock();
PKTIMER Timer = &Thread->Timer;
PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock;
WaitBlock->NextWaitBlock = WaitTimer;
Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;
WaitTimer->NextWaitBlock = WaitBlock;
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
KiTimerUnlock();
}
// KiTimerExpiration has removed the timer but the objects were not signaled, so we have a timeout
// (remove this when the thread scheduler is here)
if (Thread->Timer.Header.Inserted == FALSE) {
WaitStatus = (NTSTATUS)(STATUS_TIMEOUT);
goto NoWait;
}
KiTimerUnlock();
}
else {
WaitBlock->NextWaitBlock = WaitBlock;
@ -2315,12 +2349,13 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
WaitBlock->NextWaitBlock = &WaitBlockArray[0];
WaitBlock = &WaitBlockArray[0];
KiWaitListLock();
do {
ObjectMutant = (PKMUTANT)WaitBlock->Object;
//InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry);
InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry);
WaitBlock = WaitBlock->NextWaitBlock;
} while (WaitBlock != &WaitBlockArray[0]);
KiWaitListUnlock();
/*
TODO: We can't implement this and the return values until we have our own thread scheduler
@ -2328,9 +2363,6 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
This code can all be enabled once we have CPU emulation and our own scheduler in v1.0
*/
// Insert the WaitBlock
//InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry);
// If the current thread is processing a queue object, wake other treads using the same queue
PRKQUEUE Queue = (PRKQUEUE)Thread->Queue;
if (Queue != NULL) {
@ -2342,7 +2374,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
Thread->WaitMode = WaitMode;
Thread->WaitReason = (UCHAR)WaitReason;
Thread->WaitTime = KeTickCount;
//Thread->State = Waiting;
Thread->State = Waiting;
//KiInsertWaitList(WaitMode, Thread);
//WaitStatus = (NTSTATUS)KiSwapThread();
@ -2357,12 +2389,15 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
//}
// TODO: Remove this after we have our own scheduler and the above is implemented
Sleep(0);
WaitStatus = WaitApc<false>([](PKTHREAD Thread) -> std::optional<ntstatus_xt> {
if (Thread->State == Ready) {
// We have been readied to resume execution, so exit the wait
return std::make_optional<ntstatus_xt>(Thread->WaitStatus);
}
return std::nullopt;
}, Timeout, Alertable, WaitMode, Thread);
// Reduce the timout if necessary
if (Timeout != nullptr) {
Timeout = KiComputeWaitInterval(OriginalTime, &DueTime, &NewTime);
}
break;
}
// Raise IRQL to DISPATCH_LEVEL and lock the database (only if it's not already at this level)
@ -2377,10 +2412,14 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
// The waiting thead has been alerted, or an APC needs to be delivered
// So unlock the dispatcher database, lower the IRQ and return the status
Thread->State = Running;
KiUnlockDispatcherDatabase(Thread->WaitIrql);
#if 0
// No need for this at the moment, since WaitApc already executes user APCs
if (WaitStatus == X_STATUS_USER_APC) {
KiExecuteUserApc();
}
#endif
RETURN(WaitStatus);
@ -2389,14 +2428,7 @@ NoWait:
// Unlock the database and return the status
//TODO: KiAdjustQuantumThread(Thread);
// Don't forget to remove the thread timer if the objects were signaled before the timer expired
// (remove this when the thread scheduler is here)
if (timeout_set && Thread->Timer.Header.Inserted == TRUE) {
KiTimerLock();
KxRemoveTreeTimer(&Thread->Timer);
KiTimerUnlock();
}
Thread->State = Running;
KiUnlockDispatcherDatabase(Thread->WaitIrql);
if (WaitStatus == X_STATUS_USER_APC) {
@ -2438,12 +2470,9 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
// Wait Loop
// This loop ends
PLARGE_INTEGER OriginalTime = Timeout;
LARGE_INTEGER DueTime, NewTime;
KWAIT_BLOCK StackWaitBlock;
PKWAIT_BLOCK WaitBlock = &StackWaitBlock;
NTSTATUS WaitStatus;
// Hack variable (remove this when the thread scheduler is here)
bool timeout_set = false;
ntstatus_xt WaitStatus;
do {
// Check if we need to let an APC run. This should immediately trigger APC interrupt via a call to UnlockDispatcherDatabase
if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
@ -2491,36 +2520,20 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
goto NoWait;
}
// Setup a timer for the thread but only once (for now)
if (!timeout_set) {
KiTimerLock();
PKTIMER Timer = &Thread->Timer;
PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock;
WaitBlock->NextWaitBlock = WaitTimer;
Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;
WaitTimer->NextWaitBlock = WaitBlock;
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
KiTimerUnlock();
goto NoWait;
}
// Boring, ensure that we only set the thread timer once. Otherwise, this will cause to insert the same
// thread timer over and over in the timer list, which will prevent KiTimerExpiration from removing these
// duplicated timers and thus it will attempt to endlessly remove the same unremoved timers, causing a deadlock.
// This can be removed once KiSwapThread and the kernel/user APCs are implemented
timeout_set = true;
DueTime.QuadPart = Timer->DueTime.QuadPart;
// Setup a timer for the thread
KiTimerLock();
PKTIMER Timer = &Thread->Timer;
PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock;
WaitBlock->NextWaitBlock = WaitTimer;
Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;
WaitTimer->NextWaitBlock = WaitBlock;
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
KiTimerUnlock();
}
// KiTimerExpiration has removed the timer but the object was not signaled, so we have a timeout
// (remove this when the thread scheduler is here)
if (Thread->Timer.Header.Inserted == FALSE) {
WaitStatus = (NTSTATUS)(STATUS_TIMEOUT);
goto NoWait;
}
KiTimerUnlock();
}
else {
WaitBlock->NextWaitBlock = WaitBlock;
@ -2532,9 +2545,6 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
This code can all be enabled once we have CPU emulation and our own scheduler in v1.0
*/
// Insert the WaitBlock
//InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry);
// If the current thread is processing a queue object, wake other treads using the same queue
PRKQUEUE Queue = (PRKQUEUE)Thread->Queue;
if (Queue != NULL) {
@ -2546,7 +2556,7 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
Thread->WaitMode = WaitMode;
Thread->WaitReason = (UCHAR)WaitReason;
Thread->WaitTime = KeTickCount;
// TODO: Thread->State = Waiting;
Thread->State = Waiting;
//KiInsertWaitList(WaitMode, Thread);
/*
@ -2561,13 +2571,21 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
return WaitStatus;
} */
// TODO: Remove this after we have our own scheduler and the above is implemented
Sleep(0);
// Insert the WaitBlock
KiWaitListLock();
InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry);
KiWaitListUnlock();
// Reduce the timout if necessary
if (Timeout != nullptr) {
Timeout = KiComputeWaitInterval(OriginalTime, &DueTime, &NewTime);
}
// TODO: Remove this after we have our own scheduler and the above is implemented
WaitStatus = WaitApc<false>([](PKTHREAD Thread) -> std::optional<ntstatus_xt> {
if (Thread->State == Ready) {
// We have been readied to resume execution, so exit the wait
return std::make_optional<ntstatus_xt>(Thread->WaitStatus);
}
return std::nullopt;
}, Timeout, Alertable, WaitMode, Thread);
break;
}
// Raise IRQL to DISPATCH_LEVEL and lock the database
@ -2582,10 +2600,14 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
// The waiting thead has been alerted, or an APC needs to be delivered
// So unlock the dispatcher database, lower the IRQ and return the status
Thread->State = Running;
KiUnlockDispatcherDatabase(Thread->WaitIrql);
#if 0
// No need for this at the moment, since WaitApc already executes user APCs
if (WaitStatus == X_STATUS_USER_APC) {
KiExecuteUserApc();
}
#endif
RETURN(WaitStatus);
@ -2594,14 +2616,7 @@ NoWait:
// Unlock the database and return the status
//TODO: KiAdjustQuantumThread(Thread);
// Don't forget to remove the thread timer if the object was signaled before the timer expired
// (remove this when the thread scheduler is here)
if (timeout_set && Thread->Timer.Header.Inserted == TRUE) {
KiTimerLock();
KxRemoveTreeTimer(&Thread->Timer);
KiTimerUnlock();
}
Thread->State = Running;
KiUnlockDispatcherDatabase(Thread->WaitIrql);
if (WaitStatus == X_STATUS_USER_APC) {

View file

@ -27,6 +27,8 @@
namespace xbox
{
extern std::atomic_flag KeSystemTimeChanged;
void_xt NTAPI KeSetSystemTime
(
IN PLARGE_INTEGER NewTime,
@ -50,5 +52,16 @@ namespace xbox
IN PKPROCESS Process
);
xbox::void_xt KeResumeThreadEx
(
IN PKTHREAD Thread
);
xbox::void_xt KeSuspendThreadEx
(
IN PKTHREAD Thread
);
void_xt KeEmptyQueueApc();
void_xt KeWaitForDpc();
}

View file

@ -59,6 +59,8 @@
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
// Also from ReactOS: KiWaitTest, KiWaitSatisfyAll, KiUnwaitThread, KiUnlinkThread
// COPYING file:
/*
GNU GENERAL PUBLIC LICENSE
@ -84,15 +86,18 @@ the said software).
#include "Logging.h" // For LOG_FUNC()
#include "EmuKrnl.h" // for the list support functions
#include "EmuKrnlKi.h"
#include "EmuKrnlKe.h"
#define MAX_TIMER_DPCS 16
#define ASSERT_TIMER_LOCKED assert(KiTimerMtx.Acquired > 0)
#define ASSERT_WAIT_LIST_LOCKED assert(KiWaitListMtx.Acquired > 0)
xbox::KPROCESS KiUniqueProcess;
const xbox::ulong_xt CLOCK_TIME_INCREMENT = 0x2710;
xbox::KDPC KiTimerExpireDpc;
xbox::KI_TIMER_LOCK KiTimerMtx;
xbox::KI_WAIT_LIST_LOCK KiWaitListMtx;
xbox::KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE];
xbox::LIST_ENTRY KiWaitInListHead;
std::mutex xbox::KiApcListMtx;
@ -129,55 +134,81 @@ xbox::void_xt xbox::KiTimerUnlock()
KiTimerMtx.Mtx.unlock();
}
xbox::void_xt xbox::KiClockIsr
(
unsigned int ScalingFactor
)
xbox::void_xt xbox::KiWaitListLock()
{
KIRQL OldIrql;
LARGE_INTEGER InterruptTime;
LARGE_INTEGER HostSystemTime;
KiWaitListMtx.Mtx.lock();
KiWaitListMtx.Acquired++;
}
xbox::void_xt xbox::KiWaitListUnlock()
{
KiWaitListMtx.Acquired--;
KiWaitListMtx.Mtx.unlock();
}
xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs)
{
LARGE_INTEGER InterruptTime, SystemTime;
ULONG Hand;
DWORD OldKeTickCount;
OldIrql = KfRaiseIrql(CLOCK_LEVEL);
static uint64_t LostUs;
uint64_t TotalMs = TotalUs / 1000;
LostUs += (TotalUs - TotalMs * 1000);
uint64_t RecoveredMs = LostUs / 1000;
TotalMs += RecoveredMs;
LostUs -= (RecoveredMs * 1000);
// Update the interrupt time
InterruptTime.u.LowPart = KeInterruptTime.LowPart;
InterruptTime.u.HighPart = KeInterruptTime.High1Time;
InterruptTime.QuadPart += (CLOCK_TIME_INCREMENT * ScalingFactor);
InterruptTime.QuadPart += (CLOCK_TIME_INCREMENT * TotalMs);
KeInterruptTime.High2Time = InterruptTime.u.HighPart;
KeInterruptTime.LowPart = InterruptTime.u.LowPart;
KeInterruptTime.High1Time = InterruptTime.u.HighPart;
// Update the system time
// NOTE: I'm not sure if we should round down the host system time to the nearest multiple
// of the Xbox clock increment...
GetSystemTimeAsFileTime((LPFILETIME)&HostSystemTime);
HostSystemTime.QuadPart += HostSystemTimeDelta.load();
KeSystemTime.High2Time = HostSystemTime.u.HighPart;
KeSystemTime.LowPart = HostSystemTime.u.LowPart;
KeSystemTime.High1Time = HostSystemTime.u.HighPart;
if (KeSystemTimeChanged.test()) [[unlikely]] {
KeSystemTimeChanged.clear();
LARGE_INTEGER HostSystemTime, OldSystemTime;
GetSystemTimeAsFileTime((LPFILETIME)&HostSystemTime);
xbox::KeSystemTime.High2Time = HostSystemTime.u.HighPart;
xbox::KeSystemTime.LowPart = HostSystemTime.u.LowPart;
xbox::KeSystemTime.High1Time = HostSystemTime.u.HighPart;
KeSetSystemTime(&HostSystemTime, &OldSystemTime);
}
else {
SystemTime.u.LowPart = KeSystemTime.LowPart;
SystemTime.u.HighPart = KeSystemTime.High1Time;
SystemTime.QuadPart += (CLOCK_TIME_INCREMENT * TotalMs);
KeSystemTime.High2Time = SystemTime.u.HighPart;
KeSystemTime.LowPart = SystemTime.u.LowPart;
KeSystemTime.High1Time = SystemTime.u.HighPart;
}
// Update the tick counter
OldKeTickCount = KeTickCount;
KeTickCount += ScalingFactor;
KeTickCount += static_cast<dword_xt>(TotalMs);
// Because this function must be fast to continuously update the kernel clocks, if somebody else is currently
// holding the lock, we won't wait and instead skip the check of the timers for this cycle
if (KiTimerMtx.Mtx.try_lock()) {
KiTimerMtx.Acquired++;
// Check if a timer has expired
Hand = OldKeTickCount & (TIMER_TABLE_SIZE - 1);
if (KiTimerTableListHead[Hand].Entry.Flink != &KiTimerTableListHead[Hand].Entry &&
(ULONGLONG)InterruptTime.QuadPart >= KiTimerTableListHead[Hand].Time.QuadPart) {
KeInsertQueueDpc(&KiTimerExpireDpc, (PVOID)Hand, 0);
// On real hw, this is called every ms, so it only needs to check a single timer index. However, testing on the emulator shows that this can have a delay
// larger than a ms. If we only check the index corresponding to OldKeTickCount, then we will miss timers that might have expired already, causing an unpredictable
// delay on threads that are waiting with those timeouts
dword_xt EndKeTickCount = (KeTickCount - OldKeTickCount) >= TIMER_TABLE_SIZE ? OldKeTickCount + TIMER_TABLE_SIZE : KeTickCount;
for (dword_xt i = OldKeTickCount; i < EndKeTickCount; ++i) {
Hand = i & (TIMER_TABLE_SIZE - 1);
if (KiTimerTableListHead[Hand].Entry.Flink != &KiTimerTableListHead[Hand].Entry &&
(ULONGLONG)InterruptTime.QuadPart >= KiTimerTableListHead[Hand].Time.QuadPart) {
KeInsertQueueDpc(&KiTimerExpireDpc, (PVOID)OldKeTickCount, (PVOID)EndKeTickCount);
break;
}
}
KiTimerMtx.Acquired--;
KiTimerMtx.Mtx.unlock();
}
KfLowerIrql(OldIrql);
}
xbox::void_xt NTAPI xbox::KiCheckTimerTable
@ -328,8 +359,8 @@ xbox::boolean_xt FASTCALL xbox::KiInsertTimerTable
IN xbox::ulong_xt Hand
)
{
LARGE_INTEGER InterruptTime;
LONGLONG DueTime = Timer->DueTime.QuadPart;
ULARGE_INTEGER InterruptTime;
ULONGLONG DueTime = Timer->DueTime.QuadPart;
BOOLEAN Expired = FALSE;
PLIST_ENTRY ListHead, NextEntry;
PKTIMER CurrentTimer;
@ -352,7 +383,7 @@ xbox::boolean_xt FASTCALL xbox::KiInsertTimerTable
CurrentTimer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
/* Now check if we can fit it before */
if ((ULONGLONG)DueTime >= CurrentTimer->DueTime.QuadPart) break;
if (DueTime >= CurrentTimer->DueTime.QuadPart) break;
/* Keep looping */
NextEntry = NextEntry->Blink;
@ -368,6 +399,10 @@ xbox::boolean_xt FASTCALL xbox::KiInsertTimerTable
KiTimerTableListHead[Hand].Time.QuadPart = DueTime;
/* Make sure it hasn't expired already */
// NOTE: DueTime must be unsigned so that we can perform un unsigned comparison with the interrupt time. Otherwise, if DueTime is very large, it will be
// interpreted as a very small negative number, which will cause the function to think the timer has already expired, when it didn't. Test case: Metal Slug 3.
// It uses KeDelayExecutionThread with a relative timeout of 0x8000000000000000, which is then interpreted here as a negative number that immediately satisfies
// the wait. The title crashes shortly after, since the wait was supposed to end with a user APC queued by NtQueueApcThread instead
InterruptTime.QuadPart = KeQueryInterruptTime();
if (DueTime <= InterruptTime.QuadPart) {
EmuLog(LOG_LEVEL::DEBUG, "Timer %p already expired", Timer);
@ -484,26 +519,20 @@ xbox::boolean_xt FASTCALL xbox::KiSignalTimer
Timer->Header.SignalState = TRUE;
/* Check if the timer has waiters */
KiWaitListLock();
if (!IsListEmpty(&Timer->Header.WaitListHead))
{
/* Check the type of event */
if (Timer->Header.Type == TimerNotificationObject)
{
/* Unwait the thread */
// KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT);
}
else
{
/* Otherwise unwait the thread and signal the timer */
// KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT);
}
KiWaitTest(Timer, 0);
}
else {
KiWaitListUnlock();
}
/* Check if we have a period */
if (Period)
{
/* Calculate the interval and insert the timer */
Interval.QuadPart = Int32x32To64(Period, -10000);
Interval.QuadPart = Period * -10000LL;
while (!KiInsertTreeTimer(Timer, Interval));
}
@ -532,7 +561,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
{
ULARGE_INTEGER SystemTime, InterruptTime;
LARGE_INTEGER Interval;
LONG Limit, Index, i;
LONG i;
ULONG Timers, ActiveTimers, DpcCalls;
PLIST_ENTRY ListHead, NextEntry;
KIRQL OldIrql;
@ -544,34 +573,25 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
/* Query system and interrupt time */
KeQuerySystemTime((PLARGE_INTEGER)&SystemTime);
InterruptTime.QuadPart = KeQueryInterruptTime();
Limit = KeTickCount;
/* Get the index of the timer and normalize it */
Index = PtrToLong(SystemArgument1);
if ((Limit - Index) >= TIMER_TABLE_SIZE)
{
/* Normalize it */
Limit = Index + TIMER_TABLE_SIZE - 1;
}
/* Setup index and actual limit */
Index--;
Limit &= (TIMER_TABLE_SIZE - 1);
dword_xt OldKeTickCount = PtrToLong(SystemArgument1);
dword_xt EndKeTickCount = PtrToLong(SystemArgument2);
/* Setup accounting data */
DpcCalls = 0;
Timers = 24;
ActiveTimers = 4;
/* Lock the Database and Raise IRQL */
/* Lock the Database */
KiTimerLock();
KiLockDispatcherDatabase(&OldIrql);
/* Start expiration loop */
do
for (dword_xt i = OldKeTickCount; i < EndKeTickCount; ++i)
{
/* Get the current index */
Index = (Index + 1) & (TIMER_TABLE_SIZE - 1);
dword_xt Index = i & (TIMER_TABLE_SIZE - 1);
/* Get list pointers and loop the list */
ListHead = &KiTimerTableListHead[Index].Entry;
@ -599,26 +619,20 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
Period = Timer->Period;
/* Check if there are any waiters */
KiWaitListLock();
if (!IsListEmpty(&Timer->Header.WaitListHead))
{
/* Check the type of event */
if (Timer->Header.Type == TimerNotificationObject)
{
/* Unwait the thread */
// KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT);
}
else
{
/* Otherwise unwait the thread and signal the timer */
// KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT);
}
KiWaitTest(Timer, 0);
}
else {
KiWaitListUnlock();
}
/* Check if we have a period */
if (Period)
{
/* Calculate the interval and insert the timer */
Interval.QuadPart = Int32x32To64(Period, -10000);
Interval.QuadPart = Period * -10000LL;
while (!KiInsertTreeTimer(Timer, Interval));
}
@ -709,7 +723,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
break;
}
}
} while (Index != Limit);
}
/* Verify the timer table, on a debug kernel only */
if (g_bIsDebugKernel) {
@ -737,17 +751,17 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
);
}
KiTimerUnlock();
/* Lower IRQL if we need to */
if (OldIrql != DISPATCH_LEVEL) {
KfLowerIrql(OldIrql);
}
KiTimerUnlock();
}
else
{
/* Unlock the dispatcher */
KiUnlockDispatcherDatabase(OldIrql);
KiTimerUnlock();
KiUnlockDispatcherDatabase(OldIrql);
}
}
@ -791,26 +805,20 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire
Period = Timer->Period;
/* Check if there's any waiters */
KiWaitListLock();
if (!IsListEmpty(&Timer->Header.WaitListHead))
{
/* Check the type of event */
if (Timer->Header.Type == TimerNotificationObject)
{
/* Unwait the thread */
// KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT);
}
else
{
/* Otherwise unwait the thread and signal the timer */
// KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT);
}
KiWaitTest(Timer, 0);
}
else {
KiWaitListUnlock();
}
/* Check if we have a period */
if (Period)
{
/* Calculate the interval and insert the timer */
Interval.QuadPart = Int32x32To64(Period, -10000);
Interval.QuadPart = Period * -10000LL;
while (!KiInsertTreeTimer(Timer, Interval));
}
@ -826,6 +834,8 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire
}
}
KiTimerUnlock();
/* Check if we still have DPC entries */
if (DpcCalls)
{
@ -849,39 +859,14 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire
/* Lower IRQL */
KfLowerIrql(OldIrql);
KiTimerUnlock();
}
else
{
/* Unlock the dispatcher */
KiUnlockDispatcherDatabase(OldIrql);
KiTimerUnlock();
}
}
xbox::void_xt FASTCALL xbox::KiWaitSatisfyAll
(
IN xbox::PKWAIT_BLOCK WaitBlock
)
{
PKMUTANT Object;
PRKTHREAD Thread;
PKWAIT_BLOCK WaitBlock1;
WaitBlock1 = WaitBlock;
Thread = WaitBlock1->Thread;
do {
if (WaitBlock1->WaitKey != (cshort_xt)STATUS_TIMEOUT) {
Object = (PKMUTANT)WaitBlock1->Object;
KiWaitSatisfyAny(Object, Thread);
}
WaitBlock1 = WaitBlock1->NextWaitBlock;
} while (WaitBlock1 != WaitBlock);
return;
}
template<xbox::MODE ApcMode>
static xbox::void_xt KiExecuteApc()
{
@ -907,12 +892,13 @@ static xbox::void_xt KiExecuteApc()
Apc->Inserted = FALSE;
xbox::KiApcListMtx.unlock();
// NOTE: we never use KernelRoutine because that is only used for kernel APCs, which we currently don't use
// This is either KiFreeUserApc, which frees the memory of the apc, or KiSuspendNop, which does nothing
(Apc->KernelRoutine)(Apc, &Apc->NormalRoutine, &Apc->NormalContext, &Apc->SystemArgument1, &Apc->SystemArgument2);
if (Apc->NormalRoutine != xbox::zeroptr) {
(Apc->NormalRoutine)(Apc->NormalContext, Apc->SystemArgument1, Apc->SystemArgument2);
}
xbox::ExFreePool(Apc);
xbox::KiApcListMtx.lock();
}
@ -966,7 +952,8 @@ xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval
}
// Source: ReactOS
xbox::void_xt NTAPI xbox::KiSuspendNop(
xbox::void_xt NTAPI xbox::KiSuspendNop
(
IN PKAPC Apc,
IN PKNORMAL_ROUTINE* NormalRoutine,
IN PVOID* NormalContext,
@ -974,7 +961,7 @@ xbox::void_xt NTAPI xbox::KiSuspendNop(
IN PVOID* SystemArgument2
)
{
/* Does nothing */
/* Does nothing because the memory of the suspend apc is part of kthread */
UNREFERENCED_PARAMETER(Apc);
UNREFERENCED_PARAMETER(NormalRoutine);
UNREFERENCED_PARAMETER(NormalContext);
@ -982,8 +969,21 @@ xbox::void_xt NTAPI xbox::KiSuspendNop(
UNREFERENCED_PARAMETER(SystemArgument2);
}
xbox::void_xt NTAPI xbox::KiFreeUserApc
(
IN PKAPC Apc,
IN PKNORMAL_ROUTINE *NormalRoutine,
IN PVOID *NormalContext,
IN PVOID *SystemArgument1,
IN PVOID *SystemArgument2
)
{
ExFreePool(Apc);
}
// Source: ReactOS
xbox::void_xt NTAPI xbox::KiSuspendThread(
xbox::void_xt NTAPI xbox::KiSuspendThread
(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
@ -1076,3 +1076,210 @@ xbox::void_xt xbox::KiInitializeContextThread(
/* Save back the new value of the kernel stack. */
Thread->KernelStack = reinterpret_cast<PVOID>(CtxSwitchFrame);
}
xbox::boolean_xt xbox::KiInsertQueueApc
(
IN PRKAPC Apc,
IN KPRIORITY Increment
)
{
PKTHREAD kThread = Apc->Thread;
KiApcListMtx.lock();
if (Apc->Inserted) {
KiApcListMtx.unlock();
return FALSE;
}
InsertTailList(&kThread->ApcState.ApcListHead[Apc->ApcMode], &Apc->ApcListEntry);
Apc->Inserted = TRUE;
KiApcListMtx.unlock();
// We can only attempt to execute the queued apc right away if it is been inserted in the current thread, because otherwise the KTHREAD
// in the fs selector will not be correct
if (Apc->ApcMode == KernelMode) { // kernel apc
kThread->ApcState.KernelApcPending = TRUE;
// NOTE: this is wrong, we should check the thread state instead of just signaling the kernel apc, but we currently
// don't set the appropriate state in kthread
if (kThread == KeGetCurrentThread()) {
KiExecuteKernelApc();
}
}
else if ((kThread->WaitMode == UserMode) && (kThread->Alertable)) { // user apc
kThread->ApcState.UserApcPending = TRUE;
// NOTE: this should also check the thread state
if (kThread == KeGetCurrentThread()) {
KiExecuteUserApc();
}
}
return TRUE;
}
xbox::void_xt xbox::KiWaitTest
(
IN PVOID Object,
IN KPRIORITY Increment
)
{
PLIST_ENTRY WaitEntry, WaitList;
PKWAIT_BLOCK WaitBlock, NextBlock;
PKTHREAD WaitThread;
PKMUTANT FirstObject = (PKMUTANT)Object;
ASSERT_WAIT_LIST_LOCKED;
/* Loop the Wait Entries */
WaitList = &FirstObject->Header.WaitListHead;
WaitEntry = WaitList->Flink;
while ((FirstObject->Header.SignalState > 0) && (WaitEntry != WaitList)) {
/* Get the current wait block */
WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry);
WaitThread = WaitBlock->Thread;
/* Check the current Wait Mode */
if (WaitBlock->WaitType == WaitAny) {
/* Easy case, satisfy only this wait */
KiWaitSatisfyAny(FirstObject, WaitThread);
}
else {
/* WaitAll, check that all the objects are signalled */
NextBlock = WaitBlock->NextWaitBlock;
while (NextBlock != WaitBlock) {
if (NextBlock->WaitKey != X_STATUS_TIMEOUT) {
PKMUTANT Mutant = (PKMUTANT)NextBlock->Object;
// NOTE: we ignore mutants because we forward them to ntdll
if (Mutant->Header.SignalState <= 0) {
// We found at least one object not in the signalled state, so we cannot satisfy the wait
goto NextWaitEntry;
}
}
NextBlock = NextBlock->NextWaitBlock;
}
KiWaitSatisfyAll(WaitBlock);
}
/* Now do the rest of the unwait */
KiUnwaitThread(WaitThread, WaitBlock->WaitKey, Increment);
NextWaitEntry:
WaitEntry = WaitEntry->Flink;
}
KiWaitListUnlock();
}
xbox::void_xt xbox::KiWaitSatisfyAll
(
IN PKWAIT_BLOCK FirstBlock
)
{
PKWAIT_BLOCK WaitBlock = FirstBlock;
PKTHREAD WaitThread = WaitBlock->Thread;
ASSERT_WAIT_LIST_LOCKED;
/* Loop through all the Wait Blocks, and wake each Object */
do {
/* Make sure it hasn't timed out */
if (WaitBlock->WaitKey != X_STATUS_TIMEOUT) {
/* Wake the Object */
KiWaitSatisfyAny((PKMUTANT)WaitBlock->Object, WaitThread);
}
/* Move to the next block */
WaitBlock = WaitBlock->NextWaitBlock;
} while (WaitBlock != FirstBlock);
}
xbox::void_xt xbox::KiWaitSatisfyAllAndLock
(
IN PKWAIT_BLOCK FirstBlock
)
{
KiWaitListLock();
KiWaitSatisfyAll(FirstBlock);
KiWaitListUnlock();
}
xbox::void_xt xbox::KiUnwaitThread
(
IN PKTHREAD Thread,
IN long_ptr_xt WaitStatus,
IN KPRIORITY Increment
)
{
ASSERT_WAIT_LIST_LOCKED;
if (Thread->State != Waiting) {
// Don't do anything if it was already unwaited
return;
}
/* Unlink the thread */
KiUnlinkThread(Thread, WaitStatus);
// We cannot schedule the thread, so we'll just set its state to Ready
Thread->State = Ready;
}
xbox::void_xt xbox::KiUnwaitThreadAndLock
(
IN PKTHREAD Thread,
IN long_ptr_xt WaitStatus,
IN KPRIORITY Increment
)
{
KiWaitListLock();
KiUnwaitThread(Thread, WaitStatus, Increment);
KiWaitListUnlock();
}
xbox::void_xt xbox::KiUnlinkThread
(
IN PKTHREAD Thread,
IN long_ptr_xt WaitStatus
)
{
PKWAIT_BLOCK WaitBlock;
PKTIMER Timer;
ASSERT_WAIT_LIST_LOCKED;
/* Update wait status */
Thread->WaitStatus |= WaitStatus;
/* Remove the Wait Blocks from the list */
WaitBlock = Thread->WaitBlockList;
do {
/* Remove it */
RemoveEntryList(&WaitBlock->WaitListEntry);
/* Go to the next one */
WaitBlock = WaitBlock->NextWaitBlock;
} while (WaitBlock != Thread->WaitBlockList);
#if 0
// Disabled, as we currently don't put threads in the ready list
/* Remove the thread from the wait list! */
if (Thread->WaitListEntry.Flink) {
RemoveEntryList(&Thread->WaitListEntry);
}
#endif
/* Check if there's a Thread Timer */
Timer = &Thread->Timer;
if (Timer->Header.Inserted) {
KiTimerLock();
KxRemoveTreeTimer(Timer);
KiTimerUnlock();
}
#if 0
// Disabled, because we don't support queues
/* Increment the Queue's active threads */
if (Thread->Queue) Thread->Queue->CurrentCount++;
#endif
// Sanity check: set WaitBlockList to nullptr so that we can catch the case where a waiter starts a new wait but forgets to setup a new wait block. This
// way, we will crash instead of silently using the pointer to the old block
Thread->WaitBlockList = zeroptr;
}

View file

@ -47,6 +47,12 @@ namespace xbox
int Acquired;
} KI_TIMER_LOCK;
typedef struct _KI_WAIT_LIST_LOCK
{
std::recursive_mutex Mtx;
int Acquired;
} KI_WAIT_LIST_LOCK;
// NOTE: since the apc list is per-thread, we could also create a different mutex for each kthread
extern std::mutex KiApcListMtx;
@ -56,10 +62,11 @@ namespace xbox
void_xt KiTimerUnlock();
void_xt KiClockIsr
(
IN unsigned int ScalingFactor
);
void_xt KiWaitListLock();
void_xt KiWaitListUnlock();
void_xt KiClockIsr(ulonglong_xt TotalUs);
xbox::void_xt NTAPI KiCheckTimerTable
(
@ -132,7 +139,7 @@ namespace xbox
IN KIRQL OldIrql
);
void_xt FASTCALL KiWaitSatisfyAll
void_xt KiWaitSatisfyAll
(
IN PKWAIT_BLOCK WaitBlock
);
@ -156,7 +163,8 @@ namespace xbox
);
// Source: ReactOS
void_xt NTAPI KiSuspendNop(
void_xt NTAPI KiSuspendNop
(
IN PKAPC Apc,
IN PKNORMAL_ROUTINE* NormalRoutine,
IN PVOID* NormalContext,
@ -164,6 +172,15 @@ namespace xbox
IN PVOID* SystemArgument2
);
void_xt NTAPI KiFreeUserApc
(
IN PKAPC Apc,
IN PKNORMAL_ROUTINE *NormalRoutine,
IN PVOID *NormalContext,
IN PVOID *SystemArgument1,
IN PVOID *SystemArgument2
);
// Source: ReactOS
void_xt NTAPI KiSuspendThread(
IN PVOID NormalContext,
@ -180,6 +197,48 @@ namespace xbox
IN PKSTART_ROUTINE StartRoutine,
IN PVOID StartContext
);
boolean_xt KiInsertQueueApc
(
IN PRKAPC Apc,
IN KPRIORITY Increment
);
void_xt KiWaitTest
(
IN PVOID Object,
IN KPRIORITY Increment
);
void_xt KiWaitSatisfyAll
(
IN PKWAIT_BLOCK FirstBlock
);
void_xt KiWaitSatisfyAllAndLock
(
IN PKWAIT_BLOCK FirstBlock
);
void_xt KiUnwaitThread
(
IN PKTHREAD Thread,
IN long_ptr_xt WaitStatus,
IN KPRIORITY Increment
);
void_xt KiUnwaitThreadAndLock
(
IN PKTHREAD Thread,
IN long_ptr_xt WaitStatus,
IN KPRIORITY Increment
);
void_xt KiUnlinkThread
(
IN PKTHREAD Thread,
IN long_ptr_xt WaitStatus
);
};
extern xbox::KPROCESS KiUniqueProcess;

View file

@ -47,6 +47,7 @@ namespace NtDll
#include "core\kernel\support\EmuFile.h" // For EmuNtSymbolicLinkObject, NtStatusToString(), etc.
#include "core\kernel\memory-manager\VMManager.h" // For g_VMManager
#include "core\kernel\support\NativeHandle.h"
#include "devices\Xbox.h"
#include "CxbxDebugger.h"
#pragma warning(disable:4005) // Ignore redefined status values
@ -58,7 +59,7 @@ namespace NtDll
#include <mutex>
// Prevent setting the system time from multiple threads at the same time
std::mutex NtSystemTimeMtx;
xbox::RTL_CRITICAL_SECTION xbox::NtSystemTimeCritSec;
// ******************************************************************
// * 0x00B8 - NtAllocateVirtualMemory()
@ -1039,7 +1040,7 @@ XBSYSAPI EXPORTNUM(206) xbox::ntstatus_xt NTAPI xbox::NtQueueApcThread
PKAPC Apc = static_cast<PKAPC>(ExAllocatePoolWithTag(sizeof(KAPC), 'pasP'));
if (Apc != zeroptr) {
KeInitializeApc(Apc, &Thread->Tcb, zeroptr, zeroptr, reinterpret_cast<PKNORMAL_ROUTINE>(ApcRoutine), UserMode, ApcRoutineContext);
KeInitializeApc(Apc, &Thread->Tcb, KiFreeUserApc, zeroptr, reinterpret_cast<PKNORMAL_ROUTINE>(ApcRoutine), UserMode, ApcRoutineContext);
if (!KeInsertQueueApc(Apc, ApcStatusBlock, ApcReserved, 0)) {
ExFreePool(Apc);
result = X_STATUS_UNSUCCESSFUL;
@ -1711,6 +1712,16 @@ XBSYSAPI EXPORTNUM(219) xbox::ntstatus_xt NTAPI xbox::NtReadFile
CxbxDebugger::ReportFileRead(FileHandle, Length, Offset);
}
// If we are emulating the Chihiro, we need to hook mbcom
if (g_bIsChihiro && FileHandle == CHIHIRO_MBCOM_HANDLE) {
g_MediaBoard->ComRead(ByteOffset->QuadPart, Buffer, Length);
// Update the Status Block
IoStatusBlock->Status = STATUS_SUCCESS;
IoStatusBlock->Information = Length;
return STATUS_SUCCESS;
}
if (ApcRoutine != nullptr) {
// Pack the original parameters to a wrapped context for a custom APC routine
CxbxIoDispatcherContext* cxbxContext = new CxbxIoDispatcherContext(IoStatusBlock, ApcRoutine, ApcContext);
@ -1856,15 +1867,20 @@ XBSYSAPI EXPORTNUM(224) xbox::ntstatus_xt NTAPI xbox::NtResumeThread
LOG_FUNC_ARG_OUT(PreviousSuspendCount)
LOG_FUNC_END;
if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) {
// Thread handles are created by ob
RETURN(NtDll::NtResumeThread(*nativeHandle, (::PULONG)PreviousSuspendCount));
}
else {
RETURN(X_STATUS_INVALID_HANDLE);
PETHREAD Thread;
ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast<PVOID *>(&Thread));
if (!X_NT_SUCCESS(result)) {
RETURN(result);
}
// TODO : Once we do our own thread-switching, implement NtResumeThread using KetResumeThread
ulong_xt PrevSuspendCount = KeResumeThread(&Thread->Tcb);
ObfDereferenceObject(Thread);
if (PreviousSuspendCount) {
*PreviousSuspendCount = PrevSuspendCount;
}
RETURN(X_STATUS_SUCCESS);
}
// ******************************************************************
@ -1971,7 +1987,7 @@ XBSYSAPI EXPORTNUM(228) xbox::ntstatus_xt NTAPI xbox::NtSetSystemTime
ret = STATUS_ACCESS_VIOLATION;
}
else {
NtSystemTimeMtx.lock();
RtlEnterCriticalSectionAndRegion(&NtSystemTimeCritSec);
NewSystemTime = *SystemTime;
if (NewSystemTime.u.HighPart > 0 && NewSystemTime.u.HighPart <= 0x20000000) {
/* Convert the time and set it in HAL */
@ -1991,7 +2007,7 @@ XBSYSAPI EXPORTNUM(228) xbox::ntstatus_xt NTAPI xbox::NtSetSystemTime
else {
ret = STATUS_INVALID_PARAMETER;
}
NtSystemTimeMtx.unlock();
RtlLeaveCriticalSectionAndRegion(&NtSystemTimeCritSec);
}
RETURN(ret);
@ -2079,15 +2095,30 @@ XBSYSAPI EXPORTNUM(231) xbox::ntstatus_xt NTAPI xbox::NtSuspendThread
LOG_FUNC_ARG_OUT(PreviousSuspendCount)
LOG_FUNC_END;
if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) {
// Thread handles are created by ob
RETURN(NtDll::NtSuspendThread(*nativeHandle, (::PULONG)PreviousSuspendCount));
}
else {
RETURN(X_STATUS_INVALID_HANDLE);
PETHREAD Thread;
ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast<PVOID *>(&Thread));
if (!X_NT_SUCCESS(result)) {
RETURN(result);
}
// TODO : Once we do our own thread-switching, implement NtSuspendThread using KeSuspendThread
if (Thread != PspGetCurrentThread()) {
if (Thread->Tcb.HasTerminated) {
ObfDereferenceObject(Thread);
RETURN(X_STATUS_THREAD_IS_TERMINATING);
}
}
ulong_xt PrevSuspendCount = KeSuspendThread(&Thread->Tcb);
ObfDereferenceObject(Thread);
if (PrevSuspendCount == X_STATUS_SUSPEND_COUNT_EXCEEDED) {
RETURN(X_STATUS_SUSPEND_COUNT_EXCEEDED);
}
if (PreviousSuspendCount) {
*PreviousSuspendCount = PrevSuspendCount;
}
RETURN(X_STATUS_SUCCESS);
}
// ******************************************************************
@ -2201,15 +2232,23 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx
if (const auto &nativeHandle = GetNativeHandle(Handles[i])) {
// This is a ob handle, so replace it with its native counterpart
nativeHandles[i] = *nativeHandle;
EmuLog(LOG_LEVEL::DEBUG, "xbox handle: %p", nativeHandles[i]);
}
else {
nativeHandles[i] = Handles[i];
EmuLog(LOG_LEVEL::DEBUG, "native handle: %p", nativeHandles[i]);
}
}
// Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves
xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional<ntstatus_xt> {
PKTHREAD kThread = KeGetCurrentThread();
kThread->WaitStatus = X_STATUS_SUCCESS;
if (!AddWaitObject(kThread, Timeout)) {
RETURN(X_STATUS_TIMEOUT);
}
xbox::ntstatus_xt ret = WaitApc<true>([Count, &nativeHandles, WaitType, Alertable](xbox::PKTHREAD kThread) -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtWaitForMultipleObjects(
@ -2221,8 +2260,11 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx
if (Status == STATUS_TIMEOUT) {
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, Timeout, Alertable, WaitMode);
// If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added
// to the thread. Test case: Steel Battalion
xbox::KiUnwaitThreadAndLock(kThread, Status, 0);
return std::make_optional<ntstatus_xt>(kThread->WaitStatus);
}, Timeout, Alertable, WaitMode, kThread);
RETURN(ret);
}
@ -2269,6 +2311,16 @@ XBSYSAPI EXPORTNUM(236) xbox::ntstatus_xt NTAPI xbox::NtWriteFile
CxbxDebugger::ReportFileWrite(FileHandle, Length, Offset);
}
// If we are emulating the Chihiro, we need to hook mbcom
if (g_bIsChihiro && FileHandle == CHIHIRO_MBCOM_HANDLE) {
g_MediaBoard->ComWrite(ByteOffset->QuadPart, Buffer, Length);
// Update the Status Block
IoStatusBlock->Status = STATUS_SUCCESS;
IoStatusBlock->Information = Length;
return STATUS_SUCCESS;
}
if (ApcRoutine != nullptr) {
// Pack the original parameters to a wrapped context for a custom APC routine
CxbxIoDispatcherContext* cxbxContext = new CxbxIoDispatcherContext(IoStatusBlock, ApcRoutine, ApcContext);

View file

@ -1023,42 +1023,53 @@ XBSYSAPI EXPORTNUM(246) xbox::ntstatus_xt NTAPI xbox::ObReferenceObjectByHandle
PVOID Object;
POBJECT_HEADER ObjectHeader;
// Check if Handle contain special handle for current thread.
if (Handle == NtCurrentThread()) {
if ((ObjectType == &PsThreadObjectType) || (ObjectType == NULL)) {
// We only accept either thread or null object type.
if ((ObjectType == &PsThreadObjectType) || (!ObjectType)) {
Object = PspGetCurrentThread();
ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
InterlockedIncrement((::PLONG)(&ObjectHeader->PointerCount));
*ReturnedObject = Object;
return X_STATUS_SUCCESS;
} else {
}
else {
result = STATUS_OBJECT_TYPE_MISMATCH;
}
} else {
Object = ObpGetObjectHandleReference(Handle);
if (Object != NULL) {
ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
if ((ObjectType == ObjectHeader->Type) || (ObjectType == NULL)) {
*ReturnedObject = Object;
return X_STATUS_SUCCESS;
} else {
ObfDereferenceObject(Object);
result = STATUS_OBJECT_TYPE_MISMATCH;
// Check if object is null pointer
if (!Object) {
DWORD flags = 0;
if (Handle == (xbox::HANDLE)-1) {
// bypass hack below check if special handle is NtCurrentProcess.
}
} else {
// HACK: Since we forward to NtDll::NtCreateEvent, this *might* be a Windows handle instead of our own
// In this case, we must return the input handle
// Test Case: Xbox Live Dashboard, Network Test (or any other Xbox Live connection)
DWORD flags = 0;
if (GetHandleInformation(Handle, &flags)) {
else if (GetHandleInformation(Handle, &flags)) {
// This was a Windows Handle, so return it.
*ReturnedObject = Handle;
return X_STATUS_SUCCESS;
}
// TODO: Remove above, inside if statement, to leave only result value set here.
result = STATUS_INVALID_HANDLE;
}
// If object is valid, then return object.
else {
ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
// Verify if object type do match with found object or any if null object type.
if ((ObjectType == ObjectHeader->Type) || (!ObjectType)) {
*ReturnedObject = Object;
return X_STATUS_SUCCESS;
}
else {
ObfDereferenceObject(Object);
result = STATUS_OBJECT_TYPE_MISMATCH;
}
}
}
*ReturnedObject = NULL;

View file

@ -121,6 +121,8 @@ static unsigned int WINAPI PCSTProxy
params.Ethread,
params.TlsDataSize);
xbox::KiExecuteKernelApc();
auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine;
// Debugging notice : When the below line shows up with an Exception dialog and a
// message like: "Exception thrown at 0x00026190 in cxbx.exe: 0xC0000005: Access
@ -406,13 +408,24 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx
assert(dupHandle);
RegisterXboxHandle(eThread->UniqueThread, dupHandle);
eThread->Tcb.Priority = GetThreadPriority(handle);
g_AffinityPolicy->SetAffinityXbox(handle);
// Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED)
if (!CreateSuspended) {
ResumeThread(handle);
// Wait for the initialization of the remaining thread state
KeSuspendThreadEx(&eThread->Tcb);
ResumeThread(handle);
while (eThread->Tcb.State == Initialized) {
std::this_thread::yield();
}
// Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED), then wait until the new thread has
// finished initialization
if (CreateSuspended) {
KeSuspendThread(&eThread->Tcb);
}
KeResumeThreadEx(&eThread->Tcb);
// Log ThreadID identical to how GetCurrentThreadID() is rendered :
EmuLog(LOG_LEVEL::DEBUG, "Created Xbox proxy thread. Handle : 0x%X, ThreadId : [0x%.4X], Native Handle : 0x%X, Native ThreadId : [0x%.4X]",
*ThreadHandle, eThread->UniqueThread, handle, ThreadId);
@ -493,10 +506,13 @@ XBSYSAPI EXPORTNUM(258) xbox::void_xt NTAPI xbox::PsTerminateSystemThread
KeQuerySystemTime(&eThread->ExitTime);
eThread->ExitStatus = ExitStatus;
eThread->Tcb.Header.SignalState = 1;
KiWaitListLock();
if (!IsListEmpty(&eThread->Tcb.Header.WaitListHead)) {
// TODO: Implement KiWaitTest's relative objects usage
//KiWaitTest()
assert(0);
KiWaitTest((PVOID)&eThread->Tcb, 0);
std::this_thread::yield();
}
else {
KiWaitListUnlock();
}
if (GetNativeHandle(eThread->UniqueThread)) {

View file

@ -89,6 +89,11 @@ xbox::boolean_xt RtlpCaptureStackLimits(
return TRUE;
}
xbox::void_xt xbox::RtlInitSystem()
{
xbox::RtlInitializeCriticalSection(&NtSystemTimeCritSec);
}
// ******************************************************************
// * 0x0104 - RtlAnsiStringToUnicodeString()
// ******************************************************************
@ -108,23 +113,28 @@ XBSYSAPI EXPORTNUM(260) xbox::ntstatus_xt NTAPI xbox::RtlAnsiStringToUnicodeStri
dword_xt total = RtlAnsiStringToUnicodeSize(SourceString);
if (total > 0xffff) {
return X_STATUS_INVALID_PARAMETER_2;
RETURN(X_STATUS_INVALID_PARAMETER_2);
}
DestinationString->Length = (USHORT)(total - sizeof(WCHAR));
if (AllocateDestinationString) {
DestinationString->MaximumLength = (USHORT)total;
if (!(DestinationString->Buffer = (USHORT*)ExAllocatePoolWithTag(total, 'grtS'))) {
return X_STATUS_NO_MEMORY;
if (!(DestinationString->Buffer = (wchar_xt*)ExAllocatePoolWithTag(total, 'grtS'))) {
RETURN(X_STATUS_NO_MEMORY);
}
}
else {
if (total > DestinationString->MaximumLength) {
return X_STATUS_BUFFER_OVERFLOW;
RETURN(X_STATUS_BUFFER_OVERFLOW);
}
}
RtlMultiByteToUnicodeN((PWSTR)DestinationString->Buffer, (ULONG)DestinationString->Length, NULL, SourceString->Buffer, SourceString->Length);
RtlMultiByteToUnicodeN((PWSTR)DestinationString->Buffer,
(ULONG)DestinationString->Length,
NULL,
SourceString->Buffer,
SourceString->Length);
DestinationString->Buffer[DestinationString->Length / sizeof(WCHAR)] = 0;
RETURN(X_STATUS_SUCCESS);
@ -521,23 +531,31 @@ XBSYSAPI EXPORTNUM(270) xbox::long_xt NTAPI xbox::RtlCompareString
LOG_FUNC_ARG(CaseInSensitive)
LOG_FUNC_END;
LONG result;
const USHORT l1 = String1->Length;
const USHORT l2 = String2->Length;
const USHORT maxLen = (l1 <= l2 ? l1 : l2);
USHORT l1 = String1->Length;
USHORT l2 = String2->Length;
USHORT maxLen = l1 <= l2 ? l1 : l2;
CHAR *str1 = String1->Buffer;
CHAR *str2 = String2->Buffer;
const PCHAR str1 = String1->Buffer;
const PCHAR str2 = String2->Buffer;
if (CaseInSensitive) {
result = _strnicmp(str1, str2, maxLen);
for (unsigned i = 0; i < maxLen; i++) {
UCHAR char1 = RtlLowerChar(str1[i]);
UCHAR char2 = RtlLowerChar(str2[i]);
if (char1 != char2) {
RETURN(char1 - char2);
}
}
}
else {
result = strncmp(str1, str2, maxLen);
for (unsigned i = 0; i < maxLen; i++) {
if (str1[i] != str2[i]) {
RETURN(str1[i] - str2[i]);
}
}
}
RETURN(result);
RETURN(l1 - l2);
}
// ******************************************************************
@ -556,23 +574,32 @@ XBSYSAPI EXPORTNUM(271) xbox::long_xt NTAPI xbox::RtlCompareUnicodeString
LOG_FUNC_ARG(CaseInSensitive)
LOG_FUNC_END;
LONG result;
const USHORT l1 = String1->Length;
const USHORT l2 = String2->Length;
const USHORT maxLen = (l1 <= l2 ? l1 : l2) / sizeof(WCHAR);
USHORT l1 = String1->Length;
USHORT l2 = String2->Length;
USHORT maxLen = l1 <= l2 ? l1 : l2;
WCHAR *str1 = (WCHAR*)(String1->Buffer);
WCHAR *str2 = (WCHAR*)(String2->Buffer);
const wchar_xt* str1 = String1->Buffer;
const wchar_xt* str2 = String2->Buffer;
if (CaseInSensitive) {
result = _wcsnicmp(str1, str2, maxLen);
for (unsigned i = 0; i < maxLen; i++) {
wchar_xt char1 = towlower(str1[i]);
wchar_xt char2 = towlower(str2[i]);
if (char1 != char2) {
RETURN(char1 - char2);
}
}
}
else {
result = wcsncmp(str1, str2, maxLen);
for (unsigned i = 0; i < maxLen; i++) {
if (str1[i] != str2[i]) {
RETURN(str1[i] - str2[i]);
}
}
}
RETURN(result);
RETURN(l1 - l2);
}
// ******************************************************************
@ -652,7 +679,7 @@ XBSYSAPI EXPORTNUM(274) xbox::boolean_xt NTAPI xbox::RtlCreateUnicodeString
BOOLEAN result = TRUE;
ULONG bufferSize = (std::u16string(SourceString).length() + 1) * sizeof(WCHAR);
DestinationString->Buffer = (USHORT *)ExAllocatePoolWithTag(bufferSize, 'grtS');
DestinationString->Buffer = (wchar_xt*)ExAllocatePoolWithTag(bufferSize, 'grtS');
if (!DestinationString->Buffer) {
result = FALSE;
}
@ -700,7 +727,7 @@ XBSYSAPI EXPORTNUM(276) xbox::ntstatus_xt NTAPI xbox::RtlDowncaseUnicodeString
if (AllocateDestinationString) {
DestinationString->MaximumLength = SourceString->Length;
DestinationString->Buffer = (USHORT*)ExAllocatePoolWithTag((ULONG)DestinationString->MaximumLength, 'grtS');
DestinationString->Buffer = (wchar_xt*)ExAllocatePoolWithTag((ULONG)DestinationString->MaximumLength, 'grtS');
if (DestinationString->Buffer == NULL) {
return X_STATUS_NO_MEMORY;
}
@ -1173,9 +1200,8 @@ XBSYSAPI EXPORTNUM(290) xbox::void_xt NTAPI xbox::RtlInitUnicodeString
LOG_FUNC_ARG(SourceString)
LOG_FUNC_END;
DestinationString->Buffer = (USHORT*)SourceString;
DestinationString->Buffer = (wchar_xt*)SourceString;
if (SourceString != NULL) {
DestinationString->Buffer = (USHORT*)SourceString;
DestinationString->Length = (USHORT)std::u16string(SourceString).length() * 2;
DestinationString->MaximumLength = DestinationString->Length + 2;
}
@ -1523,6 +1549,7 @@ no_mapping:
#define TICKSPERSEC 10000000
#define TICKSPERMSEC 10000
#define MSECSPERSEC 1000
#define SECSPERDAY 86400
#define SECSPERHOUR 3600
#define SECSPERMIN 60
@ -1541,6 +1568,11 @@ no_mapping:
#define SECS_1601_TO_1980 ((379 * 365 + 91) * (ULONGLONG)SECSPERDAY)
#define TICKS_1601_TO_1980 (SECS_1601_TO_1980 * TICKSPERSEC)
const xbox::LARGE_INTEGER Magic10000 = { .QuadPart = 0xd1b71758e219652ci64 };
#define SHIFT10000 13
xbox::LARGE_INTEGER Magic86400000 = { .QuadPart = 0xc6d750ebfa67b90ei64 };
#define SHIFT86400000 26
static const int MonthLengths[2][MONSPERYEAR] =
{
@ -1594,26 +1626,31 @@ XBSYSAPI EXPORTNUM(304) xbox::boolean_xt NTAPI xbox::RtlTimeFieldsToTime
OUT PLARGE_INTEGER Time
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(TimeFields)
LOG_FUNC_ARG_OUT(Time)
LOG_FUNC_END;
int month, year, cleaps, day;
/* FIXME: normalize the TIME_FIELDS structure here */
/* No, native just returns 0 (error) if the fields are not */
if (TimeFields->Millisecond < 0 || TimeFields->Millisecond > 999 ||
TimeFields->Second < 0 || TimeFields->Second > 59 ||
TimeFields->Minute < 0 || TimeFields->Minute > 59 ||
TimeFields->Hour < 0 || TimeFields->Hour > 23 ||
TimeFields->Month < 1 || TimeFields->Month > 12 ||
TimeFields->Day < 1 ||
TimeFields->Day > MonthLengths
[TimeFields->Month == 2 || IsLeapYear(TimeFields->Year)]
[TimeFields->Month - 1] ||
TimeFields->Year < 1601)
/* Verify each TimeFields' variables are within range */
if (TimeFields->Millisecond < 0 || TimeFields->Millisecond > 999) {
return FALSE;
}
if (TimeFields->Second < 0 || TimeFields->Second > 59) {
return FALSE;
}
if (TimeFields->Minute < 0 || TimeFields->Minute > 59) {
return FALSE;
}
if (TimeFields->Hour < 0 || TimeFields->Hour > 23) {
return FALSE;
}
if (TimeFields->Month < 1 || TimeFields->Month > 12) {
return FALSE;
}
if (TimeFields->Day < 1 ||
TimeFields->Day > MonthLengths[IsLeapYear(TimeFields->Year)][TimeFields->Month - 1]) {
return FALSE;
}
if (TimeFields->Year < 1601) {
return FALSE;
}
/* now calculate a day count from the date
* First start counting years from March. This way the leap days
@ -1629,20 +1666,21 @@ XBSYSAPI EXPORTNUM(304) xbox::boolean_xt NTAPI xbox::RtlTimeFieldsToTime
month = TimeFields->Month + 1;
year = TimeFields->Year;
}
cleaps = (3 * (year / 100) + 3) / 4; /* nr of "century leap years"*/
day = (36525 * year) / 100 - cleaps + /* year * dayperyr, corrected */
(1959 * month) / 64 + /* months * daypermonth */
TimeFields->Day - /* day of the month */
584817; /* zero that on 1601-01-01 */
cleaps = (3 * (year / 100) + 3) / 4; /* nr of "century leap years"*/
day = (36525 * year) / 100 - cleaps + /* year * DayPerYear, corrected */
(1959 * month) / 64 + /* months * DayPerMonth */
TimeFields->Day - /* day of the month */
584817; /* zero that on 1601-01-01 */
/* done */
Time->QuadPart = (((((LONGLONG)day * HOURSPERDAY +
TimeFields->Hour) * MINSPERHOUR +
TimeFields->Minute) * SECSPERMIN +
TimeFields->Second) * 1000 +
TimeFields->Millisecond) * TICKSPERMSEC;
/* Convert into Time format */
Time->QuadPart = day * HOURSPERDAY;
Time->QuadPart = (Time->QuadPart + TimeFields->Hour) * MINSPERHOUR;
Time->QuadPart = (Time->QuadPart + TimeFields->Minute) * SECSPERMIN;
Time->QuadPart = (Time->QuadPart + TimeFields->Second) * MSECSPERSEC;
Time->QuadPart = (Time->QuadPart + TimeFields->Millisecond) * TICKSPERMSEC;
RETURN(TRUE);
return TRUE;
}
// ******************************************************************
@ -1654,36 +1692,27 @@ XBSYSAPI EXPORTNUM(305) xbox::void_xt NTAPI xbox::RtlTimeToTimeFields
OUT PTIME_FIELDS TimeFields
)
{
LOG_FUNC_BEGIN
LOG_FUNC_ARG(Time)
LOG_FUNC_ARG_OUT(TimeFields)
LOG_FUNC_END;
LONGLONG Days, cleaps, years, yearday, months;
int SecondsInDay;
long int cleaps, years, yearday, months;
long int Days;
LONGLONG _Time;
/* Extract millisecond from time and convert time into seconds */
TimeFields->Millisecond =
(cshort_xt)((Time->QuadPart % TICKSPERSEC) / TICKSPERMSEC);
_Time = Time->QuadPart / TICKSPERSEC;
/* The native version of RtlTimeToTimeFields does not take leap seconds
* into account */
/* Split the time into days and seconds within the day */
Days = (long int )(_Time / SECSPERDAY);
SecondsInDay = _Time % SECSPERDAY;
/* Extract milliseconds from time and days from milliseconds */
// NOTE: Reverse engineered native kernel uses RtlExtendedMagicDivide calls.
// Using similar code of ReactOS does not emulate native kernel's implement for
// one increment over large integer's max value.
xbox::LARGE_INTEGER MillisecondsTotal = RtlExtendedMagicDivide(*Time, Magic10000, SHIFT10000);
Days = RtlExtendedMagicDivide(MillisecondsTotal, Magic86400000, SHIFT86400000).u.LowPart;
MillisecondsTotal.QuadPart -= Days * SECSPERDAY * MSECSPERSEC;
/* compute time of day */
TimeFields->Hour = (cshort_xt)(SecondsInDay / SECSPERHOUR);
SecondsInDay = SecondsInDay % SECSPERHOUR;
TimeFields->Minute = (cshort_xt)(SecondsInDay / SECSPERMIN);
TimeFields->Second = (cshort_xt)(SecondsInDay % SECSPERMIN);
TimeFields->Millisecond = MillisecondsTotal.u.LowPart % MSECSPERSEC;
dword_xt RemainderTime = MillisecondsTotal.u.LowPart / MSECSPERSEC;
TimeFields->Second = RemainderTime % SECSPERMIN;
RemainderTime /= SECSPERMIN;
TimeFields->Minute = RemainderTime % MINSPERHOUR;
RemainderTime /= MINSPERHOUR;
TimeFields->Hour = RemainderTime; // NOTE: Remaining hours did not received 24 hours modulo treatment.
/* compute day of week */
TimeFields->Weekday = (cshort_xt)((EPOCHWEEKDAY + Days) % DAYSPERWEEK);
TimeFields->Weekday = (EPOCHWEEKDAY + Days) % DAYSPERWEEK;
/* compute year, month and day of month. */
cleaps = (3 * ((4 * Days + 1227) / DAYSPERQUADRICENTENNIUM) + 3) / 4;
@ -1692,7 +1721,7 @@ XBSYSAPI EXPORTNUM(305) xbox::void_xt NTAPI xbox::RtlTimeToTimeFields
yearday = Days - (years * DAYSPERNORMALQUADRENNIUM) / 4;
months = (64 * yearday) / 1959;
/* the result is based on a year starting on March.
* To convert take 12 from Januari and Februari and
* To convert take 12 from January and February and
* increase the year by one. */
if (months < 14) {
TimeFields->Month = (USHORT)(months - 1);
@ -2025,7 +2054,7 @@ XBSYSAPI EXPORTNUM(314) xbox::ntstatus_xt NTAPI xbox::RtlUpcaseUnicodeString
if (AllocateDestinationString) {
DestinationString->MaximumLength = SourceString->Length;
DestinationString->Buffer = (USHORT*)ExAllocatePoolWithTag((ULONG)DestinationString->MaximumLength, 'grtS');
DestinationString->Buffer = (wchar_xt*)ExAllocatePoolWithTag((ULONG)DestinationString->MaximumLength, 'grtS');
if (DestinationString->Buffer == NULL) {
return X_STATUS_NO_MEMORY;
}
@ -2219,6 +2248,11 @@ XBSYSAPI EXPORTNUM(319) xbox::ulong_xt NTAPI xbox::RtlWalkFrameChain
ulong_ptr_xt NewStack = *(ulong_ptr_xt*)Stack;
ulong_xt Eip = *(ulong_ptr_xt*)(Stack + sizeof(ulong_ptr_xt));
/* Check if Eip is not below executable's dos header */
if (Eip < KiB(64)) {
break;
}
/* Check if the new pointer is above the old one and past the end */
if (!((Stack < NewStack) && (NewStack < StackEnd))) {
/* Stop searching after this entry */

View file

@ -41,13 +41,19 @@ XBSYSAPI EXPORTNUM(321) xbox::XBOX_KEY_DATA xbox::XboxEEPROMKey = { 0 };
// ******************************************************************
// * 0x0142 - XboxHardwareInfo
// ******************************************************************
// TODO: The main goal is to completely unset custom init values and have
// them set from kernel's initialization end and device classes.
// Although, device classes does not really set this value but read
// from PCI space for Gpu rev, Mcp rev, and possibility INTERNAL_USB flag.
XBSYSAPI EXPORTNUM(322) xbox::XBOX_HARDWARE_INFO xbox::XboxHardwareInfo =
{
0xC0000031, // Flags: 1=INTERNAL_USB, 2=DEVKIT, 4=MACROVISION, 8=CHIHIRO
0xA2, // GpuRevision, byte read from NV2A first register, at 0xFD0000000 - see NV_PMC_BOOT_0
0xD3, // McpRevision, Retail 1.6 - see https://github.com/JayFoxRox/xqemu-jfr/wiki/MCPX-and-bootloader
0, // unknown
0 // unknown
// TODO: What exactly 0xC0000030 flags are? Might need default to null then set them later properly.
// NOTE: Will be set by src/devices/Xbox.cpp and maybe other file(s)...
.Flags = 0xC0000030, // Flags: 1=INTERNAL_USB, 2=DEVKIT, 4=MACROVISION, 8=CHIHIRO
.GpuRevision = 0xD3, // GpuRevision, byte read from NV2A first register, at 0xFD0000000 - see NV_PMC_BOOT_0
.McpRevision = 0, // NOTE: Will be set by src/devices/Xbox.cpp file.
.Unknown3 = 0, // unknown
.Unknown4 = 0 // unknown
};
// ******************************************************************

View file

@ -99,9 +99,6 @@ bool g_bIsChihiro = false;
bool g_bIsDevKit = false;
bool g_bIsRetail = false;
// Indicates to disable/enable all interrupts when cli and sti instructions are executed
std::atomic_bool g_bEnableAllInterrupts = true;
// Set by the VMManager during initialization. Exported because it's needed in other parts of the emu
size_t g_SystemMaxMemory = 0;
@ -113,6 +110,8 @@ ULONG g_CxbxFatalErrorCode = FATAL_ERROR_NONE;
// Define function located in EmuXApi so we can call it from here
void SetupXboxDeviceTypes();
extern xbox::void_xt NTAPI system_events(xbox::PVOID arg);
void SetupPerTitleKeys()
{
// Generate per-title keys from the XBE Certificate
@ -329,64 +328,6 @@ void InitSoftwareInterrupts()
}
#endif
static xbox::void_xt NTAPI CxbxKrnlInterruptThread(xbox::PVOID param)
{
CxbxSetThreadName("CxbxKrnl Interrupts");
#if 0
InitSoftwareInterrupts();
#endif
std::mutex m;
std::unique_lock<std::mutex> lock(m);
while (true) {
for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) {
// If the interrupt is pending and connected, process it
if (HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) {
HalSystemInterrupts[i].Trigger(EmuInterruptList[i]);
}
}
g_InterruptSignal.wait(lock, []() { return g_AnyInterruptAsserted.load() && g_bEnableAllInterrupts.load(); });
g_AnyInterruptAsserted = false;
}
assert(0);
}
static void CxbxKrnlClockThread(void* pVoid)
{
LARGE_INTEGER CurrentTicks;
uint64_t Delta;
uint64_t Microseconds;
unsigned int IncrementScaling;
static uint64_t LastTicks = 0;
static uint64_t Error = 0;
static uint64_t UnaccountedMicroseconds = 0;
// This keeps track of how many us have elapsed between two cycles, so that the xbox clocks are updated
// with the proper increment (instead of blindly adding a single increment at every step)
if (LastTicks == 0) {
QueryPerformanceCounter(&CurrentTicks);
LastTicks = CurrentTicks.QuadPart;
CurrentTicks.QuadPart = 0;
}
QueryPerformanceCounter(&CurrentTicks);
Delta = CurrentTicks.QuadPart - LastTicks;
LastTicks = CurrentTicks.QuadPart;
Error += (Delta * SCALE_S_IN_US);
Microseconds = Error / HostQPCFrequency;
Error -= (Microseconds * HostQPCFrequency);
UnaccountedMicroseconds += Microseconds;
IncrementScaling = (unsigned int)(UnaccountedMicroseconds / 1000); // -> 1 ms = 1000us -> time between two xbox clock interrupts
UnaccountedMicroseconds -= (IncrementScaling * 1000);
xbox::KiClockIsr(IncrementScaling);
}
void MapThunkTable(uint32_t* kt, uint32_t* pThunkTable)
{
const bool SendDebugReports = (pThunkTable == CxbxKrnl_KernelThunkTable) && CxbxDebugger::CanReport();
@ -542,7 +483,10 @@ static void CxbxrKrnlSetupMemorySystem(int BootFlags, unsigned emulate_system, u
}
}
static bool CxbxrKrnlXbeSystemSelector(int BootFlags, unsigned& reserved_systems, blocks_reserved_t blocks_reserved)
static bool CxbxrKrnlXbeSystemSelector(int BootFlags,
unsigned& reserved_systems,
blocks_reserved_t blocks_reserved,
HardwareModel &hardwareModel)
{
unsigned int emulate_system = 0;
// Get title path :
@ -683,6 +627,7 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags, unsigned& reserved_systems
// Detect XBE type :
XbeType xbeType = CxbxKrnl_Xbe->GetXbeType();
EmuLogInit(LOG_LEVEL::INFO, "Auto detect: XbeType = %s", GetXbeTypeToStr(xbeType));
// Convert XBE type into corresponding system to emulate.
switch (xbeType) {
case XbeType::xtChihiro:
@ -696,6 +641,10 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags, unsigned& reserved_systems
break;
DEFAULT_UNREACHABLE;
}
if (std::filesystem::exists(xbeDirectory / "boot.id")) {
emulate_system = SYSTEM_CHIHIRO;
}
}
EmuLogInit(LOG_LEVEL::INFO, "Host's compatible system types: %2X", reserved_systems);
@ -717,6 +666,17 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags, unsigned& reserved_systems
g_bIsChihiro = (emulate_system == SYSTEM_CHIHIRO);
g_bIsDevKit = (emulate_system == SYSTEM_DEVKIT);
g_bIsRetail = (emulate_system == SYSTEM_XBOX);
if (g_bIsChihiro) {
hardwareModel = HardwareModel::Chihiro_Type1; // TODO: Make configurable to support Type-3 console.
}
else if (g_bIsDevKit) {
hardwareModel = HardwareModel::DebugKit_r1_2; // Unlikely need to make configurable.
}
// Retail (default)
else {
// Should not be configurable. Otherwise, titles compiled with newer XDK will patch older xbox kernel.
hardwareModel = HardwareModel::Revision1_6;
}
#ifdef CHIHIRO_WORK
// If this is a Chihiro title, we need to patch the init flags to disable HDD setup
@ -934,7 +894,8 @@ static bool CxbxrKrnlPrepareXbeMap()
Xbe::Header* XbeHeader,
uint32_t XbeHeaderSize,
void (*Entry)(),
int BootFlags
int BootFlags,
HardwareModel hardwareModel
);
void CxbxKrnlEmulate(unsigned int reserved_systems, blocks_reserved_t blocks_reserved)
@ -1088,7 +1049,8 @@ void CxbxKrnlEmulate(unsigned int reserved_systems, blocks_reserved_t blocks_res
// using XeLoadImage from LaunchDataPage->Header.szLaunchPath
// Now we can load the XBE :
if (!CxbxrKrnlXbeSystemSelector(BootFlags, reserved_systems, blocks_reserved)) {
HardwareModel hardwareModel = HardwareModel::Revision1_6; // It is configured by CxbxrKrnlXbeSystemSelector function according to console type.
if (!CxbxrKrnlXbeSystemSelector(BootFlags, reserved_systems, blocks_reserved, hardwareModel)) {
return;
}
@ -1116,7 +1078,8 @@ void CxbxKrnlEmulate(unsigned int reserved_systems, blocks_reserved_t blocks_res
(Xbe::Header*)CxbxKrnl_Xbe->m_Header.dwBaseAddr,
CxbxKrnl_Xbe->m_Header.dwSizeofHeaders,
(void(*)())EntryPoint,
BootFlags
BootFlags,
hardwareModel
);
}
@ -1141,7 +1104,10 @@ static void CxbxrLogDumpXbeInfo(Xbe::LibraryVersion* libVersionInfo)
EmuLogInit(LOG_LEVEL::INFO, "XBE TitleID : %s", FormatTitleId(g_pCertificate->dwTitleId).c_str());
EmuLogInit(LOG_LEVEL::INFO, "XBE TitleID (Hex) : 0x%s", titleIdHex.str().c_str());
EmuLogInit(LOG_LEVEL::INFO, "XBE Version : 1.%02d", g_pCertificate->dwVersion);
if (CxbxKrnl_Xbe != nullptr) {
EmuLogInit(LOG_LEVEL::INFO, "XBE Version : %d.%d", CxbxKrnl_Xbe->GetDiscVersion(), CxbxKrnl_Xbe->GetPatchVersion());
}
EmuLogInit(LOG_LEVEL::INFO, "XBE Version (Hex): %08X", g_pCertificate->dwVersion);
EmuLogInit(LOG_LEVEL::INFO, "XBE TitleName : %.40ls", g_pCertificate->wsTitleName);
EmuLogInit(LOG_LEVEL::INFO, "XBE Region : %s", CxbxKrnl_Xbe->GameRegionToString());
}
@ -1175,7 +1141,8 @@ static void CxbxrKrnlInitHacks()
Xbe::Header *pXbeHeader,
uint32_t dwXbeHeaderSize,
void(*Entry)(),
int BootFlags)
int BootFlags,
HardwareModel hardwareModel)
{
unsigned Host2XbStackBaseReserved = 0;
__asm mov Host2XbStackBaseReserved, esp;
@ -1199,11 +1166,11 @@ static void CxbxrKrnlInitHacks()
g_pCertificate = &CxbxKrnl_Xbe->m_Certificate;
// Initialize timer subsystem
Timer_Init();
timer_init();
// for unicode conversions
setlocale(LC_ALL, "English");
// Initialize time-related variables for the kernel and the timers
CxbxInitPerformanceCounters();
// Initialize DPC global
InitDpcData();
#ifdef _DEBUG
// PopupCustom(LOG_LEVEL::INFO, "Attach a Debugger");
// Debug child processes using https://marketplace.visualstudio.com/items?itemName=GreggMiskelly.MicrosoftChildProcessDebuggingPowerTool
@ -1341,10 +1308,11 @@ static void CxbxrKrnlInitHacks()
}
xbox::PsInitSystem();
xbox::KiInitSystem();
xbox::RtlInitSystem();
// initialize graphics
EmuLogInit(LOG_LEVEL::DEBUG, "Initializing render window.");
CxbxInitWindow(true);
CxbxInitWindow();
// Now process the boot flags to see if there are any special conditions to handle
if (BootFlags & BOOT_EJECT_PENDING) {} // TODO
@ -1380,7 +1348,7 @@ static void CxbxrKrnlInitHacks()
SetupXboxDeviceTypes();
}
InitXboxHardware(HardwareModel::Revision1_5); // TODO : Make configurable
InitXboxHardware(hardwareModel);
// Read Xbox video mode from the SMC, store it in HalBootSMCVideoMode
xbox::HalReadSMBusValue(SMBUS_ADDRESS_SYSTEM_MICRO_CONTROLLER, SMC_COMMAND_AV_PACK, FALSE, (xbox::PULONG)&xbox::HalBootSMCVideoMode);
@ -1406,6 +1374,29 @@ static void CxbxrKrnlInitHacks()
// See: https://multimedia.cx/eggs/xbox-sphinx-protocol/
ApplyMediaPatches();
// Verify that the emulator region matches the game region, if not, show a warning
// that it may not work.
if (!(g_pCertificate->dwGameRegion & EEPROM->EncryptedSettings.GameRegion))
{
auto expected = CxbxKrnl_Xbe->GameRegionToString();
auto actual = CxbxKrnl_Xbe->GameRegionToString(EEPROM->EncryptedSettings.GameRegion);
std::stringstream ss;
ss << "The loaded title is designed for region: " << expected << "\n";
ss << "However Cxbx-Reloaded is configured as: " << actual << "\n\n";
ss << "This means that you may encounter emulation issues\n\n";
ss << "You can fix this by changing your emulated Xbox region in EEPROM Settings\n\n";
ss << "Please do not submit bug reports that result from incorrect region flags\n\n";
ss << "Would you like to attempt emulation anyway?";
PopupReturn ret = PopupWarningEx(nullptr, PopupButtons::YesNo, PopupReturn::No, ss.str().c_str());
if (ret != PopupReturn::Yes)
{
CxbxrShutDown();
}
}
// Chihiro games require more patches
// The chihiro BIOS does this to bypass XAPI cache init
if (g_bIsChihiro) {
@ -1430,23 +1421,17 @@ static void CxbxrKrnlInitHacks()
#endif
EmuX86_Init();
// Create the interrupt processing thread
// Start the event thread
xbox::HANDLE hThread;
CxbxrCreateThread(&hThread, xbox::zeroptr, CxbxKrnlInterruptThread, xbox::zeroptr, FALSE);
// Start the kernel clock thread
TimerObject* KernelClockThr = Timer_Create(CxbxKrnlClockThread, nullptr, "Kernel clock thread", true);
Timer_Start(KernelClockThr, SCALE_MS_IN_NS);
xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, system_events, xbox::zeroptr, FALSE);
// Launch the xbe
xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxLaunchXbe, Entry, FALSE);
EmuKeFreePcr();
__asm add esp, Host2XbStackSizeReserved;
// This will wait forever
std::condition_variable cv;
std::mutex m;
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [] { return false; });
xbox::KeRaiseIrqlToDpcLevel();
while (true) {
xbox::KeWaitForDpc();
ExecuteDpcQueue();
}
}
// REMARK: the following is useless, but PatrickvL has asked to keep it for documentation purposes
@ -1468,7 +1453,7 @@ void CxbxrKrnlSuspendThreads()
// Don't use EmuKeGetPcr because that asserts kpcr
xbox::KPCR* Pcr = reinterpret_cast<xbox::PKPCR>(__readfsdword(TIB_ArbitraryDataSlot));
// If there's nothing in list entry, skip this step.
if (!ThreadListEntry) {
return;

View file

@ -155,7 +155,9 @@ void CxbxKrnlPanic();
/*! empty function */
void CxbxKrnlNoFunc();
void CxbxInitPerformanceCounters(); // Implemented in EmuKrnlKe.cpp
void InitDpcData(); // Implemented in EmuKrnlKe.cpp
bool IsDpcActive();
void ExecuteDpcQueue();
/*! kernel thunk table */
extern uint32_t CxbxKrnl_KernelThunkTable[379];

View file

@ -57,15 +57,8 @@ static void ApplyMediaPatches()
| XBEIMAGE_MEDIA_TYPE_DVD_5_RO
| XBEIMAGE_MEDIA_TYPE_DVD_9_RO
| XBEIMAGE_MEDIA_TYPE_DVD_5_RW
| XBEIMAGE_MEDIA_TYPE_DVD_9_RW
;
// Patch the XBE Header to allow running on all regions
g_pCertificate->dwGameRegion = 0
| XBEIMAGE_GAME_REGION_MANUFACTURING
| XBEIMAGE_GAME_REGION_NA
| XBEIMAGE_GAME_REGION_JAPAN
| XBEIMAGE_GAME_REGION_RESTOFWORLD
;
| XBEIMAGE_MEDIA_TYPE_DVD_9_RW;
// Patch the XBE Security Flag
// This field is only present if the Xbe Size is >= than our Certificate Structure
// This works as our structure is large enough to fit the newer certificate size,

View file

@ -49,11 +49,6 @@ bool g_DisablePixelShaders = false;
bool g_UseAllCores = false;
bool g_SkipRdtscPatching = false;
// Delta added to host SystemTime, used in KiClockIsr and KeSetSystemTime
// This shouldn't need to be atomic, but because raising the IRQL to high lv in KeSetSystemTime doesn't really stop KiClockIsr from running,
// we need it for now to prevent reading a corrupted value while KeSetSystemTime is in the middle of updating it
std::atomic_int64_t HostSystemTimeDelta(0);
// Static Function(s)
static int ExitException(LPEXCEPTION_POINTERS e);

View file

@ -68,9 +68,6 @@ extern HWND g_hEmuWindow;
extern HANDLE g_CurrentProcessHandle; // Set in CxbxKrnlMain
// Delta added to host SystemTime, used in KiClockIsr and KeSetSystemTime
extern std::atomic_int64_t HostSystemTimeDelta;
typedef struct DUMMY_KERNEL
{
IMAGE_DOS_HEADER DosHeader;

View file

@ -40,7 +40,6 @@
#include <ntstatus.h>
#pragma warning(default:4005)
#include "Logging.h"
#include "common/util/strConverter.hpp" // utf16_to_ascii
#include "common/util/cliConfig.hpp"
#include "common/CxbxDebugger.h"
#include "EmuShared.h"
@ -790,20 +789,21 @@ std::string CxbxConvertXboxToHostPath(const std::string_view XboxDevicePath)
std::wstring wXbePath;
// We pretend to come from NtCreateFile to force symbolic link resolution
CxbxConvertFilePath(XboxDevicePath.data(), wXbePath, &rootDirectoryHandle, "NtCreateFile");
// Convert Wide String as returned by above to a string, for XbePath
std::string XbePath = utf16_to_ascii(wXbePath.c_str());
std::filesystem::path XbePath;
// If the rootDirectoryHandle is not null, we have a relative path
// We need to prepend the path of the root directory to get a full DOS path
if (rootDirectoryHandle != nullptr) {
WCHAR directoryPathBuffer[MAX_PATH];
GetFinalPathNameByHandleW(rootDirectoryHandle, directoryPathBuffer, MAX_PATH, VOLUME_NAME_DOS);
std::string directoryPath = utf16_to_ascii(directoryPathBuffer);
XbePath = directoryPath + std::string("\\") + XbePath;
XbePath = directoryPathBuffer;
XbePath /= wXbePath;
}
else {
XbePath = wXbePath;
}
return XbePath;
return XbePath.string();
}
int CxbxRegisterDeviceHostPath(const std::string_view XboxDevicePath, std::string HostDevicePath, bool IsFile, std::size_t size)

View file

@ -26,6 +26,7 @@
#include <core\kernel\exports\xboxkrnl.h>
#include <filesystem>
#include "core\kernel\init\CxbxKrnl.h"
#include <vector>
#include <cstdio>

View file

@ -91,10 +91,13 @@ uint8_t SMCDevice::ReadByte(uint8_t command)
// See https://xboxdevwiki.net/PIC#PIC_version_string
switch (m_revision) {
case SCMRevision::P01: buffer[1] = "P01"[m_PICVersionStringIndex]; break;
case SCMRevision::P2L: buffer[1] = "P05"[m_PICVersionStringIndex]; break; // ??
case SCMRevision::P05: buffer[1] = "P05"[m_PICVersionStringIndex]; break;
case SCMRevision::P11: buffer[1] = "P11"[m_PICVersionStringIndex]; break;
case SCMRevision::P2L: buffer[1] = "P2L"[m_PICVersionStringIndex]; break;
case SCMRevision::D01: buffer[1] = "DXB"[m_PICVersionStringIndex]; break;
case SCMRevision::D05: buffer[1] = "D05"[m_PICVersionStringIndex]; break; // ??
// default: UNREACHABLE(m_revision);
case SCMRevision::B11: buffer[1] = "B11"[m_PICVersionStringIndex]; break; // ??
default: CxbxrAbort("Unknown PIC revision: %d", m_revision);
}
m_PICVersionStringIndex = (m_PICVersionStringIndex + 1) % 3;

View file

@ -93,10 +93,14 @@
typedef enum {
// https://xboxdevwiki.net/System_Management_Controller
// https://xboxdevwiki.net/Xboxen_Info
P01,
P05,
P11,
P2L,
D01, // Seen in a debug kit
D05, // Seen in a earlier model chihiro
B11,
} SCMRevision;
class SMCDevice : public SMDevice {

View file

@ -25,9 +25,14 @@
// *
// ******************************************************************
#define LOG_PREFIX CXBXR_MODULE::XBOX
#include "Xbox.h" // For HardwareModel
#include "common\xbe\Xbe.h" // Without this HLEIntercept complains about some undefined xbe variables
#include "core\kernel\common\xbox.h"
#include "cxbxr.hpp"
#include "core\hle\Intercept.hpp"
#include "EmuShared.h"
PCIBus* g_PCIBus;
SMBus* g_SMBus;
@ -38,69 +43,86 @@ NVNetDevice* g_NVNet;
NV2ADevice* g_NV2A;
ADM1032Device* g_ADM1032;
USBDevice* g_USB0;
MediaBoard* g_MediaBoard;
MCPXRevision MCPXRevisionFromHardwareModel(HardwareModel hardwareModel)
{
switch (hardwareModel) {
case Revision1_0:
case Revision1_1:
case Revision1_2:
case Revision1_3:
case Revision1_4:
case Revision1_5:
case Revision1_6:
// https://xboxdevwiki.net/Xboxen_Info
switch (GET_HW_CONSOLE(hardwareModel)) {
case Retail:
return MCPXRevision::MCPX_X3;
case DebugKit:
// EmuLog(LOG_LEVEL::WARNING, "Guessing MCPXVersion");
case Chihiro:
return MCPXRevision::MCPX_X2;
default:
// UNREACHABLE(hardwareModel);
return MCPXRevision::MCPX_X3;
CxbxrAbort("MCPXRevisionFromHardwareModel: Unknown conversion for hardware model (0x%02X)", hardwareModel);
}
}
SCMRevision SCMRevisionFromHardwareModel(HardwareModel hardwareModel)
{
// https://xboxdevwiki.net/Xboxen_Info
switch (hardwareModel) {
case Revision1_0:
return SCMRevision::P01; // Our SCM returns PIC version string "P01"
return SCMRevision::P01;
case Revision1_1:
return SCMRevision::P05;
case Revision1_2:
case Revision1_3:
case Revision1_4:
return SCMRevision::P11;
case Revision1_5:
case Revision1_6:
// EmuLog(LOG_LEVEL::WARNING, "Guessing SCMRevision");
return SCMRevision::P2L; // Assumption; Our SCM returns PIC version string "P05"
case DebugKit:
return SCMRevision::D01; // Our SCM returns PIC version string "DXB"
default:
// UNREACHABLE(hardwareModel);
return SCMRevision::P2L;
case DebugKit:
return SCMRevision::D01;
case Chihiro_Type1:
return SCMRevision::D05;
case DebugKit_r1_2:
case Chihiro_Type3:
return SCMRevision::B11;
default:
CxbxrAbort("SCMRevisionFromHardwareModel: Unknown conversion for hardware model (0x%02X)", hardwareModel);
}
}
TVEncoder TVEncoderFromHardwareModel(HardwareModel hardwareModel)
{
switch (hardwareModel) {
// https://xboxdevwiki.net/Xboxen_Info
// LukeUsher : My debug kit and at least most of them (maybe all?)
// are equivalent to v1.0 and have Conexant encoders.
switch (GET_HW_REVISION(hardwareModel)) {
case Revision1_0:
case Revision1_1:
case Revision1_2:
case Revision1_3:
return TVEncoder::Conexant;
case Revision1_4:
case Revision1_5: // Assumption
return TVEncoder::Focus;
case Revision1_5:
return TVEncoder::Focus; // Assumption
case Revision1_6:
return TVEncoder::XCalibur;
case DebugKit:
// LukeUsher : My debug kit and at least most of them (maybe all?)
// are equivalent to v1.0 and have Conexant encoders.
return TVEncoder::Conexant;
default:
// UNREACHABLE(hardwareModel);
return TVEncoder::Focus;
default:
CxbxrAbort("TVEncoderFromHardwareModel: Unknown conversion for hardware model (0x%02X)", hardwareModel);
}
}
xbox::uchar_xt MCP_PCIRevisionFromHardwareModel(HardwareModel hardwareModel)
{
// https://xboxdevwiki.net/Xboxen_Info
switch (GET_HW_REVISION(hardwareModel)) {
case Revision1_0:
return 0xB2;
case Revision1_1:
case Revision1_2:
case Revision1_3:
case Revision1_4:
case Revision1_5: // Assumption
return 0xD4;
case Revision1_6:
return 0xD5;
default:
CxbxrAbort("MCP_PCIRevisionFromHardwareModel: Unknown conversion for hardware model (0x%02X)", hardwareModel);
}
}
@ -111,22 +133,43 @@ void InitXboxHardware(HardwareModel hardwareModel)
SCMRevision smc_revision = SCMRevisionFromHardwareModel(hardwareModel);
TVEncoder tv_encoder = TVEncoderFromHardwareModel(hardwareModel);
// Only Xbox 1.0 has usb daughterboard supplied, later revisions are integrated onto motherboard.
if (GET_HW_REVISION(hardwareModel) == HardwareModel::Revision1_0) {
xbox::XboxHardwareInfo.Flags |= XBOX_HW_FLAG_INTERNAL_USB_HUB;
}
// Set the special type of consoles according to xbox kernel designed respectively.
if (IS_DEVKIT(hardwareModel)) {
xbox::XboxHardwareInfo.Flags |= XBOX_HW_FLAG_DEVKIT_KERNEL;
}
else if (IS_CHIHIRO(hardwareModel)) {
xbox::XboxHardwareInfo.Flags |= XBOX_HW_FLAG_ARCADE;
}
xbox::XboxHardwareInfo.McpRevision = MCP_PCIRevisionFromHardwareModel(hardwareModel);
// Create busses
g_PCIBus = new PCIBus();
g_SMBus = new SMBus();
// Create devices
g_MCPX = new MCPXDevice(mcpx_revision);
g_SMC = new SMCDevice(smc_revision, g_bIsChihiro ? 6 : 1); // 6 = AV_PACK_STANDARD, 1 = AV_PACK_HDTV. Chihiro doesn't support HDTV!
// SMC uses different AV_PACK values than the Kernel
// See https://xboxdevwiki.net/PIC#The_AV_Pack
// TODO: For Chihiro, different games modes require different DIP switch settings
// Chihiro FilterBoard dip-switches 6,7,8 change this value!
g_SMC = new SMCDevice(smc_revision, IS_CHIHIRO(hardwareModel) ? 0 : 1); // 0 = AV_PACK_SCART, 1 = AV_PACK_HDTV. Chihiro doesn't support HDTV!
// SMC uses different AV_PACK values than the Kernel
// See https://xboxdevwiki.net/PIC#The_AV_Pack
g_EEPROM = new EEPROMDevice();
g_NVNet = new NVNetDevice();
g_NV2A = new NV2ADevice();
g_ADM1032 = new ADM1032Device();
if (bLLE_USB) {
g_USB0 = new USBDevice();
g_USB0 = new USBDevice();
if (IS_CHIHIRO(hardwareModel)) {
g_MediaBoard = new MediaBoard();
char MediaBoardMountPath[xbox::max_path];
g_EmuShared->GetTitleMountPath(MediaBoardMountPath);
g_MediaBoard->SetMountPath(MediaBoardMountPath);
}
// Connect devices to SM bus
@ -156,14 +199,12 @@ void InitXboxHardware(HardwareModel hardwareModel)
//g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(5, 0)), g_NVAPU);
//g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(6, 0)), g_AC97);
g_PCIBus->ConnectDevice(PCI_DEVID(1, PCI_DEVFN(0, 0)), g_NV2A);
if (bLLE_USB) {
// ergo720: according to some research done by LukeUsher, only Xbox Alpha Kits have a two HCs configuration. This seems to also be confirmed by the xboxdevwiki,
// which states that it has a xircom PGPCI2(OPTI 82C861) 2 USB port PCI card -> 2 ports, not 4. Finally, I disassembled various xbe's and discovered that the number
// of ports per HC is hardcoded as 4 in the driver instead of being detected at runtime by reading the HcRhDescriptorA register and so a game would have to be
// recompiled to support 2 HCs, which further confirms the point. Because we are not going to emulate an Alpha Kit, we can simply ignore the USB1 device.
// ergo720: according to some research done by LukeUsher, only Xbox Alpha Kits have a two HCs configuration. This seems to also be confirmed by the xboxdevwiki,
// which states that it has a xircom PGPCI2(OPTI 82C861) 2 USB port PCI card -> 2 ports, not 4. Finally, I disassembled various xbe's and discovered that the number
// of ports per HC is hardcoded as 4 in the driver instead of being detected at runtime by reading the HcRhDescriptorA register and so a game would have to be
// recompiled to support 2 HCs, which further confirms the point. Because we are not going to emulate an Alpha Kit, we can simply ignore the USB1 device.
g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(2, 0)), g_USB0);
}
g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(2, 0)), g_USB0);
// TODO : Handle other SMBUS Addresses, like PIC_ADDRESS, XCALIBUR_ADDRESS
// Resources : http://pablot.com/misc/fancontroller.cpp

View file

@ -35,6 +35,7 @@
#include "ADM1032Device.h" // For ADM1032
#include "devices\video\nv2a.h" // For NV2ADevice
#include "Usb\USBDevice.h" // For USBDevice
#include "chihiro\MediaBoard.h"
#define SMBUS_ADDRESS_MCPX 0x10 // = Write; Read = 0x11
#define SMBUS_ADDRESS_TV_ENCODER 0x88 // = Write; Read = 0x89
@ -53,9 +54,21 @@ typedef enum {
Revision1_4,
Revision1_5,
Revision1_6,
DebugKit
Retail = 0x00,
// We don't need include revison 1.0 to 1.6 here, use above revision range instead.
DebugKit = 0x10, // TODO: Since there are 1.0/1.1/1.2 revisions. For now, let's go with 1.2 by default.
DebugKit_r1_2 = DebugKit | Revision1_2,
Chihiro = 0x20,
Chihiro_Type1 = Chihiro | Revision1_1,
Chihiro_Type3 = Chihiro | Revision1_2, // TODO: Need verify on Chihiro hw, it is currently base on (B11) Debug Kit list.
} HardwareModel;
#define GET_HW_REVISION(hardwareModel) (hardwareModel & 0x0F)
#define GET_HW_CONSOLE(hardwareModel) (hardwareModel & 0xF0)
#define IS_RETAIL(hardwareModel) (GET_HW_CONSOLE(hardwareModel) == Retail)
#define IS_DEVKIT(hardwareModel) (GET_HW_CONSOLE(hardwareModel) == DebugKit)
#define IS_CHIHIRO(hardwareModel) (GET_HW_CONSOLE(hardwareModel) == Chihiro)
typedef enum { // TODO : Move to it's own file
// https://xboxdevwiki.net/Hardware_Revisions#Video_encoder
Conexant,
@ -71,5 +84,6 @@ extern EEPROMDevice* g_EEPROM;
extern NVNetDevice* g_NVNet;
extern NV2ADevice* g_NV2A;
extern USBDevice* g_USB0;
extern MediaBoard* g_MediaBoard;
extern void InitXboxHardware(HardwareModel hardwareModel);

View file

@ -0,0 +1,443 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// ******************************************************************
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them under the terms of the GNU General Public
// * License as published by the Free Software Foundation; either
// * version 2 of the license, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2019 Luke Usher
// *
// * All rights reserved
// *
// ******************************************************************
#include "JvsIo.h"
#include <cstdio>
#include <string>
JvsIo* g_pJvsIo;
//#define DEBUG_JVS_PACKETS
#include <vector>
#include <Windows.h>
// We will emulate SEGA 837-13551 IO Board
JvsIo::JvsIo(uint8_t* sense)
{
pSense = sense;
// Version info BCD Format: X.X
CommandFormatRevision = 0x11;
JvsVersion = 0x20;
CommunicationVersion = 0x10;
BoardID = "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551";
}
void JvsIo::Update()
{
// Handle coin input
static bool previousCoinButtonsState = false;
bool currentCoinButtonState = GetAsyncKeyState('5');
if (currentCoinButtonState && !previousCoinButtonsState) {
Inputs.coins[0].coins += 1;
}
previousCoinButtonsState = currentCoinButtonState;
// TODO: Update Jvs inputs based on user configuration
// For now, hardcode the inputs for the game we are currently testing (Ollie King)
Inputs.switches.player[0].start = GetAsyncKeyState('1'); // Start
Inputs.analog[1].value = GetAsyncKeyState(VK_LEFT) ? 0x9000 : (GetAsyncKeyState(VK_RIGHT) ? 0x7000 : 0x8000); // Board Swing
Inputs.switches.player[0].up = GetAsyncKeyState(VK_UP); // Board Front
Inputs.switches.player[0].down = GetAsyncKeyState(VK_DOWN); // Board Rear
Inputs.switches.player[0].button[0] = GetAsyncKeyState('A'); // Left Button
Inputs.switches.player[0].button[1] = GetAsyncKeyState('S'); // Right Button
}
uint8_t JvsIo::GetDeviceId()
{
return BroadcastPacket ? 0x00 : DeviceId;
}
int JvsIo::Jvs_Command_F0_Reset(uint8_t* data)
{
uint8_t ensure_reset = data[1];
if (ensure_reset == 0xD9) {
// Set sense to 3 (2.5v) to instruct the baseboard we're ready.
*pSense = 3;
ResponseBuffer.push_back(ReportCode::Handled); // Note : Without this, Chihiro software stops sending packets (but JVS V3 doesn't send this?)
DeviceId = 0;
}
#if 0 // TODO : Is the following required?
else {
ResponseBuffer.push_back(ReportCode::InvalidParameter);
}
#endif
#if 0 // TODO : Is the following required?
// Detect a consecutive reset
if (data[2] == 0xF0) {
// TODO : Probably ensure the second reset too : if (data[3] == 0xD9) {
// TODO : Handle two consecutive reset's here?
return 3;
}
#endif
return 1;
}
int JvsIo::Jvs_Command_F1_SetDeviceId(uint8_t* data)
{
// Set Address
DeviceId = data[1];
*pSense = 0; // Set sense to 0v
ResponseBuffer.push_back(ReportCode::Handled);
return 1;
}
int JvsIo::Jvs_Command_10_GetBoardId()
{
// Get Board ID
ResponseBuffer.push_back(ReportCode::Handled);
for (char& c : BoardID) {
ResponseBuffer.push_back(c);
}
return 0;
}
int JvsIo::Jvs_Command_11_GetCommandFormat()
{
ResponseBuffer.push_back(ReportCode::Handled);
ResponseBuffer.push_back(CommandFormatRevision);
return 0;
}
int JvsIo::Jvs_Command_12_GetJvsRevision()
{
ResponseBuffer.push_back(ReportCode::Handled);
ResponseBuffer.push_back(JvsVersion);
return 0;
}
int JvsIo::Jvs_Command_13_GetCommunicationVersion()
{
ResponseBuffer.push_back(ReportCode::Handled);
ResponseBuffer.push_back(CommunicationVersion);
return 0;
}
int JvsIo::Jvs_Command_14_GetCapabilities()
{
ResponseBuffer.push_back(ReportCode::Handled);
// Capabilities list (4 bytes each)
// Input capabilities
ResponseBuffer.push_back(CapabilityCode::PlayerSwitchButtonSets);
ResponseBuffer.push_back(JVS_MAX_PLAYERS); // number of players
ResponseBuffer.push_back(13); // 13 button switches per player
ResponseBuffer.push_back(0);
ResponseBuffer.push_back(CapabilityCode::CoinSlots);
ResponseBuffer.push_back(JVS_MAX_COINS); // number of coin slots
ResponseBuffer.push_back(0);
ResponseBuffer.push_back(0);
ResponseBuffer.push_back(CapabilityCode::AnalogInputs);
ResponseBuffer.push_back(JVS_MAX_ANALOG); // number of analog input channels
ResponseBuffer.push_back(16); // 16 bits per analog input channel
ResponseBuffer.push_back(0);
// Output capabilities
ResponseBuffer.push_back(CapabilityCode::GeneralPurposeOutputs);
ResponseBuffer.push_back(6); // number of outputs
ResponseBuffer.push_back(0);
ResponseBuffer.push_back(0);
ResponseBuffer.push_back(CapabilityCode::EndOfCapabilities);
return 0;
}
int JvsIo::Jvs_Command_20_ReadSwitchInputs(uint8_t* data)
{
static jvs_switch_player_inputs_t default_switch_player_input;
uint8_t nr_switch_players = data[1];
uint8_t bytesPerSwitchPlayerInput = data[2];
ResponseBuffer.push_back(ReportCode::Handled);
ResponseBuffer.push_back(Inputs.switches.system.GetByte0());
for (int i = 0; i < nr_switch_players; i++) {
for (int j = 0; j < bytesPerSwitchPlayerInput; j++) {
// If a title asks for more switch player inputs than we support, pad with dummy data
jvs_switch_player_inputs_t &switch_player_input = (i >= JVS_MAX_PLAYERS) ? default_switch_player_input : Inputs.switches.player[i];
uint8_t value
= (j == 0) ? switch_player_input.GetByte0()
: (j == 1) ? switch_player_input.GetByte1()
: 0; // Pad any remaining bytes with 0, as we don't have that many inputs available
ResponseBuffer.push_back(value);
}
}
return 2;
}
int JvsIo::Jvs_Command_21_ReadCoinInputs(uint8_t* data)
{
static jvs_coin_slots_t default_coin_slot;
uint8_t nr_coin_slots = data[1];
ResponseBuffer.push_back(ReportCode::Handled);
for (int i = 0; i < nr_coin_slots; i++) {
const uint8_t bytesPerCoinSlot = 2;
for (int j = 0; j < bytesPerCoinSlot; j++) {
// If a title asks for more coin slots than we support, pad with dummy data
jvs_coin_slots_t &coin_slot = (i >= JVS_MAX_COINS) ? default_coin_slot : Inputs.coins[i];
uint8_t value
= (j == 0) ? coin_slot.GetByte0()
: (j == 1) ? coin_slot.GetByte1()
: 0; // Pad any remaining bytes with 0, as we don't have that many inputs available
ResponseBuffer.push_back(value);
}
}
return 1;
}
int JvsIo::Jvs_Command_22_ReadAnalogInputs(uint8_t* data)
{
static jvs_analog_input_t default_analog;
uint8_t nr_analog_inputs = data[1];
ResponseBuffer.push_back(ReportCode::Handled);
for (int i = 0; i < nr_analog_inputs; i++) {
const uint8_t bytesPerAnalogInput = 2;
for (int j = 0; j < bytesPerAnalogInput; j++) {
// If a title asks for more analog input than we support, pad with dummy data
jvs_analog_input_t &analog_input = (i >= JVS_MAX_ANALOG) ? default_analog : Inputs.analog[i];
uint8_t value
= (j == 0) ? analog_input.GetByte0()
: (j == 1) ? analog_input.GetByte1()
: 0; // Pad any remaining bytes with 0, as we don't have that many inputs available
ResponseBuffer.push_back(value);
}
}
return 1;
}
int JvsIo::Jvs_Command_32_GeneralPurposeOutput(uint8_t* data)
{
uint8_t banks = data[1];
ResponseBuffer.push_back(ReportCode::Handled);
// TODO: Handle output
// Input data size is 1 byte indicating the number of banks, followed by one byte per bank
return 1 + banks;
}
uint8_t JvsIo::GetByte(uint8_t* &buffer)
{
uint8_t value = *buffer++;
#ifdef DEBUG_JVS_PACKETS
printf(" %02X", value);
#endif
return value;
}
uint8_t JvsIo::GetEscapedByte(uint8_t* &buffer)
{
uint8_t value = GetByte(buffer);
// Special case: 0xD0 is an exception byte that actually returns the next byte + 1
if (value == ESCAPE_BYTE) {
value = GetByte(buffer) + 1;
}
return value;
}
void JvsIo::HandlePacket(jvs_packet_header_t* header, std::vector<uint8_t>& packet)
{
// It's possible for a JVS packet to contain multiple commands, so we must iterate through it
ResponseBuffer.push_back(StatusCode::StatusOkay); // Assume we'll handle the command just fine
for (size_t i = 0; i < packet.size(); i++) {
BroadcastPacket = packet[i] >= 0xF0; // Set a flag when broadcast packet
uint8_t* command_data = &packet[i];
switch (packet[i]) {
// Broadcast Commands
case 0xF0: i += Jvs_Command_F0_Reset(command_data); break;
case 0xF1: i += Jvs_Command_F1_SetDeviceId(command_data); break;
// Init Commands
case 0x10: i += Jvs_Command_10_GetBoardId(); break;
case 0x11: i += Jvs_Command_11_GetCommandFormat(); break;
case 0x12: i += Jvs_Command_12_GetJvsRevision(); break;
case 0x13: i += Jvs_Command_13_GetCommunicationVersion(); break;
case 0x14: i += Jvs_Command_14_GetCapabilities(); break;
case 0x20: i += Jvs_Command_20_ReadSwitchInputs(command_data); break;
case 0x21: i += Jvs_Command_21_ReadCoinInputs(command_data); break;
case 0x22: i += Jvs_Command_22_ReadAnalogInputs(command_data); break;
case 0x32: i += Jvs_Command_32_GeneralPurposeOutput(command_data); break;
default:
// Overwrite the verly-optimistic StatusCode::StatusOkay with Status::Unsupported command
// Don't process any further commands. Existing processed commands must still return their responses.
ResponseBuffer[0] = StatusCode::UnsupportedCommand;
printf("JvsIo::HandlePacket: Unhandled Command %02X\n", packet[i]);
return;
}
}
}
size_t JvsIo::SendPacket(uint8_t* buffer)
{
// Remember where the buffer started (so we can calculate the number of bytes we've handled)
uint8_t* buffer_start = buffer;
// Scan the packet header
jvs_packet_header_t header;
// First, read the sync byte
#ifdef DEBUG_JVS_PACKETS
printf("JvsIo::SendPacket:");
#endif
header.sync = GetByte(buffer); // Do not unescape the sync-byte!
if (header.sync != SYNC_BYTE) {
#ifdef DEBUG_JVS_PACKETS
printf(" [Missing SYNC_BYTE!]\n");
#endif
// If it's wrong, return we've processed (actually, skipped) one byte
return 1;
}
// Read the target and count bytes
header.target = GetEscapedByte(buffer);
header.count = GetEscapedByte(buffer);
// Calculate the checksum
uint8_t actual_checksum = header.target + header.count;
// Decode the payload data
std::vector<uint8_t> packet;
for (int i = 0; i < header.count - 1; i++) { // Note : -1 to avoid adding the checksum byte to the packet
uint8_t value = GetEscapedByte(buffer);
packet.push_back(value);
actual_checksum += value;
}
// Read the checksum from the last byte
uint8_t packet_checksum = GetEscapedByte(buffer);
#ifdef DEBUG_JVS_PACKETS
printf("\n");
#endif
// Verify checksum - skip packet if invalid
ResponseBuffer.clear();
if (packet_checksum != actual_checksum) {
ResponseBuffer.push_back(StatusCode::ChecksumError);
} else {
// If the packet was intended for us, we need to handle it
if (header.target == TARGET_BROADCAST || header.target == DeviceId) {
HandlePacket(&header, packet);
}
}
// Calculate and return the total packet size including header
size_t total_packet_size = buffer - buffer_start;
return total_packet_size;
}
void JvsIo::SendByte(uint8_t* &buffer, uint8_t value)
{
*buffer++ = value;
}
void JvsIo::SendEscapedByte(uint8_t* &buffer, uint8_t value)
{
// Special case: Send an exception byte followed by value - 1
if (value == SYNC_BYTE || value == ESCAPE_BYTE) {
SendByte(buffer, ESCAPE_BYTE);
value--;
}
SendByte(buffer, value);
}
size_t JvsIo::ReceivePacket(uint8_t* buffer)
{
if (ResponseBuffer.empty()) {
return 0;
}
// Build a JVS response packet containing the payload
jvs_packet_header_t header;
header.sync = SYNC_BYTE;
header.target = TARGET_MASTER_DEVICE;
header.count = (uint8_t)ResponseBuffer.size() + 1; // Set data size to payload + 1 checksum byte
// TODO : What if count overflows (meaning : responses are bigger than 255 bytes); Should we split it over multiple packets??
// Remember where the buffer started (so we can calculate the number of bytes we've send)
uint8_t* buffer_start = buffer;
// Send the header bytes
SendByte(buffer, header.sync); // Do not escape the sync byte!
SendEscapedByte(buffer, header.target);
SendEscapedByte(buffer, header.count);
// Calculate the checksum
uint8_t packet_checksum = header.target + header.count;
// Encode the payload data
for (size_t i = 0; i < ResponseBuffer.size(); i++) {
uint8_t value = ResponseBuffer[i];
SendEscapedByte(buffer, value);
packet_checksum += value;
}
// Write the checksum to the last byte
SendEscapedByte(buffer, packet_checksum);
ResponseBuffer.clear();
// Calculate an return the total packet size including header
size_t total_packet_size = buffer - buffer_start;
#ifdef DEBUG_JVS_PACKETS
printf("JvsIo::ReceivePacket:");
for (size_t i = 0; i < total_packet_size; i++) {
printf(" %02X", buffer_start[i]);
}
printf("\n");
#endif
return total_packet_size;
}

215
src/devices/chihiro/JvsIo.h Normal file
View file

@ -0,0 +1,215 @@
// ******************************************************************
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them under the terms of the GNU General Public
// * License as published by the Free Software Foundation; either
// * version 2 of the license, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2019 Luke Usher
// *
// * All rights reserved
// *
// ******************************************************************
#ifndef JVSIO_H
#define JVSIO_H
#include <cstdint>
#include <vector>
#include <string>
typedef struct {
uint8_t sync;
uint8_t target;
uint8_t count;
} jvs_packet_header_t;
#define JVS_MAX_PLAYERS (2)
#define JVS_MAX_ANALOG (8)
#define JVS_MAX_COINS (JVS_MAX_PLAYERS)
typedef struct _jvs_switch_player_inputs_t {
bool start = false;
bool service = false;
bool up = false;
bool down = false;
bool left = false;
bool right = false;
bool button[7] = { false };
uint8_t GetByte0() {
uint8_t value = 0;
value |= start ? 1 << 7 : 0;
value |= service ? 1 << 6 : 0;
value |= up ? 1 << 5 : 0;
value |= down ? 1 << 4 : 0;
value |= left ? 1 << 3 : 0;
value |= right ? 1 << 2 : 0;
value |= button[0] ? 1 << 1 : 0;
value |= button[1] ? 1 << 0 : 0;
return value;
}
uint8_t GetByte1() {
uint8_t value = 0;
value |= button[2] ? 1 << 7 : 0;
value |= button[3] ? 1 << 6 : 0;
value |= button[4] ? 1 << 5 : 0;
value |= button[5] ? 1 << 4 : 0;
value |= button[6] ? 1 << 3 : 0;
return value;
}
} jvs_switch_player_inputs_t;
typedef struct _jvs_switch_system_inputs_t {
bool test = false;
bool tilt1 = false;
bool tilt2 = false;
bool tilt3 = false;
uint8_t GetByte0() {
uint8_t value = 0;
value |= test ? 1 << 7 : 0;
value |= tilt1 ? 1 << 6 : 0;
value |= tilt2 ? 1 << 5 : 0;
value |= tilt3 ? 1 << 4 : 0;
return value;
}
} jvs_switch_system_inputs_t;
typedef struct {
jvs_switch_system_inputs_t system;
jvs_switch_player_inputs_t player[JVS_MAX_PLAYERS];
} jvs_switch_inputs_t;
typedef struct _jvs_analog_input_t {
uint16_t value = 0x8000;
uint8_t GetByte0() {
return (value >> 8) & 0xFF;
}
uint8_t GetByte1() {
return value & 0xFF;
}
} jvs_analog_input_t;
typedef struct _jvs_coin_slots_t {
uint16_t coins = 0;
uint8_t status = 0;
uint8_t GetByte0() {
uint8_t value = 0;
value |= (status << 6) & 0xC0;
value |= (coins >> 8) & 0x3F;
return value;
}
uint8_t GetByte1() {
return coins & 0xFF;
}
} jvs_coin_slots_t;
typedef struct {
jvs_switch_inputs_t switches;
jvs_analog_input_t analog[JVS_MAX_ANALOG];
jvs_coin_slots_t coins[JVS_MAX_COINS];
} jvs_input_states_t;
class JvsIo
{
public:
JvsIo(uint8_t *sense);
size_t SendPacket(uint8_t *buffer);
size_t ReceivePacket(uint8_t *buffer);
uint8_t GetDeviceId();
void Update();
private:
const uint8_t SYNC_BYTE = 0xE0;
const uint8_t ESCAPE_BYTE = 0xD0;
const uint8_t TARGET_MASTER_DEVICE = 0x00;
const uint8_t TARGET_BROADCAST = 0xFF;
uint8_t GetByte(uint8_t *&buffer);
uint8_t GetEscapedByte(uint8_t *&buffer);
void HandlePacket(jvs_packet_header_t *header, std::vector<uint8_t> &packet);
void SendByte(uint8_t *&buffer, uint8_t value);
void SendEscapedByte(uint8_t *&buffer, uint8_t value);
enum StatusCode {
StatusOkay = 1,
UnsupportedCommand = 2,
ChecksumError = 3,
AcknowledgeOverflow = 4,
};
enum ReportCode {
Handled = 1,
NotEnoughParameters = 2,
InvalidParameter = 3,
Busy = 4,
};
enum CapabilityCode {
EndOfCapabilities = 0x00,
// Input capabilities :
PlayerSwitchButtonSets = 0x01,
CoinSlots = 0x02,
AnalogInputs = 0x03,
RotaryInputs = 0x04, // Params : JVS_MAX_ROTARY, 0, 0
KeycodeInputs = 0x05,
ScreenPointerInputs = 0x06, // Params : Xbits, Ybits, JVS_MAX_POINTERS
SwitchInputs = 0x07,
// Output capabilities :
CardSystem = 0x10, // Params : JVS_MAX_CARDS, 0, 0
MedalHopper = 0x11, // Params : max?, 0, 0
GeneralPurposeOutputs = 0x12, // Params : number of outputs, 0, 0
AnalogOutput = 0x13, // Params : channels, 0, 0
CharacterOutput = 0x14, // Params : width, height, type
BackupData = 0x15,
};
// Commands
// These return the additional param bytes used
int Jvs_Command_F0_Reset(uint8_t *data);
int Jvs_Command_F1_SetDeviceId(uint8_t *data);
int Jvs_Command_10_GetBoardId();
int Jvs_Command_11_GetCommandFormat();
int Jvs_Command_12_GetJvsRevision();
int Jvs_Command_13_GetCommunicationVersion();
int Jvs_Command_14_GetCapabilities();
int Jvs_Command_20_ReadSwitchInputs(uint8_t *data);
int Jvs_Command_21_ReadCoinInputs(uint8_t *data);
int Jvs_Command_22_ReadAnalogInputs(uint8_t *data);
int Jvs_Command_32_GeneralPurposeOutput(uint8_t *data);
bool BroadcastPacket; // Set when the last command was a broadcast
uint8_t *pSense = nullptr; // Pointer to Sense line
uint8_t DeviceId = 0; // Device ID assigned by running title
std::vector<uint8_t> ResponseBuffer; // Command Response
// Device info
uint8_t CommandFormatRevision;
uint8_t JvsVersion;
uint8_t CommunicationVersion;
std::string BoardID;
jvs_input_states_t Inputs;
};
extern JvsIo *g_pJvsIo;
#endif

View file

@ -0,0 +1,161 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// ******************************************************************
// * src->devices->chihiro->MediaBoard.cpp
// *
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them under the terms of the GNU General Public
// * License as published by the Free Software Foundation; either
// * version 2 of the license, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2019 Luke Usher
// *
// * All rights reserved
// *
// ******************************************************************
#include "MediaBoard.h"
#include <cstdio>
#include <string>
#define _XBOXKRNL_DEFEXTRN_
#define LOG_PREFIX CXBXR_MODULE::JVS // TODO: XBAM
#include <core\kernel\exports\xboxkrnl.h>
#include "core\kernel\init\\CxbxKrnl.h"
#include "core\kernel\exports\EmuKrnl.h" // for HalSystemInterrupts
chihiro_bootid &MediaBoard::GetBootId()
{
return BootID;
}
void MediaBoard::SetMountPath(std::string path)
{
m_MountPath = path;
// Load Boot.id from file
FILE* bootidFile = fopen((path+"/boot.id").c_str(), "rb");
if (bootidFile == nullptr) {
CxbxrAbort("Could not open Chihiro boot.id");
}
fread(&BootID, 1, sizeof(chihiro_bootid), bootidFile);
fclose(bootidFile);
}
uint32_t MediaBoard::LpcRead(uint32_t addr, int size)
{
switch (addr) {
case 0x401E: return 0x0317; // Firmware Version Number
case 0x4020: return 0x00A0; // XBAM String (SEGABOOT reports Media Board is not present if these values change)
case 0x4022: return 0x4258; // Continued
case 0x4024: return 0x4D41; // Continued
// TODO: Find a way to make the switch between Type-1 and Type-3 (internal value holder maybe?)
case 0x40F0: return 0x0000; // Media Board Type (Type-1 vs Type-3), 0x0000 = Type-1, 0x0100 = Type-3
case 0x40F4: return 0x03; // 1GB
}
printf("MediaBoard::LpcRead: Unknown Addr %08X\n", addr);
return 0;
}
void MediaBoard::LpcWrite(uint32_t addr, uint32_t value, int size)
{
switch (addr) {
case 0x40E1: HalSystemInterrupts[10].Assert(false); break;
default:
printf("MediaBoard::LpcWrite: Unknown Addr %08X = %08X\n", addr, value);
break;
}
}
void MediaBoard::ComRead(uint32_t offset, void* buffer, uint32_t length)
{
// Copy the current read buffer to the output
memcpy(buffer, readBuffer, 0x20);
}
void MediaBoard::ComWrite(uint32_t offset, void* buffer, uint32_t length)
{
// Instant replies cause race conditions, software seems to expect at least a little delay
Sleep(100);
if (offset == 0x900000) { // Some kind of reset?
memcpy(readBuffer, buffer, 0x20);
return;
} else if (offset == 0x900200) { // Command Sector
// Copy the written data to our internal, so we don't trash the original data
memcpy(writeBuffer, buffer, 0x20);
// Create accessor pointers
auto inputBuffer16 = (uint16_t*)writeBuffer;
auto inputBuffer32 = (uint32_t*)writeBuffer;
auto outputBuffer16 = (uint16_t*)readBuffer;
auto outputBuffer32 = (uint32_t*)readBuffer;
// If no command word was specified, do nothing
if (inputBuffer16[0] == 0) {
return;
}
// First word of output gets set to first word of the input, second word gets OR'D with ACK
outputBuffer16[0] = inputBuffer16[0];
outputBuffer16[1] = inputBuffer16[1] | 0x8000; // ACK?
// Read the given Command and handle it
uint32_t command = inputBuffer16[1];
switch (command) {
case MB_CMD_DIMM_SIZE:
outputBuffer32[1] = 1024 * ONE_MB;
break;
case MB_CMD_STATUS:
outputBuffer32[1] = MB_STATUS_READY;
outputBuffer32[2] = 100; // Load/Test Percentage (0-100)
break;
case MB_CMD_FIRMWARE_VERSION:
outputBuffer32[1] = 0x0317;
break;
case MB_CMD_SYSTEM_TYPE:
outputBuffer32[1] = MB_SYSTEM_TYPE_DEVELOPER | MB_SYSTEM_TYPE_GDROM;
break;
case MB_CMD_SERIAL_NUMBER:
memcpy(&outputBuffer32[1], "A89E-25A47354512", 17);
break;
case MB_CMD_HARDWARE_TEST: {
uint32_t testType = inputBuffer32[1];
xbox::addr_xt resultWritePtr = inputBuffer32[2];
outputBuffer32[1] = inputBuffer32[1];
printf("Perform Test Type %X, place result at %08X\n", testType, resultWritePtr);
// For now, just pretend we did the test and was successful
// TODO: How to report percentage? Get's stuck on "CHECKING 0% but still shows "TEST OK"
memcpy((void*)resultWritePtr, "TEST OK", 8);
} break;
default: printf("Unhandled MediaBoard Command: %04X\n", command);
}
// Clear the command bytes
inputBuffer16[0] = 0;
inputBuffer16[1] = 0;
// Trigger LPC Interrupt
HalSystemInterrupts[10].Assert(true);
return;
}
printf("Unhandled MediaBoard mbcom: offset %08X\n", offset);
}

View file

@ -0,0 +1,98 @@
// ******************************************************************
// * src->devices->chihiro->MediaBoard.h
// *
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them under the terms of the GNU General Public
// * License as published by the Free Software Foundation; either
// * version 2 of the license, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2019 Luke Usher
// *
// * All rights reserved
// *
// ******************************************************************
#ifndef MEDIABOARD_H
#define MEDIABOARD_H
#include <cstdint>
#include <string>
#define MB_CMD_DIMM_SIZE 0x0001
#define MB_CMD_STATUS 0x0100
#define MB_STATUS_INIT 0
#define MB_STATUS_CHECKING_NETWORK 1
#define MB_STATUS_SYSTEM_DISC 2
#define MB_STATUS_TESTING 3
#define MB_STATUS_LOADING 4
#define MB_STATUS_READY 5
#define MB_STATUS_ERROR 6
#define MB_CMD_FIRMWARE_VERSION 0x0101
#define MB_CMD_SYSTEM_TYPE 0x0102
#define MB_SYSTEM_TYPE_DEVELOPER 0x8000
#define MB_SYSTEM_TYPE_GDROM 0x0001
#define MB_CMD_SERIAL_NUMBER 0x0103
#define MB_CMD_HARDWARE_TEST 0x0301
#define MB_CHIHIRO_REGION_FLAG_JAPAN 0x2
#define MB_CHIHIRO_REGION_FLAG_USA 0x4
#define MB_CHIHIRO_REGION_FLAG_EXPORT 0x8
typedef struct {
char magic[4]; // 0x00 (Always BTID)
uint32_t unknown0[3];
uint32_t unknown1[4];
char mediaboardType[4]; // 0x20 (XBAM for Chihiro)
uint32_t unknown2;
uint16_t year; // 0x28
uint8_t month; // 0x2A
uint8_t day; // 0x2B
uint8_t videoMode; // 0x2C unknown bitmask, resolutions + horizontal/vertical
uint8_t unknown3;
uint8_t type3Compatible; // 0x2E (Type-3 compatible titles have this set to 1)
uint8_t unknown4;
char gameId[8]; // 0x30
uint32_t regionFlags; // 0x38
uint32_t unknown6[9];
char manufacturer[0x20]; // 0x60
char gameName[0x20]; // 0x80
char gameExecutable[0x20]; // 0xA0
char testExecutable[0x20]; // 0xC0
char creditTypes[8][0x20]; // 0xE0
} chihiro_bootid;
class MediaBoard
{
public:
void SetMountPath(std::string path);
// LPC IO handlers
uint32_t LpcRead(uint32_t addr, int size);
void LpcWrite(uint32_t addr, uint32_t value, int size);
// Mbcom partition handlers
void ComRead(uint32_t offset, void* buffer, uint32_t length);
void ComWrite(uint32_t offset, void* buffer, uint32_t length);
chihiro_bootid &GetBootId();
private:
uint8_t readBuffer[512];
uint8_t writeBuffer[512];
std::string m_MountPath;
chihiro_bootid BootID;
};
#endif

View file

@ -476,15 +476,18 @@ void EmuNVNet_Write(xbox::addr_xt addr, uint32_t value, int size)
}
std::thread NVNetRecvThread;
static void NVNetRecvThreadProc(NvNetState_t *s)
void NVNetRecvThreadProc()
{
// NOTE: profiling shows that the winpcap function can take up to 1/6th of the total cpu time of the loader process, so avoid placing
// this function in system_events
g_AffinityPolicy->SetAffinityOther();
uint8_t packet[65536];
static std::unique_ptr<uint8_t[]> packet(new uint8_t[65536]);
while (true) {
int size = g_NVNet->PCAPReceive(packet, 65536);
int size = g_NVNet->PCAPReceive(packet.get(), 65536);
if (size > 0) {
EmuNVNet_DMAPacketToGuest(packet, size);
}
EmuNVNet_DMAPacketToGuest(packet.get(), size);
}
_mm_pause();
}
}
@ -527,7 +530,7 @@ void NVNetDevice::Init()
};
PCAPInit();
NVNetRecvThread = std::thread(NVNetRecvThreadProc, &NvNetState);
NVNetRecvThread = std::thread(NVNetRecvThreadProc);
}
void NVNetDevice::Reset()

View file

@ -279,11 +279,6 @@ OHCI::OHCI(USBDevice* UsbObj)
OHCI_StateReset();
}
void OHCI::OHCI_FrameBoundaryWrapper(void* pVoid)
{
static_cast<OHCI*>(pVoid)->OHCI_FrameBoundaryWorker();
}
void OHCI::OHCI_FrameBoundaryWorker()
{
OHCI_HCCA hcca;
@ -358,7 +353,7 @@ void OHCI::OHCI_FrameBoundaryWorker()
}
// Do SOF stuff here
OHCI_SOF(false);
OHCI_SOF();
// Writeback HCCA
if (OHCI_WriteHCCA(m_Registers.HcHCCA, &hcca)) {
@ -877,32 +872,23 @@ void OHCI::OHCI_StateReset()
void OHCI::OHCI_BusStart()
{
// Create the EOF timer.
m_pEOFtimer = Timer_Create(OHCI_FrameBoundaryWrapper, this, "", false);
m_pEOFtimer = true;
EmuLog(LOG_LEVEL::DEBUG, "Operational event");
// SOF event
OHCI_SOF(true);
OHCI_SOF();
}
void OHCI::OHCI_BusStop()
{
if (m_pEOFtimer) {
// Delete existing EOF timer
Timer_Exit(m_pEOFtimer);
}
m_pEOFtimer = nullptr;
m_pEOFtimer = false;
}
void OHCI::OHCI_SOF(bool bCreate)
void OHCI::OHCI_SOF()
{
// set current SOF time
m_SOFtime = GetTime_NS(m_pEOFtimer);
// make timer expire at SOF + 1 ms from now
if (bCreate) {
Timer_Start(m_pEOFtimer, m_UsbFrameTime);
}
m_SOFtime = get_now();
OHCI_SetInterrupt(OHCI_INTR_SF);
}
@ -1254,6 +1240,23 @@ void OHCI::OHCI_WriteRegister(xbox::addr_xt Addr, uint32_t Value)
}
}
uint64_t OHCI::OHCI_next(uint64_t now)
{
if (m_pEOFtimer) {
constexpr uint64_t ohci_period = 1000;
uint64_t next = m_SOFtime + ohci_period;
if (now >= next) {
OHCI_FrameBoundaryWorker();
return ohci_period;
}
return m_SOFtime + ohci_period - now; // time remaining until EOF
}
return -1;
}
void OHCI::OHCI_UpdateInterrupt()
{
if ((m_Registers.HcInterrupt & OHCI_INTR_MIE) && (m_Registers.HcInterruptStatus & m_Registers.HcInterrupt)) {
@ -1278,7 +1281,7 @@ uint32_t OHCI::OHCI_GetFrameRemaining()
}
// Being in USB operational state guarantees that m_pEOFtimer and m_SOFtime were set already
ticks = GetTime_NS(m_pEOFtimer) - m_SOFtime;
ticks = get_now() - m_SOFtime;
// Avoid Muldiv64 if possible
if (ticks >= m_UsbFrameTime) {

View file

@ -145,6 +145,10 @@ class OHCI
uint32_t OHCI_ReadRegister(xbox::addr_xt Addr);
// write a register
void OHCI_WriteRegister(xbox::addr_xt Addr, uint32_t Value);
// calculates when the next EOF is due
uint64_t OHCI_next(uint64_t now);
// EOF callback function
void OHCI_FrameBoundaryWorker();
private:
@ -153,7 +157,7 @@ class OHCI
// all the registers available in the OHCI standard
OHCI_Registers m_Registers;
// end-of-frame timer
TimerObject* m_pEOFtimer = nullptr;
bool m_pEOFtimer = false;
// time at which a SOF was sent
uint64_t m_SOFtime;
// the duration of a usb frame
@ -173,10 +177,6 @@ class OHCI
// indicates if there is a pending asynchronous packet to process
int m_AsyncComplete = 0;
// EOF callback wrapper
static void OHCI_FrameBoundaryWrapper(void* pVoid);
// EOF callback function
void OHCI_FrameBoundaryWorker();
// inform the HCD that we got a problem here...
void OHCI_FatalError();
// initialize packet struct
@ -189,8 +189,8 @@ class OHCI
void OHCI_BusStart();
// stop sending SOF tokens across the usb bus
void OHCI_BusStop();
// generate a SOF event, and start a timer for EOF
void OHCI_SOF(bool bCreate);
// generate a SOF event
void OHCI_SOF();
// change interrupt status
void OHCI_UpdateInterrupt();
// fire an interrupt

View file

@ -82,6 +82,7 @@ DEVICE_WRITE32(PRAMDAC)
} else {
d->pramdac.core_clock_freq = (NV2A_CRYSTAL_FREQ * n)
/ (1 << p) / m;
d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq);
}
break;

View file

@ -35,17 +35,13 @@
#include "common\util\CxbxUtil.h"
#define NANOSECONDS_PER_SECOND 1000000000
/* PTIMER - time measurement and time-based alarms */
static uint64_t ptimer_get_clock(NV2AState * d)
static uint64_t ptimer_get_clock(NV2AState *d)
{
// Get time in nanoseconds
uint64_t time = std::chrono::duration<uint64_t, std::nano>(std::chrono::steady_clock::now().time_since_epoch()).count();
return Muldiv64(Muldiv64(time,
return Muldiv64(Muldiv64(get_now(),
(uint32_t)d->pramdac.core_clock_freq, // TODO : Research how this can be updated to accept uint64_t
NANOSECONDS_PER_SECOND), // Was CLOCKS_PER_SEC
SCALE_S_IN_US), // Was CLOCKS_PER_SEC
d->ptimer.denominator,
d->ptimer.numerator);
}
@ -91,6 +87,13 @@ DEVICE_WRITE32(PTIMER)
break;
case NV_PTIMER_INTR_EN_0:
d->ptimer.enabled_interrupts = value;
if (d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) {
d->ptimer_last = get_now();
d->ptimer_active = true;
}
else if ((d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) == 0) {
d->ptimer_active = false;
}
update_irq(d);
break;
case NV_PTIMER_DENOMINATOR:
@ -101,6 +104,7 @@ DEVICE_WRITE32(PTIMER)
break;
case NV_PTIMER_ALARM_0:
d->ptimer.alarm_time = value;
d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq);
break;
default:
//DEVICE_WRITE32_REG(ptimer); // Was : DEBUG_WRITE32_UNHANDLED(PTIMER);

View file

@ -51,6 +51,7 @@
#include "core\kernel\init\CxbxKrnl.h" // For XBOX_MEMORY_SIZE, DWORD, etc
#include "core\kernel\support\Emu.h"
#include "core\kernel\support\NativeHandle.h"
#include "core\kernel\exports\EmuKrnl.h"
#include <backends/imgui_impl_win32.h>
#include <backends/imgui_impl_opengl3.h>
@ -58,6 +59,7 @@
#include "core\hle\Intercept.hpp"
#include "common/win32/Threads.h"
#include "Logging.h"
#include "Timer.h"
#include "vga.h"
#include "nv2a.h" // For NV2AState
@ -128,6 +130,14 @@ static void update_irq(NV2AState *d)
d->pmc.pending_interrupts &= ~NV_PMC_INTR_0_PVIDEO;
}
/* PTIMER */
if (d->ptimer.pending_interrupts & d->ptimer.enabled_interrupts) {
d->pmc.pending_interrupts |= NV_PMC_INTR_0_PTIMER;
}
else {
d->pmc.pending_interrupts &= ~NV_PMC_INTR_0_PTIMER;
}
/* TODO : PBUS * /
if (d->pbus.pending_interrupts & d->pbus.enabled_interrupts) {
d->pmc.pending_interrupts |= NV_PMC_INTR_0_PBUS;
@ -319,8 +329,8 @@ const NV2ABlockInfo* EmuNV2A_Block(xbox::addr_xt addr)
// HACK: Until we implement VGA/proper interrupt generation
// we simulate VBLANK by calling the interrupt at 60Hz
std::thread vblank_thread;
extern std::chrono::steady_clock::time_point GetNextVBlankTime();
extern void hle_vblank();
void _check_gl_reset()
{
@ -1097,25 +1107,27 @@ void NV2ADevice::UpdateHostDisplay(NV2AState *d)
}
// TODO: Fix this properly
static void nv2a_vblank_thread(NV2AState *d)
template<bool should_update_hle>
void nv2a_vblank_interrupt(void *opaque)
{
g_AffinityPolicy->SetAffinityOther();
CxbxSetThreadName("Cxbx NV2A VBLANK");
auto nextVBlankTime = GetNextVBlankTime();
NV2AState *d = static_cast<NV2AState *>(opaque);
while (!d->exiting) {
// Handle VBlank
if (std::chrono::steady_clock::now() > nextVBlankTime) {
d->pcrtc.pending_interrupts |= NV_PCRTC_INTR_0_VBLANK;
update_irq(d);
nextVBlankTime = GetNextVBlankTime();
if (!d->exiting) [[likely]] {
d->pcrtc.pending_interrupts |= NV_PCRTC_INTR_0_VBLANK;
update_irq(d);
// TODO: We should swap here for the purposes of supporting overlays + direct framebuffer access
// But it causes crashes on AMD hardware for reasons currently unknown...
//NV2ADevice::UpdateHostDisplay(d);
// trigger the gpu interrupt if it was asserted in update_irq
if (g_bEnableAllInterrupts && HalSystemInterrupts[3].IsPending() && EmuInterruptList[3] && EmuInterruptList[3]->Connected) {
HalSystemInterrupts[3].Trigger(EmuInterruptList[3]);
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// TODO: We should swap here for the purposes of supporting overlays + direct framebuffer access
// But it causes crashes on AMD hardware for reasons currently unknown...
//NV2ADevice::UpdateHostDisplay(d);
if constexpr (should_update_hle) {
hle_vblank();
}
}
}
@ -1189,8 +1201,8 @@ void NV2ADevice::Init()
d->vram_ptr = (uint8_t*)PHYSICAL_MAP_BASE;
d->vram_size = g_SystemMaxMemory;
d->pramdac.core_clock_coeff = 0x00011c01; /* 189MHz...? */
d->pramdac.core_clock_freq = 189000000;
d->pramdac.core_clock_coeff = 0x00011C01; /* 233MHz...? */
d->pramdac.core_clock_freq = 233333324;
d->pramdac.memory_clock_coeff = 0;
d->pramdac.video_clock_coeff = 0x0003C20D; /* 25182Khz...? */
@ -1202,7 +1214,13 @@ void NV2ADevice::Init()
pvideo_init(d);
}
vblank_thread = std::thread(nv2a_vblank_thread, d);
d->vblank_last = get_now();
if (bLLE_GPU) {
d->vblank_cb = nv2a_vblank_interrupt<false>;
}
else {
d->vblank_cb = nv2a_vblank_interrupt<true>;
}
qemu_mutex_init(&d->pfifo.pfifo_lock);
qemu_cond_init(&d->pfifo.puller_cond);
@ -1227,9 +1245,8 @@ void NV2ADevice::Reset()
qemu_cond_broadcast(&d->pfifo.pusher_cond);
d->pfifo.puller_thread.join();
d->pfifo.pusher_thread.join();
qemu_mutex_destroy(&d->pfifo.pfifo_lock); // Cbxbx addition
qemu_mutex_destroy(&d->pfifo.pfifo_lock); // Cxbxr addition
if (d->pgraph.opengl_enabled) {
vblank_thread.join();
pvideo_destroy(d);
}
@ -1377,3 +1394,45 @@ int NV2ADevice::GetFrameWidth(NV2AState* d)
return width;
}
uint64_t NV2ADevice::vblank_next(uint64_t now)
{
// TODO: this should use a vblank period of 20ms when we are in 50Hz PAL mode
constexpr uint64_t vblank_period = 16.6666666667 * 1000;
uint64_t next = m_nv2a_state->vblank_last + vblank_period;
if (now >= next) {
m_nv2a_state->vblank_cb(m_nv2a_state);
m_nv2a_state->vblank_last = get_now();
return vblank_period;
}
return m_nv2a_state->vblank_last + vblank_period - now; // time remaining until next vblank
}
uint64_t NV2ADevice::ptimer_next(uint64_t now)
{
// Test case: Dead or Alive Ultimate uses this when in PAL50 mode only
if (m_nv2a_state->ptimer_active) {
const uint64_t ptimer_period = m_nv2a_state->ptimer_period;
uint64_t next = m_nv2a_state->ptimer_last + ptimer_period;
if (now >= next) {
if (!m_nv2a_state->exiting) [[likely]] {
m_nv2a_state->ptimer.pending_interrupts |= NV_PTIMER_INTR_0_ALARM;
update_irq(m_nv2a_state);
// trigger the gpu interrupt if it was asserted in update_irq
if (g_bEnableAllInterrupts && HalSystemInterrupts[3].IsPending() && EmuInterruptList[3] && EmuInterruptList[3]->Connected) {
HalSystemInterrupts[3].Trigger(EmuInterruptList[3]);
}
}
m_nv2a_state->ptimer_last = get_now();
return ptimer_period;
}
return m_nv2a_state->ptimer_last + ptimer_period - now; // time remaining until next ptimer interrupt
}
return -1;
}

View file

@ -108,6 +108,10 @@ public:
static int GetFrameWidth(NV2AState *d);
static int GetFrameHeight(NV2AState *d);
uint64_t vblank_next(uint64_t now);
uint64_t ptimer_next(uint64_t now);
private:
NV2AState *m_nv2a_state;
};

Some files were not shown because too many files have changed in this diff Show more