Compare commits

...

50 Commits

Author SHA1 Message Date
Luke Usher 3515449200 Merge branch 'testbed' 2024-06-07 20:05:04 +01:00
Luke Usher 6aba34fe0d Merge branch 'master' of https://github.com/cxbx-reloaded/cxbx-reloaded 2024-06-07 20:04:53 +01:00
Luke Usher a3b2306b12 Merge branch 'master' of https://github.com/cxbx-reloaded/cxbx-reloaded into testbed 2024-06-07 20:04:18 +01:00
PatrickvL bfae57175e
Merge pull request #2458 from LukeUsher/fix-compilation-vs2022 2024-05-25 11:02:05 +02:00
Luke Usher 23134a1d44 chihiro: merge experimental Chihiro emulation 2024-05-23 09:55:44 +01:00
Luke Usher 00939039c4 Revert "Update dependencies"
This ended up fetching SDL3 which broke the build

This reverts commit 643ce9207c.
2024-05-23 09:46:24 +01:00
Luke Usher 4cdb5f5cba Merge remote-tracking branch 'ergo720/less_busy_loops' into testbed 2024-05-23 09:22:33 +01:00
Luke Usher 5eb505a71b EmuX86: Let invalid memory accesses trigger a warning rather than a fatal error
This seems to resolve most regressions we have had in recent history.
2024-05-22 14:02:38 +01:00
Luke Usher eb2381eb8e Merge branch 'fix-compilation-vs2022' 2024-05-22 12:50:04 +01:00
Luke Usher 953c91a8c2
Merge pull request #2457 from Ryce-Fast/master
Update dependencies
2024-05-22 12:48:43 +01:00
Ryce-Fast 643ce9207c Update dependencies
I have updated the dependencies that I think are worth updating. SDL2 notably adds new compatible controllers.
2024-03-14 14:06:25 +01:00
Luke Usher c3f52e33cc chihiro: prevent JVS register updates from being missed due to long delays
This really needs a better solution, but for now, this will do.
2023-10-29 11:37:44 +01:00
Luke Usher 78f7e097d5 chihiro: emulate a chihiro system when boot.id is present 2023-10-29 11:37:44 +01:00
Luke Usher 30e00d8f24 chihiro: fix an issue where media board detection failed due to instant response time 2023-10-29 11:37:44 +01:00
RadWolfie 04ce11fd87 Cleanly rebase chihiro-work on develop
Co-authored-by: Luke Usher <luke.usher@outlook.com>
Co-authored-by: wutno <aaron@installgentoo.net>
Co-authored-by: RadWolfie <RadWolfie@users.noreply.github.com>
2023-10-29 11:37:44 +01:00
ergo720 7563dd3ecf Never change the thread priority on the host and the disable boost flag too
This fixes almost all the games that were broken in this branch
2023-04-05 17:48:31 +02:00
ergo720 32ec4ee6f9 Use a DPC for expired timers + don't execute NV2A DPCs from the timer thread to avoid the exception overhead 2023-04-01 19:17:20 +02:00
ergo720 e89b5b2130 Fixed an issue in WaitApc where the wait block was not removed when using a zero timeout or when satisfied by a user APC + properly lock the wait block operations to avoid a race between SatisfyWait and KiTimerExpiration 2023-03-31 15:53:28 +02:00
ergo720 709108b045 Implemented PTIMER alarm interrupt of NV2A + fixed a bug in timer_init
This fixes DOAU showing the dirty disk error in PAL50 mode
2023-03-28 17:59:11 +02:00
ergo720 ccd77fcf4d Fixed wrong nv2a clock frequency
This is accessed by DOAU via PTIMER only in PAL50 mode
2023-03-26 21:26:30 +02:00
ergo720 ea1657018f Fixed a bug in KeTickCount + check all timer indices when we are late in KiClockIsr
This almost completely fixes the slowness in Panzer Dragoon Orta
2023-03-23 01:54:45 +01:00
ergo720 ac31523b09 Make sure to reset WaitStatus when a new wait starts
This fixes an issue in Panzer Dragoon Orta, where KeDelayExecutionThread would return X_STATUS_TIMEOUT | X_STATUS_USER_APC
2023-03-22 21:42:34 +01:00
ergo720 680340f53d Make sure that GetNativeHandle succeeds before attempting to get the native handle
This fixes a sporadic crash in Panzer Dragoon Orta, where the title calls KeSetBasePriorityThread on a thread that has already terminated
2023-03-22 20:51:21 +01:00
ergo720 c292874487 Fixed a bug in KiInsertTimerTable + log all objects being waited on in NtWaitForMultipleObjectsEx
This fixes a crash in Metal Slug 3
2023-03-22 12:17:00 +01:00
ergo720 9238ae3f81 Account for partial milliseconds in KiClockIsr
This fixes the slowness in The lord of the rings: the third era
2023-03-22 12:17:00 +01:00
ergo720 e454e901cc Fixed a race condition in WaitApc + removed wrong InsertTailList for ktimers used during a timeout
This fixes almost all broken games in this branch. Still broken: PDO: 1 fps vs 10 fps, DOA3: freezes after title screen, Lord of the rings The third era: Unable to determine default Xbox backbuffer error???
2023-03-22 12:17:00 +01:00
ergo720 253c198421 Always create a wait object even when we satisfy the wait on the host side + fixed a bug in KiWaitTestNoYield
This fixes an occasionl freeze in Steel Battalion + the slowness in JSRF
2023-03-22 12:16:59 +01:00
ergo720 8cefa8ba8f Revert to using the host to do thread suspension 2023-03-19 23:01:44 +01:00
ergo720 c3b3a1b107 Hack: <= thread priority instead of >= 2023-03-19 23:01:43 +01:00
ergo720 745f450a6c Setup a KTIMER for the other functions using WaitApc too 2023-03-19 23:01:43 +01:00
ergo720 fad4120841 Restore single interrupt loop in update_non_periodic_events 2023-03-19 23:01:43 +01:00
ergo720 9e3873d1df Place nvnet in its own thread 2023-03-19 23:01:43 +01:00
ergo720 7dc2ac080f Use get_now directly in system_events instead of qpc 2023-03-19 23:01:43 +01:00
ergo720 5d510752e6 Adjust KeSystemTime when the host system time is changed by the user 2023-03-19 23:01:42 +01:00
ergo720 b72cfaa909 Account for delays between calls to KiClockIsr
This fixes the slowness in the dashboard
2023-03-19 23:01:42 +01:00
ergo720 32b4393085 Raise priority of system events thread 2023-03-19 23:01:42 +01:00
ergo720 4ea6ecd247 Removed delta amount added to KeSystemTime 2023-03-19 23:01:42 +01:00
ergo720 c828d586db Fixed thread order initialization when a thread starts suspended 2023-03-19 23:01:41 +01:00
ergo720 d1c9883604 Make sure to hold the DPC lock until the DPC list has been emptied
This fixes a crash in Lord of the rings: The fellowship of the ring
2023-03-19 23:01:41 +01:00
ergo720 f52e261c4a Implemented kernel unwait routines + updated/fixed KeWaitForMultipleObjects and KeWaitForSingleObject 2023-03-19 23:01:41 +01:00
ergo720 e323ad50b5 Only change the priority of a thread if it is being set above normal 2023-03-19 23:01:41 +01:00
ergo720 b1ee59fab2 Unpatch D3DDevice_BlockUntilVerticalBlank and D3DDevice_SetVerticalBlankCallback 2023-03-19 23:01:40 +01:00
ergo720 6e63ecd7cc Avoid triggering multiple gpu interrupts outside the vblank 2023-03-19 23:01:40 +01:00
ergo720 c349fbbc00 Moved position of ObfDereferenceObject in NtSuspendThread 2023-03-19 23:01:40 +01:00
ergo720 d8ae1892b4 Removed scaling hack in KeInterruptTime and KeTickCount + added yield in system_events routine
This fixes the stuttering in Halo 2, Metal slug 3, JSRF and restores PDO, PSO to the same state as in master
2023-03-19 23:01:39 +01:00
ergo720 c7c107720e Implemented suspend/resume kernel Nt routines with the corresponding Ke routines 2023-03-19 23:01:39 +01:00
ergo720 d11a5e8773 Fixed a bug in KeSetBasePriorityThread 2023-03-19 23:01:39 +01:00
ergo720 6d8bd34049 Merge many different periodic events in a single thread, instead of each having its own busy loop
This merges vblank, ohci's eof, pit interrupt, dsound sync and async workers, nvnet packet processing and system interrupt
2023-03-19 23:01:39 +01:00
ergo720 a3fda7b275 Merge lle and hle vblank routines in a single thread 2023-03-19 23:01:39 +01:00
ergo720 2476ad43b0 Removed unnecessary lock in the interrupt thread 2023-03-19 23:01:39 +01:00
53 changed files with 3057 additions and 857 deletions

4
.gitmodules vendored
View File

@ -43,3 +43,7 @@
[submodule "import/nv2a_vsh_cpu"] [submodule "import/nv2a_vsh_cpu"]
path = import/nv2a_vsh_cpu path = import/nv2a_vsh_cpu
url = https://github.com/abaire/nv2a_vsh_cpu.git url = https://github.com/abaire/nv2a_vsh_cpu.git
[submodule "import/mio"]
path = import/mio
url = https://github.com/mandreyel/mio.git
shadow = true

View File

@ -22,6 +22,8 @@ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/XbSymbolDatabase")
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/SDL2" EXCLUDE_FROM_ALL) add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/SDL2" EXCLUDE_FROM_ALL)
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/mio" EXCLUDE_FROM_ALL)
# Cxbx-Reloaded projects # Cxbx-Reloaded projects
set(CXBXR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}) 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/XbInternalDSVoice.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/common/XbInternalStruct.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/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/Patches.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/XACTENG/XactEng.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/XACTENG/XactEng.cpp"
"${CXBXR_ROOT_DIR}/src/core/hle/XGRAPHIC/XGraphic.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/NativeHandle.cpp"
"${CXBXR_ROOT_DIR}/src/core/kernel/support/PatchRdtsc.cpp" "${CXBXR_ROOT_DIR}/src/core/kernel/support/PatchRdtsc.cpp"
"${CXBXR_ROOT_DIR}/src/devices/ADM1032Device.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/EEPROMDevice.cpp"
"${CXBXR_ROOT_DIR}/src/devices/network/NVNetDevice.cpp" "${CXBXR_ROOT_DIR}/src/devices/network/NVNetDevice.cpp"
"${CXBXR_ROOT_DIR}/src/devices/MCPXDevice.cpp" "${CXBXR_ROOT_DIR}/src/devices/MCPXDevice.cpp"

1
import/mio vendored Submodule

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

View File

@ -42,6 +42,9 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
LTC_NO_PRNGS LTC_NO_PRNGS
LTC_NO_MISC LTC_NO_MISC
LTC_NO_PROTOTYPES LTC_NO_PROTOTYPES
# Enable Chihiro work
CHIHIRO_WORK
) )
# Reference: https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically # Reference: https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically
@ -176,6 +179,7 @@ target_link_libraries(cxbx
SDL2 SDL2
imgui imgui
libusb libusb
mio::mio_min_winapi
${WINS_LIB} ${WINS_LIB}
) )

View File

@ -48,6 +48,9 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# Use inline XXHash version # Use inline XXHash version
XXH_INLINE_ALL XXH_INLINE_ALL
# Enable Chihiro work
CHIHIRO_WORK
) )
add_compile_options( add_compile_options(
/EHs /EHs
@ -172,6 +175,7 @@ target_link_libraries(cxbxr-emu
imgui imgui
libusb libusb
nv2a_vsh_emulator nv2a_vsh_emulator
mio::mio_min_winapi
${WINS_LIB} ${WINS_LIB}
) )

View File

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

View File

@ -25,19 +25,45 @@
// * // *
// ****************************************************************** // ******************************************************************
#ifdef _WIN32 #include <core\kernel\exports\xboxkrnl.h>
#include <windows.h> #include <windows.h>
#endif
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <mutex> #include <mutex>
#include <array>
#include "Timer.h" #include "Timer.h"
#include "common\util\CxbxUtil.h" #include "common\util\CxbxUtil.h"
#include "core\kernel\support\EmuFS.h" #include "core\kernel\support\EmuFS.h"
#include "core/kernel/exports/EmuKrnlPs.hpp" #include "core\kernel\exports\EmuKrnlPs.hpp"
#ifdef __linux__ #include "core\kernel\exports\EmuKrnl.h"
#include <time.h> #include "devices\Xbox.h"
#endif #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 // More precise sleep, but with increased CPU usage
void SleepPrecise(std::chrono::steady_clock::time_point targetTime) 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. // NOTE: the pit device is not implemented right now, so we put this here
// See the QEMUClockType QEMU_CLOCK_VIRTUAL of XQEMU for more info. static uint64_t pit_next(uint64_t now)
#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)
{ {
#ifdef _WIN32 constexpr uint64_t pit_period = 1000;
uint64_t Ret = Timer_GetScaledPerformanceCounter(SCALE_S_IN_NS); uint64_t next = pit_last + pit_period;
#elif __linux__
static struct timespec ts; if (now >= next) {
clock_gettime(CLOCK_MONOTONIC_RAW, &ts); xbox::KiClockIsr(now - pit_last);
uint64_t Ret = Muldiv64(ts.tv_sec, SCALE_S_IN_NS, 1) + ts.tv_nsec; pit_last = get_now();
#else return pit_period;
#error "Unsupported OS" }
#endif
return Ret; return pit_last + pit_period - now; // time remaining until next clock interrupt
} }
// Calculates the next expire time of the timer static void update_non_periodic_events()
static inline uint64_t GetNextExpireTime(TimerObject* Timer)
{ {
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 uint64_t get_now()
void Timer_Destroy(TimerObject* Timer)
{ {
unsigned int index, i; LARGE_INTEGER now;
std::lock_guard<std::mutex>lock(TimerMtx); QueryPerformanceCounter(&now);
uint64_t elapsed_us = now.QuadPart - last_qpc;
index = TimerList.size(); last_qpc = now.QuadPart;
for (i = 0; i < index; i++) { elapsed_us *= 1000000;
if (Timer == TimerList[i]) { elapsed_us /= HostQPCFrequency;
index = i; exec_time += elapsed_us;
} return exec_time;
}
assert(index != TimerList.size());
delete Timer;
TimerList.erase(TimerList.begin() + index);
} }
void Timer_Shutdown() static uint64_t get_next(uint64_t now)
{ {
unsigned i, iXboxThreads = 0; std::array<uint64_t, 5> next = {
TimerMtx.lock(); pit_next(now),
g_NV2A->vblank_next(now),
for (i = 0; i < TimerList.size(); i++) { g_NV2A->ptimer_next(now),
TimerObject* Timer = TimerList[i]; g_USB0->m_HostController->OHCI_next(now),
// We only need to terminate host threads. dsound_next(now)
if (!Timer->IsXboxTimer) { };
Timer_Exit(Timer); return *std::min_element(next.begin(), next.end());
}
// 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();
} }
// Thread that runs the timer xbox::void_xt NTAPI system_events(xbox::PVOID arg)
void NTAPI ClockThread(void *TimerArg)
{ {
TimerObject *Timer = static_cast<TimerObject *>(TimerArg); // 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.
if (!Timer->Name.empty()) { // So we increase its priority to above normal, so that it scheduled more often
CxbxSetThreadName(Timer->Name.c_str()); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
}
if (!Timer->IsXboxTimer) {
g_AffinityPolicy->SetAffinityOther();
}
uint64_t NewExpireTime = GetNextExpireTime(Timer); // Always run this thread at dpc level to prevent it from ever executing APCs/DPCs
xbox::KeRaiseIrqlToDpcLevel();
while (true) { while (true) {
if (GetTime_NS(Timer) > NewExpireTime) { const uint64_t last_time = get_now();
if (Timer->Exit.load()) { const uint64_t nearest_next = get_next(last_time);
Timer_Destroy(Timer);
return; while (true) {
update_non_periodic_events();
uint64_t elapsed_us = get_now() - last_time;
if (elapsed_us >= nearest_next) {
break;
} }
Timer->Callback(Timer->Opaque); std::this_thread::yield();
NewExpireTime = GetNextExpireTime(Timer);
} }
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) int64_t Timer_GetScaledPerformanceCounter(int64_t Period)
{ {
LARGE_INTEGER currentQPC; LARGE_INTEGER currentQPC;

View File

@ -40,33 +40,12 @@
#define SCALE_MS_IN_US 1000 #define SCALE_MS_IN_US 1000
#define SCALE_US_IN_US 1 #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; extern int64_t HostQPCFrequency;
/* Timer exported functions */ void timer_init();
TimerObject* Timer_Create(TimerCB Callback, void* Arg, std::string Name, bool IsXboxTimer); uint64_t get_now();
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();
int64_t Timer_GetScaledPerformanceCounter(int64_t Period); int64_t Timer_GetScaledPerformanceCounter(int64_t Period);
void SleepPrecise(std::chrono::steady_clock::time_point targetTime); void SleepPrecise(std::chrono::steady_clock::time_point targetTime);
#endif #endif

View File

@ -121,9 +121,6 @@ bool HandleFirstLaunch()
} }
// NOTE: Require to be after g_renderbase's shutdown process. // 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! // NOTE: Must be last step of shutdown process and before CxbxUnlockFilePath call!
// Shutdown the memory manager // Shutdown the memory manager
g_VMManager.Shutdown(); g_VMManager.Shutdown();

View File

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

View File

@ -72,7 +72,7 @@ Xbe::Xbe(const char *x_szFilename)
// This is necessary because CxbxInitWindow internally calls g_AffinityPolicy->SetAffinityOther. If we are launched directly from the command line and the dashboard // 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 // cannot be opened, we will crash below because g_AffinityPolicy will be empty
g_AffinityPolicy = AffinityPolicy::InitPolicy(); g_AffinityPolicy = AffinityPolicy::InitPolicy();
CxbxInitWindow(false); CxbxInitWindow();
ULONG FatalErrorCode = FATAL_ERROR_XBE_DASH_GENERIC; ULONG FatalErrorCode = FATAL_ERROR_XBE_DASH_GENERIC;

View File

@ -38,6 +38,7 @@
#include "core\kernel\support\Emu.h" #include "core\kernel\support\Emu.h"
#include "core\kernel\support\EmuFS.h" #include "core\kernel\support\EmuFS.h"
#include "core\kernel\support\NativeHandle.h" #include "core\kernel\support\NativeHandle.h"
#include "core\kernel\exports\EmuKrnlKe.h"
#include "EmuShared.h" #include "EmuShared.h"
#include "..\FixedFunctionState.h" #include "..\FixedFunctionState.h"
#include "core\hle\D3D8\ResourceTracker.h" #include "core\hle\D3D8\ResourceTracker.h"
@ -160,8 +161,6 @@ static IDirect3DQuery *g_pHostQueryWaitForIdle = nullptr;
static IDirect3DQuery *g_pHostQueryCallbackEvent = nullptr; static IDirect3DQuery *g_pHostQueryCallbackEvent = nullptr;
static int g_RenderUpscaleFactor = 1; 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 DWORD g_VBLastSwap = 0;
static xbox::dword_xt g_Xbox_PresentationInterval_Default = D3DPRESENT_INTERVAL_IMMEDIATE; 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_D3DSWAPDATA g_Xbox_SwapData = {0}; // current swap information
static xbox::X_D3DSWAPCALLBACK g_pXbox_SwapCallback = xbox::zeroptr; // Swap/Present callback routine 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_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; xbox::X_D3DSurface *g_pXbox_BackBufferSurface = xbox::zeroptr;
static xbox::X_D3DSurface *g_pXbox_DefaultDepthStencilSurface = 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 Function(s)
static DWORD WINAPI EmuRenderWindow(LPVOID); static DWORD WINAPI EmuRenderWindow(LPVOID);
static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); 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 inline void EmuVerifyResourceIsRegistered(xbox::X_D3DResource *pResource, DWORD D3DUsage, int iTextureStage, DWORD dwSize);
static void UpdateCurrentMSpFAndFPS(); // Used for benchmarking/fps count static void UpdateCurrentMSpFAndFPS(); // Used for benchmarking/fps count
static void CxbxImpl_SetRenderTarget(xbox::X_D3DSurface *pRenderTarget, xbox::X_D3DSurface *pNewZStencil); static void CxbxImpl_SetRenderTarget(xbox::X_D3DSurface *pRenderTarget, xbox::X_D3DSurface *pNewZStencil);
@ -629,25 +626,13 @@ const char *D3DErrorString(HRESULT hResult)
return buffer; return buffer;
} }
void CxbxInitWindow(bool bFullInit) void CxbxInitWindow()
{ {
g_EmuShared->GetVideoSettings(&g_XBVideo); g_EmuShared->GetVideoSettings(&g_XBVideo);
if(g_XBVideo.bFullScreen) if(g_XBVideo.bFullScreen)
CxbxKrnl_hEmuParent = NULL; 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 : /* TODO : Port this Dxbx code :
// create vblank handling thread // create vblank handling thread
{ {
@ -1706,6 +1691,13 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
} }
break; break;
case ID_SYNC_TIME_CHANGE:
{
// Sent by the GUI when it detects WM_TIMECHANGE
xbox::KeSystemTimeChanged.test_and_set();
}
break;
default: default:
break; break;
} }
@ -1723,6 +1715,14 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
} }
break; 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: case WM_SYSKEYDOWN:
{ {
if(wParam == VK_RETURN) 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); return steady_clock::now() + duration_cast<steady_clock::duration>(ms);
} }
// timing thread procedure void hle_vblank()
static xbox::void_xt NTAPI EmuUpdateTickCount(xbox::PVOID Arg)
{ {
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) // TODO: This can't be accurate...
{ g_Xbox_SwapData.TimeUntilSwapVBlank = 0;
// 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();
// Increment the VBlank Counter and Wake all threads there were waiting for the VBlank to occur // TODO: Recalculate this for PAL version if necessary.
std::unique_lock<std::mutex> lk(g_VBConditionMutex); // Also, we should check the D3DPRESENT_INTERVAL value for accurracy.
g_Xbox_VBlankData.VBlank++; // g_Xbox_SwapData.TimeBetweenSwapVBlanks = 1/60;
g_VBConditionVariable.notify_all(); g_Xbox_SwapData.TimeBetweenSwapVBlanks = 0;
// 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;
}
} }
void UpdateDepthStencilFlags(IDirect3DSurface *pDepthStencilSurface) void UpdateDepthStencilFlags(IDirect3DSurface *pDepthStencilSurface)
@ -3163,6 +3141,12 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(Direct3D_CreateDevice)
Direct3D_CreateDevice_Start(pPresentationParameters); 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 // Only then call Xbox CreateDevice function
hresult_xt hRet = XB_TRMP(Direct3D_CreateDevice)(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); 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; 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 // * patch: D3DDevice_SetRenderState_Simple
// ****************************************************************** // ******************************************************************

View File

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

View File

@ -40,7 +40,7 @@
void LookupTrampolinesD3D(); void LookupTrampolinesD3D();
// initialize render window // initialize render window
extern void CxbxInitWindow(bool bFullInit); extern void CxbxInitWindow();
void CxbxUpdateNativeD3DResources(); void CxbxUpdateNativeD3DResources();

View File

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

View File

@ -81,3 +81,6 @@ extern DsBufferStreaming g_dsBufferStreaming;
extern void DirectSoundDoWork_Buffer(xbox::LARGE_INTEGER& time); extern void DirectSoundDoWork_Buffer(xbox::LARGE_INTEGER& time);
extern void DirectSoundDoWork_Stream(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);

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

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

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

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

View File

@ -29,6 +29,7 @@
#include "core\kernel\init\CxbxKrnl.h" #include "core\kernel\init\CxbxKrnl.h"
#include "core\kernel\support\Emu.h" #include "core\kernel\support\Emu.h"
#include "core\hle\D3D8\Direct3D9/Direct3D9.h" #include "core\hle\D3D8\Direct3D9/Direct3D9.h"
#include "core\hle\JVS\JVS.h"
#include "core\hle\DSOUND\DirectSound\DirectSound.hpp" #include "core\hle\DSOUND\DirectSound\DirectSound.hpp"
#include "Patches.hpp" #include "Patches.hpp"
#include "Intercept.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_BeginPush_8", xbox::EMUPATCH(D3DDevice_BeginPush_8), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_BeginVisibilityTest", xbox::EMUPATCH(D3DDevice_BeginVisibilityTest), 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_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_Clear", xbox::EMUPATCH(D3DDevice_Clear), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_CopyRects", xbox::EMUPATCH(D3DDevice_CopyRects), 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), // 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_SetVertexShaderConstant_8", xbox::EMUPATCH(D3DDevice_SetVertexShaderConstant_8), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_SetVertexShaderInput", xbox::EMUPATCH(D3DDevice_SetVertexShaderInput), 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_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_SetViewport", xbox::EMUPATCH(D3DDevice_SetViewport), PATCH_HLE_D3D),
PATCH_ENTRY("D3DDevice_Swap", xbox::EMUPATCH(D3DDevice_Swap), 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), 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("timeSetEvent", xbox::EMUPATCH(timeSetEvent), PATCH_ALWAYS),
PATCH_ENTRY("XReadMUMetaData", xbox::EMUPATCH(XReadMUMetaData), PATCH_ALWAYS), PATCH_ENTRY("XReadMUMetaData", xbox::EMUPATCH(XReadMUMetaData), PATCH_ALWAYS),
PATCH_ENTRY("XUnmountMU", xbox::EMUPATCH(XUnmountMU), 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; std::unordered_map<std::string, subhook::Hook> g_FunctionHooks;

View File

@ -960,15 +960,41 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait)
NewTime.QuadPart += (static_cast<xbox::ulonglong_xt>(dwMilliseconds) * CLOCK_TIME_INCREMENT); 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); DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable);
if (dwRet == WAIT_TIMEOUT) { if (dwRet == WAIT_TIMEOUT) {
return std::nullopt; return std::nullopt;
} }
return std::make_optional<DWORD>(dwRet); // 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
}, Timeout, bAlertable, UserMode); // to the thread
xbox::ntstatus_xt Status;
switch (dwRet)
{
case WAIT_ABANDONED: Status = X_STATUS_ABANDONED; break;
case WAIT_IO_COMPLETION: Status = X_STATUS_USER_APC; break;
case WAIT_OBJECT_0: Status = X_STATUS_SUCCESS; break;
default: Status = X_STATUS_INVALID_HANDLE;
}
xbox::KiUnwaitThreadAndLock(kThread, Status, 0);
return std::make_optional<ntstatus_xt>(kThread->WaitStatus);
}, Timeout, bAlertable, UserMode, kThread);
RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret); xbox::dword_xt ret;
switch (status)
{
case X_STATUS_ABANDONED: ret = WAIT_ABANDONED; break;
case X_STATUS_USER_APC: ret = WAIT_IO_COMPLETION; break;
case X_STATUS_SUCCESS: ret = WAIT_OBJECT_0; break;
case X_STATUS_TIMEOUT: ret = WAIT_TIMEOUT; break;
default: ret = WAIT_FAILED;
}
RETURN(ret);
} }
// ****************************************************************** // ******************************************************************

View File

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

View File

@ -98,6 +98,8 @@ typedef void* LPSECURITY_ATTRIBUTES;
#define X_STATUS_FILE_IS_A_DIRECTORY 0xC00000BAL #define X_STATUS_FILE_IS_A_DIRECTORY 0xC00000BAL
#define X_STATUS_END_OF_FILE 0xC0000011L #define X_STATUS_END_OF_FILE 0xC0000011L
#define X_STATUS_INVALID_PAGE_PROTECTION 0xC0000045L #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_CONFLICTING_ADDRESSES 0xC0000018L
#define X_STATUS_UNABLE_TO_FREE_VM 0xC000001AL #define X_STATUS_UNABLE_TO_FREE_VM 0xC000001AL
#define X_STATUS_FREE_VM_NOT_AT_BASE 0xC000009FL #define X_STATUS_FREE_VM_NOT_AT_BASE 0xC000009FL
@ -1945,7 +1947,7 @@ typedef struct _KTHREAD
/* 0x56/86 */ char_xt WaitNext; /* 0x56/86 */ char_xt WaitNext;
/* 0x57/87 */ char_xt WaitReason; /* 0x57/87 */ char_xt WaitReason;
/* 0x58/88 */ PKWAIT_BLOCK WaitBlockList; /* 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; /* 0x64/100 */ ulong_xt WaitTime;
/* 0x68/104 */ ulong_xt KernelApcDisable; /* 0x68/104 */ ulong_xt KernelApcDisable;
/* 0x6C/108 */ ulong_xt Quantum; /* 0x6C/108 */ ulong_xt Quantum;
@ -1969,6 +1971,8 @@ typedef struct _KTHREAD
} }
KTHREAD, *PKTHREAD, *RESTRICTED_POINTER PRKTHREAD; KTHREAD, *PKTHREAD, *RESTRICTED_POINTER PRKTHREAD;
#define X_MAXIMUM_SUSPEND_COUNT 0x7F
// ****************************************************************** // ******************************************************************
// * ETHREAD // * ETHREAD
// ****************************************************************** // ******************************************************************

View File

@ -85,6 +85,8 @@ void InsertTailList(xbox::PLIST_ENTRY pListHead, xbox::PLIST_ENTRY pEntry)
//#define RemoveEntryList(e) do { PLIST_ENTRY f = (e)->Flink, b = (e)->Blink; f->Blink = b; b->Flink = f; (e)->Flink = (e)->Blink = NULL; } while (0) //#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. // 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::boolean_xt RemoveEntryList(xbox::PLIST_ENTRY pEntry)
{ {
xbox::PLIST_ENTRY _EX_Flink = pEntry->Flink; xbox::PLIST_ENTRY _EX_Flink = pEntry->Flink;
@ -140,8 +142,6 @@ void RestoreInterruptMode(bool value)
g_bInterruptsEnabled = value; g_bInterruptsEnabled = value;
} }
extern void ExecuteDpcQueue();
void KiUnexpectedInterrupt() void KiUnexpectedInterrupt()
{ {
xbox::KeBugCheck(TRAP_CAUSE_UNKNOWN); // see xbox::KeBugCheck(TRAP_CAUSE_UNKNOWN); // see
@ -178,6 +178,33 @@ void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql)
HalInterruptRequestRegister ^= (1 << 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 // This masks have been verified to be correct against a kernel dump
const DWORD IrqlMasks[] = { const DWORD IrqlMasks[] = {
0xFFFFFFFE, // IRQL 0 0xFFFFFFFE, // IRQL 0

View File

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

View File

@ -248,6 +248,14 @@ XBSYSAPI EXPORTNUM(66) xbox::ntstatus_xt NTAPI xbox::IoCreateFile
LOG_FUNC_ARG(Options) LOG_FUNC_ARG(Options)
LOG_FUNC_END; 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; NativeObjectAttributes nativeObjectAttributes;
// If we are NOT accessing a directory, and we match a partition path, we need to redirect to a partition.bin file // If we are NOT accessing a directory, and we match a partition path, we need to redirect to a partition.bin file

View File

@ -97,10 +97,12 @@ namespace NtDll
typedef struct _DpcData { typedef struct _DpcData {
CRITICAL_SECTION Lock; CRITICAL_SECTION Lock;
std::atomic_flag IsDpcActive; std::atomic_flag IsDpcActive;
std::atomic_flag IsDpcPending;
xbox::LIST_ENTRY DpcQueue; // TODO : Use KeGetCurrentPrcb()->DpcListHead instead xbox::LIST_ENTRY DpcQueue; // TODO : Use KeGetCurrentPrcb()->DpcListHead instead
} DpcData; } DpcData;
DpcData g_DpcData = { 0 }; // Note : g_DpcData is initialized in InitDpcData() 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) xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value)
{ {
@ -130,6 +132,33 @@ xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value)
break; \ 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() // * EmuKeGetPcr()
@ -166,7 +195,7 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime
) )
{ {
KIRQL OldIrql, OldIrql2; KIRQL OldIrql, OldIrql2;
LARGE_INTEGER DeltaTime, HostTime; LARGE_INTEGER DeltaTime;
PLIST_ENTRY ListHead, NextEntry; PLIST_ENTRY ListHead, NextEntry;
PKTIMER Timer; PKTIMER Timer;
LIST_ENTRY TempList, TempList2; LIST_ENTRY TempList, TempList2;
@ -184,10 +213,6 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime
/* Query the system time now */ /* Query the system time now */
KeQuerySystemTime(OldTime); 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 */ /* Calculate the difference between the new and the old time */
DeltaTime.QuadPart = NewTime->QuadPart - OldTime->QuadPart; 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); KiTimerListExpire(&TempList2, OldIrql);
} }
@ -463,6 +488,7 @@ void ExecuteDpcQueue()
g_DpcData.IsDpcActive.test_and_set(); g_DpcData.IsDpcActive.test_and_set();
KeGetCurrentPrcb()->DpcRoutineActive = TRUE; // Experimental KeGetCurrentPrcb()->DpcRoutineActive = TRUE; // Experimental
LeaveCriticalSection(&(g_DpcData.Lock)); LeaveCriticalSection(&(g_DpcData.Lock));
EmuLog(LOG_LEVEL::DEBUG, "Global DpcQueue, calling DPC object 0x%.8X at 0x%.8X", pkdpc, pkdpc->DeferredRoutine); EmuLog(LOG_LEVEL::DEBUG, "Global DpcQueue, calling DPC object 0x%.8X at 0x%.8X", pkdpc, pkdpc->DeferredRoutine);
// Call the Deferred Procedure : // Call the Deferred Procedure :
@ -477,6 +503,8 @@ void ExecuteDpcQueue()
g_DpcData.IsDpcActive.clear(); g_DpcData.IsDpcActive.clear();
} }
g_DpcData.IsDpcPending.clear();
// Assert(g_DpcData._dwThreadId == GetCurrentThreadId()); // Assert(g_DpcData._dwThreadId == GetCurrentThreadId());
// Assert(g_DpcData._dwDpcThreadId == g_DpcData._dwThreadId); // Assert(g_DpcData._dwDpcThreadId == g_DpcData._dwThreadId);
// g_DpcData._dwDpcThreadId = 0; // 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 // We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well
// Test case: Metal Slug 3 // 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; NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0; ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime); 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) { if (Status >= 0 && Status != STATUS_ALERTED && Status != STATUS_USER_APC) {
return std::nullopt; return std::nullopt;
} }
return std::make_optional<ntstatus_xt>(Status); // 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
}, Interval, Alertable, WaitMode); // 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) { if (ret == X_STATUS_TIMEOUT) {
// NOTE: this function considers a timeout a success // 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->SystemArgument1 = SystemArgument1;
Apc->SystemArgument2 = SystemArgument2; Apc->SystemArgument2 = SystemArgument2;
if (Apc->Inserted) { boolean_xt result = KiInsertQueueApc(Apc, Increment);
KfLowerIrql(OldIrql); KfLowerIrql(OldIrql);
RETURN(FALSE); RETURN(result);
}
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);
}
} }
} }
@ -1266,18 +1277,25 @@ XBSYSAPI EXPORTNUM(119) xbox::boolean_xt NTAPI xbox::KeInsertQueueDpc
Dpc->SystemArgument1 = SystemArgument1; Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2; Dpc->SystemArgument2 = SystemArgument2;
InsertTailList(&(g_DpcData.DpcQueue), &(Dpc->DpcListEntry)); 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 // TODO : Instead of DpcQueue, add the DPC to KeGetCurrentPrcb()->DpcListHead
// Signal the Dpc handling code there's work to do // Signal the Dpc handling code there's work to do
if (!IsDpcActive()) { if (!IsDpcActive()) {
HalRequestSoftwareInterrupt(DISPATCH_LEVEL); HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
} }
// OpenXbox has this instead: // OpenXbox has this instead:
// if (!pKPRCB->DpcRoutineActive && !pKPRCB->DpcInterruptRequested) { // if (!pKPRCB->DpcRoutineActive && !pKPRCB->DpcInterruptRequested) {
// pKPRCB->DpcInterruptRequested = TRUE; // pKPRCB->DpcInterruptRequested = TRUE;
} }
else {
LeaveCriticalSection(&(g_DpcData.Lock));
}
// Thread-safety is no longer required anymore // Thread-safety is no longer required anymore
LeaveCriticalSection(&(g_DpcData.Lock));
// TODO : Instead, enable interrupts - use KeLowerIrql(OldIrql) ? // TODO : Instead, enable interrupts - use KeLowerIrql(OldIrql) ?
RETURN(NeedsInsertion); RETURN(NeedsInsertion);
@ -1353,13 +1371,14 @@ XBSYSAPI EXPORTNUM(123) xbox::long_xt NTAPI xbox::KePulseEvent
} }
LONG OldState = Event->Header.SignalState; LONG OldState = Event->Header.SignalState;
KiWaitListLock();
if ((OldState == 0) && (IsListEmpty(&Event->Header.WaitListHead) == FALSE)) { if ((OldState == 0) && (IsListEmpty(&Event->Header.WaitListHead) == FALSE)) {
Event->Header.SignalState = 1; Event->Header.SignalState = 1;
// TODO: KiWaitTest(Event, Increment); KiWaitTest(Event, Increment);
// For now, we just sleep to give other threads time to wake std::this_thread::yield();
// KiWaitTest and related functions require correct thread scheduling to implement first }
// This will have to wait until CPU emulation at v1.0 else {
Sleep(1); KiWaitListUnlock();
} }
Event->Header.SignalState = 0; Event->Header.SignalState = 0;
@ -1385,9 +1404,7 @@ XBSYSAPI EXPORTNUM(124) xbox::long_xt NTAPI xbox::KeQueryBasePriorityThread
KIRQL OldIrql; KIRQL OldIrql;
KiLockDispatcherDatabase(&OldIrql); KiLockDispatcherDatabase(&OldIrql);
// It cannot fail because all thread handles are created by ob long_xt ret = Thread->Priority;
const auto& nativeHandle = GetNativeHandle<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread);
long_xt ret = GetThreadPriority(*nativeHandle);
KiUnlockDispatcherDatabase(OldIrql); KiUnlockDispatcherDatabase(OldIrql);
@ -1540,12 +1557,14 @@ XBSYSAPI EXPORTNUM(132) xbox::long_xt NTAPI xbox::KeReleaseSemaphore
} }
Semaphore->Header.SignalState = adjusted_signalstate; Semaphore->Header.SignalState = adjusted_signalstate;
//TODO: Implement KiWaitTest KiWaitListLock();
#if 0
if ((initial_state == 0) && (IsListEmpty(&Semaphore->Header.WaitListHead) == FALSE)) { if ((initial_state == 0) && (IsListEmpty(&Semaphore->Header.WaitListHead) == FALSE)) {
KiWaitTest(&Semaphore->Header, Increment); KiWaitTest(&Semaphore->Header, Increment);
std::this_thread::yield();
}
else {
KiWaitListUnlock();
} }
#endif
if (Wait) { if (Wait) {
PKTHREAD current_thread = KeGetCurrentThread(); PKTHREAD current_thread = KeGetCurrentThread();
@ -1759,11 +1778,29 @@ XBSYSAPI EXPORTNUM(140) xbox::ulong_xt NTAPI xbox::KeResumeThread
{ {
LOG_FUNC_ONE_ARG(Thread); 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 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_BEGIN
LOG_FUNC_ARG_OUT(Thread) LOG_FUNC_ARG(Thread)
LOG_FUNC_ARG_OUT(Priority) LOG_FUNC_ARG(Priority)
LOG_FUNC_END; LOG_FUNC_END;
KIRQL oldIRQL; KIRQL oldIRQL;
KiLockDispatcherDatabase(&oldIRQL); KiLockDispatcherDatabase(&oldIRQL);
// It cannot fail because all thread handles are created by ob Thread->Priority = Priority;
const auto &nativeHandle = GetNativeHandle<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread); long_xt ret = Thread->Priority;
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());
}
}
KiUnlockDispatcherDatabase(oldIRQL); KiUnlockDispatcherDatabase(oldIRQL);
@ -1844,17 +1871,9 @@ XBSYSAPI EXPORTNUM(144) xbox::boolean_xt NTAPI xbox::KeSetDisableBoostThread
KIRQL oldIRQL; KIRQL oldIRQL;
KiLockDispatcherDatabase(&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; boolean_xt prevDisableBoost = Thread->DisableBoost;
Thread->DisableBoost = (CHAR)Disable; Thread->DisableBoost = (CHAR)Disable;
BOOL bRet = SetThreadPriorityBoost(*nativeHandle, Disable);
if (!bRet) {
EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost failed: %s", WinError2Str().c_str());
}
KiUnlockDispatcherDatabase(oldIRQL); KiUnlockDispatcherDatabase(oldIRQL);
RETURN(prevDisableBoost); RETURN(prevDisableBoost);
@ -1889,7 +1908,9 @@ XBSYSAPI EXPORTNUM(145) xbox::long_xt NTAPI xbox::KeSetEvent
} }
LONG OldState = Event->Header.SignalState; LONG OldState = Event->Header.SignalState;
KiWaitListLock();
if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) { if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) {
KiWaitListUnlock();
Event->Header.SignalState = 1; Event->Header.SignalState = 1;
} else { } else {
PKWAIT_BLOCK WaitBlock = CONTAINING_RECORD(Event->Header.WaitListHead.Flink, KWAIT_BLOCK, WaitListEntry); 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)) { (WaitBlock->WaitType != WaitAny)) {
if (OldState == 0) { if (OldState == 0) {
Event->Header.SignalState = 1; Event->Header.SignalState = 1;
// TODO: KiWaitTest(Event, Increment); KiWaitTest(Event, Increment);
// For now, we just sleep to give other threads time to wake }
// See KePulseEvent else {
Sleep(1); KiWaitListUnlock();
} }
} else { } else {
// TODO: KiUnwaitThread(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment); KiUnwaitThread(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment);
// For now, we just sleep to give other threads time to wake KiWaitListUnlock();
// See KePulseEvent
Sleep(1);
} }
} }
@ -1943,6 +1962,7 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority
return; return;
} }
KiWaitListLock();
if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) { if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) {
Event->Header.SignalState = 1; Event->Header.SignalState = 1;
} else { } else {
@ -1953,11 +1973,9 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority
} }
WaitThread->Quantum = WaitThread->ApcState.Process->ThreadQuantum; WaitThread->Quantum = WaitThread->ApcState.Process->ThreadQuantum;
// TODO: KiUnwaitThread(WaitThread, X_STATUS_SUCCESS, 1); KiUnwaitThread(WaitThread, X_STATUS_SUCCESS, 1);
// For now, we just sleep to give other threads time to wake
// See KePulseEvent
Sleep(1);
} }
KiWaitListUnlock();
KiUnlockDispatcherDatabase(OldIrql); KiUnlockDispatcherDatabase(OldIrql);
} }
@ -1989,8 +2007,8 @@ XBSYSAPI EXPORTNUM(148) xbox::boolean_xt NTAPI xbox::KeSetPriorityThread
) )
{ {
LOG_FUNC_BEGIN LOG_FUNC_BEGIN
LOG_FUNC_ARG_OUT(Thread) LOG_FUNC_ARG(Thread)
LOG_FUNC_ARG_OUT(Priority) LOG_FUNC_ARG(Priority)
LOG_FUNC_END; LOG_FUNC_END;
LOG_UNIMPLEMENTED(); LOG_UNIMPLEMENTED();
@ -2103,11 +2121,38 @@ XBSYSAPI EXPORTNUM(152) xbox::ulong_xt NTAPI xbox::KeSuspendThread
{ {
LOG_FUNC_ONE_ARG(Thread); 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 // Wait Loop
// This loop ends // This loop ends
PLARGE_INTEGER OriginalTime = Timeout; PLARGE_INTEGER OriginalTime = Timeout;
LARGE_INTEGER DueTime, NewTime; PKWAIT_BLOCK WaitBlock;
KWAIT_BLOCK StackWaitBlock;
PKWAIT_BLOCK WaitBlock = &StackWaitBlock;
BOOLEAN WaitSatisfied; BOOLEAN WaitSatisfied;
NTSTATUS WaitStatus; NTSTATUS WaitStatus;
PKMUTANT ObjectMutant; PKMUTANT ObjectMutant;
// Hack variable (remove this when the thread scheduler is here)
bool timeout_set = false;
do { do {
Thread->WaitBlockList = WaitBlockArray;
// Check if we need to let an APC run. This should immediately trigger APC interrupt via a call to UnlockDispatcherDatabase // 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)) { if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
KiUnlockDispatcherDatabase(Thread->WaitIrql); KiUnlockDispatcherDatabase(Thread->WaitIrql);
@ -2270,7 +2313,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
// Check if the wait can be satisfied immediately // Check if the wait can be satisfied immediately
if ((WaitType == WaitAll) && (WaitSatisfied)) { if ((WaitType == WaitAll) && (WaitSatisfied)) {
WaitBlock->NextWaitBlock = &WaitBlockArray[0]; WaitBlock->NextWaitBlock = &WaitBlockArray[0];
KiWaitSatisfyAll(WaitBlock); KiWaitSatisfyAllAndLock(WaitBlock);
WaitStatus = (NTSTATUS)Thread->WaitStatus; WaitStatus = (NTSTATUS)Thread->WaitStatus;
goto NoWait; goto NoWait;
} }
@ -2285,36 +2328,20 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
goto NoWait; goto NoWait;
} }
// Setup a timer for the thread but only once (for now) // Setup a timer for the thread
if (!timeout_set) { KiTimerLock();
KiTimerLock(); PKTIMER Timer = &Thread->Timer;
PKTIMER Timer = &Thread->Timer; PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock;
PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock; WaitBlock->NextWaitBlock = WaitTimer;
WaitBlock->NextWaitBlock = WaitTimer; Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry; Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry; WaitTimer->NextWaitBlock = WaitBlock;
WaitTimer->NextWaitBlock = WaitBlock; if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) { WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
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;
KiTimerUnlock(); 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; goto NoWait;
} }
KiTimerUnlock();
} }
else { else {
WaitBlock->NextWaitBlock = WaitBlock; WaitBlock->NextWaitBlock = WaitBlock;
@ -2322,12 +2349,13 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
WaitBlock->NextWaitBlock = &WaitBlockArray[0]; WaitBlock->NextWaitBlock = &WaitBlockArray[0];
WaitBlock = &WaitBlockArray[0]; WaitBlock = &WaitBlockArray[0];
KiWaitListLock();
do { do {
ObjectMutant = (PKMUTANT)WaitBlock->Object; ObjectMutant = (PKMUTANT)WaitBlock->Object;
//InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry); InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry);
WaitBlock = WaitBlock->NextWaitBlock; WaitBlock = WaitBlock->NextWaitBlock;
} while (WaitBlock != &WaitBlockArray[0]); } while (WaitBlock != &WaitBlockArray[0]);
KiWaitListUnlock();
/* /*
TODO: We can't implement this and the return values until we have our own thread scheduler 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 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 // If the current thread is processing a queue object, wake other treads using the same queue
PRKQUEUE Queue = (PRKQUEUE)Thread->Queue; PRKQUEUE Queue = (PRKQUEUE)Thread->Queue;
if (Queue != NULL) { if (Queue != NULL) {
@ -2349,7 +2374,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects
Thread->WaitMode = WaitMode; Thread->WaitMode = WaitMode;
Thread->WaitReason = (UCHAR)WaitReason; Thread->WaitReason = (UCHAR)WaitReason;
Thread->WaitTime = KeTickCount; Thread->WaitTime = KeTickCount;
//Thread->State = Waiting; Thread->State = Waiting;
//KiInsertWaitList(WaitMode, Thread); //KiInsertWaitList(WaitMode, Thread);
//WaitStatus = (NTSTATUS)KiSwapThread(); //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 // 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 break;
if (Timeout != nullptr) {
Timeout = KiComputeWaitInterval(OriginalTime, &DueTime, &NewTime);
}
} }
// Raise IRQL to DISPATCH_LEVEL and lock the database (only if it's not already at this level) // 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 // 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 // So unlock the dispatcher database, lower the IRQ and return the status
Thread->State = Running;
KiUnlockDispatcherDatabase(Thread->WaitIrql); KiUnlockDispatcherDatabase(Thread->WaitIrql);
#if 0
// No need for this at the moment, since WaitApc already executes user APCs
if (WaitStatus == X_STATUS_USER_APC) { if (WaitStatus == X_STATUS_USER_APC) {
KiExecuteUserApc(); KiExecuteUserApc();
} }
#endif
RETURN(WaitStatus); RETURN(WaitStatus);
@ -2396,14 +2428,7 @@ NoWait:
// Unlock the database and return the status // Unlock the database and return the status
//TODO: KiAdjustQuantumThread(Thread); //TODO: KiAdjustQuantumThread(Thread);
// Don't forget to remove the thread timer if the objects were signaled before the timer expired Thread->State = Running;
// (remove this when the thread scheduler is here)
if (timeout_set && Thread->Timer.Header.Inserted == TRUE) {
KiTimerLock();
KxRemoveTreeTimer(&Thread->Timer);
KiTimerUnlock();
}
KiUnlockDispatcherDatabase(Thread->WaitIrql); KiUnlockDispatcherDatabase(Thread->WaitIrql);
if (WaitStatus == X_STATUS_USER_APC) { if (WaitStatus == X_STATUS_USER_APC) {
@ -2445,12 +2470,9 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
// Wait Loop // Wait Loop
// This loop ends // This loop ends
PLARGE_INTEGER OriginalTime = Timeout; PLARGE_INTEGER OriginalTime = Timeout;
LARGE_INTEGER DueTime, NewTime;
KWAIT_BLOCK StackWaitBlock; KWAIT_BLOCK StackWaitBlock;
PKWAIT_BLOCK WaitBlock = &StackWaitBlock; PKWAIT_BLOCK WaitBlock = &StackWaitBlock;
NTSTATUS WaitStatus; ntstatus_xt WaitStatus;
// Hack variable (remove this when the thread scheduler is here)
bool timeout_set = false;
do { do {
// Check if we need to let an APC run. This should immediately trigger APC interrupt via a call to UnlockDispatcherDatabase // 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)) { if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
@ -2498,36 +2520,20 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
goto NoWait; goto NoWait;
} }
// Setup a timer for the thread but only once (for now) // Setup a timer for the thread
if (!timeout_set) { KiTimerLock();
KiTimerLock(); PKTIMER Timer = &Thread->Timer;
PKTIMER Timer = &Thread->Timer; PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock;
PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock; WaitBlock->NextWaitBlock = WaitTimer;
WaitBlock->NextWaitBlock = WaitTimer; Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry; Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry; WaitTimer->NextWaitBlock = WaitBlock;
WaitTimer->NextWaitBlock = WaitBlock; if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) { WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
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;
KiTimerUnlock(); 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; goto NoWait;
} }
KiTimerUnlock();
} }
else { else {
WaitBlock->NextWaitBlock = WaitBlock; 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 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 // If the current thread is processing a queue object, wake other treads using the same queue
PRKQUEUE Queue = (PRKQUEUE)Thread->Queue; PRKQUEUE Queue = (PRKQUEUE)Thread->Queue;
if (Queue != NULL) { if (Queue != NULL) {
@ -2553,7 +2556,7 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
Thread->WaitMode = WaitMode; Thread->WaitMode = WaitMode;
Thread->WaitReason = (UCHAR)WaitReason; Thread->WaitReason = (UCHAR)WaitReason;
Thread->WaitTime = KeTickCount; Thread->WaitTime = KeTickCount;
// TODO: Thread->State = Waiting; Thread->State = Waiting;
//KiInsertWaitList(WaitMode, Thread); //KiInsertWaitList(WaitMode, Thread);
/* /*
@ -2568,13 +2571,21 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject
return WaitStatus; return WaitStatus;
} */ } */
// TODO: Remove this after we have our own scheduler and the above is implemented // Insert the WaitBlock
Sleep(0); KiWaitListLock();
InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry);
KiWaitListUnlock();
// Reduce the timout if necessary // TODO: Remove this after we have our own scheduler and the above is implemented
if (Timeout != nullptr) { WaitStatus = WaitApc<false>([](PKTHREAD Thread) -> std::optional<ntstatus_xt> {
Timeout = KiComputeWaitInterval(OriginalTime, &DueTime, &NewTime); 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 // 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 // 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 // So unlock the dispatcher database, lower the IRQ and return the status
Thread->State = Running;
KiUnlockDispatcherDatabase(Thread->WaitIrql); KiUnlockDispatcherDatabase(Thread->WaitIrql);
#if 0
// No need for this at the moment, since WaitApc already executes user APCs
if (WaitStatus == X_STATUS_USER_APC) { if (WaitStatus == X_STATUS_USER_APC) {
KiExecuteUserApc(); KiExecuteUserApc();
} }
#endif
RETURN(WaitStatus); RETURN(WaitStatus);
@ -2601,14 +2616,7 @@ NoWait:
// Unlock the database and return the status // Unlock the database and return the status
//TODO: KiAdjustQuantumThread(Thread); //TODO: KiAdjustQuantumThread(Thread);
// Don't forget to remove the thread timer if the object was signaled before the timer expired Thread->State = Running;
// (remove this when the thread scheduler is here)
if (timeout_set && Thread->Timer.Header.Inserted == TRUE) {
KiTimerLock();
KxRemoveTreeTimer(&Thread->Timer);
KiTimerUnlock();
}
KiUnlockDispatcherDatabase(Thread->WaitIrql); KiUnlockDispatcherDatabase(Thread->WaitIrql);
if (WaitStatus == X_STATUS_USER_APC) { if (WaitStatus == X_STATUS_USER_APC) {

View File

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

View File

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

View File

@ -47,6 +47,12 @@ namespace xbox
int Acquired; int Acquired;
} KI_TIMER_LOCK; } 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 // NOTE: since the apc list is per-thread, we could also create a different mutex for each kthread
extern std::mutex KiApcListMtx; extern std::mutex KiApcListMtx;
@ -56,10 +62,11 @@ namespace xbox
void_xt KiTimerUnlock(); void_xt KiTimerUnlock();
void_xt KiClockIsr void_xt KiWaitListLock();
(
IN unsigned int ScalingFactor void_xt KiWaitListUnlock();
);
void_xt KiClockIsr(ulonglong_xt TotalUs);
xbox::void_xt NTAPI KiCheckTimerTable xbox::void_xt NTAPI KiCheckTimerTable
( (
@ -132,7 +139,7 @@ namespace xbox
IN KIRQL OldIrql IN KIRQL OldIrql
); );
void_xt FASTCALL KiWaitSatisfyAll void_xt KiWaitSatisfyAll
( (
IN PKWAIT_BLOCK WaitBlock IN PKWAIT_BLOCK WaitBlock
); );
@ -156,7 +163,8 @@ namespace xbox
); );
// Source: ReactOS // Source: ReactOS
void_xt NTAPI KiSuspendNop( void_xt NTAPI KiSuspendNop
(
IN PKAPC Apc, IN PKAPC Apc,
IN PKNORMAL_ROUTINE* NormalRoutine, IN PKNORMAL_ROUTINE* NormalRoutine,
IN PVOID* NormalContext, IN PVOID* NormalContext,
@ -164,6 +172,15 @@ namespace xbox
IN PVOID* SystemArgument2 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 // Source: ReactOS
void_xt NTAPI KiSuspendThread( void_xt NTAPI KiSuspendThread(
IN PVOID NormalContext, IN PVOID NormalContext,
@ -180,6 +197,48 @@ namespace xbox
IN PKSTART_ROUTINE StartRoutine, IN PKSTART_ROUTINE StartRoutine,
IN PVOID StartContext 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; extern xbox::KPROCESS KiUniqueProcess;

View File

@ -47,6 +47,7 @@ namespace NtDll
#include "core\kernel\support\EmuFile.h" // For EmuNtSymbolicLinkObject, NtStatusToString(), etc. #include "core\kernel\support\EmuFile.h" // For EmuNtSymbolicLinkObject, NtStatusToString(), etc.
#include "core\kernel\memory-manager\VMManager.h" // For g_VMManager #include "core\kernel\memory-manager\VMManager.h" // For g_VMManager
#include "core\kernel\support\NativeHandle.h" #include "core\kernel\support\NativeHandle.h"
#include "devices\Xbox.h"
#include "CxbxDebugger.h" #include "CxbxDebugger.h"
#pragma warning(disable:4005) // Ignore redefined status values #pragma warning(disable:4005) // Ignore redefined status values
@ -58,7 +59,7 @@ namespace NtDll
#include <mutex> #include <mutex>
// Prevent setting the system time from multiple threads at the same time // Prevent setting the system time from multiple threads at the same time
std::mutex NtSystemTimeMtx; xbox::RTL_CRITICAL_SECTION xbox::NtSystemTimeCritSec;
// ****************************************************************** // ******************************************************************
// * 0x00B8 - NtAllocateVirtualMemory() // * 0x00B8 - NtAllocateVirtualMemory()
@ -1039,7 +1040,7 @@ XBSYSAPI EXPORTNUM(206) xbox::ntstatus_xt NTAPI xbox::NtQueueApcThread
PKAPC Apc = static_cast<PKAPC>(ExAllocatePoolWithTag(sizeof(KAPC), 'pasP')); PKAPC Apc = static_cast<PKAPC>(ExAllocatePoolWithTag(sizeof(KAPC), 'pasP'));
if (Apc != zeroptr) { 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)) { if (!KeInsertQueueApc(Apc, ApcStatusBlock, ApcReserved, 0)) {
ExFreePool(Apc); ExFreePool(Apc);
result = X_STATUS_UNSUCCESSFUL; result = X_STATUS_UNSUCCESSFUL;
@ -1711,6 +1712,16 @@ XBSYSAPI EXPORTNUM(219) xbox::ntstatus_xt NTAPI xbox::NtReadFile
CxbxDebugger::ReportFileRead(FileHandle, Length, Offset); 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) { if (ApcRoutine != nullptr) {
// Pack the original parameters to a wrapped context for a custom APC routine // Pack the original parameters to a wrapped context for a custom APC routine
CxbxIoDispatcherContext* cxbxContext = new CxbxIoDispatcherContext(IoStatusBlock, ApcRoutine, ApcContext); 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_ARG_OUT(PreviousSuspendCount)
LOG_FUNC_END; LOG_FUNC_END;
if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) { PETHREAD Thread;
// Thread handles are created by ob ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast<PVOID *>(&Thread));
RETURN(NtDll::NtResumeThread(*nativeHandle, (::PULONG)PreviousSuspendCount)); if (!X_NT_SUCCESS(result)) {
} RETURN(result);
else {
RETURN(X_STATUS_INVALID_HANDLE);
} }
// 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; ret = STATUS_ACCESS_VIOLATION;
} }
else { else {
NtSystemTimeMtx.lock(); RtlEnterCriticalSectionAndRegion(&NtSystemTimeCritSec);
NewSystemTime = *SystemTime; NewSystemTime = *SystemTime;
if (NewSystemTime.u.HighPart > 0 && NewSystemTime.u.HighPart <= 0x20000000) { if (NewSystemTime.u.HighPart > 0 && NewSystemTime.u.HighPart <= 0x20000000) {
/* Convert the time and set it in HAL */ /* Convert the time and set it in HAL */
@ -1991,7 +2007,7 @@ XBSYSAPI EXPORTNUM(228) xbox::ntstatus_xt NTAPI xbox::NtSetSystemTime
else { else {
ret = STATUS_INVALID_PARAMETER; ret = STATUS_INVALID_PARAMETER;
} }
NtSystemTimeMtx.unlock(); RtlLeaveCriticalSectionAndRegion(&NtSystemTimeCritSec);
} }
RETURN(ret); RETURN(ret);
@ -2079,15 +2095,30 @@ XBSYSAPI EXPORTNUM(231) xbox::ntstatus_xt NTAPI xbox::NtSuspendThread
LOG_FUNC_ARG_OUT(PreviousSuspendCount) LOG_FUNC_ARG_OUT(PreviousSuspendCount)
LOG_FUNC_END; LOG_FUNC_END;
if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) { PETHREAD Thread;
// Thread handles are created by ob ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast<PVOID *>(&Thread));
RETURN(NtDll::NtSuspendThread(*nativeHandle, (::PULONG)PreviousSuspendCount)); if (!X_NT_SUCCESS(result)) {
} RETURN(result);
else {
RETURN(X_STATUS_INVALID_HANDLE);
} }
// 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])) { if (const auto &nativeHandle = GetNativeHandle(Handles[i])) {
// This is a ob handle, so replace it with its native counterpart // This is a ob handle, so replace it with its native counterpart
nativeHandles[i] = *nativeHandle; nativeHandles[i] = *nativeHandle;
EmuLog(LOG_LEVEL::DEBUG, "xbox handle: %p", nativeHandles[i]);
} }
else { else {
nativeHandles[i] = Handles[i]; 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 // 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; NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0; ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtWaitForMultipleObjects( NTSTATUS Status = NtDll::NtWaitForMultipleObjects(
@ -2221,8 +2260,11 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx
if (Status == STATUS_TIMEOUT) { if (Status == STATUS_TIMEOUT) {
return std::nullopt; return std::nullopt;
} }
return std::make_optional<ntstatus_xt>(Status); // 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
}, Timeout, Alertable, WaitMode); // 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); RETURN(ret);
} }
@ -2269,6 +2311,16 @@ XBSYSAPI EXPORTNUM(236) xbox::ntstatus_xt NTAPI xbox::NtWriteFile
CxbxDebugger::ReportFileWrite(FileHandle, Length, Offset); 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) { if (ApcRoutine != nullptr) {
// Pack the original parameters to a wrapped context for a custom APC routine // Pack the original parameters to a wrapped context for a custom APC routine
CxbxIoDispatcherContext* cxbxContext = new CxbxIoDispatcherContext(IoStatusBlock, ApcRoutine, ApcContext); CxbxIoDispatcherContext* cxbxContext = new CxbxIoDispatcherContext(IoStatusBlock, ApcRoutine, ApcContext);

View File

@ -121,6 +121,8 @@ static unsigned int WINAPI PCSTProxy
params.Ethread, params.Ethread,
params.TlsDataSize); params.TlsDataSize);
xbox::KiExecuteKernelApc();
auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine; auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine;
// Debugging notice : When the below line shows up with an Exception dialog and a // 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 // 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); assert(dupHandle);
RegisterXboxHandle(eThread->UniqueThread, dupHandle); RegisterXboxHandle(eThread->UniqueThread, dupHandle);
eThread->Tcb.Priority = GetThreadPriority(handle);
g_AffinityPolicy->SetAffinityXbox(handle); g_AffinityPolicy->SetAffinityXbox(handle);
// Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED) // Wait for the initialization of the remaining thread state
if (!CreateSuspended) { KeSuspendThreadEx(&eThread->Tcb);
ResumeThread(handle); 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 : // 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]", 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); *ThreadHandle, eThread->UniqueThread, handle, ThreadId);
@ -493,10 +506,13 @@ XBSYSAPI EXPORTNUM(258) xbox::void_xt NTAPI xbox::PsTerminateSystemThread
KeQuerySystemTime(&eThread->ExitTime); KeQuerySystemTime(&eThread->ExitTime);
eThread->ExitStatus = ExitStatus; eThread->ExitStatus = ExitStatus;
eThread->Tcb.Header.SignalState = 1; eThread->Tcb.Header.SignalState = 1;
KiWaitListLock();
if (!IsListEmpty(&eThread->Tcb.Header.WaitListHead)) { if (!IsListEmpty(&eThread->Tcb.Header.WaitListHead)) {
// TODO: Implement KiWaitTest's relative objects usage KiWaitTest((PVOID)&eThread->Tcb, 0);
//KiWaitTest() std::this_thread::yield();
assert(0); }
else {
KiWaitListUnlock();
} }
if (GetNativeHandle(eThread->UniqueThread)) { if (GetNativeHandle(eThread->UniqueThread)) {

View File

@ -89,6 +89,11 @@ xbox::boolean_xt RtlpCaptureStackLimits(
return TRUE; return TRUE;
} }
xbox::void_xt xbox::RtlInitSystem()
{
xbox::RtlInitializeCriticalSection(&NtSystemTimeCritSec);
}
// ****************************************************************** // ******************************************************************
// * 0x0104 - RtlAnsiStringToUnicodeString() // * 0x0104 - RtlAnsiStringToUnicodeString()
// ****************************************************************** // ******************************************************************

View File

@ -99,9 +99,6 @@ bool g_bIsChihiro = false;
bool g_bIsDevKit = false; bool g_bIsDevKit = false;
bool g_bIsRetail = 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 // Set by the VMManager during initialization. Exported because it's needed in other parts of the emu
size_t g_SystemMaxMemory = 0; 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 // Define function located in EmuXApi so we can call it from here
void SetupXboxDeviceTypes(); void SetupXboxDeviceTypes();
extern xbox::void_xt NTAPI system_events(xbox::PVOID arg);
void SetupPerTitleKeys() void SetupPerTitleKeys()
{ {
// Generate per-title keys from the XBE Certificate // Generate per-title keys from the XBE Certificate
@ -329,64 +328,6 @@ void InitSoftwareInterrupts()
} }
#endif #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) void MapThunkTable(uint32_t* kt, uint32_t* pThunkTable)
{ {
const bool SendDebugReports = (pThunkTable == CxbxKrnl_KernelThunkTable) && CxbxDebugger::CanReport(); const bool SendDebugReports = (pThunkTable == CxbxKrnl_KernelThunkTable) && CxbxDebugger::CanReport();
@ -686,6 +627,7 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags,
// Detect XBE type : // Detect XBE type :
XbeType xbeType = CxbxKrnl_Xbe->GetXbeType(); XbeType xbeType = CxbxKrnl_Xbe->GetXbeType();
EmuLogInit(LOG_LEVEL::INFO, "Auto detect: XbeType = %s", GetXbeTypeToStr(xbeType)); EmuLogInit(LOG_LEVEL::INFO, "Auto detect: XbeType = %s", GetXbeTypeToStr(xbeType));
// Convert XBE type into corresponding system to emulate. // Convert XBE type into corresponding system to emulate.
switch (xbeType) { switch (xbeType) {
case XbeType::xtChihiro: case XbeType::xtChihiro:
@ -699,6 +641,10 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags,
break; break;
DEFAULT_UNREACHABLE; DEFAULT_UNREACHABLE;
} }
if (std::filesystem::exists(xbeDirectory / "boot.id")) {
emulate_system = SYSTEM_CHIHIRO;
}
} }
EmuLogInit(LOG_LEVEL::INFO, "Host's compatible system types: %2X", reserved_systems); EmuLogInit(LOG_LEVEL::INFO, "Host's compatible system types: %2X", reserved_systems);
@ -1220,7 +1166,7 @@ static void CxbxrKrnlInitHacks()
g_pCertificate = &CxbxKrnl_Xbe->m_Certificate; g_pCertificate = &CxbxKrnl_Xbe->m_Certificate;
// Initialize timer subsystem // Initialize timer subsystem
Timer_Init(); timer_init();
// for unicode conversions // for unicode conversions
setlocale(LC_ALL, "English"); setlocale(LC_ALL, "English");
// Initialize DPC global // Initialize DPC global
@ -1362,10 +1308,11 @@ static void CxbxrKrnlInitHacks()
} }
xbox::PsInitSystem(); xbox::PsInitSystem();
xbox::KiInitSystem(); xbox::KiInitSystem();
xbox::RtlInitSystem();
// initialize graphics // initialize graphics
EmuLogInit(LOG_LEVEL::DEBUG, "Initializing render window."); 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 // Now process the boot flags to see if there are any special conditions to handle
if (BootFlags & BOOT_EJECT_PENDING) {} // TODO if (BootFlags & BOOT_EJECT_PENDING) {} // TODO
@ -1474,23 +1421,17 @@ static void CxbxrKrnlInitHacks()
#endif #endif
EmuX86_Init(); EmuX86_Init();
// Create the interrupt processing thread // Start the event thread
xbox::HANDLE hThread; xbox::HANDLE hThread;
CxbxrCreateThread(&hThread, xbox::zeroptr, CxbxKrnlInterruptThread, xbox::zeroptr, FALSE); xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, system_events, xbox::zeroptr, FALSE);
// Start the kernel clock thread // Launch the xbe
TimerObject* KernelClockThr = Timer_Create(CxbxKrnlClockThread, nullptr, "Kernel clock thread", true);
Timer_Start(KernelClockThr, SCALE_MS_IN_NS);
xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxLaunchXbe, Entry, FALSE); xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxLaunchXbe, Entry, FALSE);
EmuKeFreePcr(); xbox::KeRaiseIrqlToDpcLevel();
__asm add esp, Host2XbStackSizeReserved; while (true) {
xbox::KeWaitForDpc();
// This will wait forever ExecuteDpcQueue();
std::condition_variable cv; }
std::mutex m;
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [] { return false; });
} }
// REMARK: the following is useless, but PatrickvL has asked to keep it for documentation purposes // REMARK: the following is useless, but PatrickvL has asked to keep it for documentation purposes
@ -1512,7 +1453,7 @@ void CxbxrKrnlSuspendThreads()
// Don't use EmuKeGetPcr because that asserts kpcr // Don't use EmuKeGetPcr because that asserts kpcr
xbox::KPCR* Pcr = reinterpret_cast<xbox::PKPCR>(__readfsdword(TIB_ArbitraryDataSlot)); xbox::KPCR* Pcr = reinterpret_cast<xbox::PKPCR>(__readfsdword(TIB_ArbitraryDataSlot));
// If there's nothing in list entry, skip this step. // If there's nothing in list entry, skip this step.
if (!ThreadListEntry) { if (!ThreadListEntry) {
return; return;

View File

@ -157,6 +157,7 @@ void CxbxKrnlNoFunc();
void InitDpcData(); // Implemented in EmuKrnlKe.cpp void InitDpcData(); // Implemented in EmuKrnlKe.cpp
bool IsDpcActive(); bool IsDpcActive();
void ExecuteDpcQueue();
/*! kernel thunk table */ /*! kernel thunk table */
extern uint32_t CxbxKrnl_KernelThunkTable[379]; extern uint32_t CxbxKrnl_KernelThunkTable[379];

View File

@ -49,11 +49,6 @@ bool g_DisablePixelShaders = false;
bool g_UseAllCores = false; bool g_UseAllCores = false;
bool g_SkipRdtscPatching = 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 Function(s)
static int ExitException(LPEXCEPTION_POINTERS e); static int ExitException(LPEXCEPTION_POINTERS e);

View File

@ -68,9 +68,6 @@ extern HWND g_hEmuWindow;
extern HANDLE g_CurrentProcessHandle; // Set in CxbxKrnlMain 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 typedef struct DUMMY_KERNEL
{ {
IMAGE_DOS_HEADER DosHeader; IMAGE_DOS_HEADER DosHeader;

View File

@ -32,6 +32,7 @@
#include "core\kernel\common\xbox.h" #include "core\kernel\common\xbox.h"
#include "cxbxr.hpp" #include "cxbxr.hpp"
#include "core\hle\Intercept.hpp" #include "core\hle\Intercept.hpp"
#include "EmuShared.h"
PCIBus* g_PCIBus; PCIBus* g_PCIBus;
SMBus* g_SMBus; SMBus* g_SMBus;
@ -42,6 +43,7 @@ NVNetDevice* g_NVNet;
NV2ADevice* g_NV2A; NV2ADevice* g_NV2A;
ADM1032Device* g_ADM1032; ADM1032Device* g_ADM1032;
USBDevice* g_USB0; USBDevice* g_USB0;
MediaBoard* g_MediaBoard;
MCPXRevision MCPXRevisionFromHardwareModel(HardwareModel hardwareModel) MCPXRevision MCPXRevisionFromHardwareModel(HardwareModel hardwareModel)
{ {
@ -151,15 +153,22 @@ void InitXboxHardware(HardwareModel hardwareModel)
// Create devices // Create devices
g_MCPX = new MCPXDevice(mcpx_revision); 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 // SMC uses different AV_PACK values than the Kernel
// See https://xboxdevwiki.net/PIC#The_AV_Pack // See https://xboxdevwiki.net/PIC#The_AV_Pack
g_EEPROM = new EEPROMDevice(); g_EEPROM = new EEPROMDevice();
g_NVNet = new NVNetDevice(); g_NVNet = new NVNetDevice();
g_NV2A = new NV2ADevice(); g_NV2A = new NV2ADevice();
g_ADM1032 = new ADM1032Device(); g_ADM1032 = new ADM1032Device();
if (bLLE_USB) { g_USB0 = new USBDevice();
g_USB0 = new USBDevice();
if (g_bIsChihiro) {
g_MediaBoard = new MediaBoard();
char MediaBoardMountPath[xbox::max_path];
g_EmuShared->GetTitleMountPath(MediaBoardMountPath);
g_MediaBoard->SetMountPath(MediaBoardMountPath);
} }
// Connect devices to SM bus // Connect devices to SM bus
@ -189,14 +198,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(5, 0)), g_NVAPU);
//g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(6, 0)), g_AC97); //g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(6, 0)), g_AC97);
g_PCIBus->ConnectDevice(PCI_DEVID(1, PCI_DEVFN(0, 0)), g_NV2A); 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,
// 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
// 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
// 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.
// 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 // TODO : Handle other SMBUS Addresses, like PIC_ADDRESS, XCALIBUR_ADDRESS
// Resources : http://pablot.com/misc/fancontroller.cpp // Resources : http://pablot.com/misc/fancontroller.cpp

View File

@ -35,6 +35,7 @@
#include "ADM1032Device.h" // For ADM1032 #include "ADM1032Device.h" // For ADM1032
#include "devices\video\nv2a.h" // For NV2ADevice #include "devices\video\nv2a.h" // For NV2ADevice
#include "Usb\USBDevice.h" // For USBDevice #include "Usb\USBDevice.h" // For USBDevice
#include "chihiro\MediaBoard.h"
#define SMBUS_ADDRESS_MCPX 0x10 // = Write; Read = 0x11 #define SMBUS_ADDRESS_MCPX 0x10 // = Write; Read = 0x11
#define SMBUS_ADDRESS_TV_ENCODER 0x88 // = Write; Read = 0x89 #define SMBUS_ADDRESS_TV_ENCODER 0x88 // = Write; Read = 0x89
@ -83,5 +84,6 @@ extern EEPROMDevice* g_EEPROM;
extern NVNetDevice* g_NVNet; extern NVNetDevice* g_NVNet;
extern NV2ADevice* g_NV2A; extern NV2ADevice* g_NV2A;
extern USBDevice* g_USB0; extern USBDevice* g_USB0;
extern MediaBoard* g_MediaBoard;
extern void InitXboxHardware(HardwareModel hardwareModel); extern void InitXboxHardware(HardwareModel hardwareModel);

View File

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

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

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

View File

@ -0,0 +1,160 @@
// 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
case 0x40F0: return 0x0000; // Media Board Type (Type-1 vs Type-3), 0x0000 = Type-1, 0x0100 = Type-3
case 0x40F4: return 0x03; // 1GB
}
printf("MediaBoard::LpcRead: Unknown Addr %08X\n", addr);
return 0;
}
void MediaBoard::LpcWrite(uint32_t addr, uint32_t value, int size)
{
switch (addr) {
case 0x40E1: HalSystemInterrupts[10].Assert(false); break;
default:
printf("MediaBoard::LpcWrite: Unknown Addr %08X = %08X\n", addr, value);
break;
}
}
void MediaBoard::ComRead(uint32_t offset, void* buffer, uint32_t length)
{
// Copy the current read buffer to the output
memcpy(buffer, readBuffer, 0x20);
}
void MediaBoard::ComWrite(uint32_t offset, void* buffer, uint32_t length)
{
// Instant replies cause race conditions, software seems to expect at least a little delay
Sleep(100);
if (offset == 0x900000) { // Some kind of reset?
memcpy(readBuffer, buffer, 0x20);
return;
} else if (offset == 0x900200) { // Command Sector
// Copy the written data to our internal, so we don't trash the original data
memcpy(writeBuffer, buffer, 0x20);
// Create accessor pointers
auto inputBuffer16 = (uint16_t*)writeBuffer;
auto inputBuffer32 = (uint32_t*)writeBuffer;
auto outputBuffer16 = (uint16_t*)readBuffer;
auto outputBuffer32 = (uint32_t*)readBuffer;
// If no command word was specified, do nothing
if (inputBuffer16[0] == 0) {
return;
}
// First word of output gets set to first word of the input, second word gets OR'D with ACK
outputBuffer16[0] = inputBuffer16[0];
outputBuffer16[1] = inputBuffer16[1] | 0x8000; // ACK?
// Read the given Command and handle it
uint32_t command = inputBuffer16[1];
switch (command) {
case MB_CMD_DIMM_SIZE:
outputBuffer32[1] = 1024 * ONE_MB;
break;
case MB_CMD_STATUS:
outputBuffer32[1] = MB_STATUS_READY;
outputBuffer32[2] = 100; // Load/Test Percentage (0-100)
break;
case MB_CMD_FIRMWARE_VERSION:
outputBuffer32[1] = 0x0317;
break;
case MB_CMD_SYSTEM_TYPE:
outputBuffer32[1] = MB_SYSTEM_TYPE_DEVELOPER | MB_SYSTEM_TYPE_GDROM;
break;
case MB_CMD_SERIAL_NUMBER:
memcpy(&outputBuffer32[1], "A89E-25A47354512", 17);
break;
case MB_CMD_HARDWARE_TEST: {
uint32_t testType = inputBuffer32[1];
xbox::addr_xt resultWritePtr = inputBuffer32[2];
outputBuffer32[1] = inputBuffer32[1];
printf("Perform Test Type %X, place result at %08X\n", testType, resultWritePtr);
// For now, just pretend we did the test and was successful
// TODO: How to report percentage? Get's stuck on "CHECKING 0% but still shows "TEST OK"
memcpy((void*)resultWritePtr, "TEST OK", 8);
} break;
default: printf("Unhandled MediaBoard Command: %04X\n", command);
}
// Clear the command bytes
inputBuffer16[0] = 0;
inputBuffer16[1] = 0;
// Trigger LPC Interrupt
HalSystemInterrupts[10].Assert(true);
return;
}
printf("Unhandled MediaBoard mbcom: offset %08X\n", offset);
}

View File

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

View File

@ -476,15 +476,18 @@ void EmuNVNet_Write(xbox::addr_xt addr, uint32_t value, int size)
} }
std::thread NVNetRecvThread; 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(); g_AffinityPolicy->SetAffinityOther();
uint8_t packet[65536]; static std::unique_ptr<uint8_t[]> packet(new uint8_t[65536]);
while (true) { while (true) {
int size = g_NVNet->PCAPReceive(packet, 65536); int size = g_NVNet->PCAPReceive(packet.get(), 65536);
if (size > 0) { if (size > 0) {
EmuNVNet_DMAPacketToGuest(packet, size); EmuNVNet_DMAPacketToGuest(packet.get(), size);
} }
_mm_pause();
} }
} }
@ -527,7 +530,7 @@ void NVNetDevice::Init()
}; };
PCAPInit(); PCAPInit();
NVNetRecvThread = std::thread(NVNetRecvThreadProc, &NvNetState); NVNetRecvThread = std::thread(NVNetRecvThreadProc);
} }
void NVNetDevice::Reset() void NVNetDevice::Reset()

View File

@ -279,11 +279,6 @@ OHCI::OHCI(USBDevice* UsbObj)
OHCI_StateReset(); OHCI_StateReset();
} }
void OHCI::OHCI_FrameBoundaryWrapper(void* pVoid)
{
static_cast<OHCI*>(pVoid)->OHCI_FrameBoundaryWorker();
}
void OHCI::OHCI_FrameBoundaryWorker() void OHCI::OHCI_FrameBoundaryWorker()
{ {
OHCI_HCCA hcca; OHCI_HCCA hcca;
@ -358,7 +353,7 @@ void OHCI::OHCI_FrameBoundaryWorker()
} }
// Do SOF stuff here // Do SOF stuff here
OHCI_SOF(false); OHCI_SOF();
// Writeback HCCA // Writeback HCCA
if (OHCI_WriteHCCA(m_Registers.HcHCCA, &hcca)) { if (OHCI_WriteHCCA(m_Registers.HcHCCA, &hcca)) {
@ -877,32 +872,23 @@ void OHCI::OHCI_StateReset()
void OHCI::OHCI_BusStart() void OHCI::OHCI_BusStart()
{ {
// Create the EOF timer. // Create the EOF timer.
m_pEOFtimer = Timer_Create(OHCI_FrameBoundaryWrapper, this, "", false); m_pEOFtimer = true;
EmuLog(LOG_LEVEL::DEBUG, "Operational event"); EmuLog(LOG_LEVEL::DEBUG, "Operational event");
// SOF event // SOF event
OHCI_SOF(true); OHCI_SOF();
} }
void OHCI::OHCI_BusStop() void OHCI::OHCI_BusStop()
{ {
if (m_pEOFtimer) { m_pEOFtimer = false;
// Delete existing EOF timer
Timer_Exit(m_pEOFtimer);
}
m_pEOFtimer = nullptr;
} }
void OHCI::OHCI_SOF(bool bCreate) void OHCI::OHCI_SOF()
{ {
// set current SOF time // set current SOF time
m_SOFtime = GetTime_NS(m_pEOFtimer); m_SOFtime = get_now();
// make timer expire at SOF + 1 ms from now
if (bCreate) {
Timer_Start(m_pEOFtimer, m_UsbFrameTime);
}
OHCI_SetInterrupt(OHCI_INTR_SF); 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() void OHCI::OHCI_UpdateInterrupt()
{ {
if ((m_Registers.HcInterrupt & OHCI_INTR_MIE) && (m_Registers.HcInterruptStatus & m_Registers.HcInterrupt)) { 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 // 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 // Avoid Muldiv64 if possible
if (ticks >= m_UsbFrameTime) { if (ticks >= m_UsbFrameTime) {

View File

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

View File

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

View File

@ -35,17 +35,13 @@
#include "common\util\CxbxUtil.h" #include "common\util\CxbxUtil.h"
#define NANOSECONDS_PER_SECOND 1000000000
/* PTIMER - time measurement and time-based alarms */ /* 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 return Muldiv64(Muldiv64(get_now(),
uint64_t time = std::chrono::duration<uint64_t, std::nano>(std::chrono::steady_clock::now().time_since_epoch()).count();
return Muldiv64(Muldiv64(time,
(uint32_t)d->pramdac.core_clock_freq, // TODO : Research how this can be updated to accept uint64_t (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.denominator,
d->ptimer.numerator); d->ptimer.numerator);
} }
@ -91,6 +87,13 @@ DEVICE_WRITE32(PTIMER)
break; break;
case NV_PTIMER_INTR_EN_0: case NV_PTIMER_INTR_EN_0:
d->ptimer.enabled_interrupts = value; 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); update_irq(d);
break; break;
case NV_PTIMER_DENOMINATOR: case NV_PTIMER_DENOMINATOR:
@ -101,6 +104,7 @@ DEVICE_WRITE32(PTIMER)
break; break;
case NV_PTIMER_ALARM_0: case NV_PTIMER_ALARM_0:
d->ptimer.alarm_time = value; 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; break;
default: default:
//DEVICE_WRITE32_REG(ptimer); // Was : DEBUG_WRITE32_UNHANDLED(PTIMER); //DEVICE_WRITE32_REG(ptimer); // Was : DEBUG_WRITE32_UNHANDLED(PTIMER);

View File

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

View File

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

View File

@ -357,10 +357,15 @@ typedef struct OverlayState {
} OverlayState; } OverlayState;
typedef struct NV2AState { typedef struct NV2AState {
void(* vblank_cb)(void *);
uint64_t vblank_last;
// PCIDevice dev; // PCIDevice dev;
// qemu_irq irq; // qemu_irq irq;
bool exiting; bool exiting;
bool enable_overlay = false; bool enable_overlay = false;
bool ptimer_active = false;
uint64_t ptimer_last;
uint64_t ptimer_period;
// VGACommonState vga; // VGACommonState vga;
// GraphicHwOps hw_ops; // GraphicHwOps hw_ops;

View File

@ -57,10 +57,15 @@ extern std::atomic_bool g_bEnableAllInterrupts;
static int field_pin = 0; static int field_pin = 0;
static thread_local bool g_tls_isEmuX86Managed;
uint32_t EmuX86_IORead(xbox::addr_xt addr, int size) 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) { switch (addr) {
case 0x8008: { // TODO : Move 0x8008 TIMER to a device case 0x8008: { // TODO : Move 0x8008 TIMER to a device
if (size == sizeof(uint32_t)) { 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) 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 // 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)) { if (g_PCIBus->IOWrite(addr, value, size)) {
return; return;
@ -197,11 +210,8 @@ uint32_t EmuX86_Read(xbox::addr_xt addr, int size)
return value; return value;
} }
// EmuX86 is not suppose to do direct read to host memory and should be handle from // EmuX86 should not directly access host memory.
// redirect from above statements. If it doesn't meet any requirement, then should be EmuLog(LOG_LEVEL::WARNING, "EmuX86_Read(0x%08X, %d) [Unhandled]", addr, size);
// handle as possible fatal crash instead of return corrupt value.
g_tls_isEmuX86Managed = false;
return 0; return 0;
} }
@ -223,10 +233,8 @@ void EmuX86_Write(xbox::addr_xt addr, uint32_t value, int size)
return; return;
} }
// EmuX86 is not suppose to do direct write to host memory and should be handle from // EmuX86 should not directly access host memory.
// redirect from above statements. If it doesn't meet any requirement, then should be EmuLog(LOG_LEVEL::WARNING, "EmuX86_Write(0x%08X, 0x%08X, %d) [Unhandled]", addr, value, size);
// handle as possible fatal crash instead of set corrupt value.
g_tls_isEmuX86Managed = false;
} }
int ContextRecordOffsetByRegisterType[/*_RegisterType*/R_DR7 + 1] = { 0 }; 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, // 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. // that case may be logged, but it shouldn't fail the opcode handler.
_DInst info; _DInst info;
g_tls_isEmuX86Managed = true;
DWORD StartingEip = e->ContextRecord->Eip; DWORD StartingEip = e->ContextRecord->Eip;
EmuLog(LOG_LEVEL::DEBUG, "Starting instruction emulation from 0x%08X", 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; return true;
} // switch info.opcode } // switch info.opcode
if (g_tls_isEmuX86Managed) {
e->ContextRecord->Eip += info.size; e->ContextRecord->Eip += info.size;
}
else {
break;
}
} // while true } // while true
return g_tls_isEmuX86Managed; return true;
opcode_error: opcode_error:
EmuLog(LOG_LEVEL::WARNING, "0x%08X: Error while handling instruction %s (%u)", e->ContextRecord->Eip, Distorm_OpcodeString(info.opcode), info.opcode); EmuLog(LOG_LEVEL::WARNING, "0x%08X: Error while handling instruction %s (%u)", e->ContextRecord->Eip, Distorm_OpcodeString(info.opcode), info.opcode);

View File

@ -394,6 +394,13 @@ LRESULT CALLBACK WndMain::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP
}; };
break; // added per PVS suggestion. 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: case WM_TIMER:
{ {
switch (wParam) switch (wParam)

View File

@ -353,6 +353,7 @@
#define ID_SETTINGS_EXPERIMENTAL 40113 #define ID_SETTINGS_EXPERIMENTAL 40113
#define ID_SETTINGS_IGNOREINVALIDXBESIG 40114 #define ID_SETTINGS_IGNOREINVALIDXBESIG 40114
#define ID_SETTINGS_IGNOREINVALIDXBESEC 40115 #define ID_SETTINGS_IGNOREINVALIDXBESEC 40115
#define ID_SYNC_TIME_CHANGE 40116
#define IDC_STATIC -1 #define IDC_STATIC -1
// Next default values for new objects // Next default values for new objects
@ -360,7 +361,7 @@
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 139 #define _APS_NEXT_RESOURCE_VALUE 139
#define _APS_NEXT_COMMAND_VALUE 40116 #define _APS_NEXT_COMMAND_VALUE 40117
#define _APS_NEXT_CONTROL_VALUE 1308 #define _APS_NEXT_CONTROL_VALUE 1308
#define _APS_NEXT_SYMED_VALUE 109 #define _APS_NEXT_SYMED_VALUE 109
#endif #endif