#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include "panic.h"
#include "curve.h"
#include "procfs.h"
#include "memory.h"
#include "font.h"
#include "menu.h"
#include "tooltip.h"
SDL_Window *window;
SDL_Renderer *renderer;
static int window_width = 640;
static int window_height = 480;
static double scale = 1;
static double pos_x = 1024;
static double pos_y = 0;
static bool brush_bit = true;
static pid_t pid = -1;
static bool is_child = false;
static bool stopped = false;
static page_list pages = {.fd = -1, .first = NULL};
static procfs_map *mappings = NULL;
static int n_mappings = 0;
static menu *current_menu = NULL;
static tooltip current_tooltip = {0};
static double get_scale() {
if (scale >= 1) {
return SDL_round(scale);
}
return scale;
}
static void add_scale(double amount) {
float mx, my;
SDL_GetMouseState(&mx, &my);
double old_scale = get_scale();
scale += amount;
if (scale > 50) scale = 50;
else if (scale < 0.125) scale = 0.125;
pos_x -= mx/get_scale() - mx/old_scale;
pos_y -= my/get_scale() - my/old_scale;
}
static void absolute_pos(double *x, double *y) {
*x = *x / get_scale() + pos_x;
*y = *y / get_scale() + pos_y;
}
static void go_to(uintptr_t addr) {
int ipos_x, ipos_y;
to_pos(addr, &ipos_x, &ipos_y);
pos_x = ipos_x; pos_y = ipos_y;
pos_x -= window_width / 2 / get_scale();
pos_y -= window_height / 2 / get_scale();
}
static void detach() {
if (pid == -1) return;
if (pages.fd != -1) {
int fd = pages.fd;
free_page_list(&pages);
close(fd);
}
if (is_child) {
kill(pid, SIGKILL);
while (true) {
int status;
pid_t w = waitpid(pid, &status, 0);
if (w == -1) {
break;
}
if (w == pid && WIFEXITED(status)) {
break;
}
}
}
pid = -1;
is_child = false;
}
static bool attach(pid_t p) {
detach();
pid = p;
int fd = procfs_open(pid);
if (fd == -1) {
pid = -1;
fprintf(stderr, "error attaching to pid %d: %s\n", p, strerror(errno));
return false;
}
init_page_list(&pages, fd);
stopped = false;
n_mappings = procfs_maps(pid, &mappings);
for (int i = 0; i < n_mappings; i++) {
if (strcmp("[stack]", mappings[i].name) == 0) {
go_to(mappings[i].max);
}
}
return true;
}
static bool spawn(char *const cmdline[]) {
detach();
int fildes[2];
if (pipe(fildes) == -1) {
fprintf(stderr, "error starting child: pipe: %d\n", strerror(errno));
return false;
}
pid_t child = fork();
if (child == -1) {
fprintf(stderr, "error starting child: %d\n", strerror(errno));
close(fildes[0]);
close(fildes[1]);
return false;
}
if (child == 0) {
close(fildes[0]);
if (fcntl(fildes[1], F_SETFD, FD_CLOEXEC) == -1) {
perror("fcntl");
exit(-1);
}
if (execvp(cmdline[0], cmdline) == -1) {
const char *err = strerror(errno);
write(fildes[1], err, strlen(err));
exit(-1);
}
}
close(fildes[1]);
char child_err[2048] = {0};
ssize_t n = read(fildes[0], child_err, 2047);
close(fildes[0]);
if (n == -1) {
fprintf(stderr,
"error starting child: read pipe: %d\n", strerror(errno)
);
goto err;
}
if (n != 0) {
fprintf(stderr, "%s: %s\n", cmdline[0], child_err);
}
if (attach(child)) {
is_child = true;
return true;
}
err:
kill(child, SIGKILL);
while (true) {
int status;
pid_t w = waitpid(child, &status, 0);
if (w == -1) {
break;
}
if (w == child && WIFEXITED(status)) {
break;
}
}
return false;
}
static void handle_child() {
int status;
pid_t w = waitpid(pid, &status, WNOHANG);
if (w == -1) {
return;
}
if (w == pid && WIFEXITED(status)) {
is_child = false;
detach();
fprintf(
stderr, "child exited with status %d\n", WEXITSTATUS(status)
);
}
}
static void replace_menu(menu *new) {
if (current_menu) close_menu(current_menu);
current_menu = new;
}
static void map_menu_selected(void *data) {
go_to((uintptr_t) data);
}
static void open_map_menu() {
menu_entry *entries = calloc(n_mappings, sizeof(menu_entry));
for (int i = 0; i < n_mappings; i++) {
entries[i].action = map_menu_selected;
entries[i].data = (void *) mappings[i].base;
snprintf(
entries[i].name, sizeof(entries[i].name), "%p %s",
mappings[i].base, mappings[i].name
);
}
float mx, my;
SDL_GetMouseState(&mx, &my);
replace_menu(create_menu(entries, n_mappings, mx, my));
}
static bool held(SDL_Keycode k) {
const bool *state = SDL_GetKeyboardState(NULL);
return state[SDL_GetScancodeFromKey(k, NULL)];
}
static void handle_key(SDL_Keycode key) {
switch (key) {
case SDLK_SPACE:
if (pid == -1) break;
kill(pid, stopped ? SIGSTOP : SIGCONT);
stopped = !stopped;
break;
case SDLK_E:
brush_bit = !brush_bit;
break;
default:
}
}
static bool handle_event(SDL_Event e) {
if (current_menu) {
if (!menu_handle_event(current_menu, e)) {
close_menu(current_menu);
current_menu = NULL;
}
return true;
}
switch (e.type) {
case SDL_EVENT_KEY_DOWN:
handle_key(e.key.key);
break;
case SDL_EVENT_MOUSE_MOTION:
SDL_MouseButtonFlags state = e.motion.state;
if (state & SDL_BUTTON_LMASK) {
pos_x -= (double) e.motion.xrel / get_scale();
pos_y -= (double) e.motion.yrel / get_scale();
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
uint8_t button = e.button.button;
if (button == SDL_BUTTON_MIDDLE) {
open_map_menu();
} else if (button == SDL_BUTTON_RIGHT && held(SDLK_LSHIFT)) {
open_map_menu();
} else if (button == SDL_BUTTON_RIGHT && held(SDLK_RSHIFT)) {
open_map_menu();
}
break;
case SDL_EVENT_MOUSE_WHEEL:
add_scale(e.wheel.y * get_scale() / 10);
break;
case SDL_EVENT_QUIT:
return false;
break;
default:
}
return true;
}
static void update() {
SDL_GetWindowSize(window, &window_width, &window_height);
if (pid != -1) {
n_mappings = procfs_maps(pid, &mappings);
if (is_child) handle_child();
}
if (current_menu) return;
if (held(SDLK_W)) {
pos_y -= 5 / get_scale();
}
if (held(SDLK_S)) {
pos_y += 5 / get_scale();
}
if (held(SDLK_A)) {
pos_x -= 5 / get_scale();
}
if (held(SDLK_D)) {
pos_x += 5 / get_scale();
}
float mx, my;
static bool mouse_held = false;
static double px, py;
SDL_MouseButtonFlags state = SDL_GetMouseState(&mx, &my);
if (state & SDL_BUTTON_RMASK) {
double x2 = mx, y2 = my;
absolute_pos(&x2, &y2);
double x1, y1;
if (mouse_held) {
x1 = px; y1 = py;
} else {
x1 = x2; y1 = y2;
}
px = x2; py = y2;
mouse_held = true;
draw_line(&pages, x1, y1, x2, y2, brush_bit);
} else {
mouse_held = false;
}
double x = mx, y = my;
absolute_pos(&x, &y);
uintptr_t addr = to_addr(x, y);
uintptr_t page_addr = addr - addr % PAGE_SIZE;
static uint8_t page_data[PAGE_SIZE];
if (read_mem(pages.fd, page_addr, page_data, PAGE_SIZE) == 0) {
uint8_t hover_byte = page_data[addr % PAGE_SIZE];
static char tooltip_buf[1024];
int n = snprintf(tooltip_buf, sizeof(tooltip_buf),
"%p\n0x%02x", addr, hover_byte
);
char *p = tooltip_buf + n;
if (hover_byte < 128 && hover_byte != 0) {
p = stpcpy(p, "\n\"");
int min, max;
for (min = addr % PAGE_SIZE; min > 0; min--) {
uint8_t next = page_data[min - 1];
if (next >= 128 || next == 0) break;
}
for (max = addr % PAGE_SIZE; max < PAGE_SIZE; max++) {
uint8_t cur = page_data[max];
if (cur >= 128 || cur == 0) break;
}
while (max - min > 64) {
if (max - addr % PAGE_SIZE > addr % PAGE_SIZE - min) {
max--;
} else {
min++;
}
}
memcpy(p, page_data + min, max - min);
p += max - min;
p = stpcpy(p, "\"");
}
set_tooltip_text(¤t_tooltip, tooltip_buf);
} else {
set_tooltip_text(¤t_tooltip, NULL);
}
}
static SDL_Texture *missing_texture();
static void show_pages() {
double s = get_scale();
int page_x = pos_x / PAGE_WIDTH;
int page_y = pos_y / PAGE_HEIGHT;
double offs_x = SDL_fmod(pos_x, PAGE_WIDTH) * s;
double offs_y = SDL_fmod(pos_y, PAGE_HEIGHT) * s;
double page_width = PAGE_WIDTH * s;
double page_height = PAGE_HEIGHT * s;
double view_width = window_width / page_width;
double view_height = window_height / page_height;
for (int draw_y = -1; draw_y <= view_height + 1; draw_y++) {
for (int draw_x = -1; draw_x <= view_width + 1; draw_x++) {
SDL_FRect src = {0, 0, PAGE_WIDTH, PAGE_HEIGHT};
SDL_FRect dest = {
draw_x * page_width - offs_x, draw_y * page_height - offs_y,
page_width, page_height
};
SDL_Texture *tex = NULL;
if (pid != -1) {
uintptr_t addr = zorder(page_x + draw_x, page_y + draw_y);
addr *= PAGE_SIZE;
page *p = get_page(&pages, addr);
tex = get_texture(p);
}
if (!tex) {
tex = missing_texture();
}
if (s < 0.75) {
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
} else {
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST);
}
SDL_RenderTexture(renderer, tex, &src, &dest);
}
}
free_unused_pages(&pages);
}
static SDL_Texture *missing_texture() {
static SDL_Texture *tex = NULL;
if (!tex) {
tex = must(SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
PAGE_WIDTH, PAGE_HEIGHT
));
uint32_t *pixels; int pitch;
SDL_Rect rect = {0, 0, PAGE_WIDTH, PAGE_HEIGHT};
must(SDL_LockTexture(tex, &rect, (void **) &pixels, &pitch));
for (int i = 0; i < PAGE_WIDTH * PAGE_HEIGHT; i++) {
if (((i / 32) + i / PAGE_WIDTH / 32) % 2) {
pixels[i] = 0xFF151515;
} else {
pixels[i] = 0xFF222222;
}
}
SDL_UnlockTexture(tex);
}
return tex;
}
static void show() {
SDL_GetWindowSize(window, &window_width, &window_height);
SDL_RenderClear(renderer);
show_pages();
SDL_RenderPresent(renderer);
if (current_menu) {
menu_render(current_menu);
hide_tooltip(¤t_tooltip);
} else {
float mx, my;
SDL_GetMouseState(&mx, &my);
show_tooltip(¤t_tooltip, mx, my);
}
}
static void main_loop() {
int last_time = 0, current_time = 0;
while (true) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (!handle_event(e)) {
return;
}
}
update();
show();
last_time = current_time;
current_time = SDL_GetTicks();
int elapsed = current_time - last_time;
if (elapsed < 5) SDL_Delay(5 - elapsed);
}
}
static void usage(const char *name) {
fprintf(stderr, "usage: %s -p <pid>\n", name);
fprintf(stderr, " %s command [arg...]\n", name);
exit(-1);
}
int main(int argc, char *argv[]) {
must(TTF_Init());
load_font();
must(SDL_Init(SDL_INIT_VIDEO));
must(SDL_CreateWindowAndRenderer("core",
window_width, window_height, SDL_WINDOW_RESIZABLE, &window, &renderer
));
SDL_EnableScreenSaver();
SDL_SetRenderVSync(renderer, 1);
if (argc > 1) {
if (argv[1][0] == '-') {
if (strcmp(argv[1], "-p") == 0) {
if (argc < 3) {
usage(argv[0]);
}
pid_t arg = strtol(argv[2], NULL, 10);
if (arg == 0 && errno == EINVAL) {
usage(argv[0]);
}
attach(arg);
} else {
usage(argv[0]);
}
} else {
spawn(argv + 1);
}
}
main_loop();
detach();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}