mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32:47 +00:00
361 lines
10 KiB
C
361 lines
10 KiB
C
|
#ifndef NO_LUA
|
||
|
#include "gm9ui.h"
|
||
|
#include "ui.h"
|
||
|
#include "fs.h"
|
||
|
#include "png.h"
|
||
|
#include "swkbd.h"
|
||
|
#include "qrcodegen.h"
|
||
|
#include "utils.h"
|
||
|
#include "hid.h"
|
||
|
|
||
|
#define MAXOPTIONS 256
|
||
|
#define MAXOPTIONS_STR "256"
|
||
|
|
||
|
#define OUTPUTMAXLINES 24
|
||
|
#define OUTPUTMAXCHARSPERLINE 80 // make sure this includes space for '\0'
|
||
|
|
||
|
// this output buffer stuff is especially a test, it needs to take into account newlines and fonts that are not 8x10
|
||
|
|
||
|
char output_buffer[OUTPUTMAXLINES][OUTPUTMAXCHARSPERLINE]; // hold 24 lines
|
||
|
|
||
|
void ShiftOutputBufferUp(void) {
|
||
|
for (int i = 0; i < OUTPUTMAXLINES - 1; i++) {
|
||
|
memcpy(output_buffer[i], output_buffer[i + 1], OUTPUTMAXCHARSPERLINE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ClearOutputBuffer(void) {
|
||
|
memset(output_buffer, 0, sizeof(output_buffer));
|
||
|
}
|
||
|
|
||
|
void WriteToOutputBuffer(char* text) {
|
||
|
strlcpy(output_buffer[OUTPUTMAXLINES - 1], text, OUTPUTMAXCHARSPERLINE);
|
||
|
}
|
||
|
|
||
|
void RenderOutputBuffer(void) {
|
||
|
ClearScreenF(false, true, COLOR_STD_BG);
|
||
|
for (int i = 0; i < OUTPUTMAXLINES; i++) {
|
||
|
DrawString(ALT_SCREEN, output_buffer[i], 0, i * 10, COLOR_STD_FONT, COLOR_TRANSPARENT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ui_echo(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.echo");
|
||
|
const char* text = lua_tostring(L, 1);
|
||
|
|
||
|
ShowPrompt(false, "%s", text);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_ask(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.ask");
|
||
|
const char* text = lua_tostring(L, 1);
|
||
|
|
||
|
bool ret = ShowPrompt(true, "%s", text);
|
||
|
lua_pushboolean(L, ret);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int ui_ask_hex(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 3, "ui.ask_hex");
|
||
|
const char* text = lua_tostring(L, 1);
|
||
|
u64 initial_hex = lua_tonumber(L, 2);
|
||
|
u32 n_digits = lua_tonumber(L, 3);
|
||
|
|
||
|
u64 ret = ShowHexPrompt(initial_hex, n_digits, "%s", text);
|
||
|
if (ret == (u64) -1) {
|
||
|
lua_pushnil(L);
|
||
|
} else {
|
||
|
lua_pushnumber(L, ret);
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int ui_ask_number(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 2, "ui.ask_number");
|
||
|
const char* text = lua_tostring(L, 1);
|
||
|
u64 initial_num = lua_tonumber(L, 2);
|
||
|
|
||
|
u64 ret = ShowNumberPrompt(initial_num, "%s", text);
|
||
|
if (ret == (u64) -1) {
|
||
|
lua_pushnil(L);
|
||
|
} else {
|
||
|
lua_pushnumber(L, ret);
|
||
|
}
|
||
|
return 1;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int ui_ask_text(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 3, "ui.ask_text");
|
||
|
const char* prompt = lua_tostring(L, 1);
|
||
|
const char* _initial_text = lua_tostring(L, 2);
|
||
|
u32 initial_text_size = strlen(_initial_text)+1;
|
||
|
char initial_text[initial_text_size];
|
||
|
snprintf(initial_text, initial_text_size, "%s", _initial_text);
|
||
|
u32 max_size = lua_tonumber(L, 3);
|
||
|
bool result = ShowKeyboardOrPrompt(initial_text, max_size, "%s", prompt);
|
||
|
if (result)
|
||
|
lua_pushstring(L, initial_text);
|
||
|
else
|
||
|
lua_pushnil(L);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int ui_clear(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 0, "ui.clear");
|
||
|
|
||
|
ClearScreen(ALT_SCREEN, COLOR_STD_BG);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_show_png(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.show_png");
|
||
|
const char* path = luaL_checkstring(L, 1);
|
||
|
u16 *screen = ALT_SCREEN;
|
||
|
u16 *bitmap = NULL;
|
||
|
u8* png = (u8*) malloc(SCREEN_SIZE(screen));
|
||
|
u32 bitmap_width, bitmap_height;
|
||
|
if (png) {
|
||
|
u32 png_size = FileGetData(path, png, SCREEN_SIZE(screen), 0);
|
||
|
if (!png_size) {
|
||
|
free(png);
|
||
|
return luaL_error(L, "Could not read %s", path);
|
||
|
}
|
||
|
if (png_size && png_size < SCREEN_SIZE(screen)) {
|
||
|
bitmap = PNG_Decompress(png, png_size, &bitmap_width, &bitmap_height);
|
||
|
if (!bitmap) {
|
||
|
free(png);
|
||
|
return luaL_error(L, "Invalid PNG file");
|
||
|
}
|
||
|
}
|
||
|
free(png);
|
||
|
if (!bitmap) {
|
||
|
return luaL_error(L, "PNG too large");
|
||
|
} else if ((SCREEN_WIDTH(screen) < bitmap_width) || (SCREEN_HEIGHT < bitmap_height)) {
|
||
|
free(bitmap);
|
||
|
return luaL_error(L, "PNG too large");
|
||
|
} else {
|
||
|
ClearScreen(ALT_SCREEN, COLOR_STD_BG);
|
||
|
DrawBitmap(
|
||
|
screen, // screen
|
||
|
-1, // x coordinate from argument
|
||
|
-1, // y coordinate from argument
|
||
|
bitmap_width, // width
|
||
|
bitmap_height, // height
|
||
|
bitmap // bitmap
|
||
|
);
|
||
|
free(bitmap);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_show_text(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.show_text");
|
||
|
|
||
|
const char* text = lua_tostring(L, 1);
|
||
|
|
||
|
ClearScreen(ALT_SCREEN, COLOR_STD_BG);
|
||
|
DrawStringCenter(ALT_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", text);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_show_game_info(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.show_game_info");
|
||
|
const char* path = luaL_checkstring(L, 1);
|
||
|
|
||
|
bool ret = (ShowGameFileIcon(path, ALT_SCREEN) == 0);
|
||
|
if (!ret) {
|
||
|
return luaL_error(L, "ShowGameFileIcon failed on %s", path);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_show_qr(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 2, "ui.show_qr");
|
||
|
size_t data_len;
|
||
|
const char* text = luaL_checkstring(L, 1);
|
||
|
const char* data = luaL_checklstring(L, 2, &data_len);
|
||
|
|
||
|
const u32 screen_size = SCREEN_SIZE(ALT_SCREEN);
|
||
|
u8* screen_copy = (u8*) malloc(screen_size);
|
||
|
u8 qrcode[qrcodegen_BUFFER_LEN_MAX];
|
||
|
u8 temp[qrcodegen_BUFFER_LEN_MAX];
|
||
|
bool ret = screen_copy && qrcodegen_encodeText(data, temp, qrcode, qrcodegen_Ecc_LOW,
|
||
|
qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
|
||
|
if (ret) {
|
||
|
memcpy(screen_copy, ALT_SCREEN, screen_size);
|
||
|
DrawQrCode(ALT_SCREEN, qrcode);
|
||
|
ShowPrompt(false, "%s", text);
|
||
|
memcpy(ALT_SCREEN, screen_copy, screen_size);
|
||
|
} else {
|
||
|
return luaL_error(L, "could not allocate memory");
|
||
|
}
|
||
|
free(screen_copy);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_show_text_viewer(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.show_text_viewer");
|
||
|
size_t len = 0;
|
||
|
const char* text = luaL_tolstring(L, 1, &len);
|
||
|
|
||
|
// validate text ourselves so we can return a better error
|
||
|
// MemTextViewer calls ShowPrompt if it's bad, and i don't want that
|
||
|
|
||
|
if (!(ValidateText(text, len))) {
|
||
|
return luaL_error(L, "text validation failed");
|
||
|
}
|
||
|
|
||
|
if (!(MemTextViewer(text, len, 1, false))) {
|
||
|
return luaL_error(L, "failed to run MemTextViewer");
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_show_file_text_viewer(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.show_file_text_viewer");
|
||
|
const char* path = luaL_checkstring(L, 1);
|
||
|
|
||
|
// validate text ourselves so we can return a better error
|
||
|
// MemTextViewer calls ShowPrompt if it's bad, and i don't want that
|
||
|
// and FileTextViewer calls the above function
|
||
|
|
||
|
char* text = malloc(STD_BUFFER_SIZE);
|
||
|
if (!text) {
|
||
|
return luaL_error(L, "could not allocate memory");
|
||
|
};
|
||
|
|
||
|
// TODO: replace this with something that can detect file read errors and actual 0-length files
|
||
|
size_t flen = FileGetData(path, text, STD_BUFFER_SIZE - 1, 0);
|
||
|
|
||
|
text[flen] = '\0';
|
||
|
u32 len = (ptrdiff_t)memchr(text, '\0', flen + 1) - (ptrdiff_t)text;
|
||
|
|
||
|
if (!(ValidateText(text, len))) {
|
||
|
free(text);
|
||
|
return luaL_error(L, "text validation failed");
|
||
|
}
|
||
|
|
||
|
if (!(MemTextViewer(text, len, 1, false))) {
|
||
|
free(text);
|
||
|
return luaL_error(L, "failed to run MemTextViewer");
|
||
|
}
|
||
|
|
||
|
free(text);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ui_ask_selection(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 2, "ui.ask_selection");
|
||
|
const char* text = lua_tostring(L, 1);
|
||
|
char* options[MAXOPTIONS];
|
||
|
const char* tmpstr;
|
||
|
size_t len;
|
||
|
int i;
|
||
|
|
||
|
luaL_argcheck(L, lua_istable(L, 2), 2, "table expected");
|
||
|
|
||
|
lua_Integer opttablesize = luaL_len(L, 2);
|
||
|
luaL_argcheck(L, opttablesize <= MAXOPTIONS, 2, "more than " MAXOPTIONS_STR " options given");
|
||
|
for (i = 0; i < opttablesize; i++) {
|
||
|
lua_geti(L, 2, i + 1);
|
||
|
tmpstr = lua_tolstring(L, -1, &len);
|
||
|
options[i] = malloc(len + 1);
|
||
|
strlcpy(options[i], tmpstr, len + 1);
|
||
|
lua_pop(L, 1);
|
||
|
}
|
||
|
int result = ShowSelectPrompt(opttablesize, (const char**)options, "%s", text);
|
||
|
for (i = 0; i < opttablesize; i++) free(options[i]);
|
||
|
// lua only treats "false" and "nil" as false values
|
||
|
// so to make this easier, return nil and not 0 if no choice was made
|
||
|
// https://www.lua.org/manual/5.4/manual.html#3.3.4
|
||
|
if (result)
|
||
|
lua_pushinteger(L, result);
|
||
|
else
|
||
|
lua_pushnil(L);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int ui_format_bytes(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.format_bytes");
|
||
|
lua_Integer size = luaL_checkinteger(L, 1);
|
||
|
|
||
|
char bytesstr[32] = { 0 };
|
||
|
FormatBytes(bytesstr, (u64)size);
|
||
|
|
||
|
lua_pushstring(L, bytesstr);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static int ui_global_print(lua_State* L) {
|
||
|
//const char* text = lua_tostring(L, 1);
|
||
|
char buf[OUTPUTMAXCHARSPERLINE] = {0};
|
||
|
int argcount = lua_gettop(L);
|
||
|
for (int i = 0; i < lua_gettop(L); i++) {
|
||
|
const char* str = luaL_tolstring(L, i+1, NULL);
|
||
|
if (str) {
|
||
|
strlcat(buf, str, OUTPUTMAXCHARSPERLINE);
|
||
|
lua_pop(L, 1);
|
||
|
} else {
|
||
|
// idk
|
||
|
strlcat(buf, "(unknown)", OUTPUTMAXCHARSPERLINE);
|
||
|
}
|
||
|
if (i < argcount) strlcat(buf, " ", OUTPUTMAXCHARSPERLINE);
|
||
|
}
|
||
|
ShiftOutputBufferUp();
|
||
|
WriteToOutputBuffer((char*)buf);
|
||
|
RenderOutputBuffer();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// TODO: use luaL_checkoption which will auto-raise an error
|
||
|
// use BUTTON_STRINGS from common/hid_map.h
|
||
|
static int ui_check_key(lua_State* L) {
|
||
|
CheckLuaArgCount(L, 1, "ui.check_key");
|
||
|
const char* key = luaL_checkstring(L, 1);
|
||
|
|
||
|
lua_pushboolean(L, CheckButton(StringToButton((char*)key)));
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static const luaL_Reg ui_lib[] = {
|
||
|
{"echo", ui_echo},
|
||
|
{"ask", ui_ask},
|
||
|
{"ask_hex", ui_ask_hex},
|
||
|
{"ask_number", ui_ask_number},
|
||
|
{"ask_text", ui_ask_text},
|
||
|
{"ask_selection", ui_ask_selection},
|
||
|
{"clear", ui_clear},
|
||
|
{"show_png", ui_show_png},
|
||
|
{"show_text", ui_show_text},
|
||
|
{"show_game_info", ui_show_game_info},
|
||
|
{"show_qr", ui_show_qr},
|
||
|
{"show_text_viewer", ui_show_text_viewer},
|
||
|
{"show_file_text_viewer", ui_show_file_text_viewer},
|
||
|
{"format_bytes", ui_format_bytes},
|
||
|
{"check_key", ui_check_key},
|
||
|
{NULL, NULL}
|
||
|
};
|
||
|
|
||
|
static const luaL_Reg ui_global_lib[] = {
|
||
|
{"print", ui_global_print},
|
||
|
{NULL, NULL}
|
||
|
};
|
||
|
|
||
|
int gm9lua_open_ui(lua_State* L) {
|
||
|
luaL_newlib(L, ui_lib);
|
||
|
lua_pushglobaltable(L); // push global table to stack
|
||
|
luaL_setfuncs(L, ui_global_lib, 0); // set global funcs
|
||
|
lua_pop(L, 1); // pop global table from stack
|
||
|
return 1;
|
||
|
}
|
||
|
#endif
|