:: commit c7a46830f69668ee915572ea84eec53ba3f1d96f

mintsuki <mintsuki@protonmail.com> — 2021-08-16 16:02

parents: 993a602afb

term: Mass backport changes done in Vinix upstream

diff --git a/stage23/Makefile b/stage23/Makefile
index 02c936dc..95114a49 100644
--- a/stage23/Makefile
+++ b/stage23/Makefile
@@ -61,6 +61,7 @@ INTERNAL_CFLAGS := \
 	-fno-omit-frame-pointer \
 	-fno-lto \
 	-fno-pic \
+	-Wno-stringop-overflow \
 	-Wno-address-of-packed-member \
 	-Wshadow \
 	-mno-80387 \
diff --git a/stage23/drivers/vga_textmode.h b/stage23/drivers/vga_textmode.h
index 41ef5c18..8ad55d64 100644
--- a/stage23/drivers/vga_textmode.h
+++ b/stage23/drivers/vga_textmode.h
@@ -2,24 +2,33 @@
 #define __DRIVERS__VGA_TEXTMODE_H__
 
 #include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
 
-void init_vga_textmode(int *rows, int *cols, bool managed);
+void init_vga_textmode(size_t *rows, size_t *cols, bool managed);
 
 void text_putchar(uint8_t c);
 void text_clear(bool move);
 void text_enable_cursor(void);
 bool text_disable_cursor(void);
-void text_set_cursor_pos(int x, int y);
-void text_get_cursor_pos(int *x, int *y);
-void text_set_text_fg(int fg);
-void text_set_text_bg(int bg);
+void text_set_cursor_pos(size_t x, size_t y);
+void text_get_cursor_pos(size_t *x, size_t *y);
+void text_set_text_fg(size_t fg);
+void text_set_text_bg(size_t bg);
+void text_set_text_fg_bright(size_t fg);
 void text_set_text_fg_default(void);
 void text_set_text_bg_default(void);
 bool text_scroll_disable(void);
 void text_scroll_enable(void);
-void text_move_character(int new_x, int new_y, int old_x, int old_y);
+void text_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
+void text_scroll(void);
+void text_swap_palette(void);
 
 void text_double_buffer(bool state);
 void text_double_buffer_flush(void);
 
+uint64_t text_context_size(void);
+void text_context_save(uint64_t ptr);
+void text_context_restore(uint64_t ptr);
+
 #endif
diff --git a/stage23/drivers/vga_textmode.s2.c b/stage23/drivers/vga_textmode.s2.c
index 61784eda..2ae68322 100644
--- a/stage23/drivers/vga_textmode.s2.c
+++ b/stage23/drivers/vga_textmode.s2.c
@@ -7,6 +7,7 @@
 #include <sys/cpu.h>
 #include <lib/real.h>
 #include <lib/libc.h>
+#include <lib/blib.h>
 #include <lib/term.h>
 #include <mm/pmm.h>
 
@@ -14,16 +15,25 @@
 #define VD_COLS (80 * 2)
 #define VD_ROWS 25
 
-static uint8_t *back_buffer = NULL;
-static uint8_t *front_buffer = NULL;
 static uint8_t *video_mem = (uint8_t *)0xb8000;
 
-static uint8_t *current_buffer;
+static uint8_t *back_buffer = NULL;
+static uint8_t *front_buffer = NULL;
 
-static size_t cursor_offset;
-static int cursor_status;
-static uint8_t text_palette;
-static uint8_t cursor_palette;
+static struct context {
+    uint8_t *current_buffer;
+#define current_buffer context.current_buffer
+    size_t cursor_offset;
+#define cursor_offset context.cursor_offset
+    bool cursor_status;
+#define cursor_status context.cursor_status
+    uint8_t text_palette;
+#define text_palette context.text_palette
+    uint8_t cursor_palette;
+#define cursor_palette context.cursor_palette
+    bool scroll_enabled;
+#define scroll_enabled context.scroll_enabled
+} context;
 
 static void clear_cursor(void) {
     if (cursor_status) {
@@ -37,7 +47,9 @@ static void draw_cursor(void) {
     }
 }
 
-static bool scroll_enabled = true;
+void text_swap_palette(void) {
+    text_palette = (text_palette << 4) | (text_palette >> 4);
+}
 
 bool text_scroll_disable(void) {
     bool ret = scroll_enabled;
@@ -49,20 +61,22 @@ void text_scroll_enable(void) {
     scroll_enabled = true;
 }
 
-static void scroll(void) {
+void text_scroll(void) {
     // move the text up by one row
-    for (size_t i = 0; i <= VIDEO_BOTTOM - VD_COLS; i++) {
+    for (size_t i = term_context.scroll_top_margin * VD_COLS;
+         i < (term_context.scroll_bottom_margin - 1) * VD_COLS; i++) {
         current_buffer[i] = current_buffer[i + VD_COLS];
         if (current_buffer == front_buffer)
             video_mem[i] = current_buffer[i + VD_COLS];
     }
     // clear the last line of the screen
-    for (size_t i = VIDEO_BOTTOM; i > VIDEO_BOTTOM - VD_COLS; i -= 2) {
-        current_buffer[i] = text_palette;
-        current_buffer[i - 1] = ' ';
+    for (size_t i = (term_context.scroll_bottom_margin - 1) * VD_COLS;
+         i < term_context.scroll_bottom_margin * VD_COLS; i += 2) {
+        current_buffer[i] = ' ';
+        current_buffer[i + 1] = text_palette;
         if (current_buffer == front_buffer) {
-            video_mem[i] = text_palette;
-            video_mem[i - 1] = ' ';
+            video_mem[i] = ' ';
+            video_mem[i + 1] = text_palette;
         }
     }
 }
@@ -84,19 +98,55 @@ void text_clear(bool move) {
 }
 
 void text_enable_cursor(void) {
-    cursor_status = 1;
+    cursor_status = true;
     draw_cursor();
     return;
 }
 
 bool text_disable_cursor(void) {
-    bool ret = cursor_status != 0;
+    bool ret = cursor_status;
     clear_cursor();
-    cursor_status = 0;
+    cursor_status = false;
     return ret;
 }
 
-void init_vga_textmode(int *_rows, int *_cols, bool managed) {
+uint64_t text_context_size(void) {
+    uint64_t ret = 0;
+
+    ret += sizeof(struct context);
+    ret += VD_ROWS * VD_COLS; // back buffer
+    ret += VD_ROWS * VD_COLS; // front buffer
+
+    return ret;
+}
+
+void text_context_save(uint64_t ptr) {
+    memcpy32to64(ptr, (uint64_t)(uintptr_t)&context, sizeof(struct context));
+    ptr += sizeof(struct context);
+
+    memcpy32to64(ptr, (uint64_t)(uintptr_t)back_buffer, VD_ROWS * VD_COLS);
+    ptr += VD_ROWS * VD_COLS;
+
+    memcpy32to64(ptr, (uint64_t)(uintptr_t)front_buffer, VD_ROWS * VD_COLS);
+}
+
+void text_context_restore(uint64_t ptr) {
+    memcpy32to64((uint64_t)(uintptr_t)&context, ptr, sizeof(struct context));
+    ptr += sizeof(struct context);
+
+    memcpy32to64((uint64_t)(uintptr_t)back_buffer, ptr, VD_ROWS * VD_COLS);
+    ptr += VD_ROWS * VD_COLS;
+
+    memcpy32to64((uint64_t)(uintptr_t)front_buffer, ptr, VD_ROWS * VD_COLS);
+
+    for (size_t i = 0; i < VD_ROWS * VD_COLS; i++) {
+        video_mem[i] = current_buffer[i];
+    }
+
+    draw_cursor();
+}
+
+void init_vga_textmode(size_t *_rows, size_t *_cols, bool managed) {
     if (current_video_mode != -1) {
         struct rm_regs r = {0};
         r.eax = 0x0003;
@@ -105,15 +155,17 @@ void init_vga_textmode(int *_rows, int *_cols, bool managed) {
         current_video_mode = -1;
     }
 
-    back_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
-    front_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
+    if (back_buffer == NULL)
+        back_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
+    if (front_buffer == NULL)
+        front_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
 
+    current_buffer = front_buffer;
     cursor_offset = 0;
-    cursor_status = 1;
+    cursor_status = true;
     text_palette = 0x07;
     cursor_palette = 0x70;
-
-    text_double_buffer(false);
+    scroll_enabled = true;
 
     text_clear(false);
 
@@ -165,48 +217,49 @@ void text_double_buffer_flush(void) {
     draw_cursor();
 }
 
-static int text_get_cursor_pos_y(void) {
-    return cursor_offset / VD_COLS;
-}
-
-void text_get_cursor_pos(int *x, int *y) {
+void text_get_cursor_pos(size_t *x, size_t *y) {
     *x = (cursor_offset % VD_COLS) / 2;
     *y = cursor_offset / VD_COLS;
 }
 
-void text_move_character(int new_x, int new_y, int old_x, int old_y) {
+void text_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y) {
+    if (old_x >= VD_COLS / 2 || old_y >= VD_ROWS
+     || new_x >= VD_COLS / 2 || new_y >= VD_ROWS) {
+        return;
+    }
+
     current_buffer[new_y * VD_COLS + new_x * 2] = current_buffer[old_y * VD_COLS + old_x * 2];
     if (current_buffer == front_buffer) {
         video_mem[new_y * VD_COLS + new_x * 2] = current_buffer[old_y * VD_COLS + old_x * 2];
     }
 }
 
-void text_set_cursor_pos(int x, int y) {
+void text_set_cursor_pos(size_t x, size_t y) {
     clear_cursor();
-    if (x < 0) {
-        x = 0;
-    } else if (x >= VD_COLS / 2) {
+    if (x >= VD_COLS / 2) {
         x = VD_COLS / 2 - 1;
     }
-    if (y < 0) {
-        y = 0;
-    } else if (y >= VD_ROWS) {
+    if (y >= VD_ROWS) {
         y = VD_ROWS - 1;
     }
     cursor_offset = y * VD_COLS + x * 2;
     draw_cursor();
 }
 
-static uint8_t ansi_colours[] = { 0, 4, 2, 0x0e, 1, 5, 3, 7 };
+static uint8_t ansi_colours[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
 
-void text_set_text_fg(int fg) {
+void text_set_text_fg(size_t fg) {
     text_palette = (text_palette & 0xf0) | ansi_colours[fg];
 }
 
-void text_set_text_bg(int bg) {
+void text_set_text_bg(size_t bg) {
     text_palette = (text_palette & 0x0f) | (ansi_colours[bg] << 4);
 }
 
+void text_set_text_fg_bright(size_t fg) {
+    text_palette = (text_palette & 0xf0) | (ansi_colours[fg] | (1 << 3));
+}
+
 void text_set_text_fg_default(void) {
     text_palette = (text_palette & 0xf0) | 7;
 }
@@ -216,46 +269,25 @@ void text_set_text_bg_default(void) {
 }
 
 void text_putchar(uint8_t c) {
-    switch (c) {
-        case '\b':
-            if (cursor_offset) {
-                clear_cursor();
-                cursor_offset -= 2;
-                draw_cursor();
-            }
-            break;
-        case '\r':
-            text_set_cursor_pos(0, text_get_cursor_pos_y());
-            break;
-        case '\n':
-            if (text_get_cursor_pos_y() == (VD_ROWS - 1)) {
-                if (scroll_enabled) {
-                    clear_cursor();
-                    scroll();
-                    text_set_cursor_pos(0, (VD_ROWS - 1));
-                }
-            } else {
-                text_set_cursor_pos(0, (text_get_cursor_pos_y() + 1));
-            }
-            break;
-        default:
-            clear_cursor();
-            current_buffer[cursor_offset] = c;
-            current_buffer[cursor_offset+1] = text_palette;
-            if (current_buffer == front_buffer) {
-                video_mem[cursor_offset] = c;
-                video_mem[cursor_offset+1] = text_palette;
-            }
-            if (cursor_offset >= (VIDEO_BOTTOM - 1)) {
-                if (scroll_enabled) {
-                    scroll();
-                    cursor_offset = VIDEO_BOTTOM - (VD_COLS - 1);
-                }
-            } else {
-                cursor_offset += 2;
-            }
-            draw_cursor();
+    clear_cursor();
+    current_buffer[cursor_offset] = c;
+    current_buffer[cursor_offset + 1] = text_palette;
+    if (current_buffer == front_buffer) {
+        video_mem[cursor_offset] = c;
+        video_mem[cursor_offset + 1] = text_palette;
     }
+    if (cursor_offset / VD_COLS == term_context.scroll_bottom_margin - 1
+     && cursor_offset % VD_COLS == VD_COLS - 2) {
+        if (scroll_enabled) {
+            text_scroll();
+            cursor_offset -= cursor_offset % VD_COLS;
+        }
+    } else if (cursor_offset >= (VIDEO_BOTTOM - 1)) {
+        cursor_offset -= cursor_offset % VD_COLS;
+    } else {
+        cursor_offset += 2;
+    }
+    draw_cursor();
 }
 
 #endif
diff --git a/stage23/lib/blib.c b/stage23/lib/blib.c
index 97bbda42..44745ecf 100644
--- a/stage23/lib/blib.c
+++ b/stage23/lib/blib.c
@@ -20,13 +20,13 @@ UINTN efi_mmap_size = 0, efi_desc_size = 0, efi_desc_ver = 0;
 
 bool verbose = false;
 
-bool parse_resolution(int *width, int *height, int *bpp, const char *buf) {
-    int res[3] = {0};
+bool parse_resolution(size_t *width, size_t *height, size_t *bpp, const char *buf) {
+    size_t res[3] = {0};
 
     const char *first = buf;
-    for (int i = 0; i < 3; i++) {
+    for (size_t i = 0; i < 3; i++) {
         const char *last;
-        int x = strtoui(first, &last, 10);
+        size_t x = strtoui(first, &last, 10);
         if (first == last)
             break;
         res[i] = x;
diff --git a/stage23/lib/blib.h b/stage23/lib/blib.h
index da7de349..d9ce48ae 100644
--- a/stage23/lib/blib.h
+++ b/stage23/lib/blib.h
@@ -6,6 +6,7 @@
 #include <stdbool.h>
 #include <fs/file.h>
 #include <lib/part.h>
+#include <lib/libc.h>
 #if uefi == 1
 #  include <efi.h>
 #endif
@@ -30,7 +31,7 @@ extern bool stage3_loaded;
 
 extern bool verbose;
 
-bool parse_resolution(int *width, int *height, int *bpp, const char *buf);
+bool parse_resolution(size_t *width, size_t *height, size_t *bpp, const char *buf);
 
 uint64_t sqrt(uint64_t a_nInput);
 size_t get_trailing_zeros(uint64_t val);
@@ -44,6 +45,12 @@ int pit_sleep_and_quit_on_keypress(int seconds);
 
 uint64_t strtoui(const char *s, const char **end, int base);
 
+#if defined (__i386__)
+void memcpy32to64(uint64_t, uint64_t, uint64_t);
+#elif defined (__x86_64__)
+#  define memcpy32to64(X, Y, Z) memcpy((void *)(uintptr_t)(X), (void *)(uintptr_t)(Y), Z)
+#endif
+
 #define DIV_ROUNDUP(a, b) (((a) + ((b) - 1)) / (b))
 
 #define ALIGN_UP(x, a) ({ \
diff --git a/stage23/lib/gterm.c b/stage23/lib/gterm.c
index 56ea1e27..d11bc6b8 100644
--- a/stage23/lib/gterm.c
+++ b/stage23/lib/gterm.c
@@ -31,7 +31,7 @@ static uint32_t ansi_colours[8];
 static uint32_t ansi_bright_colours[8];
 static uint32_t default_fg, default_bg;
 
-static int frame_height, frame_width;
+static size_t frame_height, frame_width;
 
 static struct image *background;
 
@@ -49,12 +49,22 @@ static uint32_t text_fg, text_bg;
 
 static bool cursor_status = true;
 
-static int cursor_x;
-static int cursor_y;
+static size_t cursor_x;
+static size_t cursor_y;
 
-static int rows;
-static int cols;
-static int margin_gradient;
+static size_t rows;
+static size_t cols;
+static size_t margin_gradient;
+
+void gterm_swap_palette(void) {
+    uint32_t tmp = text_bg;
+    text_bg = text_fg;
+    if (tmp == 0xffffffff) {
+        text_fg = default_bg;
+    } else {
+        text_fg = tmp;
+    }
+}
 
 #define A(rgb) (uint8_t)(rgb >> 24)
 #define R(rgb) (uint8_t)(rgb >> 16)
@@ -73,14 +83,14 @@ static inline uint32_t colour_blend(uint32_t fg, uint32_t bg) {
     return ARGB(0, r, g, b);
 }
 
-void gterm_plot_px(int x, int y, uint32_t hex) {
+void gterm_plot_px(size_t x, size_t y, uint32_t hex) {
     size_t fb_i = x + (gterm_pitch / sizeof(uint32_t)) * y;
 
     gterm_framebuffer[fb_i] = hex;
 }
 
-static uint32_t blend_gradient_from_box(int x, int y, uint32_t bg_px, uint32_t hex) {
-    int distance, x_distance, y_distance;
+static uint32_t blend_gradient_from_box(size_t x, size_t y, uint32_t bg_px, uint32_t hex) {
+    size_t distance, x_distance, y_distance;
 
     if (x < frame_width)
         x_distance = frame_width - x;
@@ -110,22 +120,22 @@ static uint32_t blend_gradient_from_box(int x, int y, uint32_t bg_px, uint32_t h
     return colour_blend((hex & 0xffffff) | (new_alpha << 24), bg_px);
 }
 
-typedef int fixedp6; // the last 6 bits are the fixed point part
-static int fixedp6_to_int(fixedp6 value) { return value / 64; }
-static fixedp6 int_to_fixedp6(int value) { return value * 64; }
+typedef size_t fixedp6; // the last 6 bits are the fixed point part
+static size_t fixedp6_to_int(fixedp6 value) { return value / 64; }
+static fixedp6 int_to_fixedp6(size_t value) { return value * 64; }
 
 // Draw rect at coordinates, copying from the image to the fb and canvas, applying fn on every pixel
-__attribute__((always_inline)) static inline void genloop(int xstart, int xend, int ystart, int yend, uint32_t (*blend)(int x, int y, uint32_t orig)) {
+__attribute__((always_inline)) static inline void genloop(size_t xstart, size_t xend, size_t ystart, size_t yend, uint32_t (*blend)(size_t x, size_t y, uint32_t orig)) {
     uint8_t *img = background->img;
-    const int img_width = background->img_width, img_height = background->img_height, img_pitch = background->pitch, colsize = background->bpp / 8;
+    const size_t img_width = background->img_width, img_height = background->img_height, img_pitch = background->pitch, colsize = background->bpp / 8;
 
     switch (background->type) {
     case IMAGE_TILED:
-        for (int y = ystart; y < yend; y++) {
-            int image_y = y % img_height, image_x = xstart % img_width;
+        for (size_t y = ystart; y < yend; y++) {
+            size_t image_y = y % img_height, image_x = xstart % img_width;
             const size_t off = img_pitch * (img_height - 1 - image_y);
-            int canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
-            for (int x = xstart; x < xend; x++) {
+            size_t canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
+            for (size_t x = xstart; x < xend; x++) {
                 uint32_t img_pixel = *(uint32_t*)(img + image_x * colsize + off);
                 uint32_t i = blend(x, y, img_pixel);
                 bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
@@ -135,20 +145,20 @@ __attribute__((always_inline)) static inline void genloop(int xstart, int xend,
         break;
 
     case IMAGE_CENTERED:
-        for (int y = ystart; y < yend; y++) {
-            int image_y = y - background->y_displacement;
+        for (size_t y = ystart; y < yend; y++) {
+            size_t image_y = y - background->y_displacement;
             const size_t off = img_pitch * (img_height - 1 - image_y);
-            int canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
-            if ((image_y < 0) || (image_y >= background->y_size)) { /* external part */
-                for (int x = xstart; x < xend; x++) {
+            size_t canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
+            if (image_y >= background->y_size) { /* external part */
+                for (size_t x = xstart; x < xend; x++) {
                     uint32_t i = blend(x, y, background->back_colour);
                     bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
                 }
             }
             else { /* internal part */
-                for (int x = xstart; x < xend; x++) {
-                    int image_x = (x - background->x_displacement);
-                    bool x_external = (image_x < 0) || (image_x >= background->x_size);
+                for (size_t x = xstart; x < xend; x++) {
+                    size_t image_x = (x - background->x_displacement);
+                    bool x_external = image_x >= background->x_size;
                     uint32_t img_pixel = *(uint32_t*)(img + image_x * colsize + off);
                     uint32_t i = blend(x, y, x_external ? background->back_colour : img_pixel);
                     bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
@@ -160,14 +170,14 @@ __attribute__((always_inline)) static inline void genloop(int xstart, int xend,
     // hence x = xstart * ratio + i * ratio
     // so you can set x = xstart * ratio, and increment by ratio at each iteration
     case IMAGE_STRETCHED:
-        for (int y = ystart; y < yend; y++) {
-            int img_y = (y * img_height) / gterm_height; // calculate Y with full precision
-            int off = img_pitch * (img_height - 1 - img_y);
-            int canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
+        for (size_t y = ystart; y < yend; y++) {
+            size_t img_y = (y * img_height) / gterm_height; // calculate Y with full precision
+            size_t off = img_pitch * (img_height - 1 - img_y);
+            size_t canvas_off = gterm_width * y, fb_off = gterm_pitch / 4 * y;
 
             size_t ratio = int_to_fixedp6(img_width) / gterm_width;
             fixedp6 img_x = ratio * xstart;
-            for (int x = xstart; x < xend; x++) {
+            for (size_t x = xstart; x < xend; x++) {
                 uint32_t img_pixel = *(uint32_t*)(img + fixedp6_to_int(img_x) * colsize + off);
                 uint32_t i = blend(x, y, img_pixel);
                 bg_canvas[canvas_off + x] = i; gterm_framebuffer[fb_off + x] = i;
@@ -177,18 +187,18 @@ __attribute__((always_inline)) static inline void genloop(int xstart, int xend,
         break;
     }
 }
-static uint32_t blend_external(int x, int y, uint32_t orig) { (void)x; (void)y; return orig; }
-static uint32_t blend_internal(int x, int y, uint32_t orig) { (void)x; (void)y; return colour_blend(default_bg, orig); }
-static uint32_t blend_margin(int x, int y, uint32_t orig) { return blend_gradient_from_box(x, y, orig, default_bg); }
+static uint32_t blend_external(size_t x, size_t y, uint32_t orig) { (void)x; (void)y; return orig; }
+static uint32_t blend_internal(size_t x, size_t y, uint32_t orig) { (void)x; (void)y; return colour_blend(default_bg, orig); }
+static uint32_t blend_margin(size_t x, size_t y, uint32_t orig) { return blend_gradient_from_box(x, y, orig, default_bg); }
 
-static void loop_external(int xstart, int xend, int ystart, int yend) { genloop(xstart, xend, ystart, yend, blend_external); }
-static void loop_margin(int xstart, int xend, int ystart, int yend) { genloop(xstart, xend, ystart, yend, blend_margin); }
-static void loop_internal(int xstart, int xend, int ystart, int yend) { genloop(xstart, xend, ystart, yend, blend_internal); }
+static void loop_external(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_external); }
+static void loop_margin(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_margin); }
+static void loop_internal(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_internal); }
 
 void gterm_generate_canvas(void) {
     if (background) {
-        const int frame_height_end = frame_height + VGA_FONT_HEIGHT * rows, frame_width_end = frame_width + VGA_FONT_WIDTH * cols;
-        const int fheight = frame_height - margin_gradient, fheight_end = frame_height_end + margin_gradient,
+        const size_t frame_height_end = frame_height + VGA_FONT_HEIGHT * rows, frame_width_end = frame_width + VGA_FONT_WIDTH * cols;
+        const size_t fheight = frame_height - margin_gradient, fheight_end = frame_height_end + margin_gradient,
             fwidth = frame_width - margin_gradient, fwidth_end = frame_width_end + margin_gradient;
 
         loop_external(0, gterm_width, 0, fheight);
@@ -205,8 +215,8 @@ void gterm_generate_canvas(void) {
 
         loop_internal(frame_width, frame_width_end, frame_height, frame_height_end);
     } else {
-        for (int y = 0; y < gterm_height; y++) {
-            for (int x = 0; x < gterm_width; x++) {
+        for (size_t y = 0; y < gterm_height; y++) {
+            for (size_t x = 0; x < gterm_width; x++) {
                 bg_canvas[y * gterm_width + x] = default_bg;
                 gterm_plot_px(x, y, default_bg);
             }
@@ -220,12 +230,12 @@ struct gterm_char {
     uint32_t bg;
 };
 
-void gterm_plot_char(struct gterm_char *c, int x, int y) {
+void gterm_plot_char(struct gterm_char *c, size_t x, size_t y) {
     bool *glyph = &vga_font_bool[c->c * VGA_FONT_HEIGHT * VGA_FONT_WIDTH];
-    for (int i = 0; i < VGA_FONT_HEIGHT; i++) {
+    for (size_t i = 0; i < VGA_FONT_HEIGHT; i++) {
         uint32_t *fb_line = gterm_framebuffer + x + (y + i) * (gterm_pitch / 4);
         uint32_t *canvas_line = bg_canvas + x + (y + i) * gterm_width;
-        for (int j = 0; j < VGA_FONT_WIDTH; j++) {
+        for (size_t j = 0; j < VGA_FONT_WIDTH; j++) {
             uint32_t bg = c->bg == 0xffffffff ? canvas_line[j] : c->bg;
             bool draw = glyph[i * VGA_FONT_WIDTH + j];
             fb_line[j] = draw ? c->fg : bg;
@@ -233,13 +243,13 @@ void gterm_plot_char(struct gterm_char *c, int x, int y) {
     }
 }
 
-void gterm_plot_char_fast(struct gterm_char *old, struct gterm_char *c, int x, int y) {
+void gterm_plot_char_fast(struct gterm_char *old, struct gterm_char *c, size_t x, size_t y) {
     bool *new_glyph = &vga_font_bool[c->c * VGA_FONT_HEIGHT * VGA_FONT_WIDTH];
     bool *old_glyph = &vga_font_bool[old->c * VGA_FONT_HEIGHT * VGA_FONT_WIDTH];
-    for (int i = 0; i < VGA_FONT_HEIGHT; i++) {
+    for (size_t i = 0; i < VGA_FONT_HEIGHT; i++) {
         uint32_t *fb_line = gterm_framebuffer + x + (y + i) * (gterm_pitch / 4);
         uint32_t *canvas_line = bg_canvas + x + (y + i) * gterm_width;
-        for (int j = 0; j < VGA_FONT_WIDTH; j++) {
+        for (size_t j = 0; j < VGA_FONT_WIDTH; j++) {
             uint32_t bg = c->bg == 0xffffffff ? canvas_line[j] : c->bg;
             bool old_draw = old_glyph[i * VGA_FONT_WIDTH + j];
             bool new_draw = new_glyph[i * VGA_FONT_WIDTH + j];
@@ -250,11 +260,11 @@ void gterm_plot_char_fast(struct gterm_char *old, struct gterm_char *c, int x, i
     }
 }
 
-static void plot_char_grid_force(struct gterm_char *c, int x, int y) {
+static void plot_char_grid_force(struct gterm_char *c, size_t x, size_t y) {
     gterm_plot_char(c, frame_width + x * VGA_FONT_WIDTH, frame_height + y * VGA_FONT_HEIGHT);
 }
 
-static void plot_char_grid(struct gterm_char *c, int x, int y) {
+static void plot_char_grid(struct gterm_char *c, size_t x, size_t y) {
     if (!double_buffer_enabled) {
         struct gterm_char *old = &grid[x + y * cols];
 
@@ -302,10 +312,11 @@ void gterm_scroll_enable(void) {
     scroll_enabled = true;
 }
 
-static void scroll(void) {
+void gterm_scroll(void) {
     clear_cursor();
 
-    for (int i = cols; i < rows * cols; i++) {
+    for (size_t i = (term_context.scroll_top_margin + 1) * cols;
+         i < term_context.scroll_bottom_margin * cols; i++) {
         if (!compare_char(&grid[i], &grid[i - cols]))
             plot_char_grid(&grid[i], (i - cols) % cols, (i - cols) / cols);
     }
@@ -315,7 +326,8 @@ static void scroll(void) {
     empty.c  = ' ';
     empty.fg = text_fg;
     empty.bg = text_bg;
-    for (int i = rows * cols - cols; i < rows * cols; i++) {
+    for (size_t i = (term_context.scroll_bottom_margin - 1) * cols;
+         i < term_context.scroll_bottom_margin * cols; i++) {
         if (!compare_char(&grid[i], &empty))
             plot_char_grid(&empty, i % cols, i / cols);
     }
@@ -330,7 +342,7 @@ void gterm_clear(bool move) {
     empty.c  = ' ';
     empty.fg = text_fg;
     empty.bg = text_bg;
-    for (int i = 0; i < rows * cols; i++) {
+    for (size_t i = 0; i < rows * cols; i++) {
         plot_char_grid(&empty, i % cols, i / cols);
     }
 
@@ -354,16 +366,12 @@ bool gterm_disable_cursor(void) {
     return ret;
 }
 
-void gterm_set_cursor_pos(int x, int y) {
+void gterm_set_cursor_pos(size_t x, size_t y) {
     clear_cursor();
-    if (x < 0) {
-        x = 0;
-    } else if (x >= cols) {
+    if (x >= cols) {
         x = cols - 1;
     }
-    if (y < 0) {
-        y = 0;
-    } else if (y >= rows) {
+    if (y >= rows) {
         y = rows - 1;
     }
     cursor_x = x;
@@ -371,12 +379,17 @@ void gterm_set_cursor_pos(int x, int y) {
     draw_cursor();
 }
 
-void gterm_get_cursor_pos(int *x, int *y) {
+void gterm_get_cursor_pos(size_t *x, size_t *y) {
     *x = cursor_x;
     *y = cursor_y;
 }
 
-void gterm_move_character(int new_x, int new_y, int old_x, int old_y) {
+void gterm_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y) {
+    if (old_x >= cols || old_y >= rows
+     || new_x >= cols || new_y >= rows) {
+        return;
+    }
+
     if (!double_buffer_enabled) {
         gterm_plot_char(&grid[old_x + old_y * cols],
                         frame_width + new_x * VGA_FONT_WIDTH,
@@ -385,19 +398,19 @@ void gterm_move_character(int new_x, int new_y, int old_x, int old_y) {
     grid[new_x + new_y * cols] = grid[old_x + old_y * cols];
 }
 
-void gterm_set_text_fg(int fg) {
+void gterm_set_text_fg(size_t fg) {
     text_fg = ansi_colours[fg];
 }
 
-void gterm_set_text_bg(int bg) {
+void gterm_set_text_bg(size_t bg) {
     text_bg = ansi_colours[bg];
 }
 
-void gterm_set_text_fg_bright(int fg) {
+void gterm_set_text_fg_bright(size_t fg) {
     text_fg = ansi_bright_colours[fg];
 }
 
-void gterm_set_text_bg_bright(int bg) {
+void gterm_set_text_bg_bright(size_t bg) {
     text_bg = ansi_bright_colours[bg];
 }
 
@@ -416,8 +429,8 @@ void gterm_double_buffer_flush(void) {
 
         front_grid[i] = grid[i];
 
-        int x = i % cols;
-        int y = i / cols;
+        size_t x = i % cols;
+        size_t y = i / cols;
 
         gterm_plot_char(&grid[i], x * VGA_FONT_WIDTH + frame_width,
                                 y * VGA_FONT_HEIGHT + frame_height);
@@ -440,54 +453,24 @@ void gterm_double_buffer(bool state) {
 }
 
 void gterm_putchar(uint8_t c) {
-    switch (c) {
-        case '\b':
-            if (cursor_x || cursor_y) {
-                clear_cursor();
-                if (cursor_x) {
-                    cursor_x--;
-                } else {
-                    cursor_y--;
-                    cursor_x = cols - 1;
-                }
-                draw_cursor();
-            }
-            break;
-        case '\r':
-            gterm_set_cursor_pos(0, cursor_y);
-            break;
-        case '\n':
-            if (cursor_y == (rows - 1)) {
-                if (scroll_enabled) {
-                    gterm_set_cursor_pos(0, rows - 1);
-                    scroll();
-                }
-            } else {
-                gterm_set_cursor_pos(0, cursor_y + 1);
-            }
-            break;
-        default: {
-            clear_cursor();
-            struct gterm_char ch;
-            ch.c  = c;
-            ch.fg = text_fg;
-            ch.bg = text_bg;
-            plot_char_grid(&ch, cursor_x++, cursor_y);
-            if (cursor_x == cols && (cursor_y < rows - 1 || scroll_enabled)) {
-                cursor_x = 0;
-                cursor_y++;
-            }
-            if (cursor_y == rows) {
-                cursor_y--;
-                scroll();
-            }
-            draw_cursor();
-            break;
-        }
+    clear_cursor();
+    struct gterm_char ch;
+    ch.c  = c;
+    ch.fg = text_fg;
+    ch.bg = text_bg;
+    plot_char_grid(&ch, cursor_x++, cursor_y);
+    if (cursor_x == cols && ((size_t)cursor_y < term_context.scroll_bottom_margin - 1 || scroll_enabled)) {
+        cursor_x = 0;
+        cursor_y++;
     }
+    if ((size_t)cursor_y == term_context.scroll_bottom_margin) {
+        cursor_y--;
+        gterm_scroll();
+    }
+    draw_cursor();
 }
 
-bool gterm_init(int *_rows, int *_cols, int width, int height) {
+bool gterm_init(size_t *_rows, size_t *_cols, size_t width, size_t height) {
     if (current_video_mode >= 0
      && fbinfo.default_res == true
      && width == 0
@@ -518,7 +501,7 @@ bool gterm_init(int *_rows, int *_cols, int width, int height) {
         return false;
 
     // default scheme
-    int margin = 64;
+    size_t margin = 64;
     margin_gradient = 4;
 
     default_bg = 0x00000000; // background (black)
@@ -538,7 +521,7 @@ bool gterm_init(int *_rows, int *_cols, int width, int height) {
         colours = config_get_value(NULL, 0, "THEME_COLORS");
     if (colours != NULL) {
         const char *first = colours;
-        int i;
+        size_t i;
         for (i = 0; i < 10; i++) {
             const char *last;
             uint32_t col = strtoui(first, &last, 16);
@@ -571,7 +554,7 @@ bool gterm_init(int *_rows, int *_cols, int width, int height) {
         bright_colours = config_get_value(NULL, 0, "THEME_BRIGHT_COLORS");
     if (bright_colours != NULL) {
         const char *first = bright_colours;
-        int i;
+        size_t i;
         for (i = 0; i < 8; i++) {
             const char *last;
             uint32_t col = strtoui(first, &last, 16);
@@ -672,8 +655,8 @@ bool gterm_init(int *_rows, int *_cols, int width, int height) {
         for (size_t i = 0; i < VGA_FONT_GLYPHS; i++) {
             uint8_t *glyph = &vga_font_bits[i * VGA_FONT_HEIGHT];
 
-            for (int y = 0; y < VGA_FONT_HEIGHT; y++) {
-                for (int x = 0; x < VGA_FONT_WIDTH; x++) {
+            for (size_t y = 0; y < VGA_FONT_HEIGHT; y++) {
+                for (size_t x = 0; x < VGA_FONT_WIDTH; x++) {
                     size_t offset = i * VGA_FONT_HEIGHT * VGA_FONT_WIDTH + y * VGA_FONT_WIDTH + x;
                     if ((glyph[y] & (0x80 >> x))) {
                         vga_font_bool[offset] = true;
diff --git a/stage23/lib/gterm.h b/stage23/lib/gterm.h
index 9859f493..56a6bb5b 100644
--- a/stage23/lib/gterm.h
+++ b/stage23/lib/gterm.h
@@ -8,23 +8,25 @@
 
 extern struct fb_info fbinfo;
 
-bool gterm_init(int *_rows, int *_cols, int width, int height);
+bool gterm_init(size_t *_rows, size_t *_cols, size_t width, size_t height);
 
 void gterm_putchar(uint8_t c);
 void gterm_clear(bool move);
 void gterm_enable_cursor(void);
 bool gterm_disable_cursor(void);
-void gterm_set_cursor_pos(int x, int y);
-void gterm_get_cursor_pos(int *x, int *y);
-void gterm_set_text_fg(int fg);
-void gterm_set_text_bg(int bg);
-void gterm_set_text_fg_bright(int fg);
-void gterm_set_text_bg_bright(int bg);
+void gterm_set_cursor_pos(size_t x, size_t y);
+void gterm_get_cursor_pos(size_t *x, size_t *y);
+void gterm_set_text_fg(size_t fg);
+void gterm_set_text_bg(size_t bg);
+void gterm_set_text_fg_bright(size_t fg);
+void gterm_set_text_bg_bright(size_t bg);
 void gterm_set_text_fg_default(void);
 void gterm_set_text_bg_default(void);
 bool gterm_scroll_disable(void);
 void gterm_scroll_enable(void);
-void gterm_move_character(int new_x, int new_y, int old_x, int old_y);
+void gterm_move_character(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
+void gterm_scroll(void);
+void gterm_swap_palette(void);
 
 void gterm_double_buffer_flush(void);
 void gterm_double_buffer(bool state);
diff --git a/stage23/lib/image.h b/stage23/lib/image.h
index 4731c908..8c7a506c 100644
--- a/stage23/lib/image.h
+++ b/stage23/lib/image.h
@@ -6,16 +6,16 @@
 
 struct image {
     struct file_handle *file;
-    int x_size;
-    int y_size;
+    size_t x_size;
+    size_t y_size;
     int type;
     uint8_t *img;
     int bpp;
     int pitch;
-    int img_width; // x_size = scaled size, img_width = bitmap size
-    int img_height;
-    int x_displacement;
-    int y_displacement;
+    size_t img_width; // x_size = scaled size, img_width = bitmap size
+    size_t img_height;
+    size_t x_displacement;
+    size_t y_displacement;
     uint32_t back_colour;
 };
 
diff --git a/stage23/lib/libgcc.s2.asm b/stage23/lib/libgcc.s2.asm
index 743f11f8..a9496aa9 100644
--- a/stage23/lib/libgcc.s2.asm
+++ b/stage23/lib/libgcc.s2.asm
@@ -55,3 +55,38 @@ __divmoddi4:
     mov dword [ecx+4], 0
     xor edx, edx
     ret
+
+global memcpy32to64
+memcpy32to64:
+bits 32
+    push ebp
+    mov ebp, esp
+
+    push esi
+    push edi
+
+    push 0x28
+    call .p1
+  .p1:
+    add dword [esp], .mode64 - .p1
+    retfd
+
+bits 64
+  .mode64:
+    mov rdi, [rbp + 8]
+    mov rsi, [rbp + 16]
+    mov rcx, [rbp + 24]
+    rep movsb
+
+    push 0x18
+    call .p2
+  .p2:
+    add qword [rsp], .mode32 - .p2
+    retfq
+
+bits 32
+  .mode32:
+    pop edi
+    pop esi
+    pop ebp
+    ret
diff --git a/stage23/lib/print.s2.c b/stage23/lib/print.s2.c
index 05c8cd45..8f83e83e 100644
--- a/stage23/lib/print.s2.c
+++ b/stage23/lib/print.s2.c
@@ -184,7 +184,7 @@ void vprint(const char *fmt, va_list args) {
     }
 
 out:
-    term_write(print_buf, print_buf_i);
+    term_write((uint64_t)(uintptr_t)print_buf, print_buf_i);
 
     for (size_t i = 0; i < print_buf_i; i++) {
         if (E9_OUTPUT) {
diff --git a/stage23/lib/readline.c b/stage23/lib/readline.c
index 7b58729e..bd4a5525 100644
--- a/stage23/lib/readline.c
+++ b/stage23/lib/readline.c
@@ -143,98 +143,3 @@ again:
     return ret;
 }
 #endif
-
-static void reprint_string(int x, int y, const char *s) {
-    int orig_x, orig_y;
-    disable_cursor();
-    get_cursor_pos(&orig_x, &orig_y);
-    set_cursor_pos(x, y);
-    term_write(s, strlen(s));
-    set_cursor_pos(orig_x, orig_y);
-    enable_cursor();
-}
-
-static void cursor_back(void) {
-    int x, y;
-    get_cursor_pos(&x, &y);
-    if (x) {
-        x--;
-    } else if (y) {
-        y--;
-        x = term_cols - 1;
-    }
-    set_cursor_pos(x, y);
-}
-
-static void cursor_fwd(void) {
-    int x, y;
-    get_cursor_pos(&x, &y);
-    if (x < term_cols - 1) {
-        x++;
-    } else if (y < term_rows - 1) {
-        y++;
-        x = 0;
-    }
-    set_cursor_pos(x, y);
-}
-
-void readline(const char *orig_str, char *buf, size_t limit) {
-    size_t orig_str_len = strlen(orig_str);
-    memmove(buf, orig_str, orig_str_len);
-    buf[orig_str_len] = 0;
-
-    int orig_x, orig_y;
-    get_cursor_pos(&orig_x, &orig_y);
-
-    term_write(orig_str, orig_str_len);
-
-    for (size_t i = orig_str_len; ; ) {
-        int c = getchar();
-        switch (c) {
-            case GETCHAR_CURSOR_LEFT:
-                if (i) {
-                    i--;
-                    cursor_back();
-                }
-                continue;
-            case GETCHAR_CURSOR_RIGHT:
-                if (i < strlen(buf)) {
-                    i++;
-                    cursor_fwd();
-                }
-                continue;
-            case '\b':
-                if (i) {
-                    i--;
-                    cursor_back();
-            case GETCHAR_DELETE:;
-                    size_t j;
-                    for (j = i; ; j++) {
-                        buf[j] = buf[j+1];
-                        if (!buf[j]) {
-                            buf[j] = ' ';
-                            break;
-                        }
-                    }
-                    reprint_string(orig_x, orig_y, buf);
-                    buf[j] = 0;
-                }
-                continue;
-            case '\n':
-                term_write("\n", 1);
-                return;
-            default:
-                if (strlen(buf) < limit - 1) {
-                    for (size_t j = strlen(buf); ; j--) {
-                        buf[j+1] = buf[j];
-                        if (j == i)
-                            break;
-                    }
-                    buf[i] = c;
-                    i++;
-                    cursor_fwd();
-                    reprint_string(orig_x, orig_y, buf);
-                }
-        }
-    }
-}
diff --git a/stage23/lib/term.c b/stage23/lib/term.c
index b5124a36..2ed68b69 100644
--- a/stage23/lib/term.c
+++ b/stage23/lib/term.c
@@ -8,7 +8,7 @@
 
 bool early_term = false;
 
-void term_vbe(int width, int height) {
+void term_vbe(size_t width, size_t height) {
     term_backend = NOT_READY;
 
     if (!gterm_init(&term_rows, &term_cols, width, height)) {
@@ -19,6 +19,8 @@ void term_vbe(int width, int height) {
         return;
     }
 
+    term_reinit();
+
     raw_putchar    = gterm_putchar;
     clear          = gterm_clear;
     enable_cursor  = gterm_enable_cursor;
@@ -34,6 +36,8 @@ void term_vbe(int width, int height) {
     scroll_disable = gterm_scroll_disable;
     scroll_enable  = gterm_scroll_enable;
     term_move_character = gterm_move_character;
+    term_scroll = gterm_scroll;
+    term_swap_palette = gterm_swap_palette;
 
     term_double_buffer       = gterm_double_buffer;
     term_double_buffer_flush = gterm_double_buffer_flush;
diff --git a/stage23/lib/term.h b/stage23/lib/term.h
index d95f3500..ea8dd2d4 100644
--- a/stage23/lib/term.h
+++ b/stage23/lib/term.h
@@ -2,45 +2,88 @@
 #define __LIB__TERM_H__
 
 #include <stddef.h>
+#include <stdint.h>
 #include <stdbool.h>
 #include <lib/image.h>
 
+#define TERM_TABSIZE (8)
+#define MAX_ESC_VALUES (16)
+
+extern struct term_context {
+    bool control_sequence;
+    bool csi;
+    bool escape;
+    bool rrr;
+    bool discard_next;
+    bool bold;
+    bool reverse_video;
+    bool dec_private;
+    bool insert_mode;
+    uint8_t g_select;
+    uint8_t charsets[2];
+    size_t current_charset;
+    size_t escape_offset;
+    size_t esc_values_i;
+    size_t saved_cursor_x;
+    size_t saved_cursor_y;
+    size_t current_primary;
+    size_t scroll_top_margin;
+    size_t scroll_bottom_margin;
+    uint32_t esc_values[MAX_ESC_VALUES];
+} term_context;
+
+enum {
+    NOT_READY,
+    VBE,
+    TEXTMODE
+};
+
+extern int current_video_mode;
+extern int term_backend;
+extern size_t term_rows, term_cols;
+extern bool term_runtime;
+extern bool early_term;
+
+void term_reinit(void);
+void term_deinit(void);
+void term_vbe(size_t width, size_t height);
+void term_textmode(void);
+void term_putchar(uint8_t c);
+void term_write(uint64_t buf, uint64_t count);
+
 extern void (*raw_putchar)(uint8_t c);
 extern void (*clear)(bool move);
 extern void (*enable_cursor)(void);
 extern bool (*disable_cursor)(void);
-extern void (*set_cursor_pos)(int x, int y);
-extern void (*get_cursor_pos)(int *x, int *y);
-extern void (*set_text_fg)(int fg);
-extern void (*set_text_bg)(int bg);
-extern void (*set_text_fg_bright)(int fg);
-extern void (*set_text_bg_bright)(int bg);
+extern void (*set_cursor_pos)(size_t x, size_t y);
+extern void (*get_cursor_pos)(size_t *x, size_t *y);
+extern void (*set_text_fg)(size_t fg);
+extern void (*set_text_bg)(size_t bg);
+extern void (*set_text_fg_bright)(size_t fg);
+extern void (*set_text_bg_bright)(size_t bg);
 extern void (*set_text_fg_default)(void);
 extern void (*set_text_bg_default)(void);
 extern bool (*scroll_disable)(void);
 extern void (*scroll_enable)(void);
-extern void (*term_move_character)(int new_x, int new_y, int old_x, int old_y);
+extern void (*term_move_character)(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
+extern void (*term_scroll)(void);
+extern void (*term_swap_palette)(void);
 
 extern void (*term_double_buffer)(bool status);
 extern void (*term_double_buffer_flush)(void);
 
-void term_vbe(int width, int height);
-void term_textmode(void);
-void term_write(const char *buf, size_t count);
+extern uint64_t (*term_context_size)(void);
+extern void (*term_context_save)(uint64_t ptr);
+extern void (*term_context_restore)(uint64_t ptr);
 
-void term_deinit(void);
+#define TERM_CB_DEC 10
+#define TERM_CB_BELL 20
+#define TERM_CB_PRIVATE_ID 30
 
-extern int term_rows, term_cols;
+#define TERM_CTX_SIZE ((uint64_t)(-1))
+#define TERM_CTX_SAVE ((uint64_t)(-2))
+#define TERM_CTX_RESTORE ((uint64_t)(-3))
 
-enum {
-    NOT_READY,
-    VBE,
-    TEXTMODE
-};
-
-extern int term_backend;
-extern int current_video_mode;
-
-extern bool early_term;
+extern void (*term_callback)(uint64_t type, uint64_t extra, uint64_t esc_val_count, uint64_t esc_values);
 
 #endif
diff --git a/stage23/lib/term.s2.c b/stage23/lib/term.s2.c
index a89e4ea1..26a8f9da 100644
--- a/stage23/lib/term.s2.c
+++ b/stage23/lib/term.s2.c
@@ -6,37 +6,93 @@
 #include <lib/image.h>
 #include <lib/blib.h>
 #include <drivers/vga_textmode.h>
+#include <lib/print.h>
 
 // Tries to implement this standard for terminfo
 // https://man7.org/linux/man-pages/man4/console_codes.4.html
 
-#define TERM_TABSIZE (8)
-#define MAX_ESC_VALUES (256)
-
 int current_video_mode = -1;
-
 int term_backend = NOT_READY;
+size_t term_rows, term_cols;
+bool term_runtime = false;
+
+static bool old_cur_stat;
 
 void (*raw_putchar)(uint8_t c);
 void (*clear)(bool move);
 void (*enable_cursor)(void);
 bool (*disable_cursor)(void);
-void (*set_cursor_pos)(int x, int y);
-void (*get_cursor_pos)(int *x, int *y);
-void (*set_text_fg)(int fg);
-void (*set_text_bg)(int bg);
-void (*set_text_fg_bright)(int fg);
-void (*set_text_bg_bright)(int bg);
+void (*set_cursor_pos)(size_t x, size_t y);
+void (*get_cursor_pos)(size_t *x, size_t *y);
+void (*set_text_fg)(size_t fg);
+void (*set_text_bg)(size_t bg);
+void (*set_text_fg_bright)(size_t fg);
+void (*set_text_bg_bright)(size_t bg);
 void (*set_text_fg_default)(void);
 void (*set_text_bg_default)(void);
 bool (*scroll_disable)(void);
 void (*scroll_enable)(void);
-void (*term_move_character)(int new_x, int new_y, int old_x, int old_y);
+void (*term_move_character)(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
+void (*term_scroll)(void);
+void (*term_swap_palette)(void);
 
 void (*term_double_buffer)(bool status);
 void (*term_double_buffer_flush)(void);
 
-int term_rows, term_cols;
+uint64_t (*term_context_size)(void);
+void (*term_context_save)(uint64_t ptr);
+void (*term_context_restore)(uint64_t ptr);
+
+void (*term_callback)(uint64_t type, uint64_t extra, uint64_t esc_val_count, uint64_t esc_values) = NULL;
+
+struct term_context term_context;
+
+#define escape_offset term_context.escape_offset
+#define control_sequence term_context.control_sequence
+#define csi term_context.csi
+#define escape term_context.escape
+#define rrr term_context.rrr
+#define discard_next term_context.discard_next
+#define bold term_context.bold
+#define reverse_video term_context.reverse_video
+#define dec_private term_context.dec_private
+#define esc_values term_context.esc_values
+#define esc_values_i term_context.esc_values_i
+#define saved_cursor_x term_context.saved_cursor_x
+#define saved_cursor_y term_context.saved_cursor_y
+#define current_primary term_context.current_primary
+#define insert_mode term_context.insert_mode
+#define scroll_top_margin term_context.scroll_top_margin
+#define scroll_bottom_margin term_context.scroll_bottom_margin
+#define current_charset term_context.current_charset
+#define charsets term_context.charsets
+#define g_select term_context.g_select
+
+#define CHARSET_DEFAULT 0
+#define CHARSET_DEC_SPECIAL 1
+
+void term_reinit(void) {
+    escape_offset = 0;
+    control_sequence = false;
+    csi = false;
+    escape = false;
+    rrr = false;
+    discard_next = false;
+    bold = false;
+    reverse_video = false;
+    dec_private = false;
+    esc_values_i = 0;
+    saved_cursor_x = 0;
+    saved_cursor_y = 0;
+    current_primary = (size_t)-1;
+    insert_mode = false;
+    scroll_top_margin = 0;
+    scroll_bottom_margin = term_rows;
+    current_charset = 0;
+    g_select = 0;
+    charsets[0] = CHARSET_DEFAULT;
+    charsets[1] = CHARSET_DEC_SPECIAL;
+}
 
 #if bios == 1
 void term_textmode(void) {
@@ -44,6 +100,8 @@ void term_textmode(void) {
 
     init_vga_textmode(&term_rows, &term_cols, true);
 
+    term_reinit();
+
     raw_putchar    = text_putchar;
     clear          = text_clear;
     enable_cursor  = text_enable_cursor;
@@ -52,17 +110,23 @@ void term_textmode(void) {
     get_cursor_pos = text_get_cursor_pos;
     set_text_fg    = text_set_text_fg;
     set_text_bg    = text_set_text_bg;
-    set_text_fg_bright = text_set_text_fg;
+    set_text_fg_bright = text_set_text_fg_bright;
     set_text_bg_bright = text_set_text_bg;
     set_text_fg_default = text_set_text_fg_default;
     set_text_bg_default = text_set_text_bg_default;
     scroll_disable = text_scroll_disable;
     scroll_enable  = text_scroll_enable;
     term_move_character = text_move_character;
+    term_scroll = text_scroll;
+    term_swap_palette = text_swap_palette;
 
     term_double_buffer       = text_double_buffer;
     term_double_buffer_flush = text_double_buffer_flush;
 
+    term_context_size = text_context_size;
+    term_context_save = text_context_save;
+    term_context_restore = text_context_restore;
+
     term_backend = TEXTMODE;
 }
 #endif
@@ -71,41 +135,86 @@ void term_deinit(void) {
     term_backend = NOT_READY;
 }
 
-static void term_putchar(uint8_t c);
+static uint64_t context_size(void) {
+    uint64_t ret = 0;
 
-static bool old_cur_stat;
+    ret += sizeof(struct term_context);
+    ret += term_context_size();
 
-void term_write(const char *buf, size_t count) {
-    if (term_backend == NOT_READY)
-        return;
-    old_cur_stat = disable_cursor();
-    for (size_t i = 0; i < count; i++)
-        term_putchar(buf[i]);
-    if (old_cur_stat)
-        enable_cursor();
+    return ret;
 }
 
-static int get_cursor_pos_x(void) {
-    int x, y;
-    get_cursor_pos(&x, &y);
-    return x;
+static void context_save(uint64_t ptr) {
+    memcpy32to64(ptr, (uint64_t)(uintptr_t)&term_context, sizeof(struct term_context));
+    ptr += sizeof(struct term_context);
+
+    term_context_save(ptr);
 }
 
-static int get_cursor_pos_y(void) {
-    int x, y;
-    get_cursor_pos(&x, &y);
-    return y;
+static void context_restore(uint64_t ptr) {
+    memcpy32to64((uint64_t)(uintptr_t)&term_context, ptr, sizeof(struct term_context));
+    ptr += sizeof(struct term_context);
+
+    term_context_restore(ptr);
 }
 
-static bool control_sequence = false;
-static bool escape = false;
-static bool rrr = false;
-static bool bold = false;
-static bool dec_private = false;
-static int32_t esc_values[MAX_ESC_VALUES];
-static size_t esc_values_i = 0;
-static int saved_cursor_x = 0, saved_cursor_y = 0;
-static int current_fg = -1;
+#if defined (__i386__)
+#define TERM_XFER_CHUNK 8192
+
+static char xfer_buf[TERM_XFER_CHUNK];
+#endif
+
+void term_write(uint64_t buf, uint64_t count) {
+    if (term_backend == NOT_READY)
+        return;
+
+    switch (count) {
+        case TERM_CTX_SIZE: {
+            uint64_t ret = context_size();
+            memcpy32to64(buf, (uint64_t)(uintptr_t)&ret, sizeof(uint64_t));
+            return;
+        }
+        case TERM_CTX_SAVE: {
+            context_save(buf);
+            return;
+        }
+        case TERM_CTX_RESTORE: {
+            context_restore(buf);
+            return;
+        }
+    }
+
+    bool native = false;
+#if defined (__x86_64__)
+    native = true;
+#endif
+
+    if (!term_runtime || native) {
+        const char *s = (const char *)(uintptr_t)buf;
+
+        old_cur_stat = disable_cursor();
+        for (size_t i = 0; i < count; i++)
+            term_putchar(s[i]);
+        if (old_cur_stat)
+            enable_cursor();
+    } else {
+#if defined (__i386__)
+        while (count != 0) {
+            uint64_t chunk = count % TERM_XFER_CHUNK;
+            memcpy32to64((uint64_t)(uintptr_t)xfer_buf, buf, chunk);
+
+            old_cur_stat = disable_cursor();
+            for (size_t i = 0; i < chunk; i++)
+                term_putchar(xfer_buf[i]);
+            if (old_cur_stat)
+                enable_cursor();
+
+            count -= chunk;
+            buf += chunk;
+        }
+#endif
+    }
+}
 
 static void sgr(void) {
     size_t i = 0;
@@ -114,65 +223,144 @@ static void sgr(void) {
         goto def;
 
     for (; i < esc_values_i; i++) {
+        size_t offset;
+
         if (esc_values[i] == 0) {
 def:
+            if (reverse_video) {
+                reverse_video = false;
+                term_swap_palette();
+            }
             bold = false;
-            current_fg = -1;
+            current_primary = (size_t)-1;
             set_text_bg_default();
             set_text_fg_default();
             continue;
         }
 
-        if (esc_values[i] == 1) {
+        else if (esc_values[i] == 1) {
             bold = true;
-            if (current_fg != -1) {
-                set_text_fg_bright(current_fg);
+            if (current_primary != (size_t)-1) {
+                if (!reverse_video) {
+                    set_text_fg_bright(current_primary);
+                } else {
+                    set_text_bg_bright(current_primary);
+                }
             }
             continue;
         }
 
-        if (esc_values[i] == 22) {
+        else if (esc_values[i] == 22) {
             bold = false;
-            if (current_fg != -1) {
-                set_text_fg(current_fg);
+            if (current_primary != (size_t)-1) {
+                if (!reverse_video) {
+                    set_text_fg(current_primary);
+                } else {
+                    set_text_bg(current_primary);
+                }
             }
             continue;
         }
 
-        if (esc_values[i] >= 30 && esc_values[i] <= 37) {
-            current_fg = esc_values[i] - 30;
-            if (bold) {
-                set_text_fg_bright(esc_values[i] - 30);
+        else if (esc_values[i] >= 30 && esc_values[i] <= 37) {
+            offset = 30;
+            current_primary = esc_values[i] - offset;
+
+            if (reverse_video) {
+                goto set_bg;
+            }
+
+set_fg:
+            if (bold && !reverse_video) {
+                set_text_fg_bright(esc_values[i] - offset);
             } else {
-                set_text_fg(esc_values[i] - 30);
+                set_text_fg(esc_values[i] - offset);
             }
             continue;
         }
 
-        if (esc_values[i] >= 40 && esc_values[i] <= 47) {
-            set_text_bg(esc_values[i] - 40);
+        else if (esc_values[i] >= 40 && esc_values[i] <= 47) {
+            offset = 40;
+            if (reverse_video) {
+                goto set_fg;
+            }
+
+set_bg:
+            if (bold && reverse_video) {
+                set_text_bg_bright(esc_values[i] - offset);
+            } else {
+                set_text_bg(esc_values[i] - offset);
+            }
             continue;
         }
 
-        if (esc_values[i] >= 90 && esc_values[i] <= 97) {
-            current_fg = esc_values[i] - 90;
-            set_text_fg_bright(esc_values[i] - 90);
+        else if (esc_values[i] >= 90 && esc_values[i] <= 97) {
+            offset = 90;
+            current_primary = esc_values[i] - offset;
+
+            if (reverse_video) {
+                goto set_bg_bright;
+            }
+
+set_fg_bright:
+            set_text_fg_bright(esc_values[i] - offset);
             continue;
         }
 
-        if (esc_values[i] >= 100 && esc_values[i] <= 107) {
-            set_text_bg_bright(esc_values[i] - 100);
+        else if (esc_values[i] >= 100 && esc_values[i] <= 107) {
+            offset = 100;
+            if (reverse_video) {
+                goto set_fg_bright;
+            }
+
+set_bg_bright:
+            set_text_bg_bright(esc_values[i] - offset);
             continue;
         }
 
-        if (esc_values[i] == 39) {
-            current_fg = -1;
+        else if (esc_values[i] == 39) {
+            current_primary = (size_t)-1;
+
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
             set_text_fg_default();
+
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
             continue;
         }
 
-        if (esc_values[i] == 49) {
+        else if (esc_values[i] == 49) {
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
             set_text_bg_default();
+
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
+            continue;
+        }
+
+        else if (esc_values[i] == 7) {
+            if (!reverse_video) {
+                reverse_video = true;
+                term_swap_palette();
+            }
+            continue;
+        }
+
+        else if (esc_values[i] == 27) {
+            if (reverse_video) {
+                reverse_video = false;
+                term_swap_palette();
+            }
             continue;
         }
     }
@@ -181,100 +369,169 @@ def:
 static void dec_private_parse(uint8_t c) {
     dec_private = false;
 
-    if (esc_values_i > 0) {
-        switch (esc_values[0]) {
-            case 25: {
-                switch (c) {
-                    case 'h': old_cur_stat = true; return;
-                    case 'l': old_cur_stat = false; return;
-                }
-            }
-        }
+    if (esc_values_i == 0) {
+        return;
     }
+
+    bool set;
+
+    switch (c) {
+        case 'h':
+            set = true; break;
+        case 'l':
+            set = false; break;
+        default:
+            return;
+    }
+
+    switch (esc_values[0]) {
+        case 25:
+            old_cur_stat = set; return;
+    }
+
+    if (term_callback != NULL)
+        term_callback(TERM_CB_DEC, c, esc_values_i, (uintptr_t)esc_values);
 }
 
-static void control_sequence_parse(uint8_t c) {
-    if (c == '?') {
-        dec_private = true;
+static void mode_toggle(uint8_t c) {
+    if (esc_values_i == 0) {
         return;
     }
 
+    bool set;
+
+    switch (c) {
+        case 'h':
+            set = true; break;
+        case 'l':
+            set = false; break;
+        default:
+            return;
+    }
+
+    switch (esc_values[0]) {
+        case 4:
+            insert_mode = set; return;
+    }
+}
+
+static void control_sequence_parse(uint8_t c) {
+    if (escape_offset == 2) {
+        switch (c) {
+            case '[':
+                discard_next = true;
+                goto cleanup;
+            case '?':
+                dec_private = true;
+                return;
+        }
+    }
+
     if (c >= '0' && c <= '9') {
+        if (esc_values_i == MAX_ESC_VALUES) {
+            return;
+        }
         rrr = true;
         esc_values[esc_values_i] *= 10;
         esc_values[esc_values_i] += c - '0';
         return;
-    } else {
-        if (rrr == true) {
-            esc_values_i++;
-            rrr = false;
-            if (c == ';')
-                return;
-        } else if (c == ';') {
-            esc_values[esc_values_i] = 1;
-            esc_values_i++;
+    }
+
+    if (rrr == true) {
+        esc_values_i++;
+        rrr = false;
+        if (c == ';')
+            return;
+    } else if (c == ';') {
+        if (esc_values_i == MAX_ESC_VALUES) {
             return;
         }
+        esc_values[esc_values_i] = 0;
+        esc_values_i++;
+        return;
     }
 
-    int esc_default;
+    size_t esc_default;
     switch (c) {
         case 'J': esc_default = 0; break;
         case 'K': esc_default = 0; break;
         default:  esc_default = 1; break;
     }
 
-    for (int i = esc_values_i; i < MAX_ESC_VALUES; i++)
+    for (size_t i = esc_values_i; i < MAX_ESC_VALUES; i++) {
         esc_values[i] = esc_default;
+    }
 
     if (dec_private == true) {
         dec_private_parse(c);
         goto cleanup;
     }
 
+    bool r = scroll_disable();
+    size_t x, y;
+    get_cursor_pos(&x, &y);
+
     switch (c) {
-        case 'A':
-            if (esc_values[0] > get_cursor_pos_y())
-                esc_values[0] = get_cursor_pos_y();
-            set_cursor_pos(get_cursor_pos_x(), get_cursor_pos_y() - esc_values[0]);
+        case 'F':
+            x = 0;
+            // FALLTHRU
+        case 'A': {
+            if (esc_values[0] > y)
+                esc_values[0] = y;
+            size_t orig_y = y;
+            size_t dest_y = y - esc_values[0];
+            bool will_be_in_scroll_region = false;
+            if ((scroll_top_margin >= dest_y && scroll_top_margin <= orig_y)
+             || (scroll_bottom_margin >= dest_y && scroll_bottom_margin <= orig_y)) {
+                will_be_in_scroll_region = true;
+            }
+            if (will_be_in_scroll_region && dest_y < scroll_top_margin) {
+                dest_y = scroll_top_margin;
+            }
+            set_cursor_pos(x, dest_y);
             break;
-        case 'B':
-            if ((get_cursor_pos_y() + esc_values[0]) > (term_rows - 1))
-                esc_values[0] = (term_rows - 1) - get_cursor_pos_y();
-            set_cursor_pos(get_cursor_pos_x(), get_cursor_pos_y() + esc_values[0]);
+        }
+        case 'E':
+            x = 0;
+            // FALLTHRU
+        case 'B': {
+            if (y + esc_values[0] > term_rows - 1)
+                esc_values[0] = (term_rows - 1) - y;
+            size_t orig_y = y;
+            size_t dest_y = y + esc_values[0];
+            bool will_be_in_scroll_region = false;
+            if ((scroll_top_margin >= orig_y && scroll_top_margin <= dest_y)
+             || (scroll_bottom_margin >= orig_y && scroll_bottom_margin <= dest_y)) {
+                will_be_in_scroll_region = true;
+            }
+            if (will_be_in_scroll_region && dest_y >= scroll_bottom_margin) {
+                dest_y = scroll_bottom_margin - 1;
+            }
+            set_cursor_pos(x, dest_y);
             break;
+        }
         case 'C':
-            if ((get_cursor_pos_x() + esc_values[0]) > (term_cols - 1))
-                esc_values[0] = (term_cols - 1) - get_cursor_pos_x();
-            set_cursor_pos(get_cursor_pos_x() + esc_values[0], get_cursor_pos_y());
+            if (x + esc_values[0] > term_cols - 1)
+                esc_values[0] = (term_cols - 1) - x;
+            set_cursor_pos(x + esc_values[0], y);
             break;
         case 'D':
-            if (esc_values[0] > get_cursor_pos_x())
-                esc_values[0] = get_cursor_pos_x();
-            set_cursor_pos(get_cursor_pos_x() - esc_values[0], get_cursor_pos_y());
-            break;
-        case 'E':
-            if (get_cursor_pos_y() + esc_values[0] >= term_rows)
-                set_cursor_pos(0, term_rows - 1);
-            else
-                set_cursor_pos(0, get_cursor_pos_y() + esc_values[0]);
-            break;
-        case 'F':
-            if (get_cursor_pos_y() - esc_values[0] < 0)
-                set_cursor_pos(0, 0);
-            else
-                set_cursor_pos(0, get_cursor_pos_y() - esc_values[0]);
+            if (esc_values[0] > x)
+                esc_values[0] = x;
+            set_cursor_pos(x - esc_values[0], y);
             break;
         case 'd':
+            esc_values[0] -= 1;
             if (esc_values[0] >= term_rows)
-                break;
-            set_cursor_pos(get_cursor_pos_x(), esc_values[0]);
+                esc_values[0] = term_rows - 1;
+            set_cursor_pos(x, esc_values[0]);
             break;
         case 'G':
         case '`':
+            esc_values[0] -= 1;
             if (esc_values[0] >= term_cols)
-                break;
-            set_cursor_pos(esc_values[0], get_cursor_pos_y());
+                esc_values[0] = term_cols - 1;
+            set_cursor_pos(esc_values[0], y);
             break;
         case 'H':
         case 'f':
@@ -289,31 +546,23 @@ static void control_sequence_parse(uint8_t c) {
         case 'J':
             switch (esc_values[0]) {
                 case 0: {
-                    int x, y;
-                    get_cursor_pos(&x, &y);
-                    int rows_remaining = term_rows - (y + 1);
-                    int cols_diff = term_cols - (x + 1);
+                    size_t rows_remaining = term_rows - (y + 1);
+                    size_t cols_diff = term_cols - (x + 1);
                     size_t to_clear = rows_remaining * term_cols + cols_diff;
-                    bool r = scroll_disable();
                     for (size_t i = 0; i < to_clear; i++) {
                         raw_putchar(' ');
                     }
                     set_cursor_pos(x, y);
-                    if (r)
-                        scroll_enable();
                     break;
                 }
                 case 1: {
-                    int x, y;
-                    get_cursor_pos(&x, &y);
-                    bool r = scroll_disable();
                     set_cursor_pos(0, 0);
                     bool b = false;
-                    for (int yc = 0; yc < term_rows; yc++) {
-                        for (int xc = 0; xc < term_cols; xc++) {
+                    for (size_t yc = 0; yc < term_rows; yc++) {
+                        for (size_t xc = 0; xc < term_cols; xc++) {
                             raw_putchar(' ');
                             if (xc == x && yc == y) {
-                                raw_putchar('\b');
+                                set_cursor_pos(x, y);
                                 b = true;
                                 break;
                             }
@@ -321,31 +570,33 @@ static void control_sequence_parse(uint8_t c) {
                         if (b == true)
                             break;
                     }
-                    if (r)
-                        scroll_enable();
                     break;
                 }
                 case 2:
+                case 3:
                     clear(false);
                     break;
-                default:
+            }
+            break;
+        case '@':
+            for (size_t i = term_cols - 1; ; i--) {
+                term_move_character(i + esc_values[0], y, i, y);
+                set_cursor_pos(i, y);
+                raw_putchar(' ');
+                if (i == x) {
                     break;
+                }
             }
+            set_cursor_pos(x, y);
             break;
-        case 'P': {
-            bool r = scroll_disable();
-            int x, y;
-            get_cursor_pos(&x, &y);
-            for (int i = x + esc_values[0]; i < term_cols; i++)
+        case 'P':
+            for (size_t i = x + esc_values[0]; i < term_cols; i++)
                 term_move_character(i - esc_values[0], y, i, y);
             set_cursor_pos(term_cols - esc_values[0], y);
-            for (int i = 0; i < esc_values[0]; i++)
+            for (size_t i = 0; i < esc_values[0]; i++)
                 raw_putchar(' ');
             set_cursor_pos(x, y);
-            if (r)
-                scroll_enable();
             break;
-        }
         case 'm':
             sgr();
             break;
@@ -355,89 +606,253 @@ static void control_sequence_parse(uint8_t c) {
         case 'u':
             set_cursor_pos(saved_cursor_x, saved_cursor_y);
             break;
-        case 'K': {
-            bool r = scroll_disable();
-            int x, y;
-            get_cursor_pos(&x, &y);
+        case 'K':
             switch (esc_values[0]) {
                 case 0: {
-                    for (int i = x; i < term_cols; i++)
+                    for (size_t i = x; i < term_cols; i++)
                         raw_putchar(' ');
                     set_cursor_pos(x, y);
                     break;
                 }
                 case 1: {
                     set_cursor_pos(0, y);
-                    for (int i = 0; i < x; i++)
+                    for (size_t i = 0; i < x; i++)
                         raw_putchar(' ');
                     break;
                 }
                 case 2: {
                     set_cursor_pos(0, y);
-                    for (int i = 0; i < term_cols; i++)
+                    for (size_t i = 0; i < term_cols; i++)
                         raw_putchar(' ');
                     set_cursor_pos(x, y);
                     break;
                 }
             }
-            if (r)
-                scroll_enable();
             break;
-        }
-        default:
+        case 'r':
+            scroll_top_margin = 0;
+            scroll_bottom_margin = 0;
+            if (esc_values_i > 0) {
+                scroll_top_margin = esc_values[0] - 1;
+            }
+            if (esc_values_i > 1) {
+                scroll_bottom_margin = esc_values[1];
+            }
+            if (scroll_top_margin >= (scroll_bottom_margin - 1)) {
+                scroll_top_margin = 0;
+                scroll_bottom_margin = term_rows;
+            }
+            set_cursor_pos(0, 0);
+            break;
+        case 'l':
+        case 'h':
+            mode_toggle(c);
             break;
     }
 
+    if (r)
+        scroll_enable();
+
 cleanup:
     control_sequence = false;
     escape = false;
 }
 
 static void escape_parse(uint8_t c) {
+    escape_offset++;
+
     if (control_sequence == true) {
         control_sequence_parse(c);
         return;
     }
 
+    if (csi == true) {
+        csi = false;
+        goto is_csi;
+    }
+
+    size_t x, y;
+    get_cursor_pos(&x, &y);
+
     switch (c) {
-        case '\e':
-            escape = false;
-            raw_putchar(c);
-            break;
         case '[':
-            for (int i = 0; i < MAX_ESC_VALUES; i++)
+is_csi:
+            for (size_t i = 0; i < MAX_ESC_VALUES; i++)
                 esc_values[i] = 0;
             esc_values_i = 0;
             rrr = false;
             control_sequence = true;
+            return;
+        case 'c':
+            term_reinit();
+            clear(true);
             break;
-        default:
-            escape = false;
+        case 'D':
+            if (y == scroll_bottom_margin - 1) {
+                term_scroll();
+                set_cursor_pos(x, y);
+            } else {
+                set_cursor_pos(x, y + 1);
+            }
             break;
+        case 'E':
+            if (y == scroll_bottom_margin - 1) {
+                term_scroll();
+                set_cursor_pos(0, y);
+            } else {
+                set_cursor_pos(0, y + 1);
+            }
+            break;
+        case 'M':
+            // "Reverse linefeed"
+            set_cursor_pos(x, y - 1);
+            break;
+        case 'Z':
+            term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0);
+            break;
+        case '(':
+        case ')':
+            g_select = c - '\'';
+            break;
+        case '\e':
+            if (term_runtime == false) {
+                raw_putchar(c);
+            }
+            break;
+    }
+
+    escape = false;
+}
+
+static uint8_t dec_special_to_cp437(uint8_t c) {
+    switch (c) {
+        case '`': return 0x04;
+        case '0': return 0xdb;
+        case '-': return 0x18;
+        case ',': return 0x1b;
+        case '.': return 0x19;
+        case 'a': return 0xb1;
+        case 'f': return 0xf8;
+        case 'g': return 0xf1;
+        case 'h': return 0xb0;
+        case 'j': return 0xd9;
+        case 'k': return 0xbf;
+        case 'l': return 0xda;
+        case 'm': return 0xc0;
+        case 'n': return 0xc5;
+        case 'q': return 0xc4;
+        case 's': return 0x5f;
+        case 't': return 0xc3;
+        case 'u': return 0xb4;
+        case 'v': return 0xc1;
+        case 'w': return 0xc2;
+        case 'x': return 0xb3;
+        case 'y': return 0xf3;
+        case 'z': return 0xf2;
+        case '~': return 0xfa;
+        case '_': return 0xff;
+        case '+': return 0x1a;
+        case '{': return 0xe3;
+        case '}': return 0x9c;
     }
+
+    return c;
 }
 
-static void term_putchar(uint8_t c) {
+void term_putchar(uint8_t c) {
+    if (discard_next || c == 0x18 || c == 0x1a) {
+        discard_next = false;
+        escape = false;
+        csi = false;
+        control_sequence = false;
+        g_select = 0;
+        return;
+    }
+
     if (escape == true) {
         escape_parse(c);
         return;
     }
 
+    if (g_select) {
+        g_select--;
+        switch (c) {
+            case 'B':
+                charsets[g_select] = CHARSET_DEFAULT; break;
+            case '0':
+                charsets[g_select] = CHARSET_DEC_SPECIAL; break;
+        }
+        g_select = 0;
+        return;
+    }
+
+    size_t x, y;
+    get_cursor_pos(&x, &y);
+
     switch (c) {
-        case '\0':
-            break;
+        case 0x7f:
+            return;
+        case 0x9b:
+            csi = true;
+            // FALLTHRU
         case '\e':
-            escape = 1;
+            escape_offset = 0;
+            escape = true;
             return;
         case '\t':
-            if ((get_cursor_pos_x() / TERM_TABSIZE + 1) >= term_cols)
-                break;
-            set_cursor_pos((get_cursor_pos_x() / TERM_TABSIZE + 1) * TERM_TABSIZE, get_cursor_pos_y());
-            break;
+            if ((x / TERM_TABSIZE + 1) >= term_cols) {
+                set_cursor_pos(term_cols - 1, y);
+                return;
+            }
+            set_cursor_pos((x / TERM_TABSIZE + 1) * TERM_TABSIZE, y);
+            return;
+        case 0x0b:
+        case 0x0c:
+        case '\n':
+            if (y == scroll_bottom_margin - 1) {
+                term_scroll();
+                set_cursor_pos(0, y);
+            } else {
+                set_cursor_pos(0, y + 1);
+            }
+            return;
+        case '\b':
+            set_cursor_pos(x - 1, y);
+            return;
+        case '\r':
+            set_cursor_pos(0, y);
+            return;
         case '\a':
+            // The bell is handled by the kernel
+            if (term_callback != NULL)
+                term_callback(TERM_CB_BELL, 0, 0, 0);
+            return;
+        case 14:
+            // Move to G1 set
+            current_charset = 1;
+            return;
+        case 15:
+            // Move to G0 set
+            current_charset = 0;
+            return;
+    }
+
+    if (insert_mode == true) {
+        for (size_t i = term_cols - 1; ; i--) {
+            term_move_character(i + 1, y, i, y);
+            if (i == x) {
+                break;
+            }
+        }
+    }
+
+    // Translate character set
+    switch (charsets[current_charset]) {
+        case CHARSET_DEFAULT:
             break;
-        default:
-            raw_putchar(c);
-            break;
+        case CHARSET_DEC_SPECIAL:
+            c = dec_special_to_cp437(c);
     }
+
+    raw_putchar(c);
 }
diff --git a/stage23/menu.c b/stage23/menu.c
index ef270cb5..c5ce73cb 100644
--- a/stage23/menu.c
+++ b/stage23/menu.c
@@ -117,7 +117,7 @@ static int validate_line(const char *buffer) {
     if (buffer[0] == '#')
         return TOK_COMMENT;
     char keybuf[64];
-    int i;
+    size_t i;
     for (i = 0; buffer[i] && i < 64; i++) {
         if (buffer[i] == '=') goto found_equals;
         keybuf[i] = buffer[i];
@@ -176,7 +176,7 @@ refresh:
     clear(true);
     disable_cursor();
     {
-        int x, y;
+        size_t x, y;
         print("\n");
         get_cursor_pos(&x, &y);
         set_cursor_pos(term_cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
@@ -187,7 +187,7 @@ refresh:
     print("    \e[32mESC\e[0m Discard and Exit    \e[32mF10\e[0m Boot\n");
 
     print("\n\xda");
-    for (int i = 0; i < term_cols - 2; i++) {
+    for (size_t i = 0; i < term_cols - 2; i++) {
         switch (i) {
             case 1: case 2: case 3:
                 if (window_offset > 0) {
@@ -196,7 +196,7 @@ refresh:
                 }
                 // FALLTHRU
             default: {
-                int title_length = strlen(title);
+                size_t title_length = strlen(title);
                 if (i == (term_cols / 2) - DIV_ROUNDUP(title_length, 2) - 1) {
                     print("%s", title);
                     i += title_length - 1;
@@ -208,7 +208,7 @@ refresh:
     }
     print("\xbf\xb3");
 
-    int cursor_x, cursor_y;
+    size_t cursor_x, cursor_y;
     size_t current_line = 0, line_offset = 0, window_size = _window_size;
     bool printed_cursor = false;
     int token_type = validate_line(buffer);
@@ -217,7 +217,7 @@ refresh:
         if (buffer[i] == '\n'
          && current_line <  window_offset + window_size
          && current_line >= window_offset) {
-            int x, y;
+            size_t x, y;
             get_cursor_pos(&x, &y);
             if (i == cursor_offset) {
                 cursor_x = x;
@@ -305,7 +305,7 @@ refresh:
 
     // syntax error alert
     if (validation_enabled) {
-        int x, y;
+        size_t x, y;
         get_cursor_pos(&x, &y);
         set_cursor_pos(0, term_rows-1);
         scroll_disable();
@@ -319,7 +319,7 @@ refresh:
     }
 
     if (current_line - window_offset < window_size) {
-        int x, y;
+        size_t x, y;
         for (size_t i = 0; i < (window_size - (current_line - window_offset)) - 1; i++) {
             get_cursor_pos(&x, &y);
             set_cursor_pos(term_cols - 1, y);
@@ -330,7 +330,7 @@ refresh:
         print("\xb3\xc0");
     }
 
-    for (int i = 0; i < term_cols - 2; i++) {
+    for (size_t i = 0; i < term_cols - 2; i++) {
         switch (i) {
             case 1: case 2: case 3:
                 if (current_line - window_offset >= window_size) {
@@ -419,10 +419,10 @@ refresh:
     goto refresh;
 }
 
-static int print_tree(const char *shift, int level, int base_index, int selected_entry,
+static size_t print_tree(const char *shift, size_t level, size_t base_index, size_t selected_entry,
                       struct menu_entry *current_entry,
                       struct menu_entry **selected_menu_entry) {
-    int max_entries = 0;
+    size_t max_entries = 0;
 
     bool no_print = false;
     if (shift == NULL) {
@@ -434,15 +434,18 @@ static int print_tree(const char *shift, int level, int base_index, int selected
             break;
         if (!no_print) print("%s", shift);
         if (level) {
-            for (int i = level - 1; i > 0; i--) {
+            for (size_t i = level - 1; ; i--) {
                 struct menu_entry *actual_parent = current_entry;
-                for (int j = 0; j < i; j++)
+                for (size_t j = 0; j < i; j++)
                     actual_parent = actual_parent->parent;
                 if (actual_parent->next != NULL) {
                     if (!no_print) print(" \xb3");
                 } else {
                     if (!no_print) print("  ");
                 }
+                if (i == 0) {
+                    break;
+                }
             }
             if (current_entry->next == NULL) {
                 if (!no_print) print(" \xc0");
@@ -482,7 +485,7 @@ char *menu(char **cmdline) {
     bool skip_timeout = false;
     struct menu_entry *selected_menu_entry = NULL;
 
-    int selected_entry = 0;
+    size_t selected_entry = 0;
     char *default_entry = config_get_value(NULL, 0, "DEFAULT_ENTRY");
     if (default_entry != NULL) {
         selected_entry = strtoui(default_entry, NULL, 10);
@@ -490,7 +493,7 @@ char *menu(char **cmdline) {
             selected_entry--;
     }
 
-    int timeout = 5;
+    size_t timeout = 5;
     char *timeout_config = config_get_value(NULL, 0, "TIMEOUT");
     if (timeout_config != NULL) {
         if (!strcmp(timeout_config, "no"))
@@ -522,7 +525,7 @@ char *menu(char **cmdline) {
     char *graphics = "yes";
 #endif
     if (graphics != NULL && !strcmp(graphics, "yes")) {
-        int req_width = 0, req_height = 0, req_bpp = 0;
+        size_t req_width = 0, req_height = 0, req_bpp = 0;
 
         char *menu_resolution = config_get_value(NULL, 0, "MENU_RESOLUTION");
         if (menu_resolution != NULL)
@@ -538,7 +541,7 @@ char *menu(char **cmdline) {
 refresh:
     clear(true);
     {
-        int x, y;
+        size_t x, y;
         print("\n");
         get_cursor_pos(&x, &y);
         set_cursor_pos(term_cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
@@ -563,16 +566,16 @@ refresh:
     }
 
     {   // Draw box around boot menu
-        int x, y;
+        size_t x, y;
         get_cursor_pos(&x, &y);
 
         print("\xda");
-        for (int i = 0; i < term_cols - 2; i++) {
+        for (size_t i = 0; i < term_cols - 2; i++) {
             print("\xc4");
         }
         print("\xbf");
 
-        for (int i = y + 1; i < term_rows - 2; i++) {
+        for (size_t i = y + 1; i < term_rows - 2; i++) {
             set_cursor_pos(0, i);
             print("\xb3");
             set_cursor_pos(term_cols - 1, i);
@@ -580,7 +583,7 @@ refresh:
         }
 
         print("\xc0");
-        for (int i = 0; i < term_cols - 2; i++) {
+        for (size_t i = 0; i < term_cols - 2; i++) {
             print("\xc4");
         }
         print("\xd9");
@@ -588,11 +591,11 @@ refresh:
         set_cursor_pos(x, y + 2);
     }
 
-    int max_entries = print_tree("\xb3   ", 0, 0, selected_entry, menu_tree,
+    size_t max_entries = print_tree("\xb3   ", 0, 0, selected_entry, menu_tree,
                                  &selected_menu_entry);
 
     {
-        int x, y;
+        size_t x, y;
         get_cursor_pos(&x, &y);
         set_cursor_pos(0, 3);
         if (editor_enabled && selected_menu_entry->sub == NULL) {
@@ -611,7 +614,7 @@ refresh:
 
     if (skip_timeout == false) {
         print("\n\n");
-        for (int i = timeout; i; i--) {
+        for (size_t i = timeout; i; i--) {
             set_cursor_pos(0, term_rows - 1);
             scroll_disable();
             print("\e[32mBooting automatically in \e[92m%u\e[32m, press any key to stop the countdown...\e[0m", i);
@@ -641,8 +644,10 @@ refresh:
 timeout_aborted:
         switch (c) {
             case GETCHAR_CURSOR_UP:
-                if (--selected_entry == -1)
+                if (selected_entry == 0)
                     selected_entry = max_entries - 1;
+                else
+                    selected_entry--;
                 goto refresh;
             case GETCHAR_CURSOR_DOWN:
                 if (++selected_entry == max_entries)
diff --git a/stage23/protos/chainload.c b/stage23/protos/chainload.c
index a0f2bd92..1e3fb648 100644
--- a/stage23/protos/chainload.c
+++ b/stage23/protos/chainload.c
@@ -97,7 +97,7 @@ void chainload(char *config) {
         drive = val;
     }
 
-    int rows, cols;
+    size_t rows, cols;
     init_vga_textmode(&rows, &cols, false);
 
     struct volume *p = volume_get_by_coord(false, drive, part);
@@ -131,7 +131,7 @@ void chainload(char *config) {
 
     term_deinit();
 
-    int req_width = 0, req_height = 0, req_bpp = 0;
+    size_t req_width = 0, req_height = 0, req_bpp = 0;
 
     char *resolution = config_get_value(config, 0, "RESOLUTION");
     if (resolution != NULL)
diff --git a/stage23/protos/linux.c b/stage23/protos/linux.c
index f8e001ed..d8dc1038 100644
--- a/stage23/protos/linux.c
+++ b/stage23/protos/linux.c
@@ -488,7 +488,7 @@ void linux_load(char *config, char *cmdline) {
 
     struct screen_info *screen_info = &boot_params->screen_info;
 
-    int req_width = 0, req_height = 0, req_bpp = 0;
+    size_t req_width = 0, req_height = 0, req_bpp = 0;
 
     char *resolution = config_get_value(config, 0, "RESOLUTION");
     if (resolution != NULL)
diff --git a/stage23/protos/multiboot1.c b/stage23/protos/multiboot1.c
index 4541be13..d89780b0 100644
--- a/stage23/protos/multiboot1.c
+++ b/stage23/protos/multiboot1.c
@@ -180,9 +180,9 @@ void multiboot1_load(char *config, char *cmdline) {
     term_deinit();
 
     if (header.flags & (1 << 2)) {
-        int req_width  = header.fb_width;
-        int req_height = header.fb_height;
-        int req_bpp    = header.fb_bpp;
+        size_t req_width  = header.fb_width;
+        size_t req_height = header.fb_height;
+        size_t req_bpp    = header.fb_bpp;
 
         if (header.fb_mode == 0) {
             char *resolution = config_get_value(config, 0, "RESOLUTION");
@@ -209,7 +209,7 @@ void multiboot1_load(char *config, char *cmdline) {
 #if uefi == 1
             panic("multiboot1: Cannot use text mode with UEFI.");
 #elif bios == 1
-            int rows, cols;
+            size_t rows, cols;
             init_vga_textmode(&rows, &cols, false);
 
             multiboot1_info.fb_addr    = 0xB8000;
@@ -228,7 +228,7 @@ void multiboot1_load(char *config, char *cmdline) {
 #if uefi == 1
         panic("multiboot1: Cannot use text mode with UEFI.");
 #elif bios == 1
-        int rows, cols;
+        size_t rows, cols;
         init_vga_textmode(&rows, &cols, false);
 #endif
     }
diff --git a/stage23/protos/stivale.c b/stage23/protos/stivale.c
index 0f6224d0..03b0e7b2 100644
--- a/stage23/protos/stivale.c
+++ b/stage23/protos/stivale.c
@@ -249,9 +249,9 @@ void stivale_load(char *config, char *cmdline) {
     term_deinit();
 
     if (stivale_hdr.flags & (1 << 0)) {
-        int req_width  = stivale_hdr.framebuffer_width;
-        int req_height = stivale_hdr.framebuffer_height;
-        int req_bpp    = stivale_hdr.framebuffer_bpp;
+        size_t req_width  = stivale_hdr.framebuffer_width;
+        size_t req_height = stivale_hdr.framebuffer_height;
+        size_t req_bpp    = stivale_hdr.framebuffer_bpp;
 
         char *resolution = config_get_value(config, 0, "RESOLUTION");
         if (resolution != NULL)
@@ -281,7 +281,7 @@ void stivale_load(char *config, char *cmdline) {
 #if uefi == 1
         panic("stivale: Cannot use text mode with UEFI.");
 #elif bios == 1
-        int rows, cols;
+        size_t rows, cols;
         init_vga_textmode(&rows, &cols, false);
 #endif
     }
diff --git a/stage23/protos/stivale2.c b/stage23/protos/stivale2.c
index 945f962c..414906d2 100644
--- a/stage23/protos/stivale2.c
+++ b/stage23/protos/stivale2.c
@@ -57,7 +57,8 @@ static void *get_tag(struct stivale2_header *s, uint64_t id) {
 #if defined (__i386__)
 extern symbol stivale2_term_write_entry;
 void *stivale2_rt_stack = NULL;
-void *stivale2_term_buf = NULL;
+uint64_t stivale2_term_callback_ptr = 0;
+void stivale2_term_callback(uint64_t, uint64_t, uint64_t, uint64_t);
 #endif
 
 void stivale2_load(char *config, char *cmdline, bool pxe, void *efi_system_table) {
@@ -381,7 +382,7 @@ failed_to_load_header_section:
 
     struct stivale2_header_tag_framebuffer *hdrtag = get_tag(&stivale2_hdr, STIVALE2_HEADER_TAG_FRAMEBUFFER_ID);
 
-    int req_width = 0, req_height = 0, req_bpp = 0;
+    size_t req_width = 0, req_height = 0, req_bpp = 0;
 
     if (hdrtag != NULL) {
         req_width  = hdrtag->framebuffer_width;
@@ -393,20 +394,42 @@ failed_to_load_header_section:
             parse_resolution(&req_width, &req_height, &req_bpp, resolution);
     }
 
+    char *textmode_str = config_get_value(config, 0, "TEXTMODE");
+    bool textmode = textmode_str != NULL && strcmp(textmode_str, "yes") == 0;
+
     struct stivale2_header_tag_terminal *terminal_hdr_tag = get_tag(&stivale2_hdr, STIVALE2_HEADER_TAG_TERMINAL_ID);
 
-    if (bits == 64 && terminal_hdr_tag != NULL && hdrtag != NULL) {
-        term_vbe(req_width, req_height);
+    if (bits == 64 && terminal_hdr_tag != NULL && (hdrtag != NULL || textmode)) {
+        if (textmode) {
+#if bios == 1
+            term_textmode();
+#elif uefi == 1
+            panic("stivale2: Text mode not supported on UEFI");
+#endif
+        } else {
+            term_vbe(req_width, req_height);
 
-        if (current_video_mode < 0) {
-            panic("stivale2: Failed to initialise terminal");
-        }
+            if (current_video_mode < 0) {
+                panic("stivale2: Failed to initialise terminal");
+            }
 
-        fb = &fbinfo;
+            fb = &fbinfo;
+        }
 
         struct stivale2_struct_tag_terminal *tag = ext_mem_alloc(sizeof(struct stivale2_struct_tag_terminal));
         tag->tag.identifier = STIVALE2_STRUCT_TAG_TERMINAL_ID;
 
+        if (terminal_hdr_tag->flags & (1 << 0)) {
+            // We provide callback
+            tag->flags |= (1 << 2);
+#if defined (__i386__)
+            term_callback = stivale2_term_callback;
+            stivale2_term_callback_ptr = terminal_hdr_tag->callback;
+#elif defined (__x86_64__)
+            term_callback = (void *)terminal_hdr_tag->callback;
+#endif
+        }
+
         // We provide max allowed string length
         tag->flags |= (1 << 1);
 
@@ -415,15 +438,13 @@ failed_to_load_header_section:
             stivale2_rt_stack = ext_mem_alloc(8192);
         }
 
-        stivale2_term_buf = ext_mem_alloc(8192);
-
         tag->term_write = (uintptr_t)(void *)stivale2_term_write_entry;
-        tag->max_length = 8192;
 #elif defined (__x86_64__)
         tag->term_write = (uintptr_t)term_write;
-        tag->max_length = 0;
 #endif
 
+        tag->max_length = 0;
+
         // We provide rows and cols
         tag->flags |= (1 << 0);
         tag->cols = term_cols;
@@ -431,7 +452,13 @@ failed_to_load_header_section:
 
         append_tag(&stivale2_struct, (struct stivale2_tag *)tag);
 
-        goto skip_modeset;
+        if (textmode) {
+#if bios == 1
+            goto have_tm_tag;
+#endif
+        } else {
+            goto have_fb_tag;
+        }
     } else {
         fb = &_fb;
     }
@@ -441,7 +468,7 @@ failed_to_load_header_section:
         term_deinit();
 
         if (fb_init(fb, req_width, req_height, req_bpp)) {
-skip_modeset:;
+have_fb_tag:;
             struct stivale2_struct_tag_framebuffer *tag = ext_mem_alloc(sizeof(struct stivale2_struct_tag_framebuffer));
             tag->tag.identifier = STIVALE2_STRUCT_TAG_FRAMEBUFFER_ID;
 
@@ -468,9 +495,10 @@ skip_modeset:;
 #if uefi == 1
         panic("stivale2: Cannot use text mode with UEFI.");
 #elif bios == 1
-        int rows, cols;
+        size_t rows, cols;
         init_vga_textmode(&rows, &cols, false);
 
+have_tm_tag:;
         struct stivale2_struct_tag_textmode *tmtag = ext_mem_alloc(sizeof(struct stivale2_struct_tag_textmode));
         tmtag->tag.identifier = STIVALE2_STRUCT_TAG_TEXTMODE_ID;
 
@@ -628,7 +656,9 @@ skip_modeset:;
     }
 
     // Clear terminal for kernels that will use the stivale2 terminal
-    term_write("\e[2J\e[H", 7);
+    term_write((uint64_t)(uintptr_t)("\e[2J\e[H"), 7);
+
+    term_runtime = true;
 
     stivale_spinup(bits, want_5lv, &pagemap, entry_point,
                    REPORTED_ADDR((uint64_t)(uintptr_t)&stivale2_struct),
diff --git a/stage23/protos/stivale2_rt.asm b/stage23/protos/stivale2_rt.asm
index 26517ca5..b5c05e8b 100644
--- a/stage23/protos/stivale2_rt.asm
+++ b/stage23/protos/stivale2_rt.asm
@@ -8,17 +8,64 @@ user_ds: resq 1
 user_es: resq 1
 user_ss: resq 1
 
-%define MAX_TERM_BUF 8192
-
 section .text
 
 extern term_write
-extern stivale2_term_buf
 extern stivale2_rt_stack
+extern stivale2_term_callback_ptr
+
+global stivale2_term_callback
+stivale2_term_callback:
+bits 32
+    push ebp
+    mov ebp, esp
+
+    push ebx
+    push esi
+    push edi
 
+    ; Go 64
+    push 0x28
+    push .mode64
+    retfd
 bits 64
+  .mode64:
+    mov eax, 0x30
+    mov ds, ax
+    mov es, ax
+    mov ss, ax
+
+    mov rdi, [rbp + 8]
+    mov rsi, [rbp + 16]
+    mov rdx, [rbp + 24]
+    mov rcx, [rbp + 32]
+
+    mov rbx, rsp
+    mov rsp, [user_stack]
+    call [stivale2_term_callback_ptr]
+    mov rsp, rbx
+
+    ; Go 32
+    push 0x18
+    push .mode32
+    retfq
+bits 32
+  .mode32:
+    mov eax, 0x20
+    mov ds, ax
+    mov es, ax
+    mov ss, ax
+
+    pop edi
+    pop esi
+    pop ebx
+    pop ebp
+
+    ret
+
 global stivale2_term_write_entry
 stivale2_term_write_entry:
+bits 64
     push rbx
     push rbp
     push r12
@@ -35,14 +82,7 @@ stivale2_term_write_entry:
     mov word [user_ss], ss
 
     push rsi
-    mov rcx, rsi
-    mov rax, MAX_TERM_BUF
-    cmp rcx, rax
-    cmovg rcx, rax
-    mov rsi, rdi
-    mov edi, [stivale2_term_buf]
-    rep movsb
-    pop rsi
+    push rdi
 
     push 0x18
     push .mode32
@@ -53,10 +93,10 @@ bits 32
     mov ds, ax
     mov es, ax
     mov ss, ax
-    push esi
-    push dword [stivale2_term_buf]
+
     call term_write
-    add esp, 8
+    add esp, 16
+
     push dword [user_cs]
     push .mode64
     retfd
diff --git a/stage23/protos/stivale2_rt.asm32 b/stage23/protos/stivale2_rt.asm32
index d39e5a3b..723a92aa 100644
--- a/stage23/protos/stivale2_rt.asm32
+++ b/stage23/protos/stivale2_rt.asm32
@@ -10,13 +10,69 @@ user_ds: resq 1
 user_es: resq 1
 user_ss: resq 1
 
-%define MAX_TERM_BUF 8192
-
 section .text
 
 extern term_write
-extern stivale2_term_buf
 extern stivale2_rt_stack
+extern stivale2_term_callback_ptr
+
+global stivale2_term_callback
+stivale2_term_callback:
+bits 32
+    push ebp
+    mov ebp, esp
+
+    push ebx
+    push esi
+    push edi
+
+    ; Go 64
+    push 0x28
+    call .p1
+  .p1:
+    add dword [esp], .mode64 - .p1
+    retfd
+bits 64
+  .mode64:
+    mov eax, 0x30
+    mov ds, ax
+    mov es, ax
+    mov ss, ax
+
+    mov rdi, [rbp + 8]
+    mov rsi, [rbp + 16]
+    mov rdx, [rbp + 24]
+    mov rcx, [rbp + 32]
+
+    call .get_got
+  .get_got:
+    pop rax
+    add rax, _GLOBAL_OFFSET_TABLE_ + $$ - .get_got wrt ..gotpc
+
+    mov rbx, rsp
+    mov rsp, [rax + user_stack wrt ..gotoff]
+    call [rax + stivale2_term_callback_ptr wrt ..gotoff]
+    mov rsp, rbx
+
+    ; Go 32
+    push 0x18
+    call .p2
+  .p2:
+    add qword [rsp], .mode32 - .p2
+    retfq
+bits 32
+  .mode32:
+    mov eax, 0x20
+    mov ds, ax
+    mov es, ax
+    mov ss, ax
+
+    pop edi
+    pop esi
+    pop ebx
+    pop ebp
+
+    ret
 
 bits 64
 global stivale2_term_write_entry
@@ -42,21 +98,12 @@ stivale2_term_write_entry:
     mov word [rbx + user_ss wrt ..gotoff], ss
 
     push rsi
-    mov rcx, rsi
-    mov rax, MAX_TERM_BUF
-    cmp rcx, rax
-    cmovg rcx, rax
-    mov rsi, rdi
-    mov edi, [rbx + stivale2_term_buf wrt ..gotoff]
-    rep movsb
-    pop rsi
+    push rdi
 
     push 0x18
     call .p1
   .p1:
-    pop rax
-    add rax, 8
-    push rax
+    add qword [rsp], .mode32 - .p1
     retfq
 bits 32
   .mode32:
@@ -64,16 +111,14 @@ bits 32
     mov ds, ax
     mov es, ax
     mov ss, ax
-    push esi
-    push dword [ebx + stivale2_term_buf wrt ..gotoff]
+
     call term_write
-    add esp, 8
+    add esp, 16
+
     push dword [ebx + user_cs wrt ..gotoff]
     call .p2
   .p2:
-    pop eax
-    add eax, 6
-    push eax
+    add dword [esp], .mode64 - .p2
     retfd
 bits 64
   .mode64:
tab: 248 wrap: offon