Luna
Fully customizable PSP shell
Luna is a homebrew PSP shell designed to replace the XMB and provide
total customizability via Lua scripting. It is intended to serve as
an alternative to risky and difficult custom theme hacks, with the
goal that skins remain compatible with newer versions.
- 74720 (Jan 31 2009): First release.
Luna is installed just like any other homebrew: Copy the
luna
directory into /PSP/GAME on your memory stick. Of
course you need
hacked firmware.
You will also want to copy the
__SCE__webbrowser
directory into /PSP/GAME. This program is a simple stub to display
the built-in web browser. It can be called from any application.
Since its name begins with __SCE__ it does not show up in the menus.
It is used when launching the browser from Luna.
A plugin is provided to allow you to have Luna completely
replace XMB. Copy luna.prx into /seplugins on the memory stick,
and add the line:
ms0:/seplugins/luna.prx 1
to the vsh.txt file. This will start Luna any time XMB would
normally start. You can bypass it by holding the L trigger. It
is also possible to run XMB from within Luna.
Luna leaves the entire user interface up to the skin, so different
skins may behave differently. These instructions apply to the
included skins. (Skin designers: follow these conventions when
possible. Consistency is a good thing.)
Press L or R to cycle through the menus.
When Luna starts you should see a menu listing all programs
installed in /PSP/GAME, in alphabetical order. Use the directional
pad to select one. Luna respects the firmware O/X swap setting, so
use whichever button you've set to be Accept to choose one.
The next menu lists the programs built into the firmware, such as
the web browser and XMB. Most of these are not implemented yet.
The third menu lets you select a file from your memory stick. You
can select a PBP or PRX file to execute it (currently only
user-mode PRXes are supported), or an MP3 file to play it (replacing
the current background music if any). Currently MP3s may not play if
they have ID3 tags or are encoded at 48khz. (There are no plans to
turn Luna into a full-fledged music player; use
LightMP3 for that.)
The last menu is not really a menu at all; it displays the
contents of /PSP/GAME/Luna/notes.txt, for convenient access to
useful information. This is a good place to put a schedule, list of
phone numbers, etc.
Most skins will use wallpaper.bmp and startup.mp3 (in
/PSP/GAME/Luna) as a background image and startup sound. These
can easily be replaced. wallpaper.bmp must be a 24-bit 480x272
bitmap image, and startup.mp3 must be a 44khz MP3 file with no
ID3 tags. You can also edit boot.lua to change the paths to
these files, make them randomly selected, etc. The startup sound
will play in the background while Luna is running, so it can be
as long as you want.
Don't be afraid to look at boot.lua, where the user settings are
stored. This is where you can enable some useful things, such as
holding certain button combinations at startup to boot a program
before the skin has even loaded, selecting a random skin at
startup, etc.

This is a basic skin, good for basing your own skins on. It
uses the default font to display text-based menus, using the
default wallpaper and startup sound, with sound effects.

This is a variation on the Simple skin in which the built-in
programs menu is replaced with a 3D icon ring. Icons are
loaded from the
icons subdirectory and named after
the programs' IDs. The ring rotates so that the selected
icon is displayed at the front. The icons are scaled so that
the selected icon is displayed at a 1:1 ratio (e.g. one
pixel in the image occupies exactly one pixel on the screen)
regardless of the icons' dimensions. (However, all icons must
have the same dimensions.) It should work with any number
of icons.
It also displays an analog clock. By editing some variables
near the beginning of script.lua, you can move or disable
the clock, and switch to a 2D icon ring or a grid.
The other menus are not changed, however a future version
will replace the memory stick programs menu with an icon
ring as well, using the icons built into each program.
Similar may be done for the file selector, using icons based
on the file type and/or thumbnails of image files.
(If you can make some half-decent icons to replace these,
I'd very much appreciate it. <_<)

This skin is like a cross between a trippy visual and a
Hollywood hacker desktop, complete with cool background
music and sound effects. A spinning RGB cube is displayed
on a background of spinning colours. Random numbers scroll
up the left side and some interesting-looking debug output
displays on the right, with hex strings scrolling by the top
of the screen.
The colours of the text also cycle with the background, and
some pieces are drawn using various XOR effects.
Near the bottom is a small piece of text where the user can
add in their name and phone number, so that anyone who finds
the PSP can return it to them.
The analog stick can be used to adjust the saturation and
value of the background colour.
(Note that the firmware version displayed in the scroller is
hardcoded in this version.)

This skin uses graphics and sound effects from various Zelda
games, and mimics the inventory menus in Ocarina of Time.
Battery life is displayed as hearts (with the double-defense
outline when charging), and free disk space as magic.
This skin is not finished - some graphics still need to be
extracted and implemented, and Zelda-style icons need to be
made. Unfortunately, I suck at graphics, so I'll probably
need to find someone to make those.
Luna's skins are in fact Lua scripts that are responsible for
drawing the interface, responding to input, launching programs, etc.
Each skin has a directory in /PSP/GAME/Luna/skins. The file
script.lua is executed when the skin is loaded. It should define at
least one of the functions FrameBegin and FrameEnd.
FrameBegin will be called at the beginning of each frame (just after
sceGuStart()), and FrameEnd will be called at the end (after
sceGuFinish() and sceGuSync()). Typically, FrameBegin is where the
interface will be drawn and the user input will be handled, and
FrameEnd is where any drawing onto the framebuffer will be done
(a good way to implement text-based menus and status displays).
Nearly all of the functions in pspgu.h and pspgum.h are available to
skins (although those that require pointers to matrices or vectors
are not usable at the moment), as well as some convenience
functions to make common tasks easier. Look at the included skins
for examples.
(Note that
LoadGU() must be called before 3D
functions can be used.)
For information on coding in Lua, see
the manual
and the
tutorial
directory. The website also has a
live demo page where
you can test simple scripts.
Please follow these guidelines to ensure your skins can be
enjoyed by all users.
- Use the accept and cancel pseudo-buttons
where appropriate.
- Use the default startup sound and wallpaper if the skin
does not provide its own.
- Respect the user settings (such as EnableSound)
defined in boot.lua.
- Avoid using a lot of images that take a long time to
load and/or use up a lot of memory. Keep in mind that not
all memory sticks are the same speed and the original PSP
model has less memory than newer ones.
- Do not indicate which menu item is selected by simply
changing its colour, as this can be confusing when a menu
has only two items. (This is not an issue if the colour is
animated.)
- Do not rely on any aspects of the user's configuration
(e.g. which plugins or games are installed, their firmware
version, how many built-in programs the firmware has, the
size of their memory stick, etc) unless absolutely
necessary.
- Avoid excessive writing to files, as flash memory is
limited in the number of times it can be rewritten.
- Do not distribute compiled or obfuscated Lua scripts.
This makes it difficult to customize the skin, which defeats
the purpose of Luna. (Future versions will automatically
compile the scripts and store them in a cache when they are
executed, so any speed improvement would be moot.)
In addition to these, some useful functions are defined and
documented in utils.lua.
These functions allow the script to do useful things such as
launching programs and reading user input.
- ExitProgram()
- Used for debugging to return to the loader/XMB.
- LaunchProgram(path)
- Takes a path to a .PBP or .PRX file, and will attempt to
execute it. PRX programs will run in the background.
Kernel-mode PRXes are currently not supported.
When a PBP is successfully executed, Luna will exit
automatically. When a PRX is successfully executed,
LaunchProgram() returns true. On failure, an integer error
code from the kernel is returned (0 means the module used
to launch programs was not successfully loaded).
Bug: Some errors are not raised until Luna has been unloaded
from memory, so the kernel will attempt to display them in
XMB instead. If you have the XMB replacement plugin, then it
will just restart Luna without displaying the error. If this
happens, try again and hold L to bypass the plugin to see
the error code.
-
RunBuiltinApp(id)
- Given the ID of a built-in program (from
GetBuiltinApps()), will attempt
to launch the program.
Returns true on success, nil and an error message on failure.
- GetAnalogDeadZone()
- Returns the current analog nub deadzone. If either axis is
within the range (127 - DeadZone) <= n <= (127 +
DeadZone), it will read as 127.
- SetAnalogDeadZone(range)
- Sets the analog nub deadzone (which is an integer).
- GetButtons(prev)
- Retrieves the current or previous frame's button state. If
prev is true, the previous frame's state is returned;
by comparing it to the current state, you can determine if a
button was just pressed or released.
The button state is a table containing boolean values whose
keys are the button names: select, unk1, unk2, start, up,
right, down, left, l, r, unk3, unk4, triangle, circle,
cross, square, home. (unk* represent unknown flags in
the button data.) home is set whenever the "exit
program" prompt is visible.
The table also contains the pseudo-buttons accept,
cancel and any. accept and cancel
represent either X or O depending which configuration the
user has selected in the firmware. (Japanese systems have O
as accept by default; many custom firmwares also allow this
setting to be changed.) These should always be used in place
of cross and circle for activating, confirming,
or cancelling an action or menu item, to ensure the user's
settings are respected.
any is set when any button is pressed.
- GetLocalTime()
- Retrieves the current local time. Returns a table
containing the integer values year, month, day, hour,
minute, second, microsecond. 4-digit years are used.
- GetTime(TZ)
- Same as GetLocalTime(), but for an arbitrary time zone.
TZ specifies the offset from UTC in minutes.
- IsLeapYear(year)
- Returns true if the specified year is a leap year, false
if not.
- NumDaysInMonth(year, month)
- Returns the number of days in the specified month.
- GetBattery()
- Retrieves information about the battery. Returns a table
containing the following keys:
- percent (number, percent remaining)
- time (number, minutes remaining)
- status (number; -1=not installed, 0=low, 1=OK)
- charging (boolean)
- temperatue (number)
- voltage (number, measured in volts)
- GetCPUSpeed()
- Retrieves CPU speed information. Returns a table
containing the keys cpu and bus, which are
their current speeds in mhz.
- ShowError(message)
- Displays an error message. Execution is halted until the
user presses Start. They can also press Select to exit to
XMB.
These functions provide convenient methods to read the directory
structure from a device.
- GetInstalledPrograms()
- Returns a table listing all programs installed in /PSP/GAME.
The keys are the name of the directory, and the values are
the name of the program specified in its EBOOT.
Returns nil on failure; an empty table if no programs are
found.
The table is not sorted. Directory names beginning with
"__SCE__" or beginning or ending with '%' are ignored.
-
GetBuiltinApps()
- Returns a table listing all built-in programs, such as the
web browser, game sharing, etc. The keys are a string
identifying the program (which can be passed to
RunBuiltinApp()), and the
values are the default display name. (e.g. "web_browser"
=> "Web Browser"). Returns nil on failure.
- IsDirectory(path)
- Given a path to a file, returns true if it is a directory,
or false if it is not.
- ReadDirectory(path, prefix)
- Reads the directory specified by path, returning a
table of the files with integer keys. The "." and ".."
directories are omitted. If prefix is true,
directories will have '/' prepended to their names.
On failure, returns nil and an error message.
- GetDiskFreePercent()
- Returns the percentage (in range 0-1, e.g. 0.24 = 24%) of
free space on the Memory Stick. (Due to bugs in the PSP Lua
library, the actual number of bytes cannot be returned.)
These functions are used to load skins and their resources.
- LoadSkin(name)
- Loads the specified skin. Note that it is not currently
safe to load another skin once one is loaded (i.e. this
should only be used in boot.lua). Returns true on success,
false on failure.
- ProcessSkinFilePath(path)
- Performs the following substitutions on path and
returns the result:
- '$' is replaced with the path to the current skin
(relative to Luna's program directory).
This is only necessary for built-in Lua functions such as
dofile that work with files, as Luna's API performs
this automatically on paths passed to it. This can be used
to load additional scripts for a skin without depending on
it having a specific name; e.g.:
dofile(ProcessSkinFilePath("$/menus.lua"))
These functions perform some math operations not natively
supported in Lua. Note that the PSP Lua interpreter will crash
when given large numbers (over 0x7FFFFFBF) in some places, so
be careful using these functions.
- SHIFT(n, s)
- Returns n shifted s places left. s
may be negative to shift right.
- AND(a, b)
Returns a AND b.
- OR(a, b)
Returns a OR b.
- XOR(a, b)
Returns a XOR b.
- NOT(n)
Returns the bitwise inverse of n.
These functions are used to create and load arrays of vertices,
and provide some convenience wrappers around GU functions.
Note that all of the functions in pspgu.h and pspgum.h are
available; however, those that require a pointer to a matrix or
vector are not currently usable. Also, those that take a colour
have it split into the individual R, G, B and A components (in
that order) to work around math bugs in the Lua engine.
-
LoadGU()
- Defines the sceGU and sceGUM functions and constants. This
must be called before using those functions.
- AllocVertices(n)
- Allocates a buffer to hold n vertices. Returns a
pointer (userdata) to the buffer on success; nil and an
error message on failure.
The initial contents of the buffer are undefined.
- FreeVertices(buf, ...)
- Takes one or more pointers to vertex buffers and frees them.
- SetVertices(buf, idx, ...)
- Sets the properties of vertices in the specified buffer.
idx specifies the zero-based index into the buffer to
modify. After idx comes one or more tables, each
specifying a vertex's properties. idx is incremented
for each table. Indices outside of the buffer are ignored.
Each table may contain any of the following keys (others are
ignored). Any property that is not specified is left at its
previous value.
- u, v: Texture coordinates.
- r, g, b, a: Colour.
- x, y, z: Coordinates.
- i: Specifies a new index to modify (i.e.
idx = i if it is present). Ignored if nil.
- LoadVerticesFromFile(buf, file)
- Loads vertices from the specified file into the specified
vertex buffer, at index zero.
Vertex files must start with "#vtx" followed by the number
of vertices as the first line, then vertices are defined one
per line in format: x y z r g b a u v
If a line does not specify all fields the rest are set to
zero. Blank lines and lines beginning with '#' (besides the
first) are ignored.
- AllocVerticesFromFile(file)
- Allocates a vertex buffer and reads the contents of the
specified file into it. On success, returns the buffer and
the number of vertices loaded. On failure, returns nil and
an error message.
- RenderVertices(buf, idx, num, rmode, tmode)
- Renders vertices from the specified buffer. idx
specifies the index of the first vertex (defaults to zero).
num specifies the number of vertices (defaults to all
from idx to the end). rmode specifies the
render mode (defaults to GU_TRIANGLES). tmode
specifies the transformation mode (defaults to
GU_TRANSFORM_3D).
- Translate(x, y, z)
- Performs a translation on the current matrix.
- Rotate(x, y, z)
- Performs a rotation on the current matrix. x, y and
z are in radians.
- RotateDegrees(x, y, z)
- Performs a rotation on the current matrix. x, y and
z are in degrees.
- ResetMatrices()
- Resets the projection, view and model matrices to the
default state they are set to when boot.lua is executed.
Tip: If you need to reset the model matrix multiple times,
it may be faster to use sceGumPushMatrix() and
sceGumPopMatrix(), e.g.:
ResetMatrices()
sceGumPushMatrix()
for i=1,10 do
DrawCoolThing(i)
sceGumPopMatrix()
sceGumPushMatrix()
end
sceGumPopMatrix()
- UseTexture(img)
- Enables texturing and selects the specified texture.
img is userdata returned from e.g.
LoadImage().
- GetFrameBuf()
- Returns a pointer to the current frame buffer. This is
compatible with the pointers used by the image and bitmap
font functions, so it can be used in FrameEnd() to draw on
the frame buffer.
- GetPrevFrameBuf()
- Returns a pointer to the previous frame's buffer.
These functions are used to load and manipulate images and VRAM.
- CreateImage(width, height)
- Creates an image. The initial contents of the image are
undefined. Returns a pointer to the image on success; nil
and an error message on failure.
- LoadImage(file)
- Loads an image from the specified file and returns a
pointer to it on success; nil and an error message on
failure. Currently only 24- and 32-bit BMP images are
supported.
- DuplicateImage(img)
- Duplicates img. Returns a pointer to the copy on
success; nil and an error message on failure.
- DeleteImage(img, ...)
- Takes one or more pointers to images and frees them.
- Image_GetInfo(img)
- Returns the image's width and height, and a pointer to its
pixel data.
- Image_MoveToVRAM(img)
- Attempts to move the image's pixel data into VRAM. Returns
true on success, false on failure. If successful, old
pointers to the pixel data will no longer be valid.
Moving textures to VRAM can significantly increase rendering
speed.
- Image_RemoveFromVRAM(img)
- Attempts to move the image's pixel data into main RAM.
Returns true on success, false on failure. If successful,
old pointers to the pixel data will no longer be valid.
There is no guarantee that it will be moved back to where it
was originally.
- Image_Swizzle(img)
- "Swizzles" (optimizes) the image. Swizzling texture images
can significantly increase rendering speed. Note that while
pointers to the pixel data remain valid, the data is
effectively scrambled. This operation is only valid for
images whose raw size is at least 512 bytes.
- Image_SwizzleToVRAM(img)
- Attempts to swizzle the image and move it to VRAM
simultaneously. Returns true on success, false on failure.
If successful, old pointers to the pixel data will no longer
be valid. On failure, the data is not changed.
- Image_Clear(img, r, g, b, a)
- Clears all pixels to the specified colour.
- Image_Copy(dst, src)
- Copies all pixels from dst to src. Images
must be the same dimensions.
- Image_Flush(img)
- Flushes an image from the data cache. Use this before
rendering an image that has been modified if it is not in
VRAM. (This function has no effect on images that are in
VRAM, as they are not cached.)
These functions are used to load and render bitmap fonts.
Bitmap fonts are stored as a .bmp and .dat file. The .dat file
contains two lines. The first specifies information about the
font in the format:
UnknownChar,TranspColour,MainColour,MarkerColour,LineHeight,TabWidth
There should be no spaces between values, and colours should be
6-digit hex values (BBGGRR).
- UnknownChar: Specifies the index of the character to be
used in place of any characters not in the character set.
- TranspColour: Specifies the colour that will be used as
transparent.
- MainColour: Specifies the colour the characters are
drawn in.
- MarkerColour: Specifies the colour the character markers
are drawn in.
- LineHeight: Specifies the height of a line. Set this to
more than the height of a character to have space between
lines.
- TabWidth: Specifies the width of a tab in pixels.
The second line contains the list of characters in the order
they appear in the image.
The image contains each character in a line. At the top row,
a pixel (whose colour is specified by MarkerColour) is drawn
between each character to separate them. No markers should
appear before the first character or after the last.
- LoadBitmapFont(name)
- Loads a font. Takes the name of the font file, without
extension. Returns a pointer to the font object on
success, nil and an error message on failure.
- GetDefaultBitmapFont()
- Returns a pointer to the default font object. (Freeing
this would be a bad idea.)
- BitmapFont_EnableShadow(font, enable)
- Enables (if enable is true) or disables (if
enable is false) drop shadowing on the specified
font object.
- BitmapFont_EnableLineWrap(font, enable)
- Enables (if enable is true) or disables (if
enable is false) line wrapping on the specified
font object.
- BitmapFont_GetLineHeight(font)
- Returns the font's line height.
- BitmapFont_DrawString(img, font, x, y, mode, [r, g, b, a | nil], strings, ...)
- Draws text using a bitmap font. img specifies
the image to draw on. x and y specify the
coordinates to draw at. mode specifies the mode
to draw in:
- FONT_DRAW_NORMAL: Render normally.
- FONT_DRAW_XOR: Each pixel of colour
MainColour (specified in the font file)
is rendered by XORing the specified colour with
the pixel that it would be drawn over. Drop
shadows are disabled in this mode.
After mode can be either nil or four values
specifying the colour to draw in. If a colour is
specified, all pixels of colour MainColour will
be drawn in that colour.
After the colour comes the text to draw. This can be one
or more strings, or a table of strings with integer keys
starting at 1. A nil value in the table ends it, even if
more strings follow. A line break is automatically
inserted at the end of each string.
These functions are used to play MP3 files. Currently, only MP3
files at 44khz with no ID3 tags can be played, and only one MP3
can be loaded at a time. The main use of these functions is to
implement background music, either provided by the skin or from
an MP3 file selected from a menu.
MP3 playback uses the media engine, and is updated in a
background thread, so it does not have any significant impact on
performance.
To delete an MP3 object, so that another can be loaded, stop it
and then set it to nil.
- LoadMP3File(file)
Loads an MP3 file. Returns a pointer to the MP3 object
on success; nil and an error message on failure.
- MP3_Play(mp3)
Begins or resumes playing an MP3. (mp3 is an MP3
object from LoadMP3File().) Returns true on success,
false on failure.
- MP3_Pause(mp3, pause)
Pauses (if pause is true) or unpauses (if
pause is false) an MP3.
- MP3_Restart(mp3)
Resets the play position to the beginning. This does not
start playing the file if it is not already playing.
- MP3_Stop(mp3)
Stops playing an MP3.
- MP3_Loop(mp3, count)
Sets an MP3's loop count. Specify 0 for no looping, -1
to loop forever.
These functions are used to play WAV files. They work almost
identically to the MP3 versions; however, multiple sound effects
can be loaded and played at once. Sound effects are played on
the CPU.
- LoadSoundEffect(file)
Loads a sound effect. Returns a pointer to the sound
object on success; nil and an error message on failure.
- SoundEffect_Play(sound)
Begins or resumes playing a sound effect. (sound
is a sound object from LoadSoundEffect().) Returns true
on success, false on failure.
- SoundEffect_Pause(sound, pause)
Pauses (if pause is true) or unpauses (if
pause is false) a sound effect.
- SoundEffect_Restart(sound)
Resets the play position to the beginning. This does not
start playing the file if it is not already playing.
- SoundEffect_Stop(sound)
Stops playing a sound effect.
- SoundEffect_Loop(sound, count)
Sets a sound effect's loop count. Specify 0 for no
looping, -1 to loop forever.
Luna is written by
HyperHacker, who can
be emailed at
hyperhacker@gmail.com.
PSPLink was used
extensively during development and is also very useful for testing
skin scripts as it eliminates the need to copy them to the memory
stick.
Copyright (c) 2009, HyperHacker
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of HyperNova Software nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY HYPERHACKER ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL HYPERHACKER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.