From d409dbc86ed28b15eeb5bff3d424f803c60ef2eb Mon Sep 17 00:00:00 2001 From: shygoo Date: Sun, 18 Feb 2018 15:37:03 -0600 Subject: [PATCH] [Debugger] Add file system interface to the JS API --- Source/Project64/UserInterface/API.js | 127 +++++ .../UserInterface/Debugger/ScriptInstance.cpp | 501 +++++++++++++++++- .../UserInterface/Debugger/ScriptInstance.h | 47 +- apidoc.htm | 146 ++++- 4 files changed, 795 insertions(+), 26 deletions(-) diff --git a/Source/Project64/UserInterface/API.js b/Source/Project64/UserInterface/API.js index 8ec0e09d7..9e27b25de 100644 --- a/Source/Project64/UserInterface/API.js +++ b/Source/Project64/UserInterface/API.js @@ -56,6 +56,133 @@ const ADDR_ANY_RDRAM_UNC = new AddressRange(0xA0000000, 0xA0800000) const ADDR_ANY_CART_ROM = new AddressRange(0x90000000, 0x96000000) const ADDR_ANY_CART_ROM_UNC = new AddressRange(0xB0000000, 0xB6000000) +const fs = { + open: function(path, mode) + { + if (["r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "r+b", "w+", "wb+", "w+b", "a+", "ab+", "a+b"].indexOf(mode) == -1) + { + return false + } + return _native.fsOpen(path, mode) + }, + close: function(fd) + { + return _native.fsClose(fd) + }, + write: function(fd, buffer, offset, length, position) + { + return _native.fsWrite(fd, buffer, offset, length, position) + }, + writeFile: function(path, buffer) + { + var fd = fs.open(path, 'wb') + + if (fd == false) + { + return false + } + + fs.write(fd, buffer) + fs.close(fd) + return true + }, + read: function (fd, buffer, offset, length, position) + { + return _native.fsRead(fd, buffer, offset, length, position) + }, + readFile: function(path) + { + var fd = fs.open(path, 'rb') + + if(fd == false) + { + return false + } + + var stats = fs.fstat(fd); + + var buf = new Buffer(stats.size) + + fs.read(fd, buf, 0, stats.size, 0) + + return buf + }, + fstat: function(fd) + { + var rawStats = _native.fsFStat(fd) + if (rawStats == false) + { + return false + } + return new fs.Stats(rawStats) + }, + stat: function(path) + { + var rawStats = _native.fsStat(path) + if (rawStats == false) + { + return false + } + return new fs.Stats(rawStats) + }, + mkdir: function(path) + { + return _native.fsMkDir(path) + }, + rmdir: function(path) + { + return _native.fsRmDir(path) + }, + readdir: function(path) + { + return _native.fsReadDir(path) + }, + unlink: function(path) + { + return _native.fsUnlink(path) + }, + Stats: function(rawstats) + { + this.dev = rawstats.dev + this.ino = rawstats.ino + this.mode = rawstats.mode + this.nlink = rawstats.nlink + this.uid = rawstats.uid + this.gid = rawstats.gid + this.rdev = rawstats.rdev + this.size = rawstats.size + this.atimeMs = rawstats.atimeMs + this.mtimeMs = rawstats.mtimeMs + this.ctimeMs = rawstats.ctimeMs + this.atime = new Date(this.atimeMs) + this.mtime = new Date(this.mtimeMs) + this.ctime = new Date(this.ctimeMs) + Object.freeze(this) + } +} + +fs.Stats.S_IFMT = 0xF000 +fs.Stats.S_IFDIR = 0x4000 +fs.Stats.S_IFCHR = 0x2000 +fs.Stats.S_IFIFO = 0x1000 +fs.Stats.S_IFREG = 0x8000 +fs.Stats.S_IREAD = 0x0100 +fs.Stats.S_IWRITE = 0x0080 +fs.Stats.S_IEXEC = 0x0040 + +fs.Stats.prototype.isDirectory = function() +{ + return ((this.mode & fs.Stats.S_IFMT) == fs.Stats.S_IFDIR) +} + +fs.Stats.prototype.isFile = function() +{ + return ((this.mode & fs.Stats.S_IFMT) == fs.Stats.S_IFREG) +} + +Object.freeze(fs.Stats) +Object.freeze(fs.Stats.prototype) + const system = { pause: function() { diff --git a/Source/Project64/UserInterface/Debugger/ScriptInstance.cpp b/Source/Project64/UserInterface/Debugger/ScriptInstance.cpp index 2190e67e2..df3db2f16 100644 --- a/Source/Project64/UserInterface/Debugger/ScriptInstance.cpp +++ b/Source/Project64/UserInterface/Debugger/ScriptInstance.cpp @@ -1,4 +1,5 @@ #include +#include #include "ScriptInstance.h" #include "ScriptSystem.h" @@ -69,8 +70,7 @@ void CScriptInstance::ForceStop() { // Close all files and delete all hooked callbacks EnterCriticalSection(&m_CriticalSection); - m_ScriptSystem->ClearCallbacksForInstance(this); - CloseAllFiles(); + CleanUp(); LeaveCriticalSection(&m_CriticalSection); SetState(STATE_STOPPED); } @@ -164,10 +164,18 @@ void CScriptInstance::StartScriptProc() } else { + CleanUp(); SetState(STATE_STOPPED); } } +void CScriptInstance::CleanUp() +{ + m_ScriptSystem->ClearCallbacksForInstance(this); + CloseAllAsyncFiles(); + CloseAllFiles(); +} + void CScriptInstance::StartEventLoop() { SetState(STATE_RUNNING); @@ -192,6 +200,7 @@ void CScriptInstance::StartEventLoop() RemoveListener(lpListener); } + CleanUp(); SetState(STATE_STOPPED); } @@ -235,23 +244,23 @@ bool CScriptInstance::HaveEvents() m_ScriptSystem->HasCallbacksForInstance(this); } -void CScriptInstance::AddFile(HANDLE fd, bool bSocket) +void CScriptInstance::AddAsyncFile(HANDLE fd, bool bSocket) { IOFD iofd; iofd.fd = fd; iofd.iocp = CreateIoCompletionPort(fd, m_hIOCompletionPort, (ULONG_PTR)fd, 0); iofd.bSocket = bSocket; - m_Files.push_back(iofd); + m_AsyncFiles.push_back(iofd); } -void CScriptInstance::CloseFile(HANDLE fd) +void CScriptInstance::CloseAsyncFile(HANDLE fd) { // Causes EVT_READ with length 0 // Not safe to remove listeners until then - for (uint32_t i = 0; i < m_Files.size(); i++) + for (uint32_t i = 0; i < m_AsyncFiles.size(); i++) { - IOFD iofd = m_Files[i]; + IOFD iofd = m_AsyncFiles[i]; if (iofd.fd != fd) { continue; @@ -270,14 +279,14 @@ void CScriptInstance::CloseFile(HANDLE fd) } } -void CScriptInstance::CloseAllFiles() +void CScriptInstance::CloseAllAsyncFiles() { // Causes EVT_READ with length 0 // Not safe to remove listeners until then - for (uint32_t i = 0; i < m_Files.size(); i++) + for (uint32_t i = 0; i < m_AsyncFiles.size(); i++) { - IOFD iofd = m_Files[i]; + IOFD iofd = m_AsyncFiles[i]; // Close file handle if (iofd.bSocket) @@ -291,18 +300,18 @@ void CScriptInstance::CloseAllFiles() } } -void CScriptInstance::RemoveFile(HANDLE fd) +void CScriptInstance::RemoveAsyncFile(HANDLE fd) { // Stop tracking an fd and remove all of its listeners - for (uint32_t i = 0; i < m_Files.size(); i++) + for (uint32_t i = 0; i < m_AsyncFiles.size(); i++) { - IOFD iofd = m_Files[i]; + IOFD iofd = m_AsyncFiles[i]; if (iofd.fd != fd) { continue; } - m_Files.erase(m_Files.begin() + i); + m_AsyncFiles.erase(m_AsyncFiles.begin() + i); RemoveListenersByFd(fd); break; } @@ -311,7 +320,7 @@ void CScriptInstance::RemoveFile(HANDLE fd) HANDLE CScriptInstance::CreateSocket() { HANDLE fd = (HANDLE)WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); - AddFile(fd, true); + AddAsyncFile(fd, true); return fd; } @@ -398,7 +407,7 @@ void CScriptInstance::InvokeListenerCallback(IOLISTENER* lpListener) else { // handle must have closed, safe to untrack fd and remove all associated listeners - RemoveFile(lpListener->fd); + RemoveAsyncFile(lpListener->fd); // pass null to callback duk_push_null(m_Ctx); @@ -476,6 +485,67 @@ void CALLBACK CScriptInstance::EvalAsyncCallback(ULONG_PTR _pendingEval) delete evalWait; } +bool CScriptInstance::AddFile(const char* path, const char* mode, int* fd) +{ + FILE* fp = fopen(path, mode); + + if (fp == NULL) + { + return false; + } + + FILE_FD filefd; + filefd.fp = fp; + filefd.fd = _fileno(fp); + m_Files.push_back(filefd); + *fd = filefd.fd; + return true; +} + +void CScriptInstance::CloseFile(int fd) +{ + size_t nFiles = m_Files.size(); + + for (size_t i = 0; i < nFiles; i++) + { + FILE_FD* filefd = &m_Files[i]; + + if (filefd->fd == fd) + { + fclose(filefd->fp); + m_Files.erase(m_Files.begin() + i); + return; + } + } +} + +void CScriptInstance::CloseAllFiles() +{ + size_t nFiles = m_Files.size(); + + for (size_t i = 0; i < nFiles; i++) + { + fclose(m_Files[i].fp); + m_Files.erase(m_Files.begin() + i); + } +} + +FILE* CScriptInstance::GetFilePointer(int fd) +{ + size_t nFiles = m_Files.size(); + + for (size_t i = 0; i < nFiles; i++) + { + FILE_FD* filefd = &m_Files[i]; + + if (filefd->fd == fd) + { + return filefd->fp; + } + } + return NULL; +} + const char* CScriptInstance::EvalFile(const char* jsPath) { int result = duk_peval_file(m_Ctx, jsPath); @@ -554,7 +624,7 @@ duk_ret_t CScriptInstance::js_ioClose(duk_context* ctx) CScriptInstance* _this = FetchInstance(ctx); HANDLE fd = (HANDLE)duk_get_uint(ctx, 0); duk_pop(ctx); - _this->CloseFile(fd); + _this->CloseAsyncFile(fd); return 1; } @@ -1222,6 +1292,12 @@ duk_ret_t CScriptInstance::js_GetRDRAMBlock(duk_context* ctx) duk_pop_n(ctx, 2); + if (g_MMU == NULL) + { + duk_push_boolean(ctx, false); + return 1; + } + uint8_t* block = (uint8_t*)duk_push_buffer(ctx, size, false); for (uint32_t i = 0; i < size; i++) @@ -1431,6 +1507,397 @@ duk_ret_t CScriptInstance::js_ScreenPrint(duk_context* ctx) return 1; } +duk_ret_t CScriptInstance::js_FSOpen(duk_context* ctx) +{ + CScriptInstance* _this = FetchInstance(ctx); + + int nargs = duk_get_top(ctx); + + if (nargs < 2) + { + duk_pop_n(ctx, nargs); + duk_push_false(ctx); + return 1; + } + + const char* path = duk_to_string(ctx, 0); + const char* mode = duk_to_string(ctx, 1); + + int fd; + bool res = _this->AddFile(path, mode, &fd); + + duk_pop_n(ctx, nargs); + + if (res == false) + { + duk_push_false(ctx); + return 1; + } + + duk_push_number(ctx, fd); + + return 1; +} + +duk_ret_t CScriptInstance::js_FSClose(duk_context* ctx) +{ + CScriptInstance* _this = FetchInstance(ctx); + + int nargs = duk_get_top(ctx); + + if (nargs < 1) + { + duk_pop_n(ctx, nargs); + return 1; + } + + int fd = duk_get_int(ctx, 0); + duk_pop_n(ctx, nargs); + + _this->CloseFile(fd); + + return 1; +} + +duk_ret_t CScriptInstance::js_FSWrite(duk_context* ctx) +{ + CScriptInstance* _this = FetchInstance(ctx); + int nargs = duk_get_top(ctx); + + size_t nBytesWritten = 0; + + if (nargs < 2) + { + goto end; + } + + int fd = duk_get_int(ctx, 0); + + FILE* fp = _this->GetFilePointer(fd); + + if (fp == NULL) + { + goto end; + } + + const char* buffer; + int offset = 0; + duk_size_t length = 0; + int position = 0; // fseek + + if (duk_is_string(ctx, 1)) + { + // string + const char* str = duk_get_string(ctx, 1); + length = strlen(str); + + buffer = str; + } + else + { + // buffer + buffer = (const char*)duk_get_buffer_data(ctx, 1, &length); + + if (buffer == NULL) + { + goto end; + } + } + + if (nargs > 2 && duk_get_type(ctx, 2) == DUK_TYPE_NUMBER) + { + offset = duk_get_int(ctx, 2); + length -= offset; + } + + if (nargs > 3 && duk_get_type(ctx, 3) == DUK_TYPE_NUMBER) + { + size_t lengthArg = duk_get_uint(ctx, 3); + + if (lengthArg <= length) + { + length = lengthArg; + } + } + + if (nargs > 4 && duk_get_type(ctx, 4) == DUK_TYPE_NUMBER) + { + position = duk_get_int(ctx, 4); + fseek(fp, position, SEEK_SET); + } + + nBytesWritten = fwrite(buffer, 1, length, fp); + + end: + duk_pop_n(ctx, nargs); + duk_push_number(ctx, nBytesWritten); + return 1; +} + +duk_ret_t CScriptInstance::js_FSRead(duk_context* ctx) +{ + CScriptInstance* _this = FetchInstance(ctx); + int nargs = duk_get_top(ctx); + + size_t nBytesRead = 0; + + // (fd, buffer, offset, length, position) + + if (nargs < 5) + { + goto end; + } + + duk_size_t bufferSize; + + int fd = duk_get_int(ctx, 0); + char* buffer = (char*)duk_get_buffer_data(ctx, 1, &bufferSize); + duk_size_t offset = duk_get_uint(ctx, 2); + duk_size_t length = duk_get_uint(ctx, 3); + duk_size_t position = duk_get_uint(ctx, 4); + + if (bufferSize == 0) + { + goto end; + } + + FILE* fp = _this->GetFilePointer(fd); + + if (fp == NULL) + { + goto end; + } + + if (offset + length > bufferSize) + { + length = bufferSize - offset; + } + + fseek(fp, position, SEEK_SET); + nBytesRead = fread(&buffer[offset], 1, length, fp); + + end: + duk_pop_n(ctx, nargs); + duk_push_number(ctx, nBytesRead); + return 1; +} + +duk_ret_t CScriptInstance::js_FSFStat(duk_context* ctx) +{ + int nargs = duk_get_top(ctx); + + if (nargs < 1) + { + duk_pop_n(ctx, nargs); + duk_push_false(ctx); + return 1; + } + + int fd = duk_get_int(ctx, 0); + + struct stat statbuf; + int res = fstat(fd, &statbuf); + + if (res != 0) + { + duk_pop_n(ctx, nargs); + duk_push_false(ctx); + return 1; + } + + duk_pop_n(ctx, nargs); + + duk_idx_t obj_idx = duk_push_object(ctx); + + const duk_number_list_entry props[] = { + { "dev", (duk_double_t)statbuf.st_dev }, + { "ino", (duk_double_t)statbuf.st_ino }, + { "mode", (duk_double_t)statbuf.st_mode }, + { "nlink", (duk_double_t)statbuf.st_nlink }, + { "uid", (duk_double_t)statbuf.st_uid }, + { "gid", (duk_double_t)statbuf.st_gid }, + { "rdev", (duk_double_t)statbuf.st_rdev }, + { "size", (duk_double_t)statbuf.st_size }, + { "atimeMs", (duk_double_t)statbuf.st_atime * 1000 }, + { "mtimeMs", (duk_double_t)statbuf.st_mtime * 1000 }, + { "ctimeMs", (duk_double_t)statbuf.st_ctime * 1000 }, + { NULL, 0 } + }; + + duk_put_number_list(ctx, obj_idx, props); + return 1; +} + +duk_ret_t CScriptInstance::js_FSStat(duk_context* ctx) +{ + int nargs = duk_get_top(ctx); + + if (nargs < 1) + { + duk_pop_n(ctx, nargs); + duk_push_false(ctx); + return 1; + } + + const char* path = duk_get_string(ctx, 0); + + struct stat statbuf; + int res = stat(path, &statbuf); + + if (res != 0) + { + duk_pop_n(ctx, nargs); + duk_push_false(ctx); + return 1; + } + + duk_pop_n(ctx, nargs); + + duk_idx_t obj_idx = duk_push_object(ctx); + + const duk_number_list_entry props[] = { + { "dev", (duk_double_t)statbuf.st_dev }, + { "ino", (duk_double_t)statbuf.st_ino }, + { "mode", (duk_double_t)statbuf.st_mode }, + { "nlink", (duk_double_t)statbuf.st_nlink }, + { "uid", (duk_double_t)statbuf.st_uid }, + { "gid", (duk_double_t)statbuf.st_gid }, + { "rdev", (duk_double_t)statbuf.st_rdev }, + { "size", (duk_double_t)statbuf.st_size }, + { "atimeMs", (duk_double_t)statbuf.st_atime * 1000 }, + { "mtimeMs", (duk_double_t)statbuf.st_mtime * 1000 }, + { "ctimeMs", (duk_double_t)statbuf.st_ctime * 1000 }, + { NULL, 0 } + }; + + duk_put_number_list(ctx, obj_idx, props); + return 1; +} + +duk_ret_t CScriptInstance::js_FSMkDir(duk_context* ctx) +{ + int nargs = duk_get_top(ctx); + + if (nargs < 1) + { + duk_push_false(ctx); + return 1; + } + + const char* path = duk_get_string(ctx, 0); + + duk_pop_n(ctx, nargs); + + if (CreateDirectory(path, NULL)) + { + duk_push_true(ctx); + } + else + { + duk_push_false(ctx); + } + + return 1; +} + +duk_ret_t CScriptInstance::js_FSRmDir(duk_context* ctx) +{ + int nargs = duk_get_top(ctx); + + if (nargs < 1) + { + duk_push_false(ctx); + return 1; + } + + const char* path = duk_get_string(ctx, 0); + + duk_pop_n(ctx, nargs); + + if (RemoveDirectory(path)) + { + duk_push_true(ctx); + } + else + { + duk_push_false(ctx); + } + + return 1; +} + +duk_ret_t CScriptInstance::js_FSUnlink(duk_context* ctx) +{ + int nargs = duk_get_top(ctx); + + if (nargs < 1) + { + duk_push_false(ctx); + return 1; + } + + const char* path = duk_get_string(ctx, 0); + + duk_pop_n(ctx, nargs); + + if (!_unlink(path)) + { + duk_push_true(ctx); + } + else + { + duk_push_false(ctx); + } + + return 1; +} + +duk_ret_t CScriptInstance::js_FSReadDir(duk_context* ctx) +{ + int nargs = duk_get_top(ctx); + + if (nargs < 1) + { + duk_push_false(ctx); + return 1; + } + + const char* path = duk_get_string(ctx, 0); + + duk_pop_n(ctx, nargs); + + WIN32_FIND_DATA ffd; + HANDLE hFind = FindFirstFile(stdstr_f("%s%s", path, "\\*").c_str(), &ffd); + + if (hFind == INVALID_HANDLE_VALUE) + { + duk_push_false(ctx); + return 1; + } + + duk_idx_t arr_idx = duk_push_array(ctx); + int nfile = 0; + + do + { + char filename[MAX_PATH]; + strcpy(filename, ffd.cFileName); + + if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) + { + continue; + } + + duk_push_string(ctx, filename); + duk_put_prop_index(ctx, arr_idx, nfile); + nfile++; + + } while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + return 1; +} + //////////// static BOOL ConnectEx(SOCKET s, const SOCKADDR* name, int namelen, PVOID lpSendBuffer, diff --git a/Source/Project64/UserInterface/Debugger/ScriptInstance.h b/Source/Project64/UserInterface/Debugger/ScriptInstance.h index 99bb54352..48cd1eb44 100644 --- a/Source/Project64/UserInterface/Debugger/ScriptInstance.h +++ b/Source/Project64/UserInterface/Debugger/ScriptInstance.h @@ -53,6 +53,13 @@ class CScriptInstance EVENT_STATUS_ERROR } EVENT_STATUS; + // For synchronous file operations + typedef struct + { + FILE* fp; + int fd; + } FILE_FD; + public: CScriptInstance(CDebuggerUI* debugger); @@ -76,10 +83,12 @@ private: HANDLE m_hIOCompletionPort; CRITICAL_SECTION m_CriticalSection; - vector m_Files; + vector m_AsyncFiles; vector m_Listeners; UINT m_NextListenerId; + vector m_Files; + CDebuggerUI* m_Debugger; CScriptSystem* m_ScriptSystem; @@ -94,13 +103,14 @@ private: void SetState(INSTANCE_STATE state); void StateChanged(); + void CleanUp(); void QueueAPC(PAPCFUNC userProc, ULONG_PTR param = 0); - void AddFile(HANDLE fd, bool bSocket = false); - void CloseFile(HANDLE fd); - void CloseAllFiles(); - void RemoveFile(HANDLE fd); + void AddAsyncFile(HANDLE fd, bool bSocket = false); + void CloseAsyncFile(HANDLE fd); + void CloseAllAsyncFiles(); + void RemoveAsyncFile(HANDLE fd); HANDLE CreateSocket(); IOLISTENER* AddListener(HANDLE fd, IOEVENTTYPE evt, void* jsCallback, void* data = NULL, int dataLen = 0); @@ -111,6 +121,11 @@ private: static void CALLBACK EvalAsyncCallback(ULONG_PTR evalWait); + bool AddFile(const char* path, const char* mode, int* fd); // return fd + void CloseFile(int fd); + FILE* GetFilePointer(int fd); + void CloseAllFiles(); + const char* EvalFile(const char* jsPath); // Lookup list of CScriptInstance instances for static js_* functions @@ -159,6 +174,17 @@ private: static duk_ret_t js_ScreenPrint(duk_context*); // (x, y, text) + static duk_ret_t js_FSOpen(duk_context*); // (path, flags) ; returns fd + static duk_ret_t js_FSClose(duk_context*); // (fd) + static duk_ret_t js_FSWrite(duk_context*); // (fd, buffer[, offset[, length[, position]]]) + static duk_ret_t js_FSRead(duk_context*); // (fd, buffer, offset, length, position) + static duk_ret_t js_FSFStat(duk_context*); // (fd) + static duk_ret_t js_FSStat(duk_context*); // (path) + static duk_ret_t js_FSMkDir(duk_context*); // (path) + static duk_ret_t js_FSRmDir(duk_context*); // (path) + static duk_ret_t js_FSUnlink(duk_context*); // (path) + static duk_ret_t js_FSReadDir(duk_context*); // (path) + static constexpr duk_function_list_entry NativeFunctions[] = { { "addCallback", js_AddCallback, DUK_VARARGS }, @@ -201,6 +227,17 @@ private: { "showCommands", js_ShowCommands, DUK_VARARGS }, { "screenPrint", js_ScreenPrint, DUK_VARARGS }, + + { "fsOpen", js_FSOpen, DUK_VARARGS }, + { "fsClose", js_FSClose, DUK_VARARGS }, + { "fsWrite", js_FSWrite, DUK_VARARGS }, + { "fsRead", js_FSRead, DUK_VARARGS }, + { "fsFStat", js_FSFStat, DUK_VARARGS }, + { "fsStat", js_FSStat, DUK_VARARGS }, + { "fsUnlink", js_FSUnlink, DUK_VARARGS }, + { "fsMkDir", js_FSMkDir, DUK_VARARGS }, + { "fsRmDir", js_FSRmDir, DUK_VARARGS }, + { "fsReadDir", js_FSReadDir, DUK_VARARGS }, { NULL, NULL, 0 } }; }; \ No newline at end of file diff --git a/apidoc.htm b/apidoc.htm index 97aad2efb..18cd9863b 100644 --- a/apidoc.htm +++ b/apidoc.htm @@ -2,7 +2,8 @@ -Project64d JS API +Project64 JS API + -
Project64d Javascript API
+
Project64 Javascript API
+
+
fs
+
+
fs.open(path, mode)
+
+ Opens the file pointed to by path in the mode specified by mode. + Returns a file descriptor on success or false if the operation failed. +
var fd = fs.open('log.txt', 'w')
+ + Valid modes: + +
+r or rb           Open file for reading.
+w or wb           Truncate to zero length or create file for writing.
+a or ab           Append; open or create file for writing at end-of-file.
+r+ or rb+ or r+b  Open file for update (reading and writing).
+w+ or wb+ or w+b  Truncate to zero length or create file for update.
+a+ or ab+ or a+b  Append; open or create file for update, writing at end-of-file.
+
+http://pubs.opengroup.org/onlinepubs/009695399/functions/fopen.html
+
+
+
+
+
fs.close(fd)
+
+ Closes the file referenced by fd. +
+
+
+
fs.write(fd, buffer[, offset[, length[, position]]])
+
+ Writes buffer to the file referenced by fd. Returns the number of bytes written. +
var fd = fs.open('log.txt', 'w')
+fs.write(fd, 'Hello world!\n')
+fs.close(fd)
+
var buf = mem.getblock(0x8033B400, 4 * 32)
+var fd = fs.open('data.bin', 'wb')
+fs.write(fd, buf)
+fs.close(fd)
+
+
+
+
fs.writeFile(path, buffer)
+
+ Writes buffer to the file pointed to by path. + Returns true if the operation was successful or false if the operation failed. +
+
+
+
fs.read(fd, buffer, offset, length, position)
+
+ Reads the file referenced by fd into buffer. Returns the number of bytes read. +
var buf = new Buffer(128)
+var fd = fs.open('data.bin', 'rb')
+var nBytesRead = fs.read(fd, buf, 0, buf.length, 0)
+fs.close(fd)
+
+
+
+
fs.readFile(path)
+
+ Reads the file pointed to by path. Returns a Buffer object representing the file, or false if the operation failed. +
+
+
+
fs.fstat(fd)
+
+ Returns an fs.Stats object describing the file referenced by fd. +
var stats = fs.fstat(fd)
+console.log('size: ' + stats.size)
+
+
+
+
+
fs.stat(path)
+
+ Returns an fs.Stats object describing the file or directory pointed to by path. Returns false if the operation failed.. +
+
+
+
fs.unlink(path)
+
Deletes a file. Returns true if the operation was successful.
+
+
+
fs.mkdir(path)
+
Creates a directory. Returns true if the operation was successful.
+
+
+
fs.rmdir(path)
+
Removes a directory. The directory must be empty. Returns true if the operation was successful.
+
+
+
fs.readdir(path)
+
+ Returns an array of file names in the directory pointed to by path. + Returns false if the operation failed. +
+
+
+ +
+
fs.Stats
+
+ + + + + + + + + + + + + + + +
stats.dev ID of the device the file resides on
stats.ino inode number
stats.mode File permissions
stats.nlink Number of links to the file
stats.uid User ID
stats.gid Group ID
stats.rdev Device ID (if file is character or block special)
stats.size Size of the file in bytes
stats.atimeMs Last access timestamp in milliseconds
stats.mtimeMs Last modification timestamp in milliseconds
stats.ctimeMs Creation timestamp in milliseconds
stats.atime JS Date object representing the last access time
stats.mtime JS Date object representing the last modification time
stats.ctime JS Date object representing the creation time
+
+
+
stats.isDirectory()
+
+ Returns true if the fs.Stats object describes a directory. +
+
+
+
stats.isFile()
+
+ Returns true if the fs.Stats object describes a regular file. +
+
+
+
console
@@ -441,7 +579,7 @@ events.onexec(0x80000180, function()
 events.ondraw(function()
 {
-    screen.print(20, 20, "power: ' + mem.u8[0x8033B21E])
+    screen.print(20, 20, 'power: ' + mem.u8[0x8033B21E])
 })
@@ -646,7 +784,7 @@ server.on('connection', function(socket) Returns a hexadecimal string representation of the number object. The resulting string is prepended with zeroes until its character length meets nChars or 8 by default.
 var sm64EntryPC = rom.u32[0x00000008]
-console.log("Entry: " + sm64EntryPC.hex()) // "Entry: 80246000"
+console.log('Entry: ' + sm64EntryPC.hex()) // "Entry: 80246000"