V1.63 bug fixes

This commit is contained in:
boreddevnl 2026-02-25 15:27:29 +01:00
parent 6e2f0d8c1a
commit cc950974e8
27 changed files with 348 additions and 69 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View file

@ -18,3 +18,4 @@ limine 2/Makefile
limine 2/limine
limine 2/limine.dSYM/Contents/Resources/DWARF/limine
limine 2/limine.exe
build/*

View file

@ -1,4 +1,4 @@
# BoredOS 1.51
# BoredOS
BoredOS has now exited Beta stage and is "stable enough" to be put out as a "stable" product.
<img src="bored.svg" width="200" /> </br>
@ -9,6 +9,7 @@ It features a DE (and WM), a FAT32 filesystem, customizable UI and much much mor
*this screenshot might be outdated*
## Features
- JPG image support
- Disk manager
- Drag and drop mouse centered UI
- Customizable UI

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
disk.img

Binary file not shown.

View file

@ -1,4 +1,4 @@
# BoredOS 1.51
# BoredOS
BoredOS has now exited Beta stage and is "stable enough" to be put out as a "stable" product.
<img src="bored.svg" width="200" /> </br>
@ -9,6 +9,7 @@ It features a DE (and WM), a FAT32 filesystem, customizable UI and much much mor
*this screenshot might be outdated*
## Features
- JPG image support
- Disk manager
- Drag and drop mouse centered UI
- Customizable UI

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 KiB

After

Width:  |  Height:  |  Size: 216 KiB

BIN
src/.DS_Store vendored

Binary file not shown.

BIN
src/kernel/.DS_Store vendored

Binary file not shown.

View file

@ -14,8 +14,8 @@ static void about_paint(Window *win) {
// Version info
draw_string(offset_x, offset_y + 105, "BoredOS 'Panda'", COLOR_WHITE);
draw_string(offset_x, offset_y + 120, "BoredOS Version 1.62", COLOR_WHITE);
draw_string(offset_x, offset_y + 135, "Kernel Version 2.5.2", COLOR_WHITE);
draw_string(offset_x, offset_y + 120, "BoredOS Version 1.63", COLOR_WHITE);
draw_string(offset_x, offset_y + 135, "Kernel Version 2.5.3", COLOR_WHITE);
// Copyright
draw_string(offset_x, offset_y + 150, "(C) 2026 boreddevnl.", COLOR_WHITE);

View file

@ -2,6 +2,6 @@
void cli_cmd_boredver(char *args) {
(void)args;
cli_write("BoredOS v1.62\n");
cli_write("BoredOS Kernel V2.5.2\n");
cli_write("BoredOS v1.63\n");
cli_write("BoredOS Kernel V2.5.3\n");
}

View file

@ -18,6 +18,7 @@ typedef struct Disk {
DiskType type;
bool is_fat32;
char name[32];
uint32_t partition_lba_offset; // LBA offset of FAT32 partition (0 for raw)
// Function pointers for driver operations
int (*read_sector)(struct Disk *disk, uint32_t sector, uint8_t *buffer);

View file

@ -217,6 +217,7 @@ void disk_manager_init(void) {
ramdisk->read_sector = ramdisk_read;
ramdisk->write_sector = ramdisk_write;
ramdisk->driver_data = NULL;
ramdisk->partition_lba_offset = 0;
disk_register(ramdisk);
}
@ -243,25 +244,95 @@ Disk* disk_get_by_index(int index) {
}
// Check for FAT32 Signature in MBR/VBR
static bool check_fat32_signature(Disk *disk) {
// === MBR Partition Table Structures ===
typedef struct {
uint8_t status; // 0x80 = bootable, 0x00 = inactive
uint8_t chs_first[3]; // CHS of first sector
uint8_t type; // Partition type
uint8_t chs_last[3]; // CHS of last sector
uint32_t lba_start; // LBA of first sector
uint32_t sector_count; // Number of sectors
} __attribute__((packed)) MBR_PartitionEntry;
// FAT32 partition type codes
#define PART_TYPE_FAT32 0x0B
#define PART_TYPE_FAT32_LBA 0x0C
// Check if sector contains a valid FAT32 BPB (Volume Boot Record)
static bool is_fat32_bpb(const uint8_t *sector) {
// Must have 0xAA55 boot signature
if (sector[510] != 0x55 || sector[511] != 0xAA) return false;
// Check for FAT32 filesystem string at offset 82
// "FAT32 " in the fs_type field of the BPB
if (sector[82] == 'F' && sector[83] == 'A' && sector[84] == 'T' &&
sector[85] == '3' && sector[86] == '2') {
return true;
}
// Also accept if bytes_per_sector is 512 and sectors_per_fat_16 is 0
// (FAT32 always has sectors_per_fat_16 == 0)
uint16_t bps = *(uint16_t*)&sector[11];
uint16_t spf16 = *(uint16_t*)&sector[22];
uint32_t spf32 = *(uint32_t*)&sector[36];
if (bps == 512 && spf16 == 0 && spf32 > 0) {
return true;
}
return false;
}
// Parse MBR partition table and find a FAT32 partition.
// Sets disk->partition_lba_offset and returns true if found.
static bool detect_fat32_partition(Disk *disk) {
uint8_t *buffer = (uint8_t*)kmalloc(512);
if (!buffer) return false;
// Read Sector 0
// Read sector 0 (MBR or raw BPB)
if (disk->read_sector(disk, 0, buffer) != 0) {
kfree(buffer);
return false;
}
// Check boot signature 0x55 0xAA at offset 510
// Must have 0xAA55 boot signature
if (buffer[510] != 0x55 || buffer[511] != 0xAA) {
kfree(buffer);
return false;
}
// Check MBR partition table entries (4 entries at offset 446)
MBR_PartitionEntry *partitions = (MBR_PartitionEntry*)&buffer[446];
for (int i = 0; i < 4; i++) {
if (partitions[i].type == PART_TYPE_FAT32 ||
partitions[i].type == PART_TYPE_FAT32_LBA) {
uint32_t part_lba = partitions[i].lba_start;
// Read the partition's first sector to verify it's a valid FAT32 BPB
uint8_t *pbuf = (uint8_t*)kmalloc(512);
if (!pbuf) { kfree(buffer); return false; }
if (disk->read_sector(disk, part_lba, pbuf) == 0 && is_fat32_bpb(pbuf)) {
disk->partition_lba_offset = part_lba;
kfree(pbuf);
kfree(buffer);
return true;
}
kfree(pbuf);
}
}
// Fallback: check if sector 0 itself is a raw FAT32 BPB (no partition table)
if (is_fat32_bpb(buffer)) {
disk->partition_lba_offset = 0;
kfree(buffer);
return true;
}
kfree(buffer);
return true;
return false;
}
static void try_add_ata_drive(uint16_t port, bool slave, const char *name) {
@ -279,13 +350,13 @@ static void try_add_ata_drive(uint16_t port, bool slave, const char *name) {
new_disk->read_sector = ata_read_sector;
new_disk->write_sector = ata_write_sector;
new_disk->driver_data = data;
new_disk->partition_lba_offset = 0;
// Check filesystem
if (check_fat32_signature(new_disk)) {
// Detect FAT32 (with MBR partition support)
if (detect_fat32_partition(new_disk)) {
new_disk->is_fat32 = true;
disk_register(new_disk);
} else {
kfree(data);
kfree(new_disk);
}

View file

@ -366,7 +366,14 @@ bool explorer_delete_permanently(const char *path) {
}
bool explorer_delete_recursive(const char *path) {
if (explorer_str_starts_with(path, "/RecycleBin")) {
// Check if path is on an external drive (not A:)
bool is_external = false;
if (path[0] && path[1] == ':' && path[0] != 'A' && path[0] != 'a') {
is_external = true;
}
if (is_external || explorer_str_starts_with(path, "/RecycleBin")) {
// External drives have no RecycleBin — delete permanently
return explorer_delete_permanently(path);
} else {
// Move to Recycle Bin
@ -885,7 +892,7 @@ static void explorer_open_item(Window *win, int index) {
}
// Draw a simple file icon
static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, const char *filename) {
static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, const char *filename, const char *current_path) {
if (is_dir) {
if (explorer_strcmp(filename, "RecycleBin") == 0) draw_recycle_bin_icon(x + 5, y + 5, "");
else draw_folder_icon(x + 5, y + 5, "");
@ -903,7 +910,12 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c
} else if (explorer_str_ends_with(filename, ".pnt")) {
draw_paint_icon(x + 5, y + 5, "");
} else if (explorer_str_ends_with(filename, ".jpg") || explorer_str_ends_with(filename, ".JPG")) {
draw_image_icon(x + 5, y + 5, filename);
// Build full path for thumbnail loading
char full_path[256];
explorer_strcpy(full_path, current_path);
if (full_path[explorer_strlen(full_path) - 1] != '/') explorer_strcat(full_path, "/");
explorer_strcat(full_path, filename);
draw_image_icon(x + 5, y + 5, full_path);
} else {
draw_document_icon(x + 5, y + 5, "");
}
@ -989,7 +1001,7 @@ static void explorer_paint(Window *win) {
draw_rounded_rect_filled(item_x, item_y, EXPLORER_ITEM_WIDTH, EXPLORER_ITEM_HEIGHT, 6, bg_color);
// Draw icon (larger area)
explorer_draw_file_icon(item_x + 5, item_y + 5, state->items[i].is_directory, state->items[i].color, state->items[i].name);
explorer_draw_file_icon(item_x + 5, item_y + 5, state->items[i].is_directory, state->items[i].color, state->items[i].name, state->current_path);
// Draw name using intelligent wrapping
const char *display_name = state->items[i].name;
@ -1098,91 +1110,96 @@ static void explorer_paint(Window *win) {
int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60;
// Dialog background
draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY);
draw_bevel_rect(dlg_x, dlg_y, 300, 110, true);
// Dialog background (modern dark, rounded)
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
// Title
const char *title = state->dialog_target_is_dir ? "Delete Folder?" : "Delete File?";
draw_string(dlg_x + 10, dlg_y + 10, title, COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 10, title, COLOR_WHITE);
// Message
if (explorer_str_starts_with(state->current_path, "/RecycleBin")) {
draw_string(dlg_x + 10, dlg_y + 35, "This action cannot be undone.", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 48, "Delete forever?", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 35, "This action cannot be undone.", 0xFFAAAAAA);
draw_string(dlg_x + 10, dlg_y + 48, "Delete forever?", 0xFFAAAAAA);
} else {
draw_string(dlg_x + 10, dlg_y + 35, "This file will be moved to", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 45, "the recycle bin.", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 35, "This file will be moved to", 0xFFAAAAAA);
draw_string(dlg_x + 10, dlg_y + 45, "the recycle bin.", 0xFFAAAAAA);
}
// Buttons
draw_button(dlg_x + 50, dlg_y + 65, 80, 25, "Delete", false);
draw_button(dlg_x + 170, dlg_y + 65, 80, 25, "Cancel", false);
// Buttons (rounded, delete button red-tinted)
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, 0xFF8B2020);
draw_string(dlg_x + 68, dlg_y + 72, "Delete", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE);
} else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) {
int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60;
// Dialog background
draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY);
draw_bevel_rect(dlg_x, dlg_y, 300, 110, true);
// Dialog background (modern dark, rounded)
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
// Title
draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_WHITE);
// Message
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
// Buttons
draw_button(dlg_x + 50, dlg_y + 70, 80, 25, "Replace", false);
draw_button(dlg_x + 170, dlg_y + 70, 80, 25, "Cancel", false);
// Buttons (rounded)
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 63, dlg_y + 77, "Replace", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE);
} else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) {
int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60;
// Dialog background
draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY);
draw_bevel_rect(dlg_x, dlg_y, 300, 110, true);
// Dialog background (modern dark, rounded)
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
// Title
draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_WHITE);
// Message
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
// Buttons
draw_button(dlg_x + 50, dlg_y + 70, 80, 25, "Replace", false);
draw_button(dlg_x + 170, dlg_y + 70, 80, 25, "Cancel", false);
// Buttons (rounded)
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 63, dlg_y + 77, "Replace", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE);
} else if (state->dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) {
int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60;
// Dialog background
draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY);
draw_bevel_rect(dlg_x, dlg_y, 300, 110, true);
// Dialog background (modern dark, rounded)
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
// Title
draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_WHITE);
// Message
draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", 0xFFAAAAAA);
draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA);
// Buttons
draw_button(dlg_x + 50, dlg_y + 70, 80, 25, "Overwrite", false);
draw_button(dlg_x + 170, dlg_y + 70, 80, 25, "Cancel", false);
// Buttons (rounded)
draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 57, dlg_y + 77, "Overwrite", COLOR_WHITE);
draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE);
} else if (state->dialog_state == DIALOG_ERROR) {
int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60;
draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY);
draw_bevel_rect(dlg_x, dlg_y, 300, 110, true);
// Dialog background (modern dark, rounded)
draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL);
draw_string(dlg_x + 10, dlg_y + 10, "Error", COLOR_RED);
draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, COLOR_BLACK);
draw_string(dlg_x + 10, dlg_y + 10, "Error", 0xFFFF6B6B);
draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, 0xFFAAAAAA);
// OK Button
draw_button(dlg_x + 110, dlg_y + 70, 80, 25, "OK", false);
// OK Button (rounded)
draw_rounded_rect_filled(dlg_x + 110, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER);
draw_string(dlg_x + 138, dlg_y + 77, "OK", COLOR_WHITE);
} else if (state->dialog_state == DIALOG_RENAME) {
int dlg_x = win->x + win->w / 2 - 150;
int dlg_y = win->y + win->h / 2 - 60;

View file

@ -44,6 +44,7 @@ typedef struct {
uint32_t root_cluster;
uint32_t fat_size; // sectors
uint32_t total_sectors;
uint32_t partition_offset; // LBA offset of partition start
bool mounted;
} FAT32_Volume;
@ -51,6 +52,17 @@ static FAT32_Volume volumes[26]; // A-Z
// === Helper Functions (Shared) ===
// Serial debug output
static void fs_serial_char(char c) {
while (!(inb(0x3F8 + 5) & 0x20));
outb(0x3F8, c);
}
static void fs_serial_str(const char *s) { while (*s) fs_serial_char(*s++); }
static void fs_serial_num(uint32_t n) {
if (n >= 10) fs_serial_num(n / 10);
fs_serial_char('0' + (n % 10));
}
static size_t fs_strlen(const char *str) {
size_t len = 0;
while (str[len]) len++;
@ -373,31 +385,49 @@ static bool realfs_mount(char drive) {
Disk *disk = disk_get_by_letter(drive);
if (!disk) return false;
// Use partition LBA offset from disk (set during MBR parsing)
uint32_t part_offset = disk->partition_lba_offset;
uint8_t *sect0 = (uint8_t*)kmalloc(512);
if (!sect0) return false;
if (disk->read_sector(disk, 0, sect0) != 0) {
// Read BPB from partition start (sector 0 for raw, partition LBA for MBR)
if (disk->read_sector(disk, part_offset, sect0) != 0) {
kfree(sect0);
return false;
}
FAT32_BootSector *bpb = (FAT32_BootSector*)sect0;
// Simple verification (could be more robust)
if (bpb->boot_signature_value != 0xAA55) {
kfree(sect0);
return false;
}
volumes[idx].disk = disk;
volumes[idx].fat_begin_lba = bpb->reserved_sectors;
volumes[idx].cluster_begin_lba = bpb->reserved_sectors + (bpb->num_fats * bpb->sectors_per_fat_32);
volumes[idx].partition_offset = part_offset;
volumes[idx].fat_begin_lba = part_offset + bpb->reserved_sectors;
volumes[idx].cluster_begin_lba = part_offset + bpb->reserved_sectors + (bpb->num_fats * bpb->sectors_per_fat_32);
volumes[idx].sectors_per_cluster = bpb->sectors_per_cluster;
volumes[idx].root_cluster = bpb->root_cluster;
volumes[idx].fat_size = bpb->sectors_per_fat_32;
volumes[idx].total_sectors = bpb->total_sectors_32;
volumes[idx].mounted = true;
fs_serial_str("[FAT32] mounted drive ");
fs_serial_char(drive);
fs_serial_str(": part_offset=");
fs_serial_num(part_offset);
fs_serial_str(" fat_lba=");
fs_serial_num(volumes[idx].fat_begin_lba);
fs_serial_str(" cluster_lba=");
fs_serial_num(volumes[idx].cluster_begin_lba);
fs_serial_str(" spc=");
fs_serial_num(volumes[idx].sectors_per_cluster);
fs_serial_str(" root_cl=");
fs_serial_num(volumes[idx].root_cluster);
fs_serial_str("\n");
kfree(sect0);
return true;
}
@ -488,6 +518,14 @@ static FAT32_FileHandle* realfs_open(char drive, const char *path, const char *m
const char *p = path;
if (*p == '/') p++;
fs_serial_str("[FAT32] realfs_open drive=");
fs_serial_char(drive);
fs_serial_str(" path='");
fs_serial_str(path);
fs_serial_str("' mode=");
fs_serial_char(mode[0]);
fs_serial_str("\n");
if (*p == 0) {
// Root dir
if (mode[0] == 'w') return NULL; // Cannot write to root as file
@ -535,6 +573,8 @@ static FAT32_FileHandle* realfs_open(char drive, const char *path, const char *m
for (int e = 0; e < entries_per_cluster; e++) {
if (entry[e].filename[0] == 0) break; // End of dir
if (entry[e].filename[0] == 0xE5) continue; // Deleted
if (entry[e].attributes == 0x0F) continue; // LFN entry
if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Volume label
// Compare name (simplistic 8.3 matching)
char name[12];
@ -558,6 +598,13 @@ static FAT32_FileHandle* realfs_open(char drive, const char *path, const char *m
if (match) {
uint32_t cluster = (entry[e].start_cluster_high << 16) | entry[e].start_cluster_low;
fs_serial_str("[FAT32] MATCH '");
fs_serial_str(name);
fs_serial_str("' cluster=");
fs_serial_num(cluster);
fs_serial_str(" size=");
fs_serial_num(entry[e].file_size);
fs_serial_str("\n");
uint32_t lba = vol->cluster_begin_lba + (search_cluster - 2) * vol->sectors_per_cluster;
int sect_in_cluster = (e * 32) / 512;
@ -922,6 +969,8 @@ static bool realfs_delete(char drive, const char *path) {
for (int e = 0; e < entries_per_cluster; e++) {
if (entry[e].filename[0] == 0) break;
if (entry[e].filename[0] == 0xE5) continue;
if (entry[e].attributes == 0x0F) continue; // Skip LFN entries
if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Skip volume label
// Format name and compare
char name[12];
@ -1062,6 +1111,8 @@ static int realfs_list_directory(char drive, const char *path, FAT32_FileInfo *e
for (int e = 0; e < entries_per_cluster && count < max_entries; e++) {
if (entry[e].filename[0] == 0) break;
if (entry[e].filename[0] == 0xE5) continue;
if (entry[e].attributes == 0x0F) continue; // Skip LFN entries
if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Skip volume label
// Format name
char name[13];

Binary file not shown.

Binary file not shown.

View file

@ -16,6 +16,7 @@
#include "about.h"
#include "minesweeper.h"
#include "fat32.h"
#include "nanojpeg.h"
#include "memory_manager.h"
#include "paint.h"
#include "disk.h"
@ -474,12 +475,109 @@ void draw_document_icon(int x, int y, const char *label) {
draw_icon_label(x, y, label);
}
// === Dynamic thumbnail cache for JPG explorer icons ===
#define THUMB_CACHE_SIZE 8
#define THUMB_PIXELS (48 * 48)
static struct {
char path[256];
uint32_t pixels[THUMB_PIXELS];
bool valid;
bool failed; // Mark as failed so we don't retry
} thumb_cache[THUMB_CACHE_SIZE];
static int thumb_cache_next = 0; // Round-robin eviction
static uint32_t* thumb_cache_lookup(const char *path) {
for (int i = 0; i < THUMB_CACHE_SIZE; i++) {
if (thumb_cache[i].valid && str_eq(thumb_cache[i].path, path) == 0) {
return thumb_cache[i].pixels;
}
}
return NULL;
}
static bool thumb_cache_is_failed(const char *path) {
for (int i = 0; i < THUMB_CACHE_SIZE; i++) {
if (thumb_cache[i].failed && str_eq(thumb_cache[i].path, path) == 0) {
return true;
}
}
return false;
}
static uint32_t* thumb_cache_decode(const char *path) {
// Open and read the JPG file
FAT32_FileHandle *fh = fat32_open(path, "r");
if (!fh) return NULL;
uint32_t file_size = fh->size;
if (file_size == 0 || file_size > 2 * 1024 * 1024) {
fat32_close(fh);
return NULL;
}
unsigned char *buf = (unsigned char*)kmalloc(file_size);
if (!buf) { fat32_close(fh); return NULL; }
int total = 0;
while (total < (int)file_size) {
int chunk = fat32_read(fh, buf + total, (int)file_size - total);
if (chunk <= 0) break;
total += chunk;
}
fat32_close(fh);
if (total <= 0) { kfree(buf); return NULL; }
// Decode JPEG
njInit();
if (njDecode(buf, total) != NJ_OK) {
njDone();
kfree(buf);
return NULL;
}
int img_w = njGetWidth();
int img_h = njGetHeight();
unsigned char *img = njGetImage();
// Store in cache — downscale to 48x48
int slot = thumb_cache_next;
thumb_cache_next = (thumb_cache_next + 1) % THUMB_CACHE_SIZE;
// Copy path
int p = 0;
while (path[p] && p < 255) { thumb_cache[slot].path[p] = path[p]; p++; }
thumb_cache[slot].path[p] = 0;
// Downscale image to 48x48 with aspect-fill
for (int ty = 0; ty < 48; ty++) {
for (int tx = 0; tx < 48; tx++) {
int sx = tx * img_w / 48;
int sy = ty * img_h / 48;
if (sx >= img_w) sx = img_w - 1;
if (sy >= img_h) sy = img_h - 1;
int idx = (sy * img_w + sx) * 3;
uint32_t r = img[idx], g = img[idx+1], b = img[idx+2];
thumb_cache[slot].pixels[ty * 48 + tx] = 0xFF000000 | (r << 16) | (g << 8) | b;
}
}
thumb_cache[slot].valid = true;
thumb_cache[slot].failed = false;
njDone();
kfree(buf);
return thumb_cache[slot].pixels;
}
void draw_image_icon(int x, int y, const char *label) {
uint32_t icon_buf[48 * 48];
for (int i = 0; i < 48 * 48; i++) icon_buf[i] = 0xFFFF00FF;
graphics_set_render_target(icon_buf, 48, 48);
uint32_t *thumb = NULL;
// Fast path: check hardcoded wallpaper names
if (str_eq(label, "moon.jpg") == 0) thumb = wallpaper_get_thumb(0);
else if (str_eq(label, "mountain.jpg") == 0) thumb = wallpaper_get_thumb(1);
else if (str_eq(label, "moon") == 0) thumb = wallpaper_get_thumb(0);
@ -490,15 +588,52 @@ void draw_image_icon(int x, int y, const char *label) {
else if (str_ends_with(label, "mountain.jpg")) thumb = wallpaper_get_thumb(1);
}
// Dynamic path: try thumbnail cache for any JPG file path
if (!thumb && !thumb_cache_is_failed(label)) {
thumb = thumb_cache_lookup(label);
if (!thumb) {
// Try to decode and cache
graphics_set_render_target(NULL, 0, 0); // Restore before file I/O
thumb = thumb_cache_decode(label);
if (!thumb) {
// Mark as failed so we don't retry every frame
int slot = thumb_cache_next;
int p = 0;
while (label[p] && p < 255) { thumb_cache[slot].path[p] = label[p]; p++; }
thumb_cache[slot].path[p] = 0;
thumb_cache[slot].valid = false;
thumb_cache[slot].failed = true;
thumb_cache_next = (thumb_cache_next + 1) % THUMB_CACHE_SIZE;
}
// Re-set render target for icon drawing
for (int i = 0; i < 48 * 48; i++) icon_buf[i] = 0xFFFF00FF;
graphics_set_render_target(icon_buf, 48, 48);
}
}
if (thumb) {
// White border
draw_rounded_rect_filled(0, 0, 48, 48, 4, 0xFFFFFFFF);
// Draw thumbnail into icon - handle both 100x60 wallpaper thumbs and 48x48 dynamic thumbs
bool is_wallpaper_thumb = false;
if (str_ends_with(label, "moon.jpg") || str_ends_with(label, "mountain.jpg") ||
str_eq(label, "moon") == 0 || str_eq(label, "mountain") == 0) {
is_wallpaper_thumb = true;
}
int dst_w = 44, dst_h = 44;
for (int ty = 0; ty < dst_h; ty++) {
for (int tx = 0; tx < dst_w; tx++) {
int sx = tx * 100 / dst_w;
int sy = ty * 60 / dst_h;
put_pixel(2 + tx, 2 + ty, thumb[sy * 100 + sx]);
uint32_t pixel;
if (is_wallpaper_thumb) {
int sx = tx * 100 / dst_w;
int sy = ty * 60 / dst_h;
pixel = thumb[sy * 100 + sx];
} else {
int sx = tx * 48 / dst_w;
int sy = ty * 48 / dst_h;
pixel = thumb[sy * 48 + sx];
}
put_pixel(2 + tx, 2 + ty, pixel);
}
}
} else {
@ -969,6 +1104,7 @@ void wm_paint(void) {
if (str_ends_with(icon->name, ".pnt")) draw_paint_icon(icon->x, icon->y, icon->name);
else if (str_ends_with(icon->name, ".jpg") || str_ends_with(icon->name, ".JPG")) {
draw_image_icon(icon->x, icon->y, icon->name);
draw_icon_label(icon->x, icon->y, icon->name);
}
else draw_document_icon(icon->x, icon->y, icon->name);
}