Is there a line that must be entered so this can happen?
Code: Select all
#include <pspkernel.h>
#include <pspdebug.h>
#include <pspctrl.h>
#include <pspdisplay.h>
#include <string.h>
#include <pspiofilemgr.h>
#include <stdio.h>
#include "pikey.h"
#include "apihook.h"
#include "blit.h"
#include "mapkey.h"
PSP_MODULE_INFO("piKey", 0x1000, 1, 1);
/*****************************************************************************/
/* Write debug info to SIO? */
/* */
/* 0 : off */
/* 1 : major events */
/* 2 : debug events */
/* 3 : extra debug */
/*****************************************************************************/
#define DEBUG_OUTPUT 0
/*****************************************************************************/
/* Internal structs */
/*****************************************************************************/
typedef struct input_info {
struct input_info *prev;
struct input_info *next;
SceUID moduleID;
char moduleName[64];
} INPUT_INFO;
typedef struct output_info {
struct output_info *prev;
struct output_info *next;
SceUID moduleID;
char moduleName[64];
} OUTPUT_INFO;
void DumpMem(void *addr,unsigned size,int num);
typedef int (*DRIVERINFOFUNC)(DRIVERINFO *);
/*****************************************************************************/
/* Internal vars */
/*****************************************************************************/
int terminate = 0;
int numInputs;
int numOutputs;
SceUID pikeythread;
INPUT_INFO *inputs;
OUTPUT_INFO *outputs;
#define QUEUE_SIZE 256
#define MAX_PATH_SIZE 256
/*****************************************************************************/
/* Cyclic buffer of max-size QUEUE_SIZE for input chars. */
/*****************************************************************************/
wchar inBuffer[QUEUE_SIZE];
int queueInputPos;
int queueOutputPos;
int queueSize;
SceUID queueSema;
SceUID gExclusiveThread;
/*****************************************************************************/
/* Keyboard info display callbacks */
/*****************************************************************************/
#define MAX_DISPLAY_CALLBACKS 10
SceUID gDisplayCallbacks[MAX_DISPLAY_CALLBACKS];
int gNumDisplayCallbacks = 0;
SceUID callbackSema;
/*****************************************************************************/
/* Status indication */
/*****************************************************************************/
// num of vblanks to display status text for. (3 secs)
#define STATUS_DISPLAY_TIME 180
#define SCREEN_TEXT_WIDTH 80
static char centredText[SCREEN_TEXT_WIDTH + 1];
int gStatusCycles = 0;
int gStatusText = 0;
int statusMessages = 1;
int sioDebug = 0;
int disabledIn150 = 0;
/*****************************************************************************/
/* VSH mode? */
/*****************************************************************************/
int gvshmode = 0;
/*****************************************************************************/
/* Keypress mode vars */
/*****************************************************************************/
int gOldKeyMode = PIKEY_KEYMODE_KEYPRESS;
int gKeyMode = PIKEY_KEYMODE_KEYPRESS;
char gPressMap[KEY_MAX];
char gHoldMap[KEY_MAX];
int numPressed = 0;
int altHeld = 0;
int ctrlHeld = 0;
int shiftHeld = 0;
/*****************************************************************************/
/* Key repeat metrics - measured in vblanks */
/* */
/* REPEAT_TIME: time to first repeat */
/* REPEAT_PERIOD: time between repeats once started. */
/*****************************************************************************/
#define REPEAT_TIME 30
#define REPEAT_PERIOD 10
// measured in microseconds - time to simulate a keyhold event when sent a
// character in keypress mode.
#define FAKE_KEYPRESS_HOLD 80000
// length of a vblank frame (60hz) in microseconds.
#define FRAME_LENGTH_USECS 16667
/*****************************************************************************/
/* ESCAPE key timeout hack */
/*****************************************************************************/
int gEscTimer = -1;
// number of vblanks to wait for further chars in a vt100 escape sequence
#define ESC_TIMEOUT 6
/*****************************************************************************/
/* Config file control vars. */
/*****************************************************************************/
#define MAX_CONFIGS 10
char * configData[MAX_CONFIGS];
char * configOffset[MAX_CONFIGS];
int numConfigs = 0;
char cfgtoken[64];
char cfgvalue[128];
/*****************************************************************************/
/* Disabled plugins list */
/*****************************************************************************/
#define MAX_DISABLED_PLUGINS 10
char disabledPlugins[MAX_DISABLED_PLUGINS][128];
int numDisabledPlugins = 0;
/*****************************************************************************/
/* */
/* Code starts here */
/* */
/*****************************************************************************/
void sioPrint(const char *str)
{
#if DEBUG_OUTPUT!=0
if (sioDebug)
{
while (*str)
{
pspDebugSioPutchar(*str);
str++;
}
}
#endif
}
void sioPrintHex(char ch)
{
#if DEBUG_OUTPUT!=0
if (sioDebug)
{
static char * hexmap="0123456789ABCDEF";
pspDebugSioPutchar( hexmap[ (ch >> 4) & 0x0F ] );
pspDebugSioPutchar( hexmap[ (ch & 0x0F) ] );
}
#endif
}
void sioPrintWord(u32 data)
{
#if DEBUG_OUTPUT!=0
if (sioDebug)
{
sioPrintHex( (data >> 24) & 0x000000FFL );
sioPrintHex( (data >> 16) & 0x000000FFL );
sioPrintHex( (data >> 8) & 0x000000FFL );
sioPrintHex( data & 0x000000FFL );
}
#endif
}
int waitForModule(const char * modname)
{
int cycles = 0;
#if DEBUG_OUTPUT>=2
sioPrint("Wait for module ");
sioPrint(modname);
sioPrint(": ");
#endif
while (sceKernelFindModuleByName(modname) == NULL)
{
#if DEBUG_OUTPUT>=2
pspDebugSioPutchar('.');
#endif
if (cycles++ > 10)
{
#if DEBUG_OUTPUT>=2
sioPrint("TIMEOUT.\r\n");
#endif
// give up waiting after 5 secs
return 0;
}
sceKernelDelayThread(500000);
}
#if DEBUG_OUTPUT>=2
sioPrint("OK.\r\n");
#endif
return 1;
}
static struct SceLibraryEntryTable *_libsFindLibrary(SceUID uid, const char *library)
{
struct SceLibraryEntryTable *entry;
SceModule *pMod;
void *entTab;
int entLen;
pMod = sceKernelFindModuleByUID(uid);
if(pMod != NULL)
{
int i = 0;
entTab = pMod->ent_top;
entLen = pMod->ent_size;
while(i < entLen)
{
entry = (struct SceLibraryEntryTable *) (entTab + i);
if((entry->libname) && (strcmp(entry->libname, library) == 0))
{
return entry;
}
else if(!entry->libname && !library)
{
return entry;
}
i += (entry->len * 4);
}
}
return NULL;
}
static void* libsFindExportAddrByNid(SceUID uid, const char *library, u32 nid)
{
u32 *addr = NULL;
SceLibraryEntryTable *entry = _libsFindLibrary(uid, library);
if(entry)
{
int count;
int total;
unsigned int *vars;
total = entry->stubcount + entry->vstubcount;
vars = entry->entrytable;
if(entry->stubcount > 0)
{
for(count = 0; count < entry->stubcount; count++)
{
if(vars[count] == nid)
{
return (void*)(*((void**)(&vars[count+total])));
}
}
}
}
return addr;
}
SceUID load_module(const char *path, int flags, int type) {
SceKernelLMOption option;
SceUID mpid;
/* If the type is 0, then load the module in the kernel partition, otherwise load it
in the user partition. */
if (type == 0) mpid = 1;
else mpid = 2;
memset(&option, 0, sizeof(option));
option.size = sizeof(option);
option.mpidtext = mpid;
option.mpiddata = mpid;
option.position = 0;
option.access = 1;
return sceKernelLoadModule(path, flags, type > 0 ? &option : NULL);
}
void centre_string(const char *text)
{
centredText[SCREEN_TEXT_WIDTH] = '\0';
memset(centredText, ' ', SCREEN_TEXT_WIDTH);
int length = strlen(text);
int offset;
// Cope with over-length string
if (length > SCREEN_TEXT_WIDTH)
{
length = SCREEN_TEXT_WIDTH;
offset = 0;
}
else
{
// Note : calculation relies on max width being even to avoid overflow
offset = (SCREEN_TEXT_WIDTH - length) / 2;
}
int ii;
for (ii = 0; ii < length; ii++)
{
centredText[offset++] = text[ii];
}
}
void displayStatusText(const char *text)
{
if (statusMessages)
{
centre_string(text);
gStatusCycles = 0;
gStatusText = 1;
#if DEBUG_OUTPUT>=2
sioPrint("Show status text: ");
sioPrint(text);
sioPrint("\r\n");
#endif
}
}
void InitVars(void)
{
// empty queues, driver counts
numInputs = 0;
numOutputs = 0;
inputs = NULL;
outputs = NULL;
queueInputPos = 0;
queueOutputPos = 0;
queueSize = 0;
ctrlHeld = 0;
altHeld = 0;
shiftHeld = 0;
gExclusiveThread = 0;
queueSema = sceKernelCreateSema("pikey_q", 0, 1, 1, NULL);
callbackSema = sceKernelCreateSema("pikey_calls", 0, 1, 1, NULL);
gNumDisplayCallbacks = 0;
gStatusText = 0;
gStatusCycles = 0;
// default to keypress mode, no keys currently pressed
gKeyMode = PIKEY_KEYMODE_KEYPRESS;
memset(gPressMap, 0, sizeof(gPressMap));
numPressed = 0;
numConfigs = 0;
int ii;
for (ii = 0; ii < MAX_CONFIGS; ii++)
{
configData[ii] = NULL;
configOffset[ii] = NULL;
}
initApiHooks();
}
/*****************************************************************************/
/* Simple malloc clone. The block ID is stored just before the returned */
/* pointer. */
/*****************************************************************************/
void *myMalloc(size_t size)
{
SceUID memID = sceKernelAllocPartitionMemory(1, "", 0, size + 4, NULL);
if (memID < 0)
{
#if DEBUG_OUTPUT>=2
sioPrint("sKAPM failed: ");
sioPrintWord(memID);
sioPrint("\r\n");
#endif
return NULL;
}
unsigned int * lptr = sceKernelGetBlockHeadAddr(memID);
*lptr = memID;
#if DEBUG_OUTPUT>=3
sioPrint("alloced blockID: ");
sioPrintWord(memID);
sioPrint(" size ");
sioPrintWord(size);
sioPrint("\r\n");
#endif
return (lptr+1);
}
/*****************************************************************************/
/* Corresponding Free func for myMalloc */
/*****************************************************************************/
void myFree(void *blockaddr)
{
SceUID blockid = *(((SceUID*)blockaddr) - 1);
#if DEBUG_OUTPUT>=3
sioPrint("Free blockID: ");
sioPrintWord(blockid);
sioPrint("\r\n");
#endif
int rc = sceKernelFreePartitionMemory(blockid);
if (rc < 0)
{
sioPrint("Error freeing block at ");
sioPrintWord((int)blockaddr);
sioPrint(" err=");
sioPrintWord(rc);
sioPrint("\r\n");
}
}
SceIoDirent dirent;
void LoadDrivers(char *path, int isInput)
{
int status;
#if DEBUG_OUTPUT>=2
sioPrint("Load drivers from ");
sioPrint(path);
sioPrint("\r\n");
#endif
// iterate through "path/", loading all the prxs
memset(&dirent, 0, sizeof(SceIoDirent));
SceUID lid = sceIoDopen(path);
if (lid >= 0)
{
int lrc = 0;
do {
lrc = sceIoDread(lid, &dirent);
if (lrc > 0) {
if (strstr(dirent.d_name, ".prx") || strstr(dirent.d_name, ".PRX")) {
char namebuff[MAX_PATH_SIZE];
strncpy(namebuff, path, MAX_PATH_SIZE);
strcat(namebuff, "/");
strcat(namebuff, dirent.d_name);
#if DEBUG_OUTPUT>=2
sioPrint(namebuff);
sioPrint(":\r\n");
#endif
int disabled = 0;
int ii;
for (ii = 0; ii < numDisabledPlugins; ii++)
{
#if DEBUG_OUTPUT>=3
sioPrint("Check : '");
sioPrint(disabledPlugins[ii]);
sioPrint("'\r\n");
#endif
if (strcmp(dirent.d_name, disabledPlugins[ii]) == 0)
{
#if DEBUG_OUTPUT>=1
sioPrint("Skip ");
sioPrint(dirent.d_name);
sioPrint(" - disabled module\r\n");
#endif
disabled = 1;
}
}
if (disabled)
{
continue;
}
SceUID modid = load_module(namebuff, 0, 0);
if (modid < 0)
{
#if DEBUG_OUTPUT>=1
sioPrint("Load module failed: 0x");
sioPrintWord(modid);
sioPrint("\r\n");
#endif
}
int done = 0;
while (!done)
{
int lrc2 = sceKernelStartModule(modid, 0, NULL, &status, NULL);
if (lrc2 < 0)
{
#if DEBUG_OUTPUT>=1
sioPrint("Start module failed: 0x");
sioPrintWord(lrc2);
#endif
if (lrc2 == 0x8002013CL)
{
#if DEBUG_OUTPUT>=1
sioPrint("...sleep and retry...\r\n");
#endif
sceKernelDelayThread(500000);
}
else
{
#if DEBUG_OUTPUT>=1
sioPrint("...Give up.\r\n");
#endif
done = 1;
}
}
else
{
#if DEBUG_OUTPUT>=2
sioPrint("Started.\r\n");
#endif
done = 1;
// Now we started the module successfully, call its driverinfo func.
DRIVERINFOFUNC info_func = NULL;
DRIVERINFO ldriverinfo;
memset(&ldriverinfo, 0, sizeof(ldriverinfo));
SceKernelModuleInfo lmodinfo;
memset(&lmodinfo, 0, sizeof(lmodinfo));
lmodinfo.size = sizeof(lmodinfo);
int lmodinforc = sceKernelQueryModuleInfo(modid, &lmodinfo);
if (lmodinforc == 0)
{
info_func = libsFindExportAddrByNid(modid, lmodinfo.name, 0x79EC1E42);
#if DEBUG_OUTPUT>=1
if (info_func == NULL)
{
sioPrint("Failed to get driver info export.\r\n");
sioPrint("Was using name: '");
sioPrint(lmodinfo.name);
sioPrint("'\r\n");
}
#endif
ldriverinfo.apiVersion = PIKEY_THIS_VERSION;
}
#if DEBUG_OUTPUT>=1
else
{
sioPrint("Failed to get module info. RC=");
sioPrintWord(lmodinforc);
sioPrint("\r\n");
}
#endif
if ((info_func == NULL) || (info_func(&ldriverinfo) != PIKEY_SUCCESS))
{
// XXX should unload the driver here.
#if DEBUG_OUTPUT>=1
sioPrint("Would unload plugin here.\r\n");
#endif
}
else
{
#if DEBUG_OUTPUT>=1
// output some info about the driver
sioPrint("Successfully loaded driver '");
sioPrint(ldriverinfo.driverName);
sioPrint("' version ");
sioPrint(ldriverinfo.driverVersion);
sioPrint("\r\n");
#endif
}
/***************************************************************/
/* Save some info in our linked list of driver info. */
/***************************************************************/
if (isInput)
{
INPUT_INFO *linfo = (INPUT_INFO*)myMalloc(sizeof(INPUT_INFO));
if (!linfo)
{
sioPrint("malloc failed\r\n");
}
else
{
linfo->next = NULL;
if (inputs == NULL)
{
linfo->prev = NULL;
inputs = linfo;
}
else
{
INPUT_INFO *lptr = inputs;
while (lptr->next)
{
lptr = lptr->next;
}
linfo->prev = lptr;
lptr->next = linfo;
}
linfo->moduleID = modid;
strcpy(linfo->moduleName, ldriverinfo.driverName);
}
numInputs++;
}
else
{
OUTPUT_INFO *linfo = (OUTPUT_INFO*)myMalloc(sizeof(OUTPUT_INFO));
if (!linfo)
{
sioPrint("malloc failed\r\n");
}
else
{
linfo->next = NULL;
if (outputs == NULL)
{
linfo->prev = NULL;
outputs = linfo;
}
else
{
OUTPUT_INFO *lptr = outputs;
while (lptr->next)
{
lptr = lptr->next;
}
linfo->prev = lptr;
lptr->next = linfo;
}
linfo->moduleID = modid;
strcpy(linfo->moduleName, ldriverinfo.driverName);
}
numOutputs++;
}
}
}
}
}
}
while (lrc > 0);
sceIoDclose(lid);
}
}
#if DEBUG_OUTPUT>=1
void DumpMem(void *addr,unsigned size,int num)//DELME
{
char buf[270];
SceUID fd;
sprintf(buf,"ms0:/%d_%08X_%08X.bin",num,(int)addr,size);
fd = sceIoOpen(buf,PSP_O_WRONLY|PSP_O_CREAT|PSP_O_TRUNC,0777);
if(fd < 0)
return;
sceIoWrite(fd,addr,size);
sceIoClose(fd);
}
#endif
int main_thread(SceSize args, void *argp)
{
unsigned int disableHotkey = 0;
unsigned int oskstate = 0;
displayStatusText("Starting");
// Read the config file for user-defined control map
int cfg = configOpen("ms0:/seplugins/pikey/pikeyconfig.txt");
if (cfg >= 0)
{
while (configRead(cfg, cfgtoken, cfgvalue))
{
if (strcmp(cfgtoken, "SIO DEBUG") == 0)
{
sioDebug = ((cfgvalue[0] == 'Y') || (cfgvalue[0] == 'y'));
}
else if (strcmp(cfgtoken, "STATUS MESSAGES") == 0)
{
statusMessages = ((cfgvalue[0] == 'Y') || (cfgvalue[0] == 'y'));
}
else if (strcmp(cfgtoken, "DISABLED IN 150") == 0)
{
disabledIn150 = ((cfgvalue[0] == 'Y') || (cfgvalue[0] == 'y'));
}
else if (strcmp(cfgtoken, "DISABLED PLUGINS") == 0)
{
// split the CSV list, save in disabledPlugins[]
char *lptr = cfgvalue;
char *ldestptr = disabledPlugins[0];
numDisabledPlugins = 0;
while (*lptr != '\0')
{
if (*lptr == ' ')
{
// skip spaces
}
else if (*lptr == ',')
{
*ldestptr = '\0';
if (numDisabledPlugins++ > MAX_DISABLED_PLUGINS)
{
// too many disabled plugins
break;
}
else
{
ldestptr = disabledPlugins[numDisabledPlugins];
}
}
else
{
*ldestptr++ = *lptr;
}
lptr++;
}
// if we read any data, then adjust the disabledplugins count
// (since we didn't increment it at the end of the string, just the
// last comma)
if (ldestptr != disabledPlugins[0])
{
numDisabledPlugins ++;
}
}
else if (strcmp(cfgtoken, "DISABLE HOTKEY") == 0)
{
if ((cfgvalue[0] == '0') &&
((cfgvalue[1] == 'x') ||
(cfgvalue[1] == 'X')))
{
unsigned int lhex = 0;
char *lptr = &(cfgvalue[2]);
while ((*lptr != '\0') && (*lptr != ' '))
{
// This is a hex number, decode it.
char lhexdigit = (*lptr) - '0';
if (lhexdigit > 9)
{
lhexdigit -= ('A' - '9' - 1);
}
if (lhexdigit > 15)
{
lhexdigit -= ('a' - 'A');
}
if (lhexdigit < 0)
{
lhexdigit = 0;
}
lhex <<= 4;
lhex += lhexdigit;
lptr++;
}
disableHotkey = lhex;
}
}
}
configClose(cfg);
}
displayStatusText("line 806");
// If we're in v1.50 firmware, and we're disabled for this mode, then exit.
if (disabledIn150)
{
if (sceKernelDevkitVersion() == 0x01050001L)
{
sceKernelExitDeleteThread(0);
}
}
// If there's a disable hotkey, and it's pressed, then exit.
if (disableHotkey != 0)
{
SceCtrlData lpad;
sceCtrlPeekBufferPositive(&lpad, 1);
if ((lpad.Buttons & disableHotkey) == disableHotkey)
{
sceKernelExitDeleteThread(0);
}
}
if (sioDebug)
{
pspDebugSioInit();
sioPrint("\r\n\r\n");
#if DEBUG_OUTPUT>=2
pspDebugSioInstallKprintf();
pspDebugSioEnableKprintf();
#endif
sioPrint(" -----------------------\r\n");
sioPrint(" -- piKey is starting --\r\n");
sioPrint(" -----------------------\r\n");
#if DEBUG_OUTPUT >= 3
int ii;
for (ii = 1; ii <= 6; ii++)
{
sioPrint("\r\nFreemem: ");
sioPrintHex(ii);
sioPrint(" = ");
SceSize mem = sceKernelPartitionMaxFreeMemSize(ii);
sioPrintWord(mem);
}
#endif
}
InitVars();
gvshmode = waitForModule("sceVshBridge_Driver");
if (gvshmode)
{
sioPrint("Detected VSH mode\r\n");
// sleep here to avoid corrupting the startup animation
sceKernelDelayThread(4 * 1000 * 1000);
}
else
{
sioPrint("Detected GAME mode\r\n");
}
LoadDrivers("ms0:/seplugins/pikey/inputdrivers", 1);
LoadDrivers("ms0:/seplugins/pikey/outputdrivers", 0);
sioPrint("--- All plugins loaded ---\r\n");
displayStatusText("piKey is now ACTIVE");
while (!terminate)
{
sceKernelDelayThread(100); // Yields in a way that waitvblank does not
// Without this tiny sleep, we won't exit
// cleanly during module tidy-up.
sceDisplayWaitVblankStart();
// If we have something to display, display it
if (gStatusText)
{
font_init();
if (++gStatusCycles > STATUS_DISPLAY_TIME)
{
gStatusCycles = 0;
gStatusText = 0;
memset(centredText, ' ', sizeof(centredText));
blit_string(0, 0, centredText, 1);
}
else
{
blit_string(0, 0, centredText, 1);
}
}
// check for held keys. We only need to do this for stream mode, with
// some keys held.
if ((gKeyMode == PIKEY_KEYMODE_VT100STREAM) &&
(numPressed > 0))
{
int ii;
for (ii=0; ii < KEY_MAX; ii++)
{
if (gPressMap[ii])
{
if ((gHoldMap[ii])++ > REPEAT_TIME + REPEAT_PERIOD)
{
gHoldMap[ii] = REPEAT_TIME;
unsigned char str[16];
if (mapScanToVT100(ii, ctrlHeld, shiftHeld, altHeld, str))
{
unsigned char *lptr = str;
while (*lptr)
{
putNextChar(*lptr);
lptr++;
}
}
}
}
}
}
// Wait until we are activated with the NUM lock key.
if (gPressMap[KEY_NUMLOCK])
{
// check if JUST pressed
if (!(oskstate & 32))
{
oskstate |= 32; // NumLock pressed
oskstate ^= 4;
if (oskstate & 4)
{
sioPrint("NUM lock is ON\r\n");
displayStatusText("NUM lock is ON");
}
else
{
sioPrint("NUM lock is OFF\r\n");
displayStatusText("NUM lock is OFF");
}
}
}
else
oskstate &= ~32; // NumLock not pressed
// Wait until we are activated with the scroll lock key.
if (gPressMap[KEY_SCROLLLOCK])
{
// check if JUST pressed
if (!(oskstate & 16))
{
oskstate |= 16; // ScrollLock pressed
oskstate ^= 2;
if (oskstate & 2)
{
sioPrint("SCROLL lock is ON\r\n");
displayStatusText("SCROLL lock is ON");
}
else
{
sioPrint("SCROLL lock is OFF\r\n");
displayStatusText("SCROLL lock is OFF");
}
}
}
else
oskstate &= ~16; // ScrollLock not pressed
// Wait until we are activated with the CAPS lock key.
if (gPressMap[KEY_CAPSLOCK])
{
// check if JUST pressed
if (!(oskstate & 8))
{
oskstate |= 8; // CapsLock pressed
oskstate ^= 1;
if (oskstate & 1)
{
sioPrint("CAPS lock is ON\r\n");
displayStatusText("CAPS lock is ON");
}
else
{
sioPrint("CAPS lock is OFF\r\n");
displayStatusText("CAPS lock is OFF");
}
}
}
else
oskstate &= ~8; // CapsLock not pressed
// if we have an ESC charinput, in keypress mode, check whether we
// need to time it out into a simple ESC.
if ((gKeyMode == PIKEY_KEYMODE_KEYPRESS) &&
(gEscTimer != -1))
{
gEscTimer++;
if ((gEscTimer > ESC_TIMEOUT) &&
!(gPressMap[KEY_ESC]))
{
// we have a lone escape key. Press it...
resetMapkey();
sendKeyPress(KEY_ESC, 1, 0, 0, 0);
}
else if (gEscTimer > (ESC_TIMEOUT +(FAKE_KEYPRESS_HOLD / FRAME_LENGTH_USECS)))
{
// ... and release it after another timeout
sendKeyPress(KEY_ESC, 0, 0, 0, 0);
gEscTimer = -1;
}
}
}
sioPrint("--- Exiting piKey ---\r\n");
sceKernelExitDeleteThread(0);
return 0;
}
int main(void) {
int rc = 0;
/***************************************************************************/
/* Create main thread. */
/***************************************************************************/
pikeythread = sceKernelCreateThread("pikey", main_thread, 16, 0x1000, 0, NULL);
if (pikeythread >= 0)
{
rc = sceKernelStartThread(pikeythread, 0, NULL);
if (rc < 0)
{
return -2;
}
}
else
{
return -1;
}
return 0;
}
void throwAwayInput(int numchars)
{
while (numchars--)
{
if (numCharsAvailable() > 0) getNextChar();
}
}
void putNextCharW(wchar inChar) {
if (gKeyMode == PIKEY_KEYMODE_VT100STREAM)
{
// block until queue is not full
int lfull = 1;
while (lfull) {
sceKernelWaitSema(queueSema, 1, 0);
if (queueSize != QUEUE_SIZE) lfull = 0;
else {
sceKernelSignalSema(queueSema, 1);
sceKernelDelayThread(5000);
}
}
// add to Q
inBuffer[queueInputPos] = inChar;
queueInputPos = (queueInputPos + 1) % QUEUE_SIZE;
queueSize ++;
// release Q sema
sceKernelSignalSema(queueSema, 1);
}
else
{
// for keypress mode, we need to simulate a keypress and release
int shift, ctrl, alt;
int lscan = mapVT100toScan(inChar, &ctrl, &alt, &shift);
#if DEBUG_OUTPUT>=3
sioPrint("vt100: ");
sioPrintHex(inChar);
sioPrint("=> ");
sioPrintWord(lscan);
sioPrint(" : ");
sioPrintHex((ctrl << 2) | (alt << 1) | (shift));
sioPrint("\r\n");
#endif
// special hack to ESC chars - timeout a single ESC after a short time,
// if it isn't followed by any other chars.
if (inChar == 0x1b)
{
gEscTimer = 0;
}
if (lscan != -1)
{
gEscTimer = -1;
sendKeyPress(lscan, 1, shift, ctrl, alt);
sceKernelDelayThread(FAKE_KEYPRESS_HOLD);
sendKeyPress(lscan, 0, shift, ctrl, alt);
}
}
}
void putNextChar(char inChar) {
// we do a conversion to wchar then call the putwchar func.
// XXX : for now assume a very simple conversion
putNextCharW((wchar)inChar);
}
int numCharsAvailable(void) {
int lreturn = 0;
// only valid in stream mode
if (gKeyMode == PIKEY_KEYMODE_VT100STREAM)
{
sceKernelWaitSema(queueSema, 1, 0);
lreturn = queueSize;
// return 0 if there's an exclusive thread and we're not it
if (gExclusiveThread != 0)
{
if (sceKernelGetThreadId() != gExclusiveThread)
{
lreturn = 0;
}
}
sceKernelSignalSema(queueSema, 1);
}
else
{
lreturn = PIKEY_ERROR_WRONG_MODE;
}
return(lreturn);
}
/*****************************************************************************/
/* Returns PIKEY_SUCCESS if there are n chars available, copies them to */
/* buffer */
/*****************************************************************************/
int peekNChars(int n, wchar *buffer) {
int ii;
// only valid in stream mode
if (gKeyMode != PIKEY_KEYMODE_VT100STREAM)
{
return PIKEY_ERROR_WRONG_MODE;
}
sceKernelWaitSema(queueSema, 1, 0);
int lqueueSize = queueSize;
// override queue size if there's an exclusive thread and we're not it
if (gExclusiveThread != 0)
{
if (sceKernelGetThreadId() != gExclusiveThread)
{
lqueueSize = 0;
}
}
if (lqueueSize < n)
{
sceKernelSignalSema(queueSema, 1);
return PIKEY_ERROR_NOT_ENOUGH_CHARS;
}
// copy to buffer
for (ii = 0; ii < n; ii++)
{
buffer[ii] = inBuffer[(queueOutputPos + ii) % QUEUE_SIZE];
}
sceKernelSignalSema(queueSema, 1);
return(PIKEY_SUCCESS);
}
void _flushBuffer(void)
{
queueInputPos = 0;
queueOutputPos = 0;
queueSize = 0;
resetMapkey();
gEscTimer = -1;
}
void flushBuffer(void) {
sceKernelWaitSema(queueSema, 1, 0);
// do nothing if there's an exclusive thread and we're not it
if ((gExclusiveThread == 0) || (sceKernelGetThreadId() == gExclusiveThread))
{
_flushBuffer();
}
sceKernelSignalSema(queueSema, 1);
}
int requestExclusive(void)
{
SceUID lthid = sceKernelGetThreadId();
int lreturn;
// semaphore this using the standard queue semaphore
sceKernelWaitSema(queueSema, 1, 0);
/***************************************************************************/
/* Return success if we already have exclusivity. */
/***************************************************************************/
if (gExclusiveThread == lthid)
{
lreturn = 1;
}
/***************************************************************************/
/* Return failure if someone else already has exclusivity */
/***************************************************************************/
else if (gExclusiveThread != 0)
{
lreturn = 0;
}
else
{
gExclusiveThread = lthid;
gOldKeyMode = gKeyMode;
lreturn = 1;
}
sceKernelSignalSema(queueSema, 1);
return lreturn;
}
void releaseExclusive(void)
{
SceUID lthid = sceKernelGetThreadId();
sceKernelWaitSema(queueSema, 1, 0);
// Can only release exclusive if we are the exclusive thread
if (lthid == gExclusiveThread)
{
gExclusiveThread = 0;
_setMode(gOldKeyMode);
}
sceKernelSignalSema(queueSema, 1);
}
// mode: 0: keypress 1: stream
int setMode(int mode)
{
int lreturn = PIKEY_SUCCESS;
// change mode only if exclusive
SceUID lthid = sceKernelGetThreadId();
sceKernelWaitSema(queueSema, 1, 0);
// Can only change mode if we are the exclusive thread
if (lthid == gExclusiveThread)
{
_setMode(mode);
}
else
{
lreturn = PIKEY_ERROR_NOT_EXCLUSIVE;
}
sceKernelSignalSema(queueSema, 1);
return lreturn;
}
void _setMode(int mode)
{
gKeyMode = mode;
_flushKeyPresses();
_flushBuffer();
}
int getMode(void)
{
return gKeyMode;
}
int isKeyPressed(int keycode)
{
sceKernelWaitSema(queueSema, 1, 0);
int lreturn = gPressMap[keycode];
// return 0 if there's an exclusive thread and we're not it
if (gExclusiveThread != 0)
{
if (sceKernelGetThreadId() != gExclusiveThread)
{
lreturn = 0;
}
}
sceKernelSignalSema(queueSema, 1);
return lreturn;
}
void getMetaKeys(int *alt, int *ctrl, int *shift)
{
sceKernelWaitSema(queueSema, 1, 0);
// meaningless when not in keypress mode.
if (gKeyMode == PIKEY_KEYMODE_KEYPRESS)
{
*alt = altHeld;
*ctrl = ctrlHeld;
*shift = shiftHeld;
}
else
{
*alt = 0;
*ctrl = 0;
*shift = 0;
}
sceKernelSignalSema(queueSema, 1);
}
void _flushKeyPresses()
{
memset(gPressMap, 0, sizeof(gPressMap));
memset(gHoldMap, 0, sizeof(gHoldMap));
numPressed = 0;
gEscTimer = -1;
resetMapkey();
}
void flushKeyPresses()
{
sceKernelWaitSema(queueSema, 1, 0);
// don't allow flush if there's an exclusive thread and we're not it
if ((gExclusiveThread == 0) ||
(sceKernelGetThreadId() == gExclusiveThread))
{
_flushKeyPresses();
}
sceKernelSignalSema(queueSema, 1);
}
int numKeysPressed()
{
int lreturn = 0;
sceKernelWaitSema(queueSema, 1, 0);
// don't allow read if there's an exclusive thread and we're not it
if ((gExclusiveThread == 0) ||
(sceKernelGetThreadId() == gExclusiveThread))
{
lreturn = numPressed;
}
sceKernelSignalSema(queueSema, 1);
return lreturn;
}
void sendKeyPress(int keycode, int pressed, int shift, int ctrl, int alt)
{
if (pressed)
{
if (!gPressMap[keycode])
{
// only if wasn't already pressed (repeat issue)
gHoldMap[keycode] = 0;
gPressMap[keycode] = 1;
numPressed ++;
}
#if DEBUG_OUTPUT >= 3
sioPrint("Keypress: ");
sioPrintWord(keycode);
sioPrint(" ");
sioPrintWord((shift << 2) | (ctrl << 1) | alt);
sioPrint("\r\n");
#endif
if (gKeyMode != PIKEY_KEYMODE_KEYPRESS)
{
// We're in stream mode, so we need to be a bit fancier.
// If this is a press event, we just add the relevant char to the
// buffer.
// XXX : this needs a mapping to allow for capslock etc.
unsigned char str[16];
if (mapScanToVT100(keycode, ctrlHeld, shiftHeld, altHeld, str))
{
unsigned char *lptr = str;
while (*lptr)
{
putNextChar(*lptr);
lptr++;
}
}
}
shiftHeld = shift;
ctrlHeld = ctrl;
altHeld = alt;
}
else
{
// currently libpspirkeyb seems bugged for keycodes on at least the
// Targus. It sends a huge stream of released presses, so we need to
// filter for those keys that are actually pressed.
if (gPressMap[keycode])
{
#if DEBUG_OUTPUT >= 3
sioPrint("Keyrelease: ");
sioPrintWord(keycode);
sioPrint(" ");
sioPrintWord((shift << 2) | (ctrl << 1) | alt);
sioPrint("\r\n");
#endif
gPressMap[keycode] = 0;
numPressed --;
shiftHeld = shift;
ctrlHeld = ctrl;
altHeld = alt;
}
}
}
wchar getNextCharW(void) {
// block until queue is not empty
int lempty = 1;
SceUID lthid = sceKernelGetThreadId();
while (lempty) {
sceKernelWaitSema(queueSema, 1, 0);
// pretend empty if there's an exclusive thread and we are not it
if ((gExclusiveThread == 0) || (lthid == gExclusiveThread))
{
if (queueSize != 0)
{
lempty = 0;
}
}
if (lempty)
{
sceKernelSignalSema(queueSema, 1);
sceKernelDelayThread(5000);
}
}
wchar lchar = inBuffer[queueOutputPos];
queueOutputPos = (queueOutputPos + 1) % QUEUE_SIZE;
queueSize --;
sceKernelSignalSema(queueSema, 1);
return(lchar);
}
char getNextChar(void) {
// we call getNextCharW, then convert to 8bit
wchar lchar = getNextCharW();
// XXX: naive conversion func.
return ((char) lchar);
}
int registerDisplayCallback(SceUID hookid)
{
int lreturn = 0;
sceKernelWaitSema(callbackSema, 1, 0);
if (gNumDisplayCallbacks < MAX_DISPLAY_CALLBACKS)
{
gDisplayCallbacks[gNumDisplayCallbacks++] = hookid;
lreturn = 1;
#if DEBUG_INFO>=2
sioPrint("Registered display callback #");
sioPrintHex(gNumDisplayCallbackss);
sioPrint(" - ID ");
sioPrintWord(hookid);
sioPrint("\r\n");
#endif
}
sceKernelSignalSema(callbackSema, 1);
return lreturn;
}
void drawKeyboardInfo(DRAWINFO *argument)
{
int ii;
sceKernelWaitSema(callbackSema, 1, 0);
for (ii = 0; ii < gNumDisplayCallbacks; ii++)
{
sceKernelNotifyCallback(gDisplayCallbacks[ii], (int)argument);
}
sceKernelSignalSema(callbackSema, 1);
}
int isVshMode()
{
return gvshmode;
}
int configOpen(const char *filename)
{
int lreturn;
sceKernelWaitSema(queueSema, 1, 0);
#if DEBUG_OUTPUT>=3
sioPrint("configOpen: ");
sioPrint(filename);
sioPrint("\r\n");
#endif
if (numConfigs == MAX_CONFIGS)
{
sceKernelSignalSema(queueSema, 1);
return PIKEY_ERROR_TOO_MANY_CONFIGS;
}
int fh = sceIoOpen(filename, PSP_O_RDONLY, 0777);
if (fh < 0)
{
sceKernelSignalSema(queueSema, 1);
return fh;
}
int len = sceIoLseek32(fh, 0, SEEK_END);
int rc = sceIoLseek32(fh, 0, SEEK_SET);
if (rc < 0)
{
sioPrint("Error on file seek: ");
sioPrintWord(rc);
sioPrint("\r\n");
sceKernelSignalSema(queueSema, 1);
return rc;
}
// allocate mem to hold the file
configData[numConfigs] = myMalloc(len + 1);
if (configData[numConfigs] == NULL)
{
sceKernelSignalSema(queueSema, 1);
return PIKEY_ERROR_NO_MEM;
}
// set progress pointer to the start of the file data
configOffset[numConfigs] = configData[numConfigs];
#if DEBUG_OUTPUT>=3
sioPrint("Read the file\r\n");
#endif
// read the file into mem, null-terminate it.
rc = sceIoRead(fh, configData[numConfigs], len);
if (rc < len)
{
sceKernelSignalSema(queueSema, 1);
return PIKEY_ERROR_MISC;
}
configData[numConfigs][len] = '\0';
#if DEBUG_OUTPUT>=3
sioPrint("Close the file\r\n");
#endif
sceIoClose(fh);
lreturn = numConfigs++;
sceKernelSignalSema(queueSema, 1);
return lreturn;
}
int configRead(int cfgHandle, char *token, char *value)
{
int lreturn = 0;
sceKernelWaitSema(queueSema, 1, 0);
// valid handle?
if ((cfgHandle >= numConfigs) ||
(cfgHandle < 0) ||
(configData[cfgHandle] == NULL) ||
(configOffset[cfgHandle] == NULL))
{
sioPrint("configRead: Invalid config handle\r\n");
lreturn = 0;
}
else
{
// We start from configOffset, and seek to an EOL or EOF
int ldone = 0;
char *lendptr;
char *lequalptr;
while (!ldone)
{
lendptr = configOffset[cfgHandle];
lequalptr = NULL;
while ((*lendptr) && (*lendptr != '\n'))
{
if (*lendptr == '=')
{
lequalptr = lendptr;
}
lendptr++;
}
if ((configOffset[cfgHandle][0] == '#') &&
(*lendptr != '\0'))
{
// this is a comment, and we're not at EOF. Read the next line.
configOffset[cfgHandle] = lendptr + 1;
}
else
{
ldone = 1;
}
}
char *linestart = configOffset[cfgHandle];
// found an equals sign that's not in the first column?
if (lequalptr == NULL)
{
lreturn = 0;
}
else
{
// scan back through whitespace from the equals
char *ltokenend = lequalptr - 1;
while ((ltokenend > linestart) &&
((*ltokenend == ' ') ||
(*ltokenend == '\t')))
{
ltokenend--;
}
// token length too small or too big?
int toklen = ltokenend - linestart + 1;
if ((toklen == 0) || (toklen > 63))
{
lreturn = 0;
}
else
{
strncpy(token, linestart, toklen);
token[toklen] = '\0';
// ... and now the value. Scan forward from the equals, through
// any whitespace.
char *lvalstart = lequalptr + 1;
while ((lvalstart < lendptr) &&
((*lvalstart == ' ') ||
(*lvalstart == '\t')))
{
lvalstart++;
}
int lvallen = lendptr - lvalstart;
// value too large?
if (lvallen > 127)
{
lreturn = 0;
}
// we allow an empty value, but it is a special case (might be -ve length)
else if (lvallen < 0)
{
value[0] = '\0';
lreturn = 1;
}
else
{
strncpy(value, lvalstart, lvallen);
value[lvallen] = '\0';
// tweak for CRLF files - remove the CR if present.
if ((lvallen >= 1) &&
(value[lvallen-1] == '\r'))
{
value[lvallen-1] = '\0';
}
// all done, and we got a value of some sort. SUCCESS!
lreturn = 1;
}
}
}
// advance the config offset appropriately, ready for the next call.
if (*lendptr == '\0')
{
// EOF
configOffset[cfgHandle] = NULL;
}
else
{
// EOL
configOffset[cfgHandle] = lendptr + 1;
}
}
sceKernelSignalSema(queueSema, 1);
return lreturn;
}
void configClose(int cfgHandle)
{
sceKernelWaitSema(queueSema, 1, 0);
if ((cfgHandle >= numConfigs) ||
(cfgHandle < 0) ||
(configData[cfgHandle] == NULL))
{
sioPrint("Attempt to close bad config handle: ");
sioPrintWord(cfgHandle);
sioPrint("\r\n");
sceKernelSignalSema(queueSema, 1);
return;
}
#if DEBUG_OUTPUT>=3
sioPrint("Valid handle\r\n");
#endif
// We don't do anything about freeing the config slot here,
// since the cost of one slot is just 8 bytes, and we expect that MAX_CONFIGS
// is plenty for all the config files we will be expected to process during
// the lifetime of the PRX invocation.
// free the mem
myFree(configData[cfgHandle]);
configData[cfgHandle] = NULL;
configOffset[cfgHandle] = NULL;
sceKernelSignalSema(queueSema, 1);
}
int module_start(SceSize args, void *argp) __attribute__((alias("_start")));
int _start(SceSize args, void *argp)
{
main();
return 0;
}
/*****************************************************************************/
/* Shut everything down - kill the main thread, unload the modules. */
/*****************************************************************************/
void terminateAll()
{
terminate = 1;
// this seems to fail to unload most of the time, or just crashes in
// GAME150 mode - probably not worth running it, for the time being.
#if 0
if (inputs != NULL)
{
INPUT_INFO *lptr = inputs;
while (lptr)
{
int status, rc;
#if DEBUG_OUTPUT>=2
sioPrint("Shutdown module (");
sioPrintWord(lptr->moduleID);
sioPrint(")...");
#endif
rc = sceKernelStopModule(lptr->moduleID, 0, NULL, &status, NULL);
if (rc < 0)
{
#if DEBUG_OUTPUT>=2
sioPrint("Failed to stop module: ");
sioPrint(lptr->moduleName);
sioPrint(": ");
sioPrintWord(rc);
#endif
}
else
{
rc = sceKernelUnloadModule(lptr->moduleID);
if (rc < 0)
{
#if DEBUG_OUTPUT>=2
sioPrint("Failed to unload module: ");
sioPrint(lptr->moduleName);
sioPrint(": ");
sioPrintWord(rc);
#endif
}
}
sioPrint("\r\n");
lptr = lptr->next;
}
}
if (outputs != NULL)
{
OUTPUT_INFO *lptr = outputs;
while (lptr)
{
int status, rc;
#if DEBUG_OUTPUT>=2
sioPrint("Shutdown module (");
sioPrintWord(lptr->moduleID);
sioPrint(")...");
#endif
rc = sceKernelStopModule(lptr->moduleID, 0, NULL, &status, NULL);
if (rc < 0)
{
#if DEBUG_OUTPUT>=2
sioPrint("Failed to stop module: ");
sioPrint(lptr->moduleName);
sioPrint(": ");
sioPrintWord(rc);
#endif
}
else
{
rc = sceKernelUnloadModule(lptr->moduleID);
if (rc < 0)
{
#if DEBUG_OUTPUT>=2
sioPrint("Failed to unload module: ");
sioPrint(lptr->moduleName);
sioPrint(": ");
sioPrintWord(rc);
#endif
}
}
sioPrint("\r\n");
lptr = lptr->next;
}
}
#endif
sceKernelWaitThreadEnd(pikeythread, 0);
}
/*****************************************************************************/
/* This is apparently never called when running as a VSH OE plugin. */
/*****************************************************************************/
int module_stop(SceSize args, void *argp)
{
#if DEBUG_OUTPUT>=2
sioPrint("---SHUTDOWN---\r\n");
#endif
terminateAll();
return 0;
}
int module_bootstart()
{
#if DEBUG_OUTPUT>=2
sioPrint("---bootstart---\r\n");
#endif
return 0;
}
/*****************************************************************************/
/* This gets called immediately before the transition from VSH to GAME mode */
/* (and presumably vice-versa). */
/* */
/* We don't do anything with this call in piKey. */
/*****************************************************************************/
int module_reboot_before()
{
#if DEBUG_OUTPUT>=2
sioPrint("---boot before---\r\n");
#endif
return 0;
}
/*****************************************************************************/
/* Called 3 times during reboot to GAME mode. arg1 is 3, 2, 1 in the 3 */
/* calls, respectively. */
/*****************************************************************************/
int module_reboot_phase(int arg1)
{
#if DEBUG_OUTPUT>=2
sioPrint("---boot phase--- ");
sioPrintHex(arg1);
sioPrint("\r\n");
#endif
if (arg1 == 3) // first reboot phase
{
terminateAll();
}
return 0;
}
#if 0
Lessons from this crash:
- if a vsh kernel thread does not properly yield (must be delaythread,
waitvblank does not count) then it will not be killed during the reboot
to game mode.
- you can detect the transition via the little-known module_reboot_* funcs
and therefore end a thread loop that doesn't yield.
#endif