diff --git a/CODE/UTSM/pe/pe_dll_manager.c b/CODE/UTSM/pe/pe_dll_manager.c new file mode 100644 index 0000000..7fd91b2 --- /dev/null +++ b/CODE/UTSM/pe/pe_dll_manager.c @@ -0,0 +1,275 @@ +/* pe_dll_manager.c — PE 真实 DLL 加载管理器实现。 + * + * 加载流程: + * 1. pe_dll_resolve(dll, func) 被 resolve_imports 调用(shim miss 后) + * 2. 若 DLL 未缓存,pe_dll_load 读取 DLL 文件 → 复制到 arena → pe_load_image + * 3. pe_load_image 内部 resolve_imports 递归解析该 DLL 的依赖 + * 4. DLL 加载完成后,解析导出表按名称查找函数地址 + * + * 内存安全:fat32_read_path 返回 static 缓冲(g_dsk_fat32_filedata), + * 递归加载会覆盖该缓冲。因此 pe_dll_load 先将文件数据复制到 arena, + * 再调 pe_load_image,确保递归不破坏外层 pe_data。 + */ + +#include "pe_dll_manager.h" +#include "pe_loader.h" +#include "pe_shim.h" +#include +#include +#include +#include + +/* ---- PE 导出目录结构(packed,与 pe_loader.h 风格一致) ---- */ +typedef struct __attribute__((packed)) { + u32 characteristics; + u32 time_date_stamp; + u16 major_version; + u16 minor_version; + u32 name_rva; + u32 ordinal_base; + u32 number_of_functions; + u32 number_of_names; + u32 address_of_functions; /* EAT RVA */ + u32 address_of_names; /* ENPT RVA */ + u32 address_of_name_ordinals; /* EOT RVA */ +} image_export_directory; + +/* ---- DLL 缓存 ---- */ +#define PE_DLL_CACHE_MAX 32 +#define PE_DLL_NAME_MAX 64 + +enum { + DLL_STATE_NONE = 0, + DLL_STATE_LOADING = 1, + DLL_STATE_LOADED = 2, +}; + +typedef struct { + char name[PE_DLL_NAME_MAX]; /* 小写 DLL 名 */ + u64 image_base; /* pe_load_image 返回基址 */ + u32 image_size; /* SizeOfImage */ + u32 export_dir_rva; /* 导出表 RVA(0=无导出) */ + u32 export_dir_size; + int state; /* DLL_STATE_* */ +} pe_dll_entry; + +static pe_dll_entry g_dll_cache[PE_DLL_CACHE_MAX]; +static pe_dll_reader_fn g_dll_reader = 0; +int g_pe_dll_manager_ready = 0; + +/* ---- 辅助函数 ---- */ + +/* ASCII 大小写不敏感字符串比较(freestanding,无 strcmp) */ +static int dll_streq_ci(const char *a, const char *b) { + if (!a || !b) return 0; + while (*a && *b) { + char ca = *a, cb = *b; + if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 32); + if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 32); + if (ca != cb) return 0; + a++; b++; + } + return *a == *b; +} + +/* 将 DLL 名转为小写到 dst(最多 n-1 字符 + NUL) */ +static void dll_name_lower(const char *src, char *dst, int n) { + int i = 0; + while (src[i] && i < n - 1) { + char c = src[i]; + if (c >= 'A' && c <= 'Z') c = (char)(c + 32); + dst[i] = c; + i++; + } + dst[i] = 0; +} + +/* 在缓存中查找 DLL 名(大小写不敏感)。返回索引,-1 未找到。 */ +static int dll_cache_find(const char *dll_name) { + char lower[PE_DLL_NAME_MAX]; + dll_name_lower(dll_name, lower, sizeof(lower)); + for (int i = 0; i < PE_DLL_CACHE_MAX; i++) { + if (g_dll_cache[i].state == DLL_STATE_NONE) continue; + if (dll_streq_ci(g_dll_cache[i].name, lower)) return i; + } + return -1; +} + +/* 在缓存中分配一个空闲槽。返回索引,-1 缓存满。 */ +static int dll_cache_alloc(void) { + for (int i = 0; i < PE_DLL_CACHE_MAX; i++) { + if (g_dll_cache[i].state == DLL_STATE_NONE) return i; + } + return -1; +} + +/* 从已加载的 PE 镜像中提取导出表 RVA 和 size。 + * image 指向 pe_load_image 分配的镜像基址(headers 已映射)。 */ +static void extract_export_dir(u64 image_base, u32 *out_rva, u32 *out_size) { + *out_rva = 0; + *out_size = 0; + const u8 *img = (const u8 *)image_base; + + /* DOS header */ + const image_dos_header *dos = (const image_dos_header *)img; + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return; + + /* NT headers */ + const u8 *nt = img + dos->e_lfanew; + if (*(const u32 *)nt != IMAGE_NT_SIGNATURE) return; + const image_file_header *fh = (const image_file_header *)(nt + 4); + const void *opt = (const void *)(fh + 1); + + u16 magic = *(const u16 *)opt; + const image_data_directory *data_dir; + if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + const image_optional_header64 *oh = (const image_optional_header64 *)opt; + data_dir = oh->data_directory; + } else { + const image_optional_header32 *oh = (const image_optional_header32 *)opt; + data_dir = oh->data_directory; + } + + *out_rva = data_dir[IMAGE_DIRECTORY_ENTRY_EXPORT].virtual_address; + *out_size = data_dir[IMAGE_DIRECTORY_ENTRY_EXPORT].size; +} + +/* 在已加载 DLL 的导出表中按名称查找函数。 + * 返回绝对地址(image_base + func_rva),0=未找到/转发导出。 */ +static u64 resolve_export_by_name(u64 image_base, u32 export_dir_rva, + const char *func_name) { + if (export_dir_rva == 0) return 0; + const u8 *img = (const u8 *)image_base; + const image_export_directory *exp = + (const image_export_directory *)(img + export_dir_rva); + if (exp->number_of_names == 0) return 0; + + const u32 *names = (const u32 *)(img + exp->address_of_names); + const u16 *ordinals = (const u16 *)(img + exp->address_of_name_ordinals); + const u32 *functions = (const u32 *)(img + exp->address_of_functions); + + for (u32 i = 0; i < exp->number_of_names; i++) { + const char *name = (const char *)(img + names[i]); + if (dll_streq_ci(name, func_name)) { + u16 eat_index = ordinals[i]; + if (eat_index >= exp->number_of_functions) return 0; + u32 func_rva = functions[eat_index]; + + /* 转发导出:func_rva 落在导出表范围内 → 是转发字符串而非代码地址 */ + if (func_rva >= export_dir_rva && + func_rva < export_dir_rva + exp->number_of_functions * 4) { + /* 粗略范围检查(更精确应使用 export_dir_size,但此处足够) */ + log_info("[PE] forwarded export (not supported)"); + return 0; + } + return image_base + func_rva; + } + } + return 0; +} + +/* ---- 公共接口 ---- */ + +void pe_dll_manager_init(pe_dll_reader_fn reader) { + g_dll_reader = reader; + for (int i = 0; i < PE_DLL_CACHE_MAX; i++) { + g_dll_cache[i].state = DLL_STATE_NONE; + } + g_pe_dll_manager_ready = 1; + log_info("[PE] DLL manager initialized (SYSTEM/lib)"); +} + +int pe_dll_load(const char *dll_name, u64 *out_base) { + if (!g_pe_dll_manager_ready || !g_dll_reader || !dll_name) { + if (out_base) *out_base = 0; + return -1; + } + + /* 查缓存 */ + int idx = dll_cache_find(dll_name); + if (idx >= 0) { + pe_dll_entry *e = &g_dll_cache[idx]; + if (e->state == DLL_STATE_LOADED) { + if (out_base) *out_base = e->image_base; + return 0; + } + if (e->state == DLL_STATE_LOADING) { + /* 循环依赖:返回 -1,调用方填 unimpl stub */ + if (out_base) *out_base = 0; + return -1; + } + } + + /* 分配缓存槽 */ + idx = dll_cache_alloc(); + if (idx < 0) { + log_warn("[PE] DLL cache full, cannot load"); + if (out_base) *out_base = 0; + return -2; + } + + pe_dll_entry *entry = &g_dll_cache[idx]; + dll_name_lower(dll_name, entry->name, PE_DLL_NAME_MAX); + entry->state = DLL_STATE_LOADING; + entry->image_base = 0; + entry->export_dir_rva = 0; + entry->export_dir_size = 0; + + /* 读取 DLL 文件 */ + u8 *file_data = 0; + u32 file_size = 0; + if (g_dll_reader(dll_name, &file_data, &file_size) != 0 || !file_data || file_size == 0) { + entry->state = DLL_STATE_NONE; /* 回收槽 */ + if (out_base) *out_base = 0; + return -3; + } + + /* 复制文件数据到 arena(防止递归加载覆盖 fat32_read_path 的 static 缓冲) */ + u8 *file_copy = (u8 *)kmem_alloc_aligned(file_size, 16); + if (!file_copy) { + entry->state = DLL_STATE_NONE; + if (out_base) *out_base = 0; + return -4; + } + for (u32 i = 0; i < file_size; i++) file_copy[i] = file_data[i]; + + /* 加载 PE 镜像(内部会递归 resolve_imports → 可能再次 pe_dll_load) */ + pe_image_info info; + if (pe_load_image(file_copy, file_size, &info) != 0) { + log_warn("[PE] DLL load failed: pe_load_image error"); + log_info(dll_name); + entry->state = DLL_STATE_NONE; + if (out_base) *out_base = 0; + return -5; + } + + /* 提取导出表信息 */ + extract_export_dir(info.image_base, &entry->export_dir_rva, + &entry->export_dir_size); + entry->image_base = info.image_base; + entry->image_size = (u32)info.image_size; + entry->state = DLL_STATE_LOADED; + + log_info("[PE] loaded DLL:"); + log_info(dll_name); + log_hex64("[PE] DLL base=", info.image_base); + + if (out_base) *out_base = info.image_base; + return 0; +} + +u64 pe_dll_resolve(const char *dll_name, const char *func_name) { + if (!g_pe_dll_manager_ready || !dll_name || !func_name) return 0; + + /* 确保 DLL 已加载 */ + int idx = dll_cache_find(dll_name); + if (idx < 0 || g_dll_cache[idx].state != DLL_STATE_LOADED) { + u64 base = 0; + if (pe_dll_load(dll_name, &base) != 0) return 0; + idx = dll_cache_find(dll_name); + if (idx < 0 || g_dll_cache[idx].state != DLL_STATE_LOADED) return 0; + } + + pe_dll_entry *e = &g_dll_cache[idx]; + return resolve_export_by_name(e->image_base, e->export_dir_rva, func_name); +} diff --git a/CODE/UTSM/pe/pe_dll_manager.d b/CODE/UTSM/pe/pe_dll_manager.d new file mode 100644 index 0000000..02f6211 --- /dev/null +++ b/CODE/UTSM/pe/pe_dll_manager.d @@ -0,0 +1,10 @@ +pe/pe_dll_manager.o: pe\pe_dll_manager.c pe\pe_dll_manager.h \ + include\utsm\types.h pe\pe_loader.h include\utsm\pe.h pe\pe_shim.h \ + include\utsm\arena.h include\utsm\log.h +pe\pe_dll_manager.h: +include\utsm\types.h: +pe\pe_loader.h: +include\utsm\pe.h: +pe\pe_shim.h: +include\utsm\arena.h: +include\utsm\log.h: diff --git a/CODE/UTSM/pe/pe_dll_manager.h b/CODE/UTSM/pe/pe_dll_manager.h new file mode 100644 index 0000000..f95ab98 --- /dev/null +++ b/CODE/UTSM/pe/pe_dll_manager.h @@ -0,0 +1,51 @@ +#ifndef PE_DLL_MANAGER_H +#define PE_DLL_MANAGER_H + +/* pe_dll_manager.h — PE 真实 DLL 加载管理器(SYSTEM/lib 兼容层)。 + * + * PE 兼容层默认使用内置 shim 表解析 imports。shim 未命中的函数, + * 由 DLL 管理器从 SYSTEM/lib/ 加载真实 DLL 并解析导出表。 + * + * 架构决策: + * - 仅 PE32+(64 位原生)启用真实 DLL 加载 + * - PE32(32 位解释器)不加载真实 DLL(x86emu32 不能调 64 位代码) + * - 不修改 pe_service 结构体 ABI(通过全局注册模式) + * - 递归依赖:pe_dll_load → pe_load_image → resolve_imports → pe_dll_resolve → pe_dll_load + * - 循环检测:DLL_LOADING 状态遇到则跳过(填 unimpl stub,安全降级) + * - 内存:每个 DLL 用 kmem_alloc_aligned 分配 SizeOfImage + 文件副本(arena 不释放) + * - 缓存上限 32 个 DLL + */ + +#include + +/* DLL 文件读取回调:从 SYSTEM/lib/ 读取 DLL 到内存。 + * name : DLL 名(如 "foo.dll",原样来自 PE import 表) + * out_data : 返回内存数据指针(调用方不负责释放) + * out_size : 返回字节大小 + * 返回 0 成功,负值失败。 */ +typedef int (*pe_dll_reader_fn)(const char *name, u8 **out_data, u32 *out_size); + +/* 全局就绪标志(1=DLL 管理器已初始化,可解析真实 DLL)。 + * pe_loader.c resolve_imports 检查此标志决定是否尝试真实 DLL。 */ +extern int g_pe_dll_manager_ready; + +/* 初始化 DLL 管理器(注册文件读取回调)。 + * 在 DKM 驱动加载完成、block provider 注册后调用。 */ +void pe_dll_manager_init(pe_dll_reader_fn reader); + +/* 加载 DLL(含递归解析其自身 import)。 + * 已加载返回缓存基址。循环依赖检测:LOADING 状态遇到则返回 -1。 + * dll_name : DLL 名(如 "foo.dll",大小写不敏感) + * out_base : 输出加载基址 + * 返回 0 成功,负值失败。 */ +int pe_dll_load(const char *dll_name, u64 *out_base); + +/* 按名称解析导出函数。 + * 若 DLL 未加载,自动调用 pe_dll_load。 + * 管理器未就绪时返回 0。 + * dll_name : DLL 名(大小写不敏感) + * func_name : 函数名(大小写不敏感,PE 导出表规范) + * 返回绝对地址,0=未找到。 */ +u64 pe_dll_resolve(const char *dll_name, const char *func_name); + +#endif /* PE_DLL_MANAGER_H */ diff --git a/CODE/desktop/Makefile.linux b/CODE/desktop/Makefile.linux new file mode 100644 index 0000000..578daf0 --- /dev/null +++ b/CODE/desktop/Makefile.linux @@ -0,0 +1,44 @@ +# Makefile.linux — Deshab 桌面 Linux SDL2 宿主构建 +# +# 用法(在 Linux 下,CODE/desktop/ 目录): +# make -f Makefile.linux # 编译 desktop_host +# make -f Makefile.linux run # 编译并运行(默认读 ../../ISO/deshab.img) +# make -f Makefile.linux clean +# +# 依赖:gcc/clang、libsdl2-dev(apt install libsdl2-dev) +# 产物:desktop_host(普通 Linux 可执行文件) +# +# 与裸机 MAKEFILE 完全独立:不带 -ffreestanding/-mno-sse/-target, +# 不使用 linker.ld;通过 -DDESHAB_HOST 启用 host_main.c + main.c 中的宿主分支。 + +CC ?= gcc +SDL_CFLAGS := $(shell sdl2-config --cflags 2>/dev/null) +SDL_LIBS := $(shell sdl2-config --libs 2>/dev/null) + +ROOT := ../.. +IMG := $(ROOT)/ISO/deshab.img + +CFLAGS := -DDESHAB_HOST -O2 -Wall -Wextra -g \ + -I. -I../UTSM/include -I../tools -I../firstInit \ + $(SDL_CFLAGS) +LDFLAGS := $(SDL_LIBS) -lm + +TARGET := desktop_host +SRCS := main.c host_main.c +OBJS := $(SRCS:.c=.o) + +.PHONY: all run clean + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +%.o: %.c host_main.h + $(CC) $(CFLAGS) -c $< -o $@ + +run: $(TARGET) + ./$(TARGET) --img $(IMG) + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/CODE/desktop/host_main.c b/CODE/desktop/host_main.c new file mode 100644 index 0000000..f163e45 --- /dev/null +++ b/CODE/desktop/host_main.c @@ -0,0 +1,376 @@ +/* host_main.c — Deshab 桌面 Linux SDL2 宿主 + * + * 仅在 -DDESHAB_HOST 编译时链接。提供: + * - SDL2 窗口 + streaming texture(模拟 framebuffer,g_host_backbuf) + * - 鼠标/键盘事件队列(模拟 PS/2,供 host_mouse_poll/host_kb_poll 消费) + * - deshab.img GPT ESP 文件 block 后端(模拟 kernel_api block provider) + * - 单调时钟 / 当天秒数(替代 rdtsc / RTC CMOS) + * + * main() 构造 dsk_boot_context 后调用 dsk_entry(),进入桌面主循环。 + * 主循环每帧末尾调用 host_pump_and_present() 上传 back buffer 并 pump 事件。 + */ +#include "host_main.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ---- 全局缓冲(供 desktop/main.c 经 extern 使用) ---- */ +u8 *g_host_sprite = NULL; +u8 *g_host_backbuf = NULL; + +/* ---- SDL 状态 ---- */ +static SDL_Window *g_win = NULL; +static SDL_Renderer *g_rend = NULL; +static SDL_Texture *g_tex = NULL; +static int g_fb_w = 1280, g_fb_h = 720; + +/* ---- 鼠标状态(SDL 事件累积,host_mouse_poll 消费) ---- */ +static struct { + int x, y; /* 绝对坐标 */ + int btn; /* bit0=左 bit1=右 */ + int prev_btn; /* 上次 poll 时的按键状态 */ + int changed; /* 自上次 poll 以来有变化 */ +} g_mse = { 400, 300, 0, 0, 0 }; + +/* ---- 键盘扫描码队列(SDL 键事件 → set-1 字节序列) ---- */ +#define KB_QSZ 64 +static u8 g_kb_q[KB_QSZ]; +static int g_kb_head = 0, g_kb_tail = 0; + +static void kb_push(u8 b) { + int nxt = (g_kb_tail + 1) % KB_QSZ; + if (nxt == g_kb_head) return; /* 满,丢弃 */ + g_kb_q[g_kb_tail] = b; + g_kb_tail = nxt; +} + +/* ---- 日志 ---- */ +void host_log(const char *s) { fputs(s, stderr); fflush(stderr); } +void host_log_char(char c) { fputc(c, stderr); fflush(stderr); } + +/* ---- 时间 ---- */ +u64 host_now_us(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (u64)ts.tv_sec * 1000000ULL + (u64)ts.tv_nsec / 1000ULL; +} + +int host_rtc_seconds(void) { + time_t t = time(NULL); + struct tm *lt = localtime(&t); + return lt->tm_hour * 3600 + lt->tm_min * 60 + lt->tm_sec; +} + +/* ---- 鼠标 ---- */ +int host_mouse_poll(int *px, int *py, int *pbtn, + int *p_left_pressed, int *p_left_released, + int *p_right_pressed, int max_x, int max_y) { + if (!g_mse.changed && g_mse.btn == g_mse.prev_btn) return 0; + g_mse.changed = 0; + int btn = g_mse.btn; + int prev = g_mse.prev_btn; + g_mse.prev_btn = btn; + int x = g_mse.x, y = g_mse.y; + if (x < 0) x = 0; if (y < 0) y = 0; + if (x > max_x) x = max_x; if (y > max_y) y = max_y; + *px = x; *py = y; *pbtn = btn; + *p_left_pressed = (btn & 1) && !(prev & 1); + *p_left_released = !(btn & 1) && (prev & 1); + *p_right_pressed = (btn & 2) && !(prev & 2); + return 1; +} + +/* ---- 键盘 ---- */ +int host_kb_poll(u8 *out_sc) { + if (g_kb_head == g_kb_tail) return 0; + *out_sc = g_kb_q[g_kb_head]; + g_kb_head = (g_kb_head + 1) % KB_QSZ; + return 1; +} + +/* SDL_Scancode → PS/2 set-1 make 码(单字节;E0 前缀键返回 0xFF 表示需 E0) */ +static u8 sdl_to_set1(SDL_Scancode sc, int *e0) { + *e0 = 0; + switch (sc) { + case SDL_SCANCODE_A: return 0x1E; case SDL_SCANCODE_B: return 0x30; + case SDL_SCANCODE_C: return 0x2E; case SDL_SCANCODE_D: return 0x20; + case SDL_SCANCODE_E: return 0x12; case SDL_SCANCODE_F: return 0x21; + case SDL_SCANCODE_G: return 0x22; case SDL_SCANCODE_H: return 0x23; + case SDL_SCANCODE_I: return 0x17; case SDL_SCANCODE_J: return 0x24; + case SDL_SCANCODE_K: return 0x25; case SDL_SCANCODE_L: return 0x26; + case SDL_SCANCODE_M: return 0x32; case SDL_SCANCODE_N: return 0x31; + case SDL_SCANCODE_O: return 0x18; case SDL_SCANCODE_P: return 0x19; + case SDL_SCANCODE_Q: return 0x10; case SDL_SCANCODE_R: return 0x13; + case SDL_SCANCODE_S: return 0x1F; case SDL_SCANCODE_T: return 0x14; + case SDL_SCANCODE_U: return 0x16; case SDL_SCANCODE_V: return 0x2F; + case SDL_SCANCODE_W: return 0x11; case SDL_SCANCODE_X: return 0x2D; + case SDL_SCANCODE_Y: return 0x15; case SDL_SCANCODE_Z: return 0x2C; + case SDL_SCANCODE_1: return 0x02; case SDL_SCANCODE_2: return 0x03; + case SDL_SCANCODE_3: return 0x04; case SDL_SCANCODE_4: return 0x05; + case SDL_SCANCODE_5: return 0x06; case SDL_SCANCODE_6: return 0x07; + case SDL_SCANCODE_7: return 0x08; case SDL_SCANCODE_8: return 0x09; + case SDL_SCANCODE_9: return 0x0A; case SDL_SCANCODE_0: return 0x0B; + case SDL_SCANCODE_RETURN: return 0x1C; + case SDL_SCANCODE_BACKSPACE: return 0x0E; + case SDL_SCANCODE_TAB: return 0x0F; + case SDL_SCANCODE_SPACE: return 0x39; + case SDL_SCANCODE_ESCAPE: return 0x01; + case SDL_SCANCODE_LSHIFT: return 0x2A; case SDL_SCANCODE_RSHIFT: return 0x36; + case SDL_SCANCODE_LCTRL: return 0x1D; case SDL_SCANCODE_RCTRL: return 0x1D; + case SDL_SCANCODE_LALT: return 0x38; case SDL_SCANCODE_RALT: return 0x38; + case SDL_SCANCODE_CAPSLOCK: return 0x3A; + case SDL_SCANCODE_MINUS: return 0x0C; + case SDL_SCANCODE_EQUALS: return 0x0D; + case SDL_SCANCODE_LEFTBRACKET: return 0x1A; + case SDL_SCANCODE_RIGHTBRACKET: return 0x1B; + case SDL_SCANCODE_SEMICOLON: return 0x27; + case SDL_SCANCODE_APOSTROPHE: return 0x28; + case SDL_SCANCODE_GRAVE: return 0x29; + case SDL_SCANCODE_BACKSLASH: return 0x2B; + case SDL_SCANCODE_COMMA: return 0x33; + case SDL_SCANCODE_PERIOD: return 0x34; + case SDL_SCANCODE_SLASH: return 0x35; + case SDL_SCANCODE_LEFT: *e0 = 1; return 0x4B; + case SDL_SCANCODE_RIGHT: *e0 = 1; return 0x4D; + case SDL_SCANCODE_UP: *e0 = 1; return 0x48; + case SDL_SCANCODE_DOWN: *e0 = 1; return 0x50; + default: return 0; + } +} + +static void handle_key_event(SDL_Scancode sc, int down) { + int e0 = 0; + u8 code = sdl_to_set1(sc, &e0); + if (!code) return; + if (e0) kb_push(0xE0); + kb_push(down ? code : (u8)(code | 0x80)); +} + +/* ---- 帧末 pump + present ---- */ +int host_pump_and_present(void) { + int quit = 0; + /* 上传 back buffer 到 SDL texture 并 present */ + if (g_tex && g_host_backbuf) { + SDL_UpdateTexture(g_tex, NULL, g_host_backbuf, g_fb_w * 4); + SDL_RenderClear(g_rend); + SDL_RenderCopy(g_rend, g_tex, NULL, NULL); + SDL_RenderPresent(g_rend); + } + /* pump 事件 */ + SDL_Event ev; + while (SDL_PollEvent(&ev)) { + switch (ev.type) { + case SDL_QUIT: + quit = 1; + break; + case SDL_MOUSEMOTION: + g_mse.x = ev.motion.x; + g_mse.y = ev.motion.y; + g_mse.changed = 1; + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: { + int down = (ev.type == SDL_MOUSEBUTTONDOWN); + if (ev.button.button == SDL_BUTTON_LEFT) { + if (down) g_mse.btn |= 1; else g_mse.btn &= ~1; + g_mse.changed = 1; + } else if (ev.button.button == SDL_BUTTON_RIGHT) { + if (down) g_mse.btn |= 2; else g_mse.btn &= ~2; + g_mse.changed = 1; + } + break; + } + case SDL_KEYDOWN: + case SDL_KEYUP: { + /* 忽略重复事件(SDL_KEYDOWN 带 repeat>0) */ + if (ev.key.repeat) break; + handle_key_event(ev.key.keysym.scancode, ev.type == SDL_KEYDOWN); + break; + } + } + } + return quit; +} + +/* ---- block 后端:deshab.img GPT ESP 文件 ---- */ +static int g_img_fd = -1; +static u64 g_esp_byte_off = 0; /* ESP 分区起始字节偏移 */ + +/* 读取 img LBA1 GPT header,定位 ESP 分区起始 LBA。失败回退 2048。 */ +static u64 find_esp_offset(int fd) { + u8 hdr[512]; + if (pread(fd, hdr, 512, 512) != 512) return 2048 * 512; + /* GPT signature "EFI PART" */ + if (memcmp(hdr, "EFI PART", 8) != 0) return 2048 * 512; + u64 entries_lba = 0; + u32 entry_count = 0, entry_size = 0; + memcpy(&entries_lba, hdr + 72, 8); + memcpy(&entry_count, hdr + 80, 4); + memcpy(&entry_size, hdr + 84, 4); + if (entry_size < 56 || entry_count == 0 || entry_count > 256) return 2048 * 512; + /* ESP 类型 GUID(混合字节序):c12a7328-f81f-11d2-ba4b-00a0c93ec93b */ + static const u8 esp_guid[16] = { + 0x28,0x73,0x2a,0xc1, 0x1f,0xf8, 0xd2,0x11, + 0xba,0x4b, 0x00,0xa0,0xc9,0x3e,0xc9,0x3b + }; + u8 *entries = (u8 *)malloc((u64)entry_count * entry_size); + if (!entries) return 2048 * 512; + u64 entries_byte = entries_lba * 512; + ssize_t got = pread(fd, entries, (u64)entry_count * entry_size, entries_byte); + if (got <= 0) { free(entries); return 2048 * 512; } + u64 esp_lba = 0; + int found = 0; + for (u32 i = 0; i < entry_count; i++) { + u8 *e = entries + (u64)i * entry_size; + if (memcmp(e, esp_guid, 16) == 0) { + memcpy(&esp_lba, e + 32, 8); + found = 1; + break; + } + } + free(entries); + return found ? (esp_lba * 512) : (2048 * 512); +} + +/* block_read/write:desktop 期望 int (u32 index, u64 lba, u32 count, void* buf) + * index 固定 0;扇区 512 字节;偏移 = esp_byte_off + lba*512。 */ +static int host_block_read(u32 index, u64 lba, u32 count, void *buf) { + (void)index; + if (g_img_fd < 0) return -1; + u64 off = g_esp_byte_off + lba * 512ULL; + u64 total = (u64)count * 512ULL; + u8 *p = (u8 *)buf; + while (total > 0) { + ssize_t n = pread(g_img_fd, p, total, off); + if (n <= 0) return -1; + p += n; off += (u64)n; total -= (u64)n; + } + return 0; +} + +static int host_block_write(u32 index, u64 lba, u32 count, const void *buf) { + (void)index; + if (g_img_fd < 0) return -1; + u64 off = g_esp_byte_off + lba * 512ULL; + u64 total = (u64)count * 512ULL; + const u8 *p = (const u8 *)buf; + while (total > 0) { + ssize_t n = pwrite(g_img_fd, p, total, off); + if (n <= 0) return -1; + p += n; off += (u64)n; total -= (u64)n; + } + return 0; +} + +/* ---- 伪造 kernel_api:让 dsk_entry 现有 block 获取路径零改动 ---- + * desktop/main.c: u64 blk = *(u64*)(ctx->dkm_kernel_api + 0xA8); + * g_block_read = *(u64*)(blk + 16); + * g_block_write = *(u64*)(blk + 24); + * 故 host_kernel_api 偏移 0xA8 处填 &host_block_api; + * host_block_api[2] = read, [3] = write(偏移 16/24)。 */ +static u64 host_block_api[8]; +static u8 host_kernel_api[0xB0 + 8]; + +static void build_fake_kernel_api(void) { + memset(host_kernel_api, 0, sizeof(host_kernel_api)); + memset(host_block_api, 0, sizeof(host_block_api)); + host_block_api[2] = (u64)(uintptr_t)host_block_read; + host_block_api[3] = (u64)(uintptr_t)host_block_write; + *(u64 *)(host_kernel_api + 0xA8) = (u64)(uintptr_t)host_block_api; +} + +/* ---- main ---- */ +int main(int argc, char **argv) { + const char *img_path = "../../ISO/deshab.img"; + int width = 1280, height = 720; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--img") == 0 && i + 1 < argc) { img_path = argv[++i]; continue; } + if (strcmp(argv[i], "--width") == 0 && i + 1 < argc) { width = atoi(argv[++i]); continue; } + if (strcmp(argv[i], "--height") == 0 && i + 1 < argc) { height = atoi(argv[++i]); continue; } + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + fprintf(stderr, "usage: desktop_host [--img PATH] [--width W] [--height H]\n"); + return 0; + } + } + g_fb_w = width; g_fb_h = height; + + /* 打开镜像 */ + g_img_fd = open(img_path, O_RDWR); + if (g_img_fd < 0) { + /* 只读回退 */ + g_img_fd = open(img_path, O_RDONLY); + if (g_img_fd < 0) { + fprintf(stderr, "[host] cannot open %s: ", img_path); + perror(""); + return 1; + } + fprintf(stderr, "[host] warning: %s opened read-only, FAT32 writes will fail\n", img_path); + } + g_esp_byte_off = find_esp_offset(g_img_fd); + fprintf(stderr, "[host] img=%s esp_off=%llu width=%d height=%d\n", + img_path, (unsigned long long)g_esp_byte_off, width, height); + + /* SDL */ + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "[host] SDL_Init: %s\n", SDL_GetError()); + return 1; + } + g_win = SDL_CreateWindow("Deshab Desktop (Linux host)", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + width, height, SDL_WINDOW_SHOWN); + if (!g_win) { fprintf(stderr, "[host] SDL_CreateWindow: %s\n", SDL_GetError()); return 1; } + g_rend = SDL_CreateRenderer(g_win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!g_rend) g_rend = SDL_CreateRenderer(g_win, -1, 0); + if (!g_rend) { fprintf(stderr, "[host] SDL_CreateRenderer: %s\n", SDL_GetError()); return 1; } + g_tex = SDL_CreateTexture(g_rend, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, width, height); + if (!g_tex) { fprintf(stderr, "[host] SDL_CreateTexture: %s\n", SDL_GetError()); return 1; } + + /* back buffer + sprite buffer */ + size_t fbsz = (size_t)width * height * 4; + g_host_backbuf = (u8 *)malloc(fbsz); + g_host_sprite = (u8 *)malloc(fbsz); + if (!g_host_backbuf || !g_host_sprite) { + fprintf(stderr, "[host] alloc buffer failed\n"); + return 1; + } + memset(g_host_backbuf, 0, fbsz); + memset(g_host_sprite, 0, fbsz); + + build_fake_kernel_api(); + + /* 构造 dsk_boot_context */ + dsk_boot_context ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.magic = DSK_BOOT_MAGIC; + ctx.abi_version = DSK_BOOT_ABI_VERSION; + ctx.size = sizeof(ctx); + ctx.framebuffer_address = (u64)(uintptr_t)g_host_backbuf; + ctx.framebuffer_width = (u64)width; + ctx.framebuffer_height = (u64)height; + ctx.framebuffer_pitch = (u64)width * 4; + ctx.framebuffer_bpp = 32; + ctx.dkm_kernel_api = (u64)(uintptr_t)host_kernel_api; + /* reserved[5]=0:无 linux_compat 服务,桌面侧 g_lxc_svc 保持 NULL */ + + fprintf(stderr, "[host] entering dsk_entry\n"); + dsk_entry(&ctx); + fprintf(stderr, "[host] dsk_entry returned, exiting\n"); + + SDL_DestroyTexture(g_tex); + SDL_DestroyRenderer(g_rend); + SDL_DestroyWindow(g_win); + SDL_Quit(); + if (g_img_fd >= 0) close(g_img_fd); + free(g_host_backbuf); + free(g_host_sprite); + return 0; +} diff --git a/CODE/desktop/host_main.h b/CODE/desktop/host_main.h new file mode 100644 index 0000000..5a3d17c --- /dev/null +++ b/CODE/desktop/host_main.h @@ -0,0 +1,59 @@ +/* host_main.h — Deshab 桌面 Linux 宿主接口 + * + * 仅在 -DDESHAB_HOST 编译时使用。提供 SDL2 窗口(模拟 framebuffer)、 + * 鼠标/键盘事件(模拟 PS/2)、deshab.img 文件 block 后端、单调时钟 + * (替代 rDTSC)与当天秒数(替代 RTC CMOS)。 + * + * desktop/main.c 通过 #ifdef DESHAB_HOST 调用本接口;裸机构建不包含本头。 + */ +#ifndef DESHAB_HOST_MAIN_H +#define DESHAB_HOST_MAIN_H + +#include + +/* dsk_entry 由 desktop/main.c 提供,host_main.c 构造好 ctx 后调用。 */ +struct dsk_boot_context; +void dsk_entry(const struct dsk_boot_context *ctx); + +/* ---- 日志(替代 COM1 串口 sputc/swrite/slog) ---- */ +void host_log(const char *s); /* 输出字符串到 stderr */ +void host_log_char(char c); /* 输出单字符到 stderr */ + +/* ---- 时间 ---- */ +/* 单调时钟微秒,替代 rdtsc。供 tsc_calibrate/g_last_click_tsc 使用。 + * g_tsc_per_sec 应设为 1000000。 */ +u64 host_now_us(void); +/* 当天秒数(0..86399),替代 rtc_day_sec。 */ +int host_rtc_seconds(void); + +/* ---- 鼠标(替代 ps2_mouse_poll) ---- + * 从 SDL 事件累积的鼠标状态生成一次快照。 + * px/py : 输出当前绝对坐标(已钳位到 [0,max]) + * pbtn : 输出按键位(bit0=左 bit1=右) + * p_left_pressed / p_left_released / p_right_pressed : 边沿事件(1 表示本次发生) + * max_x/max_y : 坐标上界(g_fb_w - CURSOR_SIZE 等) + * 返回 1 表示有移动或按键变化,0 表示无事件。 */ +int host_mouse_poll(int *px, int *py, int *pbtn, + int *p_left_pressed, int *p_left_released, + int *p_right_pressed, int max_x, int max_y); + +/* ---- 键盘(替代 inb(0x64)/inb(0x60) 扫描码轮询) ---- + * 取一个 PS/2 set-1 扫描码(make 码;break 码为 |0x80)。 + * 返回 1 表示取到,0 表示队列空。 */ +int host_kb_poll(u8 *out_sc); + +/* ---- 帧末:pump SDL 事件 + present back buffer ---- + * 在主循环末尾调用(替代 __asm__("pause"))。 + * 1. 把 back buffer(g_host_backbuf)上传到 SDL texture 并 RenderPresent + * 2. SDL_PollEvent 排空:鼠标/键盘事件入内部状态,SDL_QUIT 返回非 0 + * 返回非 0 表示收到退出请求(窗口关闭),调用方应置 g_quit。 */ +int host_pump_and_present(void); + +/* ---- host 分配的缓冲(替代裸机硬编码物理地址) ---- + * g_host_sprite : sprite 渲染缓冲(替代 SPRITE_BUF_ADDR 0x7000000) + * g_host_backbuf: 真实 framebuffer 对应的 back buffer(替代 g_real_fb 指向的显存) + * 大小均为 width*height*4 字节,由 host_main.c 在初始化时分配。 */ +extern u8 *g_host_sprite; +extern u8 *g_host_backbuf; + +#endif /* DESHAB_HOST_MAIN_H */ diff --git a/CODE/tools/fat32_lfn.h b/CODE/tools/fat32_lfn.h new file mode 100644 index 0000000..8e7ab49 --- /dev/null +++ b/CODE/tools/fat32_lfn.h @@ -0,0 +1,231 @@ +/* fat32_lfn.h — Deshab FAT32 VFAT 长文件名(LFN)解析共享库(static inline,无库) + * + * 前置条件:包含者已定义 u8/u16/u32 基础类型(与 fat32_io.h 一致)。 + * + * FAT32 LFN 原理: + * 目录项中 attr==0x0F 的是 LFN 片段项。每项含 13 个 UTF-16 LE 字符 + * (name1[5] + name2[6] + name3[2]),按逆序存储——序号最高(带 0x40 + * 标志)的项最先出现,序号递减,最后紧跟一个 8.3 短目录项作为"主项"。 + * 短名项的 checksum(由 name11 计算)用于校验整条 LFN 链完整性。 + * + * 用法(在目录遍历循环中): + * fat32_lfn_buf lfn; + * fat32_lfn_init(&lfn); + * // 外层簇遍历循环——lfn 必须跨簇存活(LFN 链可能跨簇边界) + * while (遍历目录簇) { + * for (每个 32 字节目录项 e) { + * if (name[0]==0) 目录结束; + * if (name[0]==0xE5) { fat32_lfn_init(&lfn); continue; } + * int is_short = fat32_lfn_process(&lfn, entry_ptr); + * if (is_short) { + * // lfn.valid==1 时有完整 LFN,可 fat32_lfn_to_ascii 取名 + * // 用 fat32_lfn_match 匹配长名或 8.3 短名 + * } + * } + * } + * + * 注意:fat32_lfn_buf 必须在簇遍历循环**外**声明,跨簇切换时不清空, + * 以支持跨簇边界的 LFN 链。 + */ + +#ifndef DESHAB_FAT32_LFN_H +#define DESHAB_FAT32_LFN_H + +/* LFN 最大字符数(FAT32 规范最多 20 个 LFN 项 = 260 字符) */ +#define FAT32_LFN_MAX 260 + +/* LFN 收集缓冲(栈/局部变量分配) */ +typedef struct { + u16 chars[FAT32_LFN_MAX]; /* UTF-16 LE 累积字符(正序) */ + int count; /* 有效字符数(不含 0x0000 终止符/0xFFFF 填充) */ + u8 checksum; /* LFN 链 checksum(来自 0x40 标志项) */ + int valid; /* 1=LFN 链完整且 checksum 与短名项匹配 */ +} fat32_lfn_buf; + +/* 初始化/清空 LFN 缓冲 */ +static inline void fat32_lfn_init(fat32_lfn_buf *buf) { + buf->count = 0; + buf->valid = 0; + buf->checksum = 0; + for (int i = 0; i < FAT32_LFN_MAX; i++) buf->chars[i] = 0; +} + +/* 计算 8.3 短名(11 字节)的 LFN checksum。 + * 标准 FAT32 算法:cksum = ((cksum & 1) << 7) + (cksum >> 1) + name[i] */ +static inline u8 fat32_lfn_checksum(const u8 name11[11]) { + u8 cksum = 0; + for (int i = 0; i < 11; i++) { + cksum = (u8)(((cksum & 1) << 7) + (cksum >> 1) + name11[i]); + } + return cksum; +} + +/* 处理一个 32 字节目录项。自动判断 LFN 项 / 短名项 / 已删除项。 + * + * 调用方对目录遍历中的每个条目(跳过 name[0]==0 结束符后)调用此函数。 + * - LFN 项(attr==0x0F):收集 13 个 UTF-16 字符到 buf,返回 0 + * - 已删除项(name[0]==0xE5):清空 buf,返回 0 + * - 短名项:校验 checksum,设 buf->valid,返回 1 + * + * 返回 1=刚处理的是短名项(此时可检查 buf->valid 判断是否有有效 LFN), + * 0=LFN 项或已删除项。 + * + * 注意:遇到带 0x40 标志的 LFN 项(链开始)时自动重置 buf。 + * 遇到已删除项时自动清空 buf(防止残留 LFN 污染下一个文件)。 */ +static inline int fat32_lfn_process(fat32_lfn_buf *buf, const u8 *entry) { + u8 attr = entry[11]; + + /* 已删除项:清空 LFN 缓冲 */ + if (entry[0] == 0xE5) { + fat32_lfn_init(buf); + return 0; + } + + /* LFN 项(attr == 0x0F) */ + if (attr == 0x0F) { + u8 seq = entry[0]; + u8 ord = (u8)(seq & 0x3F); /* 序号 1-20 */ + if (ord == 0 || ord > 20) { + fat32_lfn_init(buf); + return 0; + } + /* 带 0x40 标志 = LFN 链开始(最高序号项),重置缓冲 */ + if (seq & 0x40) { + int prev_cksum = buf->checksum; + fat32_lfn_init(buf); + buf->checksum = entry[13]; + (void)prev_cksum; + } + /* 提取 13 个 UTF-16 LE 字符,放到 (ord-1)*13 位置(正序) */ + int base = (ord - 1) * 13; + if (base + 13 > FAT32_LFN_MAX) { + fat32_lfn_init(buf); + return 0; + } + /* name1: entry[1..10] = 5 个 UTF-16 LE 字符 */ + for (int i = 0; i < 5; i++) { + u16 c = (u16)entry[1 + i * 2] | ((u16)entry[2 + i * 2] << 8); + buf->chars[base + i] = c; + } + /* name2: entry[14..25] = 6 个 UTF-16 LE 字符 */ + for (int i = 0; i < 6; i++) { + u16 c = (u16)entry[14 + i * 2] | ((u16)entry[15 + i * 2] << 8); + buf->chars[base + 5 + i] = c; + } + /* name3: entry[28..31] = 2 个 UTF-16 LE 字符 */ + for (int i = 0; i < 2; i++) { + u16 c = (u16)entry[28 + i * 2] | ((u16)entry[29 + i * 2] << 8); + buf->chars[base + 11 + i] = c; + } + return 0; /* LFN 项,还不是短名项 */ + } + + /* 短名项:校验 checksum */ + u8 cksum = fat32_lfn_checksum(entry); + if (buf->checksum != 0 && buf->checksum == cksum) { + /* LFN 链有效——计算实际字符数(截断于 0x0000 终止符或 0xFFFF 填充符) */ + int n = 0; + for (int i = 0; i < FAT32_LFN_MAX; i++) { + if (buf->chars[i] == 0x0000 || buf->chars[i] == 0xFFFF) break; + n++; + } + buf->count = n; + buf->valid = 1; + } else { + /* checksum 不匹配或无 LFN 链:清空,回退 8.3 */ + fat32_lfn_init(buf); + } + return 1; /* 短名项处理完毕 */ +} + +/* 从 LFN 缓冲提取 ASCII 名(UTF-16→ASCII 缩窄,仅 BMP U+0000..U+007F)。 + * out_cap 为 out 缓冲容量(含 NUL)。 + * 返回字符串长度(不含 NUL),-1=无有效 LFN 或含非 ASCII 字符。 */ +static inline int fat32_lfn_to_ascii(const fat32_lfn_buf *buf, char *out, int out_cap) { + if (!buf->valid || buf->count == 0 || out_cap <= 0) return -1; + int n = buf->count; + if (n >= out_cap) n = out_cap - 1; + for (int i = 0; i < n; i++) { + u16 c = buf->chars[i]; + if (c > 0x7F) return -1; /* 含非 ASCII 字符 */ + out[i] = (char)(u8)c; + } + out[n] = 0; + return n; +} + +/* ASCII 大小写不敏感字符串比较 */ +static inline int fat32_lfn_streq_ci(const char *a, const char *b) { + if (!a || !b) return 0; + while (*a && *b) { + char ca = *a, cb = *b; + if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 32); + if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 32); + if (ca != cb) return 0; + a++; b++; + } + return *a == *b; +} + +/* 判断 ASCII 字符串是否以指定后缀结尾(大小写不敏感)。 + * 例如 fat32_lfn_ends_with_ci("libtest.so", ".so") → 1 */ +static inline int fat32_lfn_ends_with_ci(const char *s, const char *suffix) { + if (!s || !suffix) return 0; + int sl = 0, xl = 0; + while (s[sl]) sl++; + while (suffix[xl]) xl++; + if (xl > sl || xl == 0) return 0; + for (int i = 0; i < xl; i++) { + char a = s[sl - xl + i]; + char b = suffix[i]; + if (a >= 'a' && a <= 'z') a = (char)(a - 32); + if (b >= 'a' && b <= 'z') b = (char)(b - 32); + if (a != b) return 0; + } + return 1; +} + +/* 匹配目录项:优先匹配 LFN 长名(大小写不敏感),回退 8.3 短名。 + * buf : LFN 收集缓冲(短名项处理后) + * name11 : 短名项的 11 字节 8.3 名 + * target_long: 目标长名(ASCII,可为 NULL=不匹配长名) + * target83 : 目标 8.3 名(11 字节,可为 NULL=不匹配 8.3) + * 返回 1=匹配,0=不匹配。 + * FAT32 规范:文件名查找大小写不敏感。 */ +static inline int fat32_lfn_match(const fat32_lfn_buf *buf, const u8 name11[11], + const char *target_long, const char *target83) { + /* 1. 优先匹配 LFN 长名 */ + if (buf->valid && buf->count > 0 && target_long) { + char ascii[FAT32_LFN_MAX]; + int n = fat32_lfn_to_ascii(buf, ascii, sizeof(ascii)); + if (n >= 0 && fat32_lfn_streq_ci(ascii, target_long)) return 1; + } + /* 2. 回退 8.3 短名匹配(精确比较 11 字节) */ + if (target83 && name11) { + for (int i = 0; i < 11; i++) { + if (name11[i] != (u8)target83[i]) return 0; + } + return 1; + } + return 0; +} + +/* 从 8.3 短名(11 字节)生成可显示 ASCII 字符串(如 "README.TXT")。 + * 与 fat32_io.h 的 f32_name_from_83 功能相同,独立提供以避免循环依赖。 */ +static inline void fat32_lfn_short_to_str(const u8 name11[11], char out[13]) { + int p = 0; + for (int j = 0; j < 8; j++) { + if (name11[j] == ' ') break; + out[p++] = (char)name11[j]; + } + if (name11[8] != ' ') { + out[p++] = '.'; + for (int j = 8; j < 11; j++) { + if (name11[j] == ' ') break; + out[p++] = (char)name11[j]; + } + } + out[p] = 0; +} + +#endif /* DESHAB_FAT32_LFN_H */ diff --git a/ISO/run_desktop_host.sh b/ISO/run_desktop_host.sh new file mode 100644 index 0000000..ec94b6e --- /dev/null +++ b/ISO/run_desktop_host.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# run_desktop_host.sh — 在 Linux 下直接启动 Deshab 桌面进行测试 +# +# 用法: +# ./ISO/run_desktop_host.sh # 编译并运行,默认 ISO/deshab.img +# ./ISO/run_desktop_host.sh /path/to.img # 指定镜像 +# +# 依赖:libsdl2-dev(Debian/Ubuntu: sudo apt install libsdl2-dev) + +set -e + +# 定位项目根(本脚本位于 ISO/ 下) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +IMG="${1:-$ROOT/ISO/deshab.img}" + +# 检查 SDL2 +if ! command -v sdl2-config >/dev/null 2>&1; then + echo "[run] 错误:未找到 sdl2-config,请先安装 libsdl2-dev" >&2 + echo " Debian/Ubuntu: sudo apt install libsdl2-dev" >&2 + echo " Fedora: sudo dnf install SDL2-devel" >&2 + exit 1 +fi + +# 检查镜像 +if [ ! -f "$IMG" ]; then + echo "[run] 错误:镜像不存在: $IMG" >&2 + echo " 请先在 Windows 下运行 build.bat 生成 ISO/deshab.img" >&2 + exit 1 +fi + +# 编译 +echo "[run] 编译 desktop_host($(basename "$IMG"))..." +make -C "$ROOT/CODE/desktop" -f Makefile.linux + +# 运行 +echo "[run] 启动桌面..." +exec "$ROOT/CODE/desktop/desktop_host" --img "$IMG" diff --git a/docs/上下文/250729/Deshab Linux_EXE 兼容层开发.md b/docs/上下文/260729/Deshab Linux_EXE 兼容层开发.md similarity index 100% rename from docs/上下文/250729/Deshab Linux_EXE 兼容层开发.md rename to docs/上下文/260729/Deshab Linux_EXE 兼容层开发.md diff --git a/docs/上下文/250729/开发操作系统以运行Deaicup软件.md b/docs/上下文/260729/开发操作系统以运行Deaicup软件.md similarity index 100% rename from docs/上下文/250729/开发操作系统以运行Deaicup软件.md rename to docs/上下文/260729/开发操作系统以运行Deaicup软件.md diff --git a/docs/上下文/250729/整理 RE_上下文 文档.md b/docs/上下文/260729/整理 RE_上下文 文档.md similarity index 100% rename from docs/上下文/250729/整理 RE_上下文 文档.md rename to docs/上下文/260729/整理 RE_上下文 文档.md diff --git a/docs/上下文/250729/自定义内核算法与驱动配置.md b/docs/上下文/260729/自定义内核算法与驱动配置.md similarity index 100% rename from docs/上下文/250729/自定义内核算法与驱动配置.md rename to docs/上下文/260729/自定义内核算法与驱动配置.md diff --git a/docs/上下文/250729/规划项目开发工作.md b/docs/上下文/260729/规划项目开发工作.md similarity index 100% rename from docs/上下文/250729/规划项目开发工作.md rename to docs/上下文/260729/规划项目开发工作.md diff --git a/docs/上下文/25072901/Deshab Linux_EXE 兼容层开发.md b/docs/上下文/26072901/Deshab Linux_EXE 兼容层开发.md similarity index 100% rename from docs/上下文/25072901/Deshab Linux_EXE 兼容层开发.md rename to docs/上下文/26072901/Deshab Linux_EXE 兼容层开发.md diff --git a/docs/上下文/25072901/Review Context and Use Multi-Agent.md b/docs/上下文/26072901/Review Context and Use Multi-Agent.md similarity index 100% rename from docs/上下文/25072901/Review Context and Use Multi-Agent.md rename to docs/上下文/26072901/Review Context and Use Multi-Agent.md diff --git a/docs/上下文/25072901/Review Contexts and Multi-Agent Progression.md b/docs/上下文/26072901/Review Contexts and Multi-Agent Progression.md similarity index 100% rename from docs/上下文/25072901/Review Contexts and Multi-Agent Progression.md rename to docs/上下文/26072901/Review Contexts and Multi-Agent Progression.md diff --git a/docs/上下文/25072901/自定义内核算法与驱动配置.md b/docs/上下文/26072901/自定义内核算法与驱动配置.md similarity index 100% rename from docs/上下文/25072901/自定义内核算法与驱动配置.md rename to docs/上下文/26072901/自定义内核算法与驱动配置.md