diff --git a/.DS_Store b/.DS_Store
index d1eab41..264aee4 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 8420e88..c7fa5b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ limine 2/Makefile
limine 2/limine
limine 2/limine.dSYM/Contents/Resources/DWARF/limine
limine 2/limine.exe
+build/*
\ No newline at end of file
diff --git a/README.md b/README.md
index b864c3d..588b4e6 100644
--- a/README.md
+++ b/README.md
@@ -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.
@@ -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
diff --git a/boredos.iso b/boredos.iso
index 57b164f..687c646 100644
Binary files a/boredos.iso and b/boredos.iso differ
diff --git a/build/about.o b/build/about.o
index 7fc3c4b..0693b34 100644
Binary files a/build/about.o and b/build/about.o differ
diff --git a/build/boredos.elf b/build/boredos.elf
index ceb34e0..4ec1247 100755
Binary files a/build/boredos.elf and b/build/boredos.elf differ
diff --git a/build/cli_apps/boredver.o b/build/cli_apps/boredver.o
index 0cfe091..f659202 100644
Binary files a/build/cli_apps/boredver.o and b/build/cli_apps/boredver.o differ
diff --git a/build/cmd.o b/build/cmd.o
index 46239d1..1da30dd 100644
Binary files a/build/cmd.o and b/build/cmd.o differ
diff --git a/build/disk_manager.o b/build/disk_manager.o
index 96806fe..98b67ce 100644
Binary files a/build/disk_manager.o and b/build/disk_manager.o differ
diff --git a/build/explorer.o b/build/explorer.o
index 5242bd7..084b397 100644
Binary files a/build/explorer.o and b/build/explorer.o differ
diff --git a/build/fat32.o b/build/fat32.o
index bbf1650..3b108a2 100644
Binary files a/build/fat32.o and b/build/fat32.o differ
diff --git a/build/wm.o b/build/wm.o
index c9cba77..b5aa53c 100644
Binary files a/build/wm.o and b/build/wm.o differ
diff --git a/disk.img b/disk.img
index 2c13218..6c579df 100644
Binary files a/disk.img and b/disk.img differ
diff --git a/iso_root/README.md b/iso_root/README.md
index b864c3d..588b4e6 100644
--- a/iso_root/README.md
+++ b/iso_root/README.md
@@ -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.
@@ -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
diff --git a/iso_root/boredos.elf b/iso_root/boredos.elf
index ceb34e0..4ec1247 100755
Binary files a/iso_root/boredos.elf and b/iso_root/boredos.elf differ
diff --git a/screenshot.jpg b/screenshot.jpg
index b0b8a89..c83ab84 100644
Binary files a/screenshot.jpg and b/screenshot.jpg differ
diff --git a/src/.DS_Store b/src/.DS_Store
index 68de793..395d9bf 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
diff --git a/src/kernel/.DS_Store b/src/kernel/.DS_Store
index 647c966..5a47950 100644
Binary files a/src/kernel/.DS_Store and b/src/kernel/.DS_Store differ
diff --git a/src/kernel/about.c b/src/kernel/about.c
index 8922505..25ff5f5 100644
--- a/src/kernel/about.c
+++ b/src/kernel/about.c
@@ -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);
diff --git a/src/kernel/cli_apps/boredver.c b/src/kernel/cli_apps/boredver.c
index 2389d93..706df41 100644
--- a/src/kernel/cli_apps/boredver.c
+++ b/src/kernel/cli_apps/boredver.c
@@ -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");
}
diff --git a/src/kernel/disk.h b/src/kernel/disk.h
index ad7ed7f..ccced27 100644
--- a/src/kernel/disk.h
+++ b/src/kernel/disk.h
@@ -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);
diff --git a/src/kernel/disk_manager.c b/src/kernel/disk_manager.c
index a32278d..1a7da1d 100644
--- a/src/kernel/disk_manager.c
+++ b/src/kernel/disk_manager.c
@@ -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*)§or[11];
+ uint16_t spf16 = *(uint16_t*)§or[22];
+ uint32_t spf32 = *(uint32_t*)§or[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);
}
diff --git a/src/kernel/explorer.c b/src/kernel/explorer.c
index ce8bda9..3e1d7c6 100644
--- a/src/kernel/explorer.c
+++ b/src/kernel/explorer.c
@@ -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;
diff --git a/src/kernel/fat32.c b/src/kernel/fat32.c
index ff8d208..13cdb0c 100644
--- a/src/kernel/fat32.c
+++ b/src/kernel/fat32.c
@@ -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];
diff --git a/src/kernel/images/.DS_Store b/src/kernel/images/.DS_Store
index da96730..3eca0ac 100644
Binary files a/src/kernel/images/.DS_Store and b/src/kernel/images/.DS_Store differ
diff --git a/src/kernel/images/wallpapers/.DS_Store b/src/kernel/images/wallpapers/.DS_Store
index a913904..5de30c9 100644
Binary files a/src/kernel/images/wallpapers/.DS_Store and b/src/kernel/images/wallpapers/.DS_Store differ
diff --git a/src/kernel/wm.c b/src/kernel/wm.c
index 670d213..4215ced 100644
--- a/src/kernel/wm.c
+++ b/src/kernel/wm.c
@@ -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);
}