PSP Development/Hello World Application

Tutorial AnalysisEdit

The goal for this article is to outline a basic, cookie-cutter Minimal Working Example (MWE) to demonstrate core common practices. Such contents should relate to actions taken more project that is considered the 'same thing each time'. Getting the Tools should already have completed, or are waiting for it to finish downloading.

The Project FolderEdit

Inside of /pspsdk is a great place to start a project folder library. Some people prefer to use an IDE. An understanding of what the IDE is doing and the use proper Makefile style project is necessary. Building happens using PSP-GCC and the tools inside of pspsdk/bin. A C compiler's bin for programs is needed for make. The articles will be manually editing the Makefile per article directly, as it has additions from a normal, everyday Makefile to compile correctly. An external Makefile is included into the Makefile and variables are set up beforehand.

Getting Set UpEdit

A common workspace that translates from reader to writer seamlessly is important. It makes facilitating the article much easier. While it isn't recommended for a beginner to skip steps, an experienced PSP programmer or an Adept programmer will find it easy to diverge from the article. The project folder should be found in your PSPDEV folder. Another good practice is to make a projects folder rooted in your User folder.

FoldersEdit

For the entire wikibook, a common projects folder will be used. Windows will have a C:/pspsdk/projects folder. Linux will have a /opt/pspsdk/projects folder. In addition to linux, Mac may have instead /usr/local/pspsdk/projects. The common folder contains source, header, and object files that should be shared commonly with projects and edited as needed. Inside the project folder is a good place to house a common folder. Some code is cookie-cutter, therefore it is wise to not only cut down on compile time, but organize a neat folder for productivity. These files should be lightly edited and not break from project to project, in that an iteration through the projects to fix any bugs or apply any improvements will be a very frequent action.

  1. Navigate to the pspsdk folder
  2. Create a folder called projects
  3. Navigate to the pspsdk/projects folder
  4. Create a folder called common

While the naming convention lightly matters, a comprehensive name to specify what the project is very helpful. This saves confusion when evaluating projects and exploring the file system, looking for a specific project. The entire path should not have spaces in it to reduce a large number of headaches that can occur, by which goes back to how pathing works. The path opt/psp sdk/projects/project will actually read as two separate arguments. To circumvent this, add nest quotes around the path. "opt/psp sdk/projects/project" will read as one argument.

  1. Navigate to the projects folder
  2. Create a project folder called hello_world

FilesEdit

A callback system occurs in every PSP project. It is wise to create a compilation unit for this, since it will not change from project to project.

  1. Navigate to the common folder
  2. Insert a new C compilation unit with the respective header
  3. Name these files callback.c and callback.h
  4. Make sure you include the header inside of the compilation unit.

An entry point to the hello_world project is necessary. This compilation unit is called main.c without a header.

  1. Navigate to hello_world folder
  2. Insert a new compilation unit named main.c

The file main.c is where all the procedural code will go as well as specific setup procedures necessary. The main.c should look like the below file, and include the common/callback.h file.

main.c

#include "../common/callback.h"

int main(int argc, char** argv)
{
	
	return 0;
}

PSP Library Naming ConventionsEdit

The PSPDEV library has many different naming conventions. This list is not a fully comprehensive list.

  • functions
    • sceFunctionName()
    • pspFunctionName()
  • libraries
    • liblibrary.*
    • psplibrary.*
  • structures/data types

Exit CallbackEdit

The main reason for having the common/callback.* compilation units is to house code which doesn't or rarely should change. In this case, a method the PSP requests the program to handle. A thread needs to be registered, which invokes a function as a callback, or seemingly like an event, telling the program to exit when the user (or program) requests for closing. The thread is slept to keep it alive. The respective files will have the following code.

callback.c

#include <pspkernel.h>

static int exitRequest  = 1;

int isRunning()
{
	return exitRequest;
}

int exitCallback(int arg1, int arg2, void *common) 
{ 
	exitRequest = 0; 
	return 0; 
} 

int callbackThread(SceSize args, void *argp) 
{ 
	int callbackID; 

	callbackID = sceKernelCreateCallback("Exit Callback", exitCallback, NULL); 
	sceKernelRegisterExitCallback(callbackID); 

	sceKernelSleepThreadCB(); 

	return 0; 
} 

int setupExitCallback() 
{ 
	int threadID = 0; 

	threadID = sceKernelCreateThread("Callback Update Thread", callbackThread, 0x11, 0xFA0, THREAD_ATTR_USER, 0); 
	 
	if(threadID >= 0) 
	{ 
		sceKernelStartThread(threadID, 0, 0); 
	} 

	return threadID; 
}

callback.h

#ifndef COMMON_CALLBACK_H
#define COMMON_CALLBACK_H

int isRunning();
int setupExitCallback();

#endif

Description of CodeEdit

Looking at the header file, it exposes two functions: isRunning() and setupExitCallback(). The isRunning() function will determine when to exit the main game loop, which updates every frame so that the program continues on gracefully, and when stopped clean up, save, and exit the program after an exist request happens. When the user requests an exit, the exitCallback function will be invoked and switches the exitRequest from 0 (no exit request) to 1 (valid exit request).

PSP main.c HeadEdit

Inside of the main.c compilation unit, the PSP requests for the programmer to set up specific parameters in the file. Before this setup, headers need to be included to work. It is common practice to create macros which point to very long, frequently typed function names. It is considered bad practice to macro everything used.

Setting Up IncludesEdit

The includes needed to operate are pspkernel.h, pspdebug.h, and pspdisplay.h. There is a debug screen at disposal, where one can output text similar to Text Mode in operating systems. The kernel and display are vital for many compilation units, as they provide common, core functions. These headers can be found in pspsdk/psp/sdk/include, but it branches off to multiple includes. Most of the code in the headers are documented with comments. It would be wise to familiarize with the function names as you go.

Include the necessary headers for the hello_world project.

main.c

#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>

Setting Up the Module InformationEdit

After the headers, the PSP require the programmer to define the environment. Using the functions PSPDEV provides, the information can be filled in - extra information can be added too. The PSP_MODULE_INFO function sets up the base information for the program and in necessary in all projects. The first argument is the title of the module. The second argument is either user mode or kernel. Always use user mode unless kernel mode is needed, which is rare outside modifying the PSP. The last two arguments are numbers that define the version and revision. The PSP_MAIN_THREAD_ATTR function sets up the environment that the main thread abides by. This function is option when not attempting to work in kernel mode. It is recommended to stay away from kernel mode, as you can Brick your PSP.

Add the code needed right after the includes.

main.c

#define VERS    1 // version
#define REVS    0 // revision

PSP_MODULE_INFO("HelloWorld", PSP_MODULE_USER, VERS, REVS);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);

PSP main.c BodyEdit

Further setup in the main compilation unit is needed. The exit callback needs to be called, program needs to be exited officially, and in between a game loop needs to update each frame. Before the game loop is where initial setup happens, however, the difference between console programs and game-based programming is the longevity of the variables. It should not be uncommon to put information or create variables outside of the main function. When multi-threading, this is a common practice.

ParametersEdit

The function int main(int argc, char** argv) gives a total of 1 arguments to start out when running from PPSSPP. The value of argc is the length of the amount of arguments passed to the program, which are stored in argv. The first index of argv returns the current path of the EBOOT.PBP executable, which is the PSP's executable file (.exe). Using PPSSPP emulator to test, you will get 'umd0:/EBOOT.PBP'. This may not be the case on an actual PSP.

Setting UpEdit

The created functions in ../common/callback.c that need to be used. This is first call in the main function. After this call, the programmer gets free reign of what the program does. At the end, return the exit code. Exiting gracefully should return the standard 0. Before the main returns, a call to the exit function sceKernelExitGame() needs to occur.

NOTE (It is in bad practice to call sceKernelExitGame() in the main thread. The documentation comments suggest sceKernelExitGame() should be called within a separate thread - the callback thread for example. This means it's necessary to do some thread management to the thread, after cleaning up and file save operations. sceKernelExitGame() instead will just crash the game, which is not a big deal to program around. It is functionally equivalent to exit(). It may take up to 20 seconds to crash, however. One problem with having the callback thread call sceKernelExitGame() in the current setup above is the inability to program after the while loop, for the program will exit then and not after the program cleans up and saves data.)

Add the code needed inside the main function.

main.c

int main(int argc, char** argv)
{
	// basic init
	setupExitCallback();
	pspDebugScreenInit();
	
	// basic exit by crashing
	sceKernelExitGame();	
	return 0;
}

Adding FunctionalityEdit

The basic template of the program is finished. Building and test will seem like it crashes, unless something happens in real time, meaning the PSP is actively running the program. A widely used feature is the debug screen. The debug screen is functionally similar to Text Mode in operating systems. While the debug screen enables the ability to write out text in a colorful manor, the default color is white text with a black background.

The function which writes to the screen is pspDebugScreenPrintf. It is functionally equivalent to stdio.h printf() in that printf("%s%d", "hello", 123) will print out hello123 to the debug screen. A common, accepted and safe practice is to define pspDebugScreenPrintf as printf - since it likely to call this function many, many times with certain outcome.

NOTE: (If you printf("%s", (int) 123), a seemingly random crash may occur. A crash will occur if you printf((int) 123). It is a must to always supply the correct format.)

Add the following after the set up functions at the top of the file.

main.c

# define printf pspDebugScreenPrintf

A while loop (main loop) is needed to not only hang the main thread, but to enable updating the graphics and logic every frame. Using the debug screen, it is necessary to print every frame for it to stay visible if clearing the screen. If the screen is cleared and something is printed only once, it will just show 1 time and disappear. That means anything you print before the main loop will not persist unless the program hangs after the printing. To circumvent this, bolster the print commands at an appropriate time in the main loop. Calling printf("\n") will go to the next line. It is possible for text to be overwritten between the printf() calls, as the printf() starts from the same position each time on the X axis. There is internal workings that track the location of the symbol drawing system. Before drawing anything, top left of the screen to print all the information again. The pspDebugScreenSetXY(0, 0) will modify where the internal cursor will operate. Before the use of the debug screen, it needs to be initialized. A proper place to do this is before the main loop, using pspDebugScreenInit().

There are a few things that need clarified about the main loop. The first is that it updates every frame at a frame rate. The second thing is that it needs to cut into something called vblank. VBlank stands for vertical blank. The description on why this is necessary goes back to console hardware. See VBlank on wikipedia for more information on vblank. The short story is the program will get graphical bugs writing to the pixels as they are being read from. One of the bugs is tearing.

The isRunning() function from ../common/callback.c will be used to keep the main loop active. This returns the flag to determine if the game should start to exit or not. Entering the vblank at the start of the main loop would be best practice, although if at the end, the very first frame (one single frame) will have graphical issues. Due to the speed of rendering, it won't be noticeable to the human eye or attention span.

Any dynamic text is problematic. For example, drawing a 20 char string of characters and drawing a 5 char string on the same line will overlap, and look like the first 5 chars of the 20 char string was replaced and the other 15 chars were never deleted. Any text drawn will stay on the screen. To circumvent this, clear the screen each frame before drawing with pspDebugScreenClear(). This call belongs after the vblank line and before printing occurs.

main.c

		pspDebugScreenInit(); // initialize the debug screen
		while(isRunning()) { // while this program is alive
			sceDisplayWaitVblankStart(); // wait for vblank
			pspDebugScreenClear(); // clears screen pixels
			pspDebugScreenSetXY(0, 0); // reset where we draw
			printf("Hello World!"); // print some text
		}

The MakefileEdit

Allowing the computer to edit the Makefile for you is problematic when it comes to the programmer understanding what is going on, especially if they don't have experience with Makefiles. It is important to follow the common practices the article demonstrates. It is beyond the scope of this article to explain the Makefile's working mechanics, but since it is a do or not work situation, it is required to be explained with some instance of thoroughness.

Create a new file called Makefile in the root of the hello_world project folder. Do not add any extension. Add the necessary code.

Makefile

TARGET = hello_world
OBJS = main.o ../common/callback.o

INCDIR = 
CFLAGS = -O2 -G0 -Wall
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)

LIBDIR =
LIBS =
LDFLAGS =

EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = HelloWorld

PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak

The Makefile you create will include another Makefile supplied by PSPDEV. The Makefile the programmer creates only instructs what the later Makefile does. If adding libraries to the project, they need to be listed under LIBS. The library files will be in pspsdk/psp/sdk/lib folder. If not stored in the lib folder, the libraries should in the project folder. If supplied -l before the library, one can drop lib and the .a extension from the library. liblua.a turns out to be -llua. Any compilation unit used needs to be added under OBJS. In this article, main.o and ../common/callback.o are the compilation units used and they need to be added to the Makefile OBJS. The PSP_EBOOT_TITLE should match with what was supplied in the program's call to PSP_MODULE_INFO. The project folder's name should match to TARGET.

BuildingEdit

A EBOOT.PBP file is needed to run on PPSSPP or the PSP. An EBOOT.PBP is the PSP's executable file. If any compilation errors occur, the programmer will need to fix them before one is generated. EBOOT.PBP files are generated from a variation of ELF files.

  1. Navigate to project folder in cmd or terminal
  2. Execute "make EBOOT.PBP"

Using PSP-GCCEdit

A Makefile will automate the build process. Below is the basic actions it does. If on Linux, translation of the -I and -L switches will be needed, as this points to a typical windows install directory. To get this path correct 100% of the time, a tool called psp-config is available. Try using psp-config -p and append it with /include for -I and /lib for -L.

psp-gcc -O0 -g3 -Wall -I. -IC:/pspsdk/psp/sdk/include -L. -LC:/pspsdk/psp/sdk/lib -D_PSP_FW_VERSION=150 -c *.c -lpspdebug -lpspdisplay -lpspge -lpspctrl -lpspsdk -lc -lpspnet -lpspnet_inet -lpspnet_apctl -lpspnet_resolver -lpsputility -lpspuser -lpspkernel

psp-gcc -O0 -g3 -Wall -I. -IC:/pspsdk/psp/sdk/include -L. -LC:/pspsdk/psp/sdk/lib -D_PSP_FW_VERSION=150 -o raw_eboot.elf *.o -lpspdebug -lpspdisplay -lpspge -lpspctrl -lpspsdk -lc -lpspnet -lpspnet_inet -lpspnet_apctl -lpspnet_resolver -lpsputility -lpspuser -lpspkernel

psp-fixup-imports raw_eboot.elf
mksfo 'XMB_TITLE' PARAM.SFO
psp-strip raw_eboot.elf -o strip_eboot.elf
pack-pbp EBOOT.PBP PARAM.SFO NULL NULL NULL NULL NULL strip_eboot.elf NULL
  1. Compile all the files into objects
  2. Link all objects and libraries into an .elf file
  3. Create a .SFO file for XMB use
  4. Strip the .elf file to shrink size and reduce redundancy
  5. Create an EBOOT.PBP file, which houses additional XMB information (see pack-pbp --help)

TestingEdit

Method 1

  1. Navigate to where EBOOT.PBP is
  2. Launch PPSSPP via command line or terminal, supplying an absolute path

Method 2

  1. Open PPSSPP
  2. Drag the EBOOT.PBP into PPSSPP

Method 3 (Windows only?)

  1. Drag EBOOT.PBP on the PPSSPP executable

Method 4

  1. Open PPSSPP
  2. Go to file
  3. Go to load
  4. Find and oepn the EBOOT.PBP

Working ExampleEdit