tuxanci2-engine
Game engine for tuxanci2
Log | Files | Refs | README | LICENSE
commit 4990e297f0e3d8a2cd709f88148d08e1c56a9e3b
parent 9d4f5eb4f036b09a58c67535fe3209d25454df5e
Author: Connor Thomson <connor@gnufault.org>
Date: Sat, 24 Oct 2026 19:26:06 -0700
Add the tavm
Diffstat:
20 files changed, 1270 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1 @@
build
-tuxanci2*
-\ No newline at end of file
diff --git a/include/ta_api.h b/include/ta_api.h
@@ -10,6 +10,7 @@ extern "C" {
#include "ta_keyboard.h"
#include "ta_title.h"
#include "ta_types.h"
+#include "ta_fs.h"
// Must be called before ta_update()
void ta_main_init(void);
@@ -17,10 +18,15 @@ void ta_main_init(void);
// Must be called every frame after ta_init()
ta_bool ta_main_update(void);
-// Misc stuff that you need to implement/wrap
+// Memory allocation
void *ta_malloc(ta_size size);
void *ta_realloc(void *ptr, ta_size new_size);
void ta_free(void *ptr);
+
+// File system
+ta_fs_file ta_read_file(const char *path);
+void ta_close_file(ta_fs_file *file);
+
void ta_exit(int status);
#ifdef __cplusplus
diff --git a/include/ta_fs.h b/include/ta_fs.h
@@ -0,0 +1,11 @@
+#ifndef TA_FS_H
+#define TA_FS_H
+
+#include "ta_types.h"
+
+typedef struct {
+ char *data;
+ ta_size size;
+} ta_fs_file;
+
+#endif // TA_FS_H
diff --git a/include/ta_mod.h b/include/ta_mod.h
@@ -0,0 +1,30 @@
+#ifndef TA_MOD_H
+#define TA_MOD_H
+
+#include "ta_types.h"
+#include "ta_vm.h"
+
+#ifndef TA_MOD_RAM_SIZE
+#define TA_MOD_RAM_SIZE (256u * 1024u)
+#endif
+
+#ifndef TA_MOD_STEPS_PER_FRAME
+#define TA_MOD_STEPS_PER_FRAME 200000u
+#endif
+
+#define TA_SYS_EXIT 0
+#define TA_SYS_YIELD 1
+#define TA_SYS_CLEAR 2
+
+extern ta_bool ta_mod_running;
+
+ta_bool ta_mod_load(const ta_uint8 *binary, ta_size size);
+ta_bool ta_mod_load_file(const char *path);
+ta_bool ta_mod_update(void);
+ta_vm_status ta_mod_status(void);
+ta_int32 ta_mod_exit_code(void);
+const ta_vm *ta_mod_vm(void);
+
+void ta_mod_unload(void);
+
+#endif // TA_MOD_H
diff --git a/include/ta_vm.h b/include/ta_vm.h
@@ -0,0 +1,46 @@
+#ifndef TA_VM_H
+#define TA_VM_H
+
+#include "ta_types.h"
+
+typedef enum {
+ TA_VM_RUNNING,
+ TA_VM_HALTED,
+ TA_VM_FAULT_ILLEGAL,
+ TA_VM_FAULT_MEMORY,
+ TA_VM_FAULT_MISALIGNED,
+ TA_VM_FAULT_EBREAK
+} ta_vm_status;
+
+typedef struct ta_vm ta_vm;
+typedef ta_vm_status (*ta_vm_ecall)(ta_vm *vm, void *user);
+
+struct ta_vm {
+ ta_uint32 regs[32];
+ ta_uint32 pc;
+ ta_uint8 *mem;
+ ta_uint32 mem_size;
+ ta_vm_ecall ecall;
+ void *user;
+ ta_uint32 steps;
+};
+
+#define TA_VM_REG_RA 1
+#define TA_VM_REG_SP 2
+#define TA_VM_REG_A0 10
+#define TA_VM_REG_A1 11
+#define TA_VM_REG_A2 12
+#define TA_VM_REG_A3 13
+#define TA_VM_REG_A4 14
+#define TA_VM_REG_A5 15
+#define TA_VM_REG_A6 16
+#define TA_VM_REG_A7 17
+
+void ta_vm_init(ta_vm *vm, ta_uint8 *mem, ta_uint32 mem_size, ta_vm_ecall ecall, void *user);
+void ta_vm_reset(ta_vm *vm, ta_uint32 entry, ta_uint32 stack_top);
+ta_vm_status ta_vm_run(ta_vm *vm, ta_uint32 max_steps);
+ta_bool ta_vm_read8(const ta_vm *vm, ta_uint32 addr, ta_uint8 *out);
+ta_bool ta_vm_read32(const ta_vm *vm, ta_uint32 addr, ta_uint32 *out);
+ta_bool ta_vm_write32(ta_vm *vm, ta_uint32 addr, ta_uint32 value);
+
+#endif // TA_VM_H
diff --git a/platforms/aalib/aalib.c b/platforms/aalib/aalib.c
@@ -4,6 +4,7 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
+#include <stdio.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
@@ -182,6 +183,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/dos/dos.c b/platforms/dos/dos.c
@@ -202,6 +202,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/dreamcast/dreamcast.c b/platforms/dreamcast/dreamcast.c
@@ -5,6 +5,7 @@
#include <dc/pvr.h>
#include <stdint.h>
#include <stdlib.h>
+#include <stdio.h>
KOS_INIT_FLAGS(INIT_DEFAULT);
@@ -171,6 +172,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
(void)status;
diff --git a/platforms/n64/n64.c b/platforms/n64/n64.c
@@ -101,6 +101,26 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ (void)path;
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ file->data = NULL;
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/ps1/nugget b/platforms/ps1/nugget
@@ -0,0 +1 @@
+Subproject commit 58d0cd1c5aa80f3eb338c8b94f70946314c9af36
diff --git a/platforms/saturn/saturn.c b/platforms/saturn/saturn.c
@@ -209,6 +209,26 @@ void ta_free(void *ptr) {
}
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ (void)path;
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ file->data = NULL;
+ file->size = 0;
+}
+
void ta_exit(int status) {
(void)status;
diff --git a/platforms/sdl2/sdl2.c b/platforms/sdl2/sdl2.c
@@ -3,6 +3,7 @@
#include <SDL2/SDL.h>
#include <unistd.h>
#include <stdlib.h>
+#include <stdio.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
@@ -132,6 +133,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/sdl3/sdl3.c b/platforms/sdl3/sdl3.c
@@ -3,6 +3,7 @@
#include <SDL3/SDL.h>
#include <unistd.h>
#include <stdlib.h>
+#include <stdio.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
@@ -140,6 +141,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/wayland/wayland.c b/platforms/wayland/wayland.c
@@ -561,6 +561,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/win32/win32.c b/platforms/win32/win32.c
@@ -2,6 +2,7 @@
#include <windows.h>
#include <stdlib.h>
#include <stdbool.h>
+#include <stdio.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
@@ -201,6 +202,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/x11/x11.c b/platforms/x11/x11.c
@@ -1,6 +1,7 @@
#include "ta_api.h"
#include <unistd.h>
#include <stdlib.h>
+#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
@@ -198,6 +199,70 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size == -1) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc(file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size element_size = 1;
+ ta_size bytes_read = fread(file.data, element_size, file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/platforms/xbox/xbox.c b/platforms/xbox/xbox.c
@@ -4,6 +4,7 @@
#include <windows.h>
#include <stdint.h>
#include <stdlib.h>
+#include <stdio.h>
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
@@ -113,6 +114,69 @@ void ta_free(void *ptr) {
free(ptr);
}
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
void ta_exit(int status) {
exit(status);
}
diff --git a/src/ta_main.c b/src/ta_main.c
@@ -1,6 +1,7 @@
#include "ta_api.h"
#include "ta_files.h"
#include "ta_screen.h"
+#include "ta_mod.h"
void ta_main_init(void) {
if (!ta_display_init() || (!ta_display->sprite_mode && !ta_fb_init(ta_display))) {
@@ -10,6 +11,10 @@ void ta_main_init(void) {
}
ta_bool ta_main_update(void) {
+ if (ta_mod_running) {
+ ta_mod_update();
+ }
+
if (!ta_screen_update()) {
return ta_false;
}
diff --git a/src/ta_mod.c b/src/ta_mod.c
@@ -0,0 +1,114 @@
+#include "ta_mod.h"
+#include "ta_api.h"
+#include "ta_display.h"
+#include "ta_fb.h"
+
+static ta_vm vm;
+static ta_uint8 *ram;
+static ta_bool loaded;
+static ta_vm_status status;
+static ta_int32 exit_code;
+ta_bool ta_mod_running;
+
+static ta_vm_status mod_ecall(ta_vm *machine, void *user) {
+ (void)user;
+
+ ta_uint32 *r = machine->regs;
+ ta_uint32 *args = &r[TA_VM_REG_A0];
+
+ switch (r[TA_VM_REG_A7]) {
+ case TA_SYS_EXIT: {
+ exit_code = (ta_int32)args[0];
+
+ return TA_VM_HALTED;
+ }
+ case TA_SYS_CLEAR: {
+ if (ta_display->sprite_mode) {
+ ta_display_sprites_clear();
+ } else {
+ ta_fb_clear(ta_display);
+ }
+
+ r[TA_VM_REG_A0] = 0;
+
+ return TA_VM_RUNNING;
+ }
+ default: {
+ return TA_VM_FAULT_ILLEGAL;
+ }
+ }
+}
+
+ta_bool ta_mod_load(const ta_uint8 *binary, ta_size size) {
+ ta_mod_unload();
+
+ if (!binary || size == 0 || size > TA_MOD_RAM_SIZE) {
+ return ta_false;
+ }
+
+ ram = (ta_uint8 *)ta_malloc(TA_MOD_RAM_SIZE);
+
+ if (!ram) {
+ return ta_false;
+ }
+
+ for (ta_size i = 0; i < TA_MOD_RAM_SIZE; i++) {
+ ram[i] = i < size ? binary[i] : 0;
+ }
+
+ ta_vm_init(&vm, ram, TA_MOD_RAM_SIZE, mod_ecall, TA_NULL);
+ status = TA_VM_RUNNING;
+ exit_code = 0;
+ loaded = ta_true;
+ ta_mod_running = loaded && (status == TA_VM_RUNNING);
+
+ return ta_true;
+}
+
+ta_bool ta_mod_load_file(const char *path) {
+ ta_fs_file file = ta_read_file(path);
+
+ if (!file.data) {
+ return ta_false;
+ }
+
+ ta_bool ok = ta_mod_load((const ta_uint8 *)file.data, file.size);
+
+ ta_close_file(&file);
+
+ return ok;
+}
+
+ta_bool ta_mod_update(void) {
+ if (!loaded || status != TA_VM_RUNNING) {
+ return ta_false;
+ }
+
+ status = ta_vm_run(&vm, TA_MOD_STEPS_PER_FRAME);
+ ta_mod_running = loaded && (status == TA_VM_RUNNING);
+
+ return ta_mod_running;
+}
+
+ta_vm_status ta_mod_status(void) {
+ return status;
+}
+
+ta_int32 ta_mod_exit_code(void) {
+ return exit_code;
+}
+
+const ta_vm *ta_mod_vm(void) {
+ return &vm;
+}
+
+void ta_mod_unload(void) {
+ if (ram) {
+ ta_free(ram);
+ ram = TA_NULL;
+ }
+
+ loaded = ta_false;
+ status = TA_VM_HALTED;
+ ta_mod_running = ta_false;
+}
diff --git a/src/ta_vm.c b/src/ta_vm.c
@@ -0,0 +1,441 @@
+#include "ta_vm.h"
+
+#define OP_LOAD 0x03
+#define OP_MISC_MEM 0x0F
+#define OP_OP_IMM 0x13
+#define OP_AUIPC 0x17
+#define OP_STORE 0x23
+#define OP_OP 0x33
+#define OP_LUI 0x37
+#define OP_BRANCH 0x63
+#define OP_JALR 0x67
+#define OP_JAL 0x6F
+#define OP_SYSTEM 0x73
+
+static ta_int32 sign_extend(ta_uint32 value, ta_uint32 bits) {
+ ta_uint32 mask = (ta_uint32)1 << (bits - 1);
+
+ return (ta_int32)((value ^ mask) - mask);
+}
+
+static ta_uint32 shift_right_arith(ta_uint32 value, ta_uint32 amount) {
+ ta_uint32 result = value >> amount;
+
+ if (amount != 0 && (value & 0x80000000u) != 0) {
+ result |= ~(0xFFFFFFFFu >> amount);
+ }
+
+ return result;
+}
+
+static ta_uint32 mul_high_unsigned(ta_uint32 a, ta_uint32 b) {
+ ta_uint32 a_lo = a & 0xFFFFu;
+ ta_uint32 a_hi = a >> 16;
+ ta_uint32 b_lo = b & 0xFFFFu;
+ ta_uint32 b_hi = b >> 16;
+ ta_uint32 lo_lo = a_lo * b_lo;
+ ta_uint32 hi_lo = a_hi * b_lo;
+ ta_uint32 lo_hi = a_lo * b_hi;
+ ta_uint32 hi_hi = a_hi * b_hi;
+ ta_uint32 cross = (lo_lo >> 16) + (hi_lo & 0xFFFFu) + lo_hi;
+
+ return hi_hi + (hi_lo >> 16) + (cross >> 16);
+}
+
+static ta_uint32 mul_high_signed(ta_uint32 a, ta_uint32 b) {
+ ta_uint32 result = mul_high_unsigned(a, b);
+
+ if (a & 0x80000000u) {
+ result -= b;
+ }
+
+ if (b & 0x80000000u) {
+ result -= a;
+ }
+
+ return result;
+}
+
+static ta_uint32 mul_high_signed_unsigned(ta_uint32 a, ta_uint32 b) {
+ ta_uint32 result = mul_high_unsigned(a, b);
+
+ if (a & 0x80000000u) {
+ result -= b;
+ }
+
+ return result;
+}
+
+static ta_uint32 div_signed(ta_uint32 a, ta_uint32 b) {
+ if (b == 0) {
+ return 0xFFFFFFFFu;
+ }
+
+ if (a == 0x80000000u && b == 0xFFFFFFFFu) {
+ return a;
+ }
+
+ return (ta_uint32)((ta_int32)a / (ta_int32)b);
+}
+
+static ta_uint32 rem_signed(ta_uint32 a, ta_uint32 b) {
+ if (b == 0) {
+ return a;
+ }
+
+ if (a == 0x80000000u && b == 0xFFFFFFFFu) {
+ return 0;
+ }
+
+ return (ta_uint32)((ta_int32)a % (ta_int32)b);
+}
+
+static ta_bool in_bounds(const ta_vm *vm, ta_uint32 addr, ta_uint32 size) {
+ return size <= vm->mem_size && addr <= vm->mem_size - size;
+}
+
+ta_bool ta_vm_read8(const ta_vm *vm, ta_uint32 addr, ta_uint8 *out) {
+ if (!in_bounds(vm, addr, 1)) {
+ return ta_false;
+ }
+
+ *out = vm->mem[addr];
+
+ return ta_true;
+}
+
+ta_bool ta_vm_read32(const ta_vm *vm, ta_uint32 addr, ta_uint32 *out) {
+ if (!in_bounds(vm, addr, 4)) {
+ return ta_false;
+ }
+
+ const ta_uint8 *p = vm->mem + addr;
+
+ *out = (ta_uint32)p[0] | ((ta_uint32)p[1] << 8) | ((ta_uint32)p[2] << 16) | ((ta_uint32)p[3] << 24);
+
+ return ta_true;
+}
+
+ta_bool ta_vm_write32(ta_vm *vm, ta_uint32 addr, ta_uint32 value) {
+ if (!in_bounds(vm, addr, 4)) {
+ return ta_false;
+ }
+
+ ta_uint8 *p = vm->mem + addr;
+
+ p[0] = (ta_uint8)value;
+ p[1] = (ta_uint8)(value >> 8);
+ p[2] = (ta_uint8)(value >> 16);
+ p[3] = (ta_uint8)(value >> 24);
+
+ return ta_true;
+}
+
+static ta_bool load_value(const ta_vm *vm, ta_uint32 addr, ta_uint32 size, ta_uint32 *out) {
+ if (!in_bounds(vm, addr, size)) {
+ return ta_false;
+ }
+
+ const ta_uint8 *p = vm->mem + addr;
+ ta_uint32 value = 0;
+
+ for (ta_uint32 i = 0; i < size; i++) {
+ value |= (ta_uint32)p[i] << (i * 8);
+ }
+
+ *out = value;
+
+ return ta_true;
+}
+
+static ta_bool store_value(ta_vm *vm, ta_uint32 addr, ta_uint32 size, ta_uint32 value) {
+ if (!in_bounds(vm, addr, size)) {
+ return ta_false;
+ }
+
+ ta_uint8 *p = vm->mem + addr;
+
+ for (ta_uint32 i = 0; i < size; i++) {
+ p[i] = (ta_uint8)(value >> (i * 8));
+ }
+
+ return ta_true;
+}
+
+void ta_vm_init(ta_vm *vm, ta_uint8 *mem, ta_uint32 mem_size, ta_vm_ecall ecall, void *user) {
+ vm->mem = mem;
+ vm->mem_size = mem_size;
+ vm->ecall = ecall;
+ vm->user = user;
+ ta_vm_reset(vm, 0, mem_size);
+}
+
+void ta_vm_reset(ta_vm *vm, ta_uint32 entry, ta_uint32 stack_top) {
+ for (ta_uint32 i = 0; i < 32; i++) {
+ vm->regs[i] = 0;
+ }
+
+ vm->regs[TA_VM_REG_SP] = stack_top & ~(ta_uint32)15;
+ vm->pc = entry;
+ vm->steps = 0;
+}
+
+ta_vm_status ta_vm_run(ta_vm *vm, ta_uint32 max_steps) {
+ ta_uint32 *x = vm->regs;
+ ta_uint32 pc = vm->pc;
+ ta_uint32 steps = 0;
+ ta_vm_status status = TA_VM_RUNNING;
+
+ while (steps < max_steps) {
+ ta_uint32 inst;
+
+ if ((pc & 3) != 0) {
+ status = TA_VM_FAULT_MISALIGNED;
+ break;
+ }
+
+ if (!ta_vm_read32(vm, pc, &inst)) {
+ status = TA_VM_FAULT_MEMORY;
+ break;
+ }
+
+ ta_uint32 opcode = inst & 0x7F;
+ ta_uint32 rd = (inst >> 7) & 31;
+ ta_uint32 funct3 = (inst >> 12) & 7;
+ ta_uint32 rs1 = (inst >> 15) & 31;
+ ta_uint32 rs2 = (inst >> 20) & 31;
+ ta_uint32 funct7 = inst >> 25;
+ ta_uint32 a = x[rs1];
+ ta_uint32 b = x[rs2];
+ ta_uint32 next = pc + 4;
+ ta_uint32 result = 0;
+ ta_bool write = ta_false;
+
+ switch (opcode) {
+ case OP_LUI: {
+ result = inst & 0xFFFFF000u;
+ write = ta_true;
+ break;
+ }
+ case OP_AUIPC: {
+ result = pc + (inst & 0xFFFFF000u);
+ write = ta_true;
+ break;
+ }
+ case OP_JAL: {
+ ta_uint32 imm = ((inst >> 31) << 20) | (((inst >> 12) & 0xFF) << 12) | (((inst >> 20) & 1) << 11) | (((inst >> 21) & 0x3FF) << 1);
+
+ result = next;
+ write = ta_true;
+ next = pc + (ta_uint32)sign_extend(imm, 21);
+ break;
+ }
+ case OP_JALR: {
+ if (funct3 != 0) {
+ status = TA_VM_FAULT_ILLEGAL;
+ break;
+ }
+
+ result = next;
+ write = ta_true;
+ next = (a + (ta_uint32)sign_extend(inst >> 20, 12)) & ~(ta_uint32)1;
+ break;
+ }
+ case OP_BRANCH: {
+ ta_uint32 imm = ((inst >> 31) << 12) | (((inst >> 7) & 1) << 11) | (((inst >> 25) & 0x3F) << 5) | (((inst >> 8) & 0xF) << 1);
+ ta_bool taken = ta_false;
+
+ switch (funct3) {
+ case 0: taken = a == b; break;
+ case 1: taken = a != b; break;
+ case 4: taken = (ta_int32)a < (ta_int32)b; break;
+ case 5: taken = (ta_int32)a >= (ta_int32)b; break;
+ case 6: taken = a < b; break;
+ case 7: taken = a >= b; break;
+ default: status = TA_VM_FAULT_ILLEGAL; break;
+ }
+
+ if (taken) {
+ next = pc + (ta_uint32)sign_extend(imm, 13);
+ }
+
+ break;
+ }
+ case OP_LOAD: {
+ ta_uint32 addr = a + (ta_uint32)sign_extend(inst >> 20, 12);
+ ta_uint32 value = 0;
+ ta_uint32 size = 0;
+
+ switch (funct3) {
+ case 0: case 4: size = 1; break;
+ case 1: case 5: size = 2; break;
+ case 2: size = 4; break;
+ default: status = TA_VM_FAULT_ILLEGAL; break;
+ }
+
+ if (status != TA_VM_RUNNING) {
+ break;
+ }
+
+ if (!load_value(vm, addr, size, &value)) {
+ status = TA_VM_FAULT_MEMORY;
+ break;
+ }
+
+ if (funct3 == 0) {
+ value = (ta_uint32)sign_extend(value, 8);
+ } else if (funct3 == 1) {
+ value = (ta_uint32)sign_extend(value, 16);
+ }
+
+ result = value;
+ write = ta_true;
+ break;
+ }
+ case OP_STORE: {
+ ta_uint32 imm = ((inst >> 25) << 5) | ((inst >> 7) & 31);
+ ta_uint32 addr = a + (ta_uint32)sign_extend(imm, 12);
+ ta_uint32 size = 0;
+
+ switch (funct3) {
+ case 0: size = 1; break;
+ case 1: size = 2; break;
+ case 2: size = 4; break;
+ default: status = TA_VM_FAULT_ILLEGAL; break;
+ }
+
+ if (status != TA_VM_RUNNING) {
+ break;
+ }
+
+ if (!store_value(vm, addr, size, b)) {
+ status = TA_VM_FAULT_MEMORY;
+ }
+
+ break;
+ }
+ case OP_OP_IMM: {
+ ta_uint32 imm = (ta_uint32)sign_extend(inst >> 20, 12);
+ ta_uint32 shamt = rs2;
+
+ switch (funct3) {
+ case 0: result = a + imm; break;
+ case 2: result = (ta_int32)a < (ta_int32)imm; break;
+ case 3: result = a < imm; break;
+ case 4: result = a ^ imm; break;
+ case 6: result = a | imm; break;
+ case 7: result = a & imm; break;
+ case 1: {
+ if (funct7 != 0x00) {
+ status = TA_VM_FAULT_ILLEGAL;
+ }
+
+ result = a << shamt;
+ break;
+ }
+ default: {
+ if (funct7 == 0x00) {
+ result = a >> shamt;
+ } else if (funct7 == 0x20) {
+ result = shift_right_arith(a, shamt);
+ } else {
+ status = TA_VM_FAULT_ILLEGAL;
+ }
+
+ break;
+ }
+ }
+
+ write = ta_true;
+ break;
+ }
+ case OP_OP: {
+ if (funct7 == 0x01) {
+ switch (funct3) {
+ case 0: result = a * b; break;
+ case 1: result = mul_high_signed(a, b); break;
+ case 2: result = mul_high_signed_unsigned(a, b); break;
+ case 3: result = mul_high_unsigned(a, b); break;
+ case 4: result = div_signed(a, b); break;
+ case 5: result = b == 0 ? 0xFFFFFFFFu : a / b; break;
+ case 6: result = rem_signed(a, b); break;
+ default: result = b == 0 ? a : a % b; break;
+ }
+ } else if (funct7 == 0x00 || funct7 == 0x20) {
+ ta_bool alt = funct7 == 0x20;
+
+ if (alt && funct3 != 0 && funct3 != 5) {
+ status = TA_VM_FAULT_ILLEGAL;
+ break;
+ }
+
+ switch (funct3) {
+ case 0: result = alt ? a - b : a + b; break;
+ case 1: result = a << (b & 31); break;
+ case 2: result = (ta_int32)a < (ta_int32)b; break;
+ case 3: result = a < b; break;
+ case 4: result = a ^ b; break;
+ case 5: result = alt ? shift_right_arith(a, b & 31) : a >> (b & 31); break;
+ case 6: result = a | b; break;
+ default: result = a & b; break;
+ }
+ } else {
+ status = TA_VM_FAULT_ILLEGAL;
+ break;
+ }
+
+ write = ta_true;
+ break;
+ }
+ case OP_MISC_MEM: {
+ break;
+ }
+ case OP_SYSTEM: {
+ ta_uint32 imm = inst >> 20;
+
+ if (funct3 != 0 || rd != 0 || rs1 != 0) {
+ status = TA_VM_FAULT_ILLEGAL;
+ } else if (imm == 0) {
+ vm->pc = pc;
+
+ if (vm->ecall) {
+ status = vm->ecall(vm, vm->user);
+ } else {
+ status = TA_VM_FAULT_ILLEGAL;
+ }
+ } else if (imm == 1) {
+ status = TA_VM_FAULT_EBREAK;
+ } else {
+ status = TA_VM_FAULT_ILLEGAL;
+ }
+
+ break;
+ }
+ default: {
+ status = TA_VM_FAULT_ILLEGAL;
+ break;
+ }
+ }
+
+ if (status != TA_VM_RUNNING && status != TA_VM_HALTED) {
+ break;
+ }
+
+ if (write && rd != 0) {
+ x[rd] = result;
+ }
+
+ x[0] = 0;
+ pc = next;
+ steps++;
+
+ if (status != TA_VM_RUNNING) {
+ break;
+ }
+ }
+
+ vm->pc = pc;
+ vm->steps = steps;
+
+ return status;
+}