* Only `io.open`; for open files, all but `file:setvbuf` and `file:lines`
* This is a custom compatibility module that uses `fs` functions. If there are differences compared to the original `io` implementation, please report them as issues.
*`package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries.
## Third-party libraries
rxi's [json.lua](https://github.com/rxi/json.lua) is pre-included. To use it, first load it:
```lua
local json = require("json")
```
---
## API reference
### Constants
#### GM9VER
The version such as `"v2.1.1-159-gff2cb913"`, the same string that is shown on the main screen.
#### SCRIPT
Path to the executed script, such as `"0:/gm9/luascripts/myscript.lua"`.
#### CURRDIR
Directory of the executed script, such as `"0:/gm9/luascripts"`.
#### GM9OUT
The value `"0:/gm9/out"`.
#### HAX
> [!WARNING]
> This needs checking if it's accurate.
One of three values:
* "ntrboot" if started from an ntrboot cart
* "sighax" if booted directly from a FIRM partition
* Empty string otherwise
#### NANDSIZE
Total size of SysNAND in bytes.
#### CONSOLE_TYPE
The string `"O3DS"` or `"N3DS"`.
#### IS_DEVKIT
`true` if the console is a developer unit.
---
### `ui` module
> [!NOTE]
> This assumes the default build is used, where the bottom screen is the main screen. If GodMode9 is compiled with `SWITCH_SCREENS=1`, then every instance where something appears on the bottom screen will actually be on the top screen and vice versa.
#### ui.echo
*`void ui.echo(string text)`
Display text on the bottom screen and wait for the user to press A.
* **Arguments**
*`text` - Text to show the user
#### ui.ask
*`bool ui.ask(string text)`
Prompt the user with a yes/no question.
* **Arguments**
*`text` - Text to ask the user
* **Returns:** `true` if the user accepts
#### ui.ask_hex
*`int ui.ask_hex(string text, int initial, int n_digits)`
Ask the user to input a hex number.
* **Arguments**
*`text` - Text to ask the user
*`initial` - Starting value
*`n_digits` - Amount of hex digits allowed
* **Returns:** the number the user entered, or `nil` if canceled
#### ui.ask_number
*`int ui.ask_number(string text, int initial)`
Ask the user to input a number.
* **Arguments**
*`text` - Text to ask the user
*`initial` - Starting value
* **Returns:** the number the user entered, or `nil` if canceled
#### ui.ask_text
*`string ui.ask_text(string prompt, string initial, int max_length)`
Ask the user to input text.
* **Arguments**
*`prompt` - Text to ask the user
*`initial` - Starting value
*`max_length` - Maximum length of the string
* **Returns:** the text the user entered, or `nil` if canceled
Ask the user to choose an option from a list. A maximum of 256 options are allowed.
* **Arguments**
*`prompt` - Text to ask the user
*`options` - Table of options
* **Returns:** index of selected option, or `nil` if canceled
#### ui.clear
*`void ui.clear()`
Clears the top screen.
#### ui.show_png
*`void ui.show_png(string path)`
Displays a PNG file on the top screen.
The image must not be larger than 400 pixels horizontal or 240 pixels vertical. If `SWITCH_SCREENS=1` is used, it must not be larger than 320 pixels horizontal.
* **Arguments**
*`path` - Path to PNG file
* **Throws**
*`"Could not read (file)"` - file does not exist or there was another read error
*`"Invalid PNG file"` - file is not a valid PNG
*`"PNG too large"` - too large horizontal or vertical, or an out-of-memory error
#### ui.show_text
*`void ui.show_text(string text)`
Displays text on the top screen.
* **Arguments**
*`text` - Text to show the user
#### ui.show_game_info
*`void ui.show_game_info(string path)`
Shows game file info. Accepts any files that include an SMDH, a DS game file, or GBA file.
* **Arguments**
*`path` - Path to game file (CIA, CCI/".3ds", SMDH, TMD, TIE (DSiWare export), Ticket, NDS, GBA)
* **Throws**
*`"ShowGameFileIcon failed on <path>"` - failed to get game info from path
#### ui.show_qr
*`void ui.show_qr(string text, string data)`
Displays a QR code on the top screen, and a prompt on the bottom screen, and waits for the user to press A.
* **Arguments**
*`text` - Text to show the user
*`data` - Data to encode into the QR code
* **Throws**
*`"could not allocate memory"` - out-of-memory error when attempting to generate the QR code
#### ui.show_text_viewer
*`void ui.show_text_viewer(string text)`
Display a scrollable text viewer.
* **Arguments**
*`text` - Text to display
* **Throws**
*`"text validation failed"` - given string contains invalid characters
*`"failed to run MemTextViewer"` - internal memory viewer error
#### ui.show_file_text_viewer
*`void ui.show_file_text_viewer(string path)`
Display a scrollable text viewer from a text file.
* **Arguments**
*`path` - Path to text file
* **Throws**
*`"could not allocate memory"` - out-of-memory error when attempting to create the text buffer
*`"text validation failed"` - text file contains invalid characters
*`"failed to run MemTextViewer"` - internal memory viewer error
#### ui.format_bytes
*`string ui.format_bytes(int bytes)`
Format a number with `Byte`, `kB`, `MB`, or `GB`.
> [!NOTE]
> This is affected by localization and may return different text if the language is not English.
*`"writing not allowed: <path>"` - user denied permission
*`"destination already exists on <src> -> <dst> and {overwrite=true} was not used"` - attempted to move an item over an existing one without using `overwrite`
*`"PathMoveCopy failed on <src> -> <dst>"` - error when moving, or user canceled
*`calc_sha` - Write `.sha` files containing a SHA-256 (default) or SHA-1 hash
*`sha1` - Use SHA-1
*`no_cancel` - Don’t allow user to cancel
*`silent` - Don’t show progress
*`overwrite` - Overwrite files
*`skip_all` - Skip existing files
*`append` - Append to the end of existing files instead of overwriting
*`recursive` - Copy directories recursively
* **Throws**
*`"writing not allowed: <path>"` - user denied permission
*`"requested directory copy without {recursive=true} on <src> -> <dst>"` - attempted to copy a directory without using `recursive`
*`"destination already exists on <src> -> <dst> and {overwrite=true} was not used"` - attempted to copy an item over an existing one without using `overwrite`
*`"PathMoveCopy failed on <src> -> <dst>"` - error when copying, or user canceled
#### fs.mkdir
*`void fs.mkdir(string path)`
Create a directory. This creates intermediate directories as required, so `fs.mkdir("a/b/c")` would create `a`, then `b`, then `c`.
* **Arguments**
*`path` - Directory to create
* **Throws**
*`"writing not allowed: <path>"` - user denied permission
*`"could not mkdir (<path>)"` - error when creating directories
#### fs.stat
*`array fs.stat(string path)`
Get information about a file or directory. The result is a stat table with these keys:
* **Arguments**
*`path` - Directory to stat
* **Returns:** A stat table with keys:
*`name` (string)
*`type` (string) - `"dir"` or `"file"`
*`size` (number)
*`read_only` (bool)
* **Throws**
*`"could not stat <path> (##)"` - error when attempting to stat item, with FatFs error number
#### fs.list_dir
*`array fs.list_dir(string path)`
Get the contents of a directory. The result is a list of stat tables with these keys:
*`name` (string)
*`type` (string) - `"dir"` or `"file"`
*`size` (number)
*`read_only` (bool)
* **Arguments**
*`path` - Directory to list
* **Returns:** A list of stat tables, each with keys:
*`name` (string)
*`type` (string) - `"dir"` or `"file"`
*`size` (number)
*`read_only` (bool)
* **Throws**
*`"could not opendir <path> (##)"` - error when attempting to open directory, with FatFs error number
*`"could not readdir <path> (##)"` - error when attempting to read directory, with FatFs error number
#### fs.stat_fs
*`array fs.stat_fs(string path)`
Get information about a filesystem.
> [!NOTE]
> This function can take several seconds before it returns an answer.
* **Arguments**
*`path` - Filesystem to stat
* **Returns:** A stat table with keys:
*`free` (number)
*`total` (number)
*`used` (number)
#### fs.dir_info
*`array fs.dir_info(string path)`
Get information about a directory.
> [!NOTE]
> This function can take several seconds before it returns an answer.
* **Arguments**
*`path` - Directory to check
* **Returns:** An info table with keys:
*`size` (number)
*`dirs` (number)
*`files` (number)
* **Throws**
*`"error when running DirInfo"` - error when scanning directory
Searches for a file based on a wildcard pattern. Returns the last result, unless `first` is specified, or `nil` if nothing was found.
Pattern can use `?` for search values, for example `00017?02` will match `00017002`, `00017102`, etc. Wildcards are also accepted.
* **Arguments**
*`pattern` - Pattern to search for
*`opts` (optional) - Option flags
*`first` - Return first result instead of last
* **Returns:** found file, or `nil` if nothing is found
* **Throws**
*`"failed to find <path> (##)"` - error when attempting to find path, with FatFs error number
#### fs.find_not
*`string fs.find(string pattern)`
Searches for a free filename based on a pattern.
Pattern can use `?` for search values, for example `nand_??.bin` will check to see if `nand_00.bin` exists. If it doesn't, it returns this string. Otherwise, it checks if `nand_01.bin` exists and keeps going until an unused filename can be found.
* **Arguments**
*`pattern` - Pattern to search for
* **Returns:** found file
* **Throws**
*`"failed to find <path> (##)"` - error when attempting to find path, with FatFs error number
Check for and request permission to write to a sensitive path.
* **Arguments**
*`path` - Path to request permission for
*`opts` (optional) - Option flags
*`ask_all` - Request to write to all files in directory
* **Returns:** `true` if granted, `false` if user declines
#### fs.img_mount
*`void fs.img_mount(string path)`
Mount an image file. Can be anything mountable through the file browser.
* **Arguments**
*`path` - Path to image file
* **Throws**
*`"failed to mount <path>"` - not a valid image file
#### fs.img_umount
*`void fs.img_umount()`
Unmount the currently mounted image file.
#### fs.get_img_mount
*`string fs.get_img_mount()`
Get the currently mounted image file.
* **Returns:** path to file, or `nil` if none is mounted
#### fs.hash_file
*`string fs.hash_file(string path, int offset, int size[, table opts {bool sha1}])`
Calculate the hash for a file. Uses SHA-256 unless `sha1` is specified. To hash an entire file, `size` should be `0`.
> [!TIP]
> * Use `fs.verify_with_sha_file` to compare with a corresponding `.sha` file.
> * Use `util.bytes_to_hex` to convert the result to printable hex characters.
> [!NOTE]
> Using an offset that is not `0`, with a size of `0` (to hash to end of file), is currently undefined behavior. In the future this should work properly.
* **Arguments**
*`path` - File to hash
*`offset` - Data offset
*`size` - Amount of data to hash, or `0` to hash to end of file
*`opts` (optional) - Option flags
*`sha1` - Use SHA-1
* **Returns:** SHA-256 or SHA-1 hash as byte string
* **Throws**
*`"failed to stat <path>"` - could not stat file to get size
*`"FileGetSha failed on <path>"` - could not read file or user canceled
Dumps title keys or seeds. Taken from both SysNAND and EmuNAND. The resulting file is saved to `0:/gm9/out`.
* **Arguments**
*`file` - One of three supported filenames: `seeddb.bin`, `encTitleKeys.bin`, `decTitleKeys.bin`
*`opts` (optional) - Option flags
*`overwrite` - Overwrite files
* **Throws**
*`"building <file> failed"` - building failed or file already exists and `overwrite` was not used
#### fs.cart_dump
*`void fs.cart_dump(string path, int size[, table opts {bool encrypted}])`
Dump the raw data from the inserted game card. No modifications are made to the data. This means for example, Card2 games will not have the save area cleared.
* **Arguments**
*`path` - File to write data to
*`size` - Amount of data to read
*`opts` (optional) - Option flags
*`encrypted` - Dump game encrypted, only for DS/DSi games?
* **Throws**
*`"out of memory"` - out-of-memory error when attempting to create the data buffer
*`"cart init fail"` - card is not inserted or some other failure when attempting to initialize
*`"cart dump failed or canceled"` - cart read failed or used canceled
---
### `title` module
#### title.decrypt, title.encrypt
*`void title.decrypt(string path)`
*`void title.decrypt(string path)`
Decrypt or encrypt a title or key database, done in-place.
* **Arguments**
*`path` - Path to title or key database
* **Throws**
*`"CryptAesKeyDb failed on <path>"` - could not crypt key database
*`"CryptGameFile failed on <path>"` - could not crypt title
*`"ApplyIPSPatch failed"` - failed to apply IPS patch
*`"ApplyBPSPatch failed"` - failed to apply BPS patch
*`"ApplyBPMPatch failed"` - failed to apply BPM patch
---
### `sys` module
#### sys.boot
*`void sys.boot(string path)`
Boot a FIRM file.
* **Arguments**
*`path` - FIRM file
* **Throws**
*`"out of memory"` - out-of-memory error when attempting to create the data buffer
*`"not a bootable firm"` - file cannot be booted
#### sys.reboot
*`void sys.reboot()`
Reboots the console.
#### sys.power_off
*`void sys.power_off()`
Powers off the console.
#### sys.region
*`string sys.region`
System region, based on SysNAND's `SecureInfo_[AB]` file. Not affected by `SecureInfo_C`.
Possible values: `"JPN"`, `"USA"`, `"EUR"`, `"AUS"`, `"CHN"`, `"KOR"`, `"TWN"`, `nil` (if it does not exist, or the region byte is invalid)
#### sys.serial
*`string sys.serial`
Serial number, based on SysNAND's `SecureInfo_[AB]` file. Not affected by `SecureInfo_C`.
Can be `nil` if the file does not exist.
#### sys.secureinfo_letter
*`string sys.secureinfo_letter`
The letter at the end of the console's `SecureInfo_[AB]` file.
Possible values: `"A"`, `"B"`, `nil` (if it does not exist)
#### sys.sys_id0
*`string sys.sys_id0`
ID0 of SysNAND as a printable hex string.
Can be `nil` if the file does not exist.
#### sys.emu_id0
*`string sys.emu_id0`
ID0 of EmuNAND as a printable hex string.
Can be `nil` if the file does not exist.
#### sys.emu_base
*`int sys.emu_base`
Current EmuNAND base.
#### sys.refresh_info
*`void sys.refresh_info()`
Refresh the following variables:
*`sys.region`
*`sys.serial`
*`sys.secureinfo_letter`
*`sys.sys_id0`
*`sys.emu_id0`
*`sys.emu_base`
This function is automatically called at the beginning of Lua's execution, but errors are caught to prevent a premature exit. This could leave some of the variables at `nil`.
* **Throws**
*`"could not read SecureInfo"` - `SecureInfo_[AB]` is missing
*`"SecureInfo region byte is invalid"` - `SecureInfo_[AB]` is not between 0 and 6 inclusive
#### sys.next_emu
*`void sys.next_emu()`
Switch to the next available EmuNAND.
This will automatically call `sys.refresh_info`.
#### sys.check_embedded_backup
*`bool sys.check_embedded_backup()`
Check if `essential.exefs` is embedded into SysNAND, and prompts the user if it isn't. This is the same as the check that is done at boot for GodMode9, but this can be called in a build compiled with `SCRIPT_RUNNER=1` for autorun scripts that require `essential.exefs` to exist.
* **Returns:** `true` if it exists or was created, `false` if user declines, `nil` if console fails genuine NCSD check (modified partition table)
#### sys.check_raw_rtc
*`bool sys.check_raw_rtc()`
Check if the Raw RTC is set correctly, and prompts the user if it isn't. This is the same as the check that is done at boot for GodMode9, but this can be called in a build compiled with `SCRIPT_RUNNER=1` for autorun scripts that require `essential.exefs` to exist.
* **Returns:** `true` if set, `false` if user declines
---
### `util` module
#### util.bytes_to_hex
*`string util.bytes_to_hex(string data)`
Convert a byte string to printable hex characters.
Returns a date stamp formatted like "241202" for 2024 December 02. Equivalent to `os.date("%y%m%d")`.
* **Returns:** Date stamp
#### util.get_timestamp
*`string util.get_timestamp()`
Returns a date stamp formatted like "010828" for 01:08:28. Equivalent to `os.date("%H%M%S")`.
* **Returns:** Time stamp
#### util.running_as_module
*`bool util.running_as_module()`
Determines if the currently executing script was directly run, or was imported by another script. Useful for scripts that want to be usable directly, while also importable.
> [!NOTE]
> This is not well tested.
* **Returns:** `true` if the current script was imported as a module