たいへんかく

Homebrew, mods, plugins, and more

View project on GitHub

The next step

taiHENkaku is everything you know and love about HENkaku and more. Just like before, you can run your favorite homebrew games, emulators, and tweaks. However, this update brings the addition of plugins as well. Based off of technology similar to Cydia Substrate (specifically thanks to substitute), taiHEN allows developers to write hooks and patches to remix games, system applications, and the kernel.

Getting started

We are still ironing out some issues but you can try an early beta today! Just visit http://beta.henkaku.xyz/ from your PS Vita device to try out the new release. If you wish to wait or go back to the last stable release, just reboot your console and visit the old site, https://henkaku.xyz/.

Please note that currently, the beta is only recommended for developers who wish to test out their own taiHEN plugins. We still recommend the last stable release for everyone else because while we rewrote HENkaku from the ground up to use the new patch system, it does not introduce any new features for the user (and does introduce new instabilities). We hope that developers will use taiHEN to write new mods that would work with taiHENkaku and any future exploits!

Developers

Calling all developers! We need you to create some awesome new tweaks using taiHEN! With the new hooking system, the possibilities are endless: cheats, UI tweaks, screen casting, and more. But we need your help is making it all a reality. Get started by downloading the latest kernel enabled toolchain and the taiHEN library. Check out the API documentation for taiHEN. Join us in the #vitasdk chatroom in FreeNode IRC for help and support.

Building

We will add build instructions for plugins here in the future. Right now, just check out how the HENkaku plugin does it or chat with us in #vitasdk.

Configuration

The configuration that determines the plugins to load and the load order can be found in ux0:tai/config.txt. The format is very simple and self explanatory.

# ignored line starting with #
# Kernel plugins are started with taiHEN and are in this section
*KERNEL
ux0:app/MLCL00001/henkaku.skprx
ux0:path/to/another.skprx
ux0:tai/plugin3.skprx
ux0:data/tai/plugin4.skprx
ux0:data/tai/plugin5.skprx
# titleid for SceSettings
*NPXS10015
ux0:app/MLCL00001/henkaku.suprx
ux0:data/tai/some_settings_plugin.suprx
# titleid for Package Installer
*NPXS10031
ux0:path/to/some_pkg_installer_plgin.suprx
# titleid for SceShell is special (does not follow the XXXXYYYYY format)
*main
ux0:app/MLCL00001/henkaku.skprx
ux0:data/tai/shell_plgin.skprx

The key things to note are

  1. # begins a comment, * begins a section, and any other character begins a path.
  2. KERNEL is a special section name denoting to load a kernel plugin when taiHEN is started up. All other section names are the title id of the application/game in which to load the plugin at startup. Note that SceShell has a special title id of main.
  3. In each section, there is a list of plugin paths that will be loaded in order. Paths can be anywhere but it is recommended that plugins reside in ux0:tai or ux0:data/tai. It is valid to have one plugin in multiple sections but the developer must ensure that the plugin knows which application it is loaded in if it needs to do things differently.

Examples

We hope you will read the API documentation to see the full power of taiHEN and the HENkaku source to see an example usage. However, below are some quick usage examples that demonstrate the power of taiHEN.

Do something on startup

Configure the following user plugin to load on an application named "AppName" with the title id "ABCD01234".

// handle to our hook
static tai_hook_ref_t app_start_ref;
// our hook for app entry
int hook_app_start(SceSize argc, const void *args) {
  printf("hello world!\n");
  return TAI_CONTINUE(int, app_start_ref, argc, args);
}
// our own plugin entry
int module_start(SceSize argc, const void *args) {
  taiHookFunctionExport(&app_start_ref,  // Output a reference
                        "AppName",       // Name of module being hooked
                        TAI_ANY_LIBRARY, // If there's multiple libs exporting this
                        0x935CD196,      // Special NID specifying `module_start`
                        hook_app_start); // Name of the hook function
  return SCE_KERNEL_START_SUCCESS;
}

We can build this as myplugin.suprx and add it to ux0:tai/config.txt under the section *ABCD01234 and it will be loaded when ABCD01234 is started and insert the hook.

Logging Filesystem

The following example will log all file opens from applications. Compile it as kernellog.skprx and add to *KERNEL section in ux0:tai/config.txt.

// handle to our hook
static tai_hook_ref_t open_ref;
// this function is in kernel space
SceUID hook_user_open(const char *path, int flags, SceMode mode, void *args) {
  char k_path[256];
  SceUID fd;
  fd = TAI_CONTINUE(SceUID, open_ref, path, flags, mode, args);
  // we need to copy the user pointer to kernel space
  sceKernelStrncpyUserToKernel(k_path, (uintptr_t)path, 256);
  // do some logging
  printf("opening: %s, res: %x\n", k_path, fd);
  return fd;
}
// plugin entry
int module_start(SceSize argc, const void *args) {
  taiHookFunctionExportForKernel(KERNEL_PID,      // Kernel process
                                 &open_ref,       // Output a reference
                                 "SceIofilemgr",  // Name of module being hooked
                                 TAI_ANY_LIBRARY, // If there's multiple libs exporting this
                                 0xCC67B6FD,      // NID specifying `sceIoOpen`
                                 hook_user_open); // Name of the hook function
  return SCE_KERNEL_START_SUCCESS;
}

Chain of hooks

Consider one kernel plugin with the code above. Now consider a second kernel plugin as follows.

// handle to our hook
static tai_hook_ref_t another_open_ref;
// this function is in kernel space
SceUID hook_user_open_differently(const char *path, int flags, SceMode mode, void *args) {
  char k_path[256];
  SceUID fd;
  fd = TAI_CONTINUE(SceUID, another_open_ref, path, flags, mode, args);
  // we need to copy the user pointer to kernel space
  sceKernelStrncpyUserToKernel(k_path, (uintptr_t)path, 256);
  // filter out certain paths
  if (strcmp(k_path, "ux0:hidden_file.bin") == 0 && fd >= 0) {
    sceIoClose(fd); // close the handle
    fd = SCE_KERNEL_ERROR_NOENT;
  }
  return fd;
}
// another plugin entry
int module_start(SceSize argc, const void *args) {
  taiHookFunctionExportForKernel(KERNEL_PID,                  // Kernel process
                                 &another_open_ref,           // Output a reference
                                 "SceIofilemgr",              // Name of module being hooked
                                 TAI_ANY_LIBRARY,             // If there's multiple libs exporting this
                                 0xCC67B6FD,                  // NID specifying `sceIoOpen`
                                 hook_user_open_differently); // Name of the hook function
  return SCE_KERNEL_START_SUCCESS;
}

Now we have both filesystem filtering and logging.

Enabling dynarec

This plugin will be loaded in kernel and changes the return value of a function that does a check to enable dynarec.

// handle to our hook
static tai_hook_ref_t some_sysroot_check_hook;
// patch function
static int some_sysroot_check_patched(void) {
  // It is important that we always call `TAI_CONTINUE` regardless if we need 
  // the return value or not. This ensures other hooks in the chain can run!
  TAI_CONTINUE(int, some_sysroot_check_hook);
  return 1;
}
// plugin entry
int module_start(SceSize argc, const void *args) {
  taiHookFunctionExportForKernel(KERNEL_PID,                  // Kernel process
                                 &some_sysroot_check_hook,    // Output a reference
                                 "SceSysmem",                 // Name of module being hooked
                                 0x3691DA45,                  // NID specifying `SceSysrootForKernel`
                                 0xF8769E86,                  // NID of the export function we patch
                                 some_sysroot_check_patched); // Name of the hook function
  return SCE_KERNEL_START_SUCCESS;
}

Local file logging

The above examples are all global hooks: every call regardless of origin will be hooked. You can also insert local hooks: a library import from a module. In this example, we have a user plugin loaded with "AppName" that only logs sceIoOpen calls from that application.

// handle to our hook
static tai_hook_ref_t local_open_hook;
// this function is in user space now
SceUID hook_local_open(const char *path, int flags, SceMode mode) {
  SceUID fd;
  fd = TAI_CONTINUE(SceUID, local_open_hook, path, flags, mode);
  printf("open in AppName: %s, ret: %x", path, fd);
  return fd;
}
// our own plugin entry
int module_start(SceSize argc, const void *args) {
  taiHookFunctionImport(&local_open_hook,  // Output a reference
                        "AppName",         // Name of module being hooked
                        0xCAE9ACE6,        // NID specifying `SceLibKernel`, a wrapper library
                        0x6C60AC61,        // NID specifying `sceIoOpen`
                        hook_local_open);  // Name of the hook function
  return SCE_KERNEL_START_SUCCESS;
}