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