hal_sim.c (7460B)
1 /* hal_sim.c - simulador de la PicoCalc en la PC (SDL3). El cable se simula con TCP. 2 * 3 * <juego>-picosim [--scale N] [--link-listen PUERTO | --link-connect PUERTO] [--store ARCHIVO] 4 * 5 * Para probar el cable: una ventana con --link-listen 9999 y otra con --link-connect 9999. */ 6 #include <SDL3/SDL.h> 7 #include <arpa/inet.h> 8 #include <errno.h> 9 #include <fcntl.h> 10 #include <netinet/in.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <sys/socket.h> 15 #include <time.h> 16 #include <unistd.h> 17 #include "hal.h" 18 #include "app_config.h" 19 20 static SDL_Window *win; 21 static SDL_Renderer *ren; 22 static SDL_Texture *tex; 23 static uint16_t fb[320 * 320]; 24 static int scale = 2, link_port = 0, lfd = -1, cfd = -1; 25 static bool link_listen; 26 static char store_path[512]; 27 static const char *keys; 28 29 static void span(Surface *s, int x, int y, int n, Color c) 30 { 31 (void)s; 32 uint16_t *p = fb + y * 320 + x; 33 while (n--) *p++ = c; 34 } 35 36 void hal_init(void) {} 37 38 uint32_t hal_ms(void) 39 { 40 struct timespec ts; 41 clock_gettime(CLOCK_MONOTONIC, &ts); 42 return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); 43 } 44 45 void hal_sleep(int ms) { SDL_Delay((Uint32)ms); } 46 47 uint32_t hal_random(void) 48 { 49 struct timespec ts; 50 clock_gettime(CLOCK_REALTIME, &ts); 51 return (uint32_t)(ts.tv_nsec ^ ts.tv_sec ^ (getpid() << 16)); 52 } 53 54 int hal_battery(void) { return -1; } 55 void hal_log(const char *line) { fprintf(stderr, "%s\n", line); } 56 void hal_log_clear(void) {} 57 58 void hal_draw(void (*paint)(Surface *, void *), void *ctx) 59 { 60 Surface s; 61 surf_init(&s, 320, 320, span, 0); 62 /* igual que en la PicoCalc: por franjas, para probar el recorte */ 63 for (int y = 0; y < 320; y += 40) { 64 gfx_band(&s, y, y + 40); 65 paint(&s, ctx); 66 } 67 const char *shot = getenv("APP_SHOT"); 68 if (shot) { 69 FILE *f = fopen(shot, "wb"); 70 if (f) { 71 fprintf(f, "P6 320 320 255\n"); 72 for (int i = 0; i < 320 * 320; i++) { 73 fputc((fb[i] >> 11) << 3, f); fputc(((fb[i] >> 5) & 63) << 2, f); fputc((fb[i] & 31) << 3, f); 74 } 75 fclose(f); 76 } 77 } 78 SDL_UpdateTexture(tex, NULL, fb, 640); 79 SDL_RenderClear(ren); 80 SDL_RenderTexture(ren, tex, NULL, NULL); 81 SDL_RenderPresent(ren); 82 } 83 84 static int map_key(SDL_Keycode k) 85 { 86 switch (k) { 87 case SDLK_UP: return K_UP; 88 case SDLK_DOWN: return K_DOWN; 89 case SDLK_LEFT: return K_LEFT; 90 case SDLK_RIGHT: return K_RIGHT; 91 case SDLK_RETURN: case SDLK_KP_ENTER: return K_OK; 92 case SDLK_ESCAPE: return K_BACK; 93 case SDLK_TAB: return K_TAB; 94 case SDLK_BACKSPACE: return K_BKSP; 95 } 96 return 0; 97 } 98 99 int hal_key(void) 100 { 101 SDL_Event e; 102 while (SDL_PollEvent(&e)) { 103 if (e.type == SDL_EVENT_QUIT) exit(0); 104 if (e.type == SDL_EVENT_KEY_DOWN) { int k = map_key(e.key.key); if (k) return k; } 105 if (e.type == SDL_EVENT_TEXT_INPUT) { const char *t = e.text.text; int c = utf8_next(&t); if (c) return c; } 106 if (e.type == SDL_EVENT_WINDOW_EXPOSED) return 0; /* 0: solo fuerza redibujo */ 107 } 108 if (keys && *keys) { /* APP_KEYS: igual que en guikit.c */ 109 int c = (unsigned char)*keys++; 110 switch (c) { 111 case '~': return K_OK; 112 case '<': return K_BACK; 113 case '>': return K_TAB; 114 case '^': return K_UP; 115 case 'v': return K_DOWN; 116 case '{': return K_LEFT; 117 case '}': return K_RIGHT; 118 case '.': SDL_Delay(300); return -1; 119 case '!': exit(0); 120 } 121 return c; 122 } 123 return -1; 124 } 125 126 /* ------------------------------------------------------------ cable por TCP */ 127 128 static void link_setup(void) 129 { 130 if (!link_port) return; 131 if (link_listen) { 132 lfd = socket(AF_INET, SOCK_STREAM, 0); 133 int one = 1; 134 setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); 135 struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = htons((uint16_t)link_port) }; 136 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 137 if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0 || listen(lfd, 1) < 0) { perror("link"); exit(1); } 138 fcntl(lfd, F_SETFL, O_NONBLOCK); 139 } 140 } 141 142 static void link_connect(void) 143 { 144 if (cfd >= 0 || !link_port) return; 145 if (link_listen) { 146 cfd = accept(lfd, 0, 0); 147 } else { 148 static uint32_t last; 149 if (hal_ms() - last < 1000) return; 150 last = hal_ms(); 151 cfd = socket(AF_INET, SOCK_STREAM, 0); 152 struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = htons((uint16_t)link_port) }; 153 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 154 if (connect(cfd, (struct sockaddr *)&a, sizeof a) < 0) { close(cfd); cfd = -1; return; } 155 } 156 if (cfd >= 0) fcntl(cfd, F_SETFL, O_NONBLOCK); 157 } 158 159 void hal_link_write(const char *buf, int n) 160 { 161 link_connect(); 162 if (cfd < 0) return; /* como un cable desenchufado: se pierde */ 163 while (n > 0) { 164 ssize_t w = send(cfd, buf, (size_t)n, MSG_NOSIGNAL); 165 if (w < 0 && errno == EAGAIN) { SDL_Delay(1); continue; } 166 if (w <= 0) { close(cfd); cfd = -1; return; } 167 buf += w; 168 n -= (int)w; 169 } 170 } 171 172 int hal_link_read(char *buf, int max) 173 { 174 link_connect(); 175 if (cfd < 0 || max <= 0) return 0; 176 ssize_t r = recv(cfd, buf, (size_t)max, 0); 177 if (r == 0 || (r < 0 && errno != EAGAIN)) { close(cfd); cfd = -1; return 0; } 178 return r < 0 ? 0 : (int)r; 179 } 180 181 /* ------------------------------------------------------------ guardado */ 182 183 bool hal_store_save(const char *data, int n) 184 { 185 FILE *f = fopen(store_path, "wb"); 186 if (!f) return false; 187 fwrite(data, 1, (size_t)n, f); 188 fclose(f); 189 return true; 190 } 191 192 int hal_store_load(char *buf, int max) 193 { 194 FILE *f = fopen(store_path, "rb"); 195 if (!f) return 0; 196 int n = (int)fread(buf, 1, (size_t)max, f); 197 fclose(f); 198 return n; 199 } 200 201 int main(int argc, char **argv) 202 { 203 const char *home = getenv("HOME"); 204 snprintf(store_path, sizeof store_path, "%s/.local/share/" APP_NAME "/picosim.sav", home ? home : "."); 205 for (int i = 1; i < argc; i++) { 206 if (!strcmp(argv[i], "--scale") && i + 1 < argc) scale = atoi(argv[++i]); 207 else if (!strcmp(argv[i], "--link-listen") && i + 1 < argc) { link_port = atoi(argv[++i]); link_listen = true; } 208 else if (!strcmp(argv[i], "--link-connect") && i + 1 < argc) link_port = atoi(argv[++i]); 209 else if (!strcmp(argv[i], "--store") && i + 1 < argc) snprintf(store_path, sizeof store_path, "%s", argv[++i]); 210 else if (!strcmp(argv[i], "--lang") && i + 1 < argc) g_lang = !strcmp(argv[++i], "en"); 211 else { fprintf(stderr, "uso: %s [--scale N] [--link-listen P | --link-connect P] [--store F] [--lang es|en]\n", argv[0]); return 1; } 212 } 213 if (scale < 1) scale = 1; 214 keys = getenv("APP_KEYS"); 215 if (!SDL_Init(SDL_INIT_VIDEO)) { fprintf(stderr, "SDL: %s\n", SDL_GetError()); return 1; } 216 char title[64]; 217 snprintf(title, sizeof title, "PicoCalc " APP_TITLE "%s", link_port ? (link_listen ? " (A)" : " (B)") : ""); 218 if (!SDL_CreateWindowAndRenderer(title, 320 * scale, 320 * scale, SDL_WINDOW_RESIZABLE, &win, &ren)) return 1; 219 SDL_SetRenderLogicalPresentation(ren, 320, 320, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); 220 tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, 320, 320); 221 SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST); 222 SDL_StartTextInput(win); 223 link_setup(); 224 app_main(); 225 return 0; 226 }