From cca07e5557213c632ed5d2c7f841c25efe375daa Mon Sep 17 00:00:00 2001 From: nicejs-is-cool Date: Thu, 28 May 2026 21:52:10 -0300 Subject: [PATCH] getLuaArch() (hell) --- .gitmodules | 4 + Makefile | 3 +- .../neoflock/NeoNucleus/nn_Architecture.java | 9 +- src/native/carbon.cpp | 6 + src/native/carbon.hpp | 1 + src/native/luaarch.h | 3 + src/native/main.cpp | 6 + .../org_neoflock_NeoNucleus_NativeBindings.h | 8 + src/tla-static/Makefile | 85 + src/tla-static/foreign/lua54 | 1 + src/tla-static/luaarch.c | 870 ++++++ src/tla-static/machine.lua | 581 ++++ src/tla-static/neonucleus.h | 2437 +++++++++++++++++ 13 files changed, 4012 insertions(+), 2 deletions(-) create mode 100644 src/native/luaarch.h create mode 100644 src/tla-static/Makefile create mode 160000 src/tla-static/foreign/lua54 create mode 100644 src/tla-static/luaarch.c create mode 100644 src/tla-static/machine.lua create mode 100644 src/tla-static/neonucleus.h diff --git a/.gitmodules b/.gitmodules index bdc357e..47276c0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "NeoNucleus"] path = NeoNucleus url = https://gitea.codersquack.nl/NeoFlock/NeoNucleus.git +[submodule "src/tla-static/foreign/lua54"] + path = src/tla-static/foreign/lua54 + url = https://github.com/lua/lua + branch = v5.4 diff --git a/Makefile b/Makefile index 0e5e3a3..f317e40 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ JAVA_HOME=/usr/lib/jvm/java-25-openjdk/ BASE_NPATH=./src/native/ JAVA_SRCS = $(wildcard ./src/main/org/neoflock/NeoNucleus/*.java) JAVA_CLASSES = $(wildcard ./src/main/org/neoflock/NeoNucleus/*.class) +TLA_PATH=./src/tla-static CPP_SRCS = $(wildcard ${BASE_NPATH}/*.cpp) OBJS = $(CPP_SRCS:.cpp=.o) @@ -17,7 +18,7 @@ all: $(TARGET) $(TARGET): $(OBJS) g++ -shared -o ${BASE_NPATH}/$(TARGET) \ - $(OBJS) ${BASE_NPATH}/libneonucleus.a -lc + $(OBJS) ${BASE_NPATH}/libneonucleus.a $(TLA_PATH)/libtla.a -lc %.o: %.cpp $(CXX) -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux $(CFLAGS) \ $< -o $@ diff --git a/src/main/org/neoflock/NeoNucleus/nn_Architecture.java b/src/main/org/neoflock/NeoNucleus/nn_Architecture.java index f3b4ec4..179772d 100644 --- a/src/main/org/neoflock/NeoNucleus/nn_Architecture.java +++ b/src/main/org/neoflock/NeoNucleus/nn_Architecture.java @@ -9,7 +9,13 @@ public final class nn_Architecture extends PointerBackedClass implements Manuall public nn_Architecture(String name, Function handler) { // string is a placeholder; please fix this.name = name; this.handler = handler; - + } + private nn_Architecture(String name) { // this overload is used by JNI when returning instances of native nn_Architecture(s) + this.name = name; + this.handler = (String tmp) -> { + // maybe in the future we can let this happen idk + throw new IllegalCallerException("Calling native architecture handlers from Java is not allowed!"); + }; } private int handleNative() { // i aint gonna be trying to call that Function from jni man @@ -18,4 +24,5 @@ public final class nn_Architecture extends PointerBackedClass implements Manuall public native boolean allocate(); public native boolean free(); + } diff --git a/src/native/carbon.cpp b/src/native/carbon.cpp index dc6e059..15a9f94 100644 --- a/src/native/carbon.cpp +++ b/src/native/carbon.cpp @@ -101,6 +101,12 @@ nn_Exit From_nn_ComputerState(JNIEnv* env, jobject a) { jint value = env->CallIntMethod(a, ordMID); return (nn_Exit) value; } +jobject Carbon::Map::To_nn_Architecture(JNIEnv* env, nn_Architecture a) { + jclass clazz = env->FindClass("org/neoflock/NeoNucleus/nn_Architecture"); + jmethodID consMID = env->GetMethodID(clazz, "", "(Ljava/lang/String;)V"); + jstring str = env->NewStringUTF(a.name); + return env->NewObject(clazz, consMID, str); +} namespace Carbon::Exceptions { CARBON_EXCEPTION_FUNC(ThrowNullPtr, "java/lang/NullPointerException"); } \ No newline at end of file diff --git a/src/native/carbon.hpp b/src/native/carbon.hpp index 1590cd7..3db712e 100644 --- a/src/native/carbon.hpp +++ b/src/native/carbon.hpp @@ -28,6 +28,7 @@ namespace Carbon { nn_Exit From_nn_Exit(JNIEnv* env, jobject a); jobject To_nn_ComputerState(JNIEnv* env, nn_ComputerState a); nn_ComputerState From_nn_ComputerState(JNIEnv* env, jobject a); + jobject To_nn_Architecture(JNIEnv* env, nn_Architecture a); } typedef struct JavaObjectTarget { // i might lowkey drop this struct diff --git a/src/native/luaarch.h b/src/native/luaarch.h new file mode 100644 index 0000000..9ade59f --- /dev/null +++ b/src/native/luaarch.h @@ -0,0 +1,3 @@ +#include "neonucleus.h" + +nn_Architecture getLuaArch(); diff --git a/src/native/main.cpp b/src/native/main.cpp index 85aac74..a69b939 100644 --- a/src/native/main.cpp +++ b/src/native/main.cpp @@ -3,6 +3,7 @@ #include "neonucleus.h" #include "ncomplib.h" #include +#include "luaarch.h" #include "carbon.hpp" @@ -175,4 +176,9 @@ JNIEXPORT jobject JNICALL Java_org_neoflock_NeoNucleus_NativeBindings_nn_1getCom nn_Computer* nnPC = (nn_Computer*) Carbon::PointerBacked::GetPointer(env, computer); NULLPTR_CHECK(nnPC, nn_Component); return Carbon::Map::To_nn_ComputerState(env, nn_getComputerState(nnPC)); +} + +JNIEXPORT jobject JNICALL Java_org_neoflock_NeoNucleus_NativeBindings_getLuaArch + (JNIEnv * env, jclass) { + return Carbon::Map::To_nn_Architecture(env, getLuaArch()); } \ No newline at end of file diff --git a/src/native/org_neoflock_NeoNucleus_NativeBindings.h b/src/native/org_neoflock_NeoNucleus_NativeBindings.h index bbf9d54..83d067c 100644 --- a/src/native/org_neoflock_NeoNucleus_NativeBindings.h +++ b/src/native/org_neoflock_NeoNucleus_NativeBindings.h @@ -7,6 +7,14 @@ #ifdef __cplusplus extern "C" { #endif +/* + * Class: org_neoflock_NeoNucleus_NativeBindings + * Method: getLuaArch + * Signature: ()Lorg/neoflock/NeoNucleus/nn_Architecture; + */ +JNIEXPORT jobject JNICALL Java_org_neoflock_NeoNucleus_NativeBindings_getLuaArch + (JNIEnv *, jclass); + /* * Class: org_neoflock_NeoNucleus_NativeBindings * Method: nn_initContext diff --git a/src/tla-static/Makefile b/src/tla-static/Makefile new file mode 100644 index 0000000..6c36520 --- /dev/null +++ b/src/tla-static/Makefile @@ -0,0 +1,85 @@ +#BIN=neonucleus +#DYNLIB=libneonucleus.so +#LIB=libneonucleus.a +LIB=libtla.a + +CC=cc +LD=$(CC) +AR=ar +RANLIB=ranlib +WARN=-Wall -Werror -Wno-format-truncation + +ifeq ($(MODE), release) +OPT=-Oz +DEBUG= +else ifeq ($(MODE), release-lto) +OPT=-Oz -flto +DEBUG= +else +OPT=-O0 +SANITIZE=undefined,address +DEBUG=-g +endif + +NN_STD=gnu99 +EMU_STD=gnu23 + +NNFLAGS= + +SANITIZE_FLAGS= + +ifdef SANITIZE + SANITIZE_FLAGS += -fsanitize=$(SANITIZE) +endif + +# no-omit-frame-pointer so if a crash does happen we can trace it +CFLAGS=-fPIC -fno-omit-frame-pointer $(OPT) $(SANITIZE_FLAGS) $(DEBUG) $(NNFLAGS) $(WARN) + +LDFLAGS=$(OPT) $(DEBUG) $(SANITIZE_FLAGS) + +#LINKRAYLIB=-lraylib +#INCLUA=-I /usr/include/lua5.4 +INCLUA=-I ./foreign/lua54 +LINKLUA=-llua5.4 +LINKLIBM=-lm +LINKLIBC= + +BUILD_DIR=build +SRC_DIR=. + +#all: bin lib dynlib +all: lib + +#$(BUILD_DIR)/neonucleus.o: $(SRC_DIR)/neonucleus.c $(SRC_DIR)/neonucleus.h +# $(CC) -o $(BUILD_DIR)/neonucleus.o -c $(SRC_DIR)/neonucleus.c $(CFLAGS) -std=$(NN_STD) + +#$(BUILD_DIR)/ncomplib.o: $(SRC_DIR)/ncomplib.c $(SRC_DIR)/ncomplib.h +# $(CC) -o $(BUILD_DIR)/ncomplib.o -c $(SRC_DIR)/ncomplib.c $(CFLAGS) -std=$(NN_STD) + +#nn: $(BUILD_DIR)/neonucleus.o $(BUILD_DIR)/ncomplib.o + +$(BUILD_DIR)/luaarch.o: $(SRC_DIR)/luaarch.c $(SRC_DIR)/machine.lua + $(CC) -o $(BUILD_DIR)/luaarch.o -c $(SRC_DIR)/luaarch.c $(CFLAGS) $(INCLUA) -std=$(EMU_STD) + +#$(BUILD_DIR)/glyphcache.o: $(SRC_DIR)/glyphcache.c $(SRC_DIR)/glyphcache.h +# $(CC) -o $(BUILD_DIR)/glyphcache.o -c $(SRC_DIR)/glyphcache.c $(CFLAGS) -std=$(EMU_STD) + +#$(BUILD_DIR)/main.o: $(SRC_DIR)/main.c $(SRC_DIR)/minBIOS.lua +# $(CC) -o $(BUILD_DIR)/main.o -c $(SRC_DIR)/main.c $(CFLAGS) $(INCLUA) -std=$(EMU_STD) + +#bin: nn $(BUILD_DIR)/main.o $(BUILD_DIR)/luaarch.o $(BUILD_DIR)/glyphcache.o +# $(LD) $(LDFLAGS) -o $(BIN) $(BUILD_DIR)/neonucleus.o $(BUILD_DIR)/ncomplib.o $(BUILD_DIR)/main.o $(BUILD_DIR)/glyphcache.o $(BUILD_DIR)/luaarch.o $(LINKLIBC) $(LINKLIBM) $(LINKRAYLIB) $(LINKLUA) +# +#lib: nn +# $(AR) rc $(LIB) $(BUILD_DIR)/neonucleus.o $(BUILD_DIR)/ncomplib.o +# $(RANLIB) $(LIB) + +lib: $(BUILD_DIR)/luaarch.o + $(AR) rc $(LIB) $(BUILD_DIR)/luaarch.o + $(RANLIB) $(LIB) + +cleancache: + rm -rf $(BUILD_DIR)/*.o + +clean: + rm -rf $(BIN) $(DYNLIB) $(LIB) diff --git a/src/tla-static/foreign/lua54 b/src/tla-static/foreign/lua54 new file mode 160000 index 0000000..934fdd4 --- /dev/null +++ b/src/tla-static/foreign/lua54 @@ -0,0 +1 @@ +Subproject commit 934fdd481ced3a9d4a7aaace4479ce889ab23582 diff --git a/src/tla-static/luaarch.c b/src/tla-static/luaarch.c new file mode 100644 index 0000000..b97d08e --- /dev/null +++ b/src/tla-static/luaarch.c @@ -0,0 +1,870 @@ +#include "neonucleus.h" +#include +#include +#include +#include +#include +#include + +// This architecture is written horrendously. +// This code is garbage, and is entirely just for testing. +// This architecture has effectively 0 sandboxing. +// The error handling in this architecture is effectively nothing. +// Also if the total memory available changes during execution, this Lua arch will NOT CARE. + +const char luaArch_machineLua[] = { +#embed "machine.lua" +,'\0' +}; + +typedef struct luaArch { + nn_Computer *computer; + lua_State *L; + size_t freeMem; +} luaArch; + +void *luaArch_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { + luaArch *arch = ud; + if(nsize == 0) { + free(ptr); + arch->freeMem += osize; + return NULL; + } + if(ptr == NULL) { + if(arch->freeMem < nsize) return NULL; + void *mem = malloc(nsize); + if(mem == NULL) return NULL; + arch->freeMem -= nsize; + return mem; + } + if(arch->freeMem + osize < nsize) return NULL; + void *mem = realloc(ptr, nsize); + if(mem == NULL) return NULL; + arch->freeMem += osize; + arch->freeMem -= nsize; + return mem; +} + +static luaArch *luaArch_from(lua_State *L) { + lua_getfield(L, LUA_REGISTRYINDEX, "archPtr"); + luaArch *arch = lua_touserdata(L, -1); + lua_pop(L, 1); + return arch; +} + +// pushes an NN value from a Lua stack index +static nn_Exit luaArch_luaToNN(luaArch *arch, lua_State *L, int luaIdx) { + nn_Computer *C = arch->computer; + + if(lua_isnoneornil(L, luaIdx)) { + return nn_pushnull(C); + } + // lua_isnumber() automatically casts + // because the Lua C API designers + // were high + if(lua_type(L, luaIdx) == LUA_TNUMBER) { + return nn_pushnumber(C, lua_tonumber(L, luaIdx)); + } + if(lua_isstring(L, luaIdx)) { + size_t len; + const char *s = lua_tolstring(L, luaIdx, &len); + return nn_pushlstring(C, s, len); + } + if(lua_isboolean(L, luaIdx)) { + return nn_pushbool(C, lua_toboolean(L, luaIdx)); + } + if(lua_isuserdata(L, luaIdx)) { + return nn_pushuserdata(C, (size_t)lua_touserdata(L, luaIdx)); + } + luaL_error(L, "bad Lua value: %s", luaL_typename(L, luaIdx)); + return NN_EBADSTATE; +} + +// pushes a Lua value from an NN stack index +static void luaArch_nnToLua(luaArch *arch, lua_State *L, size_t nnIdx) { + nn_Computer *C = arch->computer; + + if(nn_isnull(C, nnIdx)) { + lua_pushnil(L); + return; + } + if(nn_isnumber(C, nnIdx)) { + lua_pushnumber(L, nn_tonumber(C, nnIdx)); + return; + } + if(nn_isstring(C, nnIdx)) { + size_t len; + const char *s = nn_tolstring(C, nnIdx, &len); + lua_pushlstring(L, s, len); + return; + } + if(nn_isboolean(C, nnIdx)) { + lua_pushboolean(L, nn_toboolean(C, nnIdx)); + return; + } + if(nn_istable(C, nnIdx)) { + size_t start = nn_getstacksize(C); + size_t len; + nn_dumptable(C, nnIdx, &len); + lua_createtable(L, 0, len); + for(size_t i = 0; i < len; i++) { + luaArch_nnToLua(arch, L, start + i * 2); + luaArch_nnToLua(arch, L, start + i * 2 + 1); + lua_settable(L, -3); + } + nn_popn(C, len * 2); + return; + } + if(nn_isuserdata(C, nnIdx)) { + lua_pushlightuserdata(L, (void *)nn_touserdata(C, nnIdx)); + return; + } + + luaL_error(L, "bad NN value: %s", nn_typenameof(C, nnIdx)); +} + +static int luaArch_computer_beep(lua_State *L) { + if(lua_type(L, 1) == LUA_TSTRING) { + nn_MorseBeep beep = {.frequency = 1000, .beepDuration = 200, .volume = 1}; + beep.pattern = lua_tostring(L, 1); + if(lua_isnumber(L, 2)) { + beep.frequency = lua_tonumber(L, 2); + } + if(lua_isnumber(L, 3)) { + beep.beepDuration = lua_tonumber(L, 3); + } + if(lua_isnumber(L, 4)) { + beep.volume = lua_tonumber(L, 4); + } + if(beep.frequency < 20) beep.frequency = 20; + if(beep.beepDuration < 0) beep.beepDuration = 0; + if(beep.volume < 0) beep.volume = 0; + if(beep.frequency > 20000) beep.frequency = 20000; + if(beep.beepDuration > 5) beep.beepDuration = 5; + if(beep.volume > 1) beep.volume = 1; + nn_beepComputerMorse(luaArch_from(L)->computer, beep); + return 0; + } + nn_Beep beep = {.frequency = 1000, .duration = 1, .volume = 1}; + if(lua_isnumber(L, 1)) { + beep.frequency = lua_tonumber(L, 1); + } + if(lua_isnumber(L, 2)) { + beep.duration = lua_tonumber(L, 2); + } + if(lua_isnumber(L, 3)) { + beep.volume = lua_tonumber(L, 3); + } + if(beep.frequency < 20) beep.frequency = 20; + if(beep.duration < 0) beep.duration = 0; + if(beep.volume < 0) beep.volume = 0; + if(beep.frequency > 20000) beep.frequency = 20000; + if(beep.duration > 5) beep.duration = 5; + if(beep.volume > 1) beep.volume = 1; + nn_beepComputer(luaArch_from(L)->computer, beep); + nn_addIdleTime(luaArch_from(L)->computer, beep.duration); + return 0; +} + +static int luaArch_computer_freeMemory(lua_State *L) { + lua_pushinteger(L, nn_getFreeMemory(luaArch_from(L)->computer)); + return 1; +} + +static int luaArch_computer_totalMemory(lua_State *L) { + lua_pushinteger(L, nn_getTotalMemory(luaArch_from(L)->computer)); + return 1; +} + +static int luaArch_computer_uptime(lua_State *L) { + lua_pushnumber(L, nn_getUptime(luaArch_from(L)->computer)); + return 1; +} + +static int luaArch_computer_address(lua_State *L) { + const char *addr = nn_getComputerAddress(luaArch_from(L)->computer); + lua_pushstring(L, addr); + return 1; +} + +static int luaArch_computer_tmpAddress(lua_State *L) { + const char *addr = nn_getTmpAddress(luaArch_from(L)->computer); + if(addr == NULL) lua_pushnil(L); + else lua_pushstring(L, addr); + return 1; +} + +static int luaArch_computer_users(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + size_t userc = 0; + while(1) { + const char *user = nn_getUser(c, userc); + if(user == NULL) break; + lua_pushstring(L, user); + userc++; + } + return userc; +} + +static int luaArch_computer_addUser(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + const char *user = luaL_checkstring(L, 1); + nn_Exit err = nn_addUser(c, user); + if(err) { + nn_setErrorFromExit(c, err); + lua_pushnil(L); + lua_pushstring(L, nn_getError(c)); + return 2; + } + lua_pushboolean(L, true); + return 1; +} + +static int luaArch_computer_removeUser(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + const char *user = luaL_checkstring(L, 1); + lua_pushboolean(L, nn_removeUser(c, user)); + return 1; +} + +static int luaArch_computer_energy(lua_State *L) { + lua_pushnumber(L, nn_getEnergy(luaArch_from(L)->computer)); + return 1; +} + +static int luaArch_computer_maxEnergy(lua_State *L) { + lua_pushnumber(L, nn_getTotalEnergy(luaArch_from(L)->computer)); + return 1; +} + +static int luaArch_computer_getArchitecture(lua_State *L) { + lua_pushstring(L, nn_getArchitecture(luaArch_from(L)->computer).name); + return 1; +} + +static int luaArch_computer_setArchitecture(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + const char *archname = luaL_checkstring(L, 1); + nn_Architecture arch = nn_findSupportedArchitecture(c, archname); + if(arch.name == NULL) { + lua_pushnil(L); + lua_pushstring(L, "unknown architecture"); + return 2; + } + nn_setComputerState(c, NN_CHARCH); + nn_setDesiredArchitecture(c, &arch); + return 0; +} + +static int luaArch_computer_getArchitectures(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + size_t len; + const nn_Architecture *arch = nn_getSupportedArchitectures(c, &len); + lua_createtable(L, len, 0); + for(size_t i = 0; i < len; i++) { + lua_pushstring(L, arch[i].name); + lua_seti(L, -2, i+1); + } + return 1; +} + +static int luaArch_computer_shutdown(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + bool restart = lua_toboolean(L, 1); + nn_setComputerState(c, restart ? NN_RESTART : NN_POWEROFF); + return 0; +} + +static int luaArch_computer_isOverused(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + lua_pushboolean(L, nn_componentsOverused(c)); + return 1; +} + +static int luaArch_computer_isIdle(lua_State *L) { + nn_Computer *c = luaArch_from(L)->computer; + lua_pushboolean(L, nn_isComputerIdle(c)); + return 1; +} + +static int luaArch_computer_pushSignal(lua_State *L) { + luaArch *arch = luaArch_from(L); + nn_Computer *c = arch->computer; + size_t signalCount = lua_gettop(L); + nn_Exit err; + for(int i = 1; i <= signalCount; i++) { + err = luaArch_luaToNN(arch, L, i); + if(err) { + nn_setErrorFromExit(c, err); + luaL_error(L, "%s", nn_getError(c)); + } + } + err = nn_pushSignal(c, signalCount); + if(err) { + nn_setErrorFromExit(c, err); + luaL_error(L, "%s", nn_getError(c)); + } + return 0; +} + +static int luaArch_computer_popSignal(lua_State *L) { + luaArch *arch = luaArch_from(L); + nn_Computer *c = arch->computer; + // no signals queued + if(nn_countSignals(c) == 0) return 0; + nn_clearstack(c); + size_t signalCount; + nn_Exit err = nn_popSignal(c, &signalCount); + if(err) goto fail; + for(size_t i = 0; i < signalCount; i++) { + luaArch_nnToLua(arch, L, i); + } + nn_clearstack(c); + return signalCount; +fail: + nn_setErrorFromExit(c, err); + luaL_error(L, "%s", nn_getError(c)); + return 0; +} + +static int luaArch_computer_getDeviceInfo(lua_State *L) { + luaArch *arch = luaArch_from(L); + nn_Computer *C = arch->computer; + lua_createtable(L, 0, 0); + for(size_t i = 0;; i++) { + const char *devAddr = nn_deviceInfoAt(C, i); + if(devAddr == NULL) break; + size_t len; + const nn_DeviceField *fields = nn_getDeviceInfo(C, i, &len); + lua_pushstring(L, devAddr); + lua_createtable(L, 0, len); + for(size_t j = 0; j < len; j++) { + lua_pushstring(L, fields[j].name); + lua_pushstring(L, fields[j].value); + lua_settable(L, -3); + } + lua_settable(L, -3); + } + return 1; +} + +static int luaArch_component_list(lua_State *L) { + luaArch *arch = luaArch_from(L); + lua_createtable(L, 64, 0); + size_t len = nn_countComponents(arch->computer); + if(len == 0) { + lua_createtable(L, 0, 0); + return 1; + } + NN_VLA(const char *, comps, len); + nn_getComponents(arch->computer, comps); + for(size_t i = 0; i < len; i++) { + nn_Component *c = nn_getComponent(arch->computer, comps[i]); + if(c != NULL) { + lua_pushstring(L, nn_getComponentType(nn_getComponent(arch->computer, comps[i]))); + lua_setfield(L, -2, comps[i]); + } + } + return 1; +} + +static int luaArch_component_invoke(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + const char *method = luaL_checkstring(L, 2); + size_t argc = lua_gettop(L); + + nn_clearstack(arch->computer); + for(size_t i = 3; i <= argc; i++) { + luaArch_luaToNN(arch, L, i); + } + nn_Exit err = nn_invokeComponent(arch->computer, address, method); + if(err != NN_OK) { + lua_pushnil(L); + lua_pushstring(L, nn_getError(arch->computer)); + return 2; + } + size_t retc = nn_getstacksize(arch->computer); + for(size_t i = 0; i < retc; i++) { + luaArch_nnToLua(arch, L, i); + } + nn_clearstack(arch->computer); + return retc; +} + +static int luaArch_component_type(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + + nn_Component *c = nn_getComponent(arch->computer, address); + if(c == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such component"); + return 2; + } + lua_pushstring(L, nn_getComponentType(c)); + return 1; +} + +static int luaArch_component_doc(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + const char *method = luaL_checkstring(L, 2); + + nn_Component *c = nn_getComponent(arch->computer, address); + if(c == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such component"); + return 2; + } + const char *doc = nn_getComponentDoc(c, method); + if(doc == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such method"); + return 2; + } + lua_pushstring(L, doc); + return 1; +} + +static int luaArch_component_getMethodFlags(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + const char *method = luaL_checkstring(L, 2); + + nn_Component *c = nn_getComponent(arch->computer, address); + if(c == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such component"); + return 2; + } + nn_MethodFlags f = nn_getComponentMethodFlags(c, method); + lua_createtable(L, 0, 3); + lua_pushboolean(L, (f & NN_DIRECT) != 0); + lua_setfield(L, -2, "direct"); + lua_pushboolean(L, (f & NN_GETTER) != 0); + lua_setfield(L, -2, "getter"); + lua_pushboolean(L, (f & NN_SETTER) != 0); + lua_setfield(L, -2, "setter"); + return 1; +} + +static int luaArch_component_slot(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + + if(nn_getComponent(arch->computer, address) == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such component"); + return 2; + } + lua_pushinteger(L, nn_getComponentSlot(arch->computer, address)); + return 1; +} + +static int luaArch_component_methods(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + + nn_Component *c = nn_getComponent(arch->computer, address); + + if(c == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such component"); + return 2; + } + size_t methodLen = nn_countComponentMethods(c); + if(methodLen == 0) { + lua_createtable(L, 0, 0); + return 1; + } + NN_VLA(const char *, methods, methodLen); + nn_getComponentMethods(c, methods, &methodLen); + lua_createtable(L, 0, methodLen); + for(size_t i = 0; i < methodLen; i++) { + nn_MethodFlags flags = nn_getComponentMethodFlags(c, methods[i]); + if(flags & NN_FIELD_MASK) continue; // skip + + lua_pushboolean(L, (flags & NN_DIRECT) != 0); + lua_setfield(L, -2, methods[i]); + } + return 1; +} + +static int luaArch_component_fields(lua_State *L) { + luaArch *arch = luaArch_from(L); + const char *address = luaL_checkstring(L, 1); + + nn_Component *c = nn_getComponent(arch->computer, address); + + if(c == NULL) { + lua_pushnil(L); + lua_pushstring(L, "no such component"); + return 2; + } + size_t methodLen = nn_countComponentMethods(c); + if(methodLen == 0) { + lua_createtable(L, 0, 0); + return 1; + } + // const char *methods[methodLen]; + NN_VLA(const char *, methods, methodLen); + nn_getComponentMethods(c, methods, &methodLen); + lua_createtable(L, 0, methodLen); + for(size_t i = 0; i < methodLen; i++) { + nn_MethodFlags flags = nn_getComponentMethodFlags(c, methods[i]); + if((flags & NN_FIELD_MASK) == 0) continue; // skip + + lua_createtable(L, 0, 3); + lua_pushboolean(L, (flags & NN_DIRECT) != 0); + lua_setfield(L, -2, "direct"); + lua_pushboolean(L, (flags & NN_GETTER) != 0); + lua_setfield(L, -2, "getter"); + lua_pushboolean(L, (flags & NN_SETTER) != 0); + lua_setfield(L, -2, "setter"); + lua_setfield(L, -2, methods[i]); + } + return 1; +} + +static int luaArch_unicode_char(lua_State *L) { + size_t argc = lua_gettop(L); + size_t len = 0; + for(int i = 1; i <= argc; i++) { + nn_codepoint codepoint = lua_tointeger(L, i); + size_t size = nn_unicode_codepointSize(codepoint); + if(size == 0) luaL_error(L, "codepoint #%d out of range", i); + len += size; + } + nn_Context *ctx = nn_getComputerContext(luaArch_from(L)->computer); + char *buf = nn_alloc(ctx, len); + size_t buflen = len; + len = 0; + for(int i = 1; i <= argc; i++) { + nn_codepoint codepoint = lua_tointeger(L, i); + size_t size = nn_unicode_codepointToChar(buf + len, codepoint); + len += size; + } + lua_pushlstring(L, buf, len); + nn_free(ctx, buf, buflen); + return 1; +} + +static int luaArch_unicode_len(lua_State *L) { + size_t len; + const char *s = lua_tolstring(L, 1, &len); + len = nn_unicode_lenPermissive(s, len); + lua_pushinteger(L, len); + return 1; +} + +static int luaArch_unicode_sub(lua_State *L) { + size_t slen; + const char *s = lua_tolstring(L, 1, &slen); + if(lua_gettop(L) < 2) lua_pushinteger(L, 1); + if(lua_gettop(L) < 3) lua_pushinteger(L, -1); + + size_t len = nn_unicode_lenPermissive(s, slen); + + // OpenOS does this... + if(len == 0) { + lua_pushstring(L, ""); + return 1; + } + + int start = lua_tointeger(L, 2); + int end = lua_tointeger(L, 3); + + if(end == 0) { + lua_pushstring(L, ""); + return 1; + } + + if(start > 0) start--; + if(start < 0) start = len + start; + if(end > 0) end--; + if(end < 0) end = len + end; + + if(start < 0) start = 0; + if(start >= len) start = len-1; + if(end < 0) end = 0; + if(end >= len) end = len-1; + + if(start > end) { + lua_pushstring(L, ""); + return 1; + } + + nn_Context *ctx = nn_getComputerContext(luaArch_from(L)->computer); + nn_codepoint *cp = nn_alloc(ctx, sizeof(*cp) * len); + nn_unicode_codepointsPermissive(s, slen, cp); + + size_t substrlen = nn_unicode_countBytes(cp + start, end - start + 1); + char *buf = nn_alloc(ctx, substrlen); + nn_unicode_writeBytes(buf, cp + start, end - start + 1); + lua_pushlstring(L, buf, substrlen); + nn_free(ctx, buf, substrlen); + nn_free(ctx, cp, sizeof(*cp) * len); + return 1; +} + +static int luaArch_unicode_wlen(lua_State *L) { + size_t slen; + const char *s = lua_tolstring(L, 1, &slen); + size_t len = nn_unicode_wlenPermissive(s, slen); + lua_pushinteger(L, len); + return 1; +} + +static int luaArch_unicode_wtrunc(lua_State *L) { + lua_pushvalue(L, 1); + return 1; +} + +static int luaArch_userdata_free(lua_State *L) { + luaArch *arch = luaArch_from(L); + size_t user = (size_t)lua_touserdata(L, 1); + nn_freeUserdata(arch->computer, user); + return 0; +} + +static int luaArch_userdata_methods(lua_State *L) { + luaArch *arch = luaArch_from(L); + size_t user = (size_t)lua_touserdata(L, 1); + if(!nn_isUserdataValid(arch->computer, user)) { + lua_pushnil(L); + lua_pushstring(L, "no such userdata"); + return 2; + } + lua_createtable(L, 0, 0); + size_t idx = 0; + while(true) { + nn_Method m; + if(nn_getUserdataMethod(arch->computer, user, idx, &m)) break; + idx++; + if(m.name == NULL) continue; + lua_pushstring(L, m.name); + lua_createtable(L, 0, 4); + + // the method data + { + lua_pushstring(L, "doc"); + if(m.doc == NULL) lua_pushnil(L); else lua_pushstring(L, m.doc); + lua_settable(L, -3); + + lua_pushstring(L, "direct"); + lua_pushboolean(L, (m.flags & NN_DIRECT) != 0); + lua_settable(L, -3); + + lua_pushstring(L, "getter"); + lua_pushboolean(L, (m.flags & NN_GETTER) != 0); + lua_settable(L, -3); + + lua_pushstring(L, "setter"); + lua_pushboolean(L, (m.flags & NN_SETTER) != 0); + lua_settable(L, -3); + } + + lua_settable(L, -3); + } + return 1; +} + +static int luaArch_userdata_invoke(lua_State *L) { + luaArch *arch = luaArch_from(L); + size_t user = (size_t)lua_touserdata(L, 1); + if(!nn_isUserdataValid(arch->computer, user)) { + lua_pushnil(L); + lua_pushstring(L, "no such userdata"); + return 2; + } + const char *method = luaL_checkstring(L, 2); + size_t argc = lua_gettop(L); + + nn_clearstack(arch->computer); + for(size_t i = 3; i <= argc; i++) { + luaArch_luaToNN(arch, L, i); + } + nn_Exit err = nn_invokeUserdata(arch->computer, user, method); + if(err != NN_OK) { + lua_pushnil(L); + lua_pushstring(L, nn_getError(arch->computer)); + return 2; + } + size_t retc = nn_getstacksize(arch->computer); + for(size_t i = 0; i < retc; i++) { + luaArch_nnToLua(arch, L, i); + } + nn_clearstack(arch->computer); + return retc; +} + +static void luaArch_loadEnv(lua_State *L) { + lua_createtable(L, 0, 10); + int computer = lua_gettop(L); + lua_pushcfunction(L, luaArch_computer_beep); + lua_setfield(L, computer, "beep"); + lua_pushcfunction(L, luaArch_computer_freeMemory); + lua_setfield(L, computer, "freeMemory"); + lua_pushcfunction(L, luaArch_computer_totalMemory); + lua_setfield(L, computer, "totalMemory"); + lua_pushcfunction(L, luaArch_computer_uptime); + lua_setfield(L, computer, "uptime"); + lua_pushcfunction(L, luaArch_computer_address); + lua_setfield(L, computer, "address"); + lua_pushcfunction(L, luaArch_computer_tmpAddress); + lua_setfield(L, computer, "tmpAddress"); + lua_pushcfunction(L, luaArch_computer_users); + lua_setfield(L, computer, "users"); + lua_pushcfunction(L, luaArch_computer_addUser); + lua_setfield(L, computer, "addUser"); + lua_pushcfunction(L, luaArch_computer_removeUser); + lua_setfield(L, computer, "removeUser"); + lua_pushcfunction(L, luaArch_computer_energy); + lua_setfield(L, computer, "energy"); + lua_pushcfunction(L, luaArch_computer_maxEnergy); + lua_setfield(L, computer, "maxEnergy"); + lua_pushcfunction(L, luaArch_computer_getArchitecture); + lua_setfield(L, computer, "getArchitecture"); + lua_pushcfunction(L, luaArch_computer_getArchitectures); + lua_setfield(L, computer, "getArchitectures"); + lua_pushcfunction(L, luaArch_computer_setArchitecture); + lua_setfield(L, computer, "setArchitecture"); + lua_pushcfunction(L, luaArch_computer_shutdown); + lua_setfield(L, computer, "shutdown"); + lua_pushcfunction(L, luaArch_computer_isOverused); + lua_setfield(L, computer, "isOverused"); + lua_pushcfunction(L, luaArch_computer_isIdle); + lua_setfield(L, computer, "isIdle"); + lua_pushcfunction(L, luaArch_computer_pushSignal); + lua_setfield(L, computer, "pushSignal"); + lua_pushcfunction(L, luaArch_computer_popSignal); + lua_setfield(L, computer, "popSignal"); + lua_pushcfunction(L, luaArch_computer_getDeviceInfo); + lua_setfield(L, computer, "getDeviceInfo"); + lua_setglobal(L, "computer"); + + lua_createtable(L, 0, 10); + int component = lua_gettop(L); + lua_pushcfunction(L, luaArch_component_list); + lua_setfield(L, component, "list"); + lua_pushcfunction(L, luaArch_component_invoke); + lua_setfield(L, component, "invoke"); + lua_pushcfunction(L, luaArch_component_doc); + lua_setfield(L, component, "doc"); + lua_pushcfunction(L, luaArch_component_type); + lua_setfield(L, component, "type"); + lua_pushcfunction(L, luaArch_component_getMethodFlags); + lua_setfield(L, component, "getMethodFlags"); + lua_pushcfunction(L, luaArch_component_slot); + lua_setfield(L, component, "slot"); + lua_pushcfunction(L, luaArch_component_methods); + lua_setfield(L, component, "methods"); + lua_pushcfunction(L, luaArch_component_fields); + lua_setfield(L, component, "fields"); + lua_setglobal(L, "component"); + + lua_createtable(L, 0, 10); + int unicode = lua_gettop(L); + lua_pushcfunction(L, luaArch_unicode_char); + lua_setfield(L, unicode, "char"); + lua_pushcfunction(L, luaArch_unicode_len); + lua_setfield(L, unicode, "len"); + lua_pushcfunction(L, luaArch_unicode_sub); + lua_setfield(L, unicode, "sub"); + lua_pushcfunction(L, luaArch_unicode_wlen); + lua_setfield(L, unicode, "wlen"); + lua_pushcfunction(L, luaArch_unicode_wtrunc); + lua_setfield(L, unicode, "wtrunc"); + lua_setglobal(L, "unicode"); + + lua_createtable(L, 0, 10); + int userdata = lua_gettop(L); + lua_pushcfunction(L, luaArch_userdata_free); + lua_setfield(L, userdata, "free"); + lua_pushcfunction(L, luaArch_userdata_methods); + lua_setfield(L, userdata, "methods"); + lua_pushcfunction(L, luaArch_userdata_invoke); + lua_setfield(L, userdata, "invoke"); + lua_setglobal(L, "userdata"); +} + +static nn_Exit luaArch_handler(nn_ArchitectureRequest *req) { + nn_Computer *computer = req->computer; + luaArch *arch = req->localState; + nn_Context *ctx = nn_getComputerContext(computer); + // the memory scale, used to give Lua extra memory in 64-bit systems + // due to it also using more memory. + double memScale = sizeof(void *) > 4 ? 1.8 : 1; + switch(req->action) { + case NN_ARCH_FREEMEM: + req->freeMemory = arch->freeMem / memScale; + return NN_OK; + case NN_ARCH_INIT: + // wrapped in a block to prevent L from leaking, because L is common in Lua code so it may be used by mistake + { + arch = nn_alloc(ctx, sizeof(*arch)); + arch->freeMem = nn_getTotalMemory(computer) * memScale; + arch->computer = computer; +#if LUA_VERSION_NUM >= 505L + lua_State *L = lua_newstate(luaArch_alloc, arch, rand()); +#else + lua_State *L = lua_newstate(luaArch_alloc, arch); +#endif + arch->L = L; + req->localState = arch; + luaL_openlibs(L); + lua_pushlightuserdata(L, arch); + lua_setfield(L, LUA_REGISTRYINDEX, "archPtr"); + + luaArch_loadEnv(L); + + lua_settop(L, 0); + luaL_loadbufferx(L, luaArch_machineLua, strlen(luaArch_machineLua), "=machine.lua", "t"); + } + return NN_OK; + case NN_ARCH_DEINIT: + lua_close(arch->L); + nn_free(ctx, arch, sizeof(*arch)); + return NN_OK; + case NN_ARCH_TICK:; + lua_pushboolean(arch->L, req->synchronized); + lua_setglobal(arch->L, "_SYNCED"); + lua_settop(arch->L, 1); + int ret = 0; +#if LUA_VERSION_NUM >= 504L + int res = lua_resume(arch->L, NULL, 0, &ret); +#else + int res = lua_resume(arch->L, NULL, 0); + ret = lua_gettop(arch->L); +#endif + //printf("res: %d\n", res); + if(res == LUA_OK) { + // halted, fuck + lua_pop(arch->L, ret); + nn_setError(computer, "machine halted"); + nn_setComputerState(computer, NN_CRASHED); + return NN_OK; + } else if(res != LUA_YIELD) { + const char *s = lua_tostring(arch->L, -1); + nn_setError(computer, s); + nn_setComputerState(computer, NN_CRASHED); + return NN_OK; + } + return NN_OK; + case NN_ARCH_SERIALIZE: + return nn_pushstring(computer, "lua stuff"); + case NN_ARCH_DESERIALIZE: + // do nothing + return NN_OK; + } + return NN_OK; +} + +nn_Architecture getLuaArch() { + return (nn_Architecture) { + .name = LUA_VERSION, + .state = NULL, + .handler = luaArch_handler, + }; +} diff --git a/src/tla-static/machine.lua b/src/tla-static/machine.lua new file mode 100644 index 0000000..0a6c8f0 --- /dev/null +++ b/src/tla-static/machine.lua @@ -0,0 +1,581 @@ +-- The boot-up code of the Lua architecture. +-- Extremely bad. +-- Do not use in a serious context, you will be hacked. +-- There is no sandboxing here. +-- NOTE: we implemented method synchronization in here btw + +os.exit = nil +os.execute = nil + +local sysyieldobj = {} +local coroutine = coroutine + +local resume, yield = coroutine.resume, coroutine.yield + +local function sysyield() + yield(sysyieldobj) +end + +function coroutine.resume(co, ...) + while true do + local t = {resume(co, ...)} + if t[1] and rawequal(t[2], sysyieldobj) then + yield(sysyieldobj) + else + return table.unpack(t) + end + end +end + +function coroutine.wrap(f) + local co = coroutine.create(f) + return function(...) + if coroutine.status(co) ~= "suspended" then return end + local t = {coroutine.resume(co, ...)} + if t[1] then + return table.unpack(t, 2) + end + error(t[2], 2) + end +end + +local clist, cinvoke, computer, component, print, unicode = component.list, component.invoke, computer, component, print, unicode +debug.print = print +debug.sysyield = sysyield + +function component.list(ctype, exact) + local list = clist() + local desired = {} + + for addr, type in pairs(list) do + if ctype then + if exact then + if ctype == type then desired[addr] = type end + else + if string.find(type, ctype) then desired[addr] = type end + end + else + desired[addr] = type + end + end + + local key = nil + setmetatable(desired, {__call = function() + local val + key, val = next(desired, key) + return key, val + end}) + return desired +end + +local syncedMethodStats + +local function unsandboxValue(val) + if type(val) == "table" then + local meta = getmetatable(val) + if meta then + if type(meta.__userdata) == "userdata" then + return meta.__userdata + end + end + local nt = {} + for sk, sv in pairs(val) do + local k, v = unsandboxValue(sk), unsandboxValue(sv) + if k ~= nil then nt[k] = v end + end + return nt + end + return val +end + +local function sandboxValue(val) + if type(val) == "table" then + local nt = {} + for k, v in pairs(val) do + local sk, sv = sandboxValue(k), sandboxValue(v) + if sk ~= nil then nt[sk] = sv end + end + return nt + end + if type(val) == "userdata" then + -- This matches how OC wraps it + -- TODO: just make our own shi with fields and stuff + -- like a component proxy, because this sucks + local userMeta = { + __gc = function() + userdata.free(val) + end, + __userdata = val, + } + + local wrapped = {type = "userdata", userdata = val} + for name, m in pairs(userdata.methods(val)) do + wrapped[name] = { + name = name, + proxy = wrapped, + } + setmetatable(wrapped[name], { + __tostring = function() return m.doc or "function" end, + __call = function(_, ...) + return userdata.invoke(val, name, ...) + end, + }) + end + return setmetatable(wrapped, userMeta) + end + return val +end + +local function realInvoke(address, method, ...) + local args = {...} + for i=1,#args do args[i] = unsandboxValue(args[i]) end + local t = {pcall(cinvoke, address, method, table.unpack(args))} + if not _SYNCED and not os.getenv("NN_FAST") then + if computer.energy() <= 0 then sysyield() end -- out of power + if computer.isOverused() then sysyield() end -- overused + if computer.isIdle() then sysyield() end -- machine idle + end + + if component.type(address) == os.getenv("NN_INVDBG") or string.find(os.getenv("NN_METDBG") or "", method, nil, true) then + print("invoked", address, method, ...) + print(string.format("got %d values", #t), table.unpack(t)) + end + + for i=1,#t do t[i] = sandboxValue(t[i]) end + + if t[1] then + return table.unpack(t, 2) + end + return nil, t[2] +end + +function component.invoke(address, method, ...) + if type(address) ~= "string" then return nil, "bad argument #1 (string expected)" end + if type(method) ~= "string" then return nil, "bad argument #2 (string expected)" end + if component.getMethodFlags(address, method).direct or os.getenv("NN_FAST") then + return realInvoke(address, method, ...) + else + -- must sync + syncedMethodStats = {address, method, ...} + sysyield() + local rets = syncedMethodStats + syncedMethodStats = nil + return table.unpack(rets) + end +end + +local componentCallback = { + __call = function(self, ...) + return component.invoke(self.address, self.name, ...) + end, + __tostring = function(self) + return component.doc(self.address, self.name) or "function" + end, +} + +local componentProxy = { + __index = function(self, key) + if self.fields[key] and self.fields[key].getter then + return component.invoke(self.address, key) + end + return rawget(self, key) + end, + __newindex = function(self, key, value) + if self.fields[key] and self.fields[key].setter then + return component.invoke(self.address, key, value) + end + rawset(self, key, value) + end, + __pairs = function(self) + local reachedFields = false + local key, val + return function() + if not reachedFields then + key, val = next(self, key) + if key == nil then reachedFields = true end + end + if reachedFields then + key = next(self.fields, key) + val = self[key] + end + return key, val + end + end, +} + +local proxyCache = setmetatable({}, {__mode = "v"}) + +function component.proxy(address) + if proxyCache[address] then return proxyCache[address] end + + local t, err = component.type(address) + if not t then return nil, err end + local slot, err = component.slot(address) + if not slot then return nil, err end + + local proxy = {address = address, type = t, slot = slot} + proxy.fields, err = component.fields(address) + if not proxy.fields then return nil, err end + + local methods = component.methods(address) + for name in pairs(methods) do + proxy[name] = setmetatable({address=address,name=name}, componentCallback) + end + + setmetatable(proxy, componentProxy) + proxyCache[address] = proxy + return proxy +end + +function computer.getProgramLocations() + return {} +end + +local shutdown, setArch = computer.shutdown, computer.setArchitecture + +function computer.shutdown(...) + shutdown(...) + sysyield() +end + +function computer.setArchitecture(arch) + if arch == computer.getArchitecture() then return end + local ok, err = setArch(arch) + sysyield() + return ok, err +end + +function computer.pullSignal(timeout) + timeout = timeout or math.huge + timeout = math.max(timeout, 0.001) + local deadline = computer.uptime() + timeout + while true do + if computer.uptime() >= deadline then return end + local t = {computer.popSignal()} + for i=1,#t do t[i] = unsandboxValue(t[i]) end + if #t == 0 then + sysyield() + else + return table.unpack(t) + end + end +end + +unicode.upper, unicode.lower = string.upper, string.lower + +unicode.isWide = function(s) + local c = unicode.sub(s, 1, 1) + return unicode.wlen(c) > unicode.len(c) +end + +unicode.wtrunc = function(str,space) + space = space - 1 + return unicode.sub(str, 1, space) +end + +unicode.reverse = string.reverse + +unicode.charWidth = function(s) + return 1 +end + +unicode.sub = function(str, a, b) + if not b then b = utf8.len(str) end + if not a then a = 1 end + -- a = math.max(a,1) + if a < 0 then + -- negative + a = utf8.len(str) + a + 1 + end + if b < 0 then + b = utf8.len(str) + b + 1 + end + if a > b then return "" end + if b >= utf8.len(str) then b = #str else b = utf8.offset(str,b+1)-1 end + if a > utf8.len(str) then return "" end + a = utf8.offset(str,a) + return str:sub(a,b) + -- return str:sub(a, b) +end + +function checkArg(arg, val, ...) + local t = {...} + for i=1,#t do + if type(val) == t[i] then return end + end + error("bad argument #" .. arg .. " (" .. table.concat(t, ", ") .. ") expected", 2) +end + +if os.getenv("NN_REPL") == "1" then + while true do + io.write("\x1b[34mlua>\x1b[0m ") + io.flush() + local l = io.read("l") + if not l then break end + local f, err = load("return " .. l, "=repl") + if f then + print(f()) + else + f, err = load(l, "=repl") + if f then f() else print(err) end + end + yield() + end + io.write("\n") + print("exiting repl") +end + +-- Save on just a tiny smudgeon of RAM +io = nil +package = nil + +local sandbox +sandbox = { + _VERSION = _VERSION, + assert = assert, + error = error, + getmetatable = getmetatable, + ipairs = ipairs, + load = function(chunk, name, ty, env) + return load(chunk, name, ty, env or sandbox) + end, + next = next, + pairs = pairs, + pcall = pcall, + rawequal = rawequal, + rawget = rawget, + rawlen = rawlen, + rawset = rawset, + select = select, + setmetatable = function(t, meta) + if type(meta) == "table" then + -- nope + rawset(meta, "__gc", nil) + end + return setmetatable(t, meta) + end, + tonumber = tonumber, + tostring = tostring, + type = type, + xpcall = xpcall, + collectgarbage = function() collectgarbage("collect") end, + + bit32 = bit32, + + coroutine = { + create = coroutine.create, + resume = coroutine.resume, + running = coroutine.running, + status = coroutine.status, + wrap = coroutine.wrap, + yield = coroutine.yield, + isyieldable = coroutine.isyieldable, + }, + + debug = { + getinfo = debug.getinfo, + traceback = debug.traceback, + getlocal = function(...) return (debug.getlocal(...)) end, + getupvalue = function(...) return (debug.getupvalue(...)) end, + print = debug.print, + }, + + math = { + abs = math.abs, + acos = math.acos, + asin = math.asin, + atan = math.atan, + atan2 = math.atan2, + ceil = math.ceil, + cos = math.cos, + cosh = math.cosh, + deg = math.deg, + exp = math.exp, + floor = math.floor, + fmod = math.fmod, + frexp = math.frexp, + huge = math.huge, + ldexp = math.ldexp, + log = math.log, + max = math.max, + min = math.min, + modf = math.modf, + pi = math.pi, + pow = math.pow, + rad = math.rad, + random = math.random, + randomseed = math.randomseed, + sin = math.sin, + sinh = math.sinh, + sqrt = math.sqrt, + tan = math.tan, + tanh = math.tanh, + maxinteger = math.maxinteger, + mininteger = math.mininteger, + type = math.type, + ult = math.ult, + }, + + os = { + clock = os.clock, + date = function(a, b, ...) + if type(a) == "number" then + a = math.floor(a) + end + if type(b) == "number" then + b = math.floor(b) + end + return os.date(a, b, ...) + end, + difftime = os.difftime, + time = os.time, + }, + + string = { + byte = string.byte, + char = string.char, + dump = string.dump, + find = string.find, + format = string.format, + gmatch = string.gmatch, + gsub = string.gsub, + len = string.len, + lower = string.lower, + match = string.match, + rep = string.rep, + reverse = string.reverse, + sub = string.sub, + upper = string.upper, + pack = string.pack, + unpack = string.unpack, + packsize = string.packsize, + }, + + table = { + concat = table.concat, + insert = table.insert, + pack = table.pack, + remove = table.remove, + sort = table.sort, + unpack = table.unpack, + move = table.move, + }, + + checkArg = checkArg, + + component = { + doc = component.doc, + fields = component.fields, + invoke = component.invoke, + list = component.list, + methods = component.methods, + proxy = component.proxy, + slot = component.slot, + type = component.type, + }, + + computer = { + address = computer.address, + addUser = computer.addUser, + beep = computer.beep, + energy = computer.energy, + freeMemory = computer.freeMemory, + getArchitectures = computer.getArchitectures, + getArchitecture = computer.getArchitecture, + getDeviceInfo = computer.getDeviceInfo, + getProgramLocations = computer.getProgramLocations, + isRobot = computer.isRobot, + maxEnergy = computer.maxEnergy, + pullSignal = computer.pullSignal, + pushSignal = function(name, ...) + checkArg(1, name, "string") + local t = {...} + for i=1,#t do + local v = unsandboxValue(t[i]) + if type(v) == "userdata" then + return nil, "userdata is forbidden" + end + t[i] = v + end + return computer.pushSignal(name, table.unpack(t)) + end, + removeUser = computer.removeUser, + setArchitecture = computer.setArchitecture, + shutdown = computer.shutdown, + tmpAddress = computer.tmpAddress, + totalMemory = computer.totalMemory, + uptime = computer.uptime, + users = computer.users, + }, + + unicode = { + len = utf8.len, + wlen = utf8.len, -- this can be very wrong. + sub = function (str,a,b) + if not b then b = utf8.len(str) end + if not a then a = 1 end + -- a = math.max(a,1) + + if a < 0 then + -- negative + + a = utf8.len(str) + a + 1 + end + + if b < 0 then + b = utf8.len(str) + b + 1 + end + + if a > b then return "" end + + if b >= utf8.len(str) then b = #str else b = utf8.offset(str,b+1)-1 end + + if a > utf8.len(str) then return "" end + a = utf8.offset(str,a) + + return str:sub(a,b) + -- return str:sub(a, b) + end, + char = utf8.char, + wtrunc = function (str,space) + space = space - 1 + return str:sub(1,(space >= utf8.len(str)) and (#str) or (utf8.offset(str,space+1)-1)) + end, + upper = string.upper, -- these are accurate... sometimes + lower = string.lower, + isWide = function () + return false + end + }, + + utf8 = utf8, +} +sandbox._G = sandbox + +local eeprom = component.list("eeprom", true)() +assert(eeprom, "missing firmware") + +-- this would automatically reboot us if it needs to be a different architecture +local arch = component.invoke(eeprom, "getArchitecture") +if arch then computer.setArchitecture(arch) end + +local code = assert(component.invoke(eeprom, "get")) +local f = assert(load(code, "=bios", nil, sandbox)) +local thread = coroutine.create(f) + +while true do + collectgarbage("collect") + if _SYNCED then + if syncedMethodStats then + --debug.print("calling synced method") + syncedMethodStats = {realInvoke(table.unpack(syncedMethodStats))} + end + else + local ok, err = resume(thread) + if not ok then + print(debug.traceback(thread, err)) + end + if coroutine.status(thread) == "dead" then break end + end + yield() +end diff --git a/src/tla-static/neonucleus.h b/src/tla-static/neonucleus.h new file mode 100644 index 0000000..013436c --- /dev/null +++ b/src/tla-static/neonucleus.h @@ -0,0 +1,2437 @@ +#ifndef NEONUCLEUS_H +#define NEONUCLEUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Platform checking support, to help out users. +// Used internally as well. +// Based off https://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + #ifndef NN_WINDOWS + #define NN_WINDOWS + #endif +#elif __APPLE__ + #ifndef NN_MACOS + #define NN_MACOS + #endif +#elif __linux__ + #ifndef NN_LINUX + #define NN_LINUX + #endif +#endif + +#if __unix__ + #ifndef NN_UNIX + #define NN_UNIX + #endif + #ifndef NN_POSIX + #define NN_POSIX + #endif +#elif defined(_POSIX_VERSION) + #ifndef NN_POSIX + #define NN_POSIX + #endif +#endif + + +#if defined(_MSC_VER) && !defined(__cplusplus) +#ifndef NN_ATOMIC_MSVC +#define NN_ATOMIC_MSVC +#endif +#endif + +#ifdef _MSC_VER +#define NN_INIT(type) +#else +#define NN_INIT(type) (type) +#endif + +// every C standard header we depend on, conveniently put here +#include // for NULL, +#include // for intptr_t +#include // for true, false and bool + +/* MSVC can't use VLA; + * What we see : NN_VLA(const char *, comps, len); + * What compiler see after preproccessor : const char * comps[len]; + * What actaully was : const char *comps[len]; +*/ +// Test: gcc -E -DNN_VLA\(type,name,count\)="type name[count]" -x c - <<< 'NN_VLA(const char *, comps, len);' +#ifdef _MSC_VER +// avoid #include , for it can pollute the namespace +// with symbols to functions which are not linked with in baremetal. +void *_alloca(size_t); +#define NN_VLA(type, name, count) type *name = (type *)_alloca(sizeof(type) * (count)) +#else +#define NN_VLA(type, name, count) type name[count] +#endif + +// Internally we need stdatomic.h and, if NN_BAREMETAL is not defined, stdlib.h and time.h + +// The entire NeoNucleus API, in one header file + +// Internal limits or constants + +#define NN_KiB (1024) +#define NN_MiB (1024 * NN_KiB) +#define NN_GiB (1024 * NN_MiB) +#define NN_TiB ((size_t)1024 * NN_GiB) + +// the alignment an allocation should have +#define NN_ALLOC_ALIGN 16 +// the maximum amount of items the callstack can have. +#define NN_MAX_STACK 256 +// the maximum size a path is allowed to have, including the NULL terminator! +#define NN_MAX_PATH 256 +// the maximum size of a label +#define NN_MAX_LABEL 256 +// maximum size of a wakeup message +#define NN_MAX_WAKEUPMSG 2048 +// the maximum amount of file descriptors that can be open simultaneously +#define NN_MAX_OPENFILES 16 +// the maximum amount of userdata that can be sent simultaneously. +#define NN_MAX_USERDATA 64 +// maximum size of a signal, computed the same as modem packet costs. +#define NN_MAX_SIGNALSIZE 8192 +// maximum amount of signals. +#define NN_MAX_SIGNALS 128 +// the maximum value of a port. Ports start at 1. +#define NN_MAX_PORT 65535 +// the magic port number to close all ports +#define NN_CLOSEPORTS 0 +// maximum amount of architectures one machine can support. +#define NN_MAX_ARCHITECTURES 32 +// maximum size of the architecture name EEPROMs can store +#define NN_MAX_ARCHNAME 64 +// maximum size of an address. +// This only matters in places where an address is returned through a component, as it is the amount of space to allocate for the response. +// Past this there would be a truncation which would invalidate the address. +// However, 64 is unrealistically long, as UUIDv4 only needs 36. +// Please, do not go above this. +#define NN_MAX_ADDRESS 64 +// the port used by tunnel cards. This port is invalid for modems. +#define NN_TUNNEL_PORT 0 +// maximum amount of users a computer can have +#define NN_MAX_USERS 64 +// maximum length of a username +#define NN_MAX_USERNAME 128 + +// the maximum size of a UTF-8 character +#define NN_MAX_UNICODE_BUFFER 4 + +// the maximum size of a component error message. If the error is bigger than this, +// it is truncated. +#define NN_MAX_ERROR_SIZE 1024 + +// unicode (UTF-8) support library + +typedef unsigned int nn_codepoint; + +bool nn_unicode_validate(const char *s, size_t len); +// validates only the *first* codepoint in the NULL-terminated string. +// This returns the length in bytes of the codepoint, with 0 meaning +// invalid. +size_t nn_unicode_validateFirstChar(const char *s, size_t len); + +// returns the amount of unicode codepoints in the UTF-8 string. +// Undefined behavior for invalid UTF-8, make sure to validate it if needed. +size_t nn_unicode_len(const char *s, size_t len); +// returns the amount of unicode codepoints in the UTF-8 string. +// If s is invalid UTF-8, all invalid bytes are considered a 1-byte codepoint. +size_t nn_unicode_lenPermissive(const char *s, size_t len); + +// Writes the codepoints of s into codepoints. +// Undefined behavior for invalid UTF-8, make sure to validate it if needed. +// The codepoints buffer must be big enough to store the string, use nn_unicode_len() +// to get the required buffer length. +void nn_unicode_codepoints(const char *s, size_t len, nn_codepoint *codepoints); +// Writes the codepoints of s into codepoints. +// If s is invalid UTF-8, all invalid bytes are considered a 1-byte codepoint. +// The codepoints buffer must be big enough to store the string, use nn_unicode_lenPermissive() +// to get the required buffer length. +void nn_unicode_codepointsPermissive(const char *s, size_t len, nn_codepoint *codepoints); + +// Returns the first codepoint from a UTF-8 string. +// If s is invalid UTF-8, the behavior is undefined. +nn_codepoint nn_unicode_firstCodepoint(const char *s); +// Returns the size, in bytes, required by UTF-8 for a codepoint. +size_t nn_unicode_codepointSize(nn_codepoint codepoint); +// Writes the UTF-8 bytes for a given codepoint into buffer. +// It does NOT write a NULL terminator, but it does return the length. +size_t nn_unicode_codepointToChar(char buffer[NN_MAX_UNICODE_BUFFER], nn_codepoint codepoint); +// the width, on a screen, for a codepoint. +// This matters for emojies. +size_t nn_unicode_charWidth(nn_codepoint codepoint); +// The width, on a screen, for an entire string. +// The behavior is undefined for +size_t nn_unicode_wlen(const char *s, size_t len); +size_t nn_unicode_wlenPermissive(const char *s, size_t len); + +// Returns the amount of bytes needed to store the UTF-8 encoded text. +// The behavior on invalid codepoints is undefined. +size_t nn_unicode_countBytes(nn_codepoint *codepoints, size_t len); +// Writes the UTF-8 encoded text. +// DOES NOT WRITE A NULL TERMINATOR. +// s must be big enough to store the string, use nn_unicode_bytelen() +// to allocate the correct amount of space. +// The behavior on invalid codepoints is undefined. +void nn_unicode_writeBytes(char *s, nn_codepoint *codepoints, size_t len); + +// Returns the uppercase version of the codepoint +nn_codepoint nn_unicode_upper(nn_codepoint codepoint); +// Returns the lowercase version of the codepoint +nn_codepoint nn_unicode_lower(nn_codepoint codepoint); + +// The type of a the function used as the allocator. +// The expected behavior is as follows: +// alloc(state, NULL, 0, newSize) -> malloc(newSize) +// alloc(state, memory, oldSize, 0) -> free(memory) +// alloc(state, memory, oldSize, newSize) -> realloc(memory, newSize) +// +// NeoNucleus will ensure oldSize is what the application requested on the last allocation. +// This is useful for allocators which may not do extensive bookkeeping. +// In the case of Out Of Memory, you are expected to return NULL. +typedef void *nn_AllocProc(void *state, void *memory, size_t oldSize, size_t newSize); + +// Meant to return the time, in seconds, since some epoch. +typedef double nn_TimeProc(void *state); + +typedef size_t nn_RngProc(void *state); + +typedef enum nn_LockAction { + // create the mutex + NN_LOCK_CREATE, + // destroy the mutex + NN_LOCK_DESTROY, + // lock the mutex + NN_LOCK_LOCK, + // unlock the mutex + NN_LOCK_UNLOCK, +} nn_LockAction; + +typedef struct nn_LockRequest { + // mutate it for NN_LOCK_INIT + void *lock; + nn_LockAction action; +} nn_LockRequest; + +// Intended for a plain mutex. +// This is used for synchronization. OpenComputers achieves synchronization +// between the worker threads by sending them as requests to a central thread (indirect methods). +// In NeoNucleus, it is the same stuff, but direct ones may still be used across threads. +// Do note that locks are only used in "full" component implementations, such as the volatile storage devices. +// The interfaces do not do any automatic synchronization via locks, all synchronization is assumed +// to be handled in the implementer of the interface, because only you know how to best synchronize +// it with the outside world. +typedef void nn_LockProc(void *state, nn_LockRequest *req); + +// The *context* NeoNucleus is operating in. +// This determines: +// - How memory is allocated +// - How random numbers are generated and what the range is +// - What the current time is +// - How locks work +typedef struct nn_Context { + void *state; + nn_AllocProc *alloc; + nn_TimeProc *time; + // the maximum value the RNG can produce. + // The RNG is assumed to generate [0, rngMaximum]. INCLUSIVE. + // When a [0, 1) range is needed, the value is divided by (rngMaximum+1), + // so rngMaximum+1 MUST NOT OVERFLOW. + size_t rngMaximum; + nn_RngProc *rng; + nn_LockProc *lock; +} nn_Context; + +// if NN_BAREMETAL is defined when NeoNucleus is compiled, +// this function will fill in a bogus context that does nothing. +// Otherwise, it fills in a context which uses the C standard library. +// This function is only meant to be called once, to get the initial context. +// It does seed the RNG when using the C standard library, so do not +// call it in loops. +void nn_initContext(nn_Context *ctx); + +// Memory allocation!!! + +void *nn_alloc(nn_Context *ctx, size_t size); +void nn_free(nn_Context *ctx, void *memory, size_t size); +void *nn_realloc(nn_Context *ctx, void *memory, size_t oldSize, size_t newSize); + +char *nn_strdup(nn_Context *ctx, const char *s); +void nn_strfree(nn_Context *ctx, char *s); + +typedef struct nn_Lock nn_Lock; + +nn_Lock *nn_createLock(nn_Context *ctx); +void nn_destroyLock(nn_Context *ctx, nn_Lock *lock); +void nn_lock(nn_Context *ctx, nn_Lock *lock); +void nn_unlock(nn_Context *ctx, nn_Lock *lock); + +double nn_currentTime(nn_Context *ctx); + +// generate a random RNG from 0 to the maximum +size_t nn_rand(nn_Context *ctx); +// generate a random float [0, 1) +double nn_randf(nn_Context *ctx); +// generate a random float [0, 1] +double nn_randfi(nn_Context *ctx); + +typedef char nn_uuid[37]; +void nn_randomUUID(nn_Context *ctx, nn_uuid uuid); + +// Basic utils + +// Does canonical path handling. Is used for sandboxing paths. +// It will turn \ to /, will turn invalid characters into spaces, +// and will resolve ... +// it also gets rid of / prefixes, / suffixes and // +void nn_simplifyPath(const char original[NN_MAX_PATH], char simplified[NN_MAX_PATH]); + +typedef enum nn_Exit { + // no error + NN_OK = 0, + // out of memory. + NN_ENOMEM, + // over the limit. For example, adding too many architectures to a machine. + NN_ELIMIT, + // internal stack underflow when managing the stack. + NN_EBELOWSTACK, + // internal stack overflow when carrying values. + NN_ENOSTACK, + // bad invocation, error message stored in computer state + NN_EBADCALL, + // bad state, the function was called at the wrong time + NN_EBADSTATE, +} nn_Exit; + +// This stores necessary data between computers +typedef struct nn_Universe nn_Universe; + +nn_Universe *nn_createUniverse(nn_Context *ctx, void *userdata); +void nn_destroyUniverse(nn_Universe *universe); +void *nn_getUniverseData(nn_Universe *universe); +size_t nn_getUniverseMemoryLimit(nn_Universe *universe); +size_t nn_limitMemory(nn_Universe *universe, size_t memory); +void nn_setUniverseMemoryLimit(nn_Universe *universe, size_t limit); +size_t nn_getUniverseStorageLimit(nn_Universe *universe); +void nn_setUniverseStorageLimit(nn_Universe *universe, size_t limit); +size_t nn_limitStorage(nn_Universe *universe, size_t storage); + +// The actual computer +typedef struct nn_Computer nn_Computer; + +typedef enum nn_ComputerState { + // the machine is running just fine + NN_RUNNING = 0, + // machine is initializing. This is the state after createComputer but before the first nn_tick. + // It is a required state of various initialization functions. + NN_BOOTUP, + // the machine has powered off. + NN_POWEROFF, + // the machine demands being restarted. + NN_RESTART, + // the machine has crashed. RIP + NN_CRASHED, + // the machine ran out of energy. + NN_BLACKOUT, + // change architecture. + NN_CHARCH, +} nn_ComputerState; + +typedef enum nn_ArchitectureAction { + // create the local state + NN_ARCH_INIT, + // destroy the local state + NN_ARCH_DEINIT, + // run 1 tick or synchronized task + NN_ARCH_TICK, + // get the free memory + NN_ARCH_FREEMEM, + // deserialize from an encoded state, passed into request. + NN_ARCH_DESERIALIZE, + // serialize to an encoded state, pushed it as a string + NN_ARCH_SERIALIZE, +} nn_ArchitectureAction; + +typedef struct nn_ArchitectureRequest { + // the state pointer passed through + void *globalState; + // the computer which made the request + nn_Computer *computer; + // the local state bound to this computer. + // In NN_ARCH_INIT, this is NULL, and must be set to the new state, or an appropriate exit code returned. + void *localState; + // the action requested + nn_ArchitectureAction action; + union { + // in the case of NN_ARCH_TICK, where the tick is synchronized + bool synchronized; + // in the case of NN_ARCH_FREEMEM, the free memory + size_t freeMemory; + // in the case of NN_ARCH_DESERIALIZE, the buffer. + struct { + const char *memIn; + size_t memLen; + }; + }; +} nn_ArchitectureRequest; + +typedef nn_Exit nn_ArchitectureHandler(nn_ArchitectureRequest *req); + +typedef struct nn_Architecture { + const char *name; + void *state; + nn_ArchitectureHandler *handler; +} nn_Architecture; + +// Standard RAM sizes. +// Standard OC goes from tier 1 to tier 6, +// NN adds 2 more tiers. +extern size_t nn_ramSizes[8]; + +typedef struct nn_Beep { + // frequenc, in Hz + double frequency; + // duration, in seconds + double duration; + // 0 is mute, 1 is 100% + double volume; +} nn_Beep; + +// Morse beep, like a normal beep except it follows a morse code pattern. +// . is a short sound +// - is a long sound, 2x in length +// a space is a pause, 2x in length and dead silent. +// Every sound, including actual pauses, have a 50ms pause between them. +typedef struct nn_MorseBeep { + const char *pattern; + // frequency, in Hz + double frequency; + // duration of a ., in seconds + double beepDuration; + // 0 is mute, 1 is 100% + double volume; +} nn_MorseBeep; + +typedef enum nn_EnvironmentAction { + NN_ENV_DRAWENERGY, + NN_ENV_POWERON, + NN_ENV_POWEROFF, + NN_ENV_CRASHED, + NN_ENV_BEEP, + NN_ENV_BEEPMORSE, +} nn_EnvironmentAction; + +typedef struct nn_EnvironmentRequest { + nn_Computer *computer; + void *userdata; + nn_EnvironmentAction action; + union { + // for DRAWENERGY, is the amount to remove, and must be set to the amount remaining + double energy; + // for BEEP, information about the beep + nn_Beep beep; + // for BEEPMORSE, information about the beep + nn_MorseBeep morseBeep; + }; +} nn_EnvironmentRequest; + +typedef void nn_EnvironmentHandler(nn_EnvironmentRequest *req); + +typedef struct nn_Environment { + void *userdata; + nn_EnvironmentHandler *handler; +} nn_Environment; + +// The state of a *RUNNING* computer. +// Powered off computers shall not have a state, and as far as NeoNucleus is aware, +// not exist. +// The computer API *is not thread-safe*, so it is recommended that you use an external lock to manage it if you are running +// it in a multi-threaded environment. +// The userdata pointer is meant to store external data required by the environment. +// totalMemory is a hint to the architecture as to how much memory to allow the runner to use. It is in bytes. +// maxComponents and maxDevices determine how many components can be connected simultaneously to this computer, and how much device info can be +// registered on this computer. +nn_Computer *nn_createComputer(nn_Universe *universe, void *userdata, const char *address, size_t totalMemory, size_t maxComponents, size_t maxDevices); +void nn_retainComputer(nn_Computer *computer); +void nn_retainComputerN(nn_Computer *computer, size_t n); +// Destroys the state, effectively shutting down the computer. +void nn_destroyComputer(nn_Computer *computer); +void nn_destroyComputerN(nn_Computer *computer, size_t n); +void nn_lockComputer(nn_Computer *computer); +void nn_unlockComputer(nn_Computer *computer); +// stops the computer if an architecture state is already present, +// will also clear the signal buffer and set the state to NN_BOOTUP. +nn_Exit nn_startComputer(nn_Computer *computer); +// destroys the architecture state if present. +// Will also do other shutdown routines, such as unmounting every +void nn_stopComputer(nn_Computer *computer); +void nn_forceCrashComputer(nn_Computer *computer, const char *s); +// returns whether an architecture state is present +bool nn_isComputerOn(nn_Computer *computer); +void nn_setComputerEnvironment(nn_Computer *computer, nn_Environment env); + +void nn_setDirectCost(nn_Computer *computer, double directCost); +double nn_getDirectCost(nn_Computer *computer); + +// Device information + +// Standard device attribute fields + +#define NN_DEVICEATTR_CLASS "class" +#define NN_DEVICEATTR_DESC "description" +#define NN_DEVICEATTR_VENDOR "vendor" +#define NN_DEVICEATTR_PRODUCT "product" +#define NN_DEVICEATTR_VERSION "version" +#define NN_DEVICEATTR_SERIAL "serial" +#define NN_DEVICEATTR_CAPACITY "capacity" +#define NN_DEVICEATTR_SIZE "size" +#define NN_DEVICEATTR_CLOCK "clock" +#define NN_DEVICEATTR_WIDTH "width" + +// Standard device classes + +#define NN_DEVICECLASS_SYSTEM "system" +#define NN_DEVICECLASS_BRIDGE "bridge" +#define NN_DEVICECLASS_MEMORY "memory" +#define NN_DEVICECLASS_PROCESSOR "processor" +#define NN_DEVICECLASS_ADDRESS "address" +#define NN_DEVICECLASS_STORAGE "storage" +#define NN_DEVICECLASS_DISK "disk" +#define NN_DEVICECLASS_TAPE "tape" +#define NN_DEVICECLASS_BUS "bus" +#define NN_DEVICECLASS_NETWORK "network" +#define NN_DEVICECLASS_DISPLAY "display" +#define NN_DEVICECLASS_INPUT "input" +#define NN_DEVICECLASS_PRINTER "printer" +#define NN_DEVICECLASS_MULTIMEDIA "multimedia" +#define NN_DEVICECLASS_COMMUNICATION "communication" +#define NN_DEVICECLASS_POWER "power" +#define NN_DEVICECLASS_VOLUME "volume" +#define NN_DEVICECLASS_GENERIC "generic" + +typedef struct nn_DeviceField { + const char *name; + const char *value; +} nn_DeviceField; + +typedef struct nn_CommonDeviceInfo { + const char *CLASS; + const char *DESC; + const char *VENDOR; + const char *PRODUCT; + const char *VERSION; + const char *SERIAL; + const char *CAPACITY; + const char *SIZE; + const char *CLOCK; + const char *WIDTH; +} nn_CommonDeviceInfo; + +const char *nn_deviceInfoAt(nn_Computer *computer, size_t idx); +const nn_DeviceField *nn_getDeviceInfo(nn_Computer *computer, size_t idx, size_t *fieldCount); +nn_Exit nn_addDeviceInfo(nn_Computer *computer, const char *addr, const nn_DeviceField *fields); +nn_Exit nn_addDeviceInfoL(nn_Computer *computer, const char *addr, const nn_DeviceField *fields, size_t fieldCount); +nn_Exit nn_addCommonDeviceInfo(nn_Computer *computer, const char *addr, nn_CommonDeviceInfo info); +// Sets every field to NULL. +void nn_clearCommonDeviceInfo(nn_CommonDeviceInfo *info); +bool nn_removeDeviceInfo(nn_Computer *computer, const char *addr); + +void nn_beepComputer(nn_Computer *computer, nn_Beep beep); +void nn_beepComputerMorse(nn_Computer *computer, nn_MorseBeep beep); + +// get the userdata pointer +void *nn_getComputerUserdata(nn_Computer *computer); +const char *nn_getComputerAddress(nn_Computer *computer); +nn_Universe *nn_getComputerUniverse(nn_Computer *computer); +nn_Context *nn_getUniverseContext(nn_Universe *universe); +nn_Context *nn_getComputerContext(nn_Computer *computer); + +// Returns the memory usage limit of the computer. +size_t nn_getTotalMemory(nn_Computer *computer); +// Gets the total amount of free memory the computer has available. The total memory - this is the amount of memory used. +size_t nn_getFreeMemory(nn_Computer *computer); +// Gets the total amount of used memory the computer has allocated. +// This is just the total minus the free, and does not take into +// account the overhead of storing the computer instance. +size_t nn_getUsedMemory(nn_Computer *computer); +// gets the current uptime of a computer. When the computer is not running, this value can be anything and loses all meaning. +double nn_getUptime(nn_Computer *computer); + +// Deserialize an encoded computer state. +// Encoding depends on architecture. +// Will push it as a string and then call the architecture, which will decode it and pop it. +nn_Exit nn_deserializeComputer(nn_Computer *computer, const char *buf, size_t buflen); + +// Serialize the computer state. +// Encoding depends on architecture. +// Pushes the output, if successful, as a string on the stack. +nn_Exit nn_serializeComputer(nn_Computer *computer); + +// address is copied. +// It can be NULL if you wish to have no tmp address. +// It can fail due to out-of-memory errors. +nn_Exit nn_setTmpAddress(nn_Computer *computer, const char *address); +// can return NULL if none was set +const char *nn_getTmpAddress(nn_Computer *computer); + +// Registers a user to the computer. +nn_Exit nn_addUser(nn_Computer *computer, const char *user); +// Unregisters a user from the computer. +// If they were never there, nothing is removed and all is fine. +// It returns if the user was originally there. +bool nn_removeUser(nn_Computer *computer, const char *user); +// NULL for out-of-bound users +// Can be used to iterate all users. +const char *nn_getUser(nn_Computer *computer, size_t idx); +// Helper function. +// Always returns true if 0 users are registered. +// If users are registered, it will only return true if the specified +// user is registered. +// This can be used for checking signals. +bool nn_hasUser(nn_Computer *computer, const char *user); + +// Sets the computer's architecture. +// The architecture determines everything from how the computer runs, to how it turns off. +// Everything is limited by the architecture. +// The architecture is copied, it can be freed after this is called. +void nn_setArchitecture(nn_Computer *computer, const nn_Architecture *arch); +// Gets the current architecture. +nn_Architecture nn_getArchitecture(nn_Computer *computer); +// Sets the computer's desired architecture. +// The desired architecture indicates, when the computer state is CHARCH, what the new architecture should be. +// This is set even if it is not in the supported architecture list, *you must check if it is in that list first.* +// The architecture is copied, it can be freed after this is called. +void nn_setDesiredArchitecture(nn_Computer *computer, const nn_Architecture *arch); +// Gets the desired architecture. This is the architecture the computer should use after changing architectures. +nn_Architecture nn_getDesiredArchitecture(nn_Computer *computer); +// Adds a new supported architecture, which indicates to the code running on this computer that it is possible to switch to that architecture. +// The architecture is copied, it can be freed after this is called. +nn_Exit nn_addSupportedArchitecture(nn_Computer *computer, const nn_Architecture *arch); +// Returns the array of supported architectures, as well as the length. +const nn_Architecture *nn_getSupportedArchitectures(nn_Computer *computer, size_t *len); +// Helper function for searching for an architecture using a computer which supports it and the architecture name. +// If the architecture is not found, it returns one with a NULL name. +nn_Architecture nn_findSupportedArchitecture(nn_Computer *computer, const char *name); + +// sets the energy capacity of the computer. +void nn_setTotalEnergy(nn_Computer *computer, double maxEnergy); +// gets the energy capacity of the computer +double nn_getTotalEnergy(nn_Computer *computer); +// gets the current amount of energy +double nn_getEnergy(nn_Computer *computer); +// Returns true if there is no more energy left, and a blackout has occured. +bool nn_removeEnergy(nn_Computer *computer, double energy); + +// copies the string into the local error buffer. The error is NULL terminated, but also capped by NN_MAX_ERROR_SIZE +void nn_setError(nn_Computer *computer, const char *s); +// set a default error message from an exit. +// Does nothing for EBADCALL. +void nn_setErrorFromExit(nn_Computer *computer, nn_Exit exit); +// copies the string into the local error buffer. The error is capped by NN_MAX_ERROR_SIZE-1. The -1 is there because the NULL terminator is still inserted at the end. +// Do note that nn_getError() still returns a NULL-terminated string, thus NULL terminators in this error will lead to a shortened error. +void nn_setLError(nn_Computer *computer, const char *s, size_t len); +// Gets a pointer to the local error buffer. This is only meaningful when NN_EBADCALL is returned. +const char *nn_getError(nn_Computer *computer); +// clears the computer's error buffer, making nn_getError return a simple empty string. +void nn_clearError(nn_Computer *computer); + +// sets the computer state to the desired state. This is meant for architectures to report things like reboots or shutdowns, DO NOT ABUSE THIS. +void nn_setComputerState(nn_Computer *computer, nn_ComputerState state); +// gets the current computer state +nn_ComputerState nn_getComputerState(nn_Computer *computer); + +// Checks if the uptime is below the idle timestamp. +bool nn_isComputerIdle(nn_Computer *computer); +// Shifts over the idle timestamp. +void nn_addIdleTime(nn_Computer *computer, double time); +void nn_resetIdleTime(nn_Computer *computer); +// runs a tick of the computer. Make sure to check the state as well! +// Does not do anything if we're currently waiting on a synced call +// This automatically resets the component budgets and call budget. +// It also sets the idle timestamp to the current uptime. +nn_Exit nn_tick(nn_Computer *computer); + +// runs a synchronized tick of the computer. How this differs depends on architecture. +// Generally, this is meant to be in the same thread for all computers, and is if the external world is fundamentally not thread-safe, +// however components must interact with it. +// In this case, those component methods would be marked as NN_INDIRECT, or more accurately will not be marked as NN_DIRECT, and the architecture would queue them as synchronized tasks. +// Architectures should generally NOT ignore this if they can. +nn_Exit nn_tickSynchronized(nn_Computer *computer); + +// raw component and methods + +typedef struct nn_Component nn_Component; + +typedef enum nn_MethodFlags { + // calling will consume the entire call budget + NN_INDIRECT = 0, + // calling will only consume 1 call from the call budget + NN_DIRECT = (1<<0), + // this indicates this method wraps a *field* + // getter means calling it with no arguments will return the current value, + NN_GETTER = (1<<1), + // this indicates this method wraps a *field* + // setter means calling it with 1 argument will try to set the value. + NN_SETTER = (1<<2), +} nn_MethodFlags; + +#define NN_FIELD_MASK (NN_GETTER | NN_SETTER) + +typedef struct nn_Method { + const char *name; + const char *doc; + nn_MethodFlags flags; +} nn_Method; + +typedef enum nn_ComponentAction { + // component dropped + NN_COMP_DROP, + // component method invoked + NN_COMP_INVOKE, + // checking if component method is enabled + // (may be locked by tier) + NN_COMP_CHECKMETHOD, + // userdata request + NN_COMP_USERDATA, +} nn_ComponentAction; + +typedef enum nn_UserdataAction { + NN_USER_DROP, + NN_USER_SERIALIZE, + NN_USER_DESERIALIZE, + NN_USER_GETMETHOD, + NN_USER_INVOKE, +} nn_UserdataAction; + +typedef struct nn_UserdataRequest { + void *state; + nn_UserdataAction action; + union { + struct { + const char *data; + size_t len; + } deserialize; + struct { + size_t idx; + // set to NULL if idx is out of bounds + nn_Method *method; + } getmethod; + struct { + const char *method; + size_t returnCount; + } invoke; + }; +} nn_UserdataRequest; + +typedef struct nn_ComponentRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + void *classState; + const char *compAddress; + nn_ComponentAction action; + // method index + unsigned int methodIdx; + union { + // return count + size_t returnCount; + // method enabled + bool methodEnabled; + nn_UserdataRequest *user; + }; +} nn_ComponentRequest; + +typedef nn_Exit (nn_ComponentHandler)(nn_ComponentRequest *request); + +// creates a blank component. +// It has no methods, +nn_Component *nn_createComponent(nn_Universe *universe, const char *address, const char *type); +void nn_retainComponent(nn_Component *c); +void nn_retainComponentN(nn_Component *c, size_t n); +void nn_dropComponent(nn_Component *c); +void nn_dropComponentN(nn_Component *c, size_t n); + +// configure the state +void nn_setComponentHandler(nn_Component *c, nn_ComponentHandler *handler); +void nn_setComponentState(nn_Component *c, void *state); +void nn_setComponentClassState(nn_Component *c, void *state); +// sets the methods, same implications as setComponentMethodsArray. +// methods is NULL-terminated, as in, it is terminated by a method with a NULL name. +nn_Exit nn_setComponentMethods(nn_Component *c, const nn_Method *methods); +// sets the methods. +// The memory of the strings is copied, so they can be freed after this returns. +// This operation is NOT atomic, if it fails, it will clear out the previous methods. +nn_Exit nn_setComponentMethodsArray(nn_Component *c, const nn_Method *methods, size_t count); +// Sets an internal type ID, which is meant to be a more precise typename. +// For example, ncomplib would set ncl-screen for the screen component, +// so the GPU can confirm it is being bound to a screen it knows how to use. +nn_Exit nn_setComponentTypeID(nn_Component *c, const char *internalTypeID); + +// Sets the method flags +void nn_setComponentMethodFlags(nn_Component *c, const char *method, nn_MethodFlags flags); +// combines method flags +void nn_addComponentMethodFlags(nn_Component *c, const char *method, nn_MethodFlags flags); +// removes method flags +void nn_removeComponentMethodFlags(nn_Component *c, const char *method, nn_MethodFlags flags); + +// get component state +void *nn_getComponentState(nn_Component *c); +// get component class state +void *nn_getComponentClassState(nn_Component *c); +// counts how many methods are registered. May return too many if some of them are not enabled. +size_t nn_countComponentMethods(nn_Component *c); +// will fill the methodnames array with the names of the *enabled* methods. +// Will set *len to the amount of methods. +void nn_getComponentMethods(nn_Component *c, const char **methodnames, size_t *len); +// whether a method is defined and enabled +bool nn_hasComponentMethod(nn_Component *c, const char *method); +const char *nn_getComponentDoc(nn_Component *c, const char *method); +nn_MethodFlags nn_getComponentMethodFlags(nn_Component *c, const char *method); +const char *nn_getComponentType(nn_Component *c); +const char *nn_getComponentTypeID(nn_Component *c); +const char *nn_getComponentAddress(nn_Component *c); + +// Adds a component to the computer on a given slot. +// This will also queue a component_added signal if the computer is in a running state, unless silent is true. +// If the component already is mounted, an error is returned. +nn_Exit nn_mountComponent(nn_Computer *c, nn_Component *comp, int slot, bool silent); +// Removes a component from the computer. +// This will also queue a component_removed signal if the computer is in a running state, unless silent is true. +// If the component is not mounted, no error is returned. +nn_Exit nn_unmountComponent(nn_Computer *c, const char *address, bool silent); +nn_Exit nn_swapComponents(nn_Computer *c, nn_Component *previous, nn_Component *next, int slot); +// gets a component by address. Will return NULL if there is none. +nn_Component *nn_getComponent(nn_Computer *c, const char *address); +int nn_getComponentSlot(nn_Computer *c, const char *address); +size_t nn_countComponents(nn_Computer *c); +void nn_getComponents(nn_Computer *c, const char **components); + +// invoke the component method. +// Everything on-stack is taken as an argument. +// Will pop off trailing nulls. +// Every remaining stack value is what the component returned. +// In the case of errors, the contents of the stack is undefined +nn_Exit nn_invokeComponent(nn_Computer *computer, const char *compAddress, const char *method); + +// send a signal to a component. +// Computer actually can be NULL, but the component may crash if the signal +// assumes one is specified. +nn_Exit nn_signalComponent(nn_Component *component, nn_Computer *computer, const char *signal); + +// Userdata!!!! + +// Allocates a userdata index. Returns -1 on failure or if there are too many. +int nn_allocUserdata(nn_Computer *computer, void *state, const char *compAddress); + +// Frees a userdata index. +void nn_freeUserdata(nn_Computer *computer, size_t userdata); + +// Returns whether the userdata index is valid +bool nn_isUserdataValid(nn_Computer *computer, size_t userdata); + +// If compAddress is correct and userdata is valid, returns the state pointer. +// If not, returns NULL, to prevent UB. +void *nn_unwrapUserdata(nn_Computer *computer, size_t userdata, const char *compAddress); + +// gets the component address which manages this userdata +const char *nn_getUserdataComponent(nn_Computer *computer, size_t userdata); + +// Gets information about a method of this userdata, by index. +// If idx is out of bounds, this returns true, which means to stop iteration. +// If method->name is NULL, the method should be skipped. +bool nn_getUserdataMethod(nn_Computer *computer, size_t userdata, size_t idx, nn_Method *method); + +// Invokes a method on some userdata, same semantics as nn_invokeComponent +nn_Exit nn_invokeUserdata(nn_Computer *computer, size_t userdata, const char *method); + +// Serializes the userdata into a buffer and pushes it as a string. +// Make sure to keep track of its index and component address! +nn_Exit nn_serializeUserdata(nn_Computer *computer, size_t userdata); + +// Deserializes userdata at a particular index. +// NOTE: if the component does not exist, or the userdata index is already taken, this errors. +nn_Exit nn_deserializeUserdata(nn_Computer *computer, size_t userdata, const char *compAddress, const char *buf, size_t len); + +// default call budgets for 4 tiers of CPUs +extern double nn_defaultCallBudgets[4]; +// the call budget of a creative CPU +extern double nn_unlimitedCallBudget; +// default component limits for 4 tiers of component buses / CPUs +extern size_t nn_defaultComponentLimits[4]; +// the component limit of a creative component bus +extern size_t nn_creativeComponentLimit; + +// Sets the call budget. +// The default is 1. +void nn_setCallBudget(nn_Computer *computer, double budget); + +// gets the total call budget +double nn_getCallBudget(nn_Computer *computer); + +// returns the remaining call budget +double nn_callBudgetRemaining(nn_Computer *computer); + +// automatically called by nn_tick() +void nn_resetCallBudget(nn_Computer *computer); + +// returns whether there is no more call budget left. +// At this point, the architecture should exit with a yield. +bool nn_componentsOverused(nn_Computer *computer); + +void nn_resetComponentBudgets(nn_Computer *computer); + +// Uses 1/perTick to the component budget. +// Upon a full component budget being used for that component, it returns true. +// nn_componentsOverused() will also return true. +// This indicates the architecture should yield, to throttle the computer for overuse. +bool nn_costComponent(nn_Computer *computer, double perTick); +// Uses amount/perTick to the component budget. +// Upon a full component budget being used for that component, it returns true. +// nn_componentsOverused() will also return true. +// This indicates the architecture should yield, to throttle the computer for overuse. +bool nn_costComponentN(nn_Computer *computer, double amount, double perTick); + +// call stack operations. +// The type system and API are inspired by Lua, as Lua remains the most popular architecture for OpenComputers. +// This does support other languages, however it may make some APIs clunky due to the usage of tables and 1-based indexing. +// Internally, reference counting is used to manage the memory automatically. The API is designed such that strong reference cycles +// cannot occur. + +// returns if there is enough space for [amount] values +bool nn_checkstack(nn_Computer *computer, size_t amount); + +// pushes a null on the call stack +nn_Exit nn_pushnull(nn_Computer *computer); +// pushes a boolean on the call stack +nn_Exit nn_pushbool(nn_Computer *computer, bool truthy); +// pushes a number on the call stack +nn_Exit nn_pushnumber(nn_Computer *computer, double num); +// casts [num] to a double and pushes it on the call stack +nn_Exit nn_pushinteger(nn_Computer *computer, intptr_t num); +// pushes a NULL-terminated string on the call stack. The string is copied, so you can free it afterwards without worry. +nn_Exit nn_pushstring(nn_Computer *computer, const char *str); +// pushes a string on the call stack. The string is copied, so you can free it afterwards without worry. The copy will have a NULL terminator inserted +// at the end for APIs which need it, but the length is also stored. +nn_Exit nn_pushlstring(nn_Computer *computer, const char *str, size_t len); +// pushes a computer userdata to the stack. This is indicative of a resource, such as an HTTP request. +nn_Exit nn_pushuserdata(nn_Computer *computer, size_t userdataIdx); +// pushes a table meant to be an array. [len] is the length of the array. The keys are numbers and 1-indexed, just like in Lua. +// The values are popped, then the array is pushed. +nn_Exit nn_pusharraytable(nn_Computer *computer, size_t len); +// pushes a table. [len] is the amount of pairs. Keys should not be duplicated, as they are not de-duplicated. +// The stack should have a sequence of K1,V1,K2,V2,etc., len pairs long. +// The pairs are popped, then the array is pushed. +nn_Exit nn_pushtable(nn_Computer *computer, size_t len); + +// stack management + +// pops the top value off the stack +nn_Exit nn_pop(nn_Computer *computer); +// pops the top N values off the stack +nn_Exit nn_popn(nn_Computer *computer, size_t n); +// pushes the top value onto the stack, effectively duplicating the top value. +nn_Exit nn_dupe(nn_Computer *computer); +// pushes the top N values onto the stack, effectively duplicating the top N values. +nn_Exit nn_dupen(nn_Computer *computer, size_t n); + +// pushes the value at idx. +nn_Exit nn_dupeat(nn_Computer *computer, size_t idx); + +// get the current amount of values on the call stack. +// For component calls, calling this at the start effectively gives you the argument count. +size_t nn_getstacksize(nn_Computer *computer); +// Removes all values from the stack. +// It is recommended to do this when initiating a component call or +// after returning from errors, as the call stack may have +// random junk on it. +void nn_clearstack(nn_Computer *computer); + +// type check! The API may misbehave if types do not match, so always type-check! + +// Returns whether the value at [idx] is a null. +// [idx] starts at 0. +bool nn_isnull(nn_Computer *computer, size_t idx); +// Returns whether the value at [idx] is a boolean. +// [idx] starts at 0. +bool nn_isboolean(nn_Computer *computer, size_t idx); +// Returns whether the value at [idx] is a number. +// [idx] starts at 0. +bool nn_isnumber(nn_Computer *computer, size_t idx); +// Returns whether the value at [idx] is a number AND +// the number can safely be cast to an intptr_t. +bool nn_isinteger(nn_Computer *computer, size_t idx); +// Returns whether the value at [idx] is a string. +// [idx] starts at 0. +bool nn_isstring(nn_Computer *computer, size_t idx); +// Returns whether the value at [idx] is a userdata. +// [idx] starts at 0. +bool nn_isuserdata(nn_Computer *computer, size_t idx); +// Returns whether the value at [idx] is a table. +// [idx] starts at 0. +bool nn_istable(nn_Computer *computer, size_t idx); +// Returns the name of the type of the value at that index. +// For out of bounds indexes, "none" is returned. +const char *nn_typenameof(nn_Computer *computer, size_t idx); + +// Argument helpers + +// Returns true if the argument at that index is not null. +bool nn_checknull(nn_Computer *computer, size_t idx, const char *errMsg); +// Returns true if the argument at that index is not a boolean. +bool nn_checkboolean(nn_Computer *computer, size_t idx, const char *errMsg); +// Returns true if the argument at that index is not a number. +bool nn_checknumber(nn_Computer *computer, size_t idx, const char *errMsg); +// Returns true if the argument at that index is not an integer. +bool nn_checkinteger(nn_Computer *computer, size_t idx, const char *errMsg); +// Returns true if the argument at that index is not a string. +bool nn_checkstring(nn_Computer *computer, size_t idx, const char *errMsg); +// Returns true if the argument at that index is not userdata. +bool nn_checkuserdata(nn_Computer *computer, size_t idx, const char *errMsg); +// Returns true if the argument at that index is a table. +bool nn_checktable(nn_Computer *computer, size_t idx, const char *errMsg); + +// Checks if idx is equal to the stack size. +// If it is, it will push a null. +nn_Exit nn_defaultnull(nn_Computer *computer, size_t idx); +// Checks if idx is equal to the stack size. +// If it is, it will push a boolean [value]. +nn_Exit nn_defaultboolean(nn_Computer *computer, size_t idx, bool value); +// Checks if idx is equal to the stack size. +// If it is, it will push a number [num]. +nn_Exit nn_defaultnumber(nn_Computer *computer, size_t idx, double num); +// Checks if idx is equal to the stack size. +// If it is, it will push an integer [num]. +nn_Exit nn_defaultinteger(nn_Computer *computer, size_t idx, intptr_t num); +// Checks if idx is equal to the stack size. +// If it is, it will push a string [str]. +nn_Exit nn_defaultstring(nn_Computer *computer, size_t idx, const char *str); +// Checks if idx is equal to the stack size. +// If it is, it will push a string [str]. +nn_Exit nn_defaultlstring(nn_Computer *computer, size_t idx, const char *str, size_t len); +// Checks if idx is equal to the stack size. +// If it is, it will push the userdata [userdataIdx]. +nn_Exit nn_defaultuserdata(nn_Computer *computer, size_t idx, size_t userdataIdx); +// Checks if idx is equal to the stack size. +// If it is, it will push an empty table. +nn_Exit nn_defaulttable(nn_Computer *computer, size_t idx); + +// NOTE: behavior of the nn_to*() functions and nn_dumptable() when the values have the wrong types or at out of bounds indexes is undefined. + +// Returns the boolean value at [idx]. +bool nn_toboolean(nn_Computer *computer, size_t idx); +// Returns the number value at [idx]. +double nn_tonumber(nn_Computer *computer, size_t idx); +// Returns the number value at [idx] cast to an intptr_t. +// NOTE: for numbers where nn_isinteger() returns false, +// the cast is undefined. +// This includes values such as infinity and NaN, where +// the behavior is platform, ABI and compiler-specific. +intptr_t nn_tointeger(nn_Computer *computer, size_t idx); +// Returns the string value at [idx]. +const char *nn_tostring(nn_Computer *computer, size_t idx); +// Returns the string value and its length at [idx]. +const char *nn_tolstring(nn_Computer *computer, size_t idx, size_t *len); +// Returns the userdata index at [idx]. +size_t nn_touserdata(nn_Computer *computer, size_t idx); +// Takes a table value and pushes onto the stack the key-value pairs, as well as writes how many there were in [len]. +// It pushes them as K1,V1,K2,V2,K3,V3,etc., just like went in to pushtable. And yes, the keys are not de-duplicated. +nn_Exit nn_dumptable(nn_Computer *computer, size_t idx, size_t *len); + +// computes the cost of the top [values] values using the same algorithm as +// the modem. +// It will return -1 if the values are invalid. +// The algorithm is as mentioned in https://ocdoc.cil.li/component:modem, with 1 small change to match OC's code. +// and is as follows: +// - Every value adds a 2 byte overhead +// - Numbers add another 8 bytes, true/false/null another 1 byte, strings as +// many bytes as they contain, except empty strings count as 1 byte. +int nn_countValueCost(nn_Computer *computer, size_t values); + +// computes the signal cost. +// This is a slightly modified version of value cost, except it allows +// tables and userdata. +// All values are always valid. +// For userdata and tables: +// - Userdata adds another 8 bytes overhead like numbers do. +// - Tables add yet another 2 byte overhead for their terminator, and the sum of all of the size of the keys and values they contain as per this algorithm. +size_t nn_countSignalCost(nn_Computer *computer, size_t values); + +// Returns the amount of signals stored +size_t nn_countSignals(nn_Computer *computer); +// Pops [valueCount] values from the call stack and pushes them as a signal. +nn_Exit nn_pushSignal(nn_Computer *computer, size_t valueCount); +// Removes the first signal and pushes the values onto the call stack, while setting valueCount to the amount of values in the signal. +// If there is no signal, it returns EBADSTATE +nn_Exit nn_popSignal(nn_Computer *computer, size_t *valueCount); + +nn_Exit nn_transferErrorFrom(nn_Exit exit, nn_Computer *from, nn_Computer *to); + +// Signal helpers + +// common mouse buttons, not an exhaustive list +#define NN_BUTTON_LEFT 0 +#define NN_BUTTON_RIGHT 1 +#define NN_BUTTON_MIDDLE 2 + +// OC keycodes +// taken from https://github.com/MightyPirates/OpenComputers/blob/52da41b5e171b43fea80342dc75d808f97a0f797/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_keyboard.lua +#define NN_KEY_UNKNOWN 0 +#define NN_KEY_1 0x02 +#define NN_KEY_2 0x03 +#define NN_KEY_3 0x04 +#define NN_KEY_4 0x05 +#define NN_KEY_5 0x06 +#define NN_KEY_6 0x07 +#define NN_KEY_7 0x08 +#define NN_KEY_8 0x09 +#define NN_KEY_9 0x0A +#define NN_KEY_0 0x0B +#define NN_KEY_A 0x1E +#define NN_KEY_B 0x30 +#define NN_KEY_C 0x2E +#define NN_KEY_D 0x20 +#define NN_KEY_E 0x12 +#define NN_KEY_F 0x21 +#define NN_KEY_G 0x22 +#define NN_KEY_H 0x23 +#define NN_KEY_I 0x17 +#define NN_KEY_J 0x24 +#define NN_KEY_K 0x25 +#define NN_KEY_L 0x26 +#define NN_KEY_M 0x32 +#define NN_KEY_N 0x31 +#define NN_KEY_O 0x18 +#define NN_KEY_P 0x19 +#define NN_KEY_Q 0x10 +#define NN_KEY_R 0x13 +#define NN_KEY_S 0x1F +#define NN_KEY_T 0x14 +#define NN_KEY_U 0x16 +#define NN_KEY_V 0x2F +#define NN_KEY_W 0x11 +#define NN_KEY_X 0x2D +#define NN_KEY_Y 0x15 +#define NN_KEY_Z 0x2C + +#define NN_KEY_APOSTROPHE 0x28 +#define NN_KEY_AT 0x91 +#define NN_KEY_BACK 0x0E +#define NN_KEY_BACKSLASH 0x2B +// caps-lock +#define NN_KEY_CAPITAL 0x3A +#define NN_KEY_COLON 0x92 +#define NN_KEY_COMMA 0x33 +#define NN_KEY_ENTER 0x1C +#define NN_KEY_EQUALS 0x0D +// accent grave +#define NN_KEY_GRAVE 0x29 +#define NN_KEY_LBRACKET 0x1A +#define NN_KEY_LCONTROL 0x1D +// left alt +#define NN_KEY_LMENU 0x38 +#define NN_KEY_LSHIFT 0x2A +#define NN_KEY_MINUS 0x0C +#define NN_KEY_NUMLOCK 0x45 +#define NN_KEY_PAUSE 0xC5 +#define NN_KEY_PERIOD 0x34 +#define NN_KEY_RBRACKET 0x1B +#define NN_KEY_RCONTROL 0x9D +// right alt +#define NN_KEY_RMENU 0xB8 +#define NN_KEY_RSHIFT 0x36 +// scroll lock +#define NN_KEY_SCROLL 0x46 +#define NN_KEY_SEMICOLON 0x27 +#define NN_KEY_SLASH 0x35 +#define NN_KEY_SPACE 0x39 +#define NN_KEY_STOP 0x95 +#define NN_KEY_TAB 0x0F +#define NN_KEY_UNDERLINE 0x93 + +#define NN_KEY_UP 0xC8 +#define NN_KEY_DOWN 0xD0 +#define NN_KEY_LEFT 0xCB +#define NN_KEY_RIGHT 0xCD +#define NN_KEY_HOME 0xC7 +#define NN_KEY_END 0xCF +#define NN_KEY_PAGEUP 0xC9 +#define NN_KEY_PAGEDOWN 0xD1 +#define NN_KEY_INSERT 0xD2 +#define NN_KEY_DELETE 0xD3 + +#define NN_KEY_F1 0x3B +#define NN_KEY_F2 0x3C +#define NN_KEY_F3 0x3D +#define NN_KEY_F4 0x3E +#define NN_KEY_F5 0x3F +#define NN_KEY_F6 0x40 +#define NN_KEY_F7 0x41 +#define NN_KEY_F8 0x42 +#define NN_KEY_F9 0x43 +#define NN_KEY_F10 0x44 +#define NN_KEY_F11 0x57 +#define NN_KEY_F12 0x58 +#define NN_KEY_F13 0x64 +#define NN_KEY_F14 0x65 +#define NN_KEY_F15 0x66 +#define NN_KEY_F16 0x67 +#define NN_KEY_F17 0x68 +#define NN_KEY_F18 0x69 +#define NN_KEY_F19 0x71 + +#define NN_KEY_KANA 0x70 +#define NN_KEY_KANJI 0x94 +#define NN_KEY_CONVERT 0x79 +#define NN_KEY_NOCONVERT 0x7B +#define NN_KEY_YEN 0x7D +#define NN_KEY_CIRCUMFLEX 0x90 +#define NN_KEY_AX 0x96 + +#define NN_KEY_NUMPAD0 0x52 +#define NN_KEY_NUMPAD1 0x4F +#define NN_KEY_NUMPAD2 0x50 +#define NN_KEY_NUMPAD3 0x51 +#define NN_KEY_NUMPAD4 0x4B +#define NN_KEY_NUMPAD5 0x4C +#define NN_KEY_NUMPAD6 0x4D +#define NN_KEY_NUMPAD7 0x47 +#define NN_KEY_NUMPAD8 0x48 +#define NN_KEY_NUMPAD9 0x49 +#define NN_KEY_NUMPADMUL 0x37 +#define NN_KEY_NUMPADDIV 0xB5 +#define NN_KEY_NUMPADSUB 0x4A +#define NN_KEY_NUMPADADD 0x4E +#define NN_KEY_NUMPADDECIMAL 0x53 +#define NN_KEY_NUMPADCOMMA 0xB3 +#define NN_KEY_NUMPADENTER 0x9C +#define NN_KEY_NUMPADEQUALS 0x8D + +// the bottom side, can also be downwards / negative y +#define NN_SIDE_BOTTOM 0 +// the top side, can also be upwards / positive y +#define NN_SIDE_TOP 1 +// backwards, can also be north / negative z +#define NN_SIDE_BACK 2 +// forwards, can also be south / positive z +#define NN_SIDE_FRONT 3 +// to the right, can also be west / negative x +#define NN_SIDE_RIGHT 4 +// to the left, can also be east / positive x +#define NN_SIDE_LEFT 5 +// absolutely no clue +#define NN_SIDE_UNKNOWN 6 + +// pushes a screen_resized signal +nn_Exit nn_pushScreenResized(nn_Computer *computer, const char *screenAddress, int newWidth, int newHeight); +// pushes a touch signal +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushTouch(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player); +// pushes a drag signal +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushDrag(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player); +// pushes a drop signal +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushDrop(nn_Computer *computer, const char *screenAddress, double x, double y, int button, const char *player); +// pushes a scroll signal +// A positive direction usually means up, a negative one usually means down. +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushScroll(nn_Computer *computer, const char *screenAddress, double x, double y, double direction, const char *player); +// pushes a walk signal +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushWalk(nn_Computer *computer, const char *screenAddress, double x, double y, const char *player); + +// pushes a key_down event +// charcode is the unicode code-point of the typed character. It should be uppercase/lowercase depending on shift or capslock. +// keycode is an OC-specific keycode, and should be from the NN_KEY_* constants. +// player is the name of the player which used the keyboard. Some programs use it for splitscreen games. +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushKeyDown(nn_Computer *computer, const char *keyboardAddress, nn_codepoint charcode, int keycode, const char *player); +// pushes a key_up event +// charcode is the unicode code-point of the typed character. It should be uppercase/lowercase depending on shift or capslock. +// keycode is an OC-specific keycode, and should be from the NN_KEY_* constants. +// player is the name of the player which used the keyboard. Some programs use it for splitscreen games. +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushKeyUp(nn_Computer *computer, const char *keyboardAddress, nn_codepoint charcode, int keycode, const char *player); +// pushes a clipboard event +// clipboard should be a NULL-terminated string. +// NN does no truncation of the contents, but it is best to limit it. +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushClipboard(nn_Computer *computer, const char *keyboardAddress, const char *clipboard, const char *player); +// pushes a clipboard event +// len is the length of the clipboard. +// NN does no truncation of the contents, but it is best to limit it. +// The signal is checked, as in, the player must be a user of the computer if users are defined. +nn_Exit nn_pushLClipboard(nn_Computer *computer, const char *keyboardAddress, const char *clipboard, size_t len, const char *player); + +// pushes a redstone_changed signal. +// side is the side of the device the redstone changed on +// oldValue is the old value +// newValue is the new value, must not be equal to oldValue +// color is the color of the redstone signal +// if color < 0, it is set to null +nn_Exit nn_pushRedstoneChanged(nn_Computer *computer, const char *redstoneAddress, int side, int oldValue, int newValue, int color); + +// pushes a motion signal. +// Do note that it is meant to only be sent if the entity has a direct line of sight and if |(relX, relY, relZ)| >= sensitivity. +// This signal does *not* check if the motion sensor actually is sensitive enough to detect it, so make sure to check it yourself. +// relX, relY and relZ are the relative postion in 3D Cartesian space. +// entityName can be NULL if the entity has no name. +nn_Exit nn_pushMotion(nn_Computer *computer, double relX, double relY, double relZ, const char *entityName); + +// Pushes an internet_ready signal. +// This signal is queued when an internet socket is ready for commmunication. +nn_Exit nn_pushInternetReady(nn_Computer *computer, const char *id, size_t idlen); + +// A buffer with encoded values +typedef struct nn_EncodedNetworkContents { + nn_Context *ctx; + char *buf; + size_t buflen; + size_t valueCount; +} nn_EncodedNetworkContents; + +// applies basic encoding to a network message. This encoding has a header, and thus should remain backwards-compatible. +// The encoding serves 2 purposes: +// 1. Prevent shared memory between computers. Values do not use atomic reference counting, and thus this could lead to UAF or memory leaks. +// 2. Simplify implementing packet queues, which should be used in relays. The lack of access to raw values would force implementers to use +// an encoding anyways. +// This only encodes the contents, not the sender, hops, or other metadata which may be needed in the queue. +// This does not pop the values, in case you need them afterwards. If you don't just call nn_popn(). +// The encoding is architecture-dependent, so be careful with storing it on-disk. +// Do note that the architecture-dependent parts are sizeof(double), sizeof(size_t) and endianness. +// The encoding is simple: +// - 0x00 for null +// - 0x01 for true +// - 0x02 for false +// - 0x03 + for a number +// - 0x04 + + for a string +// - 0x05 + for resource +// - 0x06 + + for a table +nn_Exit nn_encodeNetworkContents(nn_Computer *computer, nn_EncodedNetworkContents *contents, size_t valueCount); +// Allocates a copy of [buf] and stores it in contents. +// This is useful for copying network contents, either from storage or from another buffer. +nn_Exit nn_copyNetworkContents(nn_Context *ctx, nn_EncodedNetworkContents *contents, const char *buf, size_t buflen, size_t valueCount); +void nn_dropNetworkContents(nn_EncodedNetworkContents *contents); +// Pushes the encoded contents onto the stack. +// This does not drop the network contents. +nn_Exit nn_pushNetworkContents(nn_Computer *computer, const nn_EncodedNetworkContents *contents); + +// push a modem_message, can be queued by both modems and tunnels. +// This does not check if the modem has that port open, so make sure to check it yourself. +// It does not check if the distance is within the modem's range, if it is wireless, and thus does not send it. +// Note that relays should change the sender. +nn_Exit nn_pushModemMessage(nn_Computer *computer, const char *modemAddress, const char *sender, int port, double distance, const nn_EncodedNetworkContents *contents); + +// EEPROM class + +// reads and writes are always 1/1 +typedef struct nn_EEPROM { + // the maximum capacity of the EEPROM + size_t size; + // the maximum capacity of the EEPROM's associated data + size_t dataSize; + // the energy cost of reading an EEPROM + double readEnergyCost; + // the energy cost of reading an EEPROM's associated data + double readDataEnergyCost; + // the energy cost of writing to an EEPROM + double writeEnergyCost; + // the energy cost of writing to an EEPROM's associated data + double writeDataEnergyCost; + // idle time added when writing code + double writeDelay; + // idle time added when writing data + double writeDataDelay; +} nn_EEPROM; + +typedef enum nn_EEPROMAction { + // component is dropped + NN_EEPROM_DROP, + // check if readonly. If so, buflen should be 1, else it should be 0. + NN_EEPROM_ISRO, + // make the EEPROM readonly. Checksum already verified. + NN_EEPROM_MKRO, + // write the contents of the code into buf. + // Set buflen to the length. + NN_EEPROM_GET, + // store the contents in buf into the EEPROM as code. + // the length of buf is in buflen. + NN_EEPROM_SET, + NN_EEPROM_GETDATA, + NN_EEPROM_SETDATA, + NN_EEPROM_GETARCH, + NN_EEPROM_SETARCH, + NN_EEPROM_GETLABEL, + NN_EEPROM_SETLABEL, +} nn_EEPROMAction; + +typedef struct nn_EEPROMRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_EEPROM *eeprom; + nn_EEPROMAction action; + union { + char *buf; + const char *robuf; + bool readonly; + }; + size_t buflen; +} nn_EEPROMRequest; + +typedef nn_Exit (nn_EEPROMHandler)(nn_EEPROMRequest *request); + +// Tier 1 - The normal EEPROM equivalent +// Tier 2 - A better EEPROM +// Tier 3 - An even better EEPROM +// Tier 4- The best EEPROM +extern const nn_EEPROM nn_defaultEEPROMs[4]; + +nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, void *state, nn_EEPROMHandler *handler); + +// Filesystem class + +typedef struct nn_Filesystem { + // the maximum capacity of the filesystem + size_t spaceTotal; + // how many read calls can be done per tick + // seek also count as reads. + double readsPerTick; + // how many write calls can be done per tick + double writesPerTick; + // how many open calls can be done per tick + double opensPerTick; + // The energy cost of an actual read/write. + // It is per-byte, so if a read returns 4096 bytes, then this cost is multiplied by 4096. + double dataEnergyCost; + // maximum size of a read. + // Do note that this entire buffer is allocated, and thus if you + // set it to a high number, you may get weird high allocations. + // This also determines read performance. + size_t maxReadSize; +} nn_Filesystem; + +typedef enum nn_FSAction { + NN_FS_DROP, + + // drive metadata + NN_FS_SPACEUSED, + NN_FS_GETLABEL, + NN_FS_SETLABEL, + NN_FS_ISRO, + + // for file I/O + NN_FS_OPEN, + NN_FS_CLOSE, + NN_FS_READ, + NN_FS_WRITE, + NN_FS_SEEK, + + // for list + NN_FS_OPENDIR, + NN_FS_READDIR, + NN_FS_CLOSEDIR, + + // checking metadata + NN_FS_STAT, + // make directory, recursively + NN_FS_MKDIR, + + // rename, if renamed to NULL then remove + NN_FS_RENAME, +} nn_FSAction; + +typedef enum nn_FSWhence { + NN_SEEK_SET, + NN_SEEK_CUR, + NN_SEEK_END, +} nn_FSWhence; + +typedef struct nn_FSRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_Filesystem *fs; + nn_FSAction action; + int fd; + union { + struct { + const char *path; + const char *mode; + } open; + struct { + char *buf; + size_t len; + } read; + struct { + const char *buf; + size_t len; + } write; + struct { + nn_FSWhence whence; + // set to new offset + int off; + } seek; + const char *opendir; + struct { + // directory path, as a reminder if need be + const char *dirpath; + char *buf; + // set to length of entry name + size_t len; + } readdir; + struct { + // set to NULL if missing + const char *path; + // whether it is a directory + bool isDirectory; + // in seconds. Result will be multiplied by 1000. + // This is because OpenOS code is garbage. + intptr_t lastModified; + // size. 0 for directories. + size_t size; + } stat; + struct { + const char *from; + // if NULL, delete from, recursively. + const char *to; + } rename; + const char *mkdir; + struct { + const char *buf; + size_t len; + } setlabel; + struct { + char *buf; + size_t len; + } getlabel; + bool isReadonly; + size_t spaceUsed; + }; +} nn_FSRequest; + +typedef nn_Exit (nn_FSHandler)(nn_FSRequest *request); + +// 4 Tiers. +// 0 - Tier 1 equivalent +// 1 - Tier 2 equivalent +// 2 - Tier 3 equivalent +// 3 - Tier 4, a better version of Tier 3. +extern const nn_Filesystem nn_defaultFilesystems[4]; +// a basic floppy +extern const nn_Filesystem nn_defaultFloppy; +// a generic tmpfs +extern const nn_Filesystem nn_defaultTmpFS; + +nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, const nn_Filesystem *fs, void *state, nn_FSHandler *handler); + +bool nn_mergeFilesystems(nn_Filesystem *merged, const nn_Filesystem *fs, size_t len); + +// Drive class + +typedef struct nn_Drive { + // The capacity of the drive. + // It is in bytes, but it MUST be a multiple of the sector size. + // The total amount of sectors, as in capacity / sectorSize, must also be divisible by the platter count. + // If it is not, it is UB. + size_t capacity; + // the sector size, typically 512 + size_t sectorSize; + // the amount of platters the drive has. This contributes to how many "rotations" are needed. + // A drive with 8 sectors but 1 platter, when seeking from sector 1 to 8, would mean 7 rotations. + // However, if it has 2 platters, it'd be seen as 1 to 4 being at the same angle as 5 to 8, which + // would mean only 3 rotations. + size_t platterCount; + // how many reads can be issued per tick. + // Anything that kicks out the current cacheline counts as a read. + size_t readsPerTick; + // how many writes can be issued per tick. + // Writing a sector counts as 1 write. + // Writing a byte counts as 1 read (may be eaten by cache) and 1 write, + // you can imagine it as reading the sector, editing the byte, + // then writing the sector back. + size_t writesPerTick; + // Set to 0 for *infinite*, effectively an SSD with infinite lifespan. + // This would mean there is 0 penalty for seeking (technically unreliastic even for an SSD). + // This is simply used to compute idle time. It is in literal full rotations per minute. + // It is aligned to the cache lines. + size_t rpm; + // If false, it behaves like a normal OC drive, where the drive can spin backwards to seek. + // However, this is unrealistic, as doing so may crack the sensitive platter and make the + // reader lose lift. + // For fans of physics, this option only allows the seeks to go forwards. + // This is super punishing at a slow RPM, so it is recommended to bump up + // the RPM to something like 7200 RPM. + bool onlySpinForwards; + // The energy cost of an actual read/write. + // It is per-byte, so if a read returns 4096 bytes, then this cost is multiplied by 4096. + double dataEnergyCost; +} nn_Drive; + +extern const nn_Drive nn_defaultDrives[4]; +extern const nn_Drive nn_floppyDrive; + + +typedef enum nn_DriveAction { + // drive gone + NN_DRIVE_DROP, + // get current label + NN_DRIVE_GETLABEL, + // set or remove current label + NN_DRIVE_SETLABEL, + // get the current head position, as a sector + NN_DRIVE_CURPOS, + // read a sector + NN_DRIVE_READSECTOR, + // write a sector + NN_DRIVE_WRITESECTOR, + // write a byte + NN_DRIVE_WRITEBYTE, + // is drive read-only + NN_DRIVE_ISRO, +} nn_DriveAction; + +typedef struct nn_DriveRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_Drive *drv; + nn_DriveAction action; + union { + struct { + char *buf; + size_t len; + } getlabel; + struct { + const char *label; + size_t len; + } setlabel; + size_t curpos; + struct { + // 1-indexed + size_t sector; + char *buf; + } readSector; + struct { + // 1-indexed + size_t sector; + const char *buf; + } writeSector; + struct { + // 1-indexed + size_t byte; + unsigned char value; + } writeByte; + bool readonly; + }; +} nn_DriveRequest; + +typedef nn_Exit (nn_DriveHandler)(nn_DriveRequest *request); + +nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const nn_Drive *drive, void *state, nn_DriveHandler *handler); + +bool nn_mergeDrives(nn_Drive *merged, const nn_Drive *drives, size_t len); + +typedef struct nn_NandFlash { + // capacity of flash + size_t capacity; + // sector size + size_t sectorSize; + // reads per tick + size_t readsPerTick; + // writes per tick + size_t writesPerTick; + // The layering, in bits. + // 1 is SLC, 2 is MLC, 3 is TLC, etc. + // This number may amplify how quickly the total write count increases. + size_t cellLevel; + // the maximum amount of write amplification. + // Set to 0 to disable amplification RNG. + // The game will generate, using Context RNG, a real number from [0, 1] + // then raise it to writeAmplificationExponent, + // then multiply it by this number, and by the cell level. + // then clamp it to be at least 1 and at most this maximum. + unsigned int maxWriteAmplification; + int writeAmplificationExponent; + // the maximum amount of writes *per sector.* + // Set to 0 to make the nandflash eternal. + size_t maxWriteCount; + // how much per byte + double dataEnergyCost; +} nn_NandFlash; + +typedef enum nn_FlashAction { + NN_FLASH_DROP, + NN_FLASH_GETLABEL, + NN_FLASH_SETLABEL, + NN_FLASH_ISRO, + // read a sector + NN_FLASH_READSECTOR, + // write a sector + // also adds an amount of writes + NN_FLASH_WRITESECTOR, + // write a sector + // also adds an amount of writes + NN_FLASH_WRITEBYTE, + // get the amount of writes + NN_FLASH_GETWRITES, +} nn_FlashAction; + +typedef struct nn_FlashRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_NandFlash *flash; + nn_FlashAction action; + union { + struct { + char *buf; + size_t len; + } getlabel; + struct { + const char *buf; + size_t len; + } setlabel; + struct { + char *buf; + // 1-indexed + size_t sec; + } readsector; + struct { + const char *buf; + // 1-indexed + size_t sec; + // how many writes to add + size_t writesAdded; + } writesector; + struct { + size_t byte; + char val; + // how many writes to add + size_t writesAdded; + } writebyte; + // for GETWRITES + size_t writeCount; + bool readonly; + }; +} nn_FlashRequest; + +typedef nn_Exit (nn_FlashHandler)(nn_FlashRequest *request); + +extern const nn_NandFlash nn_defaultSSDs[4]; +extern const nn_NandFlash nn_floppySSD; + +nn_Component *nn_createFlash(nn_Universe *universe, const char *address, const nn_NandFlash *drive, void *state, nn_FlashHandler *handler); + +bool nn_mergeFlash(nn_NandFlash *merged, const nn_NandFlash *flash, size_t len); + +// Screen class + +typedef enum nn_ScreenFeatures { + NN_SCRF_NONE = 0, + // whether it supports mouse input. + // If it doesn't, it should not emit + // touch, drag or other mouse events. + // Walk events should also not be emitted. + NN_SCRF_MOUSE = 1<<0, + // Whether precise mode is supported. + NN_SCRF_PRECISE = 1<<1, + // Whether touch inverted is supported. + NN_SCRF_TOUCHINVERTED = 1<<2, + // it indicates that the palette can be edited. + NN_SCRF_EDITABLECOLORS = 1<<3, +} nn_ScreenFeatures; + +// A struct for the reference screen configurations +// This does not influence the interface at all, +// however it exists as a runtime reference of what +// the conventional screen tiers are. +typedef struct nn_ScreenConfig { + // maximum width + int maxWidth; + // maximum height + int maxHeight; + // screen features + nn_ScreenFeatures features; + // default palette, if applicable. + // Can be NULL if there is none, + // in which case consider memsetting + // them to #000000. + int *defaultPalette; + // the amount of editable palette colors + int paletteColors; + // how many editable palette colors there are. + // It'd always be the first N ones. + int editableColors; + // the maximum depth of the screen + char maxDepth; + // energy per fully white pixel. + // Scaled to mathc luminance of each pixel. + // This is meant to be per Minecraft tick, so 20 times per second. + double energyPerPixel; + // minimum brightness. Default brightness is always 100% + // The value here is meant to be scaled such that 1 means 100% brightness + double minBrightness; + // maximum brightness. Note that brightness rendering is emulator-specific + // The value here is meant to be scaled such that 1 means 100% brightness + double maxBrightness; +} nn_ScreenConfig; + +// OC has 3 tiers, NN adds a 4th one as well. +extern const nn_ScreenConfig nn_defaultScreens[4]; + + +// GPU class +typedef struct nn_GPU { + // the minimum between these and the screen's + // are the maximum width/height/depth supported. + int maxWidth; + int maxHeight; + char maxDepth; + // this is in pixels. + size_t totalVRAM; + // amount of times copy can be called before running out of budget. + int copyPerTick; + // amount of times fill can be called before running out of budget. + int fillPerTick; + // amount of times set can be called before running out of budget. + int setPerTick; + // amount of times setForeground can be called before running out of budget. + int setForegroundPerTick; + // amount of times setBackground can be called before running out of budget. + int setBackgroundPerTick; + // energy per pixel set. + double energyPerWrite; + // energy per pixel filled + double energyPerFill; + // energy per pixel copied + double energyPerCopy; + // energy per space set. + double energyPerClear; +} nn_GPU; + +// 1 GPU tier for every screen. +extern const nn_GPU nn_defaultGPUs[4]; + +typedef enum nn_GPUAction { + NN_GPU_DROP, + NN_GPU_BIND, + NN_GPU_GETSCREEN, + NN_GPU_GETBG, + NN_GPU_SETBG, + NN_GPU_GETFG, + NN_GPU_SETFG, + NN_GPU_GETPALETTE, + NN_GPU_SETPALETTE, + NN_GPU_MAXDEPTH, + NN_GPU_GETDEPTH, + NN_GPU_SETDEPTH, + NN_GPU_MAXRES, + NN_GPU_GETRES, + NN_GPU_SETRES, + NN_GPU_GETVIEWPORT, + NN_GPU_SETVIEWPORT, + NN_GPU_GET, + NN_GPU_SET, + NN_GPU_COPY, + NN_GPU_FILL, + NN_GPU_GETACTIVEBUF, + NN_GPU_SETACTIVEBUF, + NN_GPU_BUFFERS, + NN_GPU_ALLOCBUF, + NN_GPU_FREEBUF, + NN_GPU_FREEALLBUFS, + NN_GPU_FREEMEM, + NN_GPU_GETBUFSIZE, + NN_GPU_BITBLT, +} nn_GPUAction; + +typedef struct nn_GPURequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_GPU *gpu; + nn_GPUAction action; + union { + struct { + const char *address; + bool reset; + } bind; + // GETSCREEN result + char screenAddr[NN_MAX_ADDRESS]; + // GET/SET BG/FG + struct { + int color; + bool isPalette; + int oldColor; + bool wasPalette; + int oldPaletteIdx; // -1 if none + } color; + // GET/SET PALETTE + struct { + int index; + int color; + int oldColor; + } palette; + // MAXDEPTH / GETDEPTH / SETDEPTH + struct { + char depth; + char oldDepth; + } depth; + // MAXRES/GETRES/SETRES/GETVIEWPORT/SETVIEWPORT + struct { + int width; + int height; + } resolution; + // GET pixel + struct { + int x, y; + nn_codepoint codepoint; + int fg, bg; + int fgIdx, bgIdx; // -1 if not palette + } get; + // SET string + struct { + int x, y; + const char *value; + size_t len; + bool vertical; + } set; + // COPY + struct { + int x, y, w, h, tx, ty; + } copy; + // FILL + struct { + int x, y, w, h; + nn_codepoint codepoint; + } fill; + // GET/SET ACTIVE BUFFER, FREE BUFFER + struct { + int index; + } buffer; + // ALLOCATE BUFFER + struct { + int w, h, index; + } allocBuf; + // TOTALMEM / FREEMEM + size_t memory; + // GETBUFSIZE + struct { + int index, w, h; + } bufSize; + // BITBLT + struct { + int dst, col, row, w, h; + int src, fromCol, fromRow; + } bitblt; + // BUFFERS / count returned here, indices + // pushed on stack by handler + size_t bufCount; + }; +} nn_GPURequest; + +typedef nn_Exit (nn_GPUHandler)(nn_GPURequest *req); + +nn_Component *nn_createGPU( + nn_Universe *universe, const char *address, + const nn_GPU *gpu, void *state, + nn_GPUHandler *handler); + +typedef enum nn_ScreenAction { + NN_SCREEN_DROP, + NN_SCREEN_ISON, + NN_SCREEN_TURNON, + NN_SCREEN_TURNOFF, + NN_SCREEN_GETASPECTRATIO, + NN_SCREEN_GETKEYBOARDS, + NN_SCREEN_SETPRECISE, + NN_SCREEN_ISPRECISE, + NN_SCREEN_SETTOUCHINVERTED, + NN_SCREEN_ISTOUCHINVERTED, + // sets the brightness (from 0 to 1) + NN_SCREEN_SETBRIGHT, + // get the brightness (from 0 to 1) + NN_SCREEN_GETBRIGHT, +} nn_ScreenAction; + +typedef struct nn_ScreenRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_ScreenConfig *screen; + nn_ScreenAction action; + union { + // turnOn / turnOff / isOn + struct { + bool wasOn; + bool isOn; + } power; + // getAspectRatio + struct { + int w, h; + } aspect; + // getKeyboards — addresses pushed on stack by + // handler; count returned here + size_t kbCount; + // setPrecise / isPrecise / + // setTouchModeInverted / isTouchModeInverted + bool flag; + double brightness; + }; +} nn_ScreenRequest; + +typedef nn_Exit (nn_ScreenHandler)(nn_ScreenRequest *req); + +nn_Component *nn_createScreen( + nn_Universe *universe, const char *address, + const nn_ScreenConfig *scrconf, void *state, + nn_ScreenHandler *handler +); + +// Computes a CRC32 checksum +unsigned int nn_computeCRC32(const char *data, size_t datalen); + +typedef struct nn_DataCard { + // The buffer size of the data card, limit in both input and output. + // In OC, this value is 1MiB regardless of tier. + // As there is no out buffer and you are expected to push strings, + // the buffer is not pre-allocated. This is intentional, as memory + // would be wasted and it could be a potential attack vector otherwise. + size_t limit; + + // The maximum amount of secure random bytes that can be generated. + // In OC, this was hardcoded to 1024. Here, its configurable. + // Unlike the normal limit, this does not preallocate the maximum capacity, + // as the amount of bytes needed is known perfectly. + size_t maxRandom; + + // for encoding/decoding. OC defaulted to 32 + size_t base64PerTick; + // for deflate/inflate. OC defaulted to 4 + size_t deflatingPerTick; + // OC defaulted to 32 + size_t crc32PerTick; + // OC defaulted to 8 + size_t md5PerTick; + // OC defaulted to 4 + size_t sha256PerTick; + // for encrypt/decrypt. OC defaulted to 4 + size_t encryptPerTick; + // for generateKeyPair. OC defaulted to 1 + size_t genPerTick; + // for deserializeKey. OC defaulted to 8 + size_t deserializePerTick; + // OC defaulted to 1 + size_t ecdhPerTick; + // OC defaulted to 1 + size_t ecdsaPerTick; + // OC defaults to 4 + size_t randomPerTick; + + // Capabilities + bool canHash; + bool canEncrypt; + bool canECDH; + bool canCompress; + + // Trivial operation cost (CRC32 and base64 encoding/decoding) + double trivialCost; + double trivialCostByte; + + // Simple operation cost (MD5 and encryption/decryption) + double simpleCost; + double simpleCostByte; + + double complexCost; + double complexCostByte; + + // Assymetric operation cost (ECDH, ECDSA). ECDH has no per-byte cost, and ECDSA uses complexCostByte as it relies on SHA256. + double assymetricCost; +} nn_DataCard; + +typedef enum nn_DataCardAction { + // Data card destroyed + NN_DATA_DROP, + + // If you want to match the behavior of OC, which you should if you want + // data compressed or encrypted in OC to work in your emulators, you should + // aim to match what the JVM, com.google.common.hash.Hashing and javax.crypto do. + // For more details, see https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/scala/li/cil/oc/server/component/DataCard.scala + + // encoding base64 + NN_DATA_ENCODE64, + + NN_DATA_DECODE64, + // hashing + + // CRC32, little endian + NN_DATA_CRC32, + + // SHA2-256 hash, optional HMAC key (javax.crypto HmacSHA256) + NN_DATA_SHA256, + + // MD5 hash, optional HMAC key (javax.crypto HmacMD5) + NN_DATA_MD5, + + // Deflate. To match OC, make it follow the ZLIB format, which has a 2 byte header. + NN_DATA_DEFLATE, + + // Inflate. Should support the ZLIB format, as thats what OC uses, and GZIP support is optional. + NN_DATA_INFLATE, + + // Encrypt data with AES-128. The full algorithm is AES/CBC/PKCS5, as is use PKCS5 for padding, CBC for block sequences, and AES-128 for encrypting blocks. + // It does also receive a 128-bit AES Initialization Vector, for better security. + NN_DATA_ENCRYPT, + + // Decrypt data, also using AES/CBC/PKCS5. + NN_DATA_DECRYPT, + + // Meant to be *secure RNG*, and can generate anywhere between 1 and the data card's maxRandom. + NN_DATA_RANDOM, + + // Generate an ECDH public/private pair of either 256 or 384 bits each. + NN_DATA_GENKEYS, + + // validate key, cuz we feel like it + NN_DATA_VALIDATEKEY, + + // Does an ECDH pass, matching javax.crypto.KeyAgreement. + // This generates a shared secret as binary data. + // This is not an AES key as output, the AES key is often computed by either MD5 hashing + // the shared secret, or SHA256 hashing it and chopping the hash in half. + NN_DATA_ECDH, + + // ECDSA algorithm, sign data using an ECDH (private) key. + NN_DATA_ECDSA_SIGN, + // ECDSA algorithm, verify data using an ECDH (public) key and signature. + NN_DATA_ECDSA_VERIFY, +} nn_DataCardAction; + +// The representation of datacard key userdata +typedef struct nn_DataKey { + bool isPublic; + unsigned short bytelen; + char bytes[]; +} nn_DataKey; + +typedef struct nn_DataCardRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_DataCard *dataCard; + nn_DataCardAction action; + // TODO: the fields + union { + struct { + const char *data; + size_t datalen; + char checksum[4]; + } crc32; + struct { + const char *data; + size_t datalen; + char checksum[16]; + } md5; + struct { + const char *data; + size_t datalen; + char checksum[32]; + } sha256; + // for encrypt/decrypt + struct { + const char *data; + size_t datalen; + const char *key; + const char *iv; + } encrypt; + struct { + char *buf; + size_t buflen; + } randbuf; + // for deflate, inflate, encode64 and decode64 + struct { + const char *data; + size_t datalen; + }; + struct { + const char *buf; + size_t len; + bool isPublic; + } validatekey; + struct { + const nn_DataKey *publicKey; + const nn_DataKey *privateKey; + } ecdh; + struct { + const char *data; + size_t len; + const nn_DataKey *privateKey; + } sign; + struct { + const char *data; + size_t datalen; + const nn_DataKey *publicKey; + const char *signature; + size_t siglen; + // returns whether the signature actually passed + bool sigpassed; + } checksig; + int genkeybitsize; + }; +} nn_DataCardRequest; + +typedef nn_Exit (nn_DataCardHandler)(nn_DataCardRequest *req); + +extern nn_DataCard nn_defaultDataCards[3]; + +nn_Component *nn_createDataCard(nn_Universe *universe, const char *address, const nn_DataCard *dataCard, void *state, nn_DataCardHandler *handler); + +typedef struct nn_Modem { + // maximum range. Set to 0 for non-wireless modems + size_t maxRange; + // maximum values in a packet + size_t maxValues; + // maximum logical packet size. Note that the encoding is more efficient than the packet size algorithm estimates + size_t maxPacketSize; + // the maximum amount of open ports + size_t maxOpenPorts; + // whether the modem supports wired connectivity. + // Support for wireless checks if maxRange > 0. + bool isWired; + // base energy cost of 1 network message + double basePacketCost; + // energy cost of a full packet, at the maximum logical size + double fullPacketCost; + // energy cost per wireless packet strength level + double costPerStrength; +} nn_Modem; + +extern nn_Modem nn_defaultWiredModem; +extern nn_Modem nn_defaultWirelessModems[2]; + +typedef enum nn_ModemAction { + // modem dropped + NN_MODEM_DROP, + // check whether a port is open + NN_MODEM_ISOPEN, + // open a port + NN_MODEM_OPEN, + // close a port + NN_MODEM_CLOSE, + // get open ports + NN_MODEM_GETPORTS, + // send/broadcast a message + NN_MODEM_SEND, + // get current modem strength + NN_MODEM_GETSTRENGTH, + // set current modem strength + NN_MODEM_SETSTRENGTH, + // returns the wake message + NN_MODEM_GETWAKEMESSAGE, + // set the wake message + NN_MODEM_SETWAKEMESSAGE, +} nn_ModemAction; + +typedef struct nn_ModemRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_Modem *modem; + const char *localAddress; + nn_ModemAction action; + union { + struct { + size_t port; + bool opened; + } isOpen; + size_t openPort; + // NN_CLOSEPORTS means close all + size_t closePort; + struct { + // store the port numbers in this buffer + unsigned short *activePorts; + // the amount of active ports. + // the initial value is the capacity of activePorts + size_t len; + } getPorts; + struct { + const nn_EncodedNetworkContents *contents; + // NULL for broadcast + const char *address; + size_t port; + // The signal strength it was sent at + // This is an *OUT* field, it is assumed the handler keeps track of the correct strength + size_t strengthSent; + } send; + // for getStrength, setStrength. + size_t strength; + struct { + char *buf; + size_t len; + bool isFuzzy; + } getWake; + struct { + const char *buf; + size_t len; + bool isFuzzy; + } setWake; + }; +} nn_ModemRequest; + +typedef nn_Exit (nn_ModemHandler)(nn_ModemRequest *req); + +nn_Component *nn_createModem(nn_Universe *universe, const char *address, const nn_Modem *modem, void *state, nn_ModemHandler *handler); + +typedef struct nn_Tunnel { + // maximum values in a packet + size_t maxValues; + // maximum logical packet size. Note that the encoding is more efficient than the packet size algorithm estimates + size_t maxPacketSize; + // minimum energy cost of 1 transmission + double basePacketCost; + // extra energy cost of 1 full transmission + double fullPacketCost; +} nn_Tunnel; + +extern nn_Tunnel nn_defaultTunnel; + +typedef enum nn_TunnelAction { + // tunnel dropped + NN_TUNNEL_DROP, + // gets the channel, result should be pushed to the top of the stack + NN_TUNNEL_GETCHANNEL, + // send/broadcast a message + NN_TUNNEL_SEND, + // returns the wake message + NN_TUNNEL_GETWAKEMESSAGE, + // set the wake message + NN_TUNNEL_SETWAKEMESSAGE, +} nn_TunnelAction; + +typedef struct nn_TunnelRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_Tunnel *tunnel; + const char *localAddress; + nn_TunnelAction action; + union { + // for send + const nn_EncodedNetworkContents *toSend; + struct { + char *buf; + size_t len; + bool isFuzzy; + } getWake; + struct { + const char *buf; + size_t len; + bool isFuzzy; + } setWake; + }; +} nn_TunnelRequest; + +typedef nn_Exit (nn_TunnelHandler)(nn_TunnelRequest *req); + +nn_Component *nn_createTunnel(nn_Universe *universe, const char *address, const nn_Tunnel *modem, void *state, nn_TunnelHandler *handler); + +typedef enum nn_InternetProtocol { + NN_INET_NONE = 0, + NN_INET_HTTP = 1<<0, + NN_INET_TCP = 1<<1, + NN_INET_UDP = 1<<2, + NN_INET_WEBSOCKET = 1<<3, + NN_INET_TLS = 1<<4, + + NN_INET_ALL = NN_INET_HTTP | NN_INET_TCP | NN_INET_UDP | NN_INET_WEBSOCKET | NN_INET_TLS, +} nn_InternetProtocol; + +typedef struct nn_InternetCard { + // bitwise OR multiple of them + unsigned char protocolsSupported; + // per-byte cost of a write + double transmissionEnergyCost; +} nn_InternetCard; + +extern nn_InternetCard nn_defaultInternetCard; + +typedef struct nn_InternetConnection { + nn_InternetProtocol protocol; + void *state; +} nn_InternetConnection; + +typedef struct nn_HTTPHeader { + const char *name; + const char *value; +} nn_HTTPHeader; + +typedef struct nn_InternetRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_Tunnel *tunnel; + const char *localAddress; + nn_InternetConnection *connection; + union { + // does a socket connection, for any of the supported protocols + struct { + // URL of connection + const char *url; + // port. Useless for HTTP connections, as they should use 80 for HTTP and 443 for HTTPS + unsigned short port; + // HTTP specific + size_t postdatalen; + const char *postdata; + const nn_HTTPHeader *headers; + size_t headerlen; + } connect; + }; +} nn_InternetRequest; + +// Colors and palettes. +// Do note that the + +// The NeoNucleus 2-bit palette +extern int nn_palette2[4]; + +// The NeoNucleus 3-bit palette +extern int nn_palette3[8]; + +// The OC 4-bit palette. +extern int nn_ocpalette4[16]; + +// The Minecraft 4-bit palette, using dye colors. +extern int nn_mcpalette4[16]; + +// The OC 8-bit palette. +extern int nn_ocpalette8[256]; + +// initializes the contents of the palettes. +void nn_initPalettes(); + +// Returns a number from 0 to 1 representing the perceived luminance. +double nn_colorLuminance(int color); +// Expensive. +// Maps a color to the closest match in a palette. +int nn_mapColor(int color, int *palette, size_t len); +// Expensive. +// Maps a color within a given depth. +// Invalid depths behave identically to 24-bit, in which case the color is left unchanged. +int nn_mapDepth(int color, int depth); + +// the name of a depth, if valid. +// If invalid, NULL is returned, thus this can be used to check +// if a depth is valid as well. +// Valid depths are 1, 2, 3, 4, 8, 16 and 24. +const char *nn_depthName(int depth); + +#ifdef __cplusplus +} +#endif + +#endif