Luna
Fully customizable PSP shell

- 0. Introduction

- 0.1. Version History

- 1. Installation

- 1.1. Replacing XMB

- 2. Usage

- 2.1. Simple Customization
- 2.2. Included Skins

- 2.2.1. Simple
- 2.2.2. Icons
- 2.2.3. Colours
- 2.2.4. Zelda

- 3. Creating Skins

- 3.1. Skin Design Guidelines

- 4. Skin API

- 4.1. System Control
- 4.2. Files
- 4.3. Skins
- 4.4. Math
- 4.5. 3D
- 4.6. Images
- 4.7. Bitmap Fonts
- 4.8. MP3 Playback
- 4.9. Sound Effects

- 5. Info

- 5.1. License

0. Introduction
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.
0.1. Version History

- 74720 (Jan 31 2009): First release.

1. Installation
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.
1.1. Replacing XMB
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.
2. Usage
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.
2.1. Simple Customization
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.
2.2. Included Skins
2.2.1. Simple
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.
2.2.2. Icons
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. <_<)
2.2.3. Colours
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.)
2.2.4. Zelda
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.
3. Creating Skins
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.
3.1. Skin Design Guidelines
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.)

4. Skin API
In addition to these, some useful functions are defined and documented in utils.lua.
4.1. System Control
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.

4.2. Files
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.)

4.3. Skins
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"))`

4.4. Math
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.

4.5. 3D
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.

4.6. Images
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.)

4.7. Bitmap Fonts
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.

4.8. MP3 Playback
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.

4.9. Sound Effects
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.

5. Info
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.
5.1. License

    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.