991 lines
28 KiB
C
991 lines
28 KiB
C
// The main file of the test emulator
|
|
// This is not a serious emulator intended for practical use,
|
|
// it is simply just to test stuff and showcase the API.
|
|
// Error handling has been omitted in most places.
|
|
|
|
#include "neonucleus.h"
|
|
#include "ncomplib.h"
|
|
#include "glyphcache.h"
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <raylib.h>
|
|
|
|
#ifdef NN_WINDOWS
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
nn_Architecture getLuaArch();
|
|
|
|
static const char minBIOS[] = {
|
|
#embed "minBIOS.lua"
|
|
,'\0'
|
|
};
|
|
|
|
static nn_Exit sandbox_handler(nn_ComponentRequest *req) {
|
|
nn_Computer *c = req->computer;
|
|
switch(req->action) {
|
|
case NN_COMP_INVOKE:
|
|
if(nn_getstacksize(c) != 1) {
|
|
nn_setError(c, "bad argument count");
|
|
return NN_EBADCALL;
|
|
}
|
|
const char *s = nn_tostring(c, 0);
|
|
puts(s);
|
|
return NN_OK;
|
|
case NN_COMP_CHECKMETHOD:
|
|
req->methodEnabled = true; // all methods always enabled
|
|
return NN_OK;
|
|
case NN_COMP_DROP:
|
|
return NN_OK;
|
|
case NN_COMP_USERDATA:
|
|
return NN_OK;
|
|
}
|
|
return NN_OK;
|
|
}
|
|
|
|
static nn_Exit ne_dataBullshit(nn_DataCardRequest *req) {
|
|
nn_Computer *C = req->computer;
|
|
nn_DataCardAction action = req->action;
|
|
nn_Exit e;
|
|
|
|
if(action == NN_DATA_DROP) {
|
|
return NN_OK;
|
|
}
|
|
if(action == NN_DATA_ENCODE64) {
|
|
int outSize = 0;
|
|
char *out = EncodeDataBase64((const unsigned char *)req->data, req->datalen, &outSize);
|
|
if(out == NULL) return NN_ENOMEM;
|
|
// -1 because raylib includes the NUL terminator??
|
|
e = nn_pushlstring(C, out, outSize-1);
|
|
MemFree(out);
|
|
return e;
|
|
}
|
|
if(action == NN_DATA_DECODE64) {
|
|
int outSize = 0;
|
|
char *out = (char *)DecodeDataBase64(req->data, &outSize);
|
|
if(out == NULL) return NN_ENOMEM;
|
|
e = nn_pushlstring(C, out, outSize);
|
|
MemFree(out);
|
|
return e;
|
|
}
|
|
if(action == NN_DATA_DEFLATE) {
|
|
int outSize = 0;
|
|
char *out = (char *)CompressData((const unsigned char *)req->data, req->datalen, &outSize);
|
|
if(out == NULL) return NN_ENOMEM;
|
|
e = nn_pushlstring(C, out, outSize);
|
|
MemFree(out);
|
|
return e;
|
|
}
|
|
if(action == NN_DATA_INFLATE) {
|
|
int outSize = 0;
|
|
char *out = (char *)DecompressData((unsigned char *)req->data, req->datalen, &outSize);
|
|
if(out == NULL) return NN_ENOMEM;
|
|
e = nn_pushlstring(C, out, outSize);
|
|
MemFree(out);
|
|
return e;
|
|
}
|
|
if(action == NN_DATA_CRC32) {
|
|
unsigned int check = nn_computeCRC32(req->crc32.data, req->crc32.datalen);
|
|
req->crc32.checksum[0] = (check >> 0) & 0xFF;
|
|
req->crc32.checksum[1] = (check >> 8) & 0xFF;
|
|
req->crc32.checksum[2] = (check >> 16) & 0xFF;
|
|
req->crc32.checksum[3] = (check >> 24) & 0xFF;
|
|
return NN_OK;
|
|
}
|
|
if(action == NN_DATA_MD5) {
|
|
unsigned int *out = ComputeMD5((unsigned char *)req->md5.data, req->md5.datalen);
|
|
if(out == NULL) return NN_ENOMEM;
|
|
memcpy(req->md5.checksum, out, 16);
|
|
return NN_OK;
|
|
}
|
|
if(action == NN_DATA_SHA256) {
|
|
// does not match OC, dunno why
|
|
unsigned int *out = ComputeSHA256((unsigned char *)req->sha256.data, req->sha256.datalen);
|
|
if(out == NULL) return NN_ENOMEM;
|
|
memcpy(req->sha256.checksum, out, 32);
|
|
return NN_OK;
|
|
}
|
|
if(action == NN_DATA_RANDOM) {
|
|
for(size_t i = 0; i < req->randbuf.buflen; i++) {
|
|
req->randbuf.buf[i] = rand();
|
|
}
|
|
return NN_OK;
|
|
}
|
|
if(action == NN_DATA_ENCRYPT) {
|
|
return nn_pushlstring(C, req->data, req->datalen);
|
|
}
|
|
if(action == NN_DATA_DECRYPT) {
|
|
return nn_pushlstring(C, req->data, req->datalen);
|
|
}
|
|
|
|
if(action == NN_DATA_VALIDATEKEY) {
|
|
// its valid, trust
|
|
return NN_OK;
|
|
}
|
|
|
|
if(action == NN_DATA_GENKEYS) {
|
|
char a = 'A' + rand() % 26;
|
|
char b = 'A' + rand() % 26;
|
|
nn_pushlstring(C, &a, 1);
|
|
return nn_pushlstring(C, &b, 1);
|
|
}
|
|
|
|
if(action == NN_DATA_ECDH) {
|
|
char buf[32];
|
|
memset(buf, 'e', 32);
|
|
return nn_pushlstring(C, buf, 32);
|
|
}
|
|
|
|
if(action == NN_DATA_ECDSA_SIGN) {
|
|
return nn_pushstring(C, "epic signature bro");
|
|
}
|
|
if(action == NN_DATA_ECDSA_VERIFY) {
|
|
req->checksig.sigpassed = strcmp(req->checksig.signature, "epic signature bro");
|
|
return NN_OK;
|
|
}
|
|
|
|
if(C) nn_setError(C, "ne: data method not implemented");
|
|
return NN_EBADCALL;
|
|
}
|
|
|
|
static nn_Exit ne_modemBullshit(nn_ModemRequest *req) {
|
|
nn_Computer *C = req->computer;
|
|
|
|
if(req->action == NN_MODEM_DROP) {
|
|
return NN_OK;
|
|
}
|
|
|
|
if(req->action == NN_MODEM_ISOPEN) {
|
|
int port = req->isOpen.port;
|
|
req->isOpen.opened = port >= 1 && port <= 3;
|
|
return NN_OK;
|
|
}
|
|
|
|
if(req->action == NN_MODEM_OPEN) {
|
|
printf("pretend we opened port %zu\n", req->openPort);
|
|
return NN_OK;
|
|
}
|
|
|
|
if(req->action == NN_MODEM_CLOSE) {
|
|
printf("pretend we closed ");
|
|
if(req->closePort == NN_CLOSEPORTS) {
|
|
printf("all ports\n");
|
|
} else {
|
|
printf("port %zu\n", req->closePort);
|
|
}
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_MODEM_GETPORTS) {
|
|
// lies
|
|
req->getPorts.len = 3;
|
|
req->getPorts.activePorts[0] = 1;
|
|
req->getPorts.activePorts[1] = 2;
|
|
req->getPorts.activePorts[2] = 3;
|
|
return NN_OK;
|
|
}
|
|
|
|
if(req->action == NN_MODEM_SEND) {
|
|
req->send.strengthSent = req->modem->maxRange;
|
|
const char *dest = req->send.address == NULL ? "*" : req->send.address;
|
|
printf("Transmission from %s to %s (port %zu) of %zu bytes (%zu values)\n", req->localAddress, dest, req->send.port, req->send.contents->buflen, req->send.contents->valueCount);
|
|
return nn_pushModemMessage(C, req->localAddress, dest, req->send.port, 0, req->send.contents);
|
|
}
|
|
|
|
if(req->action == NN_MODEM_GETWAKEMESSAGE) {
|
|
req->getWake.len = 0;
|
|
req->getWake.isFuzzy = false;
|
|
return NN_OK;
|
|
}
|
|
|
|
if(req->action == NN_MODEM_SETWAKEMESSAGE) {
|
|
return NN_OK;
|
|
}
|
|
|
|
if(C) nn_setError(C, "ne: modem method not implemented");
|
|
return NN_EBADCALL;
|
|
}
|
|
|
|
static nn_Exit ne_tunnelBullshit(nn_TunnelRequest *req) {
|
|
nn_Computer *C = req->computer;
|
|
|
|
if(req->action == NN_TUNNEL_DROP) {
|
|
return NN_OK;
|
|
}
|
|
|
|
if(req->action == NN_TUNNEL_GETCHANNEL) {
|
|
return nn_pushstring(C, "creative");
|
|
}
|
|
|
|
if(req->action == NN_TUNNEL_SEND) {
|
|
printf("Transmission from tunnel %s of %zu bytes (%zu values)\n", req->localAddress, req->toSend->buflen, req->toSend->valueCount);
|
|
return nn_pushModemMessage(C, req->localAddress, nn_getComputerAddress(C), NN_TUNNEL_PORT, 0, req->toSend);
|
|
}
|
|
|
|
if(req->action == NN_TUNNEL_GETWAKEMESSAGE) {
|
|
req->getWake.len = 0;
|
|
req->getWake.isFuzzy = false;
|
|
return NN_OK;
|
|
}
|
|
|
|
if(req->action == NN_TUNNEL_SETWAKEMESSAGE) {
|
|
return NN_OK;
|
|
}
|
|
|
|
if(C) nn_setError(C, "ne: modem method not implemented");
|
|
return NN_EBADCALL;
|
|
}
|
|
|
|
static unsigned char ne_processColorPart(unsigned char channel, double brightness) {
|
|
double n = (double)channel / 255;
|
|
n *= brightness;
|
|
if(n < 0) n = 0;
|
|
if(n > 1) n = 1;
|
|
return n * 255;
|
|
}
|
|
|
|
Color ne_processColor(unsigned int color, double brightness) {
|
|
color <<= 8;
|
|
color |= 0xFF;
|
|
Color c = GetColor(color);
|
|
c.r = ne_processColorPart(c.r, brightness);
|
|
c.g = ne_processColorPart(c.g, brightness);
|
|
c.b = ne_processColorPart(c.b, brightness);
|
|
return c;
|
|
}
|
|
|
|
int keycode_to_oc(int keycode) {
|
|
switch (keycode) {
|
|
case KEY_NULL:
|
|
return 0;
|
|
case KEY_APOSTROPHE:
|
|
return NN_KEY_APOSTROPHE;
|
|
case KEY_COMMA:
|
|
return NN_KEY_COMMA;
|
|
case KEY_MINUS:
|
|
return NN_KEY_MINUS;
|
|
case KEY_PERIOD:
|
|
return NN_KEY_PERIOD;
|
|
case KEY_SLASH:
|
|
return NN_KEY_SLASH;
|
|
case KEY_ZERO:
|
|
return NN_KEY_0;
|
|
case KEY_ONE:
|
|
return NN_KEY_1;
|
|
case KEY_TWO:
|
|
return NN_KEY_2;
|
|
case KEY_THREE:
|
|
return NN_KEY_3;
|
|
case KEY_FOUR:
|
|
return NN_KEY_4;
|
|
case KEY_FIVE:
|
|
return NN_KEY_5;
|
|
case KEY_SIX:
|
|
return NN_KEY_6;
|
|
case KEY_SEVEN:
|
|
return NN_KEY_7;
|
|
case KEY_EIGHT:
|
|
return NN_KEY_8;
|
|
case KEY_NINE:
|
|
return NN_KEY_9;
|
|
case KEY_SEMICOLON:
|
|
return NN_KEY_SEMICOLON;
|
|
case KEY_EQUAL:
|
|
return NN_KEY_EQUALS;
|
|
case KEY_A:
|
|
return NN_KEY_A;
|
|
case KEY_B:
|
|
return NN_KEY_B;
|
|
case KEY_C:
|
|
return NN_KEY_C;
|
|
case KEY_D:
|
|
return NN_KEY_D;
|
|
case KEY_E:
|
|
return NN_KEY_E;
|
|
case KEY_F:
|
|
return NN_KEY_F;
|
|
case KEY_G:
|
|
return NN_KEY_G;
|
|
case KEY_H:
|
|
return NN_KEY_H;
|
|
case KEY_I:
|
|
return NN_KEY_I;
|
|
case KEY_J:
|
|
return NN_KEY_J;
|
|
case KEY_K:
|
|
return NN_KEY_K;
|
|
case KEY_L:
|
|
return NN_KEY_L;
|
|
case KEY_M:
|
|
return NN_KEY_M;
|
|
case KEY_N:
|
|
return NN_KEY_N;
|
|
case KEY_O:
|
|
return NN_KEY_O;
|
|
case KEY_P:
|
|
return NN_KEY_P;
|
|
case KEY_Q:
|
|
return NN_KEY_Q;
|
|
case KEY_R:
|
|
return NN_KEY_R;
|
|
case KEY_S:
|
|
return NN_KEY_S;
|
|
case KEY_T:
|
|
return NN_KEY_T;
|
|
case KEY_U:
|
|
return NN_KEY_U;
|
|
case KEY_V:
|
|
return NN_KEY_V;
|
|
case KEY_W:
|
|
return NN_KEY_W;
|
|
case KEY_X:
|
|
return NN_KEY_X;
|
|
case KEY_Y:
|
|
return NN_KEY_Y;
|
|
case KEY_Z:
|
|
return NN_KEY_Z;
|
|
case KEY_LEFT_BRACKET:
|
|
return NN_KEY_LBRACKET;
|
|
case KEY_BACKSLASH:
|
|
return NN_KEY_BACKSLASH;
|
|
case KEY_RIGHT_BRACKET:
|
|
return NN_KEY_RBRACKET;
|
|
case KEY_GRAVE:
|
|
return NN_KEY_GRAVE;
|
|
case KEY_SPACE:
|
|
return NN_KEY_SPACE;
|
|
case KEY_ESCAPE:
|
|
return 0;
|
|
case KEY_ENTER:
|
|
return NN_KEY_ENTER;
|
|
case KEY_TAB:
|
|
return NN_KEY_TAB;
|
|
case KEY_BACKSPACE:
|
|
return NN_KEY_BACK;
|
|
case KEY_INSERT:
|
|
return NN_KEY_INSERT;
|
|
case KEY_DELETE:
|
|
return NN_KEY_DELETE;
|
|
case KEY_RIGHT:
|
|
return NN_KEY_RIGHT;
|
|
case KEY_LEFT:
|
|
return NN_KEY_LEFT;
|
|
case KEY_DOWN:
|
|
return NN_KEY_DOWN;
|
|
case KEY_UP:
|
|
return NN_KEY_UP;
|
|
case KEY_PAGE_UP:
|
|
return NN_KEY_PAGEUP;
|
|
case KEY_PAGE_DOWN:
|
|
return NN_KEY_PAGEDOWN;
|
|
case KEY_HOME:
|
|
return NN_KEY_HOME;
|
|
case KEY_END:
|
|
return NN_KEY_END;
|
|
case KEY_CAPS_LOCK:
|
|
return NN_KEY_CAPITAL;
|
|
case KEY_SCROLL_LOCK:
|
|
return NN_KEY_SCROLL;
|
|
case KEY_NUM_LOCK:
|
|
return NN_KEY_NUMLOCK;
|
|
case KEY_PRINT_SCREEN:
|
|
return 0;
|
|
case KEY_PAUSE:
|
|
return NN_KEY_PAUSE;
|
|
case KEY_F1:
|
|
return NN_KEY_F1;
|
|
case KEY_F2:
|
|
return NN_KEY_F2;
|
|
case KEY_F3:
|
|
return NN_KEY_F3;
|
|
case KEY_F4:
|
|
return NN_KEY_F4;
|
|
case KEY_F5:
|
|
return NN_KEY_F5;
|
|
case KEY_F6:
|
|
return NN_KEY_F6;
|
|
case KEY_F7:
|
|
return NN_KEY_F7;
|
|
case KEY_F8:
|
|
return NN_KEY_F8;
|
|
case KEY_F9:
|
|
return NN_KEY_F9;
|
|
case KEY_F10:
|
|
return NN_KEY_F10;
|
|
case KEY_F11:
|
|
return NN_KEY_F11;
|
|
case KEY_F12:
|
|
return NN_KEY_F12;
|
|
case KEY_LEFT_SHIFT:
|
|
return NN_KEY_LSHIFT;
|
|
case KEY_LEFT_CONTROL:
|
|
return NN_KEY_LCONTROL;
|
|
case KEY_LEFT_ALT:
|
|
return NN_KEY_LMENU;
|
|
case KEY_LEFT_SUPER:
|
|
return 0;
|
|
case KEY_RIGHT_SHIFT:
|
|
return NN_KEY_RSHIFT;
|
|
case KEY_RIGHT_CONTROL:
|
|
return NN_KEY_RCONTROL;
|
|
case KEY_RIGHT_ALT:
|
|
return NN_KEY_RMENU;
|
|
case KEY_RIGHT_SUPER:
|
|
return 0;
|
|
case KEY_KB_MENU:
|
|
return 0;
|
|
case KEY_KP_0:
|
|
return NN_KEY_NUMPAD0;
|
|
case KEY_KP_1:
|
|
return NN_KEY_NUMPAD1;
|
|
case KEY_KP_2:
|
|
return NN_KEY_NUMPAD2;
|
|
case KEY_KP_3:
|
|
return NN_KEY_NUMPAD3;
|
|
case KEY_KP_4:
|
|
return NN_KEY_NUMPAD4;
|
|
case KEY_KP_5:
|
|
return NN_KEY_NUMPAD5;
|
|
case KEY_KP_6:
|
|
return NN_KEY_NUMPAD6;
|
|
case KEY_KP_7:
|
|
return NN_KEY_NUMPAD7;
|
|
case KEY_KP_8:
|
|
return NN_KEY_NUMPAD8;
|
|
case KEY_KP_9:
|
|
return NN_KEY_NUMPAD9;
|
|
case KEY_KP_DECIMAL:
|
|
return NN_KEY_NUMPADDECIMAL;
|
|
case KEY_KP_DIVIDE:
|
|
return NN_KEY_NUMPADDIV;
|
|
case KEY_KP_MULTIPLY:
|
|
return NN_KEY_NUMPADMUL;
|
|
case KEY_KP_SUBTRACT:
|
|
return NN_KEY_NUMPADSUB;
|
|
case KEY_KP_ADD:
|
|
return NN_KEY_NUMPADADD;
|
|
case KEY_KP_ENTER:
|
|
return NN_KEY_NUMPADENTER;
|
|
case KEY_KP_EQUAL:
|
|
return NN_KEY_NUMPADEQUALS;
|
|
case KEY_BACK:
|
|
return 0;
|
|
case KEY_MENU:
|
|
return 0;
|
|
case KEY_VOLUME_DOWN:
|
|
return 0;
|
|
case KEY_VOLUME_UP:
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
size_t ne_alignAlloc(size_t num, size_t align) {
|
|
if(num % align == 0) return num;
|
|
return num + align - (num % align);
|
|
}
|
|
|
|
typedef struct ne_memSand {
|
|
char *buf;
|
|
size_t used;
|
|
size_t cap;
|
|
} ne_memSand;
|
|
|
|
void *ne_sandbox_alloc(void *state, void *memory, size_t oldSize, size_t newSize) {
|
|
ne_memSand *sand = (ne_memSand *)state;
|
|
|
|
oldSize = ne_alignAlloc(oldSize, NN_ALLOC_ALIGN);
|
|
newSize = ne_alignAlloc(newSize, NN_ALLOC_ALIGN);
|
|
|
|
// never free
|
|
if(newSize == 0) return NULL;
|
|
if(memory == NULL) {
|
|
if(sand->cap - sand->used < newSize) return NULL;
|
|
// alloc new
|
|
void *mem = sand->buf + sand->used;
|
|
sand->used += newSize;
|
|
return mem;
|
|
}
|
|
// realloc
|
|
if(newSize <= oldSize) return memory;
|
|
if(sand->cap - sand->used < newSize) return NULL;
|
|
void *mem = sand->buf + sand->used;
|
|
sand->used += newSize;
|
|
memcpy(mem, memory, oldSize);
|
|
return mem;
|
|
}
|
|
|
|
double accumulatedEnergyCost = 0;
|
|
double totalEnergyLoss = 0;
|
|
// default capacity of a tablet
|
|
double allEnergy = 10000;
|
|
|
|
void ne_env(nn_EnvironmentRequest *req) {
|
|
if(req->action == NN_ENV_BEEP) {
|
|
printf("beep: %f Hz %fs %.02f%%\n", req->beep.frequency, req->beep.duration, req->beep.volume*100);
|
|
return;
|
|
}
|
|
if(req->action == NN_ENV_DRAWENERGY) {
|
|
accumulatedEnergyCost += req->energy;
|
|
totalEnergyLoss += req->energy;
|
|
allEnergy -= req->energy;
|
|
req->energy = nn_getTotalEnergy(req->computer);
|
|
req->energy = allEnergy;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *player = getenv("USER");
|
|
#ifdef NN_WINDOWS
|
|
if(player == NULL) player = getenv("USERNAME");
|
|
#endif
|
|
if(player == NULL) player = "me";
|
|
|
|
bool sandboxMem = getenv("NN_MEMSAND") != NULL;
|
|
bool showStats = getenv("NN_STAT") != NULL;
|
|
|
|
const char *mainDir = "openos";
|
|
if(argc > 1) mainDir = argv[1];
|
|
|
|
nn_Context ctx;
|
|
nn_initContext(&ctx);
|
|
nn_initPalettes();
|
|
|
|
ne_memSand sand;
|
|
sand.buf = NULL;
|
|
|
|
if(sandboxMem) {
|
|
// 1 MiB pre-allocated to prevent erasing the free-list
|
|
sand.used = NN_MiB;
|
|
sand.cap = 1 * NN_GiB;
|
|
sand.buf = malloc(sand.cap);
|
|
ctx.state = &sand;
|
|
ctx.alloc = ne_sandbox_alloc;
|
|
}
|
|
|
|
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
|
InitWindow(800, 600, "NeoNucleus Test Emulator");
|
|
|
|
// create the universe
|
|
nn_Universe *u = nn_createUniverse(&ctx, NULL);
|
|
|
|
nn_Architecture arch = getLuaArch();
|
|
|
|
nn_Method sandboxMethods[] = {
|
|
{"log", "log(msg: string) - Log to stdout", NN_DIRECT},
|
|
{NULL},
|
|
};
|
|
nn_Component *ocelotCard = nn_createComponent(u, NULL, "ocelot");
|
|
nn_setComponentMethods(ocelotCard, sandboxMethods);
|
|
nn_setComponentHandler(ocelotCard, sandbox_handler);
|
|
|
|
nn_Component *dataCard = nn_createDataCard(u, NULL, &nn_defaultDataCards[2], NULL, ne_dataBullshit);
|
|
nn_Component *modem = nn_createModem(u, NULL, &nn_defaultWirelessModems[1], NULL, ne_modemBullshit);
|
|
nn_Component *tunnel = nn_createTunnel(u, NULL, &nn_defaultTunnel, NULL, ne_tunnelBullshit);
|
|
|
|
char *eepromCode = (char *)minBIOS;
|
|
size_t eepromSize = strlen(minBIOS);
|
|
const char *eepromPath = getenv("NN_EEPROM");
|
|
if(eepromPath != NULL) {
|
|
FILE *eeprom = fopen(eepromPath, "rb");
|
|
if(eeprom == NULL) {
|
|
fprintf(stderr, "no such eeprom: %s\n", eepromPath);
|
|
return 1;
|
|
}
|
|
|
|
fseek(eeprom, 0, SEEK_END);
|
|
eepromSize = ftell(eeprom);
|
|
fseek(eeprom, 0, SEEK_SET);
|
|
|
|
eepromCode = malloc(eepromSize);
|
|
size_t amount = 0;
|
|
while(amount < eepromSize) {
|
|
amount += fread(eepromCode + amount, sizeof(char), eepromSize - amount, eeprom);
|
|
}
|
|
}
|
|
|
|
nn_Component *eepromCard = ncl_createEEPROM(u, NULL, &nn_defaultEEPROMs[3], eepromCode, eepromSize, false);
|
|
|
|
nn_Filesystem mainfsconf;
|
|
nn_Filesystem fsparts[] = {
|
|
nn_defaultFilesystems[3],
|
|
nn_defaultFilesystems[3],
|
|
nn_defaultFilesystems[3],
|
|
};
|
|
nn_mergeFilesystems(&mainfsconf, fsparts, sizeof(fsparts) / sizeof(fsparts[0]));
|
|
|
|
char mainfspath[NN_MAX_PATH];
|
|
snprintf(mainfspath, NN_MAX_PATH, "data/%s", mainDir);
|
|
nn_Component *managedfs = ncl_createFilesystem(u, NULL, mainfspath, &mainfsconf, true);
|
|
//nn_Component *tmpfs = ncl_createTmpFS(u, NULL, &nn_defaultTmpFS, NCL_FILECOST_DEFAULT, false);
|
|
nn_Component *tmpfs = ncl_createFilesystem(u, NULL, "/tmp", &mainfsconf, false);
|
|
nn_Component *testingfs = ncl_createFilesystem(u, NULL, "aux", &nn_defaultFilesystems[3], false);
|
|
|
|
const char * const testDriveData =
|
|
"local g, s = component.list('gpu')(), component.list('screen')()\n"
|
|
"local d = computer.getBootAddress()\n"
|
|
"component.invoke(g, 'bind', s, true)\n"
|
|
"component.invoke(g, 'set', 1, 1, 'starting sequential bench...')\n"
|
|
"local start = computer.uptime()\n"
|
|
"local ss = component.invoke(d, 'getSectorSize')\n"
|
|
"local cap = component.invoke(d, 'getCapacity')\n"
|
|
"local bc = cap / ss\n"
|
|
"local tc = 256\n"
|
|
"for i=1,tc do component.invoke(d, 'readSector', i) end\n"
|
|
"local now = computer.uptime()\n"
|
|
"component.invoke(g, 'set', 1, 2, 'took ' .. (now - start) .. 's')\n"
|
|
"component.invoke(g, 'set', 1, 3, 'sequential read speed: ' .. (tc * ss / (now - start)) .. 'B/s')\n"
|
|
"while computer.uptime() < now + 3 do computer.pullSignal(0.05) end\n"
|
|
"component.invoke(g, 'bind', s, true)\n"
|
|
"component.invoke(g, 'set', 1, 1, 'starting random bench...')\n"
|
|
"start = computer.uptime()\n"
|
|
"for i=1,tc do local i = math.random(1, bc) component.invoke(d, 'readSector', i) end\n"
|
|
"now = computer.uptime()\n"
|
|
"component.invoke(g, 'set', 1, 2, 'took ' .. (now - start) .. 's')\n"
|
|
"component.invoke(g, 'set', 1, 3, 'random read speed: ' .. (tc * ss / (now - start)) .. 'B/s')\n"
|
|
"while computer.uptime() < now + 3 do computer.pullSignal(0.05) end\n"
|
|
"computer.shutdown(true)\n"
|
|
;
|
|
nn_Component *testDrive = ncl_createDrive(u, NULL, &nn_defaultDrives[3], testDriveData, strlen(testDriveData), false);
|
|
nn_Component *testFlash = ncl_createFlash(u, NULL, &nn_defaultSSDs[3], testDriveData, strlen(testDriveData), false);
|
|
|
|
ncl_setCLabel(eepromCard, "EEPROM");
|
|
ncl_setCLabel(managedfs, "Main Filesystem");
|
|
ncl_setCLabel(testingfs, "Secondary Filesystem");
|
|
ncl_setCLabel(testDrive, "Unmanaged Storage");
|
|
ncl_setCLabel(testFlash, "Flash Storage");
|
|
|
|
size_t ramTotal = 0;
|
|
ramTotal += 4 * nn_ramSizes[7];
|
|
//ramTotal += nn_ramSizes[0];
|
|
|
|
SetExitKey(KEY_NULL);
|
|
|
|
const char *fontPath = getenv("NN_FONT");
|
|
if(fontPath == NULL) fontPath = "unscii-16-full.ttf";
|
|
ncl_GlyphCache *gc = ncl_createGlyphCache(fontPath, 20);
|
|
double tickDelay = 0.05;
|
|
bool noIdle = getenv("NN_NOIDLE") != NULL;
|
|
|
|
if(getenv("NN_TICKDELAY") != NULL) {
|
|
tickDelay = atof(getenv("NN_TICKDELAY"));
|
|
}
|
|
|
|
struct {int key; nn_codepoint unicode;} keybuf[512];
|
|
memset(keybuf, 0, sizeof(keybuf));
|
|
size_t keycap = sizeof(keybuf) / sizeof(keybuf[0]);
|
|
|
|
double nextTick = 0;
|
|
double nextSecond = 0;
|
|
double wattage = 0;
|
|
|
|
nn_Component *screen = ncl_createScreen(u, NULL, &nn_defaultScreens[3]);
|
|
nn_GPU gpuConf = nn_defaultGPUs[3];
|
|
gpuConf.maxWidth = 1920;
|
|
gpuConf.maxHeight = 1080;
|
|
nn_Component *gpuCard = ncl_createGPU(u, NULL, &gpuConf);
|
|
nn_Component *keyboard = nn_createComponent(
|
|
u, "mainKB", "keyboard");
|
|
|
|
ncl_ScreenState *scrstate = nn_getComponentState(screen);
|
|
ncl_mountKeyboard(scrstate, "mainKB");
|
|
ncl_setScreenMaxResolution(scrstate, 320, 90);
|
|
|
|
// we assume server basically
|
|
nn_Computer *c = nn_createComputer(u, NULL, NULL, ramTotal, nn_defaultComponentLimits[3] * 4, 256);
|
|
nn_Environment cEnv = {
|
|
.userdata = NULL,
|
|
.handler = ne_env,
|
|
};
|
|
nn_setComputerEnvironment(c, cEnv);
|
|
nn_setCallBudget(c, nn_defaultCallBudgets[3]);
|
|
nn_setTotalEnergy(c, allEnergy);
|
|
if(getenv("NN_FAST") != NULL) {
|
|
tickDelay = 0;
|
|
noIdle = true;
|
|
nn_setCallBudget(c, 0);
|
|
}
|
|
|
|
nn_setArchitecture(c, &arch);
|
|
nn_addSupportedArchitecture(c, &arch);
|
|
|
|
nn_setTmpAddress(c, nn_getComponentAddress(tmpfs));
|
|
|
|
nn_CommonDeviceInfo cinfo;
|
|
nn_clearCommonDeviceInfo(&cinfo);
|
|
cinfo.CLASS = NN_DEVICECLASS_SYSTEM;
|
|
cinfo.DESC = "The main computer";
|
|
cinfo.VENDOR = "NeoNucleus Inc.";
|
|
cinfo.PRODUCT = "NeoComputer";
|
|
cinfo.VERSION = "0.-1.0";
|
|
cinfo.CLOCK = "1 MHz";
|
|
cinfo.CAPACITY = "10 kJ";
|
|
|
|
nn_addCommonDeviceInfo(c, nn_getComputerAddress(c), cinfo);
|
|
|
|
nn_mountComponent(c, screen, -1, false);
|
|
nn_mountComponent(c, ocelotCard, -1, false);
|
|
nn_mountComponent(c, tmpfs, -1, false);
|
|
nn_mountComponent(c, keyboard, -1, false);
|
|
nn_mountComponent(c, eepromCard, 0, false);
|
|
nn_mountComponent(c, managedfs, 1, false);
|
|
nn_mountComponent(c, gpuCard, 2, false);
|
|
nn_mountComponent(c, testingfs, 3, false);
|
|
nn_mountComponent(c, testDrive, 4, false);
|
|
nn_mountComponent(c, testFlash, 5, false);
|
|
nn_mountComponent(c, dataCard, 6, false);
|
|
nn_mountComponent(c, modem, 7, false);
|
|
nn_mountComponent(c, tunnel, 8, false);
|
|
int ltx = 0, lty = 0;
|
|
double scrollBuf = 0;
|
|
double tickTime = 0;
|
|
SetTargetFPS(60);
|
|
while(true) {
|
|
if(WindowShouldClose()) break;
|
|
|
|
BeginDrawing();
|
|
ClearBackground(BLACK);
|
|
|
|
// drawing the screen + screen events
|
|
{
|
|
const char *scraddr = nn_getComponentAddress(screen);
|
|
ncl_ScreenState *scrbuf = nn_getComponentState(screen);
|
|
ncl_lockScreen(scrbuf);
|
|
size_t scrw, scrh;
|
|
ncl_getScreenViewport(scrbuf, &scrw, &scrh);
|
|
|
|
ncl_ScreenFlags scrflags = ncl_getScreenFlags(scrbuf);
|
|
|
|
int cheight = GetScreenHeight() / scrh;
|
|
if(cheight != ncl_cellHeight(gc)) {
|
|
ncl_destroyGlyphCache(gc);
|
|
gc = ncl_createGlyphCache(fontPath, cheight);
|
|
}
|
|
int cwidth = ncl_cellWidth(gc);
|
|
int offX = (GetScreenWidth() - cwidth * scrw) / 2;
|
|
int offY = (GetScreenHeight() - cheight * scrh) / 2;
|
|
|
|
double scrbright = ncl_getScreenBrightness(scrbuf);
|
|
if(scrflags & NCL_SCREEN_ON) {
|
|
for(int y = 1; y <= scrh; y++) {
|
|
for(int x = 1; x <= scrw; x++) {
|
|
ncl_Pixel p = ncl_getScreenPixel(scrbuf, x, y);
|
|
Vector2 pos = {
|
|
offX + (x - 1) * cwidth,
|
|
offY + (y - 1) * cheight,
|
|
};
|
|
DrawRectangle(pos.x, pos.y, cwidth, cheight, ne_processColor(p.bgColor, scrbright));
|
|
}
|
|
}
|
|
for(int y = 1; y <= scrh; y++) {
|
|
for(int x = 1; x <= scrw; x++) {
|
|
ncl_Pixel p = ncl_getScreenPixel(scrbuf, x, y);
|
|
Vector2 pos = {
|
|
offX + (x - 1) * cwidth,
|
|
offY + (y - 1) * cheight,
|
|
};
|
|
ncl_needGlyph(gc, p.codepoint);
|
|
if(p.codepoint != 0) {
|
|
ncl_drawGlyph(gc, p.codepoint, pos, cheight, ne_processColor(p.fgColor, scrbright));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DrawRectangleLines(offX, offY, cwidth * scrw, cheight * scrh, WHITE);
|
|
ncl_unlockScreen(scrbuf);
|
|
ncl_flushGlyphs(gc);
|
|
|
|
int tx = (double)(GetMouseX() - offX) / cwidth + 1;
|
|
int ty = (double)(GetMouseY() - offY) / cheight + 1;
|
|
|
|
if(tx >= 1 && ty >= 1 && tx <= scrw && ty <= scrh) {
|
|
struct {int btn; int ocbtn;} btns[] = {
|
|
{MOUSE_BUTTON_LEFT, 0},
|
|
{MOUSE_BUTTON_RIGHT, 1},
|
|
{MOUSE_BUTTON_MIDDLE, 2},
|
|
};
|
|
size_t btnc = sizeof(btns) / sizeof(btns[0]);
|
|
for(size_t i = 0; i < btnc; i++) {
|
|
// we only care about left click here
|
|
int mbtn = btns[i].btn;
|
|
int ocbtn = btns[i].ocbtn;
|
|
if(IsMouseButtonPressed(mbtn)) {
|
|
nn_pushTouch(c, scraddr, tx, ty, ocbtn, player);
|
|
}
|
|
if(IsMouseButtonReleased(mbtn)) {
|
|
nn_pushDrop(c, scraddr, tx, ty, ocbtn, player);
|
|
}
|
|
if(IsMouseButtonDown(mbtn)) {
|
|
if(ltx != tx || lty != ty) {
|
|
nn_pushDrag(c, scraddr, tx, ty, ocbtn, player);
|
|
}
|
|
}
|
|
}
|
|
if(fabs(scrollBuf) >= 1) {
|
|
nn_pushScroll(c, scraddr, tx, ty, scrollBuf, player);
|
|
scrollBuf = 0;
|
|
}
|
|
ltx = tx;
|
|
lty = ty;
|
|
}
|
|
}
|
|
|
|
int statY = 10;
|
|
if(sand.buf != NULL) {
|
|
DrawText(TextFormat("mem used: %.2f%%", (double)sand.used / sand.cap * 100), 10, statY, 20, YELLOW);
|
|
statY += 20;
|
|
}
|
|
if(showStats) {
|
|
double memUsagePercent = (double)nn_getUsedMemory(c) * 100 / nn_getTotalMemory(c);
|
|
DrawText(TextFormat("power usage: %.2f W", wattage), 10, statY, 20, GREEN);
|
|
statY += 20;
|
|
DrawText(TextFormat("energy loss: %.2f J", totalEnergyLoss), 10, statY, 20, GREEN);
|
|
statY += 20;
|
|
DrawText(TextFormat("VM mem usage: %.2f%%", memUsagePercent), 10, statY, 20, GREEN);
|
|
statY += 20;
|
|
DrawText(TextFormat("Tick time: %.5fs", tickTime), 10, statY, 20, (tickTime < tickDelay || tickDelay == 0) ? GREEN : RED);
|
|
statY += 20;
|
|
}
|
|
|
|
EndDrawing();
|
|
|
|
scrollBuf += GetMouseWheelMove();
|
|
|
|
// keyboard input
|
|
|
|
// 1: clipboard
|
|
if(IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) {
|
|
const char *t = GetClipboardText();
|
|
if(t != NULL) nn_pushClipboard(c, "mainKB", t, player);
|
|
}
|
|
|
|
while(1) {
|
|
int keycode = GetKeyPressed();
|
|
nn_codepoint unicode = GetCharPressed();
|
|
|
|
if(keycode == 0 && unicode == 0) break;
|
|
|
|
keybuf[keycode].key = keycode;
|
|
keybuf[keycode].unicode = unicode;
|
|
|
|
if(keycode != 0) {
|
|
if(keycode == KEY_ENTER) unicode = '\r';
|
|
if(keycode == KEY_BACKSPACE) unicode = '\b';
|
|
if(keycode == KEY_TAB) unicode = '\t';
|
|
|
|
bool isCtrlPressed = keybuf[KEY_LEFT_CONTROL].key != 0;
|
|
if(isCtrlPressed && isalpha(keycode)) {
|
|
unicode = keycode - 'A' + 1;
|
|
}
|
|
}
|
|
|
|
nn_pushKeyDown(c, "mainKB", unicode, keycode_to_oc(keycode), player);
|
|
}
|
|
|
|
for(size_t i = 0; i < keycap; i++) {
|
|
if(keybuf[i].key != 0) {
|
|
if(IsKeyReleased(keybuf[i].key)) {
|
|
int key = keycode_to_oc(keybuf[i].key);
|
|
keybuf[i].key = 0;
|
|
nn_pushKeyUp(c, "mainKB", keybuf[i].unicode, key, player);
|
|
}
|
|
// unicode keys handled by raylib
|
|
if(IsKeyPressedRepeat(keybuf[i].key) && keybuf[i].unicode == 0) {
|
|
int key = keycode_to_oc(keybuf[i].key);
|
|
nn_pushKeyDown(c, "mainKB", keybuf[i].unicode, key, player);
|
|
}
|
|
}
|
|
}
|
|
|
|
double tickNow = GetTime();
|
|
|
|
if(tickNow >= nextSecond) {
|
|
nextSecond = tickNow + 1;
|
|
wattage = accumulatedEnergyCost;
|
|
accumulatedEnergyCost = 0;
|
|
}
|
|
|
|
if(tickNow >= nextTick) {
|
|
nextTick = tickNow + tickDelay;
|
|
nn_clearstack(c);
|
|
|
|
nn_removeEnergy(c, ncl_getScreenEnergyUsage(nn_getComponentState(screen)));
|
|
|
|
if(noIdle) nn_resetIdleTime(c);
|
|
// OC computers consume 0.5W when running, 0.05W when running but idle
|
|
double normalPowerUsage = 0.5, idlePowerUsage = 0.05;
|
|
nn_Exit e = nn_tick(c);
|
|
tickTime = GetTime() - tickNow;
|
|
if(tickTime < tickDelay) {
|
|
double working = tickTime / tickDelay;
|
|
nn_removeEnergy(c, normalPowerUsage * working + idlePowerUsage * (1 - working));
|
|
} else if(tickDelay == 0) {
|
|
nn_removeEnergy(c, normalPowerUsage);
|
|
} else {
|
|
nn_removeEnergy(c, normalPowerUsage * tickTime / tickDelay);
|
|
}
|
|
if(e != NN_OK) {
|
|
printf("error: %s\n", nn_getError(c));
|
|
goto cleanup;
|
|
}
|
|
e = nn_tickSynchronized(c);
|
|
if(e != NN_OK) {
|
|
printf("sync method error: %s\n", nn_getError(c));
|
|
goto cleanup;
|
|
}
|
|
|
|
nn_ComputerState state = nn_getComputerState(c);
|
|
if(state == NN_POWEROFF) break;
|
|
if(state == NN_CRASHED) {
|
|
printf("error: %s\n", nn_getError(c));
|
|
goto cleanup;
|
|
}
|
|
|
|
if(state == NN_CHARCH) {
|
|
printf("new arch: %s\n", nn_getDesiredArchitecture(c).name);
|
|
goto cleanup;
|
|
}
|
|
if(state == NN_BLACKOUT) {
|
|
printf("out of energy\n");
|
|
goto cleanup;
|
|
}
|
|
if(state == NN_RESTART) {
|
|
printf("restart requested\n");
|
|
nn_stopComputer(c);
|
|
ncl_resetScreen(nn_getComponentState(screen));
|
|
nn_addIdleTime(c, 1);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:;
|
|
nn_destroyComputer(c);
|
|
nn_dropComponent(ocelotCard);
|
|
nn_dropComponent(eepromCard);
|
|
nn_dropComponent(managedfs);
|
|
nn_dropComponent(tmpfs);
|
|
nn_dropComponent(testingfs);
|
|
nn_dropComponent(testDrive);
|
|
nn_dropComponent(testFlash);
|
|
nn_dropComponent(screen);
|
|
nn_dropComponent(gpuCard);
|
|
nn_dropComponent(keyboard);
|
|
nn_dropComponent(dataCard);
|
|
nn_dropComponent(modem);
|
|
nn_dropComponent(tunnel);
|
|
// rip the universe
|
|
nn_destroyUniverse(u);
|
|
ncl_destroyGlyphCache(gc);
|
|
if(eepromPath != NULL) free(eepromCode);
|
|
CloseWindow();
|
|
free(sand.buf);
|
|
return 0;
|
|
}
|