fix(dsk,firstInit,build): let the system boot all the way to the desktop

Four independent blockers sat between "the image boots UTSM" and "the desktop
comes up". Each one wedged the boot on its own; all four are fixed here and the
tree now reaches "[desktop] desktop ready" under QEMU/OVMF
(evidence: .build_tmp/d2.log).

1. FirstInit made networking a hard gate (CODE/firstInit/main.c)
   The setup wizard always showed the network page, and on a machine with no
   wireless device the only exit was Save - pressing Connect raised NET-E01.
   Networking is not a precondition for entering the system, so a wizard that
   cannot be completed without it is a plain design defect.
   - No wireless device detected -> skip the network page entirely (the
     g_no_wireless_device flag already existed but was only logged).
   - With a device present, add a Skip button (draw + mouse hit-box + keyboard
     Enter), mirroring "I don't have internet" on Windows.
   - Connect keeps its zero-fallback NET-E01 panic: the unimplemented path is
     still honest, it just no longer holds the user hostage.

2. ESP cluster size left no room in the root directory
   (CODE/linux/pack_system_image.sh)
   With the cluster size left to mkfs it chose 1 sector, i.e. 16 directory
   entries per cluster. The ESP root ends up holding exactly 16 entries
   (DESHABBOOT/EFI/LIMINE/BOOT/DRIVER/SYSTEM/USER/NVVARS plus one long-name
   entry each), and the root chain is a single cluster - so on first boot the
   DSK could never write USER.CONF.
   Pin -s 4 (2KiB, 64 entries): it clears the FAT32 65525 lower bound and the
   0xFFF5 boundary that -s 8 lands on at this 256MiB size, and leaves 48 free
   entries. The alternatives were measured, not guessed (see the comment).

3. DSK could not grow the root directory (CODE/dsk/main.c)
   fat32_write_root_file returned -4 "no free dir entry" whenever the scan of
   the root chain came up empty. A FAT filesystem is supposed to extend the
   directory chain, so allocate a cluster, link it at the current tail, write
   the entry there and commit the FAT. Fixing only (2) would have papered over
   this: any real disk whose root fills up would still fail the same way.

4. Nothing generated the CJK bitmap font packs (build.ps1)
   The desktop loads system/font/simhei_16.dbf and panics DESK-E04 FONT PACK
   MISSING without it. The .dbf files are generated from simhei.ttf by
   CODE/font/mkfont.py and are gitignored, but no build step ever invoked it -
   same class of bug as the previously untracked .drv binaries. Generate
   simhei_16.dbf / simhei_24.dbf in build.ps1 (with the existing WSL python3
   fallback for Pillow), rebuilding only when the TTF is newer than the pack.

docs/STATUS.md updated to match: FirstInit wizard and the D3 named API export
table move to done, and the FAT32 / ESP cluster size / CJK font pack entries
are added or corrected. D3 was already fully implemented (table, lookup,
selftest with positive and negative cases, TST-E21..E24 instrumentation) -
only the status row was stale.

Verified boot chain (QEMU/OVMF, dev variant):
  BdsDxe -> Limine -> UTSM -> DKM 17/17 -> DSK -> FirstInit (network page
  skipped) -> USER.CONF written -> login (password accepted) -> desktop:
  "[zhfont] dbf loaded (16px ok)" ... "[desktop] desktop ready"
This commit is contained in:
boonxiong2
2026-10-05 18:32:24 +08:00
parent 10599b62c4
commit 780230599d
5 changed files with 190 additions and 21 deletions
+80 -1
View File
@@ -950,7 +950,86 @@ static int fat32_write_root_file(const char *name11, const u8 *data, u32 size) {
}
} else {
/* Allocate new clusters */
if (free_entry < 0) { logl("[DSK] fat32 write: no free dir entry"); return -4; }
if (free_entry < 0) {
/* 根目录链里没有空位 —— 按 FAT32 规范扩展目录链:分配一个新簇,
* 挂到根目录链尾,在新簇的第一个条目里放目录项。(原来这里直接
* return -4,导致 ESP 根目录一簇写满后 USER.CONF 永远写不进去。) */
u32 tail = root_clus;
if (tail < 2) { logl("[DSK] fat32 write: bad root cluster"); return -4; }
for (;;) {
u32 nxt = fat32_fat_read_entry(fat_byte_off, tail);
if (nxt >= 2 && nxt < 0x0FFFFFF8) { tail = nxt; continue; }
break;
}
u32 newdir = fat32_find_free_cluster(fat_byte_off, (fat_sectors * 512) / 4);
if (newdir == 0) { logl("[DSK] fat32 write: no free cluster for dir growth"); return -3; }
if (fat32_fat_write_entry(fat_byte_off, newdir, 0x0FFFFFF8) != 0) return -7;
if (fat32_fat_write_entry(fat_byte_off, tail, newdir) != 0) return -7;
logh("[DSK] fat32 write: root dir grown, new clus=", newdir);
/* 新簇整簇清零(0x00 = 目录结束标记),再把目录项写在首项 */
u8 *nb = g_cluster;
for (u32 b = 0; b < cluster_bytes; b++) nb[b] = 0;
fat32_de *nd = (fat32_de *)nb;
for (int i = 0; i < 11; i++) nd[0].name[i] = name11[i];
nd[0].attr = 0x20;
nd[0].ntr = 0; nd[0].ctenth = 0; nd[0].ctime = 0; nd[0].cdate = 0;
nd[0].adate = 0; nd[0].wtime = 0; nd[0].wdate = 0;
/* 数据簇先分配好,再填进目录项(与下方 !found 分支同序) */
u32 dfirst = 0, dprev = 0;
for (u32 i = 0; i < clusters_needed; i++) {
u32 newc = fat32_find_free_cluster(fat_byte_off, (fat_sectors * 512) / 4);
if (newc == 0) { logl("[DSK] fat32 write: no free clusters"); return -3; }
logh("[DSK] fat32 write: alloc clus=", newc);
if (fat32_fat_write_entry(fat_byte_off, newc, 0x0FFFFFF8) != 0) return -7;
if (i == 0) dfirst = newc;
if (dprev >= 2) {
if (fat32_fat_write_entry(fat_byte_off, dprev, newc) != 0) return -7;
}
dprev = newc;
}
nd[0].chigh = (u16)((dfirst >> 16) & 0xFFFF);
nd[0].clow = (u16)(dfirst & 0xFFFF);
nd[0].fsize = size;
u32 nlba = data_lba + (newdir - 2) * spc;
if (fat32_write_sectors(nlba, spc, nb) != 0) {
logl("[DSK] fat32 write: new dir cluster write failed");
return -8;
}
/* 写数据簇 */
u32 rem2 = size;
const u8 *s2 = data;
u32 c2 = dfirst;
u32 ci2 = 0;
while (c2 >= 2 && c2 < 0x0FFFFFF8 && ci2 < clusters_needed) {
u32 clba2 = data_lba + (c2 - 2) * spc;
u32 chunk2 = rem2 < cluster_bytes ? rem2 : cluster_bytes;
if (fat32_read_sectors(clba2, spc, g_cluster) != 0) return -5;
for (u32 b = 0; b < chunk2; b++) g_cluster[b] = s2[b];
for (u32 b = chunk2; b < cluster_bytes; b++) g_cluster[b] = 0;
if (fat32_write_sectors(clba2, spc, g_cluster) != 0) return -6;
s2 += chunk2; rem2 -= chunk2; ci2++;
c2 = fat32_fat_read_entry(fat_byte_off, c2);
}
/* FAT 表写回(与下方第 6 步同逻辑:只写 g_disk 缓冲覆盖得到的那些 FAT 副本) */
{
u32 max_fat_sectors_in_buffer = (256 - fat_lba) / fat_sectors;
if (max_fat_sectors_in_buffer > bpb->fc) max_fat_sectors_in_buffer = bpb->fc;
for (u32 f = 0; f < max_fat_sectors_in_buffer; f++) {
u32 flba = fat_lba + f * fat_sectors;
u32 sectors_to_write = fat_sectors;
if (flba + sectors_to_write > 256) sectors_to_write = 256 - flba;
if (sectors_to_write > 0 && flba < 256) {
if (fat32_write_sectors(flba, sectors_to_write, disk + (u64)flba * 512) != 0) return -7;
}
}
}
return 0; /* 已自行完成写入,跳过下方通用路径 */
}
for (u32 i = 0; i < clusters_needed; i++) {
u32 newc = fat32_find_free_cluster(fat_byte_off, (fat_sectors * 512) / 4);
if (newc == 0) { logl("[DSK] fat32 write: no free clusters"); return -3; }