taiHEN is a custom firmware (CFW) framework for PS Vita™ and implements a configuration to help packagers and users control which modules or plugins are loaded and when. taihen-parser provides a convenient C API for interacting with these configuration files to help developers write supporting tools for taiHEN. taihen-parser provides both a lexer and parser API for configuration files.
The problem with CFW of a previous era was that it was one person's imagination of a custom firmware. Average developer X could not easily replace the in-game menu within the provided CFW. Likewise, average user Y could not strip out features they did not like. Here, taiHEN provides a solution by providing a configuration file where CFW can be defined as a set of modules and plugins.
Person X may like live-area mod1 and person Y may like live-area mod2. No longer do these two need to chose between CFW A and CFW B that implements mod1 and mod2, respectively. Instead, they can modify this configuration from their favourite CFW to use whichever mod they prefer. This architecture promotes the custom in custom firmware by encouraging developers to move away from huge monolithic CFW of the past and help nurture an open, compatible and user orientated custom firmware experience.
taiHEN employs a text based format for configuring automatic loading of modules. The configuration format is a UTF-8 text file that utilises line seperation to ease parsing and human readability. Each line must be exclusive to one of four types:
- An empty line
- A comment
- Section
- Module path
Each line can be at most CONFIG_MAX_LINE_LENGTH
characters wide, and trailing/leading whitespace is permitted.
The config lexer produces the following tokens:
CONFIG_START_TOKEN
CONFIG_END_TOKEN
CONFIG_COMMENT_TOKEN
CONFIG_SECTION_TOKEN
CONFIG_SECTION_HALT_TOKEN
CONFIG_SECTION_NAME_TOKEN
CONFIG_PATH_TOKEN
A valid configuration format should obey the grammar:
config ::= CONFIG_START_TOKEN (CONFIG_COMMENT_TOKEN | section)* CONFIG_END_TOKEN
section ::= CONFIG_SECTION_TOKEN CONFIG_SECTION_HALT_TOKEN? CONFIG_SECTION_NAME_TOKEN ('\n' | EOF) path*
path ::= CONFIG_PATH_TOKEN ('\n' | EOF)
A section in the configuration file functions as a filter and controller for CFW module loading.
Each section begins with a *
and can optionally be followed with a !
to mark the section as a halt point (see further below). After these tokens, the rest of the line a UTF-8 name for the section.
A section of the same name may appear in the file multiple times. This functionality is intended to allow users to take advantage of taiHEN's load ordering policy.
A section can optionally have the halt point token !
following the section token *
in the configuration file. This token instructs the parser to stop further parsing of the file if the section name is within context. See the examples below for a visual worked case on this feature.
There are currently two reserved names for sections:
ALL
- A catch all user-mode section that will load the modules it contains for every user-mode process.KERNEL
- A section that loads resident kernel modules on the start of taiHEN.
Using the halt point !
on these sections results in undefined behaviour.
This API currently offers no guarantee of stability. Please remember that it may change drastically upon any future versions of taiHEN. taiHEN's configuration parser exposes it's lexer algorithm to assist in development of supporting tools. Please consult the header files for documentation.
Below is an example of a very simple configuration:
# example simple config
*ALL
ux0:/plugins/my_plugin.suprx
ux0:/plugins/my_plugin2.suprx
This example consists of a single section ALL
. Which means that every game/application/homebrew that is launched will have both my_plugin.suprx
and my_plugin2.suprx
loaded in that process space and in order.
More precise functionality may be required for certain homebrew. Perhaps you wish to package your own CFW, in which case you may create a complex configuration as shown below:
# hello this is a comment. this line is ignored
# this line also
# this too, whitespace at the start of a line is OK
*COOL_GAME
# i'm within a section, woo!
ux0:/coolgame/plugin.suprx
# indentation is ok with me
ux0:/coolgame/plugin2.suprx
# spaces within path is ok
ux0:/really cool/I haVe spaces and caps/plugin3.suprx
# next section
*ALL
# i'm a special section!
# i'm always included... usually
ux0:/plugins/ingamemusic.suprx
*KERNEL
# i'm a special section also!
# my plugins are loaded to kernel memory as resident modules
ux0:/taihen/henkaku.skprx
ux0:/psphacks.skprx
*COOL_GAME
# this section again?! this is ok! this is a way packagers
# can take advantage of load order.
ux0:/coolgame/idependoningamemusic.suprx
*!COOL_GAME2
# what is the '!' for?
# the '!' prevents further parsing
# this would make more sense to put at the start if you want to
# blacklist certain modules
# look, nothing to load!
*ALL
ux0:/plugins/ibreak_coolgame2.suprx
# emojis?
ux0:/🤔/🦄/👻/🎃.suprx
Much more complex, but really I expect even more complexity when real CFW components come around. As mentioned previously, parsing occurs from top to bottom, identical to load order. When parsing, a section context is selected. In the case of taiHEN, this context is a title id such as MLCL00001
for our molecularShell homebrew. In this case, lets assume for ease that we have selected COOL_GAME
and it is a user-mode process.
Comments are ignored, so lets continue until we reach the first section: COOL_GAME
. Since our selected section matches this first section, the paths below are loaded until a new section is reached.
ux0:/coolgame/plugin.suprx
ux0:/coolgame/plugin2.suprx
ux0:/really cool/I haVe spaces and caps/plugin3.suprx
Then we reach a new section ALL
. As mentioned above, ALL
is a special reserved section name that matches every user-mode process. So our loaded module list grows:
ux0:/coolgame/plugin.suprx
ux0:/coolgame/plugin2.suprx
ux0:/really cool/I haVe spaces and caps/plugin3.suprx
ux0:/plugins/ingamemusic.suprx
Next section we reach is the special section KERNEL
. This is not processed within our context we so continue until we reach the next section: COOL_GAME
. We have already had this section before, but we multiple sections are allowed to take advantage of taiHEN's module load ordering. This is extremely useful when you have dependencies between plugins/modules that need resolved. In this example we have idependoningamemusic.suprx
which must be loaded after ingamemusic.suprx
.
Our load list now looks like:
ux0:/coolgame/plugin.suprx
ux0:/coolgame/plugin2.suprx
ux0:/really cool/I haVe spaces and caps/plugin3.suprx
ux0:/plugins/ingamemusic.suprx
ux0:/coolgame/idependoningamemusic.suprx
Next section is COOL_GAME2
which does not match our section context. This section has a halt point !
but we ignore it in this case because we do not much it.
Lastly, we have the final section ALL
again, which completes our load list:
ux0:/coolgame/plugin.suprx
ux0:/coolgame/plugin2.suprx
ux0:/really cool/I haVe spaces and caps/plugin3.suprx
ux0:/plugins/ingamemusic.suprx
ux0:/coolgame/idependoningamemusic.suprx
ux0:/plugins/ibreak_coolgame2.suprx
ux0:/🤔/🦄/👻/🎃.suprx
NOTE: I don't know conclusively if the Vita filesystem supports emojis. Don't use them...
Following the same logic as above, we will walk through the configuration as COOL_GAME2
context.
First section is COOL_GAME
, not a match so we skip it.
Second section is ALL
, so we load modules from it:
ux0:/plugins/ingamemusic.suprx
Third section is KERNEL
, so we skip it.
Fourth section is COOL_GAME
again so we skip it.
Fifth section is COOL_GAME2
so we process it. This time we have a halt point so this will be the last section we process. Remember, the halt point !
stops any further parsing. This section however has no modules, so nothing is loaded. A section with no modules is OK. In this case, the following ALL
section breaks COOL_GAME2
in our hypothetical world. By using the halt point correctly, a CFW packager can maximise compatibility whilst maintaining load ordering.
Our final module loading list for COOL_GAME2
:
ux0:/plugins/ingamemusic.suprx
To build taihen-parser, you require CMake to generate the appropriate build scripts. From within the repository directory:
$ mkdir build && cd build
$ cmake ..
$ make
To build the included tests you require the boost unit_test_framework
installed. Then instead use:
$ mkdir build && cd build
$ cmake -DTEST=ON ..
$ make
To install to a specified location define CMAKE_INSTALL_PREFIX
:
$ mkdir build && cd build
$ cmake -DCMAKE_INSTALL_PREFIX=/my/install/location ..
$ make
$ make install
Team molecule for HENkaku, Yifan Lu for taiHEN and xyz for immense support of the vitasdk.
taihen-parser is licensed under the terms of the MIT license which can be read in the LICENSE
file in the root of the repository.
(C) 2016 David "Davee" Morgan