mirror of
https://github.com/Decscots/Lockpick_RCM
synced 2024-11-15 12:06:33 +00:00
Add key generation to menu and fix repeat dump bug
This commit is contained in:
parent
035134f36e
commit
d6105b95e1
3 changed files with 88 additions and 44 deletions
|
@ -52,6 +52,8 @@ extern int sd_save_to_file(void *buf, u32 size, const char *filename);
|
||||||
|
|
||||||
extern hekate_config h_cfg;
|
extern hekate_config h_cfg;
|
||||||
|
|
||||||
|
extern bool clear_sector_cache;
|
||||||
|
|
||||||
u32 _key_count = 0, _titlekey_count = 0;
|
u32 _key_count = 0, _titlekey_count = 0;
|
||||||
u32 color_idx = 0;
|
u32 color_idx = 0;
|
||||||
sdmmc_storage_t storage;
|
sdmmc_storage_t storage;
|
||||||
|
@ -73,7 +75,21 @@ u32 start_time, end_time;
|
||||||
#define SAVE_KEY(name, src, len) _save_key(name, src, len, text_buffer)
|
#define SAVE_KEY(name, src, len) _save_key(name, src, len, text_buffer)
|
||||||
#define SAVE_KEY_FAMILY(name, src, start, count, len) _save_key_family(name, src, start, count, len, text_buffer)
|
#define SAVE_KEY_FAMILY(name, src, start, count, len) _save_key_family(name, src, start, count, len, text_buffer)
|
||||||
|
|
||||||
static u8 temp_key[0x10],
|
// key functions
|
||||||
|
static bool _key_exists(const void *data) { return memcmp(data, zeros, 0x10); };
|
||||||
|
static void _save_key(const char *name, const void *data, u32 len, char *outbuf);
|
||||||
|
static void _save_key_family(const char *name, const void *data, u32 start_key, u32 num_keys, u32 len, char *outbuf);
|
||||||
|
static void _generate_kek(u32 ks, const void *key_source, void *master_key, const void *kek_seed, const void *key_seed);
|
||||||
|
// nca functions
|
||||||
|
static void *_nca_process(u32 hk_ks1, u32 hk_ks2, FIL *fp, u32 key_offset, u32 len, const u8 key_area_key[3][KB_FIRMWARE_VERSION_MAX+1][0x10]);
|
||||||
|
static u32 _nca_fread_ctr(u32 ks, FIL *fp, void *buffer, u32 offset, u32 len, u8 *ctr);
|
||||||
|
static void _update_ctr(u8 *ctr, u32 ofs);
|
||||||
|
// titlekey functions
|
||||||
|
static bool _test_key_pair(const void *E, const void *D, const void *N);
|
||||||
|
static void _mgf1_xor(void *masked, u32 masked_size, const void *seed, u32 seed_size);
|
||||||
|
|
||||||
|
void dump_keys() {
|
||||||
|
u8 temp_key[0x10],
|
||||||
bis_key[4][0x20] = {0},
|
bis_key[4][0x20] = {0},
|
||||||
device_key[0x10] = {0},
|
device_key[0x10] = {0},
|
||||||
new_device_key[0x10] = {0},
|
new_device_key[0x10] = {0},
|
||||||
|
@ -85,7 +101,7 @@ static u8 temp_key[0x10],
|
||||||
// other sysmodule sources
|
// other sysmodule sources
|
||||||
es_keys[3][0x10] = {0},
|
es_keys[3][0x10] = {0},
|
||||||
eticket_rsa_kek[0x10] = {0},
|
eticket_rsa_kek[0x10] = {0},
|
||||||
ssl_keys[2][0x10] = {0},
|
ssl_keys[0x10] = {0},
|
||||||
ssl_rsa_kek[0x10] = {0},
|
ssl_rsa_kek[0x10] = {0},
|
||||||
// keyblob-derived families
|
// keyblob-derived families
|
||||||
keyblob[KB_FIRMWARE_VERSION_600+1][0x90] = {0},
|
keyblob[KB_FIRMWARE_VERSION_600+1][0x90] = {0},
|
||||||
|
@ -99,20 +115,6 @@ static u8 temp_key[0x10],
|
||||||
package2_key[KB_FIRMWARE_VERSION_MAX+1][0x10] = {0},
|
package2_key[KB_FIRMWARE_VERSION_MAX+1][0x10] = {0},
|
||||||
titlekek[KB_FIRMWARE_VERSION_MAX+1][0x10] = {0};
|
titlekek[KB_FIRMWARE_VERSION_MAX+1][0x10] = {0};
|
||||||
|
|
||||||
// key functions
|
|
||||||
static bool _key_exists(const void *data) { return memcmp(data, zeros, 0x10); };
|
|
||||||
static void _save_key(const char *name, const void *data, u32 len, char *outbuf);
|
|
||||||
static void _save_key_family(const char *name, const void *data, u32 start_key, u32 num_keys, u32 len, char *outbuf);
|
|
||||||
static void _generate_kek(u32 ks, const void *key_source, void *master_key, const void *kek_seed, const void *key_seed);
|
|
||||||
// nca functions
|
|
||||||
static void *_nca_process(u32 hk_ks1, u32 hk_ks2, FIL *fp, u32 key_offset, u32 len);
|
|
||||||
static u32 _nca_fread_ctr(u32 ks, FIL *fp, void *buffer, u32 offset, u32 len, u8 *ctr);
|
|
||||||
static void _update_ctr(u8 *ctr, u32 ofs);
|
|
||||||
// titlekey functions
|
|
||||||
static bool _test_key_pair(const void *E, const void *D, const void *N);
|
|
||||||
static void _mgf1_xor(void *masked, u32 masked_size, const void *seed, u32 seed_size);
|
|
||||||
|
|
||||||
void dump_keys() {
|
|
||||||
display_backlight_brightness(h_cfg.backlight, 1000);
|
display_backlight_brightness(h_cfg.backlight, 1000);
|
||||||
gfx_clear_partial_grey(0x1B, 0, 1256);
|
gfx_clear_partial_grey(0x1B, 0, 1256);
|
||||||
gfx_con_setpos(0, 0);
|
gfx_con_setpos(0, 0);
|
||||||
|
@ -122,6 +124,10 @@ void dump_keys() {
|
||||||
|
|
||||||
tui_sbar(true);
|
tui_sbar(true);
|
||||||
|
|
||||||
|
_key_count = 0;
|
||||||
|
_titlekey_count = 0;
|
||||||
|
color_idx = 0;
|
||||||
|
|
||||||
start_time = get_tmr_us();
|
start_time = get_tmr_us();
|
||||||
u32 begin_time = get_tmr_us();
|
u32 begin_time = get_tmr_us();
|
||||||
u32 retries = 0;
|
u32 retries = 0;
|
||||||
|
@ -575,7 +581,7 @@ pkg2_done:
|
||||||
}
|
}
|
||||||
__attribute__ ((aligned (16))) FATFS emmc_fs;
|
__attribute__ ((aligned (16))) FATFS emmc_fs;
|
||||||
if (f_mount(&emmc_fs, "emmc:", 1)) {
|
if (f_mount(&emmc_fs, "emmc:", 1)) {
|
||||||
EPRINTF("Mount Unable.");
|
EPRINTF("Unable to mount system partition.");
|
||||||
goto key_output;
|
goto key_output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,7 +663,7 @@ pkg2_done:
|
||||||
}
|
}
|
||||||
hash_index = 0;
|
hash_index = 0;
|
||||||
// decrypt only what is needed to locate needed keys
|
// decrypt only what is needed to locate needed keys
|
||||||
temp_file = (u8*)_nca_process(5, 4, &fp, start_offset, 0xc0);
|
temp_file = (u8*)_nca_process(5, 4, &fp, start_offset, 0xc0, key_area_key);
|
||||||
for (u32 i = 0; i <= 0xb0; ) {
|
for (u32 i = 0; i <= 0xb0; ) {
|
||||||
se_calc_sha256(temp_hash, temp_file + i, 0x10);
|
se_calc_sha256(temp_hash, temp_file + i, 0x10);
|
||||||
if (!memcmp(temp_hash, es_hashes_sha256[hash_order[hash_index]], 0x10)) {
|
if (!memcmp(temp_hash, es_hashes_sha256[hash_order[hash_index]], 0x10)) {
|
||||||
|
@ -703,11 +709,11 @@ pkg2_done:
|
||||||
}
|
}
|
||||||
if (!memcmp(pkg1_id->id, "2016", 4))
|
if (!memcmp(pkg1_id->id, "2016", 4))
|
||||||
start_offset = 0x449dc;
|
start_offset = 0x449dc;
|
||||||
temp_file = (u8*)_nca_process(5, 4, &fp, start_offset, 0x70);
|
temp_file = (u8*)_nca_process(5, 4, &fp, start_offset, 0x70, key_area_key);
|
||||||
for (u32 i = 0; i <= 0x60; i++) {
|
for (u32 i = 0; i <= 0x60; i++) {
|
||||||
se_calc_sha256(temp_hash, temp_file + i, 0x10);
|
se_calc_sha256(temp_hash, temp_file + i, 0x10);
|
||||||
if (!memcmp(temp_hash, ssl_hashes_sha256[1], 0x10)) {
|
if (!memcmp(temp_hash, ssl_hashes_sha256[1], 0x10)) {
|
||||||
memcpy(ssl_keys[1], temp_file + i, 0x10);
|
memcpy(ssl_keys, temp_file + i, 0x10);
|
||||||
// only get ssl_rsa_kek_source_x from SSL on 1.0.0
|
// only get ssl_rsa_kek_source_x from SSL on 1.0.0
|
||||||
// we get it from ES on every other firmware
|
// we get it from ES on every other firmware
|
||||||
// and it's located oddly distant from ssl_rsa_kek_source_y on >= 6.0.0
|
// and it's located oddly distant from ssl_rsa_kek_source_y on >= 6.0.0
|
||||||
|
@ -735,11 +741,11 @@ pkg2_done:
|
||||||
_generate_kek(7, es_keys[1], master_key[0], temp_key, NULL);
|
_generate_kek(7, es_keys[1], master_key[0], temp_key, NULL);
|
||||||
se_aes_crypt_block_ecb(7, 0, eticket_rsa_kek, es_keys[0]);
|
se_aes_crypt_block_ecb(7, 0, eticket_rsa_kek, es_keys[0]);
|
||||||
}
|
}
|
||||||
if (_key_exists(ssl_keys[1]) && _key_exists(es_keys[2]) && _key_exists(master_key[0])) {
|
if (_key_exists(ssl_keys) && _key_exists(es_keys[2]) && _key_exists(master_key[0])) {
|
||||||
for (u32 i = 0; i < 0x10; i++)
|
for (u32 i = 0; i < 0x10; i++)
|
||||||
temp_key[i] = aes_kek_generation_source[i] ^ aes_kek_seed_01[i];
|
temp_key[i] = aes_kek_generation_source[i] ^ aes_kek_seed_01[i];
|
||||||
_generate_kek(7, es_keys[2], master_key[0], temp_key, NULL);
|
_generate_kek(7, es_keys[2], master_key[0], temp_key, NULL);
|
||||||
se_aes_crypt_block_ecb(7, 0, ssl_rsa_kek, ssl_keys[1]);
|
se_aes_crypt_block_ecb(7, 0, ssl_rsa_kek, ssl_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memcmp(pkg1_id->id, "2016", 4)) {
|
if (memcmp(pkg1_id->id, "2016", 4)) {
|
||||||
|
@ -786,7 +792,7 @@ get_titlekeys:
|
||||||
gfx_printf("%k Minerva not found!\n This may take up to a minute...\n", colors[(color_idx++) % 6]);
|
gfx_printf("%k Minerva not found!\n This may take up to a minute...\n", colors[(color_idx++) % 6]);
|
||||||
gfx_printf(" For better performance, download Hekate\n and put bootloader/sys/libsys_minerva.bso\n on SD.\n");
|
gfx_printf(" For better performance, download Hekate\n and put bootloader/sys/libsys_minerva.bso\n on SD.\n");
|
||||||
}
|
}
|
||||||
gfx_printf("%kTitlekeys... ", colors[(color_idx++) % 6]);
|
gfx_printf("%kTitlekeys... ", colors[color_idx % 6]);
|
||||||
u32 save_x = gfx_con.x, save_y = gfx_con.y;
|
u32 save_x = gfx_con.x, save_y = gfx_con.y;
|
||||||
gfx_printf("\n");
|
gfx_printf("\n");
|
||||||
|
|
||||||
|
@ -949,8 +955,8 @@ get_titlekeys:
|
||||||
|
|
||||||
dismount:
|
dismount:
|
||||||
f_mount(NULL, "emmc:", 1);
|
f_mount(NULL, "emmc:", 1);
|
||||||
|
clear_sector_cache = true;
|
||||||
nx_emmc_gpt_free(&gpt);
|
nx_emmc_gpt_free(&gpt);
|
||||||
emummc_storage_end(&storage);
|
|
||||||
|
|
||||||
key_output: ;
|
key_output: ;
|
||||||
char *text_buffer = (char *)malloc(_titlekey_count * 68 < 0x3000 ? 0x3000 : _titlekey_count * 68 + 1);
|
char *text_buffer = (char *)malloc(_titlekey_count * 68 < 0x3000 ? 0x3000 : _titlekey_count * 68 + 1);
|
||||||
|
@ -1003,7 +1009,7 @@ key_output: ;
|
||||||
SAVE_KEY("secure_boot_key", sbk, 0x10);
|
SAVE_KEY("secure_boot_key", sbk, 0x10);
|
||||||
SAVE_KEY("ssl_rsa_kek", ssl_rsa_kek, 0x10);
|
SAVE_KEY("ssl_rsa_kek", ssl_rsa_kek, 0x10);
|
||||||
SAVE_KEY("ssl_rsa_kek_source_x", es_keys[2], 0x10);
|
SAVE_KEY("ssl_rsa_kek_source_x", es_keys[2], 0x10);
|
||||||
SAVE_KEY("ssl_rsa_kek_source_y", ssl_keys[1], 0x10);
|
SAVE_KEY("ssl_rsa_kek_source_y", ssl_keys, 0x10);
|
||||||
SAVE_KEY_FAMILY("titlekek", titlekek, 0, MAX_KEY, 0x10);
|
SAVE_KEY_FAMILY("titlekek", titlekek, 0, MAX_KEY, 0x10);
|
||||||
SAVE_KEY("titlekek_source", titlekek_source, 0x10);
|
SAVE_KEY("titlekek_source", titlekek_source, 0x10);
|
||||||
SAVE_KEY("tsec_key", tsec_keys, 0x10);
|
SAVE_KEY("tsec_key", tsec_keys, 0x10);
|
||||||
|
@ -1014,7 +1020,6 @@ key_output: ;
|
||||||
|
|
||||||
end_time = get_tmr_us();
|
end_time = get_tmr_us();
|
||||||
gfx_printf("\n%k Found %d keys.\n\n", colors[(color_idx++) % 6], _key_count);
|
gfx_printf("\n%k Found %d keys.\n\n", colors[(color_idx++) % 6], _key_count);
|
||||||
_key_count = 0;
|
|
||||||
gfx_printf("%kLockpick totally done in %d us\n\n", colors[(color_idx++) % 6], end_time - begin_time);
|
gfx_printf("%kLockpick totally done in %d us\n\n", colors[(color_idx++) % 6], end_time - begin_time);
|
||||||
gfx_printf("%kFound through master_key_%02x.\n\n", colors[(color_idx++) % 6], MAX_KEY - 1);
|
gfx_printf("%kFound through master_key_%02x.\n\n", colors[(color_idx++) % 6], MAX_KEY - 1);
|
||||||
|
|
||||||
|
@ -1051,7 +1056,7 @@ key_output: ;
|
||||||
|
|
||||||
out_wait:
|
out_wait:
|
||||||
h_cfg.emummc_force_disable = emummc_load_cfg();
|
h_cfg.emummc_force_disable = emummc_load_cfg();
|
||||||
sd_unmount();
|
emummc_storage_end(&storage);
|
||||||
gfx_printf("\n%kPress any key to return to the main menu.", colors[(color_idx) % 6], colors[(color_idx + 1) % 6], colors[(color_idx + 2) % 6]);
|
gfx_printf("\n%kPress any key to return to the main menu.", colors[(color_idx) % 6], colors[(color_idx + 1) % 6], colors[(color_idx + 2) % 6]);
|
||||||
btn_wait();
|
btn_wait();
|
||||||
}
|
}
|
||||||
|
@ -1093,7 +1098,7 @@ static inline u32 _read_le_u32(const void *buffer, u32 offset) {
|
||||||
(*(u8*)(buffer + offset + 3) << 0x18);
|
(*(u8*)(buffer + offset + 3) << 0x18);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_nca_process(u32 hk_ks1, u32 hk_ks2, FIL *fp, u32 key_offset, u32 len) {
|
static void *_nca_process(u32 hk_ks1, u32 hk_ks2, FIL *fp, u32 key_offset, u32 len, const u8 key_area_key[3][KB_FIRMWARE_VERSION_MAX+1][0x10]) {
|
||||||
u32 read_bytes = 0, crypt_offset, read_size, num_files, string_table_size, rodata_offset;
|
u32 read_bytes = 0, crypt_offset, read_size, num_files, string_table_size, rodata_offset;
|
||||||
|
|
||||||
u8 *temp_file = (u8*)malloc(0x400),
|
u8 *temp_file = (u8*)malloc(0x400),
|
||||||
|
|
|
@ -46,8 +46,9 @@ typedef struct {
|
||||||
} sector_cache_t;
|
} sector_cache_t;
|
||||||
|
|
||||||
#define MAX_SEC_CACHE_ENTRIES 64
|
#define MAX_SEC_CACHE_ENTRIES 64
|
||||||
static sector_cache_t *sector_cache;
|
static sector_cache_t *sector_cache = NULL;
|
||||||
static u32 secindex = 0;
|
static u32 secindex = 0;
|
||||||
|
bool clear_sector_cache = false;
|
||||||
|
|
||||||
DSTATUS disk_status (
|
DSTATUS disk_status (
|
||||||
BYTE pdrv /* Physical drive number to identify the drive */
|
BYTE pdrv /* Physical drive number to identify the drive */
|
||||||
|
@ -152,8 +153,11 @@ DRESULT disk_read (
|
||||||
u32 tweak_exp = 0;
|
u32 tweak_exp = 0;
|
||||||
bool regen_tweak = true, cache_sector = false;
|
bool regen_tweak = true, cache_sector = false;
|
||||||
|
|
||||||
if (secindex == 0) {
|
if (secindex == 0 || clear_sector_cache) {
|
||||||
|
free(sector_cache);
|
||||||
sector_cache = (sector_cache_t *)malloc(sizeof(sector_cache_t) * MAX_SEC_CACHE_ENTRIES);
|
sector_cache = (sector_cache_t *)malloc(sizeof(sector_cache_t) * MAX_SEC_CACHE_ENTRIES);
|
||||||
|
clear_sector_cache = false;
|
||||||
|
secindex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 s = 0;
|
u32 s = 0;
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "gfx/di.h"
|
#include "gfx/di.h"
|
||||||
#include "gfx/gfx.h"
|
#include "gfx/gfx.h"
|
||||||
#include "gfx/tui.h"
|
#include "gfx/tui.h"
|
||||||
|
#include "hos/pkg1.h"
|
||||||
#include "libs/fatfs/ff.h"
|
#include "libs/fatfs/ff.h"
|
||||||
#include "mem/heap.h"
|
#include "mem/heap.h"
|
||||||
#include "mem/minerva.h"
|
#include "mem/minerva.h"
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
#include "soc/bpmp.h"
|
#include "soc/bpmp.h"
|
||||||
#include "soc/hw_init.h"
|
#include "soc/hw_init.h"
|
||||||
#include "storage/emummc.h"
|
#include "storage/emummc.h"
|
||||||
|
#include "storage/nx_emmc.h"
|
||||||
#include "storage/sdmmc.h"
|
#include "storage/sdmmc.h"
|
||||||
#include "utils/sprintf.h"
|
#include "utils/sprintf.h"
|
||||||
#include "utils/util.h"
|
#include "utils/util.h"
|
||||||
|
@ -166,8 +168,8 @@ void dump_emunand()
|
||||||
}
|
}
|
||||||
|
|
||||||
ment_t ment_top[] = {
|
ment_t ment_top[] = {
|
||||||
MDEF_HANDLER("Dump keys from SysNAND", dump_sysnand, COLOR_RED),
|
MDEF_HANDLER("Dump from SysNAND | Key generation: unk", dump_sysnand, COLOR_RED),
|
||||||
MDEF_HANDLER("Dump keys from emuMMC", dump_emunand, COLOR_ORANGE),
|
MDEF_HANDLER("Dump from EmuNAND | Key generation: unk", dump_emunand, COLOR_ORANGE),
|
||||||
MDEF_CAPTION("---------------", COLOR_YELLOW),
|
MDEF_CAPTION("---------------", COLOR_YELLOW),
|
||||||
MDEF_HANDLER("Reboot (Normal)", reboot_normal, COLOR_GREEN),
|
MDEF_HANDLER("Reboot (Normal)", reboot_normal, COLOR_GREEN),
|
||||||
MDEF_HANDLER("Reboot (RCM)", reboot_rcm, COLOR_BLUE),
|
MDEF_HANDLER("Reboot (RCM)", reboot_rcm, COLOR_BLUE),
|
||||||
|
@ -177,7 +179,38 @@ ment_t ment_top[] = {
|
||||||
|
|
||||||
menu_t menu_top = { ment_top, NULL, 0, 0 };
|
menu_t menu_top = { ment_top, NULL, 0, 0 };
|
||||||
|
|
||||||
#define IPL_STACK_TOP 0x90010000//0x4003F000
|
void _get_key_generations(char *sysnand_label, char *emunand_label) {
|
||||||
|
sdmmc_t sdmmc;
|
||||||
|
sdmmc_storage_t storage;
|
||||||
|
sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4);
|
||||||
|
u8 *pkg1 = (u8 *)malloc(NX_EMMC_BLOCKSIZE);
|
||||||
|
sdmmc_storage_set_mmc_partition(&storage, 1);
|
||||||
|
sdmmc_storage_read(&storage, 0x100000 / NX_EMMC_BLOCKSIZE, 1, pkg1);
|
||||||
|
const pkg1_id_t *pkg1_id = pkg1_identify(pkg1);
|
||||||
|
sdmmc_storage_end(&storage);
|
||||||
|
|
||||||
|
if (pkg1_id)
|
||||||
|
sprintf(sysnand_label + 36, "% 3d", pkg1_id->kb);
|
||||||
|
ment_top[0].caption = sysnand_label;
|
||||||
|
if (h_cfg.emummc_force_disable) {
|
||||||
|
free(pkg1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emummc_storage_init_mmc(&storage, &sdmmc);
|
||||||
|
memset(pkg1, 0, NX_EMMC_BLOCKSIZE);
|
||||||
|
emummc_storage_set_mmc_partition(&storage, 1);
|
||||||
|
emummc_storage_read(&storage, 0x100000 / NX_EMMC_BLOCKSIZE, 1, pkg1);
|
||||||
|
pkg1_id = pkg1_identify(pkg1);
|
||||||
|
emummc_storage_end(&storage);
|
||||||
|
|
||||||
|
if (pkg1_id)
|
||||||
|
sprintf(emunand_label + 36, "% 3d", pkg1_id->kb);
|
||||||
|
free(pkg1);
|
||||||
|
ment_top[1].caption = emunand_label;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IPL_STACK_TOP 0x90010000
|
||||||
#define IPL_HEAP_START 0x90020000
|
#define IPL_HEAP_START 0x90020000
|
||||||
|
|
||||||
extern void pivot_stack(u32 stack_top);
|
extern void pivot_stack(u32 stack_top);
|
||||||
|
@ -218,6 +251,8 @@ void ipl_main()
|
||||||
ment_top[1].handler = NULL;
|
ment_top[1].handler = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_get_key_generations((char *)ment_top[0].caption, (char *)ment_top[1].caption);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
tui_do_menu(&menu_top);
|
tui_do_menu(&menu_top);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue