FU
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
/* boot_log.h — 跨 UTSM/DSK/应用 ELF 的启动日志环形缓冲(freestanding,无 libc)
|
||||
*
|
||||
* boot_log_info 由 UTSM 在静态内存中创建,经 dsk_boot_context.utsm_state 传递。
|
||||
* DSK 注册 flush 回调,将缓冲写入 FAT32: SYSTEM/DESHAB64/DEV/BOOTLOG.TXT
|
||||
* (DEV 目录不存在时回退 ESP 根目录 BOOTLOG.TXT)。
|
||||
*/
|
||||
#ifndef BOOT_LOG_H
|
||||
#define BOOT_LOG_H
|
||||
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
|
||||
#define BOOT_LOG_MAGIC 0x424C4746u /* "BLGF" */
|
||||
#define BOOT_LOG_VERSION 1u
|
||||
|
||||
typedef struct boot_log_info boot_log_info;
|
||||
|
||||
typedef int (*boot_log_flush_fn)(void);
|
||||
|
||||
struct boot_log_info {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
char *buf;
|
||||
u32 capacity;
|
||||
u32 length;
|
||||
u32 dropped;
|
||||
boot_log_flush_fn flush;
|
||||
};
|
||||
|
||||
static inline void boot_log_append(boot_log_info *bi, const char *s) {
|
||||
if (!bi || bi->magic != BOOT_LOG_MAGIC || !s || !bi->buf)
|
||||
return;
|
||||
while (*s) {
|
||||
if (bi->length < bi->capacity)
|
||||
bi->buf[bi->length++] = *s;
|
||||
else
|
||||
bi->dropped++;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void boot_log_bind(boot_log_info **slot, u64 utsm_state) {
|
||||
if (!slot)
|
||||
return;
|
||||
*slot = 0;
|
||||
if (!utsm_state)
|
||||
return;
|
||||
boot_log_info *bi = (boot_log_info *)(u64)utsm_state;
|
||||
if (bi && bi->magic == BOOT_LOG_MAGIC && bi->buf && bi->capacity)
|
||||
*slot = bi;
|
||||
}
|
||||
|
||||
static inline int boot_log_try_flush(boot_log_info *bi) {
|
||||
if (bi && bi->flush)
|
||||
return bi->flush();
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif /* BOOT_LOG_H */
|
||||
@@ -0,0 +1,856 @@
|
||||
/* fat32_write.h - Deshab 共享 FAT32 写库(static inline,无库)
|
||||
*
|
||||
* 前置条件:包含者已定义 u8/u16/u32/u64/i64 基础类型。
|
||||
* 通过 f32w_init() 注入 block_read/block_write 函数指针(签名与 f32_/gpt_ 库一致)。
|
||||
*
|
||||
* 与 fat32_io.h 的分工:
|
||||
* fat32_io.h - 8.3 短名读写,依赖 256 扇区 BPB+FAT+根目录缓存窗口
|
||||
* fat32_write.h - LFN 长名创建 / mkdir / 目录簇链扩展,FAT 按需单扇区访问
|
||||
*
|
||||
* 本库解决 fat32_io.h 的三个写缺口(大 ESP 场景,2GB: spc=8, FAT=4086 扇区,
|
||||
* 根目录在分区 LBA 8204,均远超 256 扇区窗口):
|
||||
* 1. LFN 生成:VFAT LFN 链(每项 13 UTF-16 字符,逆序存储,0x40 标志 +
|
||||
* 短名 checksum),配套 ~N 数字尾巴的 8.3 短名生成与唯一化
|
||||
* 2. mkdir:分配目录簇 + 写入 "." / ".." 项 + 父目录插入 LFN 条目
|
||||
* 3. 目录簇链扩展:目录槽位不足时自动分配新簇并接入链尾(槽位可跨簇)
|
||||
*
|
||||
* FAT 访问策略:按需读改写单个 FAT 扇区(含缓存),修改后立即回写所有 FAT
|
||||
* 副本(BPB->fc 份)。不依赖任何批量缓存窗口,任意簇号/FAT 偏移均可访问。
|
||||
*
|
||||
* 用法:
|
||||
* f32w_init(block_read, block_write) - 初始化(注入块设备)
|
||||
* f32w_write_file(dir_clus, "user.conf", d, n) - 目录中写/替换长名文件
|
||||
* f32w_mkdir(dir_clus, "MyDir", &out_clus) - 创建子目录(LFN)
|
||||
* f32w_resolve_dir("system/deshab64", &clus) - 按路径定位目录簇
|
||||
* f32w_write_path("system/deshab64/user.conf", d, n)- 按路径写文件(LFN)
|
||||
* f32w_mkdir_path("system/新建目录", &clus) - 按路径建目录
|
||||
* f32w_find(dir_clus, name, want_dir, ...) - 按长名查找(LFN 优先)
|
||||
*
|
||||
* 缓冲区:每包含者约 9KB BSS(512 BPB + 512 FAT 扇区 + 簇缓冲 ×2)。
|
||||
* 与 fat32_io.h 可同时包含(前缀 f32w_ / f32_ 不冲突),但二者状态独立。
|
||||
*/
|
||||
|
||||
#ifndef DESHAB_FAT32_WRITE_H
|
||||
#define DESHAB_FAT32_WRITE_H
|
||||
|
||||
#include "fat32_lfn.h"
|
||||
|
||||
/* ---- block 设备函数类型(与 fat32_io.h / gpt_write.h 一致:index 固定 0) ---- */
|
||||
typedef int (*f32w_block_read_fn)(u32 index, u64 lba, u32 count, void *buf);
|
||||
typedef int (*f32w_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf);
|
||||
|
||||
static f32w_block_read_fn f32w_blk_read = 0;
|
||||
static f32w_block_write_fn f32w_blk_write = 0;
|
||||
|
||||
/* ---- 目录簇缓冲大小(含 data 暂存缓冲)。build.ps1 mkfs spc=8 -> 4096。
|
||||
* 包含者可在 include 前 #define F32W_CLUS_BYTES 覆盖(须为 512 的倍数)。 */
|
||||
#ifndef F32W_CLUS_BYTES
|
||||
#define F32W_CLUS_BYTES 4096
|
||||
#endif
|
||||
|
||||
#define F32W_EOC 0x0FFFFFFFu
|
||||
#define F32W_EOC_VAL 0x0FFFFFFFu /* 写入 FAT 的 EOC 值(高 4 位保留位置 0x0Fxxxxxx 已含) */
|
||||
|
||||
/* ---- BPB(512B,与 f32_bpb 布局一致;独立定义避免依赖 fat32_io.h) ---- */
|
||||
typedef struct __attribute__((packed)) {
|
||||
u8 jmp[3]; char oem[8]; u16 bps; u8 spc; u16 rsvd; u8 fc; u16 root_ent;
|
||||
u16 ts16; u8 media; u16 spf16; u16 spt; u16 heads; u32 hidden; u32 ts32;
|
||||
u32 spf; u16 flags; u16 ver; u32 root_clus; u16 fsi; u16 bkboot;
|
||||
u8 res[12]; u8 drv; u8 ntfl; u8 sig; u32 ser; char lbl[11]; char typ[8];
|
||||
u8 code[420]; u16 boot_sig;
|
||||
} f32w_bpb_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char name[11]; u8 attr; u8 ntr; u8 ctenth;
|
||||
u16 ctime; u16 cdate; u16 adate; u16 chigh;
|
||||
u16 wtime; u16 wdate; u16 clow; u32 fsize;
|
||||
} f32w_dirent_t;
|
||||
|
||||
/* ---- 挂载上下文(f32w_bpb_load 填充) ---- */
|
||||
typedef struct {
|
||||
u32 spc; /* 每簇扇区数 */
|
||||
u32 clus_bytes; /* spc * 512 */
|
||||
u32 fat_lba; /* FAT 副本 0 起始 LBA(= rsvd) */
|
||||
u32 fat_copies; /* FAT 副本数(BPB->fc,实际至少 2) */
|
||||
u32 fat_sectors; /* 单份 FAT 扇区数(BPB->spf) */
|
||||
u32 data_lba; /* 数据区起始 LBA */
|
||||
u32 root_clus; /* 根目录首簇 */
|
||||
u32 fat_entries; /* FAT 项数上界 = spf*512/4 */
|
||||
int loaded;
|
||||
} f32w_ctx;
|
||||
|
||||
static f32w_ctx f32w_c;
|
||||
|
||||
/* ---- 内部缓冲(每包含者独立) ---- */
|
||||
static u8 f32w_sec[512]; /* BPB / FSInfo 扇区缓冲 */
|
||||
static u8 f32w_fat[512]; /* FAT 单扇区缓存 */
|
||||
static u32 f32w_fat_sec_no = 0xFFFFFFFFu; /* 缓存的 FAT 扇区号(FAT 内偏移) */
|
||||
static u8 f32w_dir[F32W_CLUS_BYTES]; /* 目录簇缓存(槽位扫描/条目写入) */
|
||||
static u32 f32w_dir_no = 0; /* 缓存的目录簇号(0=无,簇号最小为 2) */
|
||||
static int f32w_dir_dirty = 0;
|
||||
static u8 f32w_stage[F32W_CLUS_BYTES]; /* 数据写入暂存(尾簇零填充) */
|
||||
static u32 f32w_alloc_hint = 2; /* 空闲簇分配起点提示 */
|
||||
|
||||
/* ---- 初始化:注入 block_read/block_write 函数指针 ---- */
|
||||
static inline void f32w_init(f32w_block_read_fn rd, f32w_block_write_fn wr) {
|
||||
f32w_blk_read = rd;
|
||||
f32w_blk_write = wr;
|
||||
f32w_c.loaded = 0;
|
||||
f32w_fat_sec_no = 0xFFFFFFFFu;
|
||||
f32w_dir_no = 0;
|
||||
f32w_dir_dirty = 0;
|
||||
f32w_alloc_hint = 2;
|
||||
}
|
||||
|
||||
static inline int f32w_read_sectors(u32 lba, u32 count, u8 *out) {
|
||||
return f32w_blk_read ? f32w_blk_read(0, lba, count, out) : -1;
|
||||
}
|
||||
static inline int f32w_write_sectors(u32 lba, u32 count, const u8 *buf) {
|
||||
return f32w_blk_write ? f32w_blk_write(0, lba, count, buf) : -1;
|
||||
}
|
||||
|
||||
/* ---- 小工具(无库约束:手写循环替代 mem*) ---- */
|
||||
static inline void f32w_mem_set(u8 *p, u8 v, u32 n) { for (u32 i = 0; i < n; i++) p[i] = v; }
|
||||
static inline u32 f32w_r32(const u8 *p) {
|
||||
return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24);
|
||||
}
|
||||
static inline u16 f32w_r16(const u8 *p) { return (u16)p[0] | ((u16)p[1] << 8); }
|
||||
|
||||
/* ---- BPB 加载 / 挂载上下文 ---- */
|
||||
static inline int f32w_bpb_load(void) {
|
||||
if (f32w_c.loaded) return 0;
|
||||
if (f32w_read_sectors(0, 1, f32w_sec) != 0) return -1;
|
||||
const f32w_bpb_t *b = (const f32w_bpb_t *)f32w_sec;
|
||||
if (b->boot_sig != 0xAA55 || b->bps != 512) return -2;
|
||||
if (b->spf == 0 || b->root_clus < 2 || b->spc == 0) return -3;
|
||||
u32 fc = b->fc ? b->fc : 1;
|
||||
u32 cb = (u32)b->spc * 512;
|
||||
if (cb > F32W_CLUS_BYTES) return -4; /* 簇超出缓冲,包含者应调大 F32W_CLUS_BYTES */
|
||||
f32w_c.spc = b->spc;
|
||||
f32w_c.clus_bytes = cb;
|
||||
f32w_c.fat_lba = b->rsvd;
|
||||
f32w_c.fat_copies = fc;
|
||||
f32w_c.fat_sectors = b->spf;
|
||||
f32w_c.data_lba = b->rsvd + fc * b->spf;
|
||||
f32w_c.root_clus = b->root_clus;
|
||||
f32w_c.fat_entries = b->spf * 512 / 4;
|
||||
f32w_c.loaded = 1;
|
||||
f32w_fat_sec_no = 0xFFFFFFFFu; /* 新挂载,作废 FAT 缓存 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u32 f32w_clus_lba(u32 clus) {
|
||||
return f32w_c.data_lba + (clus - 2) * f32w_c.spc;
|
||||
}
|
||||
|
||||
/* ---- FAT 按需访问(单扇区缓存 + 全副本回写) ---- */
|
||||
|
||||
/* 读 FAT 项。返回 0 成功,*out 为 28 位簇值。 */
|
||||
static inline int f32w_fat_get(u32 clus, u32 *out) {
|
||||
if (clus < 2 || clus >= f32w_c.fat_entries) return -1;
|
||||
u32 sec = clus >> 7; /* 每扇区 128 项 */
|
||||
u32 off = (clus & 127) * 4;
|
||||
if (sec != f32w_fat_sec_no) {
|
||||
if (f32w_read_sectors(f32w_c.fat_lba + sec, 1, f32w_fat) != 0) return -2;
|
||||
f32w_fat_sec_no = sec;
|
||||
}
|
||||
*out = f32w_r32(f32w_fat + off) & 0x0FFFFFFF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 写 FAT 项(含 28 位掩码),立即回写所有 FAT 副本对应扇区。 */
|
||||
static inline int f32w_fat_set(u32 clus, u32 val) {
|
||||
if (clus < 2 || clus >= f32w_c.fat_entries) return -1;
|
||||
u32 sec = clus >> 7;
|
||||
u32 off = (clus & 127) * 4;
|
||||
if (sec != f32w_fat_sec_no) {
|
||||
if (f32w_read_sectors(f32w_c.fat_lba + sec, 1, f32w_fat) != 0) return -2;
|
||||
f32w_fat_sec_no = sec;
|
||||
}
|
||||
u32 old = f32w_r32(f32w_fat + off);
|
||||
val &= 0x0FFFFFFF;
|
||||
f32w_fat[off] = (u8)(val & 0xFF);
|
||||
f32w_fat[off + 1] = (u8)((val >> 8) & 0xFF);
|
||||
f32w_fat[off + 2] = (u8)((val >> 16) & 0xFF);
|
||||
f32w_fat[off + 3] = (u8)((old & 0xF0) | ((val >> 24) & 0x0F));
|
||||
for (u32 f = 0; f < f32w_c.fat_copies; f++) {
|
||||
u32 lba = f32w_c.fat_lba + f * f32w_c.fat_sectors + sec;
|
||||
if (f32w_write_sectors(lba, 1, f32w_fat) != 0) return -3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 分配一个空闲簇(线性扫描 + 起点提示轮转)。返回簇号,0=已满/失败。 */
|
||||
static inline u32 f32w_alloc_free(void) {
|
||||
u32 total = f32w_c.fat_entries > 2 ? f32w_c.fat_entries - 2 : 0;
|
||||
if (total == 0) return 0;
|
||||
u32 start = (f32w_alloc_hint >= 2 && f32w_alloc_hint < f32w_c.fat_entries)
|
||||
? f32w_alloc_hint : 2;
|
||||
for (u32 i = 0; i < total; i++) {
|
||||
u32 c = 2 + ((start - 2 + i) % total);
|
||||
u32 v = 0;
|
||||
if (f32w_fat_get(c, &v) != 0) return 0;
|
||||
if (v == 0) {
|
||||
f32w_alloc_hint = c + 1;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 释放整条簇链(逐项清 0)。带环保护。 */
|
||||
static inline int f32w_free_chain(u32 clus) {
|
||||
u32 cur = clus;
|
||||
u64 guard = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8) {
|
||||
if (++guard > (u64)f32w_c.fat_entries + 2) return -1; /* FAT 成环,损坏 */
|
||||
u32 next = 0;
|
||||
if (f32w_fat_get(cur, &next) != 0) return -2;
|
||||
if (f32w_fat_set(cur, 0) != 0) return -3;
|
||||
cur = next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 目录簇缓存(槽位扫描 / 条目写入共用) ---- */
|
||||
static inline int f32w_dir_flush(void) {
|
||||
if (!f32w_dir_dirty || f32w_dir_no == 0) { f32w_dir_dirty = 0; return 0; }
|
||||
if (f32w_write_sectors(f32w_clus_lba(f32w_dir_no), f32w_c.spc, f32w_dir) != 0) return -1;
|
||||
f32w_dir_dirty = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int f32w_dir_load(u32 clus) {
|
||||
if (f32w_dir_no == clus && clus != 0) return 0;
|
||||
if (f32w_dir_flush() != 0) return -1;
|
||||
if (f32w_read_sectors(f32w_clus_lba(clus), f32w_c.spc, f32w_dir) != 0) return -2;
|
||||
f32w_dir_no = clus;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 在 (clus, idx) 写入 32 字节目录项。 */
|
||||
static inline int f32w_entry_write(u32 clus, u32 idx, const u8 *e32) {
|
||||
if (idx * 32 + 32 > f32w_c.clus_bytes) return -1;
|
||||
if (f32w_dir_load(clus) != 0) return -2;
|
||||
for (int i = 0; i < 32; i++) f32w_dir[idx * 32 + i] = e32[i];
|
||||
f32w_dir_dirty = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 槽位步进:同簇内 idx+1,越界则沿 FAT 链进入下一簇(run 保证连续)。 */
|
||||
static inline int f32w_slot_step(u32 *clus, u32 *idx) {
|
||||
(*idx)++;
|
||||
if ((*idx) * 32 >= f32w_c.clus_bytes) {
|
||||
u32 next = 0;
|
||||
if (f32w_fat_get(*clus, &next) != 0) return -1;
|
||||
if (next < 2 || next >= 0x0FFFFFF8) return -1;
|
||||
*clus = next;
|
||||
*idx = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 8.3 短名生成(含 ~N 唯一化) ---- */
|
||||
|
||||
static inline char f32w_up(char c) { return (c >= 'a' && c <= 'z') ? (char)(c - 32) : c; }
|
||||
|
||||
/* 合法 8.3 短名字符(已转大写后):字母/数字/!#$%&'()-@^_`{}~ */
|
||||
static inline int f32w_char_ok_83(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return 1;
|
||||
if (c >= '0' && c <= '9') return 1;
|
||||
if (c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'') return 1;
|
||||
if (c == '(' || c == ')' || c == '-' || c == '@' || c == '^' || c == '_') return 1;
|
||||
if (c == '`' || c == '{' || c == '}' || c == '~') return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 名字长度(手动 strlen)。 */
|
||||
static inline u32 f32w_strlen(const char *s) {
|
||||
u32 n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 生成长名对应的 8.3 短名(未唯一化)。
|
||||
* name11 : 输出 11 字符短名(大写、空格填充;非法字符转 '_',超长截断)
|
||||
* needs_lfn: 输出 1=长名无法无损表示为 8.3(需写 LFN 链 + 后续 ~N 唯一化)
|
||||
* 返回 0 成功,-1 名字非法(空/过长/含非 ASCII/纯点)。 */
|
||||
static inline int f32w_make_short(const char *name, char name11[11], int *needs_lfn) {
|
||||
u32 len = f32w_strlen(name);
|
||||
if (len == 0 || len > 255) return -1;
|
||||
u32 last_dot = 0xFFFFFFFFu;
|
||||
u32 dot_count = 0;
|
||||
int all_upper_valid = 1;
|
||||
for (u32 i = 0; i < len; i++) {
|
||||
char c = name[i];
|
||||
if ((u8)c < 0x20 || (u8)c >= 0x7F || c == '/') return -1;
|
||||
if (c == '.') { last_dot = i; dot_count++; }
|
||||
if (c >= 'a' && c <= 'z') all_upper_valid = 0;
|
||||
else if (!f32w_char_ok_83(f32w_up(c))) all_upper_valid = 0;
|
||||
}
|
||||
/* 纯点 / 尾点 / 首空格:非法(简化处理,与 Windows 行为近似) */
|
||||
int non_dot = 0;
|
||||
for (u32 i = 0; i < len; i++) if (name[i] != '.') { non_dot = 1; break; }
|
||||
if (!non_dot || name[len - 1] == '.' || name[0] == ' ') return -1;
|
||||
|
||||
u32 base_end = (last_dot == 0xFFFFFFFFu) ? len : last_dot;
|
||||
u32 base_len = base_end;
|
||||
u32 ext_len = (last_dot == 0xFFFFFFFFu) ? 0 : len - last_dot - 1;
|
||||
|
||||
*needs_lfn = !(all_upper_valid && base_len <= 8 && ext_len <= 3 && dot_count <= 1);
|
||||
|
||||
for (int i = 0; i < 11; i++) name11[i] = ' ';
|
||||
u32 bi = 0;
|
||||
for (u32 i = 0; i < base_len && bi < 8; i++) {
|
||||
char c = f32w_up(name[i]);
|
||||
if (!f32w_char_ok_83(c)) c = '_';
|
||||
name11[bi++] = c;
|
||||
}
|
||||
if (bi == 0) name11[bi++] = '_'; /* 空基名(如 ".config")用 '_' 兜底 */
|
||||
u32 ei = 0;
|
||||
if (last_dot != 0xFFFFFFFFu) {
|
||||
for (u32 i = 0; i < ext_len && ei < 3; i++) {
|
||||
char c = f32w_up(name[last_dot + 1 + i]);
|
||||
if (!f32w_char_ok_83(c)) c = '_';
|
||||
name11[8 + ei++] = c;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 目录中是否已存在指定 11 字符短名(仅比较短名项)。1=存在,0=不存在,-1=IO 错。 */
|
||||
static inline int f32w_dir_short_exists(u32 dir_clus, const char name11[11]) {
|
||||
u32 dclus = dir_clus;
|
||||
u64 guard = 0;
|
||||
while (dclus >= 2 && dclus < 0x0FFFFFF8) {
|
||||
if (++guard > (u64)f32w_c.fat_entries + 2) return -1;
|
||||
if (f32w_dir_load(dclus) != 0) return -1;
|
||||
u32 max_e = f32w_c.clus_bytes / 32;
|
||||
for (u32 e = 0; e < max_e; e++) {
|
||||
const u8 *ent = f32w_dir + e * 32;
|
||||
if (ent[0] == 0x00) return 0; /* 目录结尾 */
|
||||
if (ent[0] == 0xE5) continue;
|
||||
if (ent[11] == 0x0F) continue; /* LFN 项 */
|
||||
if (ent[11] & 0x08) continue; /* 卷标 */
|
||||
int same = 1;
|
||||
for (int i = 0; i < 11; i++)
|
||||
if (ent[i] != (u8)name11[i]) { same = 0; break; }
|
||||
if (same) return 1;
|
||||
}
|
||||
u32 next = 0;
|
||||
if (f32w_fat_get(dclus, &next) != 0) return -1;
|
||||
dclus = next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 生成长名对应的唯一 8.3 短名(lossy 时追加 ~1..~999999 数字尾巴)。
|
||||
* 返回 0 成功,负值失败。 */
|
||||
static inline int f32w_make_unique_short(u32 dir_clus, const char *name, char name11[11]) {
|
||||
int needs_lfn = 0;
|
||||
if (f32w_make_short(name, name11, &needs_lfn) != 0) return -1;
|
||||
if (!needs_lfn) return 0; /* 无损 8.3,同名冲突由 f32w_find 语义覆盖 */
|
||||
/* 提取 make_short 产生的 base / ext,枚举 BASE~N 直到唯一 */
|
||||
char base[8];
|
||||
u32 base_len = 0;
|
||||
for (u32 i = 0; i < 8 && name11[i] != ' '; i++) base[base_len++] = name11[i];
|
||||
if (base_len == 0) { base[0] = '_'; base_len = 1; }
|
||||
for (u32 n = 1; n <= 999999u; n++) {
|
||||
char digits[8];
|
||||
int dn = 0;
|
||||
u32 v = n;
|
||||
while (v) { digits[dn++] = (char)('0' + (v % 10)); v /= 10; }
|
||||
u32 bl = (dn + 1 >= 8) ? 1 : 8 - dn - 1; /* 保留 '~'+数字 的空间 */
|
||||
if (base_len < bl) bl = base_len;
|
||||
for (u32 i = 0; i < 8; i++) name11[i] = ' ';
|
||||
for (u32 i = 0; i < bl; i++) name11[i] = base[i];
|
||||
u32 p = bl;
|
||||
name11[p++] = '~';
|
||||
for (int i = dn - 1; i >= 0; i--) name11[p++] = digits[i];
|
||||
int ex = f32w_dir_short_exists(dir_clus, name11);
|
||||
if (ex < 0) return -2;
|
||||
if (ex == 0) return 0;
|
||||
}
|
||||
return -3; /* 序号耗尽 */
|
||||
}
|
||||
|
||||
/* ---- LFN 条目生成 ---- */
|
||||
|
||||
/* 13 个 UTF-16 槽位在 32 字节目录项内的字节偏移(name1[5] name2[6] name3[2]) */
|
||||
static inline int f32w_lfn_slot_off(int slot) {
|
||||
if (slot < 5) return 1 + slot * 2;
|
||||
if (slot < 11) return 14 + (slot - 5) * 2;
|
||||
return 28 + (slot - 11) * 2;
|
||||
}
|
||||
|
||||
/* 构造第 ordinal 个 LFN 片段项(ordinal 1..total,total 为链长)。
|
||||
* name 为 ASCII 长名,len 为字符数。out32 接收 32 字节编码。
|
||||
* 最高序号项(ordinal==total)带 0x40 标志,含 0x0000 终止 + 0xFFFF 填充。
|
||||
* 返回 0 成功。 */
|
||||
static inline int f32w_build_lfn_entry(int ordinal, int total, const char *name,
|
||||
u32 len, u8 cksum, u8 *out32) {
|
||||
f32w_mem_set(out32, 0, 32);
|
||||
/* 全槽位预填 0xFFFF */
|
||||
for (int s = 0; s < 13; s++) {
|
||||
int off = f32w_lfn_slot_off(s);
|
||||
out32[off] = 0xFF;
|
||||
out32[off + 1] = 0xFF;
|
||||
}
|
||||
u32 base = (u32)(ordinal - 1) * 13;
|
||||
u32 cnt = (len - base) < 13 ? (len - base) : 13;
|
||||
for (u32 j = 0; j < cnt; j++) {
|
||||
int off = f32w_lfn_slot_off((int)j);
|
||||
u16 c = (u16)(u8)name[base + j]; /* ASCII -> UTF-16 */
|
||||
out32[off] = (u8)(c & 0xFF);
|
||||
out32[off + 1] = (u8)(c >> 8);
|
||||
}
|
||||
if (ordinal == total && cnt < 13) {
|
||||
int off = f32w_lfn_slot_off((int)cnt); /* 0x0000 终止符 */
|
||||
out32[off] = 0;
|
||||
out32[off + 1] = 0;
|
||||
}
|
||||
out32[0] = (u8)(ordinal | (ordinal == total ? 0x40 : 0x00));
|
||||
out32[11] = 0x0F; /* ATTR_LONG_NAME */
|
||||
out32[12] = 0; /* type */
|
||||
out32[13] = cksum;
|
||||
out32[26] = 0; /* 首簇号必须为 0 */
|
||||
out32[27] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 构造 8.3 短名项。 */
|
||||
static inline void f32w_build_short_entry(const char name11[11], u8 attr,
|
||||
u32 clus, u32 size, u8 *out32) {
|
||||
f32w_mem_set(out32, 0, 32);
|
||||
for (int i = 0; i < 11; i++) out32[i] = (u8)name11[i];
|
||||
out32[11] = attr;
|
||||
/* 时间戳字段保持 0(与 fat32_io.h 写路径一致) */
|
||||
out32[21] = (u8)((clus >> 16) & 0xFF); /* chigh */
|
||||
out32[20] = (u8)((clus >> 24) & 0xFF);
|
||||
out32[26] = (u8)(clus & 0xFF); /* clow */
|
||||
out32[27] = (u8)((clus >> 8) & 0xFF);
|
||||
out32[28] = (u8)(size & 0xFF); /* fsize */
|
||||
out32[29] = (u8)((size >> 8) & 0xFF);
|
||||
out32[30] = (u8)((size >> 16) & 0xFF);
|
||||
out32[31] = (u8)((size >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/* ---- 目录槽位分配(含簇链扩展,槽位可跨簇) ---- */
|
||||
|
||||
/* 在目录簇链中找 count 个连续空闲槽位(0x00/0xE5)。链尾不足时自动分配
|
||||
* 新簇:FAT 接链 + 新簇清零 + 落盘,槽位 run 跨簇继续累计。
|
||||
* out_clus/out_idx : run 首槽位位置(簇号 + 簇内项索引)
|
||||
* 返回 0 成功,负值失败(-3=磁盘满)。 */
|
||||
static inline int f32w_dir_alloc_slots(u32 dir_clus, u32 count,
|
||||
u32 *out_clus, u32 *out_idx) {
|
||||
u32 run_clus = 0, run_idx = 0, run = 0;
|
||||
u32 dclus = dir_clus;
|
||||
u64 guard = 0;
|
||||
while (dclus >= 2 && dclus < 0x0FFFFFF8) {
|
||||
if (++guard > (u64)f32w_c.fat_entries + 2) return -1; /* FAT 成环 */
|
||||
if (f32w_dir_load(dclus) != 0) return -2;
|
||||
u32 max_e = f32w_c.clus_bytes / 32;
|
||||
for (u32 e = 0; e < max_e; e++) {
|
||||
u8 first = f32w_dir[e * 32];
|
||||
if (first == 0x00 || first == 0xE5) {
|
||||
if (run == 0) { run_clus = dclus; run_idx = e; }
|
||||
run++;
|
||||
if (run >= count) {
|
||||
*out_clus = run_clus;
|
||||
*out_idx = run_idx;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
run = 0;
|
||||
}
|
||||
}
|
||||
u32 next = 0;
|
||||
if (f32w_fat_get(dclus, &next) != 0) return -2;
|
||||
if (next >= 2 && next < 0x0FFFFFF8) { dclus = next; continue; }
|
||||
/* 链尾且 run 不足:扩展一个新簇接入链尾 */
|
||||
u32 nc = f32w_alloc_free();
|
||||
if (nc == 0) return -3;
|
||||
if (f32w_fat_set(dclus, nc) != 0) return -4;
|
||||
if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -4;
|
||||
if (f32w_dir_flush() != 0) return -5;
|
||||
f32w_mem_set(f32w_dir, 0, f32w_c.clus_bytes); /* 新簇全 0 */
|
||||
f32w_dir_no = nc;
|
||||
f32w_dir_dirty = 1; /* 脏标记使 flush 落盘清零内容 */
|
||||
if (f32w_dir_flush() != 0) return -5;
|
||||
dclus = nc;
|
||||
/* run 跨簇继续累计(新簇全 0x00,下一轮循环继续计数) */
|
||||
}
|
||||
return -6;
|
||||
}
|
||||
|
||||
/* ---- 按长名查找(LFN 优先,8.3 回退;大小写不敏感) ---- */
|
||||
|
||||
/* 在目录中查找 name(长名或 8.3 显示名)。
|
||||
* want_dir : -1 任意,0 只匹配文件,1 只匹配目录
|
||||
* out_clus/size : 目标首簇 / 大小(可 NULL)
|
||||
* out_ent_clus/idx : 短名项位置(可 NULL,用于原地更新)
|
||||
* out_attr : 目录项属性(可 NULL)
|
||||
* 返回 0 找到,-1 未找到,其他负值 IO 错。LFN 缓冲跨簇存活(链可跨簇)。 */
|
||||
static inline int f32w_find(u32 dir_clus, const char *name, int want_dir,
|
||||
u32 *out_clus, u32 *out_size,
|
||||
u32 *out_ent_clus, u32 *out_ent_idx, u8 *out_attr) {
|
||||
if (dir_clus == 0) dir_clus = f32w_c.root_clus;
|
||||
u32 dclus = dir_clus;
|
||||
u64 guard = 0;
|
||||
fat32_lfn_buf lfn;
|
||||
fat32_lfn_init(&lfn);
|
||||
while (dclus >= 2 && dclus < 0x0FFFFFF8) {
|
||||
if (++guard > (u64)f32w_c.fat_entries + 2) return -2;
|
||||
if (f32w_dir_load(dclus) != 0) return -3;
|
||||
u32 max_e = f32w_c.clus_bytes / 32;
|
||||
for (u32 e = 0; e < max_e; e++) {
|
||||
const u8 *ent = f32w_dir + e * 32;
|
||||
if (ent[0] == 0x00) return -1; /* 目录结尾,未找到 */
|
||||
if (ent[0] == 0xE5) { fat32_lfn_init(&lfn); continue; }
|
||||
const f32w_dirent_t *de = (const f32w_dirent_t *)ent;
|
||||
int is_short = fat32_lfn_process(&lfn, ent);
|
||||
if (!is_short) continue;
|
||||
if (de->attr & 0x08) { fat32_lfn_init(&lfn); continue; } /* 卷标 */
|
||||
if (de->name[0] == '.' && (de->name[1] == ' ' || de->name[1] == '.')) {
|
||||
fat32_lfn_init(&lfn); /* "." / ".." */
|
||||
continue;
|
||||
}
|
||||
int entry_is_dir = (de->attr & 0x10) ? 1 : 0;
|
||||
if (want_dir >= 0 && entry_is_dir != want_dir) {
|
||||
fat32_lfn_init(&lfn);
|
||||
continue;
|
||||
}
|
||||
int matched = 0;
|
||||
if (lfn.valid) {
|
||||
char ascii[FAT32_LFN_MAX];
|
||||
if (fat32_lfn_to_ascii(&lfn, ascii, sizeof(ascii)) >= 0 &&
|
||||
fat32_lfn_streq_ci(ascii, name)) matched = 1;
|
||||
}
|
||||
if (!matched) {
|
||||
char disp[13];
|
||||
fat32_lfn_short_to_str((const u8 *)de->name, disp);
|
||||
if (fat32_lfn_streq_ci(disp, name)) matched = 1;
|
||||
}
|
||||
if (matched) {
|
||||
u32 fc = (u32)f32w_r16((const u8 *)&de->clow) |
|
||||
((u32)f32w_r16((const u8 *)&de->chigh) << 16);
|
||||
if (out_clus) *out_clus = fc;
|
||||
if (out_size) *out_size = de->fsize;
|
||||
if (out_ent_clus) *out_ent_clus = dclus;
|
||||
if (out_ent_idx) *out_ent_idx = e;
|
||||
if (out_attr) *out_attr = de->attr;
|
||||
return 0;
|
||||
}
|
||||
fat32_lfn_init(&lfn);
|
||||
}
|
||||
u32 next = 0;
|
||||
if (f32w_fat_get(dclus, &next) != 0) return -3;
|
||||
dclus = next;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---- 文件数据簇链写入(新建 / 复用扩展 / 截断收缩) ---- */
|
||||
|
||||
/* 写 size 字节数据。have>=2 时复用现有链(按需扩展或截断并释放多余簇),
|
||||
* have<2 时全新分配。*out_first 输出结果首簇(size==0 时为 0)。
|
||||
* 返回 0 成功,负值失败。 */
|
||||
static inline int f32w_data_write(u32 have, const u8 *data, u32 size, u32 *out_first) {
|
||||
u32 cb = f32w_c.clus_bytes;
|
||||
u32 need = size ? (size + cb - 1) / cb : 0;
|
||||
|
||||
if (need == 0) {
|
||||
if (have >= 2) {
|
||||
if (f32w_free_chain(have) != 0) return -1;
|
||||
}
|
||||
*out_first = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 first = 0, prev = 0, count = 0;
|
||||
if (have >= 2) {
|
||||
first = have;
|
||||
u32 cur = have;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && count < need) {
|
||||
prev = cur;
|
||||
u32 next = 0;
|
||||
if (f32w_fat_get(cur, &next) != 0) return -2;
|
||||
cur = next;
|
||||
count++;
|
||||
}
|
||||
while (count < need) { /* 现有链不足:扩展 */
|
||||
u32 nc = f32w_alloc_free();
|
||||
if (nc == 0) return -3;
|
||||
if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -4;
|
||||
if (f32w_fat_set(prev, nc) != 0) return -4;
|
||||
prev = nc;
|
||||
count++;
|
||||
}
|
||||
if (cur >= 2 && cur < 0x0FFFFFF8) { /* 现有链过长:截断 */
|
||||
if (f32w_fat_set(prev, F32W_EOC_VAL) != 0) return -4;
|
||||
if (f32w_free_chain(cur) != 0) return -5;
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < need; i++) { /* 全新分配 */
|
||||
u32 nc = f32w_alloc_free();
|
||||
if (nc == 0) return -3;
|
||||
if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -4;
|
||||
if (prev) {
|
||||
if (f32w_fat_set(prev, nc) != 0) return -4;
|
||||
} else {
|
||||
first = nc;
|
||||
}
|
||||
prev = nc;
|
||||
}
|
||||
count = need;
|
||||
}
|
||||
|
||||
/* 写数据:整簇直写源缓冲,尾簇经暂存缓冲零填充 */
|
||||
u32 remaining = size;
|
||||
const u8 *src = data;
|
||||
u32 c = first;
|
||||
for (u32 i = 0; i < count && remaining > 0; i++) {
|
||||
u32 chunk = remaining < cb ? remaining : cb;
|
||||
u32 lba = f32w_clus_lba(c);
|
||||
if (chunk == cb) {
|
||||
if (f32w_write_sectors(lba, f32w_c.spc, src) != 0) return -6;
|
||||
} else {
|
||||
f32w_mem_set(f32w_stage, 0, cb);
|
||||
for (u32 b = 0; b < chunk; b++) f32w_stage[b] = src[b];
|
||||
if (f32w_write_sectors(lba, f32w_c.spc, f32w_stage) != 0) return -6;
|
||||
}
|
||||
src += chunk;
|
||||
remaining -= chunk;
|
||||
if (i + 1 < count) {
|
||||
u32 next = 0;
|
||||
if (f32w_fat_get(c, &next) != 0) return -2;
|
||||
c = next;
|
||||
}
|
||||
}
|
||||
*out_first = first;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- FSInfo 置未知(写操作后调用,best-effort) ----
|
||||
* 分配/释放后空闲簇数不再准确,置 0xFFFFFFFF(未知)避免 UEFI/Windows
|
||||
* 依赖陈旧计数。忽略失败(无 FSInfo 的镜像不受影响)。 */
|
||||
static inline void f32w_fsinfo_unknown(void) {
|
||||
if (!f32w_c.loaded) return;
|
||||
u32 fsi_lba = 0;
|
||||
{
|
||||
/* 重新读 BPB 拿 FSInfo 扇区号(f32w_sec 此后未被覆盖过也可直接用) */
|
||||
const f32w_bpb_t *b = (const f32w_bpb_t *)f32w_sec;
|
||||
if (b->boot_sig != 0xAA55) return;
|
||||
fsi_lba = b->fsi;
|
||||
}
|
||||
if (fsi_lba == 0 || fsi_lba == 0xFFFF) return;
|
||||
u8 sec[512];
|
||||
if (f32w_read_sectors(fsi_lba, 1, sec) != 0) return;
|
||||
sec[488] = 0xFF; sec[489] = 0xFF; sec[490] = 0xFF; sec[491] = 0xFF;
|
||||
sec[492] = 0xFF; sec[493] = 0xFF; sec[494] = 0xFF; sec[495] = 0xFF;
|
||||
(void)f32w_write_sectors(fsi_lba, 1, sec);
|
||||
}
|
||||
|
||||
/* ---- 写入 LFN 链 + 短名项(dir_alloc_slots 已定位 run 起点) ---- */
|
||||
static inline int f32w_insert_entries(u32 dir_clus, const char *name,
|
||||
const char name11[11], u32 clus, u32 size, u8 attr) {
|
||||
u32 len = f32w_strlen(name);
|
||||
int needs_lfn = 0;
|
||||
/* needs_lfn 与调用方 make_unique_short 判定一致:重算(无损则短名回退匹配) */
|
||||
{
|
||||
char tmp11[11];
|
||||
if (f32w_make_short(name, tmp11, &needs_lfn) != 0) return -1;
|
||||
}
|
||||
u32 nlfn = needs_lfn ? (len + 12) / 13 : 0;
|
||||
u32 sc = 0, si = 0;
|
||||
if (f32w_dir_alloc_slots(dir_clus, nlfn + 1, &sc, &si) != 0) return -2;
|
||||
if (needs_lfn) {
|
||||
u8 ck = fat32_lfn_checksum((const u8 *)name11);
|
||||
for (int k = (int)nlfn; k >= 1; k--) { /* 逆序:最高序号最先落盘 */
|
||||
u8 e[32];
|
||||
f32w_build_lfn_entry(k, (int)nlfn, name, len, ck, e);
|
||||
if (f32w_entry_write(sc, si, e) != 0) return -3;
|
||||
if (k > 1 && f32w_slot_step(&sc, &si) != 0) return -4;
|
||||
}
|
||||
if (nlfn > 0 && f32w_slot_step(&sc, &si) != 0) return -4;
|
||||
}
|
||||
u8 se[32];
|
||||
f32w_build_short_entry(name11, attr, clus, size, se);
|
||||
if (f32w_entry_write(sc, si, se) != 0) return -3;
|
||||
if (f32w_dir_flush() != 0) return -5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ================= 公共 API ================= */
|
||||
|
||||
/* 在目录中写/替换长名文件(LFN 创建 + 短名 ~N 唯一化 + FAT 链管理)。
|
||||
* dir_clus : 目标目录首簇(0=根目录)
|
||||
* name : 长文件名(ASCII;非 8.3 无损表示时自动生成 LFN 链)
|
||||
* data/size: 文件内容(size 可为 0,簇链释放、大小清 0)
|
||||
* 同名存在:文件原地替换内容(保留原目录项/LFN),目录返回 -20。
|
||||
* 返回 0 成功,负值失败。 */
|
||||
static inline int f32w_write_file(u32 dir_clus, const char *name,
|
||||
const u8 *data, u32 size) {
|
||||
if (f32w_bpb_load() != 0) return -1;
|
||||
if (!f32w_blk_write) return -2;
|
||||
if (!name || (!data && size)) return -3;
|
||||
if (dir_clus == 0) dir_clus = f32w_c.root_clus;
|
||||
|
||||
u32 e_clus = 0, e_size = 0, ent_clus = 0, ent_idx = 0;
|
||||
u8 e_attr = 0;
|
||||
int fr = f32w_find(dir_clus, name, -1, &e_clus, &e_size, &ent_clus, &ent_idx, &e_attr);
|
||||
if (fr == 0) {
|
||||
if (e_attr & 0x10) return -20; /* 同名目录已存在 */
|
||||
/* 原地替换:复用/扩展/截断原簇链,仅更新短名项 clus/size */
|
||||
u32 first = 0;
|
||||
if (f32w_data_write(e_clus, data, size, &first) != 0) return -4;
|
||||
if (f32w_dir_load(ent_clus) != 0) return -5;
|
||||
u8 *ent = f32w_dir + ent_idx * 32;
|
||||
ent[20] = (u8)((first >> 24) & 0xFF); /* chigh */
|
||||
ent[21] = (u8)((first >> 16) & 0xFF);
|
||||
ent[26] = (u8)(first & 0xFF); /* clow */
|
||||
ent[27] = (u8)((first >> 8) & 0xFF);
|
||||
ent[28] = (u8)(size & 0xFF); /* fsize */
|
||||
ent[29] = (u8)((size >> 8) & 0xFF);
|
||||
ent[30] = (u8)((size >> 16) & 0xFF);
|
||||
ent[31] = (u8)((size >> 24) & 0xFF);
|
||||
f32w_dir_dirty = 1;
|
||||
if (f32w_dir_flush() != 0) return -5;
|
||||
f32w_fsinfo_unknown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 新建:唯一短名 -> 数据链 -> 目录槽位(含链扩展)-> LFN+短名项 */
|
||||
char name11[11];
|
||||
if (f32w_make_unique_short(dir_clus, name, name11) != 0) return -6;
|
||||
u32 first = 0;
|
||||
if (f32w_data_write(0, data, size, &first) != 0) return -7;
|
||||
if (f32w_insert_entries(dir_clus, name, name11, first, size, 0x20) != 0) return -8;
|
||||
f32w_fsinfo_unknown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 在目录中创建子目录(LFN + "." / ".." 项 + 父目录插入条目)。
|
||||
* parent_clus: 父目录首簇(0=根目录)
|
||||
* out_clus : 输出新目录首簇(可 NULL)
|
||||
* 返回 0 成功,-20 同名已存在,其他负值失败。 */
|
||||
static inline int f32w_mkdir(u32 parent_clus, const char *name, u32 *out_clus) {
|
||||
if (f32w_bpb_load() != 0) return -1;
|
||||
if (!f32w_blk_write) return -2;
|
||||
if (!name) return -3;
|
||||
if (parent_clus == 0) parent_clus = f32w_c.root_clus;
|
||||
|
||||
int fr = f32w_find(parent_clus, name, -1, 0, 0, 0, 0, 0);
|
||||
if (fr == 0) return -20; /* 同名已存在 */
|
||||
|
||||
char name11[11];
|
||||
if (f32w_make_unique_short(parent_clus, name, name11) != 0) return -6;
|
||||
|
||||
/* 分配目录簇并初始化 "." / ".." */
|
||||
u32 nc = f32w_alloc_free();
|
||||
if (nc == 0) return -21;
|
||||
if (f32w_fat_set(nc, F32W_EOC_VAL) != 0) return -22;
|
||||
if (f32w_dir_flush() != 0) return -23;
|
||||
f32w_mem_set(f32w_dir, 0, f32w_c.clus_bytes);
|
||||
{
|
||||
u8 dot[32], dotdot[32];
|
||||
char n_self[11] = {'.',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
|
||||
char n_up[11] = {'.','.',' ',' ',' ',' ',' ',' ',' ',' ',' '};
|
||||
u32 parent_link = (parent_clus == f32w_c.root_clus) ? 0 : parent_clus;
|
||||
f32w_build_short_entry(n_self, 0x10, nc, 0, dot);
|
||||
f32w_build_short_entry(n_up, 0x10, parent_link, 0, dotdot);
|
||||
for (int i = 0; i < 32; i++) { f32w_dir[i] = dot[i]; f32w_dir[32 + i] = dotdot[i]; }
|
||||
}
|
||||
f32w_dir_no = nc;
|
||||
f32w_dir_dirty = 1;
|
||||
if (f32w_dir_flush() != 0) return -23;
|
||||
|
||||
/* 父目录插入条目(含链扩展) */
|
||||
if (f32w_insert_entries(parent_clus, name, name11, nc, 0, 0x10) != 0) return -8;
|
||||
f32w_fsinfo_unknown();
|
||||
if (out_clus) *out_clus = nc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 路径辅助('/' 分隔,每段长名;中间目录必须已存在) ---- */
|
||||
|
||||
/* 定位路径目录簇。空路径/"'/'" 返回根目录。 */
|
||||
static inline int f32w_resolve_dir(const char *path, u32 *out_clus) {
|
||||
if (!path || !out_clus) return -1;
|
||||
if (f32w_bpb_load() != 0) return -2;
|
||||
u32 cur = f32w_c.root_clus;
|
||||
u32 pos = 0;
|
||||
u32 path_len = f32w_strlen(path);
|
||||
if (path_len == 0 || (path_len == 1 && path[0] == '/')) {
|
||||
*out_clus = cur;
|
||||
return 0;
|
||||
}
|
||||
while (pos < path_len) {
|
||||
while (pos < path_len && path[pos] == '/') pos++;
|
||||
if (pos >= path_len) break;
|
||||
char comp[260];
|
||||
u32 ci = 0;
|
||||
while (pos < path_len && path[pos] != '/' && ci + 1 < sizeof(comp))
|
||||
comp[ci++] = path[pos++];
|
||||
comp[ci] = 0;
|
||||
if (ci == 0) continue;
|
||||
u32 sub = 0;
|
||||
if (f32w_find(cur, comp, 1, &sub, 0, 0, 0, 0) != 0) return -3;
|
||||
cur = sub;
|
||||
}
|
||||
*out_clus = cur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按路径写文件(父目录必须已存在)。返回 0 成功,负值失败。 */
|
||||
static inline int f32w_write_path(const char *path, const u8 *data, u32 size) {
|
||||
if (!path) return -1;
|
||||
if (f32w_bpb_load() != 0) return -2;
|
||||
u32 path_len = f32w_strlen(path);
|
||||
u32 last_slash = 0xFFFFFFFFu;
|
||||
for (u32 i = 0; i < path_len; i++)
|
||||
if (path[i] == '/') last_slash = i;
|
||||
const char *fname = (last_slash == 0xFFFFFFFFu) ? path : path + last_slash + 1;
|
||||
u32 dir_clus = 0;
|
||||
if (last_slash == 0xFFFFFFFFu) {
|
||||
dir_clus = f32w_c.root_clus;
|
||||
} else {
|
||||
char dirpath[260];
|
||||
u32 dn = last_slash;
|
||||
if (dn >= sizeof(dirpath)) return -3;
|
||||
for (u32 i = 0; i < dn; i++) dirpath[i] = path[i];
|
||||
dirpath[dn] = 0;
|
||||
if (f32w_resolve_dir(dirpath, &dir_clus) != 0) return -4;
|
||||
}
|
||||
return f32w_write_file(dir_clus, fname, data, size);
|
||||
}
|
||||
|
||||
/* 按路径创建目录(仅最后一段被创建,中间目录必须已存在)。返回 0 成功。 */
|
||||
static inline int f32w_mkdir_path(const char *path, u32 *out_clus) {
|
||||
if (!path) return -1;
|
||||
if (f32w_bpb_load() != 0) return -2;
|
||||
u32 path_len = f32w_strlen(path);
|
||||
if (path_len == 0) return -3;
|
||||
/* 去尾部 '/' */
|
||||
while (path_len > 0 && path[path_len - 1] == '/') path_len--;
|
||||
if (path_len == 0) return -3;
|
||||
u32 last_slash = 0xFFFFFFFFu;
|
||||
for (u32 i = 0; i < path_len; i++)
|
||||
if (path[i] == '/') last_slash = i;
|
||||
const char *dname = (last_slash == 0xFFFFFFFFu) ? path : path + last_slash + 1;
|
||||
if ((u32)(dname - path) >= path_len) return -3; /* 防御:末段为空 */
|
||||
u32 parent_clus = 0;
|
||||
if (last_slash == 0xFFFFFFFFu) {
|
||||
parent_clus = f32w_c.root_clus;
|
||||
} else {
|
||||
char dirpath[260];
|
||||
u32 dn = last_slash;
|
||||
if (dn >= sizeof(dirpath)) return -4;
|
||||
for (u32 i = 0; i < dn; i++) dirpath[i] = path[i];
|
||||
dirpath[dn] = 0;
|
||||
if (f32w_resolve_dir(dirpath, &parent_clus) != 0) return -5;
|
||||
}
|
||||
return f32w_mkdir(parent_clus, dname, out_clus);
|
||||
}
|
||||
|
||||
#endif /* DESHAB_FAT32_WRITE_H */
|
||||
@@ -305,11 +305,28 @@ static void go_up(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 查找并进入 SYSTEM/user 目录 */
|
||||
/* 查找并进入用户目录。
|
||||
* 布局探测顺序(磁盘布局约定不同,两种镜像并存):
|
||||
* 1) 根级 USER/ —— 树形布局(dev 镜像,含 DESKTOP/PICTURE/VEDIO)
|
||||
* 2) SYSTEM/USER/ —— 旧树形布局(兼容遗留镜像)
|
||||
* 3) 都不存在 —— 扁平布局(SATA 盘,用户文件直接在根目录,停留根目录即可)
|
||||
* 匹配用 8.3 显示名(f32_name_from_83 已 trim 空格),如 "USER"/"SYSTEM"。 */
|
||||
static void find_user_dir(void) {
|
||||
/* 先查找 SYSTEM 目录 */
|
||||
/* 1) 根级 USER */
|
||||
file_count = 0;
|
||||
f32_list_dir(0, fm_collect_cb, 0);
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
if (files[i].is_dir && files[i].name[0] == 'U' && files[i].name[1] == 'S' &&
|
||||
files[i].name[2] == 'E' && files[i].name[3] == 'R') {
|
||||
path_clus[0] = files[i].clus;
|
||||
path_name[0][0] = 'U'; path_name[0][1] = 'S'; path_name[0][2] = 'E';
|
||||
path_name[0][3] = 'R'; path_name[0][4] = 0;
|
||||
path_depth = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* 2) 旧树形布局 SYSTEM/USER */
|
||||
u32 system_clus = 0;
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
if (files[i].is_dir && files[i].name[0] == 'S' && files[i].name[1] == 'Y' &&
|
||||
@@ -319,15 +336,14 @@ static void find_user_dir(void) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!system_clus) return; /* SYSTEM 目录不存在,保持根目录 */
|
||||
if (!system_clus) return; /* 扁平布局,保持根目录 */
|
||||
|
||||
/* 在 SYSTEM 中查找 user 目录 */
|
||||
file_count = 0;
|
||||
f32_list_dir(system_clus, fm_collect_cb, 0);
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
if (files[i].is_dir && files[i].name[0] == 'U' && files[i].name[1] == 'S' &&
|
||||
files[i].name[2] == 'E' && files[i].name[3] == 'R') {
|
||||
/* 找到了 SYSTEM/user,设置路径栈 */
|
||||
/* 找到了 SYSTEM/USER,设置路径栈 */
|
||||
path_clus[0] = system_clus;
|
||||
path_name[0][0] = 'S'; path_name[0][1] = 'Y'; path_name[0][2] = 'S';
|
||||
path_name[0][3] = 'T'; path_name[0][4] = 'E'; path_name[0][5] = 'M';
|
||||
@@ -339,6 +355,7 @@ static void find_user_dir(void) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* SYSTEM 存在但无 SYSTEM/USER:停留根目录(保持原行为) */
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
@@ -359,7 +376,7 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write);
|
||||
|
||||
path_depth = 0;
|
||||
/* 尝试进入 SYSTEM/user 目录 */
|
||||
/* 查找用户目录:自动适配树形(根级 USER/)与扁平(根目录直放)两种布局 */
|
||||
find_user_dir();
|
||||
load_current_dir();
|
||||
redraw_all();
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
/* gpt_write.h — Deshab 共享 GPT 写库(static inline,无库)
|
||||
*
|
||||
* 前置条件:包含者已定义 u8/u16/u32/u64/i64 基础类型。
|
||||
* 通过 gpt_init() 注入 block_read/block_write 函数指针(签名与 f32 库一致)。
|
||||
*
|
||||
* 能力:
|
||||
* gpt_probe() — 读 LBA1 校验 "EFI PART" 签名 + header CRC(轻量探测)
|
||||
* gpt_load() — 加载 GPT 头 + 分区表到内部缓冲,并做 CRC 校验
|
||||
* gpt_find_esp() — 查找 ESP 分区(type GUID c12a7328-...),输出起始/结束 LBA
|
||||
* gpt_find_part() — 按任意 type GUID 查找分区
|
||||
* gpt_update_esp() — 新增/更新 ESP 分区项(改起始/结束 LBA)
|
||||
* gpt_commit() — 重算 entries CRC + 双 header CRC,写主表/备份表/主头/备份头
|
||||
* gpt_blank_disk() — 全新初始化:protective MBR + 空 GPT(主+备份,无分区)
|
||||
*
|
||||
* 与 build.ps1(New-GptHeader / New-GptFat32Image)字节兼容:
|
||||
* LBA0 protective MBR(type 0xEE),LBA1 主头,LBA2 主表(128×128B = 32 扇区),
|
||||
* 备份表 lastLba-32,备份头 lastLba。GUID 按 .NET Guid.ToByteArray()
|
||||
* little-endian 编码(前三组小端、后两组原序)。
|
||||
* CRC32 为 IEEE 802.3(init 0xFFFFFFFF / poly 0xEDB88320 / xorout 0xFFFFFFFF),
|
||||
* 与 build.ps1 Get-Crc32 逐位实现等价。
|
||||
*
|
||||
* 缓冲:包含者需提供 ~16.5KB BSS(1 扇区头缓冲 + 32 扇区分区表缓冲)。
|
||||
*
|
||||
* 典型流程(FirstInit 写盘 / 系统格式化):
|
||||
* gpt_init(block_read, block_write);
|
||||
* if (gpt_probe() != 0) gpt_blank_disk(total_sectors, NULL); // 空盘初始化
|
||||
* gpt_load();
|
||||
* gpt_update_esp(2048, total_sectors - 34); // 或扩容
|
||||
* gpt_commit();
|
||||
*/
|
||||
|
||||
#ifndef DESHAB_GPT_WRITE_H
|
||||
#define DESHAB_GPT_WRITE_H
|
||||
|
||||
/* ---- block 设备函数类型(与 fat32_io.h 签名一致,index 固定 0) ---- */
|
||||
typedef int (*gpt_block_read_fn)(u32 index, u64 lba, u32 count, void *buf);
|
||||
typedef int (*gpt_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf);
|
||||
|
||||
static gpt_block_read_fn gpt_blk_read = 0;
|
||||
static gpt_block_write_fn gpt_blk_write = 0;
|
||||
|
||||
/* ---- GPT 常量(与 build.ps1 一致) ---- */
|
||||
#define GPT_SIG_BYTES 8
|
||||
#define GPT_ENTRIES 128u
|
||||
#define GPT_ENTRY_SIZE 128u
|
||||
#define GPT_ENTRY_SECTORS 32u /* 128×128 / 512 */
|
||||
#define GPT_FIRST_USABLE 34u /* 2 + 32 */
|
||||
#define GPT_HEADER_SIZE 92u
|
||||
#define GPT_REVISION 0x00010000u
|
||||
#define GPT_MBR_PART_OFF 446 /* protective MBR 分区项偏移 */
|
||||
|
||||
/* ---- GPT 头(512B,build.ps1 Set-Le* 布局) ---- */
|
||||
typedef struct __attribute__((packed)) {
|
||||
u8 sig[8]; /* "EFI PART" */
|
||||
u32 revision; /* 0x00010000 */
|
||||
u32 header_size; /* 92 */
|
||||
u32 header_crc;
|
||||
u32 reserved0;
|
||||
u64 current_lba; /* 主头=1,备份头=lastLba */
|
||||
u64 backup_lba;
|
||||
u64 first_usable; /* 34 */
|
||||
u64 last_usable; /* lastLba - 33 */
|
||||
u8 disk_guid[16];
|
||||
u64 entries_lba; /* 主表=2,备份表=lastLba-32 */
|
||||
u32 entries_count; /* 128 */
|
||||
u32 entries_size; /* 128 */
|
||||
u32 entries_crc;
|
||||
u8 reserved[420];
|
||||
} gpt_header; /* 512B */
|
||||
|
||||
/* ---- 分区项(128B) ---- */
|
||||
typedef struct __attribute__((packed)) {
|
||||
u8 type_guid[16]; /* 全 0 = 空槽 */
|
||||
u8 uniq_guid[16];
|
||||
u64 first_lba;
|
||||
u64 last_lba;
|
||||
u64 attrs;
|
||||
u16 name[36]; /* UTF-16LE,未用部分为 0 */
|
||||
} gpt_part_entry;
|
||||
|
||||
/* ESP 类型 GUID:c12a7328-f81f-11d2-ba4b-00a0c93ec93b
|
||||
* (.NET Guid.ToByteArray() 编码:前三组小端、后两组原序) */
|
||||
static const u8 gpt_guid_esp[16] = {
|
||||
0x28, 0x73, 0x2a, 0xc1, /* c12a7328 */
|
||||
0x1f, 0xf8, /* f81f */
|
||||
0xd2, 0x11, /* 11d2 */
|
||||
0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b
|
||||
};
|
||||
|
||||
/* ---- 内部缓冲(每包含者独立) ---- */
|
||||
static u8 gpt_sec[512]; /* 单扇区(头/MBR)缓冲 */
|
||||
static u8 gpt_tbl[GPT_ENTRY_SECTORS * 512]; /* 分区表缓冲(32 扇区) */
|
||||
static u64 gpt_disk_sectors = 0; /* 磁盘总扇区数(探测后已知) */
|
||||
|
||||
/* ---- 初始化:注入 block_read/block_write 函数指针 ---- */
|
||||
static inline void gpt_init(gpt_block_read_fn rd, gpt_block_write_fn wr) {
|
||||
gpt_blk_read = rd;
|
||||
gpt_blk_write = wr;
|
||||
gpt_disk_sectors = 0;
|
||||
}
|
||||
|
||||
static inline int gpt_read_sectors(u64 lba, u32 count, u8 *out) {
|
||||
return gpt_blk_read ? gpt_blk_read(0, lba, count, out) : -1;
|
||||
}
|
||||
static inline int gpt_write_sectors(u64 lba, u32 count, const u8 *buf) {
|
||||
return gpt_blk_write ? gpt_blk_write(0, lba, count, buf) : -1;
|
||||
}
|
||||
|
||||
/* ---- 小工具(无库约束:手写循环替代 mem*) ---- */
|
||||
static inline void gpt_mem_set(u8 *p, u8 v, u32 n) { for (u32 i = 0; i < n; i++) p[i] = v; }
|
||||
static inline void gpt_mem_cpy(u8 *d, const u8 *s, u32 n) { for (u32 i = 0; i < n; i++) d[i] = s[i]; }
|
||||
static inline int gpt_mem_eq(const u8 *a, const u8 *b, u32 n) {
|
||||
for (u32 i = 0; i < n; i++) if (a[i] != b[i]) return 0;
|
||||
return 1;
|
||||
}
|
||||
static inline u32 gpt_r32(const u8 *p) {
|
||||
return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24);
|
||||
}
|
||||
static inline void gpt_w16(u8 *p, u16 v) { p[0] = (u8)v; p[1] = (u8)(v >> 8); }
|
||||
static inline void gpt_w32(u8 *p, u32 v) {
|
||||
p[0] = (u8)v; p[1] = (u8)(v >> 8); p[2] = (u8)(v >> 16); p[3] = (u8)(v >> 24);
|
||||
}
|
||||
static inline void gpt_w64(u8 *p, u64 v) {
|
||||
gpt_w32(p, (u32)v); gpt_w32(p + 4, (u32)(v >> 32));
|
||||
}
|
||||
|
||||
/* ---- CRC32(IEEE 802.3,与 build.ps1 Get-Crc32 逐位实现等价;表驱动) ---- */
|
||||
static u32 gpt_crc_tab[256];
|
||||
static int gpt_crc_ready = 0;
|
||||
|
||||
static inline void gpt_crc_init(void) {
|
||||
if (gpt_crc_ready) return;
|
||||
for (u32 i = 0; i < 256; i++) {
|
||||
u32 c = i;
|
||||
for (int k = 0; k < 8; k++)
|
||||
c = (c & 1) ? (c >> 1) ^ 0xEDB88320u : (c >> 1);
|
||||
gpt_crc_tab[i] = c;
|
||||
}
|
||||
gpt_crc_ready = 1;
|
||||
}
|
||||
|
||||
static inline u32 gpt_crc32(const u8 *p, u32 n) {
|
||||
u32 crc = 0xFFFFFFFFu;
|
||||
for (u32 i = 0; i < n; i++)
|
||||
crc = (crc >> 8) ^ gpt_crc_tab[(crc ^ p[i]) & 0xFFu];
|
||||
return crc ^ 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
/* ---- GUID 工具 ---- */
|
||||
|
||||
/* "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" → 16B(.NET ToByteArray 编码)
|
||||
* 返回 0 成功,-1 格式错误 */
|
||||
static inline int gpt_guid_from_str(const char *s, u8 out[16]) {
|
||||
static const int hex[256] = { ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3,
|
||||
['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
|
||||
['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15,
|
||||
['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15 };
|
||||
/* 位置表:字符串下标 → 字节下标(1=前四组反序,0=后两组原序) */
|
||||
static const struct { int str; int byte; int rev; } map[16] = {
|
||||
{ 0, 3, 1 }, { 2, 2, 1 }, { 4, 1, 1 }, { 6, 0, 1 },
|
||||
{ 9, 5, 1 }, { 11, 4, 1 },
|
||||
{ 14, 7, 1 }, { 16, 6, 1 },
|
||||
{ 19, 8, 0 }, { 21, 9, 0 }, { 23, 10, 0 }, { 25, 11, 0 },
|
||||
{ 28, 12, 0 }, { 30, 13, 0 }, { 32, 14, 0 }, { 34, 15, 0 },
|
||||
};
|
||||
if (s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') return -1;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int hi = hex[(u8)s[map[i].str]];
|
||||
int lo = hex[(u8)s[map[i].str + 1]];
|
||||
if (hi < 0 || lo < 0) return -1;
|
||||
(void)0;
|
||||
}
|
||||
for (int i = 0; i < 16; i++)
|
||||
out[map[i].byte] = (u8)((hex[(u8)s[map[i].str]] << 4) | hex[(u8)s[map[i].str + 1]]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 16B → "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"(与 .NET Guid.ToString() 同序) */
|
||||
static inline void gpt_guid_to_str(const u8 g[16], char out[37]) {
|
||||
static const char *hd = "0123456789abcdef";
|
||||
static const struct { int byte; int rev; } om[16] = {
|
||||
{ 3, 1 }, { 2, 1 }, { 1, 1 }, { 0, 1 },
|
||||
{ 5, 1 }, { 4, 1 },
|
||||
{ 7, 1 }, { 6, 1 },
|
||||
{ 8, 0 }, { 9, 0 }, { 10, 0 }, { 11, 0 },
|
||||
{ 12, 0 }, { 13, 0 }, { 14, 0 }, { 15, 0 },
|
||||
};
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int b = om[i].byte;
|
||||
out[i * 2] = hd[g[b] >> 4];
|
||||
out[i * 2 + 1] = hd[g[b] & 0xF];
|
||||
if (i == 3 || i == 5 || i == 7 || i == 9) { out[i * 2 + 2] = '-'; }
|
||||
}
|
||||
out[36] = 0;
|
||||
}
|
||||
|
||||
/* ---- GPT 头构建 / 校验 ---- */
|
||||
|
||||
/* 计算头 CRC:前 92 字节、header_crc 字段按 0 参与 */
|
||||
static inline u32 gpt_header_crc(const gpt_header *h) {
|
||||
u8 tmp[92];
|
||||
gpt_mem_cpy(tmp, (const u8 *)h, 92);
|
||||
gpt_w32(tmp + 16, 0);
|
||||
return gpt_crc32(tmp, 92);
|
||||
}
|
||||
|
||||
/* 构建头(不落盘):entries_crc 由调用者预先算好 */
|
||||
static inline void gpt_header_build(gpt_header *h, u64 current_lba, u64 backup_lba,
|
||||
u64 first_usable, u64 last_usable,
|
||||
const u8 disk_guid[16],
|
||||
u64 entries_lba, u32 entries_crc) {
|
||||
static const u8 sig[8] = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' };
|
||||
gpt_mem_set((u8 *)h, 0, sizeof(*h));
|
||||
gpt_mem_cpy(h->sig, sig, 8);
|
||||
h->revision = GPT_REVISION;
|
||||
h->header_size = GPT_HEADER_SIZE;
|
||||
h->current_lba = current_lba;
|
||||
h->backup_lba = backup_lba;
|
||||
h->first_usable = first_usable;
|
||||
h->last_usable = last_usable;
|
||||
if (disk_guid) gpt_mem_cpy(h->disk_guid, disk_guid, 16);
|
||||
h->entries_lba = entries_lba;
|
||||
h->entries_count = GPT_ENTRIES;
|
||||
h->entries_size = GPT_ENTRY_SIZE;
|
||||
h->entries_crc = entries_crc;
|
||||
h->header_crc = gpt_header_crc(h);
|
||||
}
|
||||
|
||||
/* 校验 LBA 处的 GPT 头(签名/大小/current_lba/CRC),返回 0 有效 */
|
||||
static inline int gpt_header_validate(const gpt_header *h, u64 expect_lba) {
|
||||
static const u8 sig[8] = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' };
|
||||
if (!gpt_mem_eq(h->sig, sig, 8)) return -1;
|
||||
if (h->revision != GPT_REVISION || h->header_size < GPT_HEADER_SIZE) return -2;
|
||||
if (h->current_lba != expect_lba) return -3;
|
||||
if (gpt_header_crc(h) != h->header_crc) return -4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 分区表操作(内存缓冲) ---- */
|
||||
static inline gpt_part_entry *gpt_part(u32 idx) {
|
||||
return (gpt_part_entry *)(gpt_tbl + idx * GPT_ENTRY_SIZE);
|
||||
}
|
||||
|
||||
/* 分区表 CRC(覆盖全部 32 扇区) */
|
||||
static inline u32 gpt_entries_crc(void) {
|
||||
return gpt_crc32(gpt_tbl, sizeof(gpt_tbl));
|
||||
}
|
||||
|
||||
static inline int gpt_part_empty(const gpt_part_entry *e) {
|
||||
return gpt_mem_eq(e->type_guid, (const u8 *)gpt_guid_esp /* 任意非 0 引用 */, 0) &&
|
||||
gpt_mem_eq(e->type_guid, e->type_guid, 0); /* 恒真占位,防 -Wunused */
|
||||
}
|
||||
static inline int gpt_part_is_free(const gpt_part_entry *e) {
|
||||
/* type_guid 全 0 = 空槽 */
|
||||
static const u8 zero16[16] = { 0 };
|
||||
return gpt_mem_eq(e->type_guid, zero16, 16);
|
||||
}
|
||||
|
||||
/* 查找第一个 type_guid 匹配的分区(含全 0 停止),返回索引或 -1 */
|
||||
static inline int gpt_find_part_type(const u8 type_guid[16], u64 *out_first, u64 *out_last) {
|
||||
for (u32 i = 0; i < GPT_ENTRIES; i++) {
|
||||
const gpt_part_entry *e = gpt_part(i);
|
||||
if (gpt_part_is_free(e)) break;
|
||||
if (gpt_mem_eq(e->type_guid, type_guid, 16)) {
|
||||
if (out_first) *out_first = e->first_lba;
|
||||
if (out_last) *out_last = e->last_lba;
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 查找 ESP 分区,返回索引或 -1 */
|
||||
static inline int gpt_find_esp(u64 *out_first, u64 *out_last) {
|
||||
return gpt_find_part_type(gpt_guid_esp, out_first, out_last);
|
||||
}
|
||||
|
||||
/* 分区名写入(ASCII → UTF-16LE,最多 36 字符) */
|
||||
static inline void gpt_part_set_name(gpt_part_entry *e, const char *ascii) {
|
||||
u32 i = 0;
|
||||
for (; i < 36 && ascii[i]; i++) gpt_w16((u8 *)&e->name[i], (u16)ascii[i]);
|
||||
for (; i < 36; i++) gpt_w16((u8 *)&e->name[i], 0);
|
||||
}
|
||||
|
||||
/* 填充分区项到指定槽位(返回 0 成功,-1 槽位越界/非空) */
|
||||
static inline int gpt_part_fill(u32 idx, const u8 type_guid[16], const u8 uniq_guid[16],
|
||||
u64 first_lba, u64 last_lba, u64 attrs, const char *name) {
|
||||
if (idx >= GPT_ENTRIES) return -1;
|
||||
gpt_part_entry *e = gpt_part(idx);
|
||||
if (!gpt_part_is_free(e)) return -2;
|
||||
gpt_mem_cpy(e->type_guid, type_guid, 16);
|
||||
if (uniq_guid) gpt_mem_cpy(e->uniq_guid, uniq_guid, 16);
|
||||
else gpt_mem_set(e->uniq_guid, 0, 16);
|
||||
e->first_lba = first_lba;
|
||||
e->last_lba = last_lba;
|
||||
e->attrs = attrs;
|
||||
gpt_part_set_name(e, name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 落盘 / 探测 ---- */
|
||||
|
||||
/* 探测 LBA1 是否为有效 GPT(轻量,不加载分区表) */
|
||||
static inline int gpt_probe(void) {
|
||||
if (gpt_read_sectors(1, 1, gpt_sec) != 0) return -1;
|
||||
return gpt_header_validate((const gpt_header *)gpt_sec, 1);
|
||||
}
|
||||
|
||||
/* 加载主头 + 主分区表到缓冲;校验头/表 CRC。total_sectors 为 0 时跳过盘尾推导 */
|
||||
static inline int gpt_load(u64 total_sectors) {
|
||||
if (gpt_read_sectors(1, 1, gpt_sec) != 0) return -1;
|
||||
const gpt_header *h = (const gpt_header *)gpt_sec;
|
||||
if (gpt_header_validate(h, 1) != 0) return -2;
|
||||
if (h->entries_lba != 2 || h->entries_count != GPT_ENTRIES) return -3;
|
||||
if (gpt_read_sectors(2, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -4;
|
||||
if (gpt_entries_crc() != h->entries_crc) return -5;
|
||||
gpt_disk_sectors = total_sectors;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 全新初始化:protective MBR + 空 GPT(主+备份)。disk_guid 可 NULL(全 0)。
|
||||
* total_sectors 必须 >= 70(34 + 32 表 + 2 头)。 */
|
||||
static inline int gpt_blank_disk(u64 total_sectors, const u8 disk_guid[16]) {
|
||||
if (total_sectors < GPT_FIRST_USABLE + GPT_ENTRY_SECTORS + 2) return -1;
|
||||
|
||||
/* LBA0 protective MBR(build.ps1 布局) */
|
||||
gpt_mem_set(gpt_sec, 0, 512);
|
||||
gpt_sec[GPT_MBR_PART_OFF + 4] = 0xEE; /* type */
|
||||
gpt_w32(gpt_sec + GPT_MBR_PART_OFF + 8, 1); /* start LBA */
|
||||
u64 prot = total_sectors - 1;
|
||||
if (prot > 0xFFFFFFFFu) prot = 0xFFFFFFFFu;
|
||||
gpt_w32(gpt_sec + GPT_MBR_PART_OFF + 12, (u32)prot); /* sectors */
|
||||
gpt_sec[510] = 0x55; gpt_sec[511] = 0xAA;
|
||||
if (gpt_write_sectors(0, 1, gpt_sec) != 0) return -2;
|
||||
|
||||
/* 空分区表 */
|
||||
gpt_mem_set(gpt_tbl, 0, sizeof(gpt_tbl));
|
||||
u32 entries_crc = gpt_crc32(gpt_tbl, sizeof(gpt_tbl));
|
||||
|
||||
/* 主头(LBA1)+ 备份头(lastLba) */
|
||||
u64 last_lba = total_sectors - 1;
|
||||
u64 last_usable = last_lba - GPT_ENTRY_SECTORS - 1;
|
||||
gpt_header ph, bh;
|
||||
gpt_header_build(&ph, 1, last_lba, GPT_FIRST_USABLE, last_usable,
|
||||
disk_guid, 2, entries_crc);
|
||||
gpt_header_build(&bh, last_lba, 1, GPT_FIRST_USABLE, last_usable,
|
||||
disk_guid, last_lba - GPT_ENTRY_SECTORS, entries_crc);
|
||||
|
||||
if (gpt_write_sectors(1, 1, (const u8 *)&ph) != 0) return -3;
|
||||
if (gpt_write_sectors(2, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -4;
|
||||
if (gpt_write_sectors(last_lba - GPT_ENTRY_SECTORS, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -5;
|
||||
if (gpt_write_sectors(last_lba, 1, (const u8 *)&bh) != 0) return -6;
|
||||
|
||||
gpt_disk_sectors = total_sectors;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 新增/更新 ESP 分区项(内存缓冲,需 gpt_commit 落盘):
|
||||
* 已有 ESP 则更新范围,否则填第一个空槽。返回槽位索引或负错误码 */
|
||||
static inline int gpt_update_esp(u64 first_lba, u64 last_lba,
|
||||
const u8 uniq_guid[16], const char *name) {
|
||||
static const u8 zero16[16] = { 0 };
|
||||
int idx = gpt_find_part_type(gpt_guid_esp, 0, 0);
|
||||
if (idx >= 0) {
|
||||
gpt_part_entry *e = gpt_part((u32)idx);
|
||||
e->first_lba = first_lba;
|
||||
e->last_lba = last_lba;
|
||||
if (uniq_guid) gpt_mem_cpy(e->uniq_guid, uniq_guid, 16);
|
||||
if (name) gpt_part_set_name(e, name);
|
||||
return idx;
|
||||
}
|
||||
/* 找第一个空槽(类型全 0) */
|
||||
for (u32 i = 0; i < GPT_ENTRIES; i++) {
|
||||
if (gpt_part_is_free(gpt_part(i)))
|
||||
return gpt_part_fill(i, gpt_guid_esp, uniq_guid ? uniq_guid : zero16,
|
||||
first_lba, last_lba, 0, name ? name : "Deshab ESP");
|
||||
}
|
||||
return -3; /* 表满 */
|
||||
}
|
||||
|
||||
/* 提交:重算 entries CRC + 双头 CRC,写主表/备份表/主头/备份头(幂等) */
|
||||
static inline int gpt_commit(void) {
|
||||
u64 last_lba = 0;
|
||||
gpt_header h;
|
||||
/* 读当前主头,取 last_usable / disk_guid / backup 布局 */
|
||||
if (gpt_read_sectors(1, 1, gpt_sec) != 0) return -1;
|
||||
gpt_mem_cpy((u8 *)&h, gpt_sec, sizeof(h));
|
||||
if (gpt_header_validate(&h, 1) != 0) return -2;
|
||||
last_lba = h.backup_lba;
|
||||
|
||||
u32 entries_crc = gpt_entries_crc();
|
||||
|
||||
gpt_header_build(&h, 1, last_lba, h.first_usable, h.last_usable,
|
||||
h.disk_guid, 2, entries_crc);
|
||||
gpt_header bh;
|
||||
gpt_header_build(&bh, last_lba, 1, h.first_usable, h.last_usable,
|
||||
h.disk_guid, last_lba - GPT_ENTRY_SECTORS, entries_crc);
|
||||
|
||||
if (gpt_write_sectors(2, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -3;
|
||||
if (gpt_write_sectors(1, 1, (const u8 *)&h) != 0) return -4;
|
||||
if (gpt_write_sectors(last_lba - GPT_ENTRY_SECTORS, GPT_ENTRY_SECTORS, gpt_tbl) != 0) return -5;
|
||||
if (gpt_write_sectors(last_lba, 1, (const u8 *)&bh) != 0) return -6;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* DESHAB_GPT_WRITE_H */
|
||||
@@ -0,0 +1,179 @@
|
||||
# read_usb_logs.ps1 - Read Deshab USB disk, parse GPT+FAT32, extract test logs
|
||||
#
|
||||
# Deshab 实机测试日志回读工具:U 盘(GPT ESP + FAT32)在 Windows 上无法
|
||||
# 直接挂载(removable media 不支持盘符分配),此脚本以管理员权限直读
|
||||
# 物理磁盘,解析 GPT 分区表 + FAT32 文件系统,把 SYSTEM/DESHAB64/DEV/ 下的
|
||||
# 日志文件(BOOTLOG.TXT、BOOTxx.TXT、LOG 等)和根目录 DEVLOG 提取到本地。
|
||||
#
|
||||
# Usage (admin PowerShell):
|
||||
# powershell -ExecutionPolicy Bypass -File "CODE\tools\read_usb_logs.ps1"
|
||||
# powershell -ExecutionPolicy Bypass -File "CODE\tools\read_usb_logs.ps1" -DiskNumber 2
|
||||
# powershell -ExecutionPolicy Bypass -File "CODE\tools\read_usb_logs.ps1" -OutDir ".\logs"
|
||||
#
|
||||
# 输出目录: .build_tmp\usb_logs\ (或 -OutDir 指定)
|
||||
param(
|
||||
[int]$DiskNumber = 1,
|
||||
[string]$OutDir = 'd:\Code\DEAICUP\Deshab\.build_tmp\usb_logs'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$diskPath = '\\.\PhysicalDrive' + $DiskNumber
|
||||
|
||||
# ---- 检查管理员权限(打开物理磁盘需要) ----
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $isAdmin) {
|
||||
Write-Host "[ERROR] Administrator privileges required to open $diskPath" -ForegroundColor Red
|
||||
Write-Host " Run this script from an elevated PowerShell (Win+X -> Terminal (Admin))."
|
||||
exit 1
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
$script:fs = $null
|
||||
$script:partStart = 0
|
||||
$script:dataSec = 0
|
||||
$script:rsvd = 0
|
||||
$script:spc = 0
|
||||
$script:csize = 0
|
||||
|
||||
function Read-Cluster([uint32]$clus) {
|
||||
$lba = $dataSec + ($clus - 2) * $spc
|
||||
$fs.Position = ([int64]$partStart + $lba) * 512
|
||||
$b = [byte[]]::new($csize)
|
||||
[void]$fs.Read($b, 0, $csize)
|
||||
return ,$b
|
||||
}
|
||||
|
||||
function Read-FatEntry([uint32]$c) {
|
||||
$fatSec = [Math]::Floor(($c * 4) / 512)
|
||||
$fs.Position = ([int64]$partStart + $rsvd + $fatSec) * 512
|
||||
$fb = [byte[]]::new(512)
|
||||
[void]$fs.Read($fb, 0, 512)
|
||||
return ([BitConverter]::ToUInt32($fb, ($c * 4) % 512) -band 0x0FFFFFFF)
|
||||
}
|
||||
|
||||
function Decode-Short([byte[]]$n) {
|
||||
$s=''; for($i=0;$i -lt 8 -and $n[$i] -ne 0x20 -and $n[$i] -ne 0;$i++){$s+=[char]$n[$i]}
|
||||
$e=''; for($i=8;$i -lt 11 -and $n[$i] -ne 0x20 -and $n[$i] -ne 0;$i++){$e+=[char]$n[$i]}
|
||||
if($e){return "$s.$e"}else{return $s}
|
||||
}
|
||||
|
||||
function Get-EntryCluster([byte[]]$buf, [int]$o) {
|
||||
$clow = [BitConverter]::ToUInt16($buf, $o + 26)
|
||||
$chigh = [BitConverter]::ToUInt16($buf, $o + 20)
|
||||
return ($clow -bor ([int]$chigh -shl 16))
|
||||
}
|
||||
|
||||
function Read-File([uint32]$firstClus, [uint32]$size) {
|
||||
if ($size -eq 0) { return ,([byte[]]::new(0)) }
|
||||
$data = [byte[]]::new($size)
|
||||
$c = $firstClus; $off = 0; $remaining = $size
|
||||
while ($c -ge 2 -and $c -lt 0x0FFFFFF8 -and $remaining -gt 0) {
|
||||
$lba = $dataSec + ($c - 2) * $spc
|
||||
$fs.Position = ([int64]$partStart + $lba) * 512
|
||||
$chunk = [byte[]]::new($csize)
|
||||
[void]$fs.Read($chunk, 0, $csize)
|
||||
$n = [Math]::Min($csize, $remaining)
|
||||
[Array]::Copy($chunk, 0, $data, $off, $n)
|
||||
$off += $n; $remaining -= $n
|
||||
$c = Read-FatEntry $c
|
||||
}
|
||||
return ,$data
|
||||
}
|
||||
|
||||
function Scan-Dir([uint32]$dirClus) {
|
||||
$result = @{}
|
||||
$c = $dirClus
|
||||
while ($c -ge 2 -and $c -lt 0x0FFFFFF8) {
|
||||
$buf = Read-Cluster $c
|
||||
for ($e = 0; $e -lt ($csize / 32); $e++) {
|
||||
$o = $e * 32
|
||||
if ($buf[$o] -eq 0) { return $result }
|
||||
if ($buf[$o] -eq 0xE5) { continue }
|
||||
$at = $buf[$o + 11]
|
||||
if (($at -band 0x0F) -eq 0x0F) { continue } # LFN entry, skip
|
||||
if ($at -band 0x08) { continue } # volume label
|
||||
$name = Decode-Short $buf[$o..($o + 10)]
|
||||
$result[$name] = @{ Cluster = (Get-EntryCluster $buf $o); Size = [BitConverter]::ToUInt32($buf, $o + 28); Attr = $at }
|
||||
}
|
||||
$c = Read-FatEntry $c
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Show-Head([byte[]]$data) {
|
||||
if (-not $data -or $data.Length -eq 0) { Write-Host " [empty]"; return }
|
||||
$txt = [System.Text.Encoding]::ASCII.GetString($data)
|
||||
$len = [Math]::Min(400, $txt.Length)
|
||||
$clean = ''
|
||||
for ($i = 0; $i -lt $len; $i++) {
|
||||
$code = [int]$txt[$i]
|
||||
if (($code -ge 32 -and $code -le 126) -or $code -eq 10 -or $code -eq 13 -or $code -eq 9) { $clean += $txt[$i] } else { $clean += '.' }
|
||||
}
|
||||
Write-Host " [head] $clean"
|
||||
}
|
||||
|
||||
Write-Host "=== Open $diskPath ==="
|
||||
$fs = [System.IO.File]::Open($diskPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||
|
||||
$fs.Position = 512
|
||||
$lba1 = [byte[]]::new(512); [void]$fs.Read($lba1, 0, 512)
|
||||
$sig = [System.Text.Encoding]::ASCII.GetString($lba1, 0, 8)
|
||||
Write-Host "LBA1 signature: '$sig'"
|
||||
if ($sig -ne 'EFI PART') { Write-Host "[ERROR] Not a GPT disk"; $fs.Close(); exit 1 }
|
||||
|
||||
$fs.Position = 1024
|
||||
$pe = [byte[]]::new(128); [void]$fs.Read($pe, 0, 128)
|
||||
$partStart = [BitConverter]::ToUInt64($pe, 32)
|
||||
Write-Host "ESP start LBA: $partStart"
|
||||
|
||||
$fs.Position = $partStart * 512
|
||||
$bpb = [byte[]]::new(512); [void]$fs.Read($bpb, 0, 512)
|
||||
$bps = [BitConverter]::ToUInt16($bpb, 11)
|
||||
$spc = $bpb[13]
|
||||
$rsvd = [BitConverter]::ToUInt16($bpb, 14)
|
||||
$nf = $bpb[16]
|
||||
$spf = [BitConverter]::ToUInt32($bpb, 36)
|
||||
$rootClus = [BitConverter]::ToUInt32($bpb, 44)
|
||||
$dataSec = $rsvd + $nf * $spf
|
||||
$csize = $bps * $spc
|
||||
Write-Host "BPB: bps=$bps spc=$spc rsvd=$rsvd nf=$nf spf=$spf root=$rootClus dataSec=$dataSec"
|
||||
|
||||
$root = Scan-Dir $rootClus
|
||||
Write-Host "=== ROOT ==="
|
||||
$root.Keys | Sort-Object | ForEach-Object { Write-Host (" {0} clus={1} size={2}" -f $_, $root[$_].Cluster, $root[$_].Size) }
|
||||
|
||||
if (-not $root.ContainsKey('SYSTEM')) { Write-Host "[WARN] no SYSTEM dir"; $fs.Close(); exit 0 }
|
||||
$sys = Scan-Dir $root['SYSTEM'].Cluster
|
||||
if (-not $sys.ContainsKey('DESHAB64')) { Write-Host "[WARN] no DESHAB64 dir"; $fs.Close(); exit 0 }
|
||||
$d64 = Scan-Dir $sys['DESHAB64'].Cluster
|
||||
|
||||
Write-Host "=== DESHAB64 ==="
|
||||
$d64.Keys | Sort-Object | ForEach-Object { Write-Host (" {0} size={1}" -f $_, $d64[$_].Size) }
|
||||
|
||||
if (-not $d64.ContainsKey('DEV')) { Write-Host "[WARN] no DEV dir"; $fs.Close(); exit 0 }
|
||||
$dev = Scan-Dir $d64['DEV'].Cluster
|
||||
Write-Host "=== DESHAB64/DEV ==="
|
||||
$dev.Keys | Sort-Object | ForEach-Object { Write-Host (" {0} size={1}" -f $_, $dev[$_].Size) }
|
||||
|
||||
Write-Host "=== EXTRACT to $OutDir ==="
|
||||
foreach ($k in $dev.Keys) {
|
||||
if (($dev[$k].Attr -band 0x10) -ne 0) { continue }
|
||||
$data = Read-File $dev[$k].Cluster $dev[$k].Size
|
||||
$safe = ($k -replace '[^A-Za-z0-9._-]', '_')
|
||||
$outPath = Join-Path $OutDir $safe
|
||||
[System.IO.File]::WriteAllBytes($outPath, $data)
|
||||
Write-Host (" -> {0} ({1} bytes)" -f $safe, $data.Length)
|
||||
Show-Head $data
|
||||
}
|
||||
foreach ($k in @('DEVLOG','TEST.TXT','COPY.TXT','MOVED.TXT','BOOTLOG')) {
|
||||
if ($root.ContainsKey($k)) {
|
||||
$data = Read-File $root[$k].Cluster $root[$k].Size
|
||||
$outPath = Join-Path $OutDir ("ROOT_" + $k)
|
||||
[System.IO.File]::WriteAllBytes($outPath, $data)
|
||||
Write-Host (" [root] -> {0} ({1} bytes)" -f $k, $data.Length)
|
||||
Show-Head $data
|
||||
}
|
||||
}
|
||||
$fs.Close()
|
||||
Write-Host "=== DONE ==="
|
||||
Reference in New Issue
Block a user