/* This file is part of jpcsp. Jpcsp 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, either version 3 of the License, or (at your option) any later version. Jpcsp 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 received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.HLE.modules; import jpcsp.HLE.CanBeNull; import jpcsp.HLE.CheckArgument; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.SceKernelErrorException; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import java.util.LinkedList; import java.util.List; import jpcsp.Controller; import jpcsp.Memory; import jpcsp.State; import jpcsp.HLE.Modules; import jpcsp.HLE.kernel.Managers; import jpcsp.HLE.kernel.managers.SystemTimeManager; import jpcsp.HLE.kernel.types.IAction; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import org.apache.log4j.Logger; public class sceCtrl extends HLEModule { public static Logger log = Modules.getLogger("sceCtrl"); private int cycle; private int mode; private int uiMake; private int uiBreak; private int uiPress; private int uiRelease; private int TimeStamp; // microseconds private byte Lx; private byte Ly; private byte Rx; private byte Ry; private int Buttons; // IdleCancelThreshold private int idlereset; private int idleback; public final static int PSP_CTRL_SELECT = 0x000001; public final static int PSP_CTRL_START = 0x000008; public final static int PSP_CTRL_UP = 0x000010; public final static int PSP_CTRL_RIGHT = 0x000020; public final static int PSP_CTRL_DOWN = 0x000040; public final static int PSP_CTRL_LEFT = 0x000080; public final static int PSP_CTRL_LTRIGGER = 0x000100; public final static int PSP_CTRL_RTRIGGER = 0x000200; public final static int PSP_CTRL_TRIANGLE = 0x001000; public final static int PSP_CTRL_CIRCLE = 0x002000; public final static int PSP_CTRL_CROSS = 0x004000; public final static int PSP_CTRL_SQUARE = 0x008000; public final static int PSP_CTRL_HOME = 0x010000; public final static int PSP_CTRL_HOLD = 0x020000; public final static int PSP_CTRL_NOTE = 0x800000; public final static int PSP_CTRL_SCREEN = 0x400000; public final static int PSP_CTRL_VOLUP = 0x100000; public final static int PSP_CTRL_VOLDOWN = 0x200000; public final static int PSP_CTRL_WLAN_UP = 0x040000; public final static int PSP_CTRL_REMOTE = 0x080000; public final static int PSP_CTRL_DISC = 0x1000000; public final static int PSP_CTRL_MS = 0x2000000; // PspCtrlMode public final static int PSP_CTRL_MODE_DIGITAL = 0; public final static int PSP_CTRL_MODE_ANALOG = 1; protected IAction sampleAction = null; protected Sample samples[]; protected int currentSamplingIndex; protected int currentReadingIndex; protected int latchSamplingCount; // PSP remembers the last 64 samples. protected final static int SAMPLE_BUFFER_SIZE = 64; protected List<ThreadWaitingForSampling> threadsWaitingForSampling; public boolean isModeDigital() { if (mode == PSP_CTRL_MODE_DIGITAL) { return true; } return false; } private static int getTimestamp() { return ((int) SystemTimeManager.getSystemTime()) & 0x7FFFFFFF; } private void setButtons(byte Lx, byte Ly, byte Rx, byte Ry, int Buttons, boolean hasRightAnalogController) { int oldButtons = this.Buttons; this.TimeStamp = getTimestamp(); this.Lx = Lx; this.Ly = Ly; if (hasRightAnalogController) { this.Rx = Rx; this.Ry = Ry; } else { this.Rx = 0; this.Ry = 0; } this.Buttons = Buttons; if (isModeDigital()) { // PSP_CTRL_MODE_DIGITAL // moving the analog stick has no effect and always returns 128,128 this.Lx = Controller.analogCenter; this.Ly = Controller.analogCenter; if (hasRightAnalogController) { this.Rx = Controller.analogCenter; this.Ry = Controller.analogCenter; } } int changed = oldButtons ^ Buttons; int unpressed = ~Buttons; uiMake |= Buttons & changed; uiBreak |= unpressed & changed; uiPress |= Buttons; uiRelease |= unpressed; } @Override public void start() { uiMake = 0; uiBreak = 0; uiPress = 0; uiRelease = ~uiPress; Lx = Controller.analogCenter; Ly = Controller.analogCenter; Rx = Controller.analogCenter; Ry = Controller.analogCenter; Buttons = 0; idlereset = -1; idleback = -1; mode = PSP_CTRL_MODE_DIGITAL; // check initial mode cycle = 0; // Allocate 1 more entry because we always leave 1 entry free // for the internal management // (to differentiate a full buffer from an empty one). samples = new Sample[SAMPLE_BUFFER_SIZE + 1]; for (int i = 0; i < samples.length; i++) { samples[i] = new Sample(); samples[i].setValues(0, Lx, Ly, Rx, Ry, Buttons); } currentSamplingIndex = 0; currentReadingIndex = 0; latchSamplingCount = 0; threadsWaitingForSampling = new LinkedList<ThreadWaitingForSampling>(); if (sampleAction == null) { sampleAction = new SamplingAction(); Managers.intr.addVBlankAction(sampleAction); } super.start(); } protected class SamplingAction implements IAction { @Override public void execute() { hleCtrlExecuteSampling(); } } protected static class ThreadWaitingForSampling { SceKernelThreadInfo thread; int readAddr; int readCount; boolean readPositive; public ThreadWaitingForSampling(SceKernelThreadInfo thread, int readAddr, int readCount, boolean readPositive) { this.thread = thread; this.readAddr = readAddr; this.readCount = readCount; this.readPositive = readPositive; } } protected static class Sample { private int TimeStamp; // microseconds private byte Lx; private byte Ly; private byte Rx; private byte Ry; private int Buttons; public void setValues(int TimeStamp, byte Lx, byte Ly, byte Rx, byte Ry, int Buttons) { this.TimeStamp = TimeStamp; this.Lx = Lx; this.Ly = Ly; this.Rx = Rx; this.Ry = Ry; this.Buttons = Buttons; } public int write(Memory mem, int addr, boolean positive) { mem.write32(addr, TimeStamp); mem.write32(addr + 4, positive ? Buttons : ~Buttons); mem.write8(addr + 8, Lx); mem.write8(addr + 9, Ly); // These 2 values are always set to 0 on a PSP, // but are used for a second analog stick on the PS3 PSP emulator (for HD remaster) mem.write8(addr + 10, Rx); mem.write8(addr + 11, Ry); // Always set to 0 mem.write8(addr + 12, (byte) 0); mem.write8(addr + 13, (byte) 0); mem.write8(addr + 14, (byte) 0); mem.write8(addr + 15, (byte) 0); return addr + 16; } @Override public String toString() { return String.format("TimeStamp=%d,Lx=%d,Ly=%d,Rx=%d,Ry=%d,Buttons=%07X", TimeStamp, Lx, Ly, Rx, Ry, Buttons); } } /** * Increment (or decrement) the given Sample Index * (currentSamplingIndex or currentReadingIndex). * * @param index the current index value * 0 <= index < samples.length * @param count the increment (or decrement) value. * -samples.length <= count <= samples.length * @return the incremented index value * 0 <= returned value < samples.length */ protected int incrementSampleIndex(int index, int count) { index += count; if (index >= samples.length) { index -= samples.length; } else if (index < 0) { index += samples.length; } return index; } /** * Increment the given Sample Index by 1 * (currentSamplingIndex or currentReadingIndex). * * @param index the current index value * 0 <= index < samples.length * @return the index value incremented by 1 * 0 <= returned value < samples.length */ protected int incrementSampleIndex(int index) { return incrementSampleIndex(index, 1); } protected int getNumberOfAvailableSamples() { int n = currentSamplingIndex - currentReadingIndex; if (n < 0) { n += samples.length; } return n; } public int checkThreshold(int threshold) { if (threshold < -1 || threshold > 128) { throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_VALUE); } return threshold; } public int checkMode(int mode) { if (mode != PSP_CTRL_MODE_DIGITAL && mode != PSP_CTRL_MODE_ANALOG) { throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_MODE); } return mode; } public int checkCycle(int cycle) { if (cycle < 0 || (cycle > 0 && cycle < 5555) || cycle > 20000) { throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_VALUE); } return cycle; } public void hleCtrlExecuteSampling() { if (log.isDebugEnabled()) { log.debug("hleCtrlExecuteSampling"); } Controller controller = State.controller; controller.hleControllerPoll(); setButtons(controller.getLx(), controller.getLy(), controller.getRx(), controller.getRy(), controller.getButtons(), controller.hasRightAnalogController()); latchSamplingCount++; Sample currentSampling = samples[currentSamplingIndex]; currentSampling.setValues(TimeStamp, Lx, Ly, Rx, Ry, Buttons); currentSamplingIndex = incrementSampleIndex(currentSamplingIndex); if (currentSamplingIndex == currentReadingIndex) { currentReadingIndex = incrementSampleIndex(currentReadingIndex); } while (!threadsWaitingForSampling.isEmpty()) { ThreadWaitingForSampling wait = threadsWaitingForSampling.remove(0); if (wait.thread.isWaitingForType(SceKernelThreadInfo.JPCSP_WAIT_CTRL)) { if (log.isDebugEnabled()) { log.debug("hleExecuteSampling waiting up thread " + wait.thread); } wait.thread.cpuContext._v0 = hleCtrlReadBufferImmediately(wait.readAddr, wait.readCount, wait.readPositive, false); Modules.ThreadManForUserModule.hleUnblockThread(wait.thread.uid); break; } if (log.isDebugEnabled()) { log.debug("hleExecuteSampling thread " + wait.thread + " was no longer blocked"); } } } protected int hleCtrlReadBufferImmediately(int addr, int count, boolean positive, boolean peek) { if (count < 0 || count > SAMPLE_BUFFER_SIZE) { return SceKernelErrors.ERROR_INVALID_SIZE; } Memory mem = Memory.getInstance(); // If more samples are available than requested, read the more recent ones int available = getNumberOfAvailableSamples(); int readIndex; if (available > count || peek) { readIndex = incrementSampleIndex(currentSamplingIndex, -count); } else { count = available; readIndex = currentReadingIndex; } if (!peek) { // Forget the remaining samples if they are not read now currentReadingIndex = currentSamplingIndex; } for (int ctrlCount = 0; ctrlCount < count; ctrlCount++) { if (log.isTraceEnabled()) { log.trace(String.format("now=%d, samples[%d]=%s", getTimestamp(), readIndex, samples[readIndex])); } addr = samples[readIndex].write(mem, addr, positive); readIndex = incrementSampleIndex(readIndex); } if (log.isDebugEnabled()) { log.debug(String.format("hleCtrlReadBufferImmediately(positive=%b, peek=%b) returning %d", positive, peek, count)); } return count; } protected int hleCtrlReadBuffer(int addr, int count, boolean positive) { if (count < 0 || count > SAMPLE_BUFFER_SIZE) { return SceKernelErrors.ERROR_INVALID_SIZE; } // Some data available in sample buffer? if (getNumberOfAvailableSamples() > 0) { // Yes, read immediately return hleCtrlReadBufferImmediately(addr, count, positive, false); } // No, wait for next sampling ThreadManForUser threadMan = Modules.ThreadManForUserModule; SceKernelThreadInfo currentThread = threadMan.getCurrentThread(); ThreadWaitingForSampling threadWaitingForSampling = new ThreadWaitingForSampling(currentThread, addr, count, positive); threadsWaitingForSampling.add(threadWaitingForSampling); threadMan.hleBlockCurrentThread(SceKernelThreadInfo.JPCSP_WAIT_CTRL); if (log.isDebugEnabled()) { log.debug("hleCtrlReadBuffer waiting for sample"); } return 0; } @HLEFunction(nid = 0x6A2774F3, version = 150, checkInsideInterrupt = true) public int sceCtrlSetSamplingCycle(@CheckArgument("checkCycle") int newCycle) { int oldCycle = cycle; this.cycle = newCycle; if (log.isDebugEnabled()) { log.debug(String.format("sceCtrlSetSamplingCycle cycle=%d returning %d", newCycle, oldCycle)); } return oldCycle; } @HLEFunction(nid = 0x02BAAD91, version = 150, checkInsideInterrupt = true) public int sceCtrlGetSamplingCycle(TPointer32 cycleAddr) { cycleAddr.setValue(cycle); return 0; } @HLEFunction(nid = 0x1F4011E6, version = 150, checkInsideInterrupt = true) public int sceCtrlSetSamplingMode(@CheckArgument("checkMode") int newMode) { int oldMode = mode; this.mode = newMode; if (log.isDebugEnabled()) { log.debug(String.format("sceCtrlSetSamplingMode mode=%d returning %d", newMode, oldMode)); } return oldMode; } @HLEFunction(nid = 0xDA6B76A1, version = 150) public int sceCtrlGetSamplingMode(TPointer32 modeAddr) { modeAddr.setValue(mode); return 0; } @HLEFunction(nid = 0x3A622550, version = 150) public int sceCtrlPeekBufferPositive(TPointer dataAddr, int numBuf) { return hleCtrlReadBufferImmediately(dataAddr.getAddress(), numBuf, true, true); } @HLEFunction(nid = 0xC152080A, version = 150) public int sceCtrlPeekBufferNegative(TPointer dataAddr, int numBuf) { return hleCtrlReadBufferImmediately(dataAddr.getAddress(), numBuf, false, true); } @HLEFunction(nid = 0x1F803938, version = 150, checkInsideInterrupt = true) public int sceCtrlReadBufferPositive(TPointer dataAddr, int numBuf) { return hleCtrlReadBuffer(dataAddr.getAddress(), numBuf, true); } @HLEFunction(nid = 0x60B81F86, version = 150, checkInsideInterrupt = true) public int sceCtrlReadBufferNegative(TPointer dataAddr, int numBuf) { return hleCtrlReadBuffer(dataAddr.getAddress(), numBuf, false); } @HLEFunction(nid = 0xB1D0E5CD, version = 150) public int sceCtrlPeekLatch(TPointer32 latchAddr) { latchAddr.setValue(0, uiMake); latchAddr.setValue(4, uiBreak); latchAddr.setValue(8, uiPress); latchAddr.setValue(12, uiRelease); return latchSamplingCount; } @HLEFunction(nid = 0x0B588501, version = 150) public int sceCtrlReadLatch(TPointer32 latchAddr) { latchAddr.setValue(0, uiMake); latchAddr.setValue(4, uiBreak); latchAddr.setValue(8, uiPress); latchAddr.setValue(12, uiRelease); if (log.isDebugEnabled()) { log.debug(String.format("sceCtrlReadLatch uiMake=0x%06X, uiBreak=0x%06X, uiPress=0x%06X, uiRelease=0x%06X, returning %d", uiMake, uiBreak, uiPress, uiRelease, latchSamplingCount)); } uiMake = 0; uiBreak = 0; uiPress = 0; uiRelease = 0; int prevLatchSamplingCount = latchSamplingCount; latchSamplingCount = 0; return prevLatchSamplingCount; } @HLEFunction(nid = 0xA7144800, version = 150) public int sceCtrlSetIdleCancelThreshold(@CheckArgument("checkThreshold") int idlereset, @CheckArgument("checkThreshold") int idleback) { this.idlereset = idlereset; this.idleback = idleback; return 0; } @HLEFunction(nid = 0x687660FA, version = 150) public int sceCtrlGetIdleCancelThreshold(@CanBeNull TPointer32 idleresetAddr, @CanBeNull TPointer32 idlebackAddr) { idleresetAddr.setValue(idlereset); idlebackAddr.setValue(idleback); return 0; } @HLEUnimplemented @HLEFunction(nid = 0x348D99D4, version = 150) public int sceCtrl_348D99D4() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xAF5960F3, version = 150) public int sceCtrl_AF5960F3() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xA68FD260, version = 150) public int sceCtrlClearRapidFire() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x6841BE1A, version = 150) public int sceCtrlSetRapidFire() { return 0; } @HLEFunction(nid = 0xC4AAD55F, version = 371) public int sceCtrlPeekBufferPositive371(TPointer dataAddr, int numBuf) { return hleCtrlReadBufferImmediately(dataAddr.getAddress(), numBuf, true, true); } @HLEFunction(nid = 0x454455AC, version = 371) public int sceCtrlReadBufferPositive371(TPointer dataAddr, int numBuf) { return hleCtrlReadBuffer(dataAddr.getAddress(), numBuf, true); } @HLEFunction(nid = 0xD073ECA4, version = 620) public int sceCtrlReadBufferPositive_620(TPointer dataAddr, int numBuf) { return hleCtrlReadBuffer(dataAddr.getAddress(), numBuf, true); } @HLEFunction(nid = 0x9F3038AC, version = 639) public int sceCtrlReadBufferPositive_639(TPointer dataAddr, int numBuf) { return hleCtrlReadBuffer(dataAddr.getAddress(), numBuf, true); } @HLEFunction(nid = 0xBE30CED0, version = 660) public int sceCtrlReadBufferPositive_660(TPointer dataAddr, int numBuf) { return hleCtrlReadBuffer(dataAddr.getAddress(), numBuf, true); } @HLEFunction(nid = 0xF6E94EA3, version = 150, checkInsideInterrupt = true) public int sceCtrlSetSamplingMode_660(@CheckArgument("checkMode") int newMode) { return sceCtrlSetSamplingMode(newMode); } @HLEUnimplemented @HLEFunction(nid = 0x6C86AF22, version = 660) public int sceCtrl_driver_6C86AF22(int unknown) { return 0; } @HLEFunction(nid = 0x2BA616AF, version = 150) public int sceCtrlPeekBufferPositive_660(TPointer dataAddr, int numBuf) { return sceCtrlPeekBufferPositive371(dataAddr, numBuf); } @HLEFunction(nid = 0xF8EC18BD, version = 150) public int sceCtrlGetSamplingMode_660(TPointer32 modeAddr) { return sceCtrlGetSamplingMode(modeAddr); } }