:: commit 34460c31a5142c7c6ed50946c111332e2bd644a5

JlXip <jlxip@protonmail.com> — 2021-02-21 02:45

parents: 8141ff540b

iso9660

diff --git a/.gitignore b/.gitignore
index 67b4bf89..ac2046e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,11 +7,14 @@
 /**/*.bin.gz
 /**/*.elf
 /**/*.hdd
+/**/*.iso
 /bochsout.txt
 /bx_enh_dbg.ini
 .vscode
 /limine-install
 !/limine.bin
 !/limine-pxe.bin
+!/limine-cd.bin
 !/stage2.map
 /stivale
+/test_image
diff --git a/Makefile b/Makefile
index 63edae00..a6f66170 100644
--- a/Makefile
+++ b/Makefile
@@ -29,6 +29,7 @@ install: all
 bootloader: | decompressor stage2
 	gzip -n -9 < stage2/stage2.bin > stage2/stage2.bin.gz
 	cd bootsect && nasm bootsect.asm -fbin -o ../limine.bin
+	cd cdboot && nasm bootsect.asm -fbin -o ../limine-cd.bin
 	cd pxeboot && nasm bootsect.asm -fbin -o ../limine-pxe.bin
 	cp stage2/stage2.map ./
 	cp stage2/stage3.bin ./limine.sys
@@ -118,3 +119,12 @@ fat32-test: test.hdd bootloader | all
 	rm -rf test_image loopback_dev
 	./limine-install ./ test.hdd
 	qemu-system-x86_64 -net none -smp 4 -enable-kvm -cpu host -hda test.hdd -debugcon stdio
+
+iso9660-test: bootloader
+	$(MAKE) -C test
+	cp stage2.map test/
+	rm -rf test_image/
+	mkdir -p test_image/boot
+	cp -rv limine-cd.bin limine.sys stage2/stages.bin test/* test_image/boot/
+	genisoimage -no-emul-boot -b boot/limine-cd.bin -o test.iso test_image/
+	qemu-system-x86_64 -net none -smp 4 -enable-kvm -cpu host -cdrom test.iso -debugcon stdio
diff --git a/cdboot/bootsect.asm b/cdboot/bootsect.asm
new file mode 100644
index 00000000..03a7ee60
--- /dev/null
+++ b/cdboot/bootsect.asm
@@ -0,0 +1,109 @@
+BITS 16
+ORG 0x7C00
+
+; Please read bootsect/bootsect.asm before this file
+
+%define ISO9660_BUFFER 0x8000
+%define ROOT_DIRECTORY 156
+%define ROOT_DIRECTORY_BUFFER (ISO9660_BUFFER + ROOT_DIRECTORY)
+
+%define DIRECTORY_RECORD_LENGTH 0
+%define DIRECTORY_RECORD_LBA 2
+%define DIRECTORY_RECORD_SIZE 10
+%define DIRECTORY_RECORD_FILENAME_LENGTH 32
+%define DIRECTORY_RECORD_FILENAME 33
+
+%define BOOT_FROM_CD 2
+
+jmp skip_bpb
+nop
+times 87 db 0
+
+skip_bpb:
+    cli
+    cld
+    jmp 0x0000:.initialise_cs
+  .initialise_cs:
+    xor ax, ax
+    mov ds, ax
+    mov es, ax
+    mov ss, ax
+    mov sp, 0x7C00
+    sti
+
+    ; int 13h?
+    mov ah, 0x41
+    mov bx, 0x55AA
+    int 0x13
+    jc err
+    cmp bx, 0xAA55
+    jne err
+
+    mov esp, 0x7C00
+
+    ; --- Load the stage 2 ---
+    ; Find and load the PVD
+    call findPVD
+    jc err
+
+    ; Load the root directory
+    mov eax, dword [ROOT_DIRECTORY_BUFFER + DIRECTORY_RECORD_LBA]
+    mov ecx, dword [ROOT_DIRECTORY_BUFFER + DIRECTORY_RECORD_SIZE]
+
+    mov esi, ecx  ; Size, for read_file
+    add ecx, 2047
+    shr ecx, 11
+    call read_2k_sectors
+    jc err
+
+    ; Find and load '/BOOT'
+    mov ebx, TXT_BOOT
+    mov cl, TXT_BOOT_SZ
+    call read_file
+    jc err
+
+    ; Find and load '/BOOT/STAGES.BIN'
+    mov ebx, TXT_STAGES
+    mov cl, TXT_STAGES_SZ
+    call read_file  ; esi is set from the last call
+    jc err
+
+    ; Enable GDT
+    lgdt [gdt]
+    cli
+    mov eax, cr0
+    or al, 1
+    mov cr0, eax
+
+    jmp 0x08:pmode
+
+err:
+    hlt
+    jmp err
+
+%include 'iso9660.asm'
+%include '../bootsect/gdt.inc'
+
+BITS 32
+pmode:
+    mov eax, 0x10
+    mov ds, ax
+    mov es, ax
+    mov fs, ax
+    mov gs, ax
+    mov ss, ax
+
+    ; Time to handle control over to the stage 2
+    push BOOT_FROM_CD
+    and edx, 0xFF
+    push edx  ; Boot drive
+    call ISO9660_BUFFER
+    hlt
+
+TXT_BOOT: db "BOOT"
+TXT_BOOT_SZ equ $ - TXT_BOOT
+TXT_STAGES: db "STAGES.BIN;1"
+TXT_STAGES_SZ equ $ - TXT_STAGES
+
+; Just making sure the entry point (ISO9660_BUFFER) is not reached
+times (0x8000 - 0x7C00) - ($ - $$) db 0
diff --git a/cdboot/findFile.asm b/cdboot/findFile.asm
new file mode 100644
index 00000000..0a66a2d1
--- /dev/null
+++ b/cdboot/findFile.asm
@@ -0,0 +1,58 @@
+BITS 16
+
+; --- Find file in directory ---
+; IN:
+; eax <- directory extent
+; ebx <- ptr to filename
+; cl  <- filename sz
+; esi <- size of directory extent
+
+; OUT:
+; eax <- LBA of the extent
+; ebx <- size in bytes
+; Carry if not found
+
+; SMASHES:
+; ch
+findFile:
+    push edx
+    push edi
+    push esi
+    mov edx, eax
+
+  .checkEntry:
+    ; Get the size of this entry
+    mov edi, dword [edx + DIRECTORY_RECORD_LENGTH]
+    and edi, 0xFF
+
+    ; Check filename size
+    mov al, byte [edx + DIRECTORY_RECORD_FILENAME_LENGTH]
+    cmp al, cl
+    jnz .gonext
+
+    ; Sizes match, check filename
+    lea eax, dword [edx + DIRECTORY_RECORD_FILENAME]
+    call strcmp
+    jnc .found
+
+    ; Go to the next
+  .gonext:
+    add edx, edi
+    sub esi, edi
+    test esi, esi
+    jnz .checkEntry
+
+  .notfound:
+    stc
+    jmp .end
+  .found:
+    clc
+    mov eax, [edx + DIRECTORY_RECORD_LBA]
+    mov ebx, [edx + DIRECTORY_RECORD_SIZE]
+  .end:
+    pop esi
+    pop edi
+    pop edx
+    ret
+
+%include 'strcmp.asm'
diff --git a/cdboot/findPVD.asm b/cdboot/findPVD.asm
new file mode 100644
index 00000000..6f9b94d2
--- /dev/null
+++ b/cdboot/findPVD.asm
@@ -0,0 +1,51 @@
+BITS 16
+
+; --- Find primary volume descriptor ---
+; IN:
+; dl <- drive number
+
+; OUT:
+; [ISO9660_BUFFER] <- PVD
+; Carry if not found
+findPVD:
+    pusha
+    ; Just start reading volume descriptors
+
+    mov eax, 0x10    ; First volume descriptor's LBA
+  .checkVD:
+    mov cx, 1    ; Each volume descriptor is 2KB
+    call read_2k_sectors
+
+    ; Check identifier
+    mov ecx, ISO9660_BUFFER
+    inc ecx
+    mov bl, byte [ecx]
+    inc ecx
+    mov ecx, dword [ecx]
+    cmp bl, 'C'
+    jne .notfound
+    cmp ecx, 0x31303044
+    jne .notfound
+
+    ; Check type = 0xFF (final)
+    mov ecx, ISO9660_BUFFER
+    mov bl, byte [ecx]
+    cmp bl, 0xFF
+    jz .notfound
+
+    ; Check type = 0x01 (PVD)
+    cmp bl, 0x01
+    jz .found
+
+    ; Didn't match, go to the next
+    inc eax
+    jmp .checkVD
+
+  .found:
+    clc
+    jmp .end
+  .notfound:
+    stc
+  .end:
+    popa
+    ret
diff --git a/cdboot/iso9660.asm b/cdboot/iso9660.asm
new file mode 100644
index 00000000..28f1551f
--- /dev/null
+++ b/cdboot/iso9660.asm
@@ -0,0 +1,35 @@
+BITS 16
+
+%include 'read_2k_sectors.asm'
+%include 'findPVD.asm'
+%include 'findFile.asm'
+
+; --- Read file ---
+; IN:
+; ebx <- ptr to filename
+; cl <- filename sz
+; esi <- size of directory extent
+
+; OUT:
+; If found: [ISO9660_BUFFER] <- contents of the file
+; If not found: CF=1
+; esi <- size in bytes
+
+; SMASHES:
+; eax, ebx, ecx
+read_file:
+    mov eax, ISO9660_BUFFER
+    call findFile
+    jc .end
+
+    ; LBA of the extent is now @ eax
+    ; ebx is size in bytes
+    mov esi, ebx  ; Return value
+
+    ; bytes to 2k sectors
+    mov ecx, ebx
+    add ecx, 2047
+    shr ecx, 11
+    call read_2k_sectors
+  .end:
+    ret
diff --git a/cdboot/read_2k_sectors.asm b/cdboot/read_2k_sectors.asm
new file mode 100644
index 00000000..f5d3dd16
--- /dev/null
+++ b/cdboot/read_2k_sectors.asm
@@ -0,0 +1,30 @@
+BITS 16
+
+; --- Read sectors from disk ---
+; IN:
+; eax <- start LBA (2k sectors)
+; cx <- number of 2k sectors
+; dl <- drive number
+; ds <- ZERO
+
+; OUT:
+; Carry if error
+
+align 8
+dapack:
+    dapack_size:    db 0x10
+    dapack_null:    db 0x00
+    dapack_nblocks: dw 0
+    dapack_buffer:  dd ISO9660_BUFFER
+    dapack_LBA:     dq 0
+
+read_2k_sectors:
+    pusha
+    mov dword [dapack_LBA], eax
+    mov word  [dapack_nblocks], cx
+
+    mov ah, 0x42
+    mov si, dapack
+    int 0x13
+    popa
+    ret
diff --git a/cdboot/strcmp.asm b/cdboot/strcmp.asm
new file mode 100644
index 00000000..fd21186f
--- /dev/null
+++ b/cdboot/strcmp.asm
@@ -0,0 +1,34 @@
+BITS 16
+
+; --- Compare strings ---
+; IN:
+; eax <- ptr to first string
+; ebx <- ptr to second string
+; cl <- size of BOTH strings (must be the same)
+
+; OUT:
+; CF=0 if equal, CF=1 otherwise
+strcmp:
+    pusha
+  .nextchar:
+    mov dh, byte [eax]
+    mov dl, byte [ebx]
+    cmp dh, dl
+    jnz .notequal
+
+    ; Characters match
+    dec cl
+    test cl, cl
+    jz .equal
+    inc eax
+    inc ebx
+    jmp .nextchar
+
+  .equal:
+    clc
+    jmp .end
+  .notequal:
+    stc
+  .end:
+    popa
+    ret
diff --git a/limine-cd.bin b/limine-cd.bin
new file mode 100644
index 00000000..43f1e81e
Binary files /dev/null and b/limine-cd.bin differ
diff --git a/limine-pxe.bin b/limine-pxe.bin
index 7406f422..d99b96dd 100644
Binary files a/limine-pxe.bin and b/limine-pxe.bin differ
diff --git a/limine.bin b/limine.bin
index 06f1233e..11a45a41 100644
Binary files a/limine.bin and b/limine.bin differ
diff --git a/limine.sys b/limine.sys
index d560af4b..bdff02c2 100644
Binary files a/limine.sys and b/limine.sys differ
diff --git a/stage2/fs/file.c b/stage2/fs/file.c
index fc0ca148..8fbc65c4 100644
--- a/stage2/fs/file.c
+++ b/stage2/fs/file.c
@@ -4,6 +4,7 @@
 #include <fs/echfs.h>
 #include <fs/ext2.h>
 #include <fs/fat32.h>
+#include <fs/iso9660.h>
 #include <lib/blib.h>
 #include <mm/pmm.h>
 #include <lib/part.h>
@@ -23,6 +24,20 @@ bool fs_get_guid(struct guid *guid, struct volume *part) {
 int fopen(struct file_handle *ret, struct volume *part, const char *filename) {
     ret->is_memfile = false;
 
+    if (iso9660_check_signature(part)) {
+        struct iso9660_file_handle *fd = ext_mem_alloc(sizeof(struct iso9660_file_handle));
+
+        int r = iso9660_open(fd, part, filename);
+        if (r)
+            return r;
+
+        ret->fd = (void *)fd;
+        ret->read = (void *)iso9660_read;
+        ret->size = fd->size;
+
+        return 0;
+    }
+
     if (echfs_check_signature(part)) {
         struct echfs_file_handle *fd = ext_mem_alloc(sizeof(struct echfs_file_handle));
 
diff --git a/stage2/fs/iso9660.c b/stage2/fs/iso9660.c
new file mode 100644
index 00000000..5326a6d0
--- /dev/null
+++ b/stage2/fs/iso9660.c
@@ -0,0 +1,197 @@
+#include <fs/iso9660.h>
+#include <lib/blib.h>
+#include <lib/libc.h>
+#include <mm/pmm.h>
+
+#define ISO9660_FIRST_VOLUME_DESCRIPTOR 0x10
+#define ISO9660_VOLUME_DESCRIPTOR_SIZE ISO9660_SECTOR_SIZE
+#define ROCK_RIDGE_MAX_FILENAME 255
+
+// --- Both endian structures ---
+struct BE16_t { uint16_t little, big; } __attribute__((packed));
+struct BE32_t { uint32_t little, big; } __attribute__((packed));
+
+// --- Directory entries ---
+struct iso9660_directory_entry {
+    uint8_t length;
+    uint8_t extended_attribute_length;
+    struct BE32_t extent;
+    struct BE32_t extent_size;
+    uint8_t datetime[7];
+    uint8_t flags;
+    uint8_t interleaved_unit_size;
+    uint8_t interleaved_gap_size;
+    struct BE16_t volume_seq;
+    uint8_t filename_size;
+} __attribute__((packed));
+
+// --- Volume descriptors ---
+// VDT = Volume Descriptor Type
+enum {
+    ISO9660_VDT_BOOT_RECORD,
+    ISO9660_VDT_PRIMARY,
+    ISO9660_VDT_SUPPLEMENTARY,
+    ISO9660_VDT_PARTITION_DESCRIPTOR,
+    ISO9660_VDT_TERMINATOR = 255
+};
+
+struct iso9660_volume_descriptor {
+    uint8_t type;
+    char identifier[5];
+    uint8_t version;
+    uint8_t data[2041];
+} __attribute__((packed));
+
+struct iso9660_primary_volume {
+    uint8_t type;
+    char standard_identifier[5];
+    uint8_t version;
+    uint8_t unused0[1];
+    char system_identifier[32];
+    char volume_identifier[32];
+    uint8_t unused1[8];
+    struct BE32_t space_size;
+    uint8_t unused2[32];
+    struct BE16_t set_size;
+    struct BE16_t volume_seq;
+    struct BE16_t LBA_size;
+    struct BE32_t path_table_size;
+
+    uint32_t LBA_path_table_little;
+    uint32_t LBA_optional_path_table_little;
+    uint32_t LBA_path_table_big;
+    uint32_t LBA_optional_path_table_big;
+
+    struct iso9660_directory_entry root;
+    uint8_t no_one_cares[1858];
+} __attribute__((packed));
+
+
+// --- Implementation ---
+// Cached root
+static void *root = NULL;
+static uint32_t root_size = 0;
+
+static void iso9660_find_PVD(struct iso9660_volume_descriptor *desc, struct volume *vol) {
+    uint32_t lba = ISO9660_FIRST_VOLUME_DESCRIPTOR;
+    while (true) {
+        volume_read(vol, desc, lba * ISO9660_SECTOR_SIZE, ISO9660_SECTOR_SIZE);
+
+        switch (desc->type) {
+        case ISO9660_VDT_PRIMARY:
+            return;
+        case ISO9660_VDT_TERMINATOR:
+            panic("ISO9660: no primary volume descriptor");
+            break;
+        }
+
+        ++lba;
+    }
+}
+
+static void iso9660_cache_root(struct volume *vol) {
+    struct iso9660_primary_volume pv;
+    iso9660_find_PVD((struct iso9660_volume_descriptor *)&pv, vol);
+
+    root_size = pv.root.extent_size.little;
+    root = ext_mem_alloc(root_size);
+    volume_read(vol, root, pv.root.extent.little * ISO9660_SECTOR_SIZE, root_size);
+}
+
+static int iso9660_strcmp(const char *a, const char *b, size_t size) {
+    while (size--) {
+        char ca = *a++;
+        char cb = *b++;
+        if (!(ca == cb || (ca - ('a'-'A')) == cb))
+            return 1;
+    }
+
+    return 0;
+}
+
+static struct iso9660_directory_entry *iso9660_find(void *buffer, uint32_t size, const char *filename) {
+    // The file can be either FILENAME or FILENAME;1
+    uint32_t len = strlen(filename);
+    char finalfile[len + 2];
+    strcpy(finalfile, filename);
+    finalfile[len + 0] = ';';
+    finalfile[len + 1] = '1';
+
+    while (size) {
+        struct iso9660_directory_entry *entry = buffer;
+        char* entry_filename = (char*)entry + sizeof(struct iso9660_directory_entry);
+
+        if (!entry->length) {
+            return NULL;
+        } else if (entry->filename_size == len && !iso9660_strcmp(filename, entry_filename, len)) {
+            return buffer;
+        } else if (entry->filename_size == len+2 && !iso9660_strcmp(finalfile, entry_filename, len+2)) {
+            return buffer;
+        } else {
+            size -= entry->length;
+            buffer += entry->length;
+        }
+    }
+
+    return NULL;
+}
+
+
+// --- Public functions ---
+int iso9660_check_signature(struct volume *vol) {
+    char buf[6];
+    const uint64_t signature = ISO9660_FIRST_VOLUME_DESCRIPTOR * ISO9660_SECTOR_SIZE + 1;
+    volume_read(vol, buf, signature, 5);
+    buf[5] = '\0';
+    return !strcmp(buf, "CD001");
+}
+
+int iso9660_open(struct iso9660_file_handle *ret, struct volume *vol, const char *path) {
+    // Is the root directory cached?
+    if (!root)
+        iso9660_cache_root(vol);
+
+    ret->context.vol = *vol;
+    ret->context.root = root;
+    ret->context.root_size = root_size;
+
+    while (*path == '/')
+        ++path;
+
+    struct iso9660_directory_entry *current = root;
+    uint32_t current_size = root_size;
+
+    uint32_t next_sector = 0;
+    uint32_t next_size = 0;
+
+    char filename[ROCK_RIDGE_MAX_FILENAME];
+    while (true) {
+        char *aux = filename;
+        while (!(*path == '/' || *path == '\0'))
+            *aux++ = *path++;
+        *aux = '\0';
+
+        struct iso9660_directory_entry *entry = iso9660_find(current, current_size, filename);
+        if (!entry)
+            return 1;    // Not found :(
+
+        next_sector = entry->extent.little;
+        next_size = entry->extent_size.little;
+
+        if (*path++ == '\0')
+            break;    // Found :)
+
+        current_size = next_size;
+        current = ext_mem_alloc(current_size);
+        volume_read(vol, current, next_sector * ISO9660_SECTOR_SIZE, current_size);
+    }
+
+    ret->LBA = next_sector;
+    ret->size = next_size;
+    return 0;
+}
+
+int iso9660_read(struct iso9660_file_handle *file, void *buf, uint64_t loc, uint64_t count) {
+    volume_read(&file->context.vol, buf, file->LBA * ISO9660_SECTOR_SIZE + loc, count);
+    return 0;
+}
diff --git a/stage2/fs/iso9660.h b/stage2/fs/iso9660.h
new file mode 100644
index 00000000..ebd0cb5a
--- /dev/null
+++ b/stage2/fs/iso9660.h
@@ -0,0 +1,25 @@
+#ifndef __FS__ISO9660_H__
+#define __FS__ISO9660_H__
+
+#include <stdint.h>
+#include <lib/part.h>
+
+#define ISO9660_SECTOR_SIZE (2 << 10)
+
+struct iso9660_context {
+    struct volume vol;
+    void* root;
+    uint32_t root_size;
+};
+
+struct iso9660_file_handle {
+    struct iso9660_context context;
+    uint32_t LBA;
+    uint32_t size;
+};
+
+int iso9660_check_signature(struct volume *vol);
+int iso9660_open(struct iso9660_file_handle *ret, struct volume *vol, const char *path);
+int iso9660_read(struct iso9660_file_handle *file, void *buf, uint64_t loc, uint64_t count);
+
+#endif
diff --git a/stage2/lib/blib.c b/stage2/lib/blib.c
index fa391d9f..878c7f81 100644
--- a/stage2/lib/blib.c
+++ b/stage2/lib/blib.c
@@ -11,6 +11,8 @@ uint8_t boot_drive;
 int     boot_partition = -1;
 
 bool booted_from_pxe = false;
+bool booted_from_cd = false;
+bool stage3_already_loaded = false;
 
 bool parse_resolution(int *width, int *height, int *bpp, const char *buf) {
     int res[3] = {0};
diff --git a/stage2/lib/blib.h b/stage2/lib/blib.h
index 5f8ddf44..70333094 100644
--- a/stage2/lib/blib.h
+++ b/stage2/lib/blib.h
@@ -9,6 +9,8 @@ extern uint8_t boot_drive;
 extern int     boot_partition;
 
 extern bool booted_from_pxe;
+extern bool booted_from_cd;
+extern bool stage3_already_loaded;
 
 bool parse_resolution(int *width, int *height, int *bpp, const char *buf);
 
diff --git a/stage2/lib/config.c b/stage2/lib/config.c
index df60239f..455701d4 100644
--- a/stage2/lib/config.c
+++ b/stage2/lib/config.c
@@ -19,14 +19,16 @@ static char *config_addr;
 extern symbol stage3_addr;
 
 int init_config_disk(struct volume *part) {
-    struct file_handle stage3;
+    if (!stage3_already_loaded) {
+        struct file_handle stage3;
 
-    if (fopen(&stage3, part, "/limine.sys")
-     && fopen(&stage3, part, "/boot/limine.sys")) {
-        panic("Could not open stage 3");
-    }
+        if (fopen(&stage3, part, "/limine.sys")
+         && fopen(&stage3, part, "/boot/limine.sys")) {
+            panic("Could not open stage 3");
+        }
 
-    fread(&stage3, stage3_addr, 0, stage3.size);
+        fread(&stage3, stage3_addr, 0, stage3.size);
+    }
 
     struct file_handle f;
 
diff --git a/stage2/lib/part.c b/stage2/lib/part.c
index f88faf6c..4ef76441 100644
--- a/stage2/lib/part.c
+++ b/stage2/lib/part.c
@@ -254,7 +254,7 @@ static size_t volume_index_i = 0;
 void volume_create_index(void) {
     size_t volume_count = 0;
 
-    for (uint8_t drive = 0x80; drive < 0x8f; drive++) {
+    for (uint8_t drive = 0x80; drive; drive++) {
         struct rm_regs r = {0};
         struct bios_drive_params drive_params;
 
@@ -298,7 +298,7 @@ void volume_create_index(void) {
 
     volume_index = ext_mem_alloc(sizeof(struct volume) * volume_count);
 
-    for (uint8_t drive = 0x80; drive < 0x8f; drive++) {
+    for (uint8_t drive = 0x80; drive; drive++) {
         struct rm_regs r = {0};
         struct bios_drive_params drive_params;
 
diff --git a/stage2/lib/uri.c b/stage2/lib/uri.c
index 0a495f9d..883dc09e 100644
--- a/stage2/lib/uri.c
+++ b/stage2/lib/uri.c
@@ -94,10 +94,10 @@ static bool parse_bios_partition(char *loc, uint8_t *drive, uint8_t *partition)
 static bool uri_bios_dispatch(struct file_handle *fd, char *loc, char *path) {
     uint8_t drive, partition;
 
+    struct volume volume;
     if (!parse_bios_partition(loc, &drive, &partition))
         return false;
 
-    struct volume volume;
     if (!volume_get_by_coord(&volume, drive, partition))
         return false;
 
@@ -154,7 +154,7 @@ static bool uri_boot_dispatch(struct file_handle *fd, char *s_part, char *path)
     if (booted_from_pxe)
         return uri_tftp_dispatch(fd, s_part, path);
 
-    uint8_t partition;
+    int partition;
 
     if (s_part[0] != '\0') {
         uint64_t val = strtoui(s_part, NULL, 10);
@@ -162,12 +162,10 @@ static bool uri_boot_dispatch(struct file_handle *fd, char *s_part, char *path)
             panic("Partition number outside range 1-256");
         }
         partition = val - 1;
+    } else if (booted_from_cd || boot_partition != -1) {
+        partition = boot_partition;
     } else {
-        if (boot_partition != -1) {
-            partition = boot_partition;
-        } else {
-            panic("Boot partition information is unavailable.");
-        }
+        panic("Boot partition information is unavailable.");
     }
 
     struct volume part;
diff --git a/stage2/main.c b/stage2/main.c
index 1f3650a2..79c14b9f 100644
--- a/stage2/main.c
+++ b/stage2/main.c
@@ -20,10 +20,18 @@
 #include <pxe/pxe.h>
 #include <pxe/tftp.h>
 
-void entry(uint8_t _boot_drive, int pxe_boot) {
+enum {
+	BOOT_FROM_HDD,
+	BOOT_FROM_PXE,
+	BOOT_FROM_CD
+};
+
+void entry(uint8_t _boot_drive, int boot_from) {
     boot_drive = _boot_drive;
 
-    booted_from_pxe = pxe_boot;
+    booted_from_pxe = (boot_from == BOOT_FROM_PXE);
+    booted_from_cd = (boot_from == BOOT_FROM_CD);
+    stage3_already_loaded = booted_from_cd; // CD loads both stages
 
     mtrr_save();
 
@@ -37,20 +45,15 @@ void entry(uint8_t _boot_drive, int pxe_boot) {
     init_e820();
     init_memmap();
 
+    struct volume part;
     volume_create_index();
 
-    if (pxe_boot) {
-        pxe_init();
-        if (init_config_pxe()) {
-            panic("Failed to load config file");
-        }
-        print("Config loaded via PXE\n");
-    } else {
+    switch (boot_from) {
+    case BOOT_FROM_HDD:
         print("Boot drive: %x\n", boot_drive);
         // Look for config file.
         print("Searching for config file...\n");
         for (int i = 0; ; i++) {
-            struct volume part;
             int ret = volume_get_by_coord(&part, boot_drive, i);
             switch (ret) {
                 case INVALID_TABLE:
@@ -66,6 +69,22 @@ void entry(uint8_t _boot_drive, int pxe_boot) {
                 break;
             }
         }
+        break;
+
+    case BOOT_FROM_PXE:
+        pxe_init();
+        if (init_config_pxe()) {
+            panic("Failed to load config file");
+        }
+        print("Config loaded via PXE\n");
+        break;
+
+    case BOOT_FROM_CD:
+        boot_partition = -1;  // raw device
+        volume_get_by_coord(&part, boot_drive, boot_partition);
+        if (init_config_disk(&part))
+            panic("Failed to load config file");
+        break;
     }
 
     trace_init();
@@ -81,7 +100,7 @@ void entry(uint8_t _boot_drive, int pxe_boot) {
     if (!strcmp(proto, "stivale")) {
         stivale_load(config, cmdline);
     } else if (!strcmp(proto, "stivale2")) {
-        stivale2_load(config, cmdline, pxe_boot);
+        stivale2_load(config, cmdline, boot_from);
     } else if (!strcmp(proto, "linux")) {
         linux_load(config, cmdline);
     } else if (!strcmp(proto, "chainload")) {
diff --git a/test/limine.cfg b/test/limine.cfg
index 0af42a2c..93019355 100644
--- a/test/limine.cfg
+++ b/test/limine.cfg
@@ -5,48 +5,48 @@ GRAPHICS=yes
 MENU_RESOLUTION=1024x768
 MENU_FONT=boot:///boot/font.bin
 E9_OUTPUT=yes
-STAGE2_MAP=bios://:1/boot/stage2.map
+STAGE2_MAP=boot:///boot/stage2.map
 
 THEME_COLOURS=60000000;aa0000;00aaff;aa5500;0000aa;aa00aa;9076de;aaaaaa
 THEME_MARGIN=64
 
-BACKGROUND_PATH=bios://:1/boot/bg.bmp
+BACKGROUND_PATH=boot:///boot/bg.bmp
 
 :+Legacy
 
 ::Stivale Test
 
 PROTOCOL=stivale
-KERNEL_PATH=boot://1/boot/test.elf
+KERNEL_PATH=boot:///boot/test.elf
 KERNEL_CMDLINE=Hi! This is an example!
 
-MODULE_PATH=bios://:1/boot/test.elf
+MODULE_PATH=boot:///boot/test.elf
 MODULE_STRING=yooooo
 
-MODULE_PATH=bios://:1/boot/bg.bmp
+MODULE_PATH=boot:///boot/bg.bmp
 MODULE_STRING=yooooo
 
 ::Stivale Test (KASLR)
 
 PROTOCOL=stivale
 KASLR=yes
-KERNEL_PATH=boot://1/boot/test.elf
+KERNEL_PATH=boot:///boot/test.elf
 KERNEL_CMDLINE=Hi! This is an example!
 
-MODULE_PATH=bios://:1/boot/test.elf
+MODULE_PATH=boot:///boot/test.elf
 MODULE_STRING=yooooo
 
-MODULE_PATH=bios://:1/boot/bg.bmp
+MODULE_PATH=boot:///boot/bg.bmp
 MODULE_STRING=yooooo
 
 :Stivale2 Test
 
 PROTOCOL=stivale2
 RESOLUTION=640x480x16
-KERNEL_PATH=bios://:1/boot/test.elf
+KERNEL_PATH=boot:///boot/test.elf
 KERNEL_CMDLINE=Woah! Another example!
 
-MODULE_PATH=bios://:1/boot/bg.bmp
+MODULE_PATH=boot:///boot/bg.bmp
 MODULE_STRING=yooooo
 
 :Stivale2 Test (KASLR)
@@ -54,8 +54,18 @@ MODULE_STRING=yooooo
 PROTOCOL=stivale2
 RESOLUTION=640x480x16
 KASLR=yes
+KERNEL_PATH=boot:///boot/test.elf
+KERNEL_CMDLINE=Woah! Another example!
+
+MODULE_PATH=boot:///boot/bg.bmp
+MODULE_STRING=yooooo
+
+:Test bios://
+
+PROTOCOL=stivale2
+RESOLUTION=640x480x16
 KERNEL_PATH=bios://:1/boot/test.elf
 KERNEL_CMDLINE=Woah! Another example!
 
 MODULE_PATH=bios://:1/boot/bg.bmp
-MODULE_STRING=yooooo
+MODULE_STRING=yooooo
\ No newline at end of file
tab: 248 wrap: offon