Compare commits
67 Commits
CI-0007d20
...
master
Author | SHA1 | Date |
---|---|---|
![]() |
ec9934af2d | |
![]() |
ce15f50848 | |
![]() |
b1df891433 | |
![]() |
5355e08b30 | |
![]() |
7762e883a8 | |
![]() |
cada16125d | |
![]() |
eecc1ac1f1 | |
![]() |
2d6696451b | |
![]() |
dd36dd598c | |
![]() |
87634a2e27 | |
![]() |
6f32d89545 | |
![]() |
ec0c288bc4 | |
![]() |
8bfbcb56fd | |
![]() |
8965d2443b | |
![]() |
b33ed95c5b | |
![]() |
8ee17b512c | |
![]() |
50334cbc31 | |
![]() |
41454b8c26 | |
![]() |
204dcf8801 | |
![]() |
77c63ceec3 | |
![]() |
2cfaba893e | |
![]() |
17b0cb81d4 | |
![]() |
daa6a816ff | |
![]() |
6caf3ea679 | |
![]() |
9a58823b70 | |
![]() |
3edd8d168b | |
![]() |
f894d31332 | |
![]() |
9241bec768 | |
![]() |
c50a0c5c7d | |
![]() |
87bab04932 | |
![]() |
ad6769bbf3 | |
![]() |
0e63131fc3 | |
![]() |
889040c56a | |
![]() |
86542c9f2e | |
![]() |
c9edbd1003 | |
![]() |
ebb122f2a0 | |
![]() |
c158a472ff | |
![]() |
6961d1c7a1 | |
![]() |
2f7cfe7e95 | |
![]() |
46d0173673 | |
![]() |
c7b028b3e7 | |
![]() |
3d12edc77d | |
![]() |
08ab4b9164 | |
![]() |
4fca5c7007 | |
![]() |
e26f20108a | |
![]() |
8475124e5b | |
![]() |
9b2ae106e5 | |
![]() |
b3bfeca3a8 | |
![]() |
b77a13b708 | |
![]() |
1b5e111ae3 | |
![]() |
1504a75a46 | |
![]() |
87496ab873 | |
![]() |
5b37a7ec21 | |
![]() |
639f42c318 | |
![]() |
8d92992a6b | |
![]() |
7323eed73e | |
![]() |
b47c1f195c | |
![]() |
7c73bfc525 | |
![]() |
1b4a3bb54f | |
![]() |
750d202fa8 | |
![]() |
e7bca5e1bf | |
![]() |
937ab9e1c2 | |
![]() |
8006f55cf3 | |
![]() |
1828ddfd6f | |
![]() |
bc42cfaa6b | |
![]() |
b1235b7733 | |
![]() |
1615ecc976 |
|
@ -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/**'
|
||||
|
|
|
@ -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@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 }} -DBUILD_CXBXR_DEBUGGER=ON
|
||||
- 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 }}
|
||||
|
@ -59,7 +61,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Re-zip artifacts
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
@ -346,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"
|
||||
|
@ -376,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"
|
||||
|
@ -425,6 +430,12 @@ file (GLOB CXBXR_SOURCE_EMU
|
|||
"${CXBXR_ROOT_DIR}/src/common/ReserveAddressRanges.cpp"
|
||||
)
|
||||
|
||||
|
||||
option(BUILD_CXBXR_DEBUGGER "Build cxbxr-debugger tool (with cheat table support)")
|
||||
if(BUILD_CXBXR_DEBUGGER)
|
||||
message(DEPRECATION "The Cxbxr-Debugger tool will eventually be removed from the upstream branch.")
|
||||
endif()
|
||||
|
||||
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/cxbx")
|
||||
|
||||
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/cxbxr-ldr")
|
||||
|
@ -449,7 +460,7 @@ endif()
|
|||
|
||||
# Check if generator is Visual Studio then enable Cxbxr-Debugger project.
|
||||
# Since C# is currently supported with Visual Studio for now.
|
||||
if(${CMAKE_GENERATOR} MATCHES "Visual Studio ([^9]|[9][0-9])")
|
||||
if(${CMAKE_GENERATOR} MATCHES "Visual Studio ([^9]|[9][0-9])" AND BUILD_CXBXR_DEBUGGER)
|
||||
# Issues with compile (the same with develop branch) and
|
||||
# for some reason did not put the files into virtual folder?
|
||||
# Might need to put the list in the source folder for workaround fix.
|
||||
|
|
10
README.md
10
README.md
|
@ -89,6 +89,7 @@ Don't open `CMakeLists.txt` from Visual Studio, as it won't generate files in th
|
|||
1. [Visual Studio](https://visualstudio.microsoft.com/downloads/) 2022
|
||||
* C++ and C# desktop development
|
||||
* Windows Universal CRT SDK
|
||||
* Windows 11 SDK (10.0.22621.0) or later
|
||||
* C++ CMake tools for Windows
|
||||
* *Optional if CMake is installed*
|
||||
* [Microsoft Child Process Debugging Power Tool](https://marketplace.visualstudio.com/items?itemName=vsdbgplat.MicrosoftChildProcessDebuggingPowerTool)
|
||||
|
@ -96,10 +97,11 @@ Don't open `CMakeLists.txt` from Visual Studio, as it won't generate files in th
|
|||
##### Generate Visual Studio files
|
||||
1. If you don't have CMake installed, open `___ Native Tools Command Prompt for VS 20##`.
|
||||
2. `cd` to the Cxbx-Reloaded directory.
|
||||
3. Run these commands.
|
||||
1. `mkdir build & cd build`
|
||||
2. `cmake .. -G "Visual Studio 17 2022" -A Win32`
|
||||
* VS2022 17.0 or later is required.
|
||||
3. Run the following command: `cmake -B build -G "Visual Studio 17 2022" -A Win32` \
|
||||
**NOTES**:
|
||||
* VS2022 17.0 or later is required.
|
||||
* To build the Cxbx-Reloaded Debugger tool, add the variable `-DBUILD_CXBXR_DEBUGGER=ON` to the above command.
|
||||
* _This debugger tool is deprecated and will be eventually removed, please use the Visual Studio debugger instead._
|
||||
4. Open `Cxbx-Reloaded.sln` from the `build` directory.
|
||||
5. Select the Release configuration, then click Build.
|
||||
* Debug builds are **significantly slower, and only for developers**.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b424665e0899769b200231ba943353a5fee1b6b6
|
||||
Subproject commit fa24d868ac2f8fd558e4e914c9863411245db8fd
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3f86a95c0784d73ce6815237ec33ed25f233b643
|
|
@ -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}
|
||||
)
|
||||
|
@ -184,7 +189,7 @@ install(TARGETS ${PROJECT_NAME}
|
|||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
if(${CMAKE_GENERATOR} MATCHES "Visual Studio ([^9]|[9][0-9])")
|
||||
if(${CMAKE_GENERATOR} MATCHES "Visual Studio ([^9]|[9][0-9])" AND BUILD_CXBXR_DEBUGGER)
|
||||
add_dependencies(cxbx cxbxr-debugger)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
this.windowsMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
this.lblStatusDeprecate = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.lblStatus = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.menuStrip.SuspendLayout();
|
||||
this.statusStrip1.SuspendLayout();
|
||||
|
@ -132,18 +133,29 @@
|
|||
//
|
||||
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(24, 24);
|
||||
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.lblStatusDeprecate,
|
||||
this.lblStatus});
|
||||
this.statusStrip1.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.VerticalStackWithOverflow;
|
||||
this.statusStrip1.Location = new System.Drawing.Point(0, 339);
|
||||
this.statusStrip1.Name = "statusStrip1";
|
||||
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 9, 0);
|
||||
this.statusStrip1.Size = new System.Drawing.Size(734, 22);
|
||||
this.statusStrip1.Size = new System.Drawing.Size(734, 61);
|
||||
this.statusStrip1.TabIndex = 2;
|
||||
this.statusStrip1.Text = "statusStrip1";
|
||||
//
|
||||
// lblStatusDeprecate
|
||||
//
|
||||
this.lblStatusDeprecate.BackColor = System.Drawing.Color.Yellow;
|
||||
this.lblStatusDeprecate.Name = "lblStatusDeprecate";
|
||||
this.lblStatusDeprecate.Size = new System.Drawing.Size(723, 15);
|
||||
this.lblStatusDeprecate.Spring = true;
|
||||
this.lblStatusDeprecate.Text = "WARNING: cxbxr-debugger will eventually be removed from upstream branch.";
|
||||
this.lblStatusDeprecate.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// lblStatus
|
||||
//
|
||||
this.lblStatus.Name = "lblStatus";
|
||||
this.lblStatus.Size = new System.Drawing.Size(39, 17);
|
||||
this.lblStatus.Size = new System.Drawing.Size(723, 15);
|
||||
this.lblStatus.Text = "Ready";
|
||||
//
|
||||
// CxbxDebuggerMain
|
||||
|
@ -180,6 +192,7 @@
|
|||
private System.Windows.Forms.ToolStripMenuItem miSuspend;
|
||||
private System.Windows.Forms.ToolStripMenuItem miResume;
|
||||
private System.Windows.Forms.StatusStrip statusStrip1;
|
||||
private System.Windows.Forms.ToolStripStatusLabel lblStatusDeprecate;
|
||||
private System.Windows.Forms.ToolStripStatusLabel lblStatus;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -90,6 +90,7 @@ static struct {
|
|||
const char* DataCustomLocation = "DataCustomLocation";
|
||||
const char* IgnoreInvalidXbeSig = "IgnoreInvalidXbeSig";
|
||||
const char *IgnoreInvalidXbeSec = "IgnoreInvalidXbeSec";
|
||||
const char* ConsoleTypeToggle = "ConsoleTypeToggle";
|
||||
} sect_gui_keys;
|
||||
|
||||
static const char* section_core = "core";
|
||||
|
@ -342,6 +343,8 @@ bool Settings::LoadConfig()
|
|||
m_gui.bIgnoreInvalidXbeSig = m_si.GetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSig, /*Default=*/false);
|
||||
m_gui.bIgnoreInvalidXbeSec = m_si.GetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSec, /*Default=*/false);
|
||||
|
||||
m_gui.ConsoleTypeToggle = (EMU_CONSOLE_TYPE)m_si.GetLongValue(section_gui, sect_gui_keys.ConsoleTypeToggle, /*Default=*/EMU_CONSOLE_TYPE_AUTO);
|
||||
|
||||
// ==== GUI End =============
|
||||
|
||||
// ==== Core Begin ==========
|
||||
|
@ -588,6 +591,8 @@ bool Settings::Save(std::string file_path)
|
|||
m_si.SetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSig, m_gui.bIgnoreInvalidXbeSig, nullptr, true);
|
||||
m_si.SetBoolValue(section_gui, sect_gui_keys.IgnoreInvalidXbeSec, m_gui.bIgnoreInvalidXbeSec, nullptr, true);
|
||||
|
||||
m_si.SetLongValue(section_gui, sect_gui_keys.ConsoleTypeToggle, m_gui.ConsoleTypeToggle, nullptr, true, true);
|
||||
|
||||
// ==== GUI End =============
|
||||
|
||||
// ==== Core Begin ==========
|
||||
|
|
|
@ -46,6 +46,14 @@ extern uint16_t g_LibVersion_DSOUND;
|
|||
"Invalid "#type" size, please verify structure is align, not adding new member, or is using placeholder reserves." \
|
||||
" Otherwise, please perform versioning upgrade and update "#type" sizeof check."
|
||||
|
||||
// Toggle emulation console mode.
|
||||
typedef enum _EMU_CONSOLE_TYPE {
|
||||
EMU_CONSOLE_TYPE_AUTO = 0,
|
||||
EMU_CONSOLE_TYPE_RETAIL = 1,
|
||||
EMU_CONSOLE_TYPE_DEVKIT = 2,
|
||||
EMU_CONSOLE_TYPE_CHIHIRO = 3,
|
||||
} EMU_CONSOLE_TYPE;
|
||||
|
||||
// Cxbx-Reloaded's data storage location.
|
||||
typedef enum _CXBX_DATA {
|
||||
CXBX_DATA_INVALID = -1,
|
||||
|
@ -91,6 +99,7 @@ public:
|
|||
std::string szCustomLocation = "";
|
||||
bool bIgnoreInvalidXbeSig;
|
||||
bool bIgnoreInvalidXbeSec;
|
||||
unsigned int ConsoleTypeToggle;
|
||||
} m_gui;
|
||||
|
||||
// Core settings
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#ifndef CXBXUTIL_H
|
||||
#define CXBXUTIL_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include "xbox_types.h"
|
||||
#include "Cxbx.h"
|
||||
|
|
|
@ -60,6 +60,15 @@ bool hasKey(std::string key)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Delete the key if it exist
|
||||
void DeleteKey(std::string key)
|
||||
{
|
||||
auto found = g_cli_configs.find(key);
|
||||
if (found != g_cli_configs.end()) {
|
||||
g_cli_configs.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
// Generic getter
|
||||
bool GetValue(const std::string key, std::string* value)
|
||||
{
|
||||
|
@ -179,4 +188,12 @@ void SetSystemType(const std::string value)
|
|||
}
|
||||
}
|
||||
|
||||
void ClearSystemType()
|
||||
{
|
||||
// Clear any system types key existence.
|
||||
DeleteKey(cli_config::system_retail);
|
||||
DeleteKey(cli_config::system_devkit);
|
||||
DeleteKey(cli_config::system_chihiro);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,5 +57,6 @@ long long GetSessionID();
|
|||
void SetLoad(const std::string value);
|
||||
|
||||
void SetSystemType(const std::string value);
|
||||
void ClearSystemType();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#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"
|
||||
|
@ -160,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;
|
||||
|
@ -169,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;
|
||||
|
@ -229,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);
|
||||
|
@ -629,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
|
||||
{
|
||||
|
@ -1706,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;
|
||||
}
|
||||
|
@ -1723,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)
|
||||
|
@ -1930,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)
|
||||
|
@ -3163,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);
|
||||
|
||||
|
@ -6541,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
|
||||
// ******************************************************************
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
void LookupTrampolinesD3D();
|
||||
|
||||
// initialize render window
|
||||
extern void CxbxInitWindow(bool bFullInit);
|
||||
extern void CxbxInitWindow();
|
||||
|
||||
void CxbxUpdateNativeD3DResources();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 ®ion = (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);
|
||||
}
|
|
@ -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
|
|
@ -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"
|
||||
|
@ -66,7 +67,7 @@ std::map<const std::string, const xbox_patch_t> g_PatchTable = {
|
|||
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),
|
||||
|
@ -183,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),
|
||||
|
@ -375,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;
|
||||
|
|
|
@ -960,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);
|
||||
}
|
||||
|
||||
// ******************************************************************
|
||||
|
|
|
@ -605,6 +605,9 @@ XBSYSAPI EXPORTNUM(352) void_xt NTAPI RtlRip
|
|||
PCHAR Message
|
||||
);
|
||||
|
||||
void_xt RtlInitSystem();
|
||||
extern RTL_CRITICAL_SECTION NtSystemTimeCritSec;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
// ******************************************************************
|
||||
|
|
|
@ -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
|
||||
|
@ -178,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -97,10 +97,12 @@ namespace NtDll
|
|||
typedef struct _DpcData {
|
||||
CRITICAL_SECTION Lock;
|
||||
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 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);
|
||||
}
|
||||
|
||||
|
@ -463,6 +488,7 @@ void ExecuteDpcQueue()
|
|||
g_DpcData.IsDpcActive.test_and_set();
|
||||
KeGetCurrentPrcb()->DpcRoutineActive = TRUE; // Experimental
|
||||
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 :
|
||||
|
@ -477,6 +503,8 @@ void ExecuteDpcQueue()
|
|||
g_DpcData.IsDpcActive.clear();
|
||||
}
|
||||
|
||||
g_DpcData.IsDpcPending.clear();
|
||||
|
||||
// Assert(g_DpcData._dwThreadId == GetCurrentThreadId());
|
||||
// Assert(g_DpcData._dwDpcThreadId == g_DpcData._dwThreadId);
|
||||
// g_DpcData._dwDpcThreadId = 0;
|
||||
|
@ -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,18 +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
|
||||
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);
|
||||
|
@ -1353,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;
|
||||
|
@ -1385,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>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread);
|
||||
long_xt ret = GetThreadPriority(*nativeHandle);
|
||||
long_xt ret = Thread->Priority;
|
||||
|
||||
KiUnlockDispatcherDatabase(OldIrql);
|
||||
|
||||
|
@ -1540,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();
|
||||
|
@ -1759,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
|
||||
|
@ -1805,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>(reinterpret_cast<PETHREAD>(Thread)->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);
|
||||
|
||||
|
@ -1844,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>(reinterpret_cast<PETHREAD>(Thread)->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);
|
||||
|
@ -1889,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);
|
||||
|
@ -1897,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1943,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 {
|
||||
|
@ -1953,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);
|
||||
}
|
||||
|
@ -1989,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();
|
||||
|
@ -2103,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);
|
||||
}
|
||||
|
||||
// ******************************************************************
|
||||
|
@ -2203,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);
|
||||
|
@ -2270,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;
|
||||
}
|
||||
|
@ -2285,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;
|
||||
|
@ -2322,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
|
||||
|
@ -2335,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) {
|
||||
|
@ -2349,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();
|
||||
|
@ -2364,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)
|
||||
|
@ -2384,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);
|
||||
|
||||
|
@ -2396,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) {
|
||||
|
@ -2445,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)) {
|
||||
|
@ -2498,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;
|
||||
|
@ -2539,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) {
|
||||
|
@ -2553,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);
|
||||
|
||||
/*
|
||||
|
@ -2568,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
|
||||
|
@ -2589,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);
|
||||
|
||||
|
@ -2601,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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,19 +519,13 @@ 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 */
|
||||
|
@ -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,19 +619,13 @@ 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 */
|
||||
|
@ -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,19 +805,13 @@ 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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -89,6 +89,11 @@ xbox::boolean_xt RtlpCaptureStackLimits(
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
xbox::void_xt xbox::RtlInitSystem()
|
||||
{
|
||||
xbox::RtlInitializeCriticalSection(&NtSystemTimeCritSec);
|
||||
}
|
||||
|
||||
// ******************************************************************
|
||||
// * 0x0104 - RtlAnsiStringToUnicodeString()
|
||||
// ******************************************************************
|
||||
|
|
|
@ -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();
|
||||
|
@ -573,12 +514,84 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags,
|
|||
// Load Xbe (this one will reside above WinMain's virtual_memory_placeholder)
|
||||
std::filesystem::path xbeDirectory = std::filesystem::path(xbePath).parent_path();
|
||||
|
||||
CxbxKrnl_Xbe = new Xbe(xbePath.c_str()); // TODO : Instead of using the Xbe class, port Dxbx _ReadXbeBlock()
|
||||
|
||||
if (CxbxKrnl_Xbe->HasFatalError()) {
|
||||
CxbxrAbort(CxbxKrnl_Xbe->GetError().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the signature of the xbe
|
||||
if (CxbxKrnl_Xbe->CheckSignature()) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Valid xbe signature. Xbe is legit");
|
||||
}
|
||||
else {
|
||||
EmuLogInit(LOG_LEVEL::WARNING, "Invalid xbe signature. Homebrew, tampered or pirated xbe?");
|
||||
}
|
||||
|
||||
// Check the integrity of the xbe sections
|
||||
for (uint32_t sectionIndex = 0; sectionIndex < CxbxKrnl_Xbe->m_Header.dwSections; sectionIndex++) {
|
||||
if (CxbxKrnl_Xbe->CheckSectionIntegrity(sectionIndex)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "SHA hash check of section %s successful", CxbxKrnl_Xbe->m_szSectionName[sectionIndex]);
|
||||
}
|
||||
else {
|
||||
EmuLogInit(LOG_LEVEL::WARNING, "SHA hash of section %s doesn't match, section is corrupted", CxbxKrnl_Xbe->m_szSectionName[sectionIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// If CLI has given console type, then enforce it.
|
||||
if (cli_config::hasKey(cli_config::system_chihiro)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Auto detect is disabled, running as chihiro.");
|
||||
emulate_system = SYSTEM_CHIHIRO;
|
||||
}
|
||||
else if (cli_config::hasKey(cli_config::system_devkit)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Auto detect is disabled, running as devkit.");
|
||||
emulate_system = SYSTEM_DEVKIT;
|
||||
}
|
||||
else if (cli_config::hasKey(cli_config::system_retail)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Auto detect is disabled, running as retail.");
|
||||
emulate_system = SYSTEM_XBOX;
|
||||
}
|
||||
// Otherwise, use auto detect method.
|
||||
else {
|
||||
// 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:
|
||||
emulate_system = SYSTEM_CHIHIRO;
|
||||
break;
|
||||
case XbeType::xtDebug:
|
||||
emulate_system = SYSTEM_DEVKIT;
|
||||
break;
|
||||
case XbeType::xtRetail:
|
||||
emulate_system = SYSTEM_XBOX;
|
||||
break;
|
||||
DEFAULT_UNREACHABLE;
|
||||
}
|
||||
|
||||
// If the XBE path contains a boot.id, it must be a Chihiro title
|
||||
// This is necessary as some Chihiro games use the Debug xor instead of the Chihiro ones
|
||||
// which means we cannot rely on that alone.
|
||||
if (std::filesystem::exists(xbeDirectory / "boot.id")) {
|
||||
emulate_system = SYSTEM_CHIHIRO;
|
||||
}
|
||||
}
|
||||
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Host's compatible system types: %2X", reserved_systems);
|
||||
// If the system to emulate isn't supported on host, enforce failure.
|
||||
if (!isSystemFlagSupport(reserved_systems, emulate_system)) {
|
||||
CxbxrAbort("Unable to emulate system type due to host is not able to reserve required memory ranges.");
|
||||
return false;
|
||||
}
|
||||
// Clear emulation system from reserved systems so all unneeded memory ranges can be freed.
|
||||
reserved_systems &= ~emulate_system;
|
||||
|
||||
#ifdef CHIHIRO_WORK
|
||||
// If the Xbe is Chihiro, and we were not launched by SEGABOOT, we need to load SEGABOOT from the Chihiro Media Board rom instead!
|
||||
// If the XBE path contains a boot.id, it must be a Chihiro title
|
||||
// This is necessary as some Chihiro games use the Debug xor instead of the Chihiro ones
|
||||
// which means we cannot rely on that alone.
|
||||
if (BootFlags == BOOT_NONE && std::filesystem::exists(xbeDirectory / "boot.id")) {
|
||||
if (BootFlags == BOOT_NONE && emulate_system == SYSTEM_CHIHIRO) {
|
||||
|
||||
std::string chihiroMediaBoardRom = g_DataFilePath + "/EmuDisk/" + MediaBoardRomFile;
|
||||
if (!std::filesystem::exists(chihiroMediaBoardRom)) {
|
||||
|
@ -639,77 +652,9 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags,
|
|||
// Launch Segaboot
|
||||
CxbxLaunchNewXbe(chihiroSegaBootNew);
|
||||
CxbxrShutDown(true);
|
||||
|
||||
}
|
||||
#endif // Chihiro wip block
|
||||
|
||||
CxbxKrnl_Xbe = new Xbe(xbePath.c_str()); // TODO : Instead of using the Xbe class, port Dxbx _ReadXbeBlock()
|
||||
|
||||
if (CxbxKrnl_Xbe->HasFatalError()) {
|
||||
CxbxrAbort(CxbxKrnl_Xbe->GetError().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the signature of the xbe
|
||||
if (CxbxKrnl_Xbe->CheckSignature()) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Valid xbe signature. Xbe is legit");
|
||||
}
|
||||
else {
|
||||
EmuLogInit(LOG_LEVEL::WARNING, "Invalid xbe signature. Homebrew, tampered or pirated xbe?");
|
||||
}
|
||||
|
||||
// Check the integrity of the xbe sections
|
||||
for (uint32_t sectionIndex = 0; sectionIndex < CxbxKrnl_Xbe->m_Header.dwSections; sectionIndex++) {
|
||||
if (CxbxKrnl_Xbe->CheckSectionIntegrity(sectionIndex)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "SHA hash check of section %s successful", CxbxKrnl_Xbe->m_szSectionName[sectionIndex]);
|
||||
}
|
||||
else {
|
||||
EmuLogInit(LOG_LEVEL::WARNING, "SHA hash of section %s doesn't match, section is corrupted", CxbxKrnl_Xbe->m_szSectionName[sectionIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// If CLI has given console type, then enforce it.
|
||||
if (cli_config::hasKey(cli_config::system_chihiro)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Auto detect is disabled, running as chihiro.");
|
||||
emulate_system = SYSTEM_CHIHIRO;
|
||||
}
|
||||
else if (cli_config::hasKey(cli_config::system_devkit)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Auto detect is disabled, running as devkit.");
|
||||
emulate_system = SYSTEM_DEVKIT;
|
||||
}
|
||||
else if (cli_config::hasKey(cli_config::system_retail)) {
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Auto detect is disabled, running as retail.");
|
||||
emulate_system = SYSTEM_XBOX;
|
||||
}
|
||||
// Otherwise, use auto detect method.
|
||||
else {
|
||||
// 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:
|
||||
emulate_system = SYSTEM_CHIHIRO;
|
||||
break;
|
||||
case XbeType::xtDebug:
|
||||
emulate_system = SYSTEM_DEVKIT;
|
||||
break;
|
||||
case XbeType::xtRetail:
|
||||
emulate_system = SYSTEM_XBOX;
|
||||
break;
|
||||
DEFAULT_UNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
EmuLogInit(LOG_LEVEL::INFO, "Host's compatible system types: %2X", reserved_systems);
|
||||
// If the system to emulate isn't supported on host, enforce failure.
|
||||
if (!isSystemFlagSupport(reserved_systems, emulate_system)) {
|
||||
CxbxrAbort("Unable to emulate system type due to host is not able to reserve required memory ranges.");
|
||||
return false;
|
||||
}
|
||||
// Clear emulation system from reserved systems so all unneeded memory ranges can be freed.
|
||||
reserved_systems &= ~emulate_system;
|
||||
|
||||
// Once we have determine which system type to run as, enforce it in future reboots.
|
||||
if ((BootFlags & BOOT_QUICK_REBOOT) == 0) {
|
||||
const char* system_str = GetSystemTypeToStr(emulate_system);
|
||||
|
@ -1220,7 +1165,7 @@ static void CxbxrKrnlInitHacks()
|
|||
g_pCertificate = &CxbxKrnl_Xbe->m_Certificate;
|
||||
|
||||
// Initialize timer subsystem
|
||||
Timer_Init();
|
||||
timer_init();
|
||||
// for unicode conversions
|
||||
setlocale(LC_ALL, "English");
|
||||
// Initialize DPC global
|
||||
|
@ -1362,10 +1307,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
|
||||
|
@ -1474,23 +1420,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
|
||||
|
@ -1512,7 +1452,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;
|
||||
|
|
|
@ -157,6 +157,7 @@ void CxbxKrnlNoFunc();
|
|||
|
||||
void InitDpcData(); // Implemented in EmuKrnlKe.cpp
|
||||
bool IsDpcActive();
|
||||
void ExecuteDpcQueue();
|
||||
|
||||
/*! kernel thunk table */
|
||||
extern uint32_t CxbxKrnl_KernelThunkTable[379];
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "core\kernel\common\xbox.h"
|
||||
#include "cxbxr.hpp"
|
||||
#include "core\hle\Intercept.hpp"
|
||||
#include "EmuShared.h"
|
||||
|
||||
PCIBus* g_PCIBus;
|
||||
SMBus* g_SMBus;
|
||||
|
@ -42,6 +43,7 @@ NVNetDevice* g_NVNet;
|
|||
NV2ADevice* g_NV2A;
|
||||
ADM1032Device* g_ADM1032;
|
||||
USBDevice* g_USB0;
|
||||
MediaBoard* g_MediaBoard;
|
||||
|
||||
MCPXRevision MCPXRevisionFromHardwareModel(HardwareModel hardwareModel)
|
||||
{
|
||||
|
@ -151,15 +153,23 @@ void InitXboxHardware(HardwareModel hardwareModel)
|
|||
|
||||
// Create devices
|
||||
g_MCPX = new MCPXDevice(mcpx_revision);
|
||||
g_SMC = new SMCDevice(smc_revision, IS_CHIHIRO(hardwareModel) ? 6 : 1); // 6 = AV_PACK_STANDARD, 1 = AV_PACK_HDTV. Chihiro doesn't support HDTV!
|
||||
|
||||
// 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
|
||||
|
@ -189,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
|
||||
|
|
|
@ -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
|
||||
|
@ -83,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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -357,10 +357,15 @@ typedef struct OverlayState {
|
|||
} OverlayState;
|
||||
|
||||
typedef struct NV2AState {
|
||||
void(* vblank_cb)(void *);
|
||||
uint64_t vblank_last;
|
||||
// PCIDevice dev;
|
||||
// qemu_irq irq;
|
||||
bool exiting;
|
||||
bool enable_overlay = false;
|
||||
bool ptimer_active = false;
|
||||
uint64_t ptimer_last;
|
||||
uint64_t ptimer_period;
|
||||
|
||||
// VGACommonState vga;
|
||||
// GraphicHwOps hw_ops;
|
||||
|
|
|
@ -57,10 +57,15 @@ extern std::atomic_bool g_bEnableAllInterrupts;
|
|||
|
||||
static int field_pin = 0;
|
||||
|
||||
static thread_local bool g_tls_isEmuX86Managed;
|
||||
|
||||
uint32_t EmuX86_IORead(xbox::addr_xt addr, int size)
|
||||
{
|
||||
// If we are running a Chihiro game, emulate the Chihiro LPC device
|
||||
if (g_bIsChihiro) {
|
||||
if (addr >= 0x4000 && addr <= 0x40FF) {
|
||||
return g_MediaBoard->LpcRead(addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
switch (addr) {
|
||||
case 0x8008: { // TODO : Move 0x8008 TIMER to a device
|
||||
if (size == sizeof(uint32_t)) {
|
||||
|
@ -95,6 +100,14 @@ uint32_t EmuX86_IORead(xbox::addr_xt addr, int size)
|
|||
|
||||
void EmuX86_IOWrite(xbox::addr_xt addr, uint32_t value, int size)
|
||||
{
|
||||
// If we are running a Chihiro game, emulate the Chihiro LPC device
|
||||
if (g_bIsChihiro) {
|
||||
if (addr >= 0x4000 && addr <= 0x40FF) {
|
||||
g_MediaBoard->LpcWrite(addr, value, size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass the IO Write to the PCI Bus, this will handle devices with BARs set to IO addresses
|
||||
if (g_PCIBus->IOWrite(addr, value, size)) {
|
||||
return;
|
||||
|
@ -197,11 +210,8 @@ uint32_t EmuX86_Read(xbox::addr_xt addr, int size)
|
|||
return value;
|
||||
}
|
||||
|
||||
// EmuX86 is not suppose to do direct read to host memory and should be handle from
|
||||
// redirect from above statements. If it doesn't meet any requirement, then should be
|
||||
// handle as possible fatal crash instead of return corrupt value.
|
||||
g_tls_isEmuX86Managed = false;
|
||||
|
||||
// EmuX86 should not directly access host memory.
|
||||
EmuLog(LOG_LEVEL::WARNING, "EmuX86_Read(0x%08X, %d) [Unhandled]", addr, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -223,10 +233,8 @@ void EmuX86_Write(xbox::addr_xt addr, uint32_t value, int size)
|
|||
return;
|
||||
}
|
||||
|
||||
// EmuX86 is not suppose to do direct write to host memory and should be handle from
|
||||
// redirect from above statements. If it doesn't meet any requirement, then should be
|
||||
// handle as possible fatal crash instead of set corrupt value.
|
||||
g_tls_isEmuX86Managed = false;
|
||||
// EmuX86 should not directly access host memory.
|
||||
EmuLog(LOG_LEVEL::WARNING, "EmuX86_Write(0x%08X, 0x%08X, %d) [Unhandled]", addr, value, size);
|
||||
}
|
||||
|
||||
int ContextRecordOffsetByRegisterType[/*_RegisterType*/R_DR7 + 1] = { 0 };
|
||||
|
@ -2928,7 +2936,6 @@ bool EmuX86_DecodeException(LPEXCEPTION_POINTERS e)
|
|||
// However, if for any reason, an opcode operand cannot be read from or written to,
|
||||
// that case may be logged, but it shouldn't fail the opcode handler.
|
||||
_DInst info;
|
||||
g_tls_isEmuX86Managed = true;
|
||||
DWORD StartingEip = e->ContextRecord->Eip;
|
||||
EmuLog(LOG_LEVEL::DEBUG, "Starting instruction emulation from 0x%08X", e->ContextRecord->Eip);
|
||||
|
||||
|
@ -3294,15 +3301,11 @@ bool EmuX86_DecodeException(LPEXCEPTION_POINTERS e)
|
|||
return true;
|
||||
} // switch info.opcode
|
||||
|
||||
if (g_tls_isEmuX86Managed) {
|
||||
e->ContextRecord->Eip += info.size;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
|
||||
e->ContextRecord->Eip += info.size;
|
||||
} // while true
|
||||
|
||||
return g_tls_isEmuX86Managed;
|
||||
return true;
|
||||
|
||||
opcode_error:
|
||||
EmuLog(LOG_LEVEL::WARNING, "0x%08X: Error while handling instruction %s (%u)", e->ContextRecord->Eip, Distorm_OpcodeString(info.opcode), info.opcode);
|
||||
|
|
|
@ -64,7 +64,7 @@ INT_PTR CALLBACK DlgAboutProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lPa
|
|||
SendMessageW(hWndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
||||
|
||||
// Build the Tab Control
|
||||
constexpr size_t text_len = longest_str({ "About", "Contributors", "License" }) + 1;
|
||||
const size_t text_len = longest_str({ "About", "Contributors", "License" }) + 1;
|
||||
char text[text_len];
|
||||
TCITEM tabInfo;
|
||||
memset(&tabInfo, 0, sizeof(tabInfo));
|
||||
|
|
|
@ -56,6 +56,8 @@
|
|||
#undef GetSystemMetrics // Force remove DirectX 8's multimon.h defined function (redirect to xGetSystemMetrics).
|
||||
#include <WinUser.h> // For GetSystemMetrics
|
||||
|
||||
#include <dwmapi.h> // For DwmSetWindowAttribute
|
||||
|
||||
#include <io.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
|
@ -68,6 +70,9 @@
|
|||
|
||||
#define XBOX_LED_FLASH_PERIOD 176 // if you know a more accurate value, put it here
|
||||
|
||||
static const char* popup_force_blank_console_type = "By force using the %s console type may cause side effects and may not be used to report to the game compatibility website.";
|
||||
static const char* popup_will_not_take_effect_until_the_next_emulation = "This will not take effect until the next time emulation is started.";
|
||||
|
||||
static int gameLogoWidth, gameLogoHeight;
|
||||
static int splashLogoWidth, splashLogoHeight;
|
||||
|
||||
|
@ -323,6 +328,10 @@ LRESULT CALLBACK WndMain::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP
|
|||
ChangeWindowMessageFilterEx(hwnd, WM_COPYDATA, MSGFLT_ALLOW, nullptr);
|
||||
ChangeWindowMessageFilterEx(hwnd, 0x0049, MSGFLT_ALLOW, nullptr);
|
||||
|
||||
// Remove rounded corners from the render window on Windows 11
|
||||
const DWM_WINDOW_CORNER_PREFERENCE corner_preference = DWMWCP_DONOTROUND;
|
||||
DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &corner_preference, sizeof(corner_preference));
|
||||
|
||||
m_bCreated = true;
|
||||
}
|
||||
break;
|
||||
|
@ -394,6 +403,13 @@ LRESULT CALLBACK WndMain::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP
|
|||
};
|
||||
break; // added per PVS suggestion.
|
||||
|
||||
case WM_TIMECHANGE:
|
||||
{
|
||||
ipc_send_kernel_update(IPC_UPDATE_KERNEL::CONFIG_CHANGE_TIME, 0, reinterpret_cast<std::uintptr_t>(m_hwndChild));
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_TIMER:
|
||||
{
|
||||
switch (wParam)
|
||||
|
@ -965,6 +981,49 @@ LRESULT CALLBACK WndMain::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP
|
|||
}
|
||||
break;
|
||||
|
||||
case ID_SETTINGS_CONFIG_CONT_AUTO:
|
||||
{
|
||||
g_Settings->m_gui.ConsoleTypeToggle = EMU_CONSOLE_TYPE_AUTO;
|
||||
if (m_bIsStarted) {
|
||||
PopupInfo(m_hwnd, popup_will_not_take_effect_until_the_next_emulation);
|
||||
}
|
||||
RefreshMenus();
|
||||
}
|
||||
break;
|
||||
|
||||
case ID_SETTINGS_CONFIG_CONT_RETAIL:
|
||||
{
|
||||
g_Settings->m_gui.ConsoleTypeToggle = EMU_CONSOLE_TYPE_RETAIL;
|
||||
PopupWarning(m_hwnd, popup_force_blank_console_type, "retail");
|
||||
if (m_bIsStarted) {
|
||||
PopupInfo(m_hwnd, popup_will_not_take_effect_until_the_next_emulation);
|
||||
}
|
||||
RefreshMenus();
|
||||
}
|
||||
break;
|
||||
|
||||
case ID_SETTINGS_CONFIG_CONT_DEVKIT:
|
||||
{
|
||||
g_Settings->m_gui.ConsoleTypeToggle = EMU_CONSOLE_TYPE_DEVKIT;
|
||||
PopupWarning(m_hwnd, popup_force_blank_console_type, "devkit");
|
||||
if (m_bIsStarted) {
|
||||
PopupInfo(m_hwnd, popup_will_not_take_effect_until_the_next_emulation);
|
||||
}
|
||||
RefreshMenus();
|
||||
}
|
||||
break;
|
||||
|
||||
case ID_SETTINGS_CONFIG_CONT_CHIHIRO:
|
||||
{
|
||||
g_Settings->m_gui.ConsoleTypeToggle = EMU_CONSOLE_TYPE_CHIHIRO;
|
||||
PopupWarning(m_hwnd, popup_force_blank_console_type, "chihiro");
|
||||
if (m_bIsStarted) {
|
||||
PopupInfo(m_hwnd, popup_will_not_take_effect_until_the_next_emulation);
|
||||
}
|
||||
RefreshMenus();
|
||||
}
|
||||
break;
|
||||
|
||||
case ID_SETTINGS_CONFIG_DLOCCUSTOM:
|
||||
{
|
||||
char szDir[MAX_PATH];
|
||||
|
@ -1092,7 +1151,7 @@ LRESULT CALLBACK WndMain::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP
|
|||
else {
|
||||
g_Settings->m_core.KrnlDebugMode = DM_NONE;
|
||||
}
|
||||
PopupInfo(m_hwnd, "This will not take effect until the next time emulation is started.");
|
||||
PopupInfo(m_hwnd, popup_will_not_take_effect_until_the_next_emulation);
|
||||
|
||||
RefreshMenus();
|
||||
|
||||
|
@ -1699,6 +1758,36 @@ void WndMain::RefreshMenus()
|
|||
chk_flag = (g_Settings->m_hacks.SkipRdtscPatching) ? MF_CHECKED : MF_UNCHECKED;
|
||||
CheckMenuItem(settings_menu, ID_HACKS_SKIPRDTSCPATCHING, chk_flag);
|
||||
|
||||
switch (g_Settings->m_gui.ConsoleTypeToggle) {
|
||||
case EMU_CONSOLE_TYPE_AUTO:
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_AUTO, MF_CHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_RETAIL, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_DEVKIT, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_CHIHIRO, MF_UNCHECKED);
|
||||
break;
|
||||
|
||||
case EMU_CONSOLE_TYPE_RETAIL:
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_AUTO, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_RETAIL, MF_CHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_DEVKIT, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_CHIHIRO, MF_UNCHECKED);
|
||||
break;
|
||||
|
||||
case EMU_CONSOLE_TYPE_DEVKIT:
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_AUTO, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_RETAIL, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_DEVKIT, MF_CHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_CHIHIRO, MF_UNCHECKED);
|
||||
break;
|
||||
|
||||
case EMU_CONSOLE_TYPE_CHIHIRO:
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_AUTO, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_RETAIL, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_DEVKIT, MF_UNCHECKED);
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_CONT_CHIHIRO, MF_CHECKED);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (g_Settings->m_gui.DataStorageToggle) {
|
||||
case CXBX_DATA_APPDATA:
|
||||
CheckMenuItem(settings_menu, ID_SETTINGS_CONFIG_DLOCAPPDATA, MF_CHECKED);
|
||||
|
@ -2237,6 +2326,20 @@ void WndMain::StartEmulation(HWND hwndParent, DebuggerState LocalDebuggerState /
|
|||
cli_config::SetLoad(m_XbeFilename);
|
||||
cli_config::SetValue(cli_config::hwnd, hwndParent);
|
||||
cli_config::SetValue(cli_config::debug_mode, g_Settings->m_core.KrnlDebugMode);
|
||||
cli_config::ClearSystemType(); // Require to reset system type in GUI in order to choose the new system option.
|
||||
if (g_Settings->m_gui.ConsoleTypeToggle > 0) {
|
||||
switch (g_Settings->m_gui.ConsoleTypeToggle) {
|
||||
case EMU_CONSOLE_TYPE_RETAIL:
|
||||
cli_config::SetSystemType(cli_config::system_retail);
|
||||
break;
|
||||
case EMU_CONSOLE_TYPE_DEVKIT:
|
||||
cli_config::SetSystemType(cli_config::system_devkit);
|
||||
break;
|
||||
case EMU_CONSOLE_TYPE_CHIHIRO:
|
||||
cli_config::SetSystemType(cli_config::system_chihiro);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (g_Settings->m_core.KrnlDebugMode == DM_FILE) {
|
||||
cli_config::SetValue(cli_config::debug_file, g_Settings->m_core.szKrnlDebug);
|
||||
}
|
||||
|
|
|
@ -763,6 +763,13 @@ BEGIN
|
|||
MENUITEM "Config &Network...", ID_SETTINGS_CONFIG_NETWORK,MFT_STRING,MFS_ENABLED
|
||||
MENUITEM "Config &Eeprom...", ID_SETTINGS_CONFIG_EEPROM,MFT_STRING,MFS_ENABLED
|
||||
MENUITEM "Config &Logging...", ID_SETTINGS_CONFIG_LOGGING,MFT_STRING,MFS_ENABLED
|
||||
POPUP "Config &Console Type..."
|
||||
BEGIN
|
||||
MENUITEM "&Auto", ID_SETTINGS_CONFIG_CONT_AUTO
|
||||
MENUITEM "&Retail", ID_SETTINGS_CONFIG_CONT_RETAIL
|
||||
MENUITEM "&Devkit", ID_SETTINGS_CONFIG_CONT_DEVKIT
|
||||
MENUITEM "&Chihiro", ID_SETTINGS_CONFIG_CONT_CHIHIRO
|
||||
END
|
||||
POPUP "Config &Data Location...", 65535,MFT_STRING,MFS_ENABLED
|
||||
BEGIN
|
||||
MENUITEM "Store in AppData", ID_SETTINGS_CONFIG_DLOCAPPDATA,MFT_STRING,MFS_ENABLED
|
||||
|
|
|
@ -353,6 +353,11 @@
|
|||
#define ID_SETTINGS_EXPERIMENTAL 40113
|
||||
#define ID_SETTINGS_IGNOREINVALIDXBESIG 40114
|
||||
#define ID_SETTINGS_IGNOREINVALIDXBESEC 40115
|
||||
#define ID_SYNC_TIME_CHANGE 40116
|
||||
#define ID_SETTINGS_CONFIG_CONT_AUTO 40117
|
||||
#define ID_SETTINGS_CONFIG_CONT_RETAIL 40118
|
||||
#define ID_SETTINGS_CONFIG_CONT_DEVKIT 40119
|
||||
#define ID_SETTINGS_CONFIG_CONT_CHIHIRO 40120
|
||||
#define IDC_STATIC -1
|
||||
|
||||
// Next default values for new objects
|
||||
|
@ -360,7 +365,7 @@
|
|||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 139
|
||||
#define _APS_NEXT_COMMAND_VALUE 40116
|
||||
#define _APS_NEXT_COMMAND_VALUE 40121
|
||||
#define _APS_NEXT_CONTROL_VALUE 1308
|
||||
#define _APS_NEXT_SYMED_VALUE 109
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue