mirror of
https://github.com/etaHEN/etaHEN.git
synced 2026-01-12 19:25:33 +08:00
etaHEN 2.4B etaHEN 2.4B Change log - Updated to support the latest PS5 Payload SDK - Fixed etaHEN and Cheats support for 8.40-10.01 - Added a Game Overlay menu to show CPU/GPU Temp and utilization, Local IP Address and other future states - Added a Kstuff menu for options like downloading the latest kstuff from github, turning off kstufff autoload and more - Added a Custom Background Package Installer for installing PKGs from internal storage from any directory (Requires DPIv2 enabled for 5.50+) - DPIv2 can now download local files via url example http://192.xxx.xxx.xxx:12800/data/etaHEN/etaHEN.log - Improved Cheats support, cheats with or without 0 sections are now supported - Added Fix by TheFlow to Improve 2.xx PS4 PKG speeds - Replaced the donation links in the etaHEN credits menu with ones to github sponsers - Removed the non-whitelist app jailbreak option and moved it to an optional Legacy CMD Server option in the etaHEN Settings off by default - Game Decryptor has been updated for the Itemzflow Dumper - Updated the Plugin loader System - The Payload SDK ELFLDR is now REQUIRED for etaHEN to load - Replaced HTTP2 with Curl for better compatibility - Added timeout for ShellUI to receive a response (will stop it from freezing if no response is given) small fix
680 lines
15 KiB
C
680 lines
15 KiB
C
/* Copyright (C) 2025 etaHEN / LightningMods
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by the
|
|
Free Software Foundation; either version 3, or (at your option) any
|
|
later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; see the file COPYING. If not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "common_utils.h"
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <sys/signal.h>
|
|
#include <freebsd-helper.h>
|
|
#include <libgen.h>
|
|
#include <ps5/klog.h>
|
|
#include "pt.h"
|
|
|
|
typedef struct app_info {
|
|
uint32_t app_id;
|
|
uint64_t unknown1;
|
|
uint32_t app_type;
|
|
char title_id[10];
|
|
char unknown2[0x3c];
|
|
} app_info_t;
|
|
|
|
int launchApp(const char *titleId);
|
|
int sceSystemServiceGetAppId(const char *title_id);
|
|
void free(void *ptr);
|
|
void *malloc(size_t size);
|
|
int elfldr_set_procname(pid_t pid, const char* name);
|
|
|
|
int sceKernelGetProcessName(int pid, char *name);
|
|
int sceKernelGetAppInfo(int pid, app_info_t *title);
|
|
pid_t elfldr_spawn(const char* cwd, int stdio, uint8_t* elf, const char* name);
|
|
|
|
atomic_bool not_connected = false;
|
|
|
|
#define SCE_NET_CTL_ERROR_NOT_CONNECTED 0x80412108
|
|
#define SCE_NET_CTL_ERROR_NOT_AVAIL 0x80412109
|
|
|
|
__asm__(
|
|
|
|
|
|
".global elfldr_start\n"
|
|
".type elfldr_start, @object\n"
|
|
".align 16\n"
|
|
"elfldr_start:\n"
|
|
".incbin \"assets/elfldr.elf\"\n"
|
|
"elfldr_end:\n"
|
|
".global elfldr_size\n"
|
|
".type elfldr_size, @object\n"
|
|
".align 4\n"
|
|
"elfldr_size:\n"
|
|
".int elfldr_end - elfldr_start\n"
|
|
);
|
|
|
|
|
|
|
|
static int
|
|
sys_ptrace(int request, pid_t pid, caddr_t addr, int data) {
|
|
pid_t mypid = getpid();
|
|
uint64_t authid;
|
|
int ret;
|
|
|
|
if (!(authid = kernel_get_ucred_authid(mypid))) {
|
|
return -1;
|
|
}
|
|
if (kernel_set_ucred_authid(mypid, 0x4800000000010003l)) {
|
|
return -1;
|
|
}
|
|
|
|
ret = (int)syscall(SYS_ptrace, request, pid, addr, data);
|
|
|
|
if (kernel_set_ucred_authid(mypid, authid)) {
|
|
return -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int pt_detach_proc(pid_t pid, int sig) {
|
|
if (sys_ptrace(PT_DETACH, pid, 0, sig) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pt_attach_proc(pid_t pid) {
|
|
if (sys_ptrace(PT_ATTACH, pid, 0, 0) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
if (waitpid(pid, 0, 0) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int get_ip_address(char *ip_address)
|
|
{
|
|
unsigned int ret = 0;
|
|
SceNetCtlInfo info;
|
|
|
|
ret = sceNetCtlGetInfo(14, &info);
|
|
if (ret < 0){
|
|
if(ret == SCE_NET_CTL_ERROR_NOT_CONNECTED || ret == SCE_NET_CTL_ERROR_NOT_AVAIL){
|
|
not_connected = true;
|
|
}
|
|
goto error;
|
|
}
|
|
|
|
memcpy(ip_address, info.ip_address, sizeof(info.ip_address));
|
|
|
|
return ret;
|
|
|
|
error:
|
|
memcpy(ip_address, "IP NOT FOUND", sizeof(info.ip_address));
|
|
return -1;
|
|
}
|
|
|
|
void etaHEN_log(const char * fmt, ...) {
|
|
char msg[0x1000];
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
__builtin_vsnprintf(msg, sizeof(msg), fmt, args);
|
|
va_end(args);
|
|
|
|
// Append newline at the end
|
|
size_t msg_len = strlen(msg);
|
|
if (msg_len < sizeof(msg) - 1) {
|
|
msg[msg_len] = '\n';
|
|
msg[msg_len + 1] = '\0';
|
|
} else {
|
|
msg[sizeof(msg) - 2] = '\n';
|
|
msg[sizeof(msg) - 1] = '\0';
|
|
}
|
|
|
|
printf("[etaHEN utils]: %s", msg); // msg already includes a newline
|
|
klog_printf("%s", msg); // msg already includes a newline
|
|
|
|
int fd = open("/data/etaHEN/etaHEN_util_daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0777);
|
|
if (fd < 0) {
|
|
return;
|
|
}
|
|
write(fd, msg, strlen(msg));
|
|
close(fd);
|
|
}
|
|
|
|
bool touch_file(const char *destfile)
|
|
{
|
|
int fd = open(destfile, O_WRONLY | O_CREAT | O_TRUNC, 0777);
|
|
if (fd > 0)
|
|
{
|
|
close(fd);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void notify(bool show_watermark, const char *text, ...)
|
|
{
|
|
OrbisNotificationRequest req;
|
|
(void)memset(&req, 0, sizeof(OrbisNotificationRequest));
|
|
char buff[3075];
|
|
|
|
// printf("******************** text: %s\n", text);
|
|
|
|
va_list args;
|
|
va_start(args, text);
|
|
vsnprintf(buff, sizeof(buff), text, args);
|
|
va_end(args);
|
|
|
|
if (show_watermark)
|
|
snprintf(req.message, sizeof(req.message), "[etaHEN] %s", buff);
|
|
else
|
|
snprintf(req.message, sizeof(req.message), "[Itemzflow] %s", buff);
|
|
|
|
req.type = 0;
|
|
req.unk3 = 0;
|
|
req.use_icon_image_uri = 1;
|
|
req.target_id = -1;
|
|
strcpy(req.uri, "cxml://psnotification/tex_icon_system");
|
|
|
|
etaHEN_log("Notify: %s", req.message);
|
|
sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
|
|
}
|
|
|
|
|
|
bool copyFile(const char *source, const char *destination)
|
|
{
|
|
|
|
FILE *src = fopen(source, "rb");
|
|
if (src == NULL)
|
|
{
|
|
notify(false, "copyFile failed for %s", source);
|
|
etaHEN_log("copyFile failed for %s", source);
|
|
return false;
|
|
}
|
|
|
|
FILE *dest = fopen(destination, "wb");
|
|
if (dest == NULL)
|
|
{
|
|
notify(false, "copyFile failed for %s", destination);
|
|
etaHEN_log("copyFile failed for %s", destination);
|
|
fclose(src);
|
|
return false;
|
|
}
|
|
|
|
char buffer[1024];
|
|
size_t bytes = 0;
|
|
|
|
while ((bytes = fread(buffer, 1, sizeof(buffer), src)) > 0)
|
|
{
|
|
fwrite(buffer, 1, bytes, dest);
|
|
}
|
|
|
|
fclose(src);
|
|
fclose(dest);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void make_hb_elf(const char *tid, const void *start, const unsigned int size) {
|
|
char path[1024];
|
|
snprintf(path, sizeof(path), "/system_ex/app/%s/homebrew.elf", tid);
|
|
FILE *fp = fopen(path, "wb+");
|
|
if (fp == NULL) {
|
|
perror("open failed");
|
|
return;
|
|
}
|
|
fwrite(start, 1, size, fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
|
|
uint8_t *get_elf_header_address(unsigned char *file_buffer)
|
|
{
|
|
// The ELF header should start right after the custom plugin header
|
|
return file_buffer + sizeof(CustomPluginHeader);
|
|
}
|
|
|
|
|
|
static bool mkdir_if_necessary(const char *path) {
|
|
if (mkdir(path, 0777) == -1) {
|
|
const int err = errno;
|
|
if (err != EEXIST) {
|
|
perror("mkdir failed");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool make_plugin_app(const char *tid, const void *start,
|
|
const unsigned int size)
|
|
{
|
|
// REDIS->NPXS40028
|
|
char sys_app[255];
|
|
static const char *json = "{\n"
|
|
" \"applicationCategoryType\": 33554432,\n"
|
|
" \"localizedParameters\": {\n"
|
|
" \"defaultLanguage\": \"en-US\",\n"
|
|
" \"en-US\": {\n"
|
|
" \"titleName\": \"etaHEN Plugin\"\n"
|
|
" }\n"
|
|
" },\n"
|
|
" \"titleId\": \"%s"
|
|
"\"\n"
|
|
"}\n";
|
|
snprintf(sys_app, sizeof(sys_app), "/system_ex/app/%s", tid);
|
|
if (mkdir(sys_app, 0777) == -1)
|
|
{
|
|
const int err = errno;
|
|
if (err != EEXIST)
|
|
{
|
|
perror("make_plugin_app mkdir /system_ex/app/");
|
|
return false;
|
|
}
|
|
make_hb_elf(tid, start, size);
|
|
return true;
|
|
}
|
|
make_hb_elf(tid, start, size);
|
|
(void)memset(sys_app, 0, sizeof(sys_app));
|
|
snprintf(sys_app, sizeof(sys_app), "/system_ex/app/%s/eboot.bin", tid);
|
|
if (!copyFile("/system_ex/app/NPXS40028/eboot.bin", sys_app))
|
|
{
|
|
puts("failed to copy redis eboot.bin");
|
|
return false;
|
|
}
|
|
(void)memset(sys_app, 0, sizeof(sys_app));
|
|
snprintf(sys_app, sizeof(sys_app), "/system_ex/app/%s/sce_sys", tid);
|
|
if (!mkdir_if_necessary(sys_app))
|
|
{
|
|
return false;
|
|
}
|
|
(void)memset(sys_app, 0, sizeof(sys_app));
|
|
snprintf(sys_app, sizeof(sys_app), "/system_ex/app/%s/sce_sys/param.json",
|
|
tid);
|
|
FILE *fp = fopen(sys_app, "w+");
|
|
if (fp == NULL)
|
|
{
|
|
perror("open failed");
|
|
return false;
|
|
}
|
|
(void)memset(sys_app, 0, sizeof(sys_app));
|
|
snprintf(sys_app, sizeof(sys_app), json, tid);
|
|
fwrite(sys_app, 1, __builtin_strlen(sys_app), fp);
|
|
fclose(fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool is_valid_plugin(const unsigned char *file_buffer)
|
|
{
|
|
// Check if the prefix matches
|
|
if (strncmp((const char *)file_buffer, "etaHEN_PLUGIN", 13) != 0)
|
|
{
|
|
puts("Plugin header prefix does not match");
|
|
return false;
|
|
}
|
|
|
|
// Validate the title ID format (4 uppercase letters followed by 4 numbers)
|
|
const CustomPluginHeader *header = (const CustomPluginHeader *)file_buffer;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (header->titleID[i] < 'A' || header->titleID[i] > 'Z')
|
|
{
|
|
puts("Invalid plugin file: titleID must contain 4 uppercase letters as the start");
|
|
return false;
|
|
}
|
|
}
|
|
for (int i = 4; i < 9; ++i)
|
|
{
|
|
if (header->titleID[i] < '0' || header->titleID[i] > '9')
|
|
{
|
|
puts("Invalid plugin file: titleID must contain 5 numbers as the end");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Ensure the title ID is null-terminated
|
|
if (header->titleID[9] != '\0')
|
|
{
|
|
puts("Invalid plugin file: titleID must be null-terminated");
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (header->plugin_version[i] == '.')
|
|
{
|
|
continue;
|
|
}
|
|
else if (header->plugin_version[i] < '0' || header->plugin_version[i] > '9')
|
|
{
|
|
puts("Invalid plugin file: version must be in the following format xx.xx");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
pid_t find_pid(const char *name)
|
|
{
|
|
int mib[4] = {
|
|
CTL_KERN,
|
|
KERN_PROC,
|
|
KERN_PROC_PROC,
|
|
0};
|
|
app_info_t appinfo;
|
|
size_t buf_size;
|
|
void *buf;
|
|
|
|
int pid = -1;
|
|
// size of query response
|
|
if (sysctl(mib, 4, NULL, &buf_size, NULL, 0))
|
|
{
|
|
etaHEN_log("sysctl failed: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
// allocate memory for query response
|
|
if (!(buf = malloc(buf_size)))
|
|
{
|
|
etaHEN_log("malloc failed %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
// query the kernel for proc info
|
|
if (sysctl(mib, 4, buf, &buf_size, NULL, 0))
|
|
{
|
|
etaHEN_log("sysctl failed: %s", strerror(errno));
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
for (void *ptr = buf; ptr < (buf + buf_size);)
|
|
{
|
|
struct kinfo_proc *ki = (struct kinfo_proc *)ptr;
|
|
ptr += ki->ki_structsize;
|
|
|
|
if (sceKernelGetAppInfo(ki->ki_pid, &appinfo))
|
|
{
|
|
memset(&appinfo, 0, sizeof(appinfo));
|
|
}
|
|
|
|
if (strcmp(ki->ki_comm, name) == 0)
|
|
{
|
|
pid = ki->ki_pid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
|
|
return pid;
|
|
}
|
|
|
|
|
|
bool is_elf_file(const void *buffer, size_t size)
|
|
{
|
|
if (size < 4)
|
|
return false;
|
|
|
|
const unsigned char elf_magic[] = {0x7F, 'E', 'L', 'F'};
|
|
return memcmp(buffer, elf_magic, 4) == 0;
|
|
}
|
|
|
|
bool load_plugin(const char *path)
|
|
{
|
|
int fd = open(path, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
etaHEN_log("Failed to open file, %s (error %s)", path, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
struct stat st;
|
|
if (fstat(fd, &st) != 0)
|
|
{
|
|
etaHEN_log("Failed to get file stats");
|
|
close(fd);
|
|
return false;
|
|
}
|
|
|
|
// Allocate buffer and read the entire file.
|
|
uint8_t *buf = (uint8_t *)malloc(st.st_size);
|
|
if (!buf)
|
|
{
|
|
etaHEN_log("Failed to allocate memory for Plugin file");
|
|
close(fd);
|
|
return false;
|
|
}
|
|
|
|
if (read(fd, buf, st.st_size) != st.st_size)
|
|
{
|
|
etaHEN_log("Failed to read Plugin file");
|
|
free(buf);
|
|
close(fd);
|
|
return false;
|
|
}
|
|
close(fd);
|
|
|
|
const CustomPluginHeader *header = (const CustomPluginHeader *)buf;
|
|
const char *filename = basename(path);
|
|
|
|
if (strstr(filename, ".elf") != NULL)
|
|
{
|
|
etaHEN_log("ELF detected: %s", filename);
|
|
|
|
if (!is_elf_file(buf, st.st_size))
|
|
{
|
|
etaHEN_log("Invalid ELF file.");
|
|
notify(true, "Invalid ELF file: %s", filename);
|
|
free(buf);
|
|
return false;
|
|
}
|
|
|
|
char pbuf[256];
|
|
snprintf(pbuf, sizeof(pbuf), "/system_tmp/%s.PID", header->titleID);
|
|
|
|
pid_t pid = -1;
|
|
int f = open(pbuf, O_RDONLY);
|
|
if (f >= 0)
|
|
{
|
|
char t[32];
|
|
int r = read(f, t, sizeof(t) - 1);
|
|
close(f);
|
|
if (r > 0)
|
|
{
|
|
t[r] = 0;
|
|
pid = atoi(t);
|
|
}
|
|
}
|
|
|
|
if (pid > 0)
|
|
{
|
|
char name[32];
|
|
if (sceKernelGetProcessName(pid, name) < 0)
|
|
{
|
|
etaHEN_log("Stale plugin PID file detected for %s, removing", header->titleID);
|
|
unlink(pbuf);
|
|
pid = -1;
|
|
}
|
|
}
|
|
|
|
if (pid > 0)
|
|
{
|
|
etaHEN_log("killing pid %d (plugin: %s)", pid, header->titleID);
|
|
kill(pid, SIGKILL);
|
|
unlink(pbuf);
|
|
}
|
|
|
|
etaHEN_log("loading elf %s", filename);
|
|
pid = elfldr_spawn("/", STDOUT_FILENO, buf, header->titleID);
|
|
|
|
if (pid >= 0)
|
|
etaHEN_log(" Launched!");
|
|
else
|
|
etaHEN_log(" Already Running!");
|
|
|
|
free(buf);
|
|
|
|
f = open(pbuf, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (f >= 0)
|
|
{
|
|
if (pid >= 0)
|
|
{
|
|
char t[32];
|
|
int l = snprintf(t, sizeof(t), "%d", pid);
|
|
write(f, t, l);
|
|
}
|
|
else
|
|
{
|
|
unlink(pbuf);
|
|
}
|
|
close(f);
|
|
}
|
|
|
|
return (pid >= 0);
|
|
}
|
|
|
|
if (!is_valid_plugin(buf))
|
|
{
|
|
etaHEN_log("Invalid plugin file.");
|
|
free(buf);
|
|
return false;
|
|
}
|
|
|
|
etaHEN_log("============== Plugin info ===============");
|
|
etaHEN_log("Plugin Prefix: %s", header->prefix);
|
|
etaHEN_log("Plugin TitleID: %s", header->titleID);
|
|
etaHEN_log("Plugin Version: %s", header->plugin_version);
|
|
etaHEN_log("=========================================");
|
|
|
|
char pbuf[256];
|
|
snprintf(pbuf, sizeof(pbuf), "/system_tmp/%s.PID", header->titleID);
|
|
|
|
pid_t pid = -1;
|
|
int f = open(pbuf, O_RDONLY);
|
|
if (f >= 0)
|
|
{
|
|
char t[32];
|
|
int r = read(f, t, sizeof(t) - 1);
|
|
close(f);
|
|
if (r > 0)
|
|
{
|
|
t[r] = 0;
|
|
pid = atoi(t);
|
|
}
|
|
}
|
|
|
|
if (pid > 0)
|
|
{
|
|
char name[32];
|
|
if (sceKernelGetProcessName(pid, name) < 0)
|
|
{
|
|
etaHEN_log("Stale plugin PID file detected for %s, removing", header->titleID);
|
|
unlink(pbuf);
|
|
pid = -1;
|
|
}
|
|
}
|
|
|
|
etaHEN_log("seeing if plugin is running");
|
|
if (pid > 0)
|
|
{
|
|
etaHEN_log("killing pid %d (plugin: %s)", pid, header->titleID);
|
|
kill(pid, SIGKILL);
|
|
unlink(pbuf);
|
|
}
|
|
|
|
uint8_t *elf = get_elf_header_address(buf);
|
|
make_plugin_app(header->titleID, elf, st.st_size - sizeof(CustomPluginHeader));
|
|
|
|
etaHEN_log("loading plugin %s", path);
|
|
pid = elfldr_spawn("/", STDOUT_FILENO, elf, header->titleID);
|
|
bool success = (pid >= 0);
|
|
if (success)
|
|
etaHEN_log(" Launched!");
|
|
else
|
|
etaHEN_log(" Failed to launch plugin");
|
|
|
|
f = open(pbuf, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (f >= 0)
|
|
{
|
|
if (success)
|
|
{
|
|
char t[32];
|
|
int l = snprintf(t, sizeof(t), "%d", pid);
|
|
write(f, t, l);
|
|
}
|
|
else
|
|
{
|
|
unlink(pbuf);
|
|
}
|
|
close(f);
|
|
}
|
|
|
|
free(buf);
|
|
return success;
|
|
}
|
|
|
|
int launchApp(const char *titleId)
|
|
{
|
|
int id = 0;
|
|
|
|
uint32_t res = sceUserServiceGetForegroundUser(&id);
|
|
if (res != 0)
|
|
{
|
|
printf("sceUserServiceGetForegroundUser failed: 0x%x\n", res);
|
|
return res;
|
|
}
|
|
etaHEN_log("[LA] user id %u", id);
|
|
|
|
LncAppParam param;
|
|
param.sz = sizeof(LncAppParam);
|
|
param.user_id = id;
|
|
param.app_opt = 0;
|
|
param.crash_report = 0;
|
|
param.check_flag = Flag_None;
|
|
|
|
|
|
puts("calling sceLncUtilLaunchApp");
|
|
int err = sceLncUtilLaunchApp(titleId, NULL, ¶m);
|
|
etaHEN_log("sceLncUtilLaunchApp returned 0x%x", (uint32_t)err);
|
|
if (err >= 0)
|
|
{
|
|
return err;
|
|
}
|
|
switch ((uint32_t)err)
|
|
{
|
|
case SCE_LNC_UTIL_ERROR_ALREADY_RUNNING:
|
|
etaHEN_log("app %s is already running", titleId);
|
|
break;
|
|
case SCE_LNC_ERROR_APP_NOT_FOUND:
|
|
etaHEN_log("app %s not found", titleId);
|
|
notify(true, "app %s not found", titleId);
|
|
break;
|
|
default:
|
|
etaHEN_log("[LA] unknown error 0x%x", (uint32_t)err);
|
|
// notify(true, "unknown error 0x%llx", (uint32_t)err);
|
|
break;
|
|
}
|
|
return err;
|
|
}
|