mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 12:07:31 +01:00
As on a PSP: pressing HOME in a game shows "Do you want to quit the game?" over it. The game keeps running but gets no input while it's up (sceCtrl samples are blanked). Left/right picks Yes or No, the confirm button (our Confirmation button setting, or what compat forces) chooses, the other face button or HOME closes it, and Yes/No can be tapped. The volume keys change the game volume a step at a time, shown on the volume bar. Yes runs the game's registered exit callback first, like the PSP does, showing "Please wait..." meanwhile. Then, if the XMB launched the game, the XMB is booted again; otherwise it's back to the game browser, like Exit to menu. If the callback hasn't ended the game in 10 s, it's quit anyway. Not in the XMB itself, the PS1 emulator (its own HOME menu) or GE dumps. It lives in UI/ImposeHome.cpp; EmuScreen only feeds it input and frames and draws it over the PSP display. With a firmware dump installed, it's drawn with the firmware's own art from flash0:/kd/resource/impose.rsc, which is what impose.prx draws it from: prerendered text for each system language (the question, Yes/No normal and highlighted, Enter/Back, Please wait...), the button icons, the lines, the volume bar, the battery and the clock glyphs. The format is documented in ImposeHome.cpp. Without one, the same screen is drawn with our font and simple shapes at the same positions. Where impose.rsc doesn't say where things go, the layout follows impose_02g.prx (6.61), decrypted and disassembled: - The screen is filled with 0x9CE7 (#393939); the images are opaque. - Lines at (80, 96) and (80, 175); impose.prx grows them from the middle when it opens, which isn't done here. - Volume bar: the speaker at (28, 223), 30 steps from x 62, 12 apart. It shows our game volume in the PSP's 30 steps. - Button guide: the cross icon always left, the circle right, "Enter" and "Back" next to whichever confirms and cancels. - Status bar: the battery at (450, 5) (60% and up full, 30% two bars, 12% one, then empty), the clock ending next to it at y 5: "M/D H:MM", or "D/M" for the DD/MM/YYYY format, no leading zero on the month, day or hour, AM/PM for the 12-hour clock. The clock is the emulated PSP's local time (__RtcGetCurrentLocalTime, new), in the system's date and time formats. The battery is this device's, since the emulated PSP's is always full; a device without one shows it full. For that, Windows now reports -1 for SYSPROP_BATTERY_PERCENTAGE without a battery (was 100), as SDL already did; SDL polls the battery even when the status overlay doesn't show it, and the overlay leaves it out when there's none. Core side: __CtrlSetGameInputBlocked; __KernelCanExitToVSH and __KernelRequestExitToVSH to quit to the XMB from outside a syscall; and __KernelInvokeRegisteredExitCallback(toVSH), so a callback that returns without exiting ends in the XMB rather than powering down. New strings: "Do you want to quit the game?", "Please wait..." (Dialog). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB244iFpvm14JH2FbwHXeQ
727 lines
22 KiB
C++
727 lines
22 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// 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 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <vector>
|
|
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Common/Math/math_util.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/HLE/HLE.h"
|
|
#include "Core/HLE/ErrorCodes.h"
|
|
#include "Core/HLE/FunctionWrappers.h"
|
|
#include "Core/HLE/sceCtrl.h"
|
|
#include "Core/HLE/sceKernel.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/HLE/sceKernelInterrupt.h"
|
|
#include "Core/HW/Display.h"
|
|
#include "Core/System.h"
|
|
#include "Core/MemMapHelpers.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/Replay.h"
|
|
#include "Core/Util/AudioFormat.h" // for clamp_u8
|
|
|
|
/* Index for the two analog directions */
|
|
#define CTRL_ANALOG_X 0
|
|
#define CTRL_ANALOG_Y 1
|
|
#define CTRL_ANALOG_CENTER 128
|
|
|
|
#define CTRL_MODE_DIGITAL 0
|
|
#define CTRL_MODE_ANALOG 1
|
|
|
|
const u32 NUM_CTRL_BUFFERS = 64;
|
|
|
|
enum {
|
|
CTRL_WAIT_POSITIVE = 1,
|
|
CTRL_WAIT_NEGATIVE = 2,
|
|
};
|
|
|
|
struct CtrlData {
|
|
u32_le frame;
|
|
u32_le buttons;
|
|
// The PSP has only one stick, but has space for more info.
|
|
// The second stick is populated for HD remasters and possibly in the PSP emulator on PS3/Vita.
|
|
u8 analog[2][2];
|
|
u8 unused[4];
|
|
};
|
|
|
|
struct CtrlLatch {
|
|
u32_le btnMake;
|
|
u32_le btnBreak;
|
|
u32_le btnPress;
|
|
u32_le btnRelease;
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// STATE BEGIN
|
|
static bool analogEnabled = false;
|
|
static int ctrlLatchBufs = 0;
|
|
static u32 ctrlOldButtons = 0;
|
|
|
|
static CtrlData ctrlBufs[NUM_CTRL_BUFFERS];
|
|
static CtrlData ctrlCurrent;
|
|
static u32 ctrlBuf = 0;
|
|
static u32 ctrlBufRead = 0;
|
|
static CtrlLatch latch;
|
|
static u32 dialogBtnMake = 0;
|
|
|
|
static int ctrlIdleReset = -1;
|
|
static int ctrlIdleBack = -1;
|
|
|
|
static int ctrlCycle = 0;
|
|
|
|
static std::vector<SceUID> waitingThreads;
|
|
static std::mutex ctrlMutex;
|
|
|
|
static int ctrlTimer = -1;
|
|
|
|
static u16 leftVibration = 0;
|
|
static u16 rightVibration = 0;
|
|
// The higher the dropout, the longer Vibration will run
|
|
static u8 vibrationLeftDropout = 160;
|
|
static u8 vibrationRightDropout = 160;
|
|
|
|
// STATE END
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Not savestated, this is emu state.
|
|
// Not related to sceCtrl*RapidFire(), although it may do the same thing.
|
|
static bool emuRapidFire = false;
|
|
static u32 emuRapidFireFrames = 0;
|
|
static bool emuRapidFireToggle = true;
|
|
static u32 emuRapidFireInterval = 5;
|
|
|
|
// Set while a system overlay (the HOME button's quit screen) owns the controls: the game then reads
|
|
// no buttons and centered sticks, as impose does on a PSP. Host-side peeks still see the real state.
|
|
static std::atomic<bool> gameInputBlocked{ false };
|
|
|
|
// These buttons are not affected by rapid fire (neither is analog.)
|
|
const u32 CTRL_EMU_RAPIDFIRE_MASK = CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT;
|
|
|
|
static void __CtrlUpdateLatch()
|
|
{
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
u64 t = CoreTiming::GetGlobalTimeUs();
|
|
|
|
u32 buttons = ctrlCurrent.buttons;
|
|
if (emuRapidFire && emuRapidFireToggle)
|
|
buttons &= CTRL_EMU_RAPIDFIRE_MASK;
|
|
|
|
ReplayApplyCtrl(buttons, ctrlCurrent.analog, t);
|
|
|
|
// Copy in the current data to the current buffer.
|
|
ctrlBufs[ctrlBuf] = ctrlCurrent;
|
|
|
|
if (PSP_CoreParameter().compat.flags().DaxterRotatedAnalogStick) {
|
|
// For some reason, Daxter rotates the analog input. See #17015
|
|
float angle = (15.0f / 360.0f) * (2.0f * M_PI);
|
|
float cosAngle = cosf(angle);
|
|
float sinAngle = sinf(angle);
|
|
float x = ctrlBufs[ctrlBuf].analog[0][0] - 128;
|
|
float y = ctrlBufs[ctrlBuf].analog[0][1] - 128;
|
|
float rX = x * cosAngle - y * sinAngle;
|
|
float rY = x * sinAngle + y * cosAngle;
|
|
ctrlBufs[ctrlBuf].analog[0][0] = (u8)Clamp(rX + 128, 0.0f, 255.0f);
|
|
ctrlBufs[ctrlBuf].analog[0][1] = (u8)Clamp(rY + 128, 0.0f, 255.0f);
|
|
}
|
|
|
|
if (gameInputBlocked) {
|
|
buttons = 0;
|
|
memset(ctrlBufs[ctrlBuf].analog, CTRL_ANALOG_CENTER, sizeof(ctrlBufs[ctrlBuf].analog));
|
|
}
|
|
|
|
ctrlBufs[ctrlBuf].buttons = buttons;
|
|
|
|
u32 changed = buttons ^ ctrlOldButtons;
|
|
latch.btnMake |= buttons & changed;
|
|
latch.btnBreak |= ctrlOldButtons & changed;
|
|
latch.btnPress |= buttons;
|
|
latch.btnRelease |= ~buttons;
|
|
dialogBtnMake |= buttons & changed;
|
|
ctrlLatchBufs++;
|
|
|
|
ctrlOldButtons = buttons;
|
|
|
|
ctrlBufs[ctrlBuf].frame = (u32)t;
|
|
if (!analogEnabled)
|
|
memset(ctrlBufs[ctrlBuf].analog, CTRL_ANALOG_CENTER, sizeof(ctrlBufs[ctrlBuf].analog));
|
|
|
|
ctrlBuf = (ctrlBuf + 1) % NUM_CTRL_BUFFERS;
|
|
|
|
// If we wrapped around, push the read head forward.
|
|
// TODO: Is this right?
|
|
if (ctrlBufRead == ctrlBuf)
|
|
ctrlBufRead = (ctrlBufRead + 1) % NUM_CTRL_BUFFERS;
|
|
}
|
|
|
|
static int __CtrlResetLatch()
|
|
{
|
|
int oldBufs = ctrlLatchBufs;
|
|
memset(&latch, 0, sizeof(CtrlLatch));
|
|
ctrlLatchBufs = 0;
|
|
return oldBufs;
|
|
}
|
|
|
|
void __CtrlSetGameInputBlocked(bool blocked) {
|
|
gameInputBlocked = blocked;
|
|
}
|
|
|
|
bool __CtrlIsGameInputBlocked() {
|
|
return gameInputBlocked;
|
|
}
|
|
|
|
u32 __CtrlPeekButtons()
|
|
{
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
|
|
return ctrlCurrent.buttons;
|
|
}
|
|
|
|
u32 __CtrlPeekButtonsVisual()
|
|
{
|
|
u32 buttons;
|
|
{
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
buttons = ctrlCurrent.buttons;
|
|
}
|
|
|
|
if (emuRapidFire && emuRapidFireToggle)
|
|
buttons &= CTRL_EMU_RAPIDFIRE_MASK;
|
|
return buttons;
|
|
}
|
|
|
|
void __CtrlPeekAnalog(int stick, float *x, float *y)
|
|
{
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
|
|
*x = (ctrlCurrent.analog[stick][CTRL_ANALOG_X] - 127.5f) / 127.5f;
|
|
*y = -(ctrlCurrent.analog[stick][CTRL_ANALOG_Y] - 127.5f) / 127.5f;
|
|
}
|
|
|
|
|
|
u32 __CtrlReadLatch()
|
|
{
|
|
u32 ret = dialogBtnMake;
|
|
dialogBtnMake = 0;
|
|
return ret;
|
|
}
|
|
|
|
// Presses held for a number of vblank samples (the debugger's input.buttons.press). Counted down on
|
|
// the emulator thread at each vblank, so how long a press lasts doesn't depend on how fast the
|
|
// emulator runs. Guarded by ctrlMutex; not saved.
|
|
struct TimedPress {
|
|
int id;
|
|
u32 buttons;
|
|
int samplesLeft;
|
|
};
|
|
static std::vector<TimedPress> timedPresses;
|
|
static int nextTimedPressId = 1;
|
|
|
|
int __CtrlPressFor(u32 buttons, int vblanks) {
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
buttons &= CTRL_MASK_USER;
|
|
ctrlCurrent.buttons |= buttons;
|
|
const int id = nextTimedPressId++;
|
|
timedPresses.push_back(TimedPress{ id, buttons, std::max(vblanks, 1) });
|
|
return id;
|
|
}
|
|
|
|
bool __CtrlPressActive(int id) {
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
for (const TimedPress &press : timedPresses) {
|
|
if (press.id == id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// After the vblank's sample, so a press of N vblanks is seen by N samples.
|
|
static void __CtrlUpdateTimedPresses() {
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
if (timedPresses.empty()) {
|
|
return;
|
|
}
|
|
u32 released = 0;
|
|
for (TimedPress &press : timedPresses) {
|
|
if (--press.samplesLeft <= 0) {
|
|
released |= press.buttons;
|
|
}
|
|
}
|
|
timedPresses.erase(std::remove_if(timedPresses.begin(), timedPresses.end(), [](const TimedPress &press) {
|
|
return press.samplesLeft <= 0;
|
|
}), timedPresses.end());
|
|
// Unless another press still holds them.
|
|
for (const TimedPress &press : timedPresses) {
|
|
released &= ~press.buttons;
|
|
}
|
|
ctrlCurrent.buttons &= ~released;
|
|
}
|
|
|
|
void __CtrlUpdateButtons(u32 bitsToSet, u32 bitsToClear)
|
|
{
|
|
bitsToClear &= CTRL_MASK_USER;
|
|
bitsToSet &= CTRL_MASK_USER;
|
|
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
// There's no atomic operation for this, so mutex it is.
|
|
ctrlCurrent.buttons = (ctrlCurrent.buttons & ~bitsToClear) | bitsToSet;
|
|
}
|
|
|
|
void __CtrlSetAnalogXY(int stick, float x, float y)
|
|
{
|
|
u8 scaledX = clamp_u8((int)ceilf(x * 127.5f + 127.5f));
|
|
// TODO: We might have too many negations of Y...
|
|
u8 scaledY = clamp_u8((int)ceilf(-y * 127.5f + 127.5f));
|
|
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
ctrlCurrent.analog[stick][CTRL_ANALOG_X] = scaledX;
|
|
ctrlCurrent.analog[stick][CTRL_ANALOG_Y] = scaledY;
|
|
}
|
|
|
|
// not making XY to use these due to mutex guard usage
|
|
void __CtrlSetAnalogX(int stick, float x) {
|
|
u8 scaledX = clamp_u8((int)ceilf(x * 127.5f + 127.5f));
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
ctrlCurrent.analog[stick][CTRL_ANALOG_X] = scaledX;
|
|
}
|
|
|
|
void __CtrlSetAnalogY(int stick, float y) {
|
|
u8 scaledY = clamp_u8((int)ceilf(-y * 127.5f + 127.5f));
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
ctrlCurrent.analog[stick][CTRL_ANALOG_Y] = scaledY;
|
|
}
|
|
|
|
void __CtrlSetRapidFire(bool state, int interval) {
|
|
emuRapidFire = state;
|
|
emuRapidFireToggle = true;
|
|
emuRapidFireInterval = interval;
|
|
}
|
|
|
|
bool __CtrlGetRapidFire()
|
|
{
|
|
return emuRapidFire;
|
|
}
|
|
|
|
static int __CtrlReadSingleBuffer(PSPPointer<CtrlData> data, bool negative)
|
|
{
|
|
if (data.IsValid())
|
|
{
|
|
*data = ctrlBufs[ctrlBufRead];
|
|
ctrlBufRead = (ctrlBufRead + 1) % NUM_CTRL_BUFFERS;
|
|
|
|
// Mask out buttons games aren't allowed to see.
|
|
data->buttons &= CTRL_MASK_USER;
|
|
if (negative)
|
|
data->buttons = ~data->buttons;
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __CtrlReadBuffer(u32 ctrlDataPtr, u32 nBufs, bool negative, bool peek)
|
|
{
|
|
if (nBufs > NUM_CTRL_BUFFERS)
|
|
return SCE_KERNEL_ERROR_INVALID_SIZE;
|
|
|
|
if (!peek && !__KernelIsDispatchEnabled())
|
|
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
|
|
if (!peek && __IsInInterrupt())
|
|
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
|
|
|
|
u32 resetRead = ctrlBufRead;
|
|
|
|
u32 availBufs;
|
|
// Peeks always work, they just go go from now X buffers.
|
|
if (peek)
|
|
availBufs = nBufs;
|
|
else
|
|
{
|
|
availBufs = (ctrlBuf - ctrlBufRead + NUM_CTRL_BUFFERS) % NUM_CTRL_BUFFERS;
|
|
if (availBufs > nBufs)
|
|
availBufs = nBufs;
|
|
}
|
|
ctrlBufRead = (ctrlBuf - availBufs + NUM_CTRL_BUFFERS) % NUM_CTRL_BUFFERS;
|
|
|
|
int done = 0;
|
|
auto data = PSPPointer<CtrlData>::Create(ctrlDataPtr);
|
|
for (u32 i = 0; i < availBufs; ++i)
|
|
done += __CtrlReadSingleBuffer(data++, negative);
|
|
|
|
if (peek)
|
|
ctrlBufRead = resetRead;
|
|
|
|
return done;
|
|
}
|
|
|
|
static void __CtrlDoSample()
|
|
{
|
|
// This samples the ctrl data into the buffers and updates the latch.
|
|
__CtrlUpdateLatch();
|
|
|
|
// Wake up a single thread that was waiting for the buffer.
|
|
retry:
|
|
if (!waitingThreads.empty() && ctrlBuf != ctrlBufRead)
|
|
{
|
|
SceUID threadID = waitingThreads[0];
|
|
waitingThreads.erase(waitingThreads.begin());
|
|
|
|
u32 error;
|
|
SceUID wVal = __KernelGetWaitID(threadID, WAITTYPE_CTRL, error);
|
|
// Make sure it didn't get woken or something.
|
|
if (wVal == 0)
|
|
goto retry;
|
|
|
|
PSPPointer<CtrlData> ctrlDataPtr;
|
|
ctrlDataPtr = __KernelGetWaitValue(threadID, error);
|
|
int retVal = __CtrlReadSingleBuffer(ctrlDataPtr, wVal == CTRL_WAIT_NEGATIVE);
|
|
__KernelResumeThreadFromWait(threadID, retVal);
|
|
__KernelReSchedule("ctrl buffers updated");
|
|
}
|
|
}
|
|
|
|
void __CtrlVblank() {
|
|
emuRapidFireFrames++;
|
|
if (emuRapidFireFrames >= emuRapidFireInterval) {
|
|
emuRapidFireFrames = 0;
|
|
emuRapidFireToggle = !emuRapidFireToggle;
|
|
}
|
|
|
|
// Reduce gamepad Vibration by set % each frame
|
|
leftVibration *= (float)vibrationLeftDropout / 256.0f;
|
|
rightVibration *= (float)vibrationRightDropout / 256.0f;
|
|
|
|
// This always runs, so make sure we're in vblank mode.
|
|
if (ctrlCycle == 0)
|
|
__CtrlDoSample();
|
|
__CtrlUpdateTimedPresses();
|
|
}
|
|
|
|
static void __CtrlTimerUpdate(u64 userdata, int cyclesLate)
|
|
{
|
|
// This only runs in timer mode (ctrlCycle > 0.)
|
|
_dbg_assert_msg_(ctrlCycle > 0, "Ctrl: sampling cycle should be > 0");
|
|
|
|
CoreTiming::ScheduleEvent(usToCycles(ctrlCycle) - cyclesLate, ctrlTimer, 0);
|
|
|
|
__CtrlDoSample();
|
|
}
|
|
|
|
void __CtrlInit() {
|
|
ctrlTimer = CoreTiming::RegisterEvent("CtrlSampleTimer", __CtrlTimerUpdate);
|
|
|
|
ctrlIdleReset = -1;
|
|
ctrlIdleBack = -1;
|
|
ctrlCycle = 0;
|
|
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
|
|
ctrlBuf = 1;
|
|
ctrlBufRead = 0;
|
|
ctrlOldButtons = 0;
|
|
ctrlLatchBufs = 0;
|
|
dialogBtnMake = 0;
|
|
|
|
memset(&latch, 0, sizeof(latch));
|
|
// Start with everything released.
|
|
latch.btnRelease = 0xffffffff;
|
|
|
|
gameInputBlocked = false;
|
|
memset(&ctrlCurrent, 0, sizeof(ctrlCurrent));
|
|
memset(ctrlCurrent.analog, CTRL_ANALOG_CENTER, sizeof(ctrlCurrent.analog));
|
|
analogEnabled = false;
|
|
|
|
for (u32 i = 0; i < NUM_CTRL_BUFFERS; i++)
|
|
memcpy(&ctrlBufs[i], &ctrlCurrent, sizeof(CtrlData));
|
|
}
|
|
|
|
void __CtrlDoState(PointerWrap &p)
|
|
{
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
|
|
auto s = p.Section("sceCtrl", 1, 3);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, analogEnabled);
|
|
Do(p, ctrlLatchBufs);
|
|
Do(p, ctrlOldButtons);
|
|
|
|
p.DoVoid(ctrlBufs, sizeof(ctrlBufs));
|
|
if (s <= 2) {
|
|
CtrlData dummy = {0};
|
|
Do(p, dummy);
|
|
}
|
|
Do(p, ctrlBuf);
|
|
Do(p, ctrlBufRead);
|
|
if (ctrlBuf >= NUM_CTRL_BUFFERS || ctrlBufRead >= NUM_CTRL_BUFFERS) {
|
|
ERROR_LOG(Log::sceCtrl, "Savestate failure: invalid ctrl buffer index");
|
|
p.SetError(p.ERROR_FAILURE);
|
|
ctrlBuf = 0;
|
|
ctrlBufRead = 0;
|
|
return;
|
|
}
|
|
Do(p, latch);
|
|
if (s == 1) {
|
|
dialogBtnMake = 0;
|
|
} else {
|
|
Do(p, dialogBtnMake);
|
|
}
|
|
|
|
Do(p, ctrlIdleReset);
|
|
Do(p, ctrlIdleBack);
|
|
|
|
Do(p, ctrlCycle);
|
|
|
|
SceUID dv = 0;
|
|
Do(p, waitingThreads, dv);
|
|
|
|
Do(p, ctrlTimer);
|
|
CoreTiming::RestoreRegisterEvent(ctrlTimer, "CtrlSampleTimer", __CtrlTimerUpdate);
|
|
}
|
|
|
|
void __CtrlShutdown()
|
|
{
|
|
waitingThreads.clear();
|
|
std::lock_guard<std::mutex> guard(ctrlMutex);
|
|
timedPresses.clear();
|
|
}
|
|
|
|
static u32 sceCtrlSetSamplingCycle(u32 cycle)
|
|
{
|
|
if ((cycle > 0 && cycle < 5555) || cycle > 20000) {
|
|
return hleLogWarning(Log::sceCtrl, SCE_KERNEL_ERROR_INVALID_VALUE);
|
|
}
|
|
|
|
u32 prev = ctrlCycle;
|
|
ctrlCycle = cycle;
|
|
|
|
if (prev > 0)
|
|
CoreTiming::UnscheduleEvent(ctrlTimer, 0);
|
|
if (cycle > 0)
|
|
CoreTiming::ScheduleEvent(usToCycles(ctrlCycle), ctrlTimer, 0);
|
|
|
|
return hleLogDebug(Log::sceCtrl, prev);
|
|
}
|
|
|
|
static int sceCtrlGetSamplingCycle(u32 cyclePtr)
|
|
{
|
|
if (Memory::IsValidAddress(cyclePtr))
|
|
Memory::WriteUnchecked_U32(ctrlCycle, cyclePtr);
|
|
return hleLogDebug(Log::sceCtrl, 0);
|
|
}
|
|
|
|
static u32 sceCtrlSetSamplingMode(u32 mode)
|
|
{
|
|
u32 retVal = 0;
|
|
if (mode > 1)
|
|
return hleLogError(Log::sceCtrl, SCE_KERNEL_ERROR_INVALID_MODE);
|
|
|
|
retVal = analogEnabled == true ? CTRL_MODE_ANALOG : CTRL_MODE_DIGITAL;
|
|
analogEnabled = mode == CTRL_MODE_ANALOG ? true : false;
|
|
return hleLogDebug(Log::sceCtrl, retVal);
|
|
}
|
|
|
|
static int sceCtrlGetSamplingMode(u32 modePtr)
|
|
{
|
|
u32 retVal = analogEnabled == true ? CTRL_MODE_ANALOG : CTRL_MODE_DIGITAL;
|
|
|
|
if (Memory::IsValidAddress(modePtr))
|
|
Memory::WriteUnchecked_U32(retVal, modePtr);
|
|
|
|
return hleLogDebug(Log::sceCtrl, 0);
|
|
}
|
|
|
|
// Hides buttons from user mode readers, for kernel code that takes them over (Daedalus, for the
|
|
// system buttons, which games don't get here anyway).
|
|
static int sceCtrlSetButtonIntercept(u32 buttons, int intercept) {
|
|
return hleLogDebug(Log::sceCtrl, 0);
|
|
}
|
|
|
|
static int sceCtrlSetIdleCancelThreshold(int idleReset, int idleBack)
|
|
{
|
|
if (idleReset < -1 || idleBack < -1 || idleReset > 128 || idleBack > 128)
|
|
return hleLogError(Log::sceCtrl, SCE_KERNEL_ERROR_INVALID_VALUE);
|
|
|
|
ctrlIdleReset = idleReset;
|
|
ctrlIdleBack = idleBack;
|
|
return hleLogDebug(Log::sceCtrl, 0);
|
|
}
|
|
|
|
static int sceCtrlGetIdleCancelThreshold(u32 idleResetPtr, u32 idleBackPtr)
|
|
{
|
|
if (idleResetPtr && !Memory::IsValid4AlignedAddress(idleResetPtr))
|
|
return hleLogError(Log::sceCtrl, SCE_KERNEL_ERROR_PRIV_REQUIRED);
|
|
if (idleBackPtr && !Memory::IsValid4AlignedAddress(idleBackPtr))
|
|
return hleLogError(Log::sceCtrl, SCE_KERNEL_ERROR_PRIV_REQUIRED);
|
|
|
|
if (idleResetPtr)
|
|
Memory::WriteUnchecked_U32(ctrlIdleReset, idleResetPtr);
|
|
if (idleBackPtr)
|
|
Memory::WriteUnchecked_U32(ctrlIdleBack, idleBackPtr);
|
|
|
|
return hleLogDebug(Log::sceCtrl, 0);
|
|
}
|
|
|
|
int sceCtrlReadBufferPositive(u32 ctrlDataPtr, u32 nBufs)
|
|
{
|
|
int done = __CtrlReadBuffer(ctrlDataPtr, nBufs, false, false);
|
|
hleEatCycles(330);
|
|
if (done != 0) {
|
|
return hleLogDebug(Log::sceCtrl, done);
|
|
} else {
|
|
waitingThreads.push_back(__KernelGetCurThread());
|
|
__KernelWaitCurThread(WAITTYPE_CTRL, CTRL_WAIT_POSITIVE, ctrlDataPtr, 0, false, "ctrl buffer waited");
|
|
return hleLogDebug(Log::sceCtrl, done, "waiting");
|
|
}
|
|
}
|
|
|
|
static int sceCtrlReadBufferNegative(u32 ctrlDataPtr, u32 nBufs)
|
|
{
|
|
int done = __CtrlReadBuffer(ctrlDataPtr, nBufs, true, false);
|
|
hleEatCycles(330);
|
|
if (done != 0) {
|
|
return hleLogDebug(Log::sceCtrl, done);
|
|
}
|
|
else
|
|
{
|
|
waitingThreads.push_back(__KernelGetCurThread());
|
|
__KernelWaitCurThread(WAITTYPE_CTRL, CTRL_WAIT_NEGATIVE, ctrlDataPtr, 0, false, "ctrl buffer waited");
|
|
return hleLogDebug(Log::sceCtrl, done, "waiting");
|
|
}
|
|
}
|
|
|
|
static int sceCtrlPeekBufferPositive(u32 ctrlDataPtr, u32 nBufs)
|
|
{
|
|
int done = __CtrlReadBuffer(ctrlDataPtr, nBufs, false, true);
|
|
// Some homebrew call this in a tight loop - so VERBOSE it is.
|
|
hleEatCycles(330);
|
|
return hleLogVerbose(Log::sceCtrl, done);
|
|
}
|
|
|
|
static int sceCtrlPeekBufferNegative(u32 ctrlDataPtr, u32 nBufs)
|
|
{
|
|
int done = __CtrlReadBuffer(ctrlDataPtr, nBufs, true, true);
|
|
// Some homebrew call this in a tight loop - so VERBOSE it is.
|
|
hleEatCycles(330);
|
|
return hleLogVerbose(Log::sceCtrl, done);
|
|
}
|
|
|
|
static void __CtrlWriteUserLatch(CtrlLatch *userLatch, int bufs) {
|
|
*userLatch = latch;
|
|
userLatch->btnBreak &= CTRL_MASK_USER;
|
|
userLatch->btnMake &= CTRL_MASK_USER;
|
|
userLatch->btnPress &= CTRL_MASK_USER;
|
|
if (bufs > 0) {
|
|
userLatch->btnRelease |= ~CTRL_MASK_USER;
|
|
}
|
|
}
|
|
|
|
static u32 sceCtrlPeekLatch(u32 latchDataPtr) {
|
|
auto userLatch = PSPPointer<CtrlLatch>::Create(latchDataPtr);
|
|
if (userLatch.IsValid()) {
|
|
__CtrlWriteUserLatch(userLatch, ctrlLatchBufs);
|
|
}
|
|
return hleLogDebug(Log::sceCtrl, ctrlLatchBufs);
|
|
}
|
|
|
|
static u32 sceCtrlReadLatch(u32 latchDataPtr) {
|
|
auto userLatch = PSPPointer<CtrlLatch>::Create(latchDataPtr);
|
|
if (userLatch.IsValid()) {
|
|
__CtrlWriteUserLatch(userLatch, ctrlLatchBufs);
|
|
}
|
|
return hleLogDebug(Log::sceCtrl, __CtrlResetLatch());
|
|
}
|
|
|
|
static const HLEFunction sceCtrl[] =
|
|
{
|
|
{0X3E65A0EA, nullptr, "sceCtrlInit", '?', "" }, //(int unknown), init with 0
|
|
{0X1F4011E6, &WrapU_U<sceCtrlSetSamplingMode>, "sceCtrlSetSamplingMode", 'x', "x" },
|
|
{0X6A2774F3, &WrapU_U<sceCtrlSetSamplingCycle>, "sceCtrlSetSamplingCycle", 'x', "x" },
|
|
{0X02BAAD91, &WrapI_U<sceCtrlGetSamplingCycle>, "sceCtrlGetSamplingCycle", 'i', "x" },
|
|
{0XDA6B76A1, &WrapI_U<sceCtrlGetSamplingMode>, "sceCtrlGetSamplingMode", 'i', "x" },
|
|
{0X1F803938, &WrapI_UU<sceCtrlReadBufferPositive>, "sceCtrlReadBufferPositive", 'i', "xx"},
|
|
{0X3A622550, &WrapI_UU<sceCtrlPeekBufferPositive>, "sceCtrlPeekBufferPositive", 'i', "xx"},
|
|
{0XC152080A, &WrapI_UU<sceCtrlPeekBufferNegative>, "sceCtrlPeekBufferNegative", 'i', "xx"},
|
|
{0X60B81F86, &WrapI_UU<sceCtrlReadBufferNegative>, "sceCtrlReadBufferNegative", 'i', "xx"},
|
|
{0XB1D0E5CD, &WrapU_U<sceCtrlPeekLatch>, "sceCtrlPeekLatch", 'i', "x" },
|
|
{0X0B588501, &WrapU_U<sceCtrlReadLatch>, "sceCtrlReadLatch", 'i', "x" },
|
|
{0X348D99D4, nullptr, "sceCtrlSetSuspendingExtraSamples", '?', "" },
|
|
{0XAF5960F3, nullptr, "sceCtrlGetSuspendingExtraSamples", '?', "" },
|
|
{0XA68FD260, nullptr, "sceCtrlClearRapidFire", '?', "" },
|
|
{0X6841BE1A, nullptr, "sceCtrlSetRapidFire", '?', "" },
|
|
{0XA7144800, &WrapI_II<sceCtrlSetIdleCancelThreshold>, "sceCtrlSetIdleCancelThreshold", 'i', "ii"},
|
|
{0X687660FA, &WrapI_UU<sceCtrlGetIdleCancelThreshold>, "sceCtrlGetIdleCancelThreshold", 'i', "xx"},
|
|
// The "2" variants take a port first: (int port, data, int count).
|
|
{0X5A36B1C2, nullptr, "sceCtrlPeekBufferPositive2", '?', "" },
|
|
{0X239A6BA7, nullptr, "sceCtrlPeekBufferNegative2", '?', "" },
|
|
{0X1098030B, nullptr, "sceCtrlReadBufferPositive2", '?', "" },
|
|
{0X7C3675AB, nullptr, "sceCtrlReadBufferNegative2", '?', "" },
|
|
// sceCtrl_driver's NID for sceCtrlPeekBufferPositive in 3.71. Imported by NJEMU's SystemButtons.prx
|
|
// kernel plugin, which picks it when sceKernelDevkitVersion() is 3.71 or newer (#8925).
|
|
{0XC4AAD55F, &WrapI_UU<sceCtrlPeekBufferPositive>, "sceCtrlPeekBufferPositive", 'i', "xx"},
|
|
// sceCtrl_driver NID for sceCtrlReadBufferPositive (firmware 3.71+, per JPCSP).
|
|
// Imported by translation-patch loaders like CLANNAD's chfont.prx.
|
|
{0X454455AC, &WrapI_UU<sceCtrlReadBufferPositive>, "sceCtrlReadBufferPositive", 'i', "xx"},
|
|
// Driver only.
|
|
{0X7CA723DC, &WrapI_UI<sceCtrlSetButtonIntercept>, "sceCtrlSetButtonIntercept", 'i', "xi"},
|
|
// sceCtrl_driver NIDs in 3.71-3.73's ctrl.prx (at the same addresses as the user exports), which
|
|
// LightMP3's support.prx imports.
|
|
{0X28E71A16, &WrapU_U<sceCtrlSetSamplingMode>, "sceCtrlSetSamplingMode", 'x', "x" },
|
|
{0X6D74BF08, &WrapU_U<sceCtrlSetSamplingCycle>, "sceCtrlSetSamplingCycle", 'x', "x" },
|
|
};
|
|
|
|
void Register_sceCtrl()
|
|
{
|
|
RegisterHLEModule("sceCtrl", ARRAY_SIZE(sceCtrl), sceCtrl);
|
|
}
|
|
|
|
void Register_sceCtrl_driver()
|
|
{
|
|
RegisterHLEModule("sceCtrl_driver", ARRAY_SIZE(sceCtrl), sceCtrl);
|
|
}
|
|
|
|
u16 sceCtrlGetRightVibration() {
|
|
return hleNoLog(rightVibration);
|
|
}
|
|
|
|
u16 sceCtrlGetLeftVibration() {
|
|
return hleNoLog(leftVibration);
|
|
}
|
|
|
|
namespace SceCtrl {
|
|
void SetRightVibration(u16 rVibration) {
|
|
rightVibration = rVibration;
|
|
}
|
|
void SetLeftVibration(u16 lVibration) {
|
|
leftVibration = lVibration;
|
|
}
|
|
void SetVibrationRightDropout(u8 vibrationRDropout) {
|
|
vibrationRightDropout = vibrationRDropout;
|
|
}
|
|
void SetVibrationLeftDropout(u8 vibrationLDropout) {
|
|
vibrationLeftDropout = vibrationLDropout;
|
|
}
|
|
}
|