1326 lines
35 KiB
C
1326 lines
35 KiB
C
#include "neonucleus.h"
|
|
#include "ncomplib.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static bool ncl_defaultHandler(ncl_VFSRequest *request);
|
|
|
|
#ifdef NN_BAREMETAL
|
|
bool ncl_defaultHandler(ncl_VFSRequest *request) {
|
|
return false; // all fails
|
|
}
|
|
#else
|
|
#include <stdio.h>
|
|
|
|
#ifdef NN_POSIX
|
|
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
|
|
#elif defined(NN_WINDOWS)
|
|
#error "Windows is not supported yet"
|
|
#endif
|
|
|
|
bool ncl_defaultHandler(ncl_VFSRequest *request) {
|
|
if(request->action == NCL_VFS_OPEN) {
|
|
char mode[3] = "rb";
|
|
mode[0] = request->open.mode[0];
|
|
FILE *f = fopen(request->open.path, mode);
|
|
request->open.file = f;
|
|
return f != NULL;
|
|
}
|
|
if(request->action == NCL_VFS_CLOSE) {
|
|
fclose(request->close);
|
|
return true;
|
|
}
|
|
if(request->action == NCL_VFS_READ) {
|
|
FILE *f = request->read.file;
|
|
if(feof(f)) {
|
|
request->read.buf = NULL;
|
|
return true;
|
|
}
|
|
request->read.len = fread(request->read.buf, sizeof(char), request->read.len, f);
|
|
return true;
|
|
}
|
|
if(request->action == NCL_VFS_WRITE) {
|
|
FILE *f = request->write.file;
|
|
size_t written = fwrite(request->write.buf, sizeof(char), request->write.len, f);
|
|
return written == request->write.len;
|
|
}
|
|
if(request->action == NCL_VFS_SEEK) {
|
|
FILE *f = request->seek.file;
|
|
nn_FSWhence wanted = request->seek.whence;
|
|
int whence = SEEK_SET;
|
|
if(wanted == NN_SEEK_SET) whence = SEEK_SET;
|
|
if(wanted == NN_SEEK_CUR) whence = SEEK_CUR;
|
|
if(wanted == NN_SEEK_END) whence = SEEK_END;
|
|
if(fseek(f, whence, request->seek.off) < 0) return false;
|
|
request->seek.off = ftell(f);
|
|
return true;
|
|
}
|
|
if(request->action == NCL_VFS_REMOVE) {
|
|
return remove(request->remove) == 0;
|
|
}
|
|
#ifdef NN_POSIX
|
|
if(request->action == NCL_VFS_OPENDIR) {
|
|
DIR *d = opendir(request->opendir.path);
|
|
request->opendir.dir = d;
|
|
return d != NULL;
|
|
}
|
|
if(request->action == NCL_VFS_CLOSEDIR) {
|
|
DIR *d = request->closedir;
|
|
closedir(d);
|
|
return true;
|
|
}
|
|
if(request->action == NCL_VFS_READDIR) {
|
|
while(1) {
|
|
DIR *d = request->readdir.dir;
|
|
struct dirent *ent = readdir(d);
|
|
if(ent == NULL) {
|
|
request->readdir.name = NULL;
|
|
} else if(strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
|
continue;
|
|
} else {
|
|
strncpy(request->readdir.name, ent->d_name, NN_MAX_PATH-1);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
if(request->action == NCL_VFS_STAT) {
|
|
struct stat s;
|
|
if(stat(request->stat.path, &s) != 0) {
|
|
request->stat.path = NULL;
|
|
return false;
|
|
}
|
|
ncl_Stat *stat = request->stat.stat;
|
|
stat->isDirectory = S_ISDIR(s.st_mode);
|
|
stat->diskSize = s.st_blocks * 512;
|
|
stat->size = stat->isDirectory ? 0 : s.st_size;
|
|
stat->lastModified = s.st_mtime;
|
|
return true;
|
|
}
|
|
if(request->action == NCL_VFS_MKDIR) {
|
|
return mkdir(request->mkdir, 0777) == 0;
|
|
}
|
|
#endif
|
|
return false; // not supported
|
|
}
|
|
#endif
|
|
|
|
ncl_VFS ncl_defaultFS = (ncl_VFS) {
|
|
.state = NULL,
|
|
.handler = ncl_defaultHandler,
|
|
#ifdef NN_WINDOWS
|
|
.pathsep = '\\',
|
|
#else
|
|
.pathsep = '/',
|
|
#endif
|
|
.fileCost = NCL_FILECOST_DEFAULT,
|
|
};
|
|
|
|
ncl_VFS ncl_installerFS = (ncl_VFS) {
|
|
.state = NULL,
|
|
.handler = ncl_defaultHandler,
|
|
#ifdef NN_WINDOWS
|
|
.pathsep = '\\',
|
|
#else
|
|
.pathsep = '/',
|
|
#endif
|
|
.fileCost = NCL_FILECOST_INSTALL,
|
|
};
|
|
|
|
void *ncl_openfile(ncl_VFS vfs, const char *path, const char *mode) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_OPEN;
|
|
req.open.path = path;
|
|
req.open.mode = mode;
|
|
if(!vfs.handler(&req)) return NULL;
|
|
return req.open.file;
|
|
}
|
|
|
|
void ncl_closefile(ncl_VFS vfs, void *file) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_CLOSE;
|
|
req.close = file;
|
|
vfs.handler(&req);
|
|
}
|
|
|
|
bool ncl_readfile(ncl_VFS vfs, void *file, char *buf, size_t *len) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_READ;
|
|
req.read.file = file;
|
|
req.read.buf = buf;
|
|
req.read.len = *len;
|
|
if(!vfs.handler(&req)) return false;
|
|
if(req.read.buf == NULL) return false;
|
|
*len = req.read.len;
|
|
return true;
|
|
}
|
|
|
|
bool ncl_writefile(ncl_VFS vfs, void *file, const char *data, size_t len) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_WRITE;
|
|
req.write.file = file;
|
|
req.write.buf = data;
|
|
req.write.len = len;
|
|
return vfs.handler(&req);
|
|
}
|
|
|
|
bool ncl_seekfile(ncl_VFS vfs, void *file, nn_FSWhence whence, int *off) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_SEEK;
|
|
req.seek.file = file;
|
|
req.seek.whence = whence;
|
|
req.seek.off = *off;
|
|
if(!vfs.handler(&req)) return false;
|
|
*off = req.seek.off;
|
|
return true;
|
|
}
|
|
|
|
bool ncl_stat(ncl_VFS vfs, const char *path, ncl_Stat *stat) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_STAT;
|
|
req.stat.path = path;
|
|
req.stat.stat = stat;
|
|
if(!vfs.handler(&req)) return false;
|
|
if(req.stat.path == NULL) return false;
|
|
return true;
|
|
}
|
|
|
|
void *ncl_opendir(ncl_VFS vfs, const char *path) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_OPENDIR;
|
|
req.opendir.path = path;
|
|
if(!vfs.handler(&req)) return NULL;
|
|
return req.opendir.dir;
|
|
}
|
|
|
|
void ncl_closedir(ncl_VFS vfs, void *dir) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_CLOSEDIR;
|
|
req.closedir = dir;
|
|
vfs.handler(&req);
|
|
}
|
|
|
|
bool ncl_readdir(ncl_VFS vfs, void *dir, char name[NN_MAX_PATH]) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_READDIR;
|
|
req.readdir.dir = dir;
|
|
req.readdir.name = name;
|
|
if(!vfs.handler(&req)) return false;
|
|
if(req.readdir.name == NULL) return false;
|
|
return true;
|
|
}
|
|
|
|
size_t ncl_spaceUsedIn(ncl_VFS vfs, const char *path) {
|
|
ncl_Stat s;
|
|
if(!ncl_stat(vfs, path, &s)) return 0;
|
|
size_t spaceUsed = vfs.fileCost + s.size;
|
|
if(!s.isDirectory) return spaceUsed;
|
|
void *dir = ncl_opendir(vfs, path);
|
|
if(dir == NULL) return spaceUsed;
|
|
char name[NN_MAX_PATH];
|
|
while(ncl_readdir(vfs, dir, name)) {
|
|
char subpath[NN_MAX_PATH];
|
|
snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name);
|
|
spaceUsed += ncl_spaceUsedIn(vfs, subpath);
|
|
}
|
|
ncl_closedir(vfs, dir);
|
|
return spaceUsed;
|
|
}
|
|
|
|
size_t ncl_spaceUsedBy(ncl_VFS vfs, const char *path) {
|
|
ncl_Stat s;
|
|
if(!ncl_stat(vfs, path, &s)) return 0;
|
|
size_t spaceUsed = s.diskSize;
|
|
if(!s.isDirectory) return s.diskSize;
|
|
void *dir = ncl_opendir(vfs, path);
|
|
if(dir == NULL) return spaceUsed;
|
|
char name[NN_MAX_PATH];
|
|
while(ncl_readdir(vfs, dir, name)) {
|
|
char subpath[NN_MAX_PATH];
|
|
snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name);
|
|
spaceUsed += ncl_spaceUsedBy(vfs, subpath);
|
|
}
|
|
ncl_closedir(vfs, dir);
|
|
return spaceUsed;
|
|
}
|
|
|
|
bool ncl_exists(ncl_VFS vfs, const char *path) {
|
|
ncl_Stat s;
|
|
return ncl_stat(vfs, path, &s);
|
|
}
|
|
|
|
bool ncl_remove(ncl_VFS vfs, const char *path) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_REMOVE;
|
|
req.remove = path;
|
|
return vfs.handler(&req);
|
|
}
|
|
|
|
bool ncl_removeRecursive(ncl_VFS vfs, const char *path) {
|
|
ncl_Stat s;
|
|
if(!ncl_stat(vfs, path, &s)) return false;
|
|
if(s.isDirectory) {
|
|
void *dir = ncl_opendir(vfs, path);
|
|
char name[NN_MAX_PATH];
|
|
while(ncl_readdir(vfs, dir, name)) {
|
|
char subpath[NN_MAX_PATH];
|
|
snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name);
|
|
ncl_removeRecursive(vfs, subpath);
|
|
}
|
|
ncl_closedir(vfs, dir);
|
|
}
|
|
return ncl_remove(vfs, path);
|
|
}
|
|
|
|
bool ncl_mkdir(ncl_VFS vfs, const char *path) {
|
|
ncl_VFSRequest req;
|
|
req.state = vfs.state;
|
|
req.action = NCL_VFS_MKDIR;
|
|
req.mkdir = path;
|
|
return vfs.handler(&req);
|
|
}
|
|
|
|
bool ncl_mkdirRecursive(ncl_VFS vfs, const char *path) {
|
|
ncl_Stat s;
|
|
if(ncl_stat(vfs, path, &s)) {
|
|
return s.isDirectory;
|
|
}
|
|
char buf[NN_MAX_PATH];
|
|
// use snprintf instead of strncpy cuz NULL terminator
|
|
snprintf(buf, NN_MAX_PATH, "%s", path);
|
|
char *sep = strrchr(buf, vfs.pathsep);
|
|
if(sep == NULL) {
|
|
return ncl_mkdir(vfs, path);
|
|
}
|
|
*sep = '\0';
|
|
if(!ncl_mkdirRecursive(vfs, buf)) return false;
|
|
return ncl_mkdir(vfs, path);
|
|
}
|
|
|
|
static bool ncl_copydir(ncl_VFS vfs, const char *from, const char *to) {
|
|
bool created = ncl_mkdir(vfs, to);
|
|
if(!created) return false;
|
|
void *dir = ncl_opendir(vfs, from);
|
|
if(dir == NULL) goto fail;
|
|
|
|
char name[NN_MAX_PATH];
|
|
while(ncl_readdir(vfs, dir, name)) {
|
|
char subpath[NN_MAX_PATH];
|
|
snprintf(subpath, NN_MAX_PATH, "%s%c%s", from, vfs.pathsep, name);
|
|
char subdest[NN_MAX_PATH];
|
|
snprintf(subdest, NN_MAX_PATH, "%s%c%s", to, vfs.pathsep, name);
|
|
printf("%s -> %s\n", subpath, subdest);
|
|
if(!ncl_copyto(vfs, subpath, subdest)) goto fail;
|
|
}
|
|
|
|
ncl_closedir(vfs, dir);
|
|
return true;
|
|
fail:
|
|
if(dir != NULL) ncl_closedir(vfs, dir);
|
|
// erase all evidence
|
|
//ncl_removeRecursive(vfs, to);
|
|
return false;
|
|
}
|
|
|
|
static bool ncl_copyfile(ncl_VFS vfs, const char *from, const char *to) {
|
|
// copy some files!
|
|
void *src = NULL;
|
|
void *dest = NULL;
|
|
src = ncl_openfile(vfs, from, "r");
|
|
if(src == NULL) goto fail;
|
|
dest = ncl_openfile(vfs, to, "w");
|
|
if(dest == NULL) goto fail;
|
|
|
|
char buf[NN_MAX_READ];
|
|
size_t len = NN_MAX_READ;
|
|
while(ncl_readfile(vfs, src, buf, &len)) {
|
|
if(!ncl_writefile(vfs, dest, buf, len)) goto fail;
|
|
len = NN_MAX_READ;
|
|
}
|
|
ncl_closefile(vfs, src);
|
|
ncl_closefile(vfs, dest);
|
|
|
|
return true;
|
|
fail:
|
|
if(src != NULL) ncl_closefile(vfs, src);
|
|
if(dest != NULL) ncl_closefile(vfs, dest);
|
|
//ncl_remove(vfs, to);
|
|
return false;
|
|
}
|
|
|
|
static bool ncl_isIllegalCopy(const char *from, const char *to) {
|
|
// check if to starts with from, or from starts with to
|
|
if(strncmp(from, to, strlen(from)) == 0) return true;
|
|
if(strncmp(to, from, strlen(to)) == 0) return true;
|
|
return false;
|
|
}
|
|
|
|
bool ncl_copyto(ncl_VFS vfs, const char *from, const char *to) {
|
|
if(strcmp(from, to) == 0) return true;
|
|
if(ncl_isIllegalCopy(from, to)) return false;
|
|
// already exists
|
|
if(ncl_exists(vfs, to)) return false;
|
|
|
|
ncl_Stat s;
|
|
// missing
|
|
if(!ncl_stat(vfs, from, &s)) return false;
|
|
|
|
if(s.isDirectory) return ncl_copydir(vfs, from, to);
|
|
return ncl_copyfile(vfs, from, to);
|
|
}
|
|
|
|
typedef struct ncl_ScreenPixel {
|
|
nn_codepoint codepoint;
|
|
int storedFg;
|
|
int storedBg;
|
|
// if negative, its in palette
|
|
int realFg;
|
|
// if negative, its in palette
|
|
int realBg;
|
|
} ncl_ScreenPixel;
|
|
|
|
typedef struct ncl_ScreenState {
|
|
nn_Context *ctx;
|
|
nn_Lock *lock;
|
|
nn_ScreenConfig conf;
|
|
int width;
|
|
int height;
|
|
int viewportWidth;
|
|
int viewportHeight;
|
|
char depth;
|
|
int *palette;
|
|
int *resolvedPalette;
|
|
ncl_ScreenPixel *pixels;
|
|
ncl_ScreenFlags flags;
|
|
size_t keyboardCount;
|
|
char *keyboards[NCL_MAX_KEYBOARD];
|
|
} ncl_ScreenState;
|
|
|
|
typedef struct nn_VRAMBuf {
|
|
int width;
|
|
int height;
|
|
ncl_ScreenPixel pixels[];
|
|
} ncl_VRAMBuf;
|
|
|
|
typedef struct ncl_GPUState {
|
|
nn_Context *ctx;
|
|
nn_Lock *lock;
|
|
nn_GPU conf;
|
|
size_t vramFree;
|
|
ncl_VRAMBuf *vram[NCL_MAX_VRAMBUF];
|
|
char *screenAddress;
|
|
int currentFg;
|
|
int currentBg;
|
|
int activeBuffer;
|
|
bool isFgPalette;
|
|
bool isBgPalette;
|
|
} ncl_GPUState;
|
|
|
|
static void ncl_freeVRAM(nn_Context *ctx, ncl_VRAMBuf *buf) {
|
|
nn_free(ctx, buf, sizeof(ncl_VRAMBuf) + sizeof(ncl_ScreenPixel) * buf->width * buf->height);
|
|
}
|
|
|
|
static ncl_VRAMBuf *ncl_allocVRAM(nn_Context *ctx, int width, int height) {
|
|
ncl_VRAMBuf *buf = nn_alloc(ctx, sizeof(ncl_VRAMBuf) + sizeof(ncl_ScreenPixel) * width * height);
|
|
if(buf == NULL) return NULL;
|
|
buf->width = width;
|
|
buf->height = height;
|
|
for(int i = 0; i < width*height; i++) {
|
|
buf->pixels[i] = (ncl_ScreenPixel) {
|
|
.codepoint = ' ',
|
|
.storedFg = 0xFFFFFF,
|
|
.storedBg = 0x000000,
|
|
.realFg = 0xFFFFFF,
|
|
.realBg = 0x000000,
|
|
};
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static ncl_ScreenPixel *ncl_vramPtr(ncl_VRAMBuf *buf, int x, int y) {
|
|
x--;
|
|
y--;
|
|
if(x < 0 || y < 0 || x >= buf->width || y >= buf->height) return NULL;
|
|
return &buf->pixels[x + y * buf->width];
|
|
}
|
|
|
|
static ncl_ScreenPixel ncl_vramGet(ncl_VRAMBuf *buf, int x, int y) {
|
|
ncl_ScreenPixel *ptr = ncl_vramPtr(buf, x, y);
|
|
if(ptr != NULL) return *ptr;
|
|
return (ncl_ScreenPixel) {
|
|
.codepoint = ' ',
|
|
.storedFg = 0xFFFFFF,
|
|
.storedBg = 0x000000,
|
|
.realFg = 0xFFFFFF,
|
|
.realBg = 0x000000,
|
|
};
|
|
}
|
|
|
|
static void ncl_vramSet(ncl_VRAMBuf *buf, int x, int y, ncl_ScreenPixel pixel) {
|
|
ncl_ScreenPixel *ptr = ncl_vramPtr(buf, x, y);
|
|
if(ptr != NULL) *ptr = pixel;
|
|
}
|
|
|
|
typedef struct ncl_FSState {
|
|
nn_Context *ctx;
|
|
nn_Lock *lock;
|
|
nn_Filesystem conf;
|
|
char *path;
|
|
ncl_VFS vfs;
|
|
// if 0, needs to be recomputed
|
|
size_t spaceUsed;
|
|
// if 0, needs to be recomputed
|
|
size_t realSpaceUsed;
|
|
size_t usage;
|
|
bool isReadonly;
|
|
// all the arrays
|
|
void *fds[NN_MAX_OPENFILES];
|
|
void *dirs[NN_MAX_OPENFILES];
|
|
char label[NN_MAX_LABEL];
|
|
size_t labellen;
|
|
} ncl_FSState;
|
|
|
|
typedef struct ncl_DriveState {
|
|
nn_Context *ctx;
|
|
nn_Lock *lock;
|
|
nn_Drive conf;
|
|
bool isReadonly;
|
|
size_t usage;
|
|
size_t lastSector;
|
|
char *path;
|
|
char label[NN_MAX_LABEL];
|
|
size_t labellen;
|
|
} ncl_DriveState;
|
|
|
|
typedef struct ncl_EEState {
|
|
nn_Context *ctx;
|
|
nn_Lock *lock;
|
|
nn_EEPROM conf;
|
|
bool isReadonly;
|
|
size_t usage;
|
|
char *codepath;
|
|
char *datapath;
|
|
char label[NN_MAX_LABEL];
|
|
size_t labellen;
|
|
} ncl_EEState;
|
|
|
|
static void ncl_fixPath(ncl_FSState *fs, const char *path, char buf[NN_MAX_PATH]) {
|
|
snprintf(buf, NN_MAX_PATH, "%s%c%s", fs->path, fs->vfs.pathsep, path);
|
|
for(size_t i = 0; buf[i]; i++) {
|
|
if(buf[i] == '/') buf[i] = fs->vfs.pathsep;
|
|
}
|
|
}
|
|
|
|
// assumes locked
|
|
static size_t ncl_fsGetUsage(ncl_FSState *fs) {
|
|
if(fs->spaceUsed == 0) {
|
|
fs->spaceUsed = ncl_spaceUsedIn(fs->vfs, fs->path);
|
|
}
|
|
if(fs->spaceUsed > fs->conf.spaceTotal) fs->spaceUsed = fs->conf.spaceTotal;
|
|
return fs->spaceUsed;
|
|
}
|
|
|
|
// assumes locked
|
|
static size_t ncl_fsGetRealUsage(ncl_FSState *fs) {
|
|
if(fs->realSpaceUsed == 0) {
|
|
fs->realSpaceUsed = ncl_spaceUsedBy(fs->vfs, fs->path);
|
|
}
|
|
return fs->realSpaceUsed;
|
|
}
|
|
|
|
// -1 on too many
|
|
static int ncl_findFileDesc(void *fds[NN_MAX_OPENFILES]) {
|
|
for(int i = 0; i < NN_MAX_OPENFILES; i++) {
|
|
if(fds[i] == NULL) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void *ncl_getFile(void *fds[NN_MAX_OPENFILES], int fd) {
|
|
if(fd < 0 || fd > NN_MAX_OPENFILES) return NULL;
|
|
return fds[fd];
|
|
}
|
|
|
|
static nn_Exit ncl_fsHandler(nn_FSRequest *req) {
|
|
ncl_FSState *state = req->state;
|
|
nn_Context *ctx = req->ctx;
|
|
nn_Computer *C = req->computer;
|
|
const nn_Filesystem *fs = req->fs;
|
|
|
|
if(req->action == NN_FS_DROP) {
|
|
for(size_t i = 0; i < NN_MAX_OPENFILES; i++) {
|
|
if(state->fds[i] != NULL) ncl_closefile(state->vfs, state->fds[i]);
|
|
if(state->dirs[i] != NULL) ncl_closedir(state->vfs, state->dirs[i]);
|
|
}
|
|
nn_destroyLock(ctx, state->lock);
|
|
nn_strfree(ctx, state->path);
|
|
nn_free(ctx, state, sizeof(*state));
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_SPACEUSED) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
req->spaceUsed = ncl_fsGetUsage(state);
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_GETLABEL) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
size_t len = state->labellen;
|
|
if(len > req->getlabel.len) len = req->getlabel.len;
|
|
memcpy(req->getlabel.buf, state->label, len);
|
|
req->getlabel.len = len;
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_SETLABEL) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
size_t len = req->setlabel.len;
|
|
if(len > NN_MAX_LABEL) len = NN_MAX_LABEL;
|
|
memcpy(state->label, req->setlabel.buf, len);
|
|
state->labellen = len;
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_ISRO) {
|
|
req->isReadonly = state->isReadonly;
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_OPEN) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
int fd = ncl_findFileDesc(state->fds);
|
|
if(fd < 0) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "too many files");
|
|
return NN_EBADCALL;
|
|
}
|
|
const char *mode = req->open.mode;
|
|
if(mode[0] != 'r' && state->isReadonly) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "is readonly");
|
|
return NN_EBADCALL;
|
|
}
|
|
char path[NN_MAX_PATH];
|
|
ncl_fixPath(state, req->open.path, path);
|
|
size_t spaceRemaining = state->conf.spaceTotal - ncl_fsGetUsage(state);
|
|
if(mode[0] == 'w' && !ncl_exists(state->vfs, path) && spaceRemaining < state->vfs.fileCost) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "out of space");
|
|
return NN_EBADCALL;
|
|
}
|
|
void *file = ncl_openfile(state->vfs, path, mode);
|
|
if(file == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, req->open.path);
|
|
return NN_EBADCALL;
|
|
}
|
|
state->fds[fd] = file;
|
|
req->fd = fd;
|
|
if(mode[0] == 'w') {
|
|
// file cleared
|
|
state->spaceUsed = 0;
|
|
state->realSpaceUsed = 0;
|
|
}
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_CLOSE) {
|
|
nn_lock(ctx, state->lock);
|
|
int fd = req->fd;
|
|
void *file = ncl_getFile(state->fds, fd);
|
|
if(file == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "bad file descriptor");
|
|
return NN_EBADCALL;
|
|
}
|
|
state->fds[fd] = NULL;
|
|
state->spaceUsed = 0;
|
|
state->realSpaceUsed = 0;
|
|
volatile ncl_VFS vfs = state->vfs;
|
|
nn_unlock(ctx, state->lock);
|
|
// out of lock for the most minimal of performance
|
|
ncl_closefile(vfs, file);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_READ) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
void *file = ncl_getFile(state->fds, req->fd);
|
|
if(file == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "bad file descriptor");
|
|
return NN_EBADCALL;
|
|
}
|
|
if(!ncl_readfile(state->vfs, file, req->read.buf, &req->read.len)) {
|
|
req->read.buf = NULL;
|
|
}
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_WRITE) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
void *file = ncl_getFile(state->fds, req->fd);
|
|
if(file == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "bad file descriptor");
|
|
return NN_EBADCALL;
|
|
}
|
|
size_t spaceRemaining = state->conf.spaceTotal - ncl_fsGetUsage(state);
|
|
// inaccurate...
|
|
if(spaceRemaining < req->write.len) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "out of space");
|
|
return NN_EBADCALL;
|
|
}
|
|
bool ok = ncl_writefile(state->vfs, file, req->write.buf, req->write.len);
|
|
state->spaceUsed = 0;
|
|
state->realSpaceUsed = 0;
|
|
nn_unlock(ctx, state->lock);
|
|
if(ok) return NN_OK;
|
|
nn_setError(C, "write failed");
|
|
return NN_EBADCALL;
|
|
}
|
|
if(req->action == NN_FS_SEEK) {
|
|
nn_lock(ctx, state->lock);
|
|
void *file = ncl_getFile(state->fds, req->fd);
|
|
if(file == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "bad file descriptor");
|
|
return NN_EBADCALL;
|
|
}
|
|
bool ok = ncl_seekfile(state->vfs, file, req->seek.whence, &req->seek.off);
|
|
nn_unlock(ctx, state->lock);
|
|
if(ok) return NN_OK;
|
|
nn_setError(C, "seek failed");
|
|
return NN_EBADCALL;
|
|
}
|
|
if(req->action == NN_FS_OPENDIR) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
int fd = ncl_findFileDesc(state->dirs);
|
|
if(fd < 0) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "too many directories listed simultaneously");
|
|
return NN_EBADCALL;
|
|
}
|
|
char path[NN_MAX_PATH];
|
|
ncl_fixPath(state, req->open.path, path);
|
|
void *dir = ncl_opendir(state->vfs, path);
|
|
if(dir == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, req->opendir);
|
|
return NN_EBADCALL;
|
|
}
|
|
state->dirs[fd] = dir;
|
|
req->fd = fd;
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_CLOSEDIR) {
|
|
nn_lock(ctx, state->lock);
|
|
int fd = req->fd;
|
|
void *file = ncl_getFile(state->dirs, fd);
|
|
if(file == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "bad file descriptor");
|
|
return NN_EBADCALL;
|
|
}
|
|
state->dirs[fd] = NULL;
|
|
volatile ncl_VFS vfs = state->vfs;
|
|
nn_unlock(ctx, state->lock);
|
|
// out of lock for the most minimal of performance
|
|
ncl_closedir(vfs, file);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_READDIR) {
|
|
nn_lock(ctx, state->lock);
|
|
int fd = req->fd;
|
|
void *dir = ncl_getFile(state->dirs, fd);
|
|
if(dir == NULL) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "bad file descriptor");
|
|
return NN_EBADCALL;
|
|
}
|
|
char name[NN_MAX_PATH];
|
|
if(!ncl_readdir(state->vfs, dir, name)) {
|
|
nn_unlock(ctx, state->lock);
|
|
req->readdir.buf = NULL;
|
|
return NN_OK;
|
|
}
|
|
char path[NN_MAX_PATH];
|
|
snprintf(path, NN_MAX_PATH, "%s%c%s%c%s", state->path, state->vfs.pathsep, req->readdir.dirpath, state->vfs.pathsep, name);
|
|
ncl_Stat s;
|
|
if(!ncl_stat(state->vfs, path, &s)) s.isDirectory = false;
|
|
if(s.isDirectory) snprintf(req->readdir.buf, req->readdir.len, "%s/", name);
|
|
else snprintf(req->readdir.buf, req->readdir.len, "%s", name);
|
|
req->readdir.len = strlen(req->readdir.buf);
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_STAT) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
char path[NN_MAX_PATH];
|
|
ncl_fixPath(state, req->stat.path, path);
|
|
ncl_Stat s;
|
|
if(!ncl_stat(state->vfs, path, &s)) {
|
|
nn_unlock(ctx, state->lock);
|
|
req->stat.path = NULL;
|
|
return NN_OK;
|
|
}
|
|
req->stat.isDirectory = s.isDirectory;
|
|
req->stat.size = s.size;
|
|
req->stat.lastModified = s.lastModified;
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_MKDIR) {
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
char path[NN_MAX_PATH];
|
|
ncl_fixPath(state, req->mkdir, path);
|
|
ncl_Stat s;
|
|
if(ncl_stat(state->vfs, path, &s)) {
|
|
nn_unlock(ctx, state->lock);
|
|
if(s.isDirectory) {
|
|
return NN_OK;
|
|
}
|
|
nn_setError(C, "not a directory");
|
|
return NN_EBADCALL;
|
|
}
|
|
if(!ncl_mkdirRecursive(state->vfs, path)) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "operation failed");
|
|
return NN_EBADCALL;
|
|
}
|
|
size_t newSpaceUsed = ncl_spaceUsedIn(state->vfs, state->path);
|
|
if(newSpaceUsed > state->conf.spaceTotal) {
|
|
ncl_removeRecursive(state->vfs, path);
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "out of space");
|
|
return NN_EBADCALL;
|
|
}
|
|
state->spaceUsed = newSpaceUsed;
|
|
state->realSpaceUsed = 0;
|
|
nn_unlock(ctx, state->lock);
|
|
return NN_OK;
|
|
}
|
|
if(req->action == NN_FS_RENAME) {
|
|
if(req->rename.from[0] == '\0') {
|
|
nn_setError(C, "root is forbidden");
|
|
return NN_EBADCALL;
|
|
}
|
|
if(req->rename.to != NULL && req->rename.to[0] == '\0') {
|
|
nn_setError(C, "root is forbidden");
|
|
return NN_EBADCALL;
|
|
}
|
|
nn_lock(ctx, state->lock);
|
|
state->usage++;
|
|
char from[NN_MAX_PATH];
|
|
ncl_fixPath(state, req->rename.from, from);
|
|
if(req->rename.to == NULL) {
|
|
bool ok = ncl_removeRecursive(state->vfs, from);
|
|
nn_unlock(ctx, state->lock);
|
|
if(!ok) {
|
|
nn_setError(C, "operation failed");
|
|
return NN_EBADCALL;
|
|
}
|
|
return NN_OK;
|
|
}
|
|
char to[NN_MAX_PATH];
|
|
ncl_fixPath(state, req->rename.to, to);
|
|
// copy a to a is illegal btw
|
|
if(ncl_isIllegalCopy(from, to)) {
|
|
nn_unlock(ctx, state->lock);
|
|
nn_setError(C, "illegal copy operation");
|
|
return NN_EBADCALL;
|
|
}
|
|
// matches tmpfs behavior
|
|
if(ncl_exists(state->vfs, to)) {
|
|
ncl_removeRecursive(state->vfs, to);
|
|
}
|
|
bool ok = ncl_copyto(state->vfs, from, to);
|
|
if(ok) {
|
|
ncl_removeRecursive(state->vfs, from);
|
|
}
|
|
state->spaceUsed = 0;
|
|
state->realSpaceUsed = 0;
|
|
nn_unlock(ctx, state->lock);
|
|
if(!ok) {
|
|
nn_setError(C, "operation failed");
|
|
return NN_EBADCALL;
|
|
}
|
|
return NN_OK;
|
|
}
|
|
|
|
if(C) nn_setError(C, "not implemented yet");
|
|
return NN_EBADCALL;
|
|
}
|
|
|
|
nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs, bool isReadonly) {
|
|
nn_Context *ctx = nn_getUniverseContext(universe);
|
|
|
|
ncl_FSState *state = nn_alloc(ctx, sizeof(*state));
|
|
if(state == NULL) return NULL;
|
|
state->ctx = ctx;
|
|
state->lock = nn_createLock(ctx);
|
|
if(state->lock == NULL) {
|
|
nn_free(ctx, state, sizeof(*state));
|
|
return NULL;
|
|
}
|
|
state->path = nn_strdup(ctx, path);
|
|
if(state->path == NULL) {
|
|
nn_destroyLock(ctx, state->lock);
|
|
nn_free(ctx, state, sizeof(*state));
|
|
return NULL;
|
|
}
|
|
state->vfs = ncl_defaultFS;
|
|
state->usage = 0;
|
|
state->isReadonly = isReadonly;
|
|
state->conf = *fs;
|
|
state->labellen = 0;
|
|
state->realSpaceUsed = 0;
|
|
state->spaceUsed = 0;
|
|
for(size_t i = 0; i < NN_MAX_OPENFILES; i++) {
|
|
state->fds[i] = NULL;
|
|
state->dirs[i] = NULL;
|
|
}
|
|
nn_Component *c = nn_createFilesystem(universe, address, fs, state, ncl_fsHandler);
|
|
if(c == NULL) {
|
|
nn_strfree(ctx, state->path);
|
|
nn_destroyLock(ctx, state->lock);
|
|
nn_free(ctx, state, sizeof(*state));
|
|
return NULL;
|
|
}
|
|
// TODO: handle OOM case
|
|
nn_setComponentTypeID(c, NCL_FS);
|
|
return c;
|
|
}
|
|
|
|
nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly);
|
|
nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *codepath, const char *datapath, bool isReadonly);
|
|
|
|
ncl_VFS ncl_setVFS(nn_Component *component, ncl_VFS vfs);
|
|
|
|
static ncl_ScreenPixel ncl_getRealScreenPixel(const ncl_ScreenState *state, int x, int y) {
|
|
if(x < 1 || y < 1 || x >= state->width || y >= state->height) {
|
|
return (ncl_ScreenPixel) {
|
|
.codepoint = ' ',
|
|
.storedFg = 0xFFFFFF,
|
|
.storedBg = 0x000000,
|
|
.realFg = 0xFFFFFF,
|
|
.realBg = 0x000000,
|
|
};
|
|
}
|
|
|
|
// make it 0-indexed
|
|
x--;
|
|
y--;
|
|
|
|
return state->pixels[x + y * state->conf.maxWidth];
|
|
}
|
|
|
|
static ncl_ScreenPixel *ncl_getRealScreenPixelPointer(const ncl_ScreenState *state, int x, int y) {
|
|
if(x < 1 || y < 1 || x >= state->width || y >= state->height) {
|
|
return NULL;
|
|
}
|
|
|
|
// make it 0-indexed
|
|
x--;
|
|
y--;
|
|
|
|
return &state->pixels[x + y * state->conf.maxWidth];
|
|
}
|
|
|
|
static void ncl_setRealScreenPixel(ncl_ScreenState *state, int x, int y, ncl_ScreenPixel pixel) {
|
|
if(x < 1 || y < 1 || x >= state->width || y >= state->height) return;
|
|
x--;
|
|
y--;
|
|
|
|
state->pixels[x + y * state->conf.maxWidth] = pixel;
|
|
}
|
|
|
|
static void ncl_recomputeScreen(const ncl_ScreenState *state) {
|
|
for(int y = 1; y <= state->height; y++) {
|
|
for(int x = 1; x <= state->width; x++) {
|
|
ncl_ScreenPixel *pixel = ncl_getRealScreenPixelPointer(state, x, y);
|
|
if(pixel == NULL) continue;
|
|
if(pixel->realFg >= 0) {
|
|
pixel->realFg = nn_mapDepth(pixel->storedFg, state->depth);
|
|
}
|
|
if(pixel->realBg >= 0) {
|
|
pixel->realBg = nn_mapDepth(pixel->storedBg, state->depth);
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < state->conf.paletteColors; i++) {
|
|
state->resolvedPalette[i] = nn_mapDepth(state->palette[i], state->depth);
|
|
}
|
|
}
|
|
|
|
static nn_Exit ncl_screenHandler(nn_ScreenRequest *req) {
|
|
nn_Context *ctx = req->ctx;
|
|
nn_Computer *C = req->computer;
|
|
ncl_ScreenState *state = req->state;
|
|
const nn_ScreenConfig *conf = req->screen;
|
|
|
|
if(req->action == NN_SCREEN_DROP) {
|
|
for(size_t i = 0; i < state->keyboardCount; i++) {
|
|
nn_strfree(ctx, state->keyboards[i]);
|
|
}
|
|
nn_destroyLock(ctx, state->lock);
|
|
nn_free(ctx, state->pixels, sizeof(ncl_ScreenPixel) * state->conf.maxWidth * state->conf.maxHeight);
|
|
nn_free(ctx, state->palette, sizeof(int) * state->conf.paletteColors);
|
|
nn_free(ctx, state->resolvedPalette, sizeof(int) * state->conf.paletteColors);
|
|
nn_free(ctx, state, sizeof(*state));
|
|
return NN_OK;
|
|
}
|
|
|
|
if(C) nn_setError(C, "ncl-screen: not implemented yet");
|
|
return NN_EBADCALL;
|
|
}
|
|
|
|
nn_Component *ncl_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *config) {
|
|
nn_Context *ctx = nn_getUniverseContext(universe);
|
|
ncl_ScreenState *screen = NULL;
|
|
ncl_ScreenPixel *pixels = NULL;
|
|
int *palette = NULL;
|
|
int *resolvedPalette = NULL;
|
|
nn_Component *c = NULL;
|
|
nn_Lock *lock = NULL;
|
|
|
|
screen = nn_alloc(ctx, sizeof(ncl_ScreenState));
|
|
if(screen == NULL) goto fail;
|
|
|
|
lock = nn_createLock(ctx);
|
|
if(lock == NULL) goto fail;
|
|
|
|
pixels = nn_alloc(ctx, sizeof(ncl_ScreenPixel) * config->maxWidth * config->maxHeight);
|
|
if(pixels == NULL) goto fail;
|
|
|
|
palette = nn_alloc(ctx, sizeof(int) * config->paletteColors);
|
|
if(palette == NULL) goto fail;
|
|
memcpy(palette, config->defaultPalette, sizeof(int) * config->paletteColors);
|
|
|
|
resolvedPalette = nn_alloc(ctx, sizeof(int) * config->paletteColors);
|
|
if(resolvedPalette == NULL) goto fail;
|
|
|
|
screen->conf = *config;
|
|
screen->ctx = ctx;
|
|
screen->lock = lock;
|
|
screen->width = config->maxWidth;
|
|
screen->height = config->maxHeight;
|
|
screen->palette = palette;
|
|
screen->resolvedPalette = resolvedPalette;
|
|
screen->pixels = pixels;
|
|
screen->flags = 0;
|
|
screen->depth = config->maxDepth;
|
|
screen->viewportWidth = screen->width;
|
|
screen->viewportHeight = screen->height;
|
|
screen->keyboardCount = 0;
|
|
|
|
ncl_resetScreen(screen);
|
|
|
|
c = nn_createScreen(universe, address, config, screen, ncl_screenHandler);
|
|
if(c == NULL) goto fail;
|
|
|
|
if(nn_setComponentTypeID(c, NCL_SCREEN)) goto fail;
|
|
|
|
return c;
|
|
|
|
fail:;
|
|
if(c != NULL) {
|
|
nn_dropComponent(c);
|
|
return NULL;
|
|
}
|
|
if(lock != NULL) nn_destroyLock(ctx, lock);
|
|
nn_free(ctx, screen, sizeof(*screen));
|
|
nn_free(ctx, palette, sizeof(int) * config->paletteColors);
|
|
nn_free(ctx, resolvedPalette, sizeof(int) * config->paletteColors);
|
|
nn_free(ctx, pixels, sizeof(ncl_ScreenPixel) * config->maxWidth * config->maxHeight);
|
|
return NULL;
|
|
}
|
|
|
|
static ncl_ScreenState *ncl_getBoundScreen(ncl_GPUState *gpu, nn_Computer *C) {
|
|
if(gpu->screenAddress == NULL) return NULL;
|
|
nn_Component *c = nn_getComponent(C, gpu->screenAddress);
|
|
if(c == NULL) return NULL;
|
|
return nn_getComponentState(c);
|
|
}
|
|
|
|
static void ncl_getGPULimits(ncl_GPUState *gpu, nn_Computer *C, int *maxWidth, int *maxHeight, char *maxDepth) {
|
|
int w = gpu->conf.maxWidth, h = gpu->conf.maxHeight;
|
|
char d = gpu->conf.maxDepth;
|
|
|
|
ncl_ScreenState *screen = ncl_getBoundScreen(gpu, C);
|
|
|
|
if(screen != NULL) {
|
|
if(w > screen->conf.maxWidth) w = screen->conf.maxWidth;
|
|
if(h > screen->conf.maxHeight) h = screen->conf.maxHeight;
|
|
if(d > screen->conf.maxDepth) d = screen->conf.maxDepth;
|
|
}
|
|
|
|
*maxWidth = w;
|
|
*maxHeight = h;
|
|
*maxDepth = d;
|
|
}
|
|
|
|
static nn_Exit ncl_gpuHandler(nn_GPURequest *req) {
|
|
nn_Context *ctx = req->ctx;
|
|
nn_Computer *C = req->computer;
|
|
ncl_GPUState *state = req->state;
|
|
const nn_GPU *gpu = req->gpu;
|
|
if(req->action == NN_GPU_DROP) {
|
|
for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) {
|
|
if(state->vram[i] != NULL) ncl_freeVRAM(ctx, state->vram[i]);
|
|
}
|
|
if(state->screenAddress != NULL) nn_strfree(ctx, state->screenAddress);
|
|
nn_free(ctx, state, sizeof(*state));
|
|
return NN_OK;
|
|
}
|
|
if(C != NULL) nn_setError(C, "ncl-gpu: not implemented yet");
|
|
return NN_EBADCALL;
|
|
}
|
|
|
|
nn_Component *ncl_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu) {
|
|
nn_Context *ctx = nn_getUniverseContext(universe);
|
|
nn_Lock *lock = NULL;
|
|
ncl_GPUState *state = NULL;
|
|
nn_Component *c = NULL;
|
|
|
|
lock = nn_createLock(ctx);
|
|
if(lock == NULL) goto fail;
|
|
|
|
state = nn_alloc(ctx, sizeof(*state));
|
|
if(state == NULL) goto fail;
|
|
|
|
state->ctx = ctx;
|
|
state->lock = lock;
|
|
state->conf = *gpu;
|
|
state->vramFree = gpu->totalVRAM;
|
|
state->screenAddress = NULL;
|
|
state->currentFg = 0xFFFFFF;
|
|
state->currentBg = 0x000000;
|
|
state->activeBuffer = 0;
|
|
state->isFgPalette = false;
|
|
state->isBgPalette = false;
|
|
for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) {
|
|
state->vram[i] = NULL;
|
|
}
|
|
|
|
c = nn_createGPU(universe, address, gpu, state, ncl_gpuHandler);
|
|
if(c == NULL) goto fail;
|
|
|
|
if(nn_setComponentTypeID(c, NCL_GPU)) goto fail;
|
|
|
|
return c;
|
|
fail:
|
|
if(c != NULL) {
|
|
nn_dropComponent(c);
|
|
return NULL;
|
|
}
|
|
if(lock != NULL) nn_destroyLock(ctx, lock);
|
|
nn_free(ctx, state, sizeof(*state));
|
|
return NULL;
|
|
}
|
|
|
|
void ncl_lockScreen(ncl_ScreenState *state) {
|
|
nn_lock(state->ctx, state->lock);
|
|
}
|
|
|
|
void ncl_unlockScreen(ncl_ScreenState *state) {
|
|
nn_unlock(state->ctx, state->lock);
|
|
}
|
|
|
|
void ncl_resetScreen(ncl_ScreenState *state) {
|
|
state->width = state->conf.maxWidth;
|
|
state->height = state->conf.maxHeight;
|
|
state->viewportWidth = state->conf.maxWidth;
|
|
state->viewportHeight = state->conf.maxHeight;
|
|
|
|
for(int y = 1; y <= state->height; y++) {
|
|
for(int x = 1; x <= state->width; x++) {
|
|
ncl_setRealScreenPixel(state, x, y, (ncl_ScreenPixel) {
|
|
.codepoint = ' ',
|
|
.storedFg = 0xFFFFFF,
|
|
.storedBg = 0x000000,
|
|
.realFg = 0xFFFFFF,
|
|
.realBg = 0x000000,
|
|
});
|
|
}
|
|
}
|
|
|
|
memcpy(state->palette, state->conf.defaultPalette, sizeof(int) * state->conf.paletteColors);
|
|
ncl_recomputeScreen(state);
|
|
}
|
|
|
|
void ncl_getScreenResolution(const ncl_ScreenState *state, size_t *width, size_t *height) {
|
|
*width = state->width;
|
|
*height = state->height;
|
|
}
|
|
|
|
void ncl_getScreenViewport(const ncl_ScreenState *state, size_t *width, size_t *height) {
|
|
*width = state->viewportWidth;
|
|
*height = state->viewportHeight;
|
|
}
|
|
|
|
ncl_Pixel ncl_getScreenPixel(const ncl_ScreenState *state, int x, int y) {
|
|
ncl_ScreenPixel p = ncl_getRealScreenPixel(state, x, y);
|
|
return (ncl_Pixel) {
|
|
.codepoint = p.codepoint,
|
|
.fgColor = p.realFg < 0 ? state->resolvedPalette[p.storedFg] : p.realFg,
|
|
.bgColor = p.realBg < 0 ? state->resolvedPalette[p.storedBg] : p.realBg,
|
|
};
|
|
}
|
|
|
|
void ncl_setScreenPixel(ncl_ScreenState *state, int x, int y, nn_codepoint codepoint, int fg, int bg, bool isFgPalette, bool isBgPalette) {
|
|
ncl_ScreenPixel p = {
|
|
.codepoint = codepoint,
|
|
.storedFg = fg,
|
|
.storedBg = bg,
|
|
.realFg = isFgPalette ? -1 : fg,
|
|
.realBg = isBgPalette ? -1 : bg,
|
|
};
|
|
ncl_setRealScreenPixel(state, x, y, p);
|
|
ncl_recomputeScreen(state);
|
|
}
|
|
|
|
ncl_ScreenFlags ncl_getScreenFlags(const ncl_ScreenState *state) {
|
|
return state->flags;
|
|
}
|
|
|
|
void ncl_setScreenFlags(ncl_ScreenState *state, ncl_ScreenFlags flags) {
|
|
state->flags = flags;
|
|
}
|
|
|
|
char ncl_getScreenDepth(ncl_ScreenState *state) {
|
|
return state->depth;
|
|
}
|
|
|
|
void ncl_setScreenDepth(ncl_ScreenState *state, char depth) {
|
|
state->depth = depth;
|
|
}
|
|
|
|
nn_Exit ncl_mountKeyboard(ncl_ScreenState *state, const char *keyboardAddress);
|
|
void ncl_unmountKeyboard(ncl_ScreenState *state, const char *keyboardAddress);
|
|
bool ncl_hasKeyboard(ncl_ScreenState *state, const char *keyboardAddress);
|
|
const char *ncl_getKeyboard(ncl_ScreenState *state, size_t idx);
|
|
|
|
// general stuff
|
|
|
|
void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) {
|
|
stat->labellen = 0;
|
|
stat->isReadonly = false;
|
|
const char *ty = nn_getComponentTypeID(component);
|
|
void *state = nn_getComponentState(component);
|
|
if(strcmp(ty, NCL_FS) == 0) {
|
|
ncl_FSState *fs = state;
|
|
nn_lock(fs->ctx, fs->lock);
|
|
stat->isReadonly = fs->isReadonly;
|
|
stat->usageCounter = fs->usage;
|
|
stat->labellen = fs->labellen;
|
|
memcpy(stat->label, fs->label, stat->labellen);
|
|
stat->fs.spaceUsed = ncl_fsGetUsage(fs);
|
|
stat->fs.realDiskUsage = ncl_fsGetRealUsage(fs);
|
|
stat->fs.conf = &fs->conf;
|
|
stat->fs.path = fs->path;
|
|
stat->fs.filesOpen = 0;
|
|
for(size_t i = 0; i < NN_MAX_OPENFILES; i++) {
|
|
if(fs->fds[i] != NULL) stat->fs.filesOpen++;
|
|
}
|
|
nn_unlock(fs->ctx, fs->lock);
|
|
return;
|
|
}
|
|
if(strcmp(ty, NCL_DRIVE) == 0) {
|
|
ncl_DriveState *drv = state;
|
|
nn_lock(drv->ctx, drv->lock);
|
|
stat->isReadonly = drv->isReadonly;
|
|
stat->usageCounter = drv->usage;
|
|
stat->labellen = drv->labellen;
|
|
memcpy(stat->label, drv->label, stat->labellen);
|
|
stat->drive.path = drv->path;
|
|
stat->drive.lastSector = drv->lastSector;
|
|
stat->drive.conf = &drv->conf;
|
|
nn_unlock(drv->ctx, drv->lock);
|
|
return;
|
|
}
|
|
if(strcmp(ty, NCL_EEPROM) == 0) {
|
|
ncl_EEState *ee = state;
|
|
nn_lock(ee->ctx, ee->lock);
|
|
stat->isReadonly = ee->isReadonly;
|
|
stat->usageCounter = ee->usage;
|
|
stat->labellen = ee->labellen;
|
|
memcpy(stat->label, ee->label, stat->labellen);
|
|
stat->eeprom.conf = &ee->conf;
|
|
stat->eeprom.codepath = ee->codepath;
|
|
stat->eeprom.datapath = ee->datapath;
|
|
nn_unlock(ee->ctx, ee->lock);
|
|
return;
|
|
}
|
|
if(strcmp(ty, NCL_SCREEN) == 0) {
|
|
ncl_ScreenState *screen = state;
|
|
nn_lock(screen->ctx, screen->lock);
|
|
stat->screen.conf = &screen->conf;
|
|
stat->screen.depth = screen->depth;
|
|
stat->screen.flags = screen->flags;
|
|
stat->screen.keyboardCount = screen->keyboardCount;
|
|
stat->screen.viewportWidth = screen->viewportWidth;
|
|
stat->screen.viewportHeight = screen->viewportHeight;
|
|
stat->screen.state = screen;
|
|
nn_unlock(screen->ctx, screen->lock);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// For EEPROMs, filesystems, drives
|
|
// Returns whether it was successful or not.
|
|
bool ncl_makeReadonly(nn_Component *component) {
|
|
const char *ty = nn_getComponentTypeID(component);
|
|
void *state = nn_getComponentState(component);
|
|
if(strcmp(ty, NCL_FS) == 0) {
|
|
ncl_FSState *fs = state;
|
|
nn_lock(fs->ctx, fs->lock);
|
|
fs->isReadonly = true;
|
|
fs->usage++;
|
|
nn_unlock(fs->ctx, fs->lock);
|
|
return true;
|
|
}
|
|
if(strcmp(ty, NCL_DRIVE) == 0) {
|
|
ncl_DriveState *drv = state;
|
|
drv->isReadonly = true;
|
|
drv->usage++;
|
|
return true;
|
|
}
|
|
if(strcmp(ty, NCL_EEPROM) == 0) {
|
|
ncl_EEState *ee = state;
|
|
ee->isReadonly = true;
|
|
ee->usage++;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// all of these are encoding states
|
|
|
|
nn_Exit ncl_encodeComponentState(nn_Universe *universe, nn_Component *comp, ncl_EncodedState *state);
|
|
void ncl_freeEncodedState(nn_Universe *universe, ncl_EncodedState *state);
|
|
nn_Exit ncl_loadComponentState(nn_Component *comp, const ncl_EncodedState *state);
|
|
|