1
0

Initial commit

Nothing has actually been tested yet.
This commit is contained in:
Patrick Gaskin
2020-04-22 00:13:02 -04:00
commit 7e5320ff6f
18 changed files with 692 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
# make gitignore
/KoboRoot
/src/libnmi.so
/src/qtplugin.moc
/src/qtplugin.o
/src/failsafe.o
/src/init.o
/src/subsys_c.o
/src/config.o
/src/dlhook.o
/src/menu.o
/src/subsys_cc.o

94
Makefile Normal file
View File

@@ -0,0 +1,94 @@
CROSS_COMPILE = arm-nickel-linux-gnueabihf-
MOC = moc
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
PKG_CONFIG = $(CROSS_COMPILE)pkg-config
DESTDIR =
ifneq ($(if $(MAKECMDGOALS),$(if $(filter-out clean gitignore install koboroot,$(MAKECMDGOALS)),YES,NO),YES),YES)
$(info -- Skipping configure)
else
PTHREAD_CFLAGS := -pthread
PTHREAD_LIBS := -pthread
define pkgconf =
$(if $(filter-out undefined,$(origin $(1)_CFLAGS) $(origin $(1)_LIBS)) \
,$(info -- Using provided CFLAGS and LIBS for $(2)) \
,$(if $(shell $(PKG_CONFIG) --exists $(2) >/dev/null 2>/dev/null && echo y) \
,$(info -- Found $(2) ($(shell $(PKG_CONFIG) --modversion $(2))) with pkg-config) \
$(eval $(1)_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags $(2))) \
$(eval $(1)_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs $(2))) \
$(if $(3) \
,$(if $(shell $(PKG_CONFIG) $(3) $(2) >/dev/null 2>/dev/null && echo y) \
,$(info .. Satisfies constraint $(3)) \
,$(info .. Does not satisfy constraint $(3)) \
$(error Dependencies do not satisfy constraints)) \
,) \
,$(info -- Could not automatically detect $(2) with pkg-config. Please specify $(1)_CFLAGS and/or $(1)_LIBS manually) \
$(error Missing dependencies)))
endef
$(call pkgconf,QT5CORE,Qt5Core)
$(call pkgconf,QT5WIDGETS,Qt5Widgets)
# temporary workaround for broken cflags in kobo-toolchain Docker image:
QT5CORE_CFLAGS := $(shell echo "$(QT5CORE_CFLAGS)" | sed 's:/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/:/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/:g')
QT5WIDGETS_CFLAGS := $(shell echo "$(QT5WIDGETS_CFLAGS)" | sed 's:/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/:/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/:g')
override CFLAGS += -I/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/include
override CXXFLAGS += -I/toolchain/arm-nickel-linux-gnueabihf/arm-nickel-linux-gnueabihf/sysroot/include
override CFLAGS += -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard -mthumb -std=gnu11
override CXXFLAGS += -march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard -mthumb -std=gnu++11
override LDFLAGS += -Wl,-rpath,/usr/local/Kobo -Wl,-rpath,/usr/local/Qt-5.2.1-arm/lib
endif
all: src/libnmi.so
clean:
rm -f $(GENERATED)
gitignore:
echo '# make gitignore' > .gitignore
echo '$(GENERATED)' | \
sed 's/ /\n/g' | \
sed 's/^./\/&/' >> .gitignore
install:
install -Dm644 src/libnmi.so $(DESTDIR)/usr/local/Kobo/plugins/libnmi.so
install -Dm644 res/doc $(DESTDIR)/mnt/onboard/.adds/nmi/doc
koboroot:
make install DESTDIR=KoboRoot
tar czf KoboRoot.tgz -C KoboRoot .; rm -rf KoboRoot
.PHONY: all clean gitignore install koboroot
override GENERATED += KoboRoot
src/libnmi.so: override CFLAGS += $(PTHREAD_CFLAGS) -fPIC
src/libnmi.so: override CXXFLAGS += $(PTHREAD_CFLAGS) $(QT5CORE_CFLAGS) $(QT5WIDGETS_CFLAGS) -fPIC
src/libnmi.so: override LDFLAGS += $(PTHREAD_LIBS) $(QT5CORE_LIBS) $(QT5WIDGETS_LIBS) -ldl -Wl,-soname,libnmi.so
src/libnmi.so: src/qtplugin.o src/init.o src/config.o src/dlhook.o src/failsafe.o src/menu.o src/subsys_c.o src/subsys_cc.o
override LIBRARIES += src/libnmi.so
override MOCS += src/qtplugin.moc
define patw =
$(foreach dir,src res,$(dir)/*$(1))
endef
define rpatw =
$(patsubst %$(1),%$(2),$(foreach w,$(call patw,$(1)),$(wildcard $(w))))
endef
$(LIBRARIES): src/%.so:
$(CC) -shared -o $@ $^ $(LDFLAGS)
$(MOCS): %.moc: %.h
$(MOC) $< -o $@
$(patsubst %.moc,%.o,$(MOCS)): %.o: %.moc
$(CXX) -xc++ $(CXXFLAGS) -c $< -o $@
$(call rpatw,.c,.o): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
$(call rpatw,.cc,.o): %.o: %.cc
$(CXX) $(CXXFLAGS) -c $< -o $@
override GENERATED += $(LIBRARIES) $(MOCS) $(patsubst %.moc,%.o,$(MOCS)) $(call rpatw,.c,.o) $(call rpatw,.cc,.o)

41
res/doc Normal file
View File

@@ -0,0 +1,41 @@
# nickel-menu-inject (libnmi.so)
#
# This tool injects menu items into Nickel.
#
# It should work on firmware 4.6+, but it has only been tested on 4.20.14622. It
# is perfectly safe to try out on any newer firmware version, as it has a lot of
# error checking, and a failsafe mechanism which automatically uninstalls it as
# a last resort.
#
# Place your configuration files in this folder (/mnt/onboard/.kobo/.adds/nmi).
# They can be named anything, and should consist of multiple lines either
# starting with # for a comment, or in the one of the following formats:
#
# menu_item:<location>:<label>:<subsystem>:<arg>
# Adds a menu item (spaces around fields are ignored).
#
# <location> the menu to add the item to, one of:
# main - the menu in the top-left corner of the home screen
# reader - the overflow menu in the reader
# <label> the label to show on the menu item (must not contain :)
# <subsystem> the type of action to run, one of:
# dbg_syslog - writes a message to syslog (for testing)
# dbg_error - always returns an error (for testing)
# kfmon - triggers a kfmon action (coming soon)
# nickel_setting - toggles a boolean setting (coming soon)
# <arg> the argument passed to the subsystem:
# dbg_syslog - the text to write
# dbg_error - the error message
# kfmon - TODO
# nickel_setting - one of:
# invert - toggles FeatureSettings.InvertScreen (all versions)
# screenshots - toggles FeatureSettings.Screenshots (all versions)
#
# uninstall
# Makes nickel-menu-inject uninstall itself.
#
# For example, you might have a configuration file in KOBOeReader/.adds/nmi/mystuff like:
#
# menu_item :main :Show an error :dbg_error :This is an error message!
# menu_item :reader :Invert Screen :nickel_setting :invert
#

42
src/config.c Normal file
View File

@@ -0,0 +1,42 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "util.h"
#include "subsys_c.h"
#include "subsys_cc.h"
// TODO: actually parse it
nmi_menu_entry_t *nmi_config_parse(size_t *n, char **err_out) {
#define NMI_ERR_RET NULL
NMI_ASSERT(n, "required argument is null");
nmi_menu_entry_t *me = calloc(4, sizeof(nmi_menu_entry_t));
me[0].loc = NMI_MENU_LOCATION_MAIN_MENU;
me[0].lbl = strdup("Test Log (main)");
me[0].arg = strdup("It worked!");
me[0].execute = nmi_subsys_dbgsyslog;
me[1].loc = NMI_MENU_LOCATION_READER_MENU;
me[1].lbl = strdup("Test Log (reader)");
me[1].arg = strdup("It worked!");
me[1].execute = nmi_subsys_dbgsyslog;
me[2].loc = NMI_MENU_LOCATION_MAIN_MENU;
me[2].lbl = strdup("Test Error (main)");
me[2].arg = strdup("It's a fake error!");
me[2].execute = nmi_subsys_dbgerror;
me[3].loc = NMI_MENU_LOCATION_READER_MENU;
me[3].lbl = strdup("Test Error (reader)");
me[3].arg = strdup("It's a fake error!");
me[3].execute = nmi_subsys_dbgerror;
*n = 4;
NMI_RETURN_OK(me);
#undef NMI_ERR_RET
}

15
src/config.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef NMI_CONFIG_H
#define NMI_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#include "menu.h"
// TODO
nmi_menu_entry_t *nmi_config_parse(size_t *n, char **err_out);
#ifdef __cplusplus
}
#endif
#endif

107
src/dlhook.c Normal file
View File

@@ -0,0 +1,107 @@
#define _GNU_SOURCE // dladdr, dladdr1, dlinfo, Dl_info
#include <dlfcn.h>
#include <elf.h>
#include <link.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include "dlhook.h"
#include "util.h"
#ifndef ELFW
#define ELFW(type) _ElfW(ELF, __ELF_NATIVE_CLASS, type)
#endif
#if __x86_64__
#define R_JUMP_SLOT R_X86_64_JUMP_SLOT
#else
#if __arm__
#define R_JUMP_SLOT R_ARM_JUMP_SLOT
#else
#error Please define the architecture-specific R_*_JUMP_SLOT type.
#endif
#endif
void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out) {
#define NMI_ERR_RET NULL
NMI_ASSERT(handle && symname && target, "BUG: required arguments are null");
// the link_map conveniently gives use the base address without /proc/maps, and it gives us a pointer to dyn
struct link_map *lm;
NMI_ASSERT(!dlinfo(handle, RTLD_DI_LINKMAP, &lm), "could not get link_map for lib");
NMI_LOG("lib %s is mapped at %lx", lm->l_name, lm->l_addr);
// stuff extracted from DT_DYNAMIC
struct {
bool plt_is_rela;
union {
ElfW(Addr) _;
ElfW(Rel) *rel;
ElfW(Rela) *rela;
} plt;
ElfW(Xword) plt_sz;
ElfW(Xword) plt_ent_sz;
ElfW(Sym) *sym;
const char *str;
} dyn = {0};
// parse DT_DYNAMIC
for (size_t i = 0; lm->l_ld[i].d_tag != DT_NULL; i++) {
switch (lm->l_ld[i].d_tag) {
case DT_PLTREL: dyn.plt_is_rela = lm->l_ld[i].d_un.d_val == DT_RELA; break; // .rel.plt - is Rel or Rela
case DT_JMPREL: dyn.plt._ = lm->l_ld[i].d_un.d_ptr; break; // .rel.plt - offset from base
case DT_PLTRELSZ: dyn.plt_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - size in bytes
case DT_RELENT: dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rel
case DT_RELAENT: dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rela
case DT_SYMTAB: dyn.sym = (ElfW(Sym)*)(lm->l_ld[i].d_un.d_val); break; // .dynsym - offset
case DT_STRTAB: dyn.str = (const char*)(lm->l_ld[i].d_un.d_val); break; // .dynstr - offset
}
}
NMI_LOG("DT_DYNAMIC: plt_is_rela=%d plt=%p plt_sz=%lu plt_ent_sz=%lu sym=%p str=%p", dyn.plt_is_rela, (void*)(dyn.plt)._, dyn.plt_sz, dyn.plt_ent_sz, dyn.sym, dyn.str);
ElfW(Xword) plt_ent_n = dyn.plt_sz / dyn.plt_ent_sz;
NMI_ASSERT(dyn.plt_sz%dyn.plt_ent_sz == 0, ".rel.plt length is not a multiple of plt_ent_sz");
NMI_ASSERT((dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel)) == dyn.plt_ent_sz, "size mismatch (%lu != %lu)", dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel), dyn.plt_ent_sz);
// parse the dynamic symbol table, resolve symbols to relocations, then GOT entries
for (size_t i = 0; i < plt_ent_n; i++) {
// note: Rela is a superset of Rel
ElfW(Rel) *rel = dyn.plt_is_rela
? (ElfW(Rel)*)(&dyn.plt.rela[i])
: &dyn.plt.rel[i];
NMI_ASSERT(ELFW(R_TYPE)(rel->r_info) == R_JUMP_SLOT, "not a jump slot relocation (R_TYPE=%lu)", ELFW(R_TYPE)(rel->r_info));
ElfW(Sym) *sym = &dyn.sym[ELFW(R_SYM)(rel->r_info)];
NMI_ASSERT(ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented");
NMI_ASSERT(ELFW(ST_TYPE)(sym->st_info) == STT_FUNC, "not a function symbol (ST_TYPE=%d)", ELFW(ST_TYPE)(sym->st_info));
NMI_ASSERT(ELFW(ST_BIND)(sym->st_info) == STB_GLOBAL, "not a globally bound symbol (ST_BIND=%d)", ELFW(ST_BIND)(sym->st_info));
const char *str = &dyn.str[sym->st_name];
if (strcmp(str, symname))
continue;
void **gotoff = (void**)(lm->l_addr + rel->r_offset);
NMI_LOG("found symbol %s (gotoff=%p [mapped=%p])", str, (void*)(rel->r_offset), gotoff);
// remove memory protection (to bypass RELRO if it is enabled)
// note: this doesn't seem to be used on the Kobo, but we might as well stay on the safe side (plus, I test this on my local machine too)
// note: the only way to read the current memory protection is to parse /proc/maps, but there's no harm in unprotecting it again if it's not protected
// note: we won't put it back afterwards, as if full RELRO (i.e. RTLD_NOW) wasn't enabled, it would cause segfaults when resolving symbols later on
long pagesize = sysconf(_SC_PAGESIZE);
NMI_ASSERT(pagesize != -1, "could not get memory page size");
void *gotpage = (void*)((size_t)(gotoff) & ~(pagesize-1));
NMI_ASSERT(!mprotect(gotpage, pagesize, PROT_READ|PROT_WRITE), "could not set memory protection of page %p containing %p to PROT_READ|PROT_WRITE", gotpage, gotoff);
// replace the target offset
void *orig = *gotoff;
*gotoff = target;
NMI_RETURN_OK(orig);
}
NMI_RETURN_ERR("could not find symbol");
#undef NMI_err_ret
}

18
src/dlhook.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef NMI_DLHOOK_H
#define NMI_DLHOOK_H
#ifdef __cplusplus
extern "C" {
#endif
// nmi_dlhook takes a lib handle from dlopen and redirects the specified symbol
// to another, returning a pointer to the original one. Only calls from within
// that library itself are affected (because it replaces that library's GOT). If
// an error occurs, NULL is returned and if err is a valid pointer, it is set to
// a malloc'd string describing it. This function requires glibc and Linux. It
// should work on any architecture, and it should be resilient to most errors.
void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out);
#ifdef __cplusplus
}
#endif
#endif

66
src/failsafe.c Normal file
View File

@@ -0,0 +1,66 @@
#define _GNU_SOURCE // asprintf, Dl_info, dladdr
#include <link.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "failsafe.h"
#include "util.h"
struct nmi_failsafe_t {
char *orig;
char *tmp;
int delay;
};
nmi_failsafe_t *nmi_failsafe_create(char **err_out) {
#define NMI_ERR_RET NULL
NMI_LOG("failsafe: allocating memory");
nmi_failsafe_t *fs;
NMI_ASSERT((fs = calloc(1, sizeof(nmi_failsafe_t))), "could not allocate memory");
NMI_LOG("failsafe: finding filenames");
Dl_info info;
NMI_ASSERT(dladdr(nmi_failsafe_create, &info), "could not find own path");
NMI_ASSERT(info.dli_fname, "dladdr did not return a filename");
NMI_ASSERT((fs->orig = realpath(info.dli_fname, NULL)), "could not resolve %s", info.dli_fname);
NMI_ASSERT(asprintf(&fs->tmp, "%s.failsafe", fs->orig) != -1, "could not generate temp filename");
NMI_LOG("failsafe: renaming %s to %s", fs->orig, fs->tmp);
NMI_ASSERT(!rename(fs->orig, fs->tmp), "could not rename lib");
NMI_LOG("failsafe: ensuring own lib remains in memory even if it is dlclosed after being loaded with a dlopen");
NMI_ASSERT(!dlopen(fs->orig, RTLD_LAZY|RTLD_NODELETE), "could not dlopen self");
NMI_RETURN_OK(fs);
#undef NMI_ERR_RET
}
static void *_nmi_failsafe_destroy(void* _fs) {
nmi_failsafe_t *fs = (nmi_failsafe_t*)(_fs);
NMI_LOG("failsafe: restoring after %d seconds", fs->delay);
sleep(fs->delay);
NMI_LOG("failsafe: renaming %s to %s", fs->tmp, fs->orig);
if (!rename(fs->tmp, fs->orig))
NMI_LOG("error: could not rename lib");
NMI_LOG("failsafe: freeing memory");
free(fs->orig);
free(fs->tmp);
free(fs);
return NULL;
}
void nmi_failsafe_destroy(nmi_failsafe_t *fs, int delay) {
fs->delay = delay;
NMI_LOG("failsafe: starting restore thread");
pthread_t t;
pthread_create(&t, NULL, _nmi_failsafe_destroy, fs);
}

25
src/failsafe.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef NMI_FAILSAFE_H
#define NMI_FAILSAFE_H
#ifdef __cplusplus
extern "C" {
#endif
// nmi_failsafe_t is a failsafe mechanism for injected shared libraries. It
// works by moving it to a temporary file (so it won't get loaded the next time)
// and dlopening itself (to prevent it from being unloaded if it is dlclose'd by
// whatever dlopen'd it). When it is disarmed, the library is moved back to its
// original location.
typedef struct nmi_failsafe_t nmi_failsafe_t;
// nmi_failsafe_create allocates and arms a failsafe mechanism for the currently
// dlopen'd or LD_PRELOAD'd library.
nmi_failsafe_t *nmi_failsafe_create(char **err_out);
// nmi_failsafe_destroy starts a pthread which disarms and frees the failsafe
// after a delay. The nmi_failsafe_t must not be used afterwards.
void nmi_failsafe_destroy(nmi_failsafe_t *fs, int delay);
#ifdef __cplusplus
}
#endif
#endif

55
src/init.c Normal file
View File

@@ -0,0 +1,55 @@
#define _GNU_SOURCE
#include <link.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdlib.h>
#include "config.h"
#include "failsafe.h"
#include "menu.h"
#include "util.h"
__attribute__((constructor)) void nmi_init() {
char *err;
NMI_LOG("init: creating failsafe");
nmi_failsafe_t *fs;
if (!(fs = nmi_failsafe_create(&err)) && err) {
NMI_LOG("error: could not create failsafe: %s, stopping", err);
free(err);
goto stop;
}
// TODO: handle uninstall
NMI_LOG("init: parsing config");
size_t entries_n;
nmi_menu_entry_t *entries;
if (!(entries = nmi_config_parse(&entries_n, &err)) && err) {
NMI_LOG("error: could not parse config: %s, stopping", err);
free(err);
goto stop_fs;
}
NMI_LOG("init: opening libnickel");
void *libnickel = dlopen("libnickel.so.1.0.0", RTLD_LAZY|RTLD_NODELETE);
if (!libnickel) {
NMI_LOG("error: could not dlopen libnickel, stopping");
goto stop_fs;
}
NMI_LOG("init: hooking libnickel");
if (nmi_menu_hook(libnickel, entries, entries_n, &err) && err) {
NMI_LOG("error: could not hook libnickel: %s, stopping", err);
free(err);
goto stop_fs;
}
stop_fs:
NMI_LOG("init: destroying failsafe");
nmi_failsafe_destroy(fs, 20);
stop:
NMI_LOG("init: done");
return;
}

58
src/menu.cc Normal file
View File

@@ -0,0 +1,58 @@
#include <QAction>
#include <QMenu>
#include <QString>
#include <QWidget>
#include <cstdlib>
#include "dlhook.h"
#include "menu.h"
#include "util.h"
typedef QWidget MenuTextItem; // it's actually a subclass, but we don't need it's functionality directly, so we'll stay on the safe side
// AbstractNickelMenuController::createMenuTextItem creates a menu item in the
// given QMenu with the specified label. The first bool parameter adds space to
// the left of the item for a checkbox, and the second bool checks it (see the
// search menu for an example of this being used). IDK what the last parameter
// is for, but I suspect it might be passed to QObject::setObjectName (I haven't
// tested it directly, and it's been empty in the menus I've tried so far).
static MenuTextItem* (*AbstractNickelMenuController_createMenuTextItem_orig)(void*, QMenu*, QString const&, bool, bool, QString const&) = NULL;
static MenuTextItem* (*AbstractNickelMenuController_createMenuTextItem)(void*, QMenu*, QString const&, bool, bool, QString const&);
// AbstractNickelMenuController::createAction finishes adding the action to the
// menu. IDK what the bool params are for, but thing1 and thing2 always seem to
// be true (I know one or the other is for if it is greyed out).
static QAction* (*AbstractNickelMenuController_createAction)(void*, QMenu*, QWidget*, bool, bool, bool);
// ConfirmationDialogFactory::showOKDialog does what it says, with the provided
// title and body text. Note that it should be called from the GUI thread (or
// a signal handler).
static void (*ConfirmationDialogFactory_showOKDialog)(QString const&, QString const&);
extern "C" MenuTextItem* AbstractNickelMenuController_createMenuTextItem_hook(void* _this, QMenu* menu, QString const& label, bool checkable, bool checked, QString const& thingy) {
NMI_LOG("AbstractNickelMenuController::createMenuTextItem(%p, `%s`, %d, %d, `%s`)", menu, qPrintable(label), checkable, checked, qPrintable(thingy));
// TODO: add items
return AbstractNickelMenuController_createMenuTextItem_orig(_this, menu, label, checkable, checked, thingy);
}
extern "C" int nmi_menu_hook(void *libnickel, nmi_menu_entry_t *entries, size_t entries_n, char **err_out) {
#define NMI_ERR_RET 1
NMI_SYM(AbstractNickelMenuController_createMenuTextItem, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_");
NMI_SYM(AbstractNickelMenuController_createAction, "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb");
NMI_SYM(ConfirmationDialogFactory_showOKDialog, "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_");
NMI_ASSERT(AbstractNickelMenuController_createMenuTextItem, "unsupported firmware: could not find AbstractNickelMenuController::createMenuTextItem(void* _this, QMenu*, QString, bool, bool, QString const&)");
NMI_ASSERT(AbstractNickelMenuController_createAction, "unsupported firmware: could not find AbstractNickelMenuController::createAction(void* _this, QMenu*, QWidget*, bool, bool, bool)");
NMI_ASSERT(AbstractNickelMenuController_createAction, "unsupported firmware: could not find ConfirmationDialogFactory::showOKDialog(QString const&, QString const&)");
char *err;
reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_orig) = nmi_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", reinterpret_cast<void*&>(AbstractNickelMenuController_createMenuTextItem_hook), &err);
NMI_ASSERT(AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", err);
#undef NMI_ERR_RET
}
// TODO

31
src/menu.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef NMI_MENU_H
#define NMI_MENU_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
typedef enum {
NMI_MENU_LOCATION_MAIN_MENU = 1,
NMI_MENU_LOCATION_READER_MENU = 2,
} nmi_menu_location_t;
typedef struct {
nmi_menu_location_t loc;
const char *lbl;
const char *arg;
int (*execute)(const char *arg, char **out_err); // can block, must return 0 on success, nonzero with out_err set to the malloc'd error message on error
} nmi_menu_entry_t;
// nmi_menu_hook hooks a dlopen'd libnickel handle to add the specified menus,
// and returns 0 on success, or 1 with err_out (if provided) set to the malloc'd
// error message on error. The provided configuration and all pointers it
// references must remain valid for the lifetime of the program (i.e. not stack
// allocated).
int nmi_menu_hook(void *libnickel, nmi_menu_entry_t *entries, size_t entries_n, char **err_out);
#ifdef __cplusplus
}
#endif
#endif

9
src/qtplugin.h Normal file
View File

@@ -0,0 +1,9 @@
#ifdef __cplusplus
#include <QObject>
#include <QtPlugin>
class NMIPlugin : public QObject {
Q_OBJECT
Q_PLUGIN_METADATA(IID "net.pgaskin.nmi");
};
#endif

23
src/subsys_c.c Normal file
View File

@@ -0,0 +1,23 @@
#define _GNU_SOURCE // asprintf
#include "subsys_c.h"
#include "util.h"
int nmi_subsys_dbgsyslog(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
NMI_LOG("dbgsyslog: %s", arg);
NMI_RETURN_OK(0);
#undef NMI_ERR_RET
}
int nmi_subsys_dbgerror(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
NMI_RETURN_ERR("%s", arg);
#undef NMI_ERR_RET
}
int nmi_subsys_kfmon(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
NMI_RETURN_ERR("not implemented yet"); // TODO
#undef NMI_ERR_RET
}

14
src/subsys_c.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef NMI_SUBSYS_C_H
#define NMI_SUBSYS_C_H
#ifdef __cplusplus
extern "C" {
#endif
int nmi_subsys_dbgsyslog(const char *arg, char **err_out);
int nmi_subsys_dbgerror(const char *arg, char **err_out);
int nmi_subsys_kfmon(const char *arg, char **err_out);
#ifdef __cplusplus
}
#endif
#endif

10
src/subsys_cc.cc Normal file
View File

@@ -0,0 +1,10 @@
#include <dlfcn.h>
#include "subsys_cc.h"
#include "util.h"
extern "C" int nmi_subsys_nickelsetting(const char *arg, char **err_out) {
#define NMI_ERR_RET 1
NMI_RETURN_ERR("not implemented yet"); // TODO
#undef NMI_ERR_RET
}

13
src/subsys_cc.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef NMI_SUBSYS_CC_H
#define NMI_SUBSYS_CC_H
#ifdef __cplusplus
extern "C" {
#endif
int nmi_subsys_dbgsyslog(const char *arg, char **err_out);
int nmi_subsys_dbgerror(const char *arg, char **err_out);
#ifdef __cplusplus
}
#endif
#endif

56
src/util.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef NMI_UTIL_H
#define NMI_UTIL_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE // asprintf
#endif
#include <stdio.h>
#include <syslog.h>
// A bunch of useful macros to simplify error handling and logging.
// NMI_LOG writes a log message.
#define NMI_LOG(fmt, ...) syslog(LOG_DEBUG, "(nickel-menu-inject) " fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__)
// NMI_RETURN returns ret, and if ret is NMI_ERR_RET and err_out is not NULL, it
// writes the formatted error message to *err_out as a malloc'd string. The
// arguments may or may not be evaluated more than once.
#define NMI_RETURN(ret, fmt, ...) do { \
typeof(ret) _ret = (ret); \
if (err_out) { \
if (_ret == NMI_ERR_RET) asprintf(err_out, fmt " (%s:%d)", ##__VA_ARGS__, __FILE__, __LINE__); \
else *err_out = NULL; \
} \
return _ret; \
} while (0)
// NMI_ASSERT is like assert, but it writes the formatted error message to
// err_out as a malloc'd string, and returns NMI_ERR_RET. Cond will always be
// evaluated exactly once. The other arguments may or may not be evaluated one
// or more times.
#define NMI_ASSERT(cond, fmt, ...) do { \
if (!(cond)) NMI_RETURN(NMI_ERR_RET, fmt " (assertion failed: %s)", ##__VA_ARGS__, #cond); \
} while (0)
// NMI_RETURN_ERR is the same as NMI_RETURN(NMI_ERR_RET, fmt, ...).
#define NMI_RETURN_ERR(fmt, ...) NMI_RETURN(NMI_ERR_RET, fmt, ##__VA_ARGS__)
// NMI_RETURN_OK is the same as NMI_RETURN(ret, "").
#define NMI_RETURN_OK(ret) NMI_RETURN(ret, "")
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
#include <dlfcn.h>
// NMI_SYM loads a symbol from the global scope.
#define NMI_SYM(var, sym) reinterpret_cast<void*&>(var) = dlsym(RTLD_DEFAULT, sym)
#endif
#endif