/* Adaptación a Windows (Win32). * * ESTADO: escrito, NO compilado ni probado (el entorno de desarrollo era Linux y no había * compilador para Windows). Hay que compilarlo y pasar las pruebas en Windows antes de * distribuirlo. Ver docs/STATUS.md. * * Todas las rutas de la API pública son UTF-8; aquí se convierten a UTF-16 para las funciones W. */ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include #include "../../core/internal.h" #include "../platform.h" static wchar_t *to_wide(const char *s) { int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0); if (n <= 0) return NULL; wchar_t *w = malloc((size_t)n * sizeof *w); if (w && MultiByteToWideChar(CP_UTF8, 0, s, -1, w, n) <= 0) { free(w); return NULL; } return w; } static bool from_wide(const wchar_t *w, char *out, size_t cap) { return WideCharToMultiByte(CP_UTF8, 0, w, -1, out, (int)cap, NULL, NULL) > 0; } static void slashes_to_backslash(wchar_t *w) { for (; *w; w++) if (*w == L'/') *w = L'\\'; } tgb_status tgb_plat_default_dirs(char *config, char *data, char *cache, char *state, size_t cap) { const wchar_t *roaming = _wgetenv(L"APPDATA"), *local = _wgetenv(L"LOCALAPPDATA"); if (!roaming || !local) return tgb_fail(TGB_ERR_NOT_FOUND, "No se encontraron APPDATA/LOCALAPPDATA"); char r[400], l[400]; if (!from_wide(roaming, r, sizeof r) || !from_wide(local, l, sizeof l)) return tgb_fail(TGB_ERR_LIMIT, "Ruta de datos de usuario demasiado larga"); snprintf(config, cap, "%s\\Tahgibu", r); snprintf(data, cap, "%s\\Tahgibu\\data", l); snprintf(cache, cap, "%s\\Tahgibu\\cache", l); snprintf(state, cap, "%s\\Tahgibu\\state", l); return TGB_OK; } tgb_status tgb_plat_mkdirs(const char *path) { wchar_t *w = to_wide(path); if (!w) return tgb_fail(TGB_ERR_INVALID_ARG, "Ruta no válida"); slashes_to_backslash(w); for (wchar_t *p = w + 1; ; p++) { if (*p == L'\\' || *p == L'\0') { wchar_t c = *p; *p = L'\0'; if (!CreateDirectoryW(w, NULL) && GetLastError() != ERROR_ALREADY_EXISTS && GetLastError() != ERROR_ACCESS_DENIED) { free(w); return tgb_fail(TGB_ERR_IO, "No se pudo crear la carpeta '%s' (error %lu)", path, GetLastError()); } *p = c; if (c == L'\0') break; } } DWORD a = GetFileAttributesW(w); free(w); return (a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY)) ? TGB_OK : tgb_fail(TGB_ERR_IO, "'%s' no es una carpeta", path); } bool tgb_plat_exists(const char *path) { wchar_t *w = to_wide(path); if (!w) return false; DWORD a = GetFileAttributesW(w); free(w); return a != INVALID_FILE_ATTRIBUTES; } bool tgb_plat_is_dir(const char *path) { wchar_t *w = to_wide(path); if (!w) return false; DWORD a = GetFileAttributesW(w); free(w); return a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY); } tgb_status tgb_plat_read_file(const char *path, uint8_t **out, size_t *len, size_t max_bytes) { wchar_t *w = to_wide(path); if (!w) return tgb_fail(TGB_ERR_INVALID_ARG, "Ruta no válida"); HANDLE h = CreateFileW(w, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); free(w); if (h == INVALID_HANDLE_VALUE) return tgb_fail(GetLastError() == ERROR_FILE_NOT_FOUND ? TGB_ERR_NOT_FOUND : TGB_ERR_IO, "No se pudo abrir '%s' (error %lu)", path, GetLastError()); LARGE_INTEGER sz; if (!GetFileSizeEx(h, &sz) || sz.QuadPart < 0) { CloseHandle(h); return tgb_fail(TGB_ERR_IO, "No se pudo leer '%s'", path); } if ((unsigned long long)sz.QuadPart > (unsigned long long)max_bytes) { CloseHandle(h); return tgb_fail(TGB_ERR_LIMIT, "'%s' es demasiado grande", path); } size_t size = (size_t)sz.QuadPart; uint8_t *buf = malloc(size ? size : 1); if (!buf) { CloseHandle(h); return tgb_fail(TGB_ERR_NOMEM, "Sin memoria"); } size_t done = 0; while (done < size) { DWORD got = 0, want = (DWORD)((size - done) > 0x40000000u ? 0x40000000u : (size - done)); if (!ReadFile(h, buf + done, want, &got, NULL) || got == 0) { free(buf); CloseHandle(h); return tgb_fail(TGB_ERR_IO, "Error leyendo '%s'", path); } done += got; } CloseHandle(h); *out = buf; *len = size; return TGB_OK; } tgb_status tgb_plat_write_atomic(const char *path, const void *data, size_t len) { char tmp[1100]; snprintf(tmp, sizeof tmp, "%s.tmp-%lu", path, GetCurrentProcessId()); wchar_t *wt = to_wide(tmp), *wp = to_wide(path); if (!wt || !wp) { free(wt); free(wp); return tgb_fail(TGB_ERR_INVALID_ARG, "Ruta no válida"); } HANDLE h = CreateFileW(wt, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { free(wt); free(wp); return tgb_fail(TGB_ERR_IO, "No se pudo crear el temporal '%s' (error %lu)", tmp, GetLastError()); } const uint8_t *p = data; size_t left = len; bool ok = true; while (left > 0 && ok) { DWORD w = 0, want = (DWORD)(left > 0x40000000u ? 0x40000000u : left); ok = WriteFile(h, p, want, &w, NULL) && w == want; p += w; left -= w; } if (ok) ok = FlushFileBuffers(h) != 0; DWORD err = ok ? 0 : GetLastError(); CloseHandle(h); if (ok && !MoveFileExW(wt, wp, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { ok = false; err = GetLastError(); } if (!ok) { DeleteFileW(wt); free(wt); free(wp); return tgb_fail(TGB_ERR_IO, "Error escribiendo '%s' (error %lu); el archivo anterior no se modificó", path, err); } free(wt); free(wp); return TGB_OK; } tgb_status tgb_plat_remove(const char *path) { wchar_t *w = to_wide(path); if (!w) return tgb_fail(TGB_ERR_INVALID_ARG, "Ruta no válida"); BOOL ok = DeleteFileW(w); DWORD e = GetLastError(); free(w); if (!ok && e != ERROR_FILE_NOT_FOUND && e != ERROR_PATH_NOT_FOUND) return tgb_fail(TGB_ERR_IO, "No se pudo borrar '%s' (error %lu)", path, e); return TGB_OK; } tgb_status tgb_plat_copy_file(const char *src, const char *dst) { uint8_t *buf; size_t len; tgb_status s = tgb_plat_read_file(src, &buf, &len, (size_t)1 << 30); if (TGB_FAILED(s)) return s; s = tgb_plat_write_atomic(dst, buf, len); free(buf); return s; } tgb_status tgb_plat_list_dir(const char *dir, char ***names, size_t *count) { char pat[1100]; snprintf(pat, sizeof pat, "%s\\*", dir); wchar_t *w = to_wide(pat); if (!w) return tgb_fail(TGB_ERR_INVALID_ARG, "Ruta no válida"); WIN32_FIND_DATAW fd; HANDLE h = FindFirstFileW(w, &fd); free(w); if (h == INVALID_HANDLE_VALUE) return tgb_fail(GetLastError() == ERROR_PATH_NOT_FOUND ? TGB_ERR_NOT_FOUND : TGB_ERR_IO, "No se pudo abrir la carpeta '%s'", dir); char **list = NULL; size_t n = 0, cap = 0; do { if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..")) continue; char u[600]; if (!from_wide(fd.cFileName, u, sizeof u)) continue; if (n == cap) { size_t nc = cap ? cap * 2 : 16; char **nl = realloc(list, nc * sizeof *nl); if (!nl) { FindClose(h); tgb_plat_free_list(list, n); return tgb_fail(TGB_ERR_NOMEM, "Sin memoria"); } list = nl; cap = nc; } list[n] = tgb_strdup(u); if (!list[n]) { FindClose(h); tgb_plat_free_list(list, n); return tgb_fail(TGB_ERR_NOMEM, "Sin memoria"); } n++; } while (FindNextFileW(h, &fd)); FindClose(h); *names = list; *count = n; return TGB_OK; } void tgb_plat_free_list(char **names, size_t count) { for (size_t i = 0; i < count; i++) free(names[i]); free(names); } size_t tgb_plat_available_memory(void) { MEMORYSTATUSEX m; m.dwLength = sizeof m; return GlobalMemoryStatusEx(&m) ? (size_t)m.ullAvailPhys : 0; } size_t tgb_plat_free_disk(const char *dir) { wchar_t *w = to_wide(dir); ULARGE_INTEGER freeb; BOOL ok = w && GetDiskFreeSpaceExW(w, &freeb, NULL, NULL); free(w); return ok ? (size_t)freeb.QuadPart : 0; } long tgb_plat_pid(void) { return (long)GetCurrentProcessId(); } int64_t tgb_plat_process_start_token(long pid) { HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)pid); if (!h) return 0; FILETIME c, e, k, u; int64_t v = 0; if (GetProcessTimes(h, &c, &e, &k, &u)) v = (int64_t)(((uint64_t)c.dwHighDateTime << 32 | c.dwLowDateTime) / 10000ull); CloseHandle(h); return v; } bool tgb_plat_process_alive(long pid, int64_t start_token) { HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)pid); if (!h) return GetLastError() == ERROR_ACCESS_DENIED; DWORD code = 0; BOOL ok = GetExitCodeProcess(h, &code); CloseHandle(h); if (!ok || code != STILL_ACTIVE) return false; if (start_token != 0) { int64_t cur = tgb_plat_process_start_token(pid); if (cur != 0 && cur != start_token) return false; } return true; } int64_t tgb_plat_now_unix(void) { return (int64_t)time(NULL); } int64_t tgb_plat_file_mtime(const char *path) { wchar_t *w = to_wide(path); WIN32_FILE_ATTRIBUTE_DATA d; int64_t r = 0; if (w && GetFileAttributesExW(w, GetFileExInfoStandard, &d)) r = (int64_t)((((uint64_t)d.ftLastWriteTime.dwHighDateTime << 32 | d.ftLastWriteTime.dwLowDateTime) / 10000000ull) - 11644473600ull); free(w); return r; } double tgb_plat_monotonic_seconds(void) { LARGE_INTEGER f, c; QueryPerformanceFrequency(&f); QueryPerformanceCounter(&c); return (double)c.QuadPart / (double)f.QuadPart; } void *tgb_plat_dlopen(const char *path) { wchar_t *w = to_wide(path); HMODULE m = w ? LoadLibraryW(w) : NULL; free(w); return (void *)m; } void *tgb_plat_dlsym(void *lib, const char *name) { return (void *)GetProcAddress((HMODULE)lib, name); } void tgb_plat_dlclose(void *lib) { if (lib) FreeLibrary((HMODULE)lib); } const char *tgb_plat_dlerror(void) { static char msg[64]; snprintf(msg, sizeof msg, "error de Windows %lu", GetLastError()); return msg; } const char *tgb_plat_dll_extension(void) { return ".dll"; }