PSP Programming/Text Menu

Feel free to use or edit any part of this page and/or code, but please leave credit where credit is due.

This is a simple program i wrote to show how you would use controls in your game, and how to code an extremely simple menu, it is thoroughly commented but there may be easier / faster ways of doing things than im aware of, if you see anything you could improve on, please feel free.

To compile this, you will need a few files(that i did not write) that you can download at http://www.psp-programming.com/tutorials/c/lesson04.zip I believe theese codes were written by Yeldarb, anyway nice work.

Somebody please do a public service and wiki format this, looks fine during edit if you want to copy/paste, like i said, just give me some kind of credit.

// First homebrew program written by 42o5H4DYH4Xo24     *NoHellshady00[@]Spamgmail.com
// Completed on January 4th, 2007, 5:55pm
// Extremely basic program to output text on the PSP
// Written using the PSPToolkit & PSPSDK
// Includes

#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspctrl.h>
extern "C" {
	#include <graphics.h>
	#include <framebuffer.h>
}

// Convenience
// RGB to decimal color conversion macro
#define RGB(r, g, b) ((r)|((g)<<8)|((b)<<16))


// PSP kernel module info
PSP_MODULE_INFO("TextDisplay",0,1,1);


// Standard callback functions
/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
          sceKernelExitGame();
          return 0;
}

/* Callback thread */
int CallbackThread(SceSize args, void *argp) {
          int cbid;

          cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
          sceKernelRegisterExitCallback(cbid);

          sceKernelSleepThreadCB();

          return 0;
}

/* Sets up the callback thread and returns its thread id */
int SetupCallbacks(void) {
          int thid = 0;

          thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
          if(thid >= 0) {
                    sceKernelStartThread(thid, 0, 0);
          }

          return thid;
} 
// End Callback functs

// Set some global variables and function prototypes

int ColorRed = RGB(255, 0, 0); // Red
int ColorBlue = RGB(0, 0, 255); // Blue
int ColorGreen = RGB(0, 255, 0); // Green
int ColorBlack = RGB(0, 0, 0); // Black

int xMenuop; // Dont change this
int& Menuop = xMenuop; // Change this instead
int startMenu(int a); // Function prototype for printing the menu to the screen with a black background, use int a to set cursor
int startMenuCtrl();
int startMenuCtrlDelay() {
	sceKernelDelayThread(1000000/7);
	return 0;
	}
SceCtrlData pad;

// Main Menu
int startMenu(int a) { //
	// Set some local variables
	char cursor[5] = "=->"; // What to use as our cursor
	int cursorColor = ColorBlue;
	int MinMenuop = 1;
	int MaxMenuop = 4; // Number of options in our menu
	// Check to make sure an invalid option wasnt passed to startMenu()... and makes the list roll over
	if (a>MaxMenuop) {
		Menuop = MinMenuop;
		startMenu(Menuop); // Recursive
	}
	if (a<MinMenuop) {
		Menuop = MaxMenuop;
		startMenu(Menuop); // Recursive
	}
	// Should never execute without a correct value to keep from displaying incorrect cursor positions
	// Print the new menu
	int cursorY = (Menuop*10)+110;
	clearScreen(ColorBlack);
	printTextScreen(120, cursorY, cursor, cursorColor); 
	printTextScreen(150, 120, "Start Game", ColorGreen);
	printTextScreen(150, 130, "Load Game", ColorGreen);
	printTextScreen(150, 140, "Multiplayer", ColorGreen);
	printTextScreen(150, 150, "Password", ColorGreen);
	sceDisplayWaitVblankStart();
	flipScreen();
	return 0;
}
int startMenuCtrl() {
	// Start a control loop
	while (1) {
		sceCtrlReadBufferPositive(&pad, 1);
		if (pad.Buttons & PSP_CTRL_DOWN) {
			Menuop++;
			startMenu(Menuop);
			startMenuCtrlDelay();
		}
		if (pad.Buttons & PSP_CTRL_UP) {
			Menuop--;
			startMenu(Menuop);
			startMenuCtrlDelay();
		}		
	}		
}

// Start
int main(void) {
	// Init some variables
	Menuop = 1;
	
	SetupCallbacks(); // Standard, setup our Callbacks
	initGraphics(); // graphics.h does all the initializing for us
	printTextScreen(0, 0, "Simple Menu v1.1 by 42o5H4DYH4Xo24", ColorGreen);
	sceDisplayWaitVblankStart();
	flipScreen();
	sceKernelDelayThread(1000000*2);
	sceDisplayWaitVblankStart();
	clearScreen(ColorBlack);
	startMenu(Menuop);
	startMenuCtrl();
	return 0; // our function main() is supposed to return an int, 
                  // so here it is... for c++ etiquette.
                  //
                  // Note: Explicit return in main is good for clarity, as otherwise this will 
                  // return 0 exit code to any caller implicitly. Forming good habits now will 
                  // save you debug headaches later. :)
}
/* Notes
Need to make PSP ftp program to send file from PSP and wait for one in return
 and a bash shell to compile the file and resend the eboot
 */

Feel free to comment/edit this code or more thoroughly describe what's happening, just leave credit where credit is due.

Note: using recursion (i.e. in the startMenu() function) is a very good way to crash your system. The reason is that when a function is called the system does so by pushing a pointer to the function onto the system stack followed by any arguments to that function in reverse order (reversed from how they appear in the source code). It's normally not a problem because programs do not usually make many successive function calls. But consider what would happen in the case of the startMenu() function: Every time the cursor goes off the end of the menu list a new function call is pushed onto the stack ( (4byte function pointer) + (4byte integer) = 8 bytes ). You say that's not a problem? Well let me ask you- what would happen if the user just holds down the up/down d-pad key and lets the cursor scroll through the menu list for a while? Those 8 bytes will be added to the stack every four startMenu() calls. So after a while the system stack will outgrow the available memory on the PSP(32mb on the phatties, 64mb on the slims). When that happens the system will barf and hang. Then the user must reset his PSP to recover. Now, maybe you think that's unlikely to happen, and in this case that's true, but consider what would happen in similar situations if you were pushing more/bigger arguments onto the stack. What would happen if this forced the user to reboot his PSP while doing something to the firmware? It would brick the PSP.

Even if things do not get quite that out of hand code like this can still be slow because the PSP's MIPS32 CPUs use what is called a Register Window to store function calls and arguments instead of an orthodox stack like the IA32 architecture. When the window of registers fills up the CPU will dump the overflow out to memory until it needs it again. That is a (relatively) slow operation even if your program never totally fills up all available RAM.

The point is that recursion is logically elegant, but a very dirty practical solution. I would recommend, instead of recursively calling the startMenu() function when the cursor runs off the end of the menu list, that the startMenu() function be changed to return an invalid menu position (i.e. -1) when the cursor goes needs to loop. Then, wherever the startMenu() function is called, the code should check the return value to see if it's invalid. If so, then simply call startMenu() again. For example:

// we'll put these in a global struct for now, ideally we'd wrap this whole menu up in a class...
struct main_menu_attributes
	{
	char cursor[5] = "=->"; // What to use as our cursor
	int cursorColor = ColorBlue;
	int MinMenuop = 1;
	int MaxMenuop = 4; // Number of options in our menu
	} mm_attribs; // global variable of type main_menu_attributes

//
//  renamed startMenu() to something more appropriate
//
int displayMenu (int argMenuop)
	{
	// Check to make sure an invalid option wasnt passed to startMenu()... and makes the list roll over
	// never execute without a correct value to keep from displaying incorrect cursor positions
	// Print the new menu
	int cursorY = (Menuop*10)+110; // 110 appears to be the pixel height for the menu so that the cursor appears next to the menu (centered on screen)
					// 10 appears to be the pixel height of a character/line of text, so multiplying the Menuop by 10 moves the cursor by
					// an entire menu row at a time
	clearScreen(ColorBlack);
	printTextScreen(120, cursorY, cursor, cursorColor);	// print the cursor first
	printTextScreen(150, 120, "Start Game", ColorGreen);	// print the first menu item's text to the frame buffer
	printTextScreen(150, 130, "Load Game", ColorGreen);	// print the second menu item's text to the frame buffer
	printTextScreen(150, 140, "Multiplayer", ColorGreen);	// print the third menu item's text to the frame buffer
	printTextScreen(150, 150, "Password", ColorGreen);	// print the fourth menu item's text to the frame buffer
	sceDisplayWaitVblankStart();				// I have no idea what we're waiting for (if that's what we're doing here)
	flipScreen();						// flipScreen() will put the stuff we just wrote onto the screen that the user is looking at
	return 0;
	}

//
// take input from the user. assuming the rest of the program code has been set up,
// the user can hit the 'HOME' button to leave the program and get back to the XMB
//
int startMenuCtrl ()
	{
	int command_code; // no need to waste CPU time initializing this since it will not be used until after it is set by calling getMenuInput()
	// Start a control loop
	while (1)
		{
		// display the screen
		startMenu(Menuop);
		// get user input
		command_code = getMenuInput ();
		// if the input is inappropriate (goes off the menu list) then loop it before displaying again

		else if (Menuop < MinMenuop)
			{
			Menuop = MaxMenuop; // if the user overstepped in the up direction then loop back to the bottom
			}
		else;
		// if the input is appropriate then the menu will be re-displayed as usual
		// now we execute depending on what the user chose (if he chose anything at all. if not, then we stay here in this loop and wait)
		executeCommand (command_code);
		}		
	}

//
// this function will grab the user input and translate it for us into a simple integer (makes dealing with it easier)
//
int getMenuInput ()
	{
	int rtrn = 0; // we stick our return value in here

	sceCtrlReadBufferPositive(&pad, 1);
	if (pad.Buttons & PSP_CTRL_DOWN)
		{
		rtrn = 1;
		}
	if (pad.Buttons & PSP_CTRL_UP)
		{
		rtrn = 2;
		}
	/*
	if (pad.Buttons & /*whatever button you want to use for "OK"*/)
		{
		// we won't touch Menuop because the user isn't moving the cursor
		startMenuCtrlDelay ();
		rtrn = /* whatever code you want to use for the button that was pushed */
		}
	// and so on and so forth for all the button handlers
	*/

	return rtrn; // the rtrn code we're returning will tell whoever called us what the user's input was
	}

//
//  moved all the command code into a nice, easy-to-read switch
//
int executeCommand (int command_code)
	{
	switch (command_code)
		{
		case 1: // user hit d-pad UP
			{
			Menuop--;				// move the cursor up one
			if (Menuop < mm_attribs.MinMenuop)
				{
				Menuop = mm_attribs.MaxMenuop;	// if the user overstepped in the up direction then loop back to the bottom
				}
			else;
			startMenuCtrlDelay ();			// wait for a moment so that we don't take input faster than the user can let off of the d-pad
			break;
			}
		case 2: // user hit d-pad DOWN
			{
			Menuop++;				// move the cursor down one
			if (Menuop > mm_attribs.MaxMenuop)
				{
				Menuop = mm_attribs.MinMenuop;	// if the user overstepped in the up direction then loop back to the bottom
				}
			else;
			startMenuCtrlDelay ();			// wait
			break;
			}
		/*
		case /*your command code here*/:
			{
			// do stuff
			break;
			}
		*/
		default: // unhandled input- consider it homework
			{
			// print a warning message and continue as normal
			break;
			}
		}
	}

There are more functions in this code, but it's not the absolute number of function calls that's the problem with recursion- it's the depth of the function calls that causes problems. On a further note, main() has always been required to return an integer value on POSIX systems. The only system/compiler which doesn't require it is Windows and Visual Studio.


edit