aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hikari/asm/asm.zig146
-rw-r--r--hikari/boot/boot.zig15
-rw-r--r--hikari/boot/params.zig109
-rw-r--r--hikari/build.zig33
-rw-r--r--hikari/disk/disk.zig3
-rw-r--r--hikari/disk/gpt/constants.zig16
-rw-r--r--hikari/disk/gpt/gpt.zig15
-rw-r--r--hikari/disk/gpt/parser.zig195
-rw-r--r--hikari/disk/gpt/types.zig77
-rw-r--r--hikari/display/display.zig11
-rw-r--r--hikari/display/font.zig80
-rw-r--r--hikari/display/framebuffer.zig156
-rw-r--r--hikari/display/text.zig198
-rw-r--r--hikari/efi/constants/constants.zig9
-rw-r--r--hikari/efi/constants/graphics.zig13
-rw-r--r--hikari/efi/constants/guids.zig130
-rw-r--r--hikari/efi/constants/keyboard.zig54
-rw-r--r--hikari/efi/constants/memory.zig39
-rw-r--r--hikari/efi/constants/status.zig39
-rw-r--r--hikari/efi/constants/tables.zig23
-rw-r--r--hikari/efi/constants/unit.zig19
-rw-r--r--hikari/efi/efi.zig6
-rw-r--r--hikari/efi/protocols/block_io.zig38
-rw-r--r--hikari/efi/protocols/device_location.zig49
-rw-r--r--hikari/efi/protocols/disk_io.zig25
-rw-r--r--hikari/efi/protocols/graphics_output.zig33
-rw-r--r--hikari/efi/protocols/loaded_image.zig22
-rw-r--r--hikari/efi/protocols/protocols.zig21
-rw-r--r--hikari/efi/protocols/simple_text_input.zig18
-rw-r--r--hikari/efi/protocols/simple_text_output.zig90
-rw-r--r--hikari/efi/protocols/simple_unit_system.zig13
-rw-r--r--hikari/efi/protocols/unit.zig96
-rw-r--r--hikari/efi/services/boot.zig295
-rw-r--r--hikari/efi/services/runtime.zig116
-rw-r--r--hikari/efi/services/services.zig9
-rw-r--r--hikari/efi/services/system.zig24
-rw-r--r--hikari/efi/types/base.zig33
-rw-r--r--hikari/efi/types/block.zig18
-rw-r--r--hikari/efi/types/graphics.zig49
-rw-r--r--hikari/efi/types/input.zig8
-rw-r--r--hikari/efi/types/memory.zig42
-rw-r--r--hikari/efi/types/reset.zig8
-rw-r--r--hikari/efi/types/table.zig16
-rw-r--r--hikari/efi/types/time.zig26
-rw-r--r--hikari/efi/types/types.zig23
-rw-r--r--hikari/efi/types/unit.zig24
-rw-r--r--hikari/fs/afs/afs.zig30
-rw-r--r--hikari/fs/afs/btree.zig354
-rw-r--r--hikari/fs/afs/constants.zig66
-rw-r--r--hikari/fs/afs/reader.zig333
-rw-r--r--hikari/fs/afs/types.zig336
-rw-r--r--hikari/fs/fat32/constants.zig43
-rw-r--r--hikari/fs/fat32/fat32.zig15
-rw-r--r--hikari/fs/fat32/reader.zig303
-rw-r--r--hikari/fs/fat32/types.zig214
-rw-r--r--hikari/fs/fs.zig4
-rw-r--r--hikari/hikari.zig322
-rw-r--r--hikari/loader/elf/constants.zig85
-rw-r--r--hikari/loader/elf/elf.zig17
-rw-r--r--hikari/loader/elf/loader.zig201
-rw-r--r--hikari/loader/elf/types.zig182
-rw-r--r--hikari/loader/loader.zig3
-rw-r--r--hikari/menu/input.zig82
-rw-r--r--hikari/menu/menu.zig125
-rw-r--r--hikari/menu/renderer.zig199
-rw-r--r--hikari/menu/theme.zig55
-rw-r--r--hikari/paging/constants.zig35
-rw-r--r--hikari/paging/paging.zig21
-rw-r--r--hikari/paging/setup.zig208
-rw-r--r--hikari/paging/types.zig88
70 files changed, 5803 insertions, 0 deletions
diff --git a/hikari/asm/asm.zig b/hikari/asm/asm.zig
new file mode 100644
index 0000000..962fe60
--- /dev/null
+++ b/hikari/asm/asm.zig
@@ -0,0 +1,146 @@
+//! Hikari Assembly Operations
+
+pub inline fn read_cr3() u64 {
+ return asm volatile ("mov %cr3, %[ret]"
+ : [ret] "=r" (-> u64),
+ );
+}
+
+pub inline fn write_cr3(value: u64) void {
+ asm volatile ("mov %[value], %cr3"
+ :
+ : [value] "r" (value),
+ : .{ .memory = true });
+}
+
+pub inline fn read_cr0() u64 {
+ return asm volatile ("mov %cr0, %[ret]"
+ : [ret] "=r" (-> u64),
+ );
+}
+
+pub inline fn write_cr0(value: u64) void {
+ asm volatile ("mov %[value], %cr0"
+ :
+ : [value] "r" (value),
+ );
+}
+
+pub inline fn read_cr4() u64 {
+ return asm volatile ("mov %cr4, %[ret]"
+ : [ret] "=r" (-> u64),
+ );
+}
+
+pub inline fn write_cr4(value: u64) void {
+ asm volatile ("mov %[value], %cr4"
+ :
+ : [value] "r" (value),
+ );
+}
+
+pub inline fn read_msr(msr: u32) u64 {
+ var low: u32 = undefined;
+ var high: u32 = undefined;
+ asm volatile ("rdmsr"
+ : [low] "={eax}" (low),
+ [high] "={edx}" (high),
+ : [msr] "{ecx}" (msr),
+ );
+ return (@as(u64, high) << 32) | low;
+}
+
+pub inline fn write_msr(msr: u32, value: u64) void {
+ const low: u32 = @truncate(value);
+ const high: u32 = @truncate(value >> 32);
+ asm volatile ("wrmsr"
+ :
+ : [msr] "{ecx}" (msr),
+ [low] "{eax}" (low),
+ [high] "{edx}" (high),
+ );
+}
+
+pub inline fn halt() noreturn {
+ while (true) {
+ asm volatile ("hlt");
+ }
+}
+
+pub inline fn disable_interrupts() void {
+ asm volatile ("cli");
+}
+
+pub inline fn enable_interrupts() void {
+ asm volatile ("sti");
+}
+
+pub inline fn invlpg(address: u64) void {
+ asm volatile ("invlpg (%[addr])"
+ :
+ : [addr] "r" (address),
+ : .{ .memory = true });
+}
+
+pub inline fn flush_tlb() void {
+ const cr3 = read_cr3();
+ write_cr3(cr3);
+}
+
+pub const msr_efer: u32 = 0xC0000080;
+pub const efer_sce: u64 = 1 << 0;
+pub const efer_lme: u64 = 1 << 8;
+pub const efer_lma: u64 = 1 << 10;
+pub const efer_nxe: u64 = 1 << 11;
+
+pub const cr0_pe: u64 = 1 << 0;
+pub const cr0_mp: u64 = 1 << 1;
+pub const cr0_em: u64 = 1 << 2;
+pub const cr0_ts: u64 = 1 << 3;
+pub const cr0_et: u64 = 1 << 4;
+pub const cr0_ne: u64 = 1 << 5;
+pub const cr0_wp: u64 = 1 << 16;
+pub const cr0_am: u64 = 1 << 18;
+pub const cr0_nw: u64 = 1 << 29;
+pub const cr0_cd: u64 = 1 << 30;
+pub const cr0_pg: u64 = 1 << 31;
+
+pub const cr4_vme: u64 = 1 << 0;
+pub const cr4_pvi: u64 = 1 << 1;
+pub const cr4_tsd: u64 = 1 << 2;
+pub const cr4_de: u64 = 1 << 3;
+pub const cr4_pse: u64 = 1 << 4;
+pub const cr4_pae: u64 = 1 << 5;
+pub const cr4_mce: u64 = 1 << 6;
+pub const cr4_pge: u64 = 1 << 7;
+pub const cr4_pce: u64 = 1 << 8;
+pub const cr4_osfxsr: u64 = 1 << 9;
+pub const cr4_osxmmexcpt: u64 = 1 << 10;
+pub const cr4_fsgsbase: u64 = 1 << 16;
+pub const cr4_osxsave: u64 = 1 << 18;
+pub const cr4_smep: u64 = 1 << 20;
+pub const cr4_smap: u64 = 1 << 21;
+
+pub fn jump_to_kernel(
+ entry_point: u64,
+ stack_top: u64,
+ boot_params: u64,
+ pml4_address: u64,
+) noreturn {
+ disable_interrupts();
+
+ write_cr3(pml4_address);
+
+ asm volatile (
+ \\mov %[stack], %%rsp
+ \\mov %[params], %%rdi
+ \\xor %%rbp, %%rbp
+ \\jmp *%[entry]
+ :
+ : [stack] "r" (stack_top),
+ [params] "r" (boot_params),
+ [entry] "r" (entry_point),
+ : .{ .memory = true });
+
+ unreachable;
+}
diff --git a/hikari/boot/boot.zig b/hikari/boot/boot.zig
new file mode 100644
index 0000000..979dc94
--- /dev/null
+++ b/hikari/boot/boot.zig
@@ -0,0 +1,15 @@
+//! Hikari Boot Subsystem
+
+pub const params = @import("params.zig");
+
+pub const BootParams = params.BootParams;
+pub const FramebufferInfo = params.FramebufferInfo;
+pub const MemoryMapInfo = params.MemoryMapInfo;
+pub const MemoryRegion = params.MemoryRegion;
+pub const MemoryType = params.MemoryType;
+pub const KernelInfo = params.KernelInfo;
+pub const AcpiInfo = params.AcpiInfo;
+pub const PixelFormat = params.PixelFormat;
+
+pub const boot_params_magic = params.boot_params_magic;
+pub const boot_params_version = params.boot_params_version;
diff --git a/hikari/boot/params.zig b/hikari/boot/params.zig
new file mode 100644
index 0000000..4494cd0
--- /dev/null
+++ b/hikari/boot/params.zig
@@ -0,0 +1,109 @@
+//! Hikari Boot Parameters
+//!
+//! This structure is passed to the Mirai kernel at boot time.
+
+const paging = @import("../paging/paging.zig");
+
+pub const boot_params_magic: u64 = 0x4152494D41424B41; // "AKBAMIRA"
+pub const boot_params_version: u32 = 1;
+
+pub const BootParams = extern struct {
+ magic: u64,
+ version: u32,
+ size: u32,
+
+ framebuffer: FramebufferInfo,
+ memory_map: MemoryMapInfo,
+ kernel: KernelInfo,
+ acpi: AcpiInfo,
+ boot_time: u64,
+
+ reserved: [256]u8,
+
+ pub fn initialize() BootParams {
+ var params: BootParams = undefined;
+ params.magic = boot_params_magic;
+ params.version = boot_params_version;
+ params.size = @sizeOf(BootParams);
+
+ var i: usize = 0;
+ while (i < 256) : (i += 1) {
+ params.reserved[i] = 0;
+ }
+
+ return params;
+ }
+
+ pub fn is_valid(self: *const BootParams) bool {
+ return self.magic == boot_params_magic and
+ self.version == boot_params_version and
+ self.size == @sizeOf(BootParams);
+ }
+};
+
+pub const FramebufferInfo = extern struct {
+ base: u64,
+ size: u64,
+ width: u32,
+ height: u32,
+ stride: u32,
+ pixel_format: PixelFormat,
+ red_mask_size: u8,
+ red_mask_shift: u8,
+ green_mask_size: u8,
+ green_mask_shift: u8,
+ blue_mask_size: u8,
+ blue_mask_shift: u8,
+ reserved: [2]u8,
+};
+
+pub const PixelFormat = enum(u32) {
+ rgb = 0,
+ bgr = 1,
+ bitmask = 2,
+ unknown = 255,
+};
+
+pub const MemoryMapInfo = extern struct {
+ entries: u64,
+ entry_count: u32,
+ entry_size: u32,
+ descriptor_version: u32,
+ reserved: u32,
+};
+
+pub const MemoryRegion = extern struct {
+ base: u64,
+ size: u64,
+ region_type: MemoryType,
+ attributes: u64,
+};
+
+pub const MemoryType = enum(u32) {
+ usable = 0,
+ reserved = 1,
+ acpi_reclaimable = 2,
+ acpi_nvs = 3,
+ bad_memory = 4,
+ bootloader_reclaimable = 5,
+ kernel = 6,
+ framebuffer = 7,
+};
+
+pub const KernelInfo = extern struct {
+ physical_base: u64,
+ virtual_base: u64,
+ size: u64,
+ entry_point: u64,
+ pml4_address: u64,
+ physmap_base: u64,
+ physmap_size: u64,
+ stack_top: u64,
+ stack_size: u64,
+};
+
+pub const AcpiInfo = extern struct {
+ rsdp_address: u64,
+ rsdp_version: u32,
+ reserved: u32,
+};
diff --git a/hikari/build.zig b/hikari/build.zig
new file mode 100644
index 0000000..57eab76
--- /dev/null
+++ b/hikari/build.zig
@@ -0,0 +1,33 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const hikari_module = b.createModule(.{
+ .root_source_file = b.path("hikari.zig"),
+ .target = b.resolveTargetQuery(.{
+ .cpu_arch = .x86_64,
+ .os_tag = .uefi,
+ .abi = .msvc,
+ }),
+ .optimize = .ReleaseSafe,
+ });
+
+ const hikari = b.addExecutable(.{
+ .name = "hikari",
+ .root_module = hikari_module,
+ });
+
+ hikari.entry = .{ .symbol_name = "hikari" };
+
+ b.installArtifact(hikari);
+
+ const copy_step = b.addInstallFile(
+ hikari.getEmittedBin(),
+ "BOOTX64.EFI",
+ );
+ copy_step.step.dependOn(&hikari.step);
+
+ const install_step = b.step("install", "Build and install Hikari");
+ install_step.dependOn(&copy_step.step);
+
+ b.default_step = install_step;
+}
diff --git a/hikari/disk/disk.zig b/hikari/disk/disk.zig
new file mode 100644
index 0000000..66f4603
--- /dev/null
+++ b/hikari/disk/disk.zig
@@ -0,0 +1,3 @@
+//! Hikari Disk Subsystem
+
+pub const gpt = @import("gpt/gpt.zig");
diff --git a/hikari/disk/gpt/constants.zig b/hikari/disk/gpt/constants.zig
new file mode 100644
index 0000000..5bc2c91
--- /dev/null
+++ b/hikari/disk/gpt/constants.zig
@@ -0,0 +1,16 @@
+//! Hikari GPT Constants
+
+pub const signature: u64 = 0x5452415020494645; // "EFI PART"
+pub const revision_1_0: u32 = 0x00010000;
+
+pub const header_size_minimum: u32 = 92;
+pub const header_lba: u64 = 1;
+
+pub const partition_entry_size_minimum: u32 = 128;
+pub const partition_entries_start_lba: u64 = 2;
+pub const partition_entries_count_maximum: u32 = 128;
+
+pub const partition_identity_length: usize = 36;
+
+pub const protective_mbr_signature: u16 = 0xAA55;
+pub const protective_mbr_partition_type: u8 = 0xEE;
diff --git a/hikari/disk/gpt/gpt.zig b/hikari/disk/gpt/gpt.zig
new file mode 100644
index 0000000..e59e07c
--- /dev/null
+++ b/hikari/disk/gpt/gpt.zig
@@ -0,0 +1,15 @@
+//! Hikari GPT (GUID Partition Table)
+
+pub const constants = @import("constants.zig");
+pub const types = @import("types.zig");
+pub const parser = @import("parser.zig");
+
+pub const Header = types.Header;
+pub const PartitionEntry = types.PartitionEntry;
+pub const PartitionAttributes = types.PartitionAttributes;
+pub const ProtectiveMbr = types.ProtectiveMbr;
+pub const MbrPartitionRecord = types.MbrPartitionRecord;
+
+pub const Parser = parser.Parser;
+pub const ParseError = parser.ParseError;
+pub const calculate_crc32 = parser.calculate_crc32;
diff --git a/hikari/disk/gpt/parser.zig b/hikari/disk/gpt/parser.zig
new file mode 100644
index 0000000..364a27f
--- /dev/null
+++ b/hikari/disk/gpt/parser.zig
@@ -0,0 +1,195 @@
+//! Hikari GPT Parser
+
+const efi = @import("../../efi/efi.zig");
+const constants = @import("constants.zig");
+const types = @import("types.zig");
+
+pub const ParseError = error{
+ read_failed,
+ invalid_protective_mbr,
+ invalid_signature,
+ invalid_revision,
+ invalid_header_size,
+ invalid_header_crc,
+ invalid_entries_crc,
+ no_partitions,
+ partition_not_found,
+};
+
+pub const Parser = struct {
+ block_io: *efi.protocols.BlockIoProtocol,
+ block_size: u32,
+ header: types.Header,
+ entries_buffer: [*]u8,
+ entries_count: u32,
+ entry_size: u32,
+
+ pub fn initialize(block_io: *efi.protocols.BlockIoProtocol, boot_services: *efi.services.BootServices) ParseError!Parser {
+ const block_size = block_io.media.block_size;
+
+ var header_buffer: [512]u8 align(8) = undefined;
+ const header_read_status = block_io.read_blocks(
+ block_io,
+ block_io.media.media_id,
+ constants.header_lba,
+ block_size,
+ &header_buffer,
+ );
+
+ if (efi.types.is_error(header_read_status)) {
+ return ParseError.read_failed;
+ }
+
+ const header: *const types.Header = @ptrCast(@alignCast(&header_buffer));
+
+ if (header.signature != constants.signature) {
+ return ParseError.invalid_signature;
+ }
+
+ if (header.revision < constants.revision_1_0) {
+ return ParseError.invalid_revision;
+ }
+
+ if (header.header_size < constants.header_size_minimum) {
+ return ParseError.invalid_header_size;
+ }
+
+ const stored_header_crc = header.header_crc32;
+ var header_for_crc = header_buffer;
+ const header_ptr: *types.Header = @ptrCast(@alignCast(&header_for_crc));
+ header_ptr.header_crc32 = 0;
+
+ const calculated_header_crc = calculate_crc32(header_for_crc[0..header.header_size]);
+ if (calculated_header_crc != stored_header_crc) {
+ return ParseError.invalid_header_crc;
+ }
+
+ if (header.partition_entries_count == 0) {
+ return ParseError.no_partitions;
+ }
+
+ const entries_total_size = header.partition_entries_count * header.partition_entry_size;
+ const entries_blocks = (entries_total_size + block_size - 1) / block_size;
+ const entries_buffer_size = entries_blocks * block_size;
+
+ var entries_buffer: [*]align(8) u8 = undefined;
+ const alloc_status = boot_services.allocate_pool(
+ .loader_data,
+ entries_buffer_size,
+ &entries_buffer,
+ );
+
+ if (efi.types.is_error(alloc_status)) {
+ return ParseError.read_failed;
+ }
+
+ const entries_read_status = block_io.read_blocks(
+ block_io,
+ block_io.media.media_id,
+ header.partition_entries_lba,
+ entries_buffer_size,
+ entries_buffer,
+ );
+
+ if (efi.types.is_error(entries_read_status)) {
+ return ParseError.read_failed;
+ }
+
+ const calculated_entries_crc = calculate_crc32(entries_buffer[0..entries_total_size]);
+ if (calculated_entries_crc != header.partition_entries_crc32) {
+ return ParseError.invalid_entries_crc;
+ }
+
+ return Parser{
+ .block_io = block_io,
+ .block_size = block_size,
+ .header = header.*,
+ .entries_buffer = entries_buffer,
+ .entries_count = header.partition_entries_count,
+ .entry_size = header.partition_entry_size,
+ };
+ }
+
+ pub fn find_partition_by_type(self: *const Parser, type_guid: efi.types.Guid) ?*const types.PartitionEntry {
+ var index: u32 = 0;
+ while (index < self.entries_count) : (index += 1) {
+ const entry = self.get_partition_entry(index);
+ if (entry.is_empty()) {
+ continue;
+ }
+ if (entry.is_type(type_guid)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ pub fn find_partition_by_index(self: *const Parser, index: u32) ?*const types.PartitionEntry {
+ if (index >= self.entries_count) {
+ return null;
+ }
+ const entry = self.get_partition_entry(index);
+ if (entry.is_empty()) {
+ return null;
+ }
+ return entry;
+ }
+
+ pub fn get_partition_entry(self: *const Parser, index: u32) *const types.PartitionEntry {
+ const offset = index * self.entry_size;
+ return @ptrCast(@alignCast(self.entries_buffer + offset));
+ }
+
+ pub fn count_valid_partitions(self: *const Parser) u32 {
+ var count: u32 = 0;
+ var index: u32 = 0;
+ while (index < self.entries_count) : (index += 1) {
+ const entry = self.get_partition_entry(index);
+ if (!entry.is_empty()) {
+ count += 1;
+ }
+ }
+ return count;
+ }
+
+ pub fn get_disk_guid(self: *const Parser) efi.types.Guid {
+ return self.header.disk_guid;
+ }
+
+ pub fn get_first_usable_lba(self: *const Parser) u64 {
+ return self.header.first_usable_lba;
+ }
+
+ pub fn get_last_usable_lba(self: *const Parser) u64 {
+ return self.header.last_usable_lba;
+ }
+};
+
+const crc32_table: [256]u32 = generate_crc32_table();
+
+fn generate_crc32_table() [256]u32 {
+ var table: [256]u32 = undefined;
+ var index: u32 = 0;
+ while (index < 256) : (index += 1) {
+ var crc: u32 = index;
+ var bit: u32 = 0;
+ while (bit < 8) : (bit += 1) {
+ if ((crc & 1) != 0) {
+ crc = (crc >> 1) ^ 0xEDB88320;
+ } else {
+ crc = crc >> 1;
+ }
+ }
+ table[index] = crc;
+ }
+ return table;
+}
+
+pub fn calculate_crc32(data: []const u8) u32 {
+ var crc: u32 = 0xFFFFFFFF;
+ for (data) |byte| {
+ const table_index = (crc ^ byte) & 0xFF;
+ crc = (crc >> 8) ^ crc32_table[table_index];
+ }
+ return crc ^ 0xFFFFFFFF;
+}
diff --git a/hikari/disk/gpt/types.zig b/hikari/disk/gpt/types.zig
new file mode 100644
index 0000000..cc6246c
--- /dev/null
+++ b/hikari/disk/gpt/types.zig
@@ -0,0 +1,77 @@
+//! Hikari GPT Types
+
+const efi = @import("../../efi/efi.zig");
+const constants = @import("constants.zig");
+
+pub const Header = extern struct {
+ signature: u64,
+ revision: u32,
+ header_size: u32,
+ header_crc32: u32,
+ reserved: u32,
+ current_lba: u64,
+ backup_lba: u64,
+ first_usable_lba: u64,
+ last_usable_lba: u64,
+ disk_guid: efi.types.Guid,
+ partition_entries_lba: u64,
+ partition_entries_count: u32,
+ partition_entry_size: u32,
+ partition_entries_crc32: u32,
+};
+
+pub const PartitionEntry = extern struct {
+ partition_type_guid: efi.types.Guid,
+ unique_partition_guid: efi.types.Guid,
+ starting_lba: u64,
+ ending_lba: u64,
+ attributes: PartitionAttributes,
+ partition_identity: [constants.partition_identity_length]u16,
+
+ pub fn is_empty(self: *const PartitionEntry) bool {
+ return self.partition_type_guid.time_low == 0 and
+ self.partition_type_guid.time_mid == 0 and
+ self.partition_type_guid.time_high_and_version == 0 and
+ @as(u64, @bitCast(self.partition_type_guid.clock_sequence_and_node)) == 0;
+ }
+
+ pub fn is_type(self: *const PartitionEntry, type_guid: efi.types.Guid) bool {
+ return self.partition_type_guid.equals(type_guid);
+ }
+
+ pub fn size_in_blocks(self: *const PartitionEntry) u64 {
+ if (self.ending_lba < self.starting_lba) {
+ return 0;
+ }
+ return self.ending_lba - self.starting_lba + 1;
+ }
+
+ pub fn size_in_bytes(self: *const PartitionEntry, block_size: u32) u64 {
+ return self.size_in_blocks() * block_size;
+ }
+};
+
+pub const PartitionAttributes = packed struct(u64) {
+ required_for_platform: bool,
+ efi_firmware_ignore: bool,
+ legacy_bios_bootable: bool,
+ reserved_bits_3_47: u45,
+ guid_specific_bits_48_63: u16,
+};
+
+pub const ProtectiveMbr = extern struct {
+ bootstrap_code: [440]u8,
+ disk_signature: u32,
+ reserved: u16,
+ partitions: [4]MbrPartitionRecord,
+ boot_signature: u16,
+};
+
+pub const MbrPartitionRecord = extern struct {
+ boot_indicator: u8,
+ starting_chs: [3]u8,
+ os_type: u8,
+ ending_chs: [3]u8,
+ starting_lba: u32,
+ size_in_lba: u32,
+};
diff --git a/hikari/display/display.zig b/hikari/display/display.zig
new file mode 100644
index 0000000..de6e303
--- /dev/null
+++ b/hikari/display/display.zig
@@ -0,0 +1,11 @@
+//! Hikari Display Subsystem
+
+pub const framebuffer = @import("framebuffer.zig");
+pub const font = @import("font.zig");
+pub const text = @import("text.zig");
+
+pub const Framebuffer = framebuffer.Framebuffer;
+pub const Color = framebuffer.Color;
+pub const Font = font.Font;
+pub const Psf2Header = font.Psf2Header;
+pub const TextRenderer = text.TextRenderer;
diff --git a/hikari/display/font.zig b/hikari/display/font.zig
new file mode 100644
index 0000000..8abb94c
--- /dev/null
+++ b/hikari/display/font.zig
@@ -0,0 +1,80 @@
+//! Hikari PSF2 Font
+
+pub const psf2_magic: u32 = 0x864AB572;
+
+pub const Psf2Header = extern struct {
+ magic: u32,
+ version: u32,
+ header_size: u32,
+ flags: u32,
+ glyph_count: u32,
+ glyph_size: u32,
+ height: u32,
+ width: u32,
+
+ pub fn is_valid(self: *const Psf2Header) bool {
+ return self.magic == psf2_magic;
+ }
+
+ pub fn has_unicode_table(self: *const Psf2Header) bool {
+ return (self.flags & 0x01) != 0;
+ }
+
+ pub fn bytes_per_row(self: *const Psf2Header) u32 {
+ return (self.width + 7) / 8;
+ }
+};
+
+pub const Font = struct {
+ header: *const Psf2Header,
+ glyphs: [*]const u8,
+ glyph_count: u32,
+ glyph_size: u32,
+ width: u32,
+ height: u32,
+ bytes_per_row: u32,
+
+ pub fn load(data: [*]const u8, size: u64) ?Font {
+ if (size < @sizeOf(Psf2Header)) {
+ return null;
+ }
+
+ const header: *const Psf2Header = @ptrCast(@alignCast(data));
+
+ if (!header.is_valid()) {
+ return null;
+ }
+
+ const glyphs = data + header.header_size;
+
+ return Font{
+ .header = header,
+ .glyphs = glyphs,
+ .glyph_count = header.glyph_count,
+ .glyph_size = header.glyph_size,
+ .width = header.width,
+ .height = header.height,
+ .bytes_per_row = header.bytes_per_row(),
+ };
+ }
+
+ pub fn get_glyph(self: *const Font, codepoint: u32) ?[*]const u8 {
+ if (codepoint >= self.glyph_count) {
+ return null;
+ }
+
+ return self.glyphs + (codepoint * self.glyph_size);
+ }
+
+ pub fn get_glyph_pixel(self: *const Font, glyph: [*]const u8, x: u32, y: u32) bool {
+ if (x >= self.width or y >= self.height) {
+ return false;
+ }
+
+ const row = glyph + (y * self.bytes_per_row);
+ const byte_index = x / 8;
+ const bit_index: u3 = @truncate(7 - (x % 8));
+
+ return ((row[byte_index] >> bit_index) & 1) != 0;
+ }
+};
diff --git a/hikari/display/framebuffer.zig b/hikari/display/framebuffer.zig
new file mode 100644
index 0000000..cf7c0ab
--- /dev/null
+++ b/hikari/display/framebuffer.zig
@@ -0,0 +1,156 @@
+//! Hikari Framebuffer
+
+const efi = @import("../efi/efi.zig");
+
+pub const Framebuffer = struct {
+ base: [*]u32,
+ width: u32,
+ height: u32,
+ stride: u32,
+ pixel_format: efi.types.graphics.PixelFormat,
+
+ pub fn initialize(gop: *efi.protocols.GraphicsOutputProtocol) Framebuffer {
+ const mode = gop.mode;
+ const info = mode.info;
+
+ return Framebuffer{
+ .base = @ptrFromInt(mode.framebuffer_base),
+ .width = info.horizontal_resolution,
+ .height = info.vertical_resolution,
+ .stride = info.pixels_per_scan_line,
+ .pixel_format = info.pixel_format,
+ };
+ }
+
+ pub fn put_pixel(self: *Framebuffer, x: u32, y: u32, color: Color) void {
+ if (x >= self.width or y >= self.height) {
+ return;
+ }
+
+ const offset = y * self.stride + x;
+ self.base[offset] = color.to_pixel(self.pixel_format);
+ }
+
+ pub fn fill_rect(self: *Framebuffer, x: u32, y: u32, w: u32, h: u32, color: Color) void {
+ const pixel = color.to_pixel(self.pixel_format);
+ const x_end = if (x + w > self.width) self.width else x + w;
+ const y_end = if (y + h > self.height) self.height else y + h;
+
+ var py = y;
+ while (py < y_end) : (py += 1) {
+ var px = x;
+ while (px < x_end) : (px += 1) {
+ const offset = py * self.stride + px;
+ self.base[offset] = pixel;
+ }
+ }
+ }
+
+ pub fn clear(self: *Framebuffer, color: Color) void {
+ self.fill_rect(0, 0, self.width, self.height, color);
+ }
+
+ pub fn draw_horizontal_line(self: *Framebuffer, x: u32, y: u32, length: u32, color: Color) void {
+ if (y >= self.height) {
+ return;
+ }
+
+ const pixel = color.to_pixel(self.pixel_format);
+ const x_end = if (x + length > self.width) self.width else x + length;
+ const row_offset = y * self.stride;
+
+ var px = x;
+ while (px < x_end) : (px += 1) {
+ self.base[row_offset + px] = pixel;
+ }
+ }
+
+ pub fn draw_vertical_line(self: *Framebuffer, x: u32, y: u32, length: u32, color: Color) void {
+ if (x >= self.width) {
+ return;
+ }
+
+ const pixel = color.to_pixel(self.pixel_format);
+ const y_end = if (y + length > self.height) self.height else y + length;
+
+ var py = y;
+ while (py < y_end) : (py += 1) {
+ self.base[py * self.stride + x] = pixel;
+ }
+ }
+
+ pub fn draw_rect(self: *Framebuffer, x: u32, y: u32, w: u32, h: u32, color: Color) void {
+ self.draw_horizontal_line(x, y, w, color);
+ self.draw_horizontal_line(x, y + h - 1, w, color);
+ self.draw_vertical_line(x, y, h, color);
+ self.draw_vertical_line(x + w - 1, y, h, color);
+ }
+
+ pub fn copy_rect(self: *Framebuffer, src_x: u32, src_y: u32, dst_x: u32, dst_y: u32, w: u32, h: u32) void {
+ if (src_y < dst_y) {
+ var row: u32 = h;
+ while (row > 0) {
+ row -= 1;
+ self.copy_row(src_x, src_y + row, dst_x, dst_y + row, w);
+ }
+ } else {
+ var row: u32 = 0;
+ while (row < h) : (row += 1) {
+ self.copy_row(src_x, src_y + row, dst_x, dst_y + row, w);
+ }
+ }
+ }
+
+ fn copy_row(self: *Framebuffer, src_x: u32, src_y: u32, dst_x: u32, dst_y: u32, w: u32) void {
+ const src_offset = src_y * self.stride + src_x;
+ const dst_offset = dst_y * self.stride + dst_x;
+
+ if (src_x < dst_x) {
+ var i: u32 = w;
+ while (i > 0) {
+ i -= 1;
+ self.base[dst_offset + i] = self.base[src_offset + i];
+ }
+ } else {
+ var i: u32 = 0;
+ while (i < w) : (i += 1) {
+ self.base[dst_offset + i] = self.base[src_offset + i];
+ }
+ }
+ }
+};
+
+pub const Color = struct {
+ r: u8,
+ g: u8,
+ b: u8,
+ a: u8,
+
+ pub fn rgb(r: u8, g: u8, b: u8) Color {
+ return Color{ .r = r, .g = g, .b = b, .a = 255 };
+ }
+
+ pub fn rgba(r: u8, g: u8, b: u8, a: u8) Color {
+ return Color{ .r = r, .g = g, .b = b, .a = a };
+ }
+
+ pub fn to_pixel(self: Color, format: efi.types.graphics.PixelFormat) u32 {
+ return switch (format) {
+ .rgb => (@as(u32, self.r) << 16) | (@as(u32, self.g) << 8) | self.b,
+ .bgr => (@as(u32, self.b) << 16) | (@as(u32, self.g) << 8) | self.r,
+ else => (@as(u32, self.r) << 16) | (@as(u32, self.g) << 8) | self.b,
+ };
+ }
+
+ pub const black = Color.rgb(0, 0, 0);
+ pub const white = Color.rgb(255, 255, 255);
+ pub const red = Color.rgb(255, 0, 0);
+ pub const green = Color.rgb(0, 255, 0);
+ pub const blue = Color.rgb(0, 0, 255);
+ pub const cyan = Color.rgb(0, 255, 255);
+ pub const magenta = Color.rgb(255, 0, 255);
+ pub const yellow = Color.rgb(255, 255, 0);
+ pub const gray = Color.rgb(128, 128, 128);
+ pub const dark_gray = Color.rgb(64, 64, 64);
+ pub const light_gray = Color.rgb(192, 192, 192);
+};
diff --git a/hikari/display/text.zig b/hikari/display/text.zig
new file mode 100644
index 0000000..806b122
--- /dev/null
+++ b/hikari/display/text.zig
@@ -0,0 +1,198 @@
+//! Hikari Text Renderer
+
+const framebuffer = @import("framebuffer.zig");
+const font_mod = @import("font.zig");
+
+pub const TextRenderer = struct {
+ fb: *framebuffer.Framebuffer,
+ font: *const font_mod.Font,
+ cursor_x: u32,
+ cursor_y: u32,
+ fg_color: framebuffer.Color,
+ bg_color: framebuffer.Color,
+ columns: u32,
+ rows: u32,
+
+ pub fn initialize(
+ fb: *framebuffer.Framebuffer,
+ font: *const font_mod.Font,
+ ) TextRenderer {
+ return TextRenderer{
+ .fb = fb,
+ .font = font,
+ .cursor_x = 0,
+ .cursor_y = 0,
+ .fg_color = framebuffer.Color.white,
+ .bg_color = framebuffer.Color.black,
+ .columns = fb.width / font.width,
+ .rows = fb.height / font.height,
+ };
+ }
+
+ pub fn set_colors(self: *TextRenderer, fg: framebuffer.Color, bg: framebuffer.Color) void {
+ self.fg_color = fg;
+ self.bg_color = bg;
+ }
+
+ pub fn set_cursor(self: *TextRenderer, col: u32, row: u32) void {
+ self.cursor_x = col;
+ self.cursor_y = row;
+ }
+
+ pub fn draw_char(self: *TextRenderer, codepoint: u32) void {
+ const glyph = self.font.get_glyph(codepoint) orelse self.font.get_glyph('?') orelse return;
+
+ const screen_x = self.cursor_x * self.font.width;
+ const screen_y = self.cursor_y * self.font.height;
+
+ var y: u32 = 0;
+ while (y < self.font.height) : (y += 1) {
+ var x: u32 = 0;
+ while (x < self.font.width) : (x += 1) {
+ const pixel_on = self.font.get_glyph_pixel(glyph, x, y);
+ const color = if (pixel_on) self.fg_color else self.bg_color;
+ self.fb.put_pixel(screen_x + x, screen_y + y, color);
+ }
+ }
+ }
+
+ pub fn draw_char_at(self: *TextRenderer, codepoint: u32, col: u32, row: u32) void {
+ const old_x = self.cursor_x;
+ const old_y = self.cursor_y;
+ self.cursor_x = col;
+ self.cursor_y = row;
+ self.draw_char(codepoint);
+ self.cursor_x = old_x;
+ self.cursor_y = old_y;
+ }
+
+ pub fn put_char(self: *TextRenderer, c: u8) void {
+ switch (c) {
+ '\n' => {
+ self.cursor_x = 0;
+ self.cursor_y += 1;
+ if (self.cursor_y >= self.rows) {
+ self.scroll_up();
+ self.cursor_y = self.rows - 1;
+ }
+ },
+ '\r' => {
+ self.cursor_x = 0;
+ },
+ '\t' => {
+ const tab_stop = 8;
+ self.cursor_x = ((self.cursor_x / tab_stop) + 1) * tab_stop;
+ if (self.cursor_x >= self.columns) {
+ self.cursor_x = 0;
+ self.cursor_y += 1;
+ if (self.cursor_y >= self.rows) {
+ self.scroll_up();
+ self.cursor_y = self.rows - 1;
+ }
+ }
+ },
+ 0x08 => {
+ if (self.cursor_x > 0) {
+ self.cursor_x -= 1;
+ }
+ },
+ else => {
+ self.draw_char(c);
+ self.cursor_x += 1;
+ if (self.cursor_x >= self.columns) {
+ self.cursor_x = 0;
+ self.cursor_y += 1;
+ if (self.cursor_y >= self.rows) {
+ self.scroll_up();
+ self.cursor_y = self.rows - 1;
+ }
+ }
+ },
+ }
+ }
+
+ pub fn print(self: *TextRenderer, str: []const u8) void {
+ for (str) |c| {
+ self.put_char(c);
+ }
+ }
+
+ pub fn print_line(self: *TextRenderer, str: []const u8) void {
+ self.print(str);
+ self.put_char('\n');
+ }
+
+ pub fn scroll_up(self: *TextRenderer) void {
+ const line_height = self.font.height;
+ const scroll_pixels = (self.rows - 1) * line_height;
+
+ self.fb.copy_rect(0, line_height, 0, 0, self.fb.width, scroll_pixels);
+
+ self.fb.fill_rect(
+ 0,
+ scroll_pixels,
+ self.fb.width,
+ line_height,
+ self.bg_color,
+ );
+ }
+
+ pub fn clear_screen(self: *TextRenderer) void {
+ self.fb.clear(self.bg_color);
+ self.cursor_x = 0;
+ self.cursor_y = 0;
+ }
+
+ pub fn clear_line(self: *TextRenderer, row: u32) void {
+ if (row >= self.rows) {
+ return;
+ }
+
+ self.fb.fill_rect(
+ 0,
+ row * self.font.height,
+ self.fb.width,
+ self.font.height,
+ self.bg_color,
+ );
+ }
+
+ pub fn print_u64(self: *TextRenderer, value: u64) void {
+ var buf: [20]u8 = undefined;
+ var i: usize = 0;
+ var v = value;
+
+ if (v == 0) {
+ self.put_char('0');
+ return;
+ }
+
+ while (v > 0) {
+ buf[i] = @truncate((v % 10) + '0');
+ v /= 10;
+ i += 1;
+ }
+
+ while (i > 0) {
+ i -= 1;
+ self.put_char(buf[i]);
+ }
+ }
+
+ pub fn print_hex(self: *TextRenderer, value: u64) void {
+ const hex_chars = "0123456789ABCDEF";
+ self.print("0x");
+
+ var started = false;
+ var shift: u6 = 60;
+ while (true) {
+ const nibble: u4 = @truncate((value >> shift) & 0xF);
+ if (nibble != 0 or started or shift == 0) {
+ self.put_char(hex_chars[nibble]);
+ started = true;
+ }
+ if (shift == 0) break;
+ shift -= 4;
+ }
+ }
+};
diff --git a/hikari/efi/constants/constants.zig b/hikari/efi/constants/constants.zig
new file mode 100644
index 0000000..409651a
--- /dev/null
+++ b/hikari/efi/constants/constants.zig
@@ -0,0 +1,9 @@
+//! Hikari EFI Constants
+
+pub const status = @import("status.zig");
+pub const memory = @import("memory.zig");
+pub const guids = @import("guids.zig");
+pub const unit = @import("unit.zig");
+pub const keyboard = @import("keyboard.zig");
+pub const graphics = @import("graphics.zig");
+pub const tables = @import("tables.zig");
diff --git a/hikari/efi/constants/graphics.zig b/hikari/efi/constants/graphics.zig
new file mode 100644
index 0000000..c1055f0
--- /dev/null
+++ b/hikari/efi/constants/graphics.zig
@@ -0,0 +1,13 @@
+//! Hikari EFI Graphics Constants
+
+pub const pixel_format_rgb: u32 = 0;
+pub const pixel_format_bgr: u32 = 1;
+pub const pixel_format_bitmask: u32 = 2;
+pub const pixel_format_blt_only: u32 = 3;
+
+pub const blt_operation_video_fill: u32 = 0;
+pub const blt_operation_video_to_buffer: u32 = 1;
+pub const blt_operation_buffer_to_video: u32 = 2;
+pub const blt_operation_video_to_video: u32 = 3;
+
+pub const graphics_output_protocol_revision: u32 = 0x00010000;
diff --git a/hikari/efi/constants/guids.zig b/hikari/efi/constants/guids.zig
new file mode 100644
index 0000000..a50117f
--- /dev/null
+++ b/hikari/efi/constants/guids.zig
@@ -0,0 +1,130 @@
+//! Hikari EFI Protocol GUIDs
+
+const types = @import("../types/types.zig");
+const Guid = types.Guid;
+
+pub const loaded_image_protocol = Guid{
+ .time_low = 0x5B1B31A1,
+ .time_mid = 0x9562,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B },
+};
+
+pub const simple_unit_system_protocol = Guid{
+ .time_low = 0x0964e5b22,
+ .time_mid = 0x6459,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const unit_info = Guid{
+ .time_low = 0x09576e92,
+ .time_mid = 0x6d3f,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const unit_system_info = Guid{
+ .time_low = 0x09576e93,
+ .time_mid = 0x6d3f,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const graphics_output_protocol = Guid{
+ .time_low = 0x9042a9de,
+ .time_mid = 0x23dc,
+ .time_high_and_version = 0x4a38,
+ .clock_sequence_and_node = .{ 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a },
+};
+
+pub const block_io_protocol = Guid{
+ .time_low = 0x964e5b21,
+ .time_mid = 0x6459,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const block_io2_protocol = Guid{
+ .time_low = 0xa77b2472,
+ .time_mid = 0xe282,
+ .time_high_and_version = 0x4e9f,
+ .clock_sequence_and_node = .{ 0xa2, 0x45, 0xc2, 0xc0, 0xe2, 0x7b, 0xbc, 0xc1 },
+};
+
+pub const disk_io_protocol = Guid{
+ .time_low = 0xce345171,
+ .time_mid = 0xba0b,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x4f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const disk_io2_protocol = Guid{
+ .time_low = 0x151c8eae,
+ .time_mid = 0x7f2c,
+ .time_high_and_version = 0x472c,
+ .clock_sequence_and_node = .{ 0x9e, 0x54, 0x98, 0x28, 0x19, 0x4f, 0x6a, 0x88 },
+};
+
+pub const device_location_protocol = Guid{
+ .time_low = 0x09576e91,
+ .time_mid = 0x6d3f,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const simple_text_input_protocol = Guid{
+ .time_low = 0x387477c1,
+ .time_mid = 0x69c7,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const simple_text_output_protocol = Guid{
+ .time_low = 0x387477c2,
+ .time_mid = 0x69c7,
+ .time_high_and_version = 0x11d2,
+ .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
+};
+
+pub const acpi_20_table = Guid{
+ .time_low = 0x8868e871,
+ .time_mid = 0xe4f1,
+ .time_high_and_version = 0x11d3,
+ .clock_sequence_and_node = .{ 0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81 },
+};
+
+pub const acpi_10_table = Guid{
+ .time_low = 0xeb9d2d30,
+ .time_mid = 0x2d88,
+ .time_high_and_version = 0x11d3,
+ .clock_sequence_and_node = .{ 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d },
+};
+
+pub const smbios_table = Guid{
+ .time_low = 0xeb9d2d31,
+ .time_mid = 0x2d88,
+ .time_high_and_version = 0x11d3,
+ .clock_sequence_and_node = .{ 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d },
+};
+
+pub const smbios3_table = Guid{
+ .time_low = 0xf2fd1544,
+ .time_mid = 0x9794,
+ .time_high_and_version = 0x4a2c,
+ .clock_sequence_and_node = .{ 0x99, 0x2e, 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94 },
+};
+
+pub const gpt_partition_type_efi_system = Guid{
+ .time_low = 0xC12A7328,
+ .time_mid = 0xF81F,
+ .time_high_and_version = 0x11D2,
+ .clock_sequence_and_node = .{ 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B },
+};
+
+pub const gpt_partition_type_akiba_afs = Guid{
+ .time_low = 0x414B4942,
+ .time_mid = 0x4146,
+ .time_high_and_version = 0x5300,
+ .clock_sequence_and_node = .{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 },
+};
diff --git a/hikari/efi/constants/keyboard.zig b/hikari/efi/constants/keyboard.zig
new file mode 100644
index 0000000..0dd5633
--- /dev/null
+++ b/hikari/efi/constants/keyboard.zig
@@ -0,0 +1,54 @@
+//! Hikari EFI Keyboard Scan Code Constants
+
+pub const scan_null: u16 = 0x0000;
+pub const scan_up: u16 = 0x0001;
+pub const scan_down: u16 = 0x0002;
+pub const scan_right: u16 = 0x0003;
+pub const scan_left: u16 = 0x0004;
+pub const scan_home: u16 = 0x0005;
+pub const scan_end: u16 = 0x0006;
+pub const scan_insert: u16 = 0x0007;
+pub const scan_delete: u16 = 0x0008;
+pub const scan_page_up: u16 = 0x0009;
+pub const scan_page_down: u16 = 0x000A;
+pub const scan_f1: u16 = 0x000B;
+pub const scan_f2: u16 = 0x000C;
+pub const scan_f3: u16 = 0x000D;
+pub const scan_f4: u16 = 0x000E;
+pub const scan_f5: u16 = 0x000F;
+pub const scan_f6: u16 = 0x0010;
+pub const scan_f7: u16 = 0x0011;
+pub const scan_f8: u16 = 0x0012;
+pub const scan_f9: u16 = 0x0013;
+pub const scan_f10: u16 = 0x0014;
+pub const scan_f11: u16 = 0x0015;
+pub const scan_f12: u16 = 0x0016;
+pub const scan_escape: u16 = 0x0017;
+pub const scan_f13: u16 = 0x0068;
+pub const scan_f14: u16 = 0x0069;
+pub const scan_f15: u16 = 0x006A;
+pub const scan_f16: u16 = 0x006B;
+pub const scan_f17: u16 = 0x006C;
+pub const scan_f18: u16 = 0x006D;
+pub const scan_f19: u16 = 0x006E;
+pub const scan_f20: u16 = 0x006F;
+pub const scan_f21: u16 = 0x0070;
+pub const scan_f22: u16 = 0x0071;
+pub const scan_f23: u16 = 0x0072;
+pub const scan_f24: u16 = 0x0073;
+pub const scan_mute: u16 = 0x007F;
+pub const scan_volume_up: u16 = 0x0080;
+pub const scan_volume_down: u16 = 0x0081;
+pub const scan_brightness_up: u16 = 0x0100;
+pub const scan_brightness_down: u16 = 0x0101;
+pub const scan_suspend: u16 = 0x0102;
+pub const scan_hibernate: u16 = 0x0103;
+pub const scan_toggle_display: u16 = 0x0104;
+pub const scan_recovery: u16 = 0x0105;
+pub const scan_eject: u16 = 0x0106;
+
+pub const unicode_char_null: u16 = 0x0000;
+pub const unicode_char_backspace: u16 = 0x0008;
+pub const unicode_char_tab: u16 = 0x0009;
+pub const unicode_char_linefeed: u16 = 0x000A;
+pub const unicode_char_carriage_return: u16 = 0x000D;
diff --git a/hikari/efi/constants/memory.zig b/hikari/efi/constants/memory.zig
new file mode 100644
index 0000000..f45ae53
--- /dev/null
+++ b/hikari/efi/constants/memory.zig
@@ -0,0 +1,39 @@
+//! Hikari EFI Memory Constants
+
+pub const memory_type_reserved: u32 = 0;
+pub const memory_type_loader_code: u32 = 1;
+pub const memory_type_loader_data: u32 = 2;
+pub const memory_type_boot_services_code: u32 = 3;
+pub const memory_type_boot_services_data: u32 = 4;
+pub const memory_type_runtime_services_code: u32 = 5;
+pub const memory_type_runtime_services_data: u32 = 6;
+pub const memory_type_conventional: u32 = 7;
+pub const memory_type_unusable: u32 = 8;
+pub const memory_type_acpi_reclaim: u32 = 9;
+pub const memory_type_acpi_nvs: u32 = 10;
+pub const memory_type_mmio: u32 = 11;
+pub const memory_type_mmio_port_space: u32 = 12;
+pub const memory_type_pal_code: u32 = 13;
+pub const memory_type_persistent: u32 = 14;
+pub const memory_type_unaccepted: u32 = 15;
+
+pub const memory_attribute_uncacheable: u64 = 0x0000000000000001;
+pub const memory_attribute_write_combining: u64 = 0x0000000000000002;
+pub const memory_attribute_write_through: u64 = 0x0000000000000004;
+pub const memory_attribute_write_back: u64 = 0x0000000000000008;
+pub const memory_attribute_uncacheable_exported: u64 = 0x0000000000000010;
+pub const memory_attribute_write_protected: u64 = 0x0000000000001000;
+pub const memory_attribute_read_protected: u64 = 0x0000000000002000;
+pub const memory_attribute_execute_protected: u64 = 0x0000000000004000;
+pub const memory_attribute_nonvolatile: u64 = 0x0000000000008000;
+pub const memory_attribute_more_reliable: u64 = 0x0000000000010000;
+pub const memory_attribute_read_only: u64 = 0x0000000000020000;
+pub const memory_attribute_specific_purpose: u64 = 0x0000000000040000;
+pub const memory_attribute_crypto_capable: u64 = 0x0000000000080000;
+pub const memory_attribute_runtime: u64 = 0x8000000000000000;
+
+pub const allocate_type_any_pages: u32 = 0;
+pub const allocate_type_max_address: u32 = 1;
+pub const allocate_type_address: u32 = 2;
+
+pub const page_size: u64 = 4096;
diff --git a/hikari/efi/constants/status.zig b/hikari/efi/constants/status.zig
new file mode 100644
index 0000000..ae959ab
--- /dev/null
+++ b/hikari/efi/constants/status.zig
@@ -0,0 +1,39 @@
+//! Hikari EFI Status Code Constants
+
+pub const success: usize = 0;
+
+pub const high_bit: usize = 1 << 63;
+
+pub const load_error: usize = high_bit | 1;
+pub const invalid_parameter: usize = high_bit | 2;
+pub const unsupported: usize = high_bit | 3;
+pub const bad_buffer_size: usize = high_bit | 4;
+pub const buffer_too_small: usize = high_bit | 5;
+pub const not_ready: usize = high_bit | 6;
+pub const device_error: usize = high_bit | 7;
+pub const write_protected: usize = high_bit | 8;
+pub const out_of_resources: usize = high_bit | 9;
+pub const volume_corrupted: usize = high_bit | 10;
+pub const volume_full: usize = high_bit | 11;
+pub const no_media: usize = high_bit | 12;
+pub const media_changed: usize = high_bit | 13;
+pub const not_found: usize = high_bit | 14;
+pub const access_denied: usize = high_bit | 15;
+pub const no_response: usize = high_bit | 16;
+pub const no_mapping: usize = high_bit | 17;
+pub const timeout: usize = high_bit | 18;
+pub const not_started: usize = high_bit | 19;
+pub const already_started: usize = high_bit | 20;
+pub const aborted: usize = high_bit | 21;
+pub const icmp_error: usize = high_bit | 22;
+pub const tftp_error: usize = high_bit | 23;
+pub const protocol_error: usize = high_bit | 24;
+pub const incompatible_version: usize = high_bit | 25;
+pub const security_violation: usize = high_bit | 26;
+pub const crc_error: usize = high_bit | 27;
+pub const end_of_media: usize = high_bit | 28;
+pub const end_of_unit: usize = high_bit | 31;
+pub const invalid_language: usize = high_bit | 32;
+pub const compromised_data: usize = high_bit | 33;
+pub const ip_address_conflict: usize = high_bit | 34;
+pub const http_error: usize = high_bit | 35;
diff --git a/hikari/efi/constants/tables.zig b/hikari/efi/constants/tables.zig
new file mode 100644
index 0000000..592113e
--- /dev/null
+++ b/hikari/efi/constants/tables.zig
@@ -0,0 +1,23 @@
+//! Hikari EFI Table Signature Constants
+
+pub const system_table_signature: u64 = 0x5453595320494249; // "IBI SYST"
+pub const boot_services_signature: u64 = 0x56524553544f4f42; // "BOOTSERV"
+pub const runtime_services_signature: u64 = 0x56524553544e5552; // "RUNTSERV"
+
+pub const system_table_revision_2_100: u32 = (2 << 16) | 100;
+pub const system_table_revision_2_90: u32 = (2 << 16) | 90;
+pub const system_table_revision_2_80: u32 = (2 << 16) | 80;
+pub const system_table_revision_2_70: u32 = (2 << 16) | 70;
+pub const system_table_revision_2_60: u32 = (2 << 16) | 60;
+pub const system_table_revision_2_50: u32 = (2 << 16) | 50;
+pub const system_table_revision_2_40: u32 = (2 << 16) | 40;
+pub const system_table_revision_2_31: u32 = (2 << 16) | 31;
+pub const system_table_revision_2_30: u32 = (2 << 16) | 30;
+pub const system_table_revision_2_20: u32 = (2 << 16) | 20;
+pub const system_table_revision_2_10: u32 = (2 << 16) | 10;
+pub const system_table_revision_2_00: u32 = (2 << 16) | 0;
+pub const system_table_revision_1_10: u32 = (1 << 16) | 10;
+pub const system_table_revision_1_02: u32 = (1 << 16) | 2;
+
+pub const specification_major_revision: u32 = 2;
+pub const specification_minor_revision: u32 = 100;
diff --git a/hikari/efi/constants/unit.zig b/hikari/efi/constants/unit.zig
new file mode 100644
index 0000000..9d4e4ae
--- /dev/null
+++ b/hikari/efi/constants/unit.zig
@@ -0,0 +1,19 @@
+//! Hikari EFI Unit Constants
+
+pub const unit_mode_read: u64 = 0x0000000000000001;
+pub const unit_mode_write: u64 = 0x0000000000000002;
+pub const unit_mode_create: u64 = 0x8000000000000000;
+
+pub const unit_attribute_read_only: u64 = 0x0000000000000001;
+pub const unit_attribute_hidden: u64 = 0x0000000000000002;
+pub const unit_attribute_system: u64 = 0x0000000000000004;
+pub const unit_attribute_reserved: u64 = 0x0000000000000008;
+pub const unit_attribute_stack: u64 = 0x0000000000000010;
+pub const unit_attribute_archive: u64 = 0x0000000000000020;
+pub const unit_attribute_valid: u64 = 0x0000000000000037;
+
+pub const unit_protocol_revision: u64 = 0x00010000;
+pub const unit_protocol_revision2: u64 = 0x00020000;
+pub const unit_protocol_latest_revision: u64 = unit_protocol_revision2;
+
+pub const simple_unit_system_protocol_revision: u64 = 0x00010000;
diff --git a/hikari/efi/efi.zig b/hikari/efi/efi.zig
new file mode 100644
index 0000000..8d67232
--- /dev/null
+++ b/hikari/efi/efi.zig
@@ -0,0 +1,6 @@
+//! Hikari EFI Interface
+
+pub const constants = @import("constants/constants.zig");
+pub const types = @import("types/types.zig");
+pub const services = @import("services/services.zig");
+pub const protocols = @import("protocols/protocols.zig");
diff --git a/hikari/efi/protocols/block_io.zig b/hikari/efi/protocols/block_io.zig
new file mode 100644
index 0000000..76ff976
--- /dev/null
+++ b/hikari/efi/protocols/block_io.zig
@@ -0,0 +1,38 @@
+//! Hikari EFI Block I/O Protocol
+
+const types = @import("../types/types.zig");
+const block = @import("../types/block.zig");
+
+pub const BlockIoProtocol = extern struct {
+ revision: u64,
+ media: *block.BlockIoMedia,
+
+ reset: *const fn (
+ self: *BlockIoProtocol,
+ extended_verification: bool,
+ ) callconv(.C) types.Status,
+
+ read_blocks: *const fn (
+ self: *BlockIoProtocol,
+ media_id: u32,
+ lba: types.Lba,
+ buffer_size: usize,
+ buffer: [*]u8,
+ ) callconv(.C) types.Status,
+
+ write_blocks: *const fn (
+ self: *BlockIoProtocol,
+ media_id: u32,
+ lba: types.Lba,
+ buffer_size: usize,
+ buffer: [*]const u8,
+ ) callconv(.C) types.Status,
+
+ flush_blocks: *const fn (
+ self: *BlockIoProtocol,
+ ) callconv(.C) types.Status,
+};
+
+pub const block_io_protocol_revision1: u64 = 0x00010000;
+pub const block_io_protocol_revision2: u64 = 0x00020001;
+pub const block_io_protocol_revision3: u64 = 0x0002001F;
diff --git a/hikari/efi/protocols/device_location.zig b/hikari/efi/protocols/device_location.zig
new file mode 100644
index 0000000..0378b5f
--- /dev/null
+++ b/hikari/efi/protocols/device_location.zig
@@ -0,0 +1,49 @@
+//! Hikari EFI Device Location Protocol
+
+pub const DeviceLocationProtocol = extern struct {
+ device_type: u8,
+ sub_type: u8,
+ length: [2]u8,
+};
+
+pub const device_location_type_hardware: u8 = 0x01;
+pub const device_location_type_acpi: u8 = 0x02;
+pub const device_location_type_messaging: u8 = 0x03;
+pub const device_location_type_media: u8 = 0x04;
+pub const device_location_type_bios_boot: u8 = 0x05;
+pub const device_location_type_end: u8 = 0x7F;
+
+pub const device_location_sub_type_end_entire: u8 = 0xFF;
+pub const device_location_sub_type_end_instance: u8 = 0x01;
+
+pub const device_location_sub_type_hard_drive: u8 = 0x01;
+pub const device_location_sub_type_cdrom: u8 = 0x02;
+pub const device_location_sub_type_vendor: u8 = 0x03;
+pub const device_location_sub_type_unit_location: u8 = 0x04;
+pub const device_location_sub_type_media_protocol: u8 = 0x05;
+pub const device_location_sub_type_piwg_firmware_unit: u8 = 0x06;
+pub const device_location_sub_type_piwg_firmware_volume: u8 = 0x07;
+pub const device_location_sub_type_relative_offset_range: u8 = 0x08;
+pub const device_location_sub_type_ram_disk: u8 = 0x09;
+
+pub const HardDriveDeviceLocation = extern struct {
+ header: DeviceLocationProtocol,
+ partition_number: u32,
+ partition_start: u64,
+ partition_size: u64,
+ partition_signature: [16]u8,
+ partition_format: u8,
+ signature_type: u8,
+};
+
+pub const partition_format_mbr: u8 = 0x01;
+pub const partition_format_gpt: u8 = 0x02;
+
+pub const signature_type_none: u8 = 0x00;
+pub const signature_type_mbr: u8 = 0x01;
+pub const signature_type_guid: u8 = 0x02;
+
+pub const UnitLocationDeviceLocation = extern struct {
+ header: DeviceLocationProtocol,
+ location_name: [*:0]u16,
+};
diff --git a/hikari/efi/protocols/disk_io.zig b/hikari/efi/protocols/disk_io.zig
new file mode 100644
index 0000000..223d8cc
--- /dev/null
+++ b/hikari/efi/protocols/disk_io.zig
@@ -0,0 +1,25 @@
+//! Hikari EFI Disk I/O Protocol
+
+const types = @import("../types/types.zig");
+
+pub const DiskIoProtocol = extern struct {
+ revision: u64,
+
+ read_disk: *const fn (
+ self: *DiskIoProtocol,
+ media_id: u32,
+ offset: u64,
+ buffer_size: usize,
+ buffer: [*]u8,
+ ) callconv(.C) types.Status,
+
+ write_disk: *const fn (
+ self: *DiskIoProtocol,
+ media_id: u32,
+ offset: u64,
+ buffer_size: usize,
+ buffer: [*]const u8,
+ ) callconv(.C) types.Status,
+};
+
+pub const disk_io_protocol_revision: u64 = 0x00010000;
diff --git a/hikari/efi/protocols/graphics_output.zig b/hikari/efi/protocols/graphics_output.zig
new file mode 100644
index 0000000..94d87d3
--- /dev/null
+++ b/hikari/efi/protocols/graphics_output.zig
@@ -0,0 +1,33 @@
+//! Hikari EFI Graphics Output Protocol
+
+const types = @import("../types/types.zig");
+const graphics = @import("../types/graphics.zig");
+
+pub const GraphicsOutputProtocol = extern struct {
+ query_mode: *const fn (
+ self: *GraphicsOutputProtocol,
+ mode_number: u32,
+ size_of_info: *usize,
+ info: **graphics.GraphicsOutputModeInformation,
+ ) callconv(.C) types.Status,
+
+ set_mode: *const fn (
+ self: *GraphicsOutputProtocol,
+ mode_number: u32,
+ ) callconv(.C) types.Status,
+
+ blt: *const fn (
+ self: *GraphicsOutputProtocol,
+ blt_buffer: ?[*]graphics.BltPixel,
+ blt_operation: graphics.BltOperation,
+ source_x: usize,
+ source_y: usize,
+ destination_x: usize,
+ destination_y: usize,
+ width: usize,
+ height: usize,
+ delta: usize,
+ ) callconv(.C) types.Status,
+
+ mode: *graphics.GraphicsOutputProtocolMode,
+};
diff --git a/hikari/efi/protocols/loaded_image.zig b/hikari/efi/protocols/loaded_image.zig
new file mode 100644
index 0000000..f596417
--- /dev/null
+++ b/hikari/efi/protocols/loaded_image.zig
@@ -0,0 +1,22 @@
+//! Hikari EFI Loaded Image Protocol
+
+const types = @import("../types/types.zig");
+const memory = @import("../types/memory.zig");
+
+pub const LoadedImageProtocol = extern struct {
+ revision: u32,
+ parent_handle: types.Handle,
+ system_table: *anyopaque,
+ device_handle: types.Handle,
+ unit_location: *anyopaque,
+ reserved: *anyopaque,
+ load_options_size: u32,
+ load_options: *anyopaque,
+ image_base: [*]u8,
+ image_size: u64,
+ image_code_type: memory.MemoryType,
+ image_data_type: memory.MemoryType,
+ unload: *const fn (image_handle: types.Handle) callconv(.C) types.Status,
+};
+
+pub const loaded_image_protocol_revision: u32 = 0x1000;
diff --git a/hikari/efi/protocols/protocols.zig b/hikari/efi/protocols/protocols.zig
new file mode 100644
index 0000000..2e086fa
--- /dev/null
+++ b/hikari/efi/protocols/protocols.zig
@@ -0,0 +1,21 @@
+//! Hikari EFI Protocols
+
+pub const simple_text_input = @import("simple_text_input.zig");
+pub const simple_text_output = @import("simple_text_output.zig");
+pub const graphics_output = @import("graphics_output.zig");
+pub const file = @import("file.zig");
+pub const simple_file_system = @import("simple_file_system.zig");
+pub const block_io = @import("block_io.zig");
+pub const disk_io = @import("disk_io.zig");
+pub const loaded_image = @import("loaded_image.zig");
+pub const device_path = @import("device_path.zig");
+
+pub const SimpleTextInputProtocol = simple_text_input.SimpleTextInputProtocol;
+pub const SimpleTextOutputProtocol = simple_text_output.SimpleTextOutputProtocol;
+pub const GraphicsOutputProtocol = graphics_output.GraphicsOutputProtocol;
+pub const FileProtocol = file.FileProtocol;
+pub const SimpleFileSystemProtocol = simple_file_system.SimpleFileSystemProtocol;
+pub const BlockIoProtocol = block_io.BlockIoProtocol;
+pub const DiskIoProtocol = disk_io.DiskIoProtocol;
+pub const LoadedImageProtocol = loaded_image.LoadedImageProtocol;
+pub const DevicePathProtocol = device_path.DevicePathProtocol;
diff --git a/hikari/efi/protocols/simple_text_input.zig b/hikari/efi/protocols/simple_text_input.zig
new file mode 100644
index 0000000..c4f07cd
--- /dev/null
+++ b/hikari/efi/protocols/simple_text_input.zig
@@ -0,0 +1,18 @@
+//! Hikari EFI Simple Text Input Protocol
+
+const types = @import("../types/types.zig");
+const input = @import("../types/input.zig");
+
+pub const SimpleTextInputProtocol = extern struct {
+ reset: *const fn (
+ self: *SimpleTextInputProtocol,
+ extended_verification: bool,
+ ) callconv(.C) types.Status,
+
+ read_key_stroke: *const fn (
+ self: *SimpleTextInputProtocol,
+ key: *input.InputKey,
+ ) callconv(.C) types.Status,
+
+ wait_for_key: types.Event,
+};
diff --git a/hikari/efi/protocols/simple_text_output.zig b/hikari/efi/protocols/simple_text_output.zig
new file mode 100644
index 0000000..168bbc9
--- /dev/null
+++ b/hikari/efi/protocols/simple_text_output.zig
@@ -0,0 +1,90 @@
+//! Hikari EFI Simple Text Output Protocol
+
+const types = @import("../types/types.zig");
+
+pub const SimpleTextOutputProtocol = extern struct {
+ reset: *const fn (
+ self: *SimpleTextOutputProtocol,
+ extended_verification: bool,
+ ) callconv(.C) types.Status,
+
+ output_string: *const fn (
+ self: *SimpleTextOutputProtocol,
+ string: [*:0]const types.Char16,
+ ) callconv(.C) types.Status,
+
+ test_string: *const fn (
+ self: *SimpleTextOutputProtocol,
+ string: [*:0]const types.Char16,
+ ) callconv(.C) types.Status,
+
+ query_mode: *const fn (
+ self: *SimpleTextOutputProtocol,
+ mode_number: usize,
+ columns: *usize,
+ rows: *usize,
+ ) callconv(.C) types.Status,
+
+ set_mode: *const fn (
+ self: *SimpleTextOutputProtocol,
+ mode_number: usize,
+ ) callconv(.C) types.Status,
+
+ set_attribute: *const fn (
+ self: *SimpleTextOutputProtocol,
+ attribute: usize,
+ ) callconv(.C) types.Status,
+
+ clear_screen: *const fn (
+ self: *SimpleTextOutputProtocol,
+ ) callconv(.C) types.Status,
+
+ set_cursor_position: *const fn (
+ self: *SimpleTextOutputProtocol,
+ column: usize,
+ row: usize,
+ ) callconv(.C) types.Status,
+
+ enable_cursor: *const fn (
+ self: *SimpleTextOutputProtocol,
+ visible: bool,
+ ) callconv(.C) types.Status,
+
+ mode: *SimpleTextOutputMode,
+};
+
+pub const SimpleTextOutputMode = extern struct {
+ max_mode: i32,
+ mode: i32,
+ attribute: i32,
+ cursor_column: i32,
+ cursor_row: i32,
+ cursor_visible: bool,
+};
+
+pub const text_attribute_black: usize = 0x00;
+pub const text_attribute_blue: usize = 0x01;
+pub const text_attribute_green: usize = 0x02;
+pub const text_attribute_cyan: usize = 0x03;
+pub const text_attribute_red: usize = 0x04;
+pub const text_attribute_magenta: usize = 0x05;
+pub const text_attribute_brown: usize = 0x06;
+pub const text_attribute_lightgray: usize = 0x07;
+pub const text_attribute_bright: usize = 0x08;
+pub const text_attribute_darkgray: usize = 0x08;
+pub const text_attribute_lightblue: usize = 0x09;
+pub const text_attribute_lightgreen: usize = 0x0A;
+pub const text_attribute_lightcyan: usize = 0x0B;
+pub const text_attribute_lightred: usize = 0x0C;
+pub const text_attribute_lightmagenta: usize = 0x0D;
+pub const text_attribute_yellow: usize = 0x0E;
+pub const text_attribute_white: usize = 0x0F;
+
+pub const background_black: usize = 0x00;
+pub const background_blue: usize = 0x10;
+pub const background_green: usize = 0x20;
+pub const background_cyan: usize = 0x30;
+pub const background_red: usize = 0x40;
+pub const background_magenta: usize = 0x50;
+pub const background_brown: usize = 0x60;
+pub const background_lightgray: usize = 0x70;
diff --git a/hikari/efi/protocols/simple_unit_system.zig b/hikari/efi/protocols/simple_unit_system.zig
new file mode 100644
index 0000000..73cce41
--- /dev/null
+++ b/hikari/efi/protocols/simple_unit_system.zig
@@ -0,0 +1,13 @@
+//! Hikari EFI Simple Unit System Protocol
+
+const types = @import("../types/types.zig");
+const unit = @import("unit.zig");
+
+pub const SimpleUnitSystemProtocol = extern struct {
+ revision: u64,
+
+ open_volume: *const fn (
+ self: *SimpleUnitSystemProtocol,
+ root: **unit.UnitProtocol,
+ ) callconv(.C) types.Status,
+};
diff --git a/hikari/efi/protocols/unit.zig b/hikari/efi/protocols/unit.zig
new file mode 100644
index 0000000..1bb9cb4
--- /dev/null
+++ b/hikari/efi/protocols/unit.zig
@@ -0,0 +1,96 @@
+//! Hikari EFI Unit Protocol
+
+const types = @import("../types/types.zig");
+
+pub const UnitProtocol = extern struct {
+ revision: u64,
+
+ open: *const fn (
+ self: *UnitProtocol,
+ new_handle: **UnitProtocol,
+ unit_name: [*:0]const types.Char16,
+ open_mode: u64,
+ attributes: u64,
+ ) callconv(.C) types.Status,
+
+ close: *const fn (
+ self: *UnitProtocol,
+ ) callconv(.C) types.Status,
+
+ delete: *const fn (
+ self: *UnitProtocol,
+ ) callconv(.C) types.Status,
+
+ read: *const fn (
+ self: *UnitProtocol,
+ buffer_size: *usize,
+ buffer: [*]u8,
+ ) callconv(.C) types.Status,
+
+ write: *const fn (
+ self: *UnitProtocol,
+ buffer_size: *usize,
+ buffer: [*]const u8,
+ ) callconv(.C) types.Status,
+
+ get_position: *const fn (
+ self: *UnitProtocol,
+ position: *u64,
+ ) callconv(.C) types.Status,
+
+ set_position: *const fn (
+ self: *UnitProtocol,
+ position: u64,
+ ) callconv(.C) types.Status,
+
+ get_info: *const fn (
+ self: *UnitProtocol,
+ information_type: *align(8) const types.Guid,
+ buffer_size: *usize,
+ buffer: [*]u8,
+ ) callconv(.C) types.Status,
+
+ set_info: *const fn (
+ self: *UnitProtocol,
+ information_type: *align(8) const types.Guid,
+ buffer_size: usize,
+ buffer: [*]const u8,
+ ) callconv(.C) types.Status,
+
+ flush: *const fn (
+ self: *UnitProtocol,
+ ) callconv(.C) types.Status,
+
+ open_ex: *const fn (
+ self: *UnitProtocol,
+ new_handle: **UnitProtocol,
+ unit_name: [*:0]const types.Char16,
+ open_mode: u64,
+ attributes: u64,
+ token: *UnitIoToken,
+ ) callconv(.C) types.Status,
+
+ read_ex: *const fn (
+ self: *UnitProtocol,
+ token: *UnitIoToken,
+ ) callconv(.C) types.Status,
+
+ write_ex: *const fn (
+ self: *UnitProtocol,
+ token: *UnitIoToken,
+ ) callconv(.C) types.Status,
+
+ flush_ex: *const fn (
+ self: *UnitProtocol,
+ token: *UnitIoToken,
+ ) callconv(.C) types.Status,
+};
+
+pub const UnitIoToken = extern struct {
+ event: types.Event,
+ status: types.Status,
+ buffer_size: usize,
+ buffer: ?*anyopaque,
+};
+
+pub const unit_position_end: u64 = 0xFFFFFFFFFFFFFFFF;
diff --git a/hikari/efi/services/boot.zig b/hikari/efi/services/boot.zig
new file mode 100644
index 0000000..6964896
--- /dev/null
+++ b/hikari/efi/services/boot.zig
@@ -0,0 +1,295 @@
+//! Hikari EFI Boot Services
+
+const types = @import("../types/types.zig");
+const table = @import("../types/table.zig");
+const memory = @import("../types/memory.zig");
+
+pub const BootServices = extern struct {
+ header: table.TableHeader,
+
+ raise_tpl: *const fn (new_tpl: usize) callconv(.C) usize,
+ restore_tpl: *const fn (old_tpl: usize) callconv(.C) void,
+
+ allocate_pages: *const fn (
+ allocate_type: memory.AllocateType,
+ memory_type: memory.MemoryType,
+ pages: usize,
+ physical_address: *types.PhysicalAddress,
+ ) callconv(.C) types.Status,
+
+ free_pages: *const fn (
+ physical_address: types.PhysicalAddress,
+ pages: usize,
+ ) callconv(.C) types.Status,
+
+ get_memory_map: *const fn (
+ memory_map_size: *usize,
+ memory_map: [*]memory.MemoryDescriptor,
+ map_key: *usize,
+ descriptor_size: *usize,
+ descriptor_version: *u32,
+ ) callconv(.C) types.Status,
+
+ allocate_pool: *const fn (
+ pool_type: memory.MemoryType,
+ size: usize,
+ buffer: *[*]align(8) u8,
+ ) callconv(.C) types.Status,
+
+ free_pool: *const fn (
+ buffer: [*]align(8) u8,
+ ) callconv(.C) types.Status,
+
+ create_event: *const fn (
+ event_type: u32,
+ notify_tpl: usize,
+ notify_function: ?*const fn (types.Event, ?*anyopaque) callconv(.C) void,
+ notify_context: ?*anyopaque,
+ event: *types.Event,
+ ) callconv(.C) types.Status,
+
+ set_timer: *const fn (
+ event: types.Event,
+ timer_type: TimerDelay,
+ trigger_time: u64,
+ ) callconv(.C) types.Status,
+
+ wait_for_event: *const fn (
+ number_of_events: usize,
+ events: [*]const types.Event,
+ index: *usize,
+ ) callconv(.C) types.Status,
+
+ signal_event: *const fn (
+ event: types.Event,
+ ) callconv(.C) types.Status,
+
+ close_event: *const fn (
+ event: types.Event,
+ ) callconv(.C) types.Status,
+
+ check_event: *const fn (
+ event: types.Event,
+ ) callconv(.C) types.Status,
+
+ install_protocol_interface: *const fn (
+ handle: *types.Handle,
+ protocol: *align(8) const types.Guid,
+ interface_type: InterfaceType,
+ interface: ?*anyopaque,
+ ) callconv(.C) types.Status,
+
+ reinstall_protocol_interface: *const fn (
+ handle: types.Handle,
+ protocol: *align(8) const types.Guid,
+ old_interface: ?*anyopaque,
+ new_interface: ?*anyopaque,
+ ) callconv(.C) types.Status,
+
+ uninstall_protocol_interface: *const fn (
+ handle: types.Handle,
+ protocol: *align(8) const types.Guid,
+ interface: ?*anyopaque,
+ ) callconv(.C) types.Status,
+
+ handle_protocol: *const fn (
+ handle: types.Handle,
+ protocol: *align(8) const types.Guid,
+ interface: *?*anyopaque,
+ ) callconv(.C) types.Status,
+
+ reserved: *anyopaque,
+
+ register_protocol_notify: *const fn (
+ protocol: *align(8) const types.Guid,
+ event: types.Event,
+ registration: **anyopaque,
+ ) callconv(.C) types.Status,
+
+ locate_handle: *const fn (
+ search_type: memory.LocateSearchType,
+ protocol: ?*align(8) const types.Guid,
+ search_key: ?*anyopaque,
+ buffer_size: *usize,
+ buffer: [*]types.Handle,
+ ) callconv(.C) types.Status,
+
+ locate_device_location: *const fn (
+ protocol: *align(8) const types.Guid,
+ device_location: **anyopaque,
+ device: *types.Handle,
+ ) callconv(.C) types.Status,
+
+ install_configuration_table: *const fn (
+ guid: *align(8) const types.Guid,
+ table_ptr: ?*anyopaque,
+ ) callconv(.C) types.Status,
+
+ load_image: *const fn (
+ boot_policy: bool,
+ parent_image_handle: types.Handle,
+ device_location: ?*anyopaque,
+ source_buffer: ?[*]const u8,
+ source_size: usize,
+ image_handle: *types.Handle,
+ ) callconv(.C) types.Status,
+
+ start_image: *const fn (
+ image_handle: types.Handle,
+ exit_data_size: *usize,
+ exit_data: ?*[*]types.Char16,
+ ) callconv(.C) types.Status,
+
+ exit: *const fn (
+ image_handle: types.Handle,
+ exit_status: types.Status,
+ exit_data_size: usize,
+ exit_data: ?[*]const types.Char16,
+ ) callconv(.C) types.Status,
+
+ unload_image: *const fn (
+ image_handle: types.Handle,
+ ) callconv(.C) types.Status,
+
+ exit_boot_services: *const fn (
+ image_handle: types.Handle,
+ map_key: usize,
+ ) callconv(.C) types.Status,
+
+ get_next_monotonic_count: *const fn (
+ count: *u64,
+ ) callconv(.C) types.Status,
+
+ stall: *const fn (
+ microseconds: usize,
+ ) callconv(.C) types.Status,
+
+ set_watchdog_timer: *const fn (
+ timeout: usize,
+ watchdog_code: u64,
+ data_size: usize,
+ watchdog_data: ?[*]const types.Char16,
+ ) callconv(.C) types.Status,
+
+ connect_controller: *const fn (
+ controller_handle: types.Handle,
+ driver_image_handle: ?types.Handle,
+ remaining_device_location: ?*anyopaque,
+ recursive: bool,
+ ) callconv(.C) types.Status,
+
+ disconnect_controller: *const fn (
+ controller_handle: types.Handle,
+ driver_image_handle: ?types.Handle,
+ child_handle: ?types.Handle,
+ ) callconv(.C) types.Status,
+
+ open_protocol: *const fn (
+ handle: types.Handle,
+ protocol: *align(8) const types.Guid,
+ interface: ?*?*anyopaque,
+ agent_handle: ?types.Handle,
+ controller_handle: ?types.Handle,
+ attributes: u32,
+ ) callconv(.C) types.Status,
+
+ close_protocol: *const fn (
+ handle: types.Handle,
+ protocol: *align(8) const types.Guid,
+ agent_handle: types.Handle,
+ controller_handle: ?types.Handle,
+ ) callconv(.C) types.Status,
+
+ open_protocol_information: *const fn (
+ handle: types.Handle,
+ protocol: *align(8) const types.Guid,
+ entry_buffer: *[*]OpenProtocolInformationEntry,
+ entry_count: *usize,
+ ) callconv(.C) types.Status,
+
+ protocols_per_handle: *const fn (
+ handle: types.Handle,
+ protocol_buffer: *[*]*align(8) types.Guid,
+ protocol_buffer_count: *usize,
+ ) callconv(.C) types.Status,
+
+ locate_handle_buffer: *const fn (
+ search_type: memory.LocateSearchType,
+ protocol: ?*align(8) const types.Guid,
+ search_key: ?*anyopaque,
+ handle_count: *usize,
+ buffer: *[*]types.Handle,
+ ) callconv(.C) types.Status,
+
+ locate_protocol: *const fn (
+ protocol: *align(8) const types.Guid,
+ registration: ?*anyopaque,
+ interface: *?*anyopaque,
+ ) callconv(.C) types.Status,
+
+ install_multiple_protocol_interfaces: *const anyopaque,
+ uninstall_multiple_protocol_interfaces: *const anyopaque,
+
+ calculate_crc32: *const fn (
+ data: [*]const u8,
+ data_size: usize,
+ crc32: *u32,
+ ) callconv(.C) types.Status,
+
+ copy_memory: *const fn (
+ destination: [*]u8,
+ source: [*]const u8,
+ length: usize,
+ ) callconv(.C) void,
+
+ set_memory: *const fn (
+ buffer: [*]u8,
+ size: usize,
+ value: u8,
+ ) callconv(.C) void,
+
+ create_event_ex: *const fn (
+ event_type: u32,
+ notify_tpl: usize,
+ notify_function: ?*const fn (types.Event, ?*anyopaque) callconv(.C) void,
+ notify_context: ?*const anyopaque,
+ event_group: ?*align(8) const types.Guid,
+ event: *types.Event,
+ ) callconv(.C) types.Status,
+};
+
+pub const TimerDelay = enum(u32) {
+ cancel = 0,
+ periodic = 1,
+ relative = 2,
+};
+
+pub const InterfaceType = enum(u32) {
+ native = 0,
+};
+
+pub const OpenProtocolInformationEntry = extern struct {
+ agent_handle: types.Handle,
+ controller_handle: types.Handle,
+ attributes: u32,
+ open_count: u32,
+};
+
+pub const open_protocol_by_handle_protocol: u32 = 0x00000001;
+pub const open_protocol_get_protocol: u32 = 0x00000002;
+pub const open_protocol_test_protocol: u32 = 0x00000004;
+pub const open_protocol_by_child_controller: u32 = 0x00000008;
+pub const open_protocol_by_driver: u32 = 0x00000010;
+pub const open_protocol_exclusive: u32 = 0x00000020;
+
+pub const tpl_application: usize = 4;
+pub const tpl_callback: usize = 8;
+pub const tpl_notify: usize = 16;
+pub const tpl_high_level: usize = 31;
+
+pub const event_timer: u32 = 0x80000000;
+pub const event_runtime: u32 = 0x40000000;
+pub const event_notify_wait: u32 = 0x00000100;
+pub const event_notify_signal: u32 = 0x00000200;
+pub const event_signal_exit_boot_services: u32 = 0x00000201;
+pub const event_signal_virtual_address_change: u32 = 0x60000202;
diff --git a/hikari/efi/services/runtime.zig b/hikari/efi/services/runtime.zig
new file mode 100644
index 0000000..add3277
--- /dev/null
+++ b/hikari/efi/services/runtime.zig
@@ -0,0 +1,116 @@
+//! Hikari EFI Runtime Services
+
+const types = @import("../types/types.zig");
+const table = @import("../types/table.zig");
+const time = @import("../types/time.zig");
+const memory = @import("../types/memory.zig");
+const reset = @import("../types/reset.zig");
+
+pub const RuntimeServices = extern struct {
+ header: table.TableHeader,
+
+ get_time: *const fn (
+ current_time: *time.Time,
+ capabilities: ?*time.TimeCapabilities,
+ ) callconv(.C) types.Status,
+
+ set_time: *const fn (
+ new_time: *const time.Time,
+ ) callconv(.C) types.Status,
+
+ get_wakeup_time: *const fn (
+ enabled: *bool,
+ pending: *bool,
+ wakeup_time: *time.Time,
+ ) callconv(.C) types.Status,
+
+ set_wakeup_time: *const fn (
+ enable: bool,
+ wakeup_time: ?*const time.Time,
+ ) callconv(.C) types.Status,
+
+ set_virtual_address_map: *const fn (
+ memory_map_size: usize,
+ descriptor_size: usize,
+ descriptor_version: u32,
+ virtual_map: [*]memory.MemoryDescriptor,
+ ) callconv(.C) types.Status,
+
+ convert_pointer: *const fn (
+ debug_disposition: usize,
+ address: **anyopaque,
+ ) callconv(.C) types.Status,
+
+ get_variable: *const fn (
+ variable_name: [*:0]const types.Char16,
+ vendor_guid: *align(8) const types.Guid,
+ attributes: ?*u32,
+ data_size: *usize,
+ data: ?[*]u8,
+ ) callconv(.C) types.Status,
+
+ get_next_variable_name: *const fn (
+ variable_name_size: *usize,
+ variable_name: [*:0]types.Char16,
+ vendor_guid: *align(8) types.Guid,
+ ) callconv(.C) types.Status,
+
+ set_variable: *const fn (
+ variable_name: [*:0]const types.Char16,
+ vendor_guid: *align(8) const types.Guid,
+ attributes: u32,
+ data_size: usize,
+ data: [*]const u8,
+ ) callconv(.C) types.Status,
+
+ get_next_high_monotonic_count: *const fn (
+ high_count: *u32,
+ ) callconv(.C) types.Status,
+
+ reset_system: *const fn (
+ reset_type: reset.ResetType,
+ reset_status: types.Status,
+ data_size: usize,
+ reset_data: ?*const anyopaque,
+ ) callconv(.C) noreturn,
+
+ update_capsule: *const fn (
+ capsule_header_array: **CapsuleHeader,
+ capsule_count: usize,
+ scatter_gather_list: types.PhysicalAddress,
+ ) callconv(.C) types.Status,
+
+ query_capsule_capabilities: *const fn (
+ capsule_header_array: **CapsuleHeader,
+ capsule_count: usize,
+ maximum_capsule_size: *u64,
+ reset_type: *reset.ResetType,
+ ) callconv(.C) types.Status,
+
+ query_variable_info: *const fn (
+ attributes: u32,
+ maximum_variable_storage_size: *u64,
+ remaining_variable_storage_size: *u64,
+ maximum_variable_size: *u64,
+ ) callconv(.C) types.Status,
+};
+
+pub const CapsuleHeader = extern struct {
+ capsule_guid: types.Guid,
+ header_size: u32,
+ flags: u32,
+ capsule_image_size: u32,
+};
+
+pub const variable_non_volatile: u32 = 0x00000001;
+pub const variable_bootservice_access: u32 = 0x00000002;
+pub const variable_runtime_access: u32 = 0x00000004;
+pub const variable_hardware_error_record: u32 = 0x00000008;
+pub const variable_authenticated_write_access: u32 = 0x00000010;
+pub const variable_time_based_authenticated_write_access: u32 = 0x00000020;
+pub const variable_append_write: u32 = 0x00000040;
+pub const variable_enhanced_authenticated_access: u32 = 0x00000080;
+
+pub const capsule_flags_persist_across_reset: u32 = 0x00010000;
+pub const capsule_flags_populate_system_table: u32 = 0x00020000;
+pub const capsule_flags_initiate_reset: u32 = 0x00040000;
diff --git a/hikari/efi/services/services.zig b/hikari/efi/services/services.zig
new file mode 100644
index 0000000..08b0ef3
--- /dev/null
+++ b/hikari/efi/services/services.zig
@@ -0,0 +1,9 @@
+//! Hikari EFI Services
+
+pub const boot = @import("boot.zig");
+pub const runtime = @import("runtime.zig");
+pub const system = @import("system.zig");
+
+pub const BootServices = boot.BootServices;
+pub const RuntimeServices = runtime.RuntimeServices;
+pub const SystemTable = system.SystemTable;
diff --git a/hikari/efi/services/system.zig b/hikari/efi/services/system.zig
new file mode 100644
index 0000000..d0dc301
--- /dev/null
+++ b/hikari/efi/services/system.zig
@@ -0,0 +1,24 @@
+//! Hikari EFI System Table
+
+const types = @import("../types/types.zig");
+const table = @import("../types/table.zig");
+const boot = @import("boot.zig");
+const runtime = @import("runtime.zig");
+const simple_text_input = @import("../protocols/simple_text_input.zig");
+const simple_text_output = @import("../protocols/simple_text_output.zig");
+
+pub const SystemTable = extern struct {
+ header: table.TableHeader,
+ firmware_vendor: [*:0]const types.Char16,
+ firmware_revision: u32,
+ console_input_handle: types.Handle,
+ console_input: *simple_text_input.SimpleTextInputProtocol,
+ console_output_handle: types.Handle,
+ console_output: *simple_text_output.SimpleTextOutputProtocol,
+ standard_error_handle: types.Handle,
+ standard_error: *simple_text_output.SimpleTextOutputProtocol,
+ runtime_services: *runtime.RuntimeServices,
+ boot_services: *boot.BootServices,
+ number_of_table_entries: usize,
+ configuration_table: [*]table.ConfigurationTableEntry,
+};
diff --git a/hikari/efi/types/base.zig b/hikari/efi/types/base.zig
new file mode 100644
index 0000000..6df9ddf
--- /dev/null
+++ b/hikari/efi/types/base.zig
@@ -0,0 +1,33 @@
+//! Hikari EFI Base Types
+
+pub const Handle = *opaque {};
+pub const Event = *opaque {};
+pub const Status = usize;
+pub const PhysicalAddress = u64;
+pub const VirtualAddress = u64;
+pub const Char8 = u8;
+pub const Char16 = u16;
+pub const Boolean = bool;
+pub const Lba = u64;
+
+pub const Guid = extern struct {
+ time_low: u32,
+ time_mid: u16,
+ time_high_and_version: u16,
+ clock_sequence_and_node: [8]u8,
+
+ pub fn equals(self: Guid, other: Guid) bool {
+ return self.time_low == other.time_low and
+ self.time_mid == other.time_mid and
+ self.time_high_and_version == other.time_high_and_version and
+ @as(u64, @bitCast(self.clock_sequence_and_node)) == @as(u64, @bitCast(other.clock_sequence_and_node));
+ }
+};
+
+pub fn is_error(status: Status) bool {
+ return (status >> 63) == 1;
+}
+
+pub fn is_success(status: Status) bool {
+ return status == 0;
+}
diff --git a/hikari/efi/types/block.zig b/hikari/efi/types/block.zig
new file mode 100644
index 0000000..46b0ff6
--- /dev/null
+++ b/hikari/efi/types/block.zig
@@ -0,0 +1,18 @@
+//! Hikari EFI Block I/O Types
+
+const base = @import("base.zig");
+
+pub const BlockIoMedia = extern struct {
+ media_id: u32,
+ removable_media: bool,
+ media_present: bool,
+ logical_partition: bool,
+ read_only: bool,
+ write_caching: bool,
+ block_size: u32,
+ io_align: u32,
+ last_block: base.Lba,
+ lowest_aligned_lba: base.Lba,
+ logical_blocks_per_physical_block: u32,
+ optimal_transfer_length_granularity: u32,
+};
diff --git a/hikari/efi/types/graphics.zig b/hikari/efi/types/graphics.zig
new file mode 100644
index 0000000..dca2097
--- /dev/null
+++ b/hikari/efi/types/graphics.zig
@@ -0,0 +1,49 @@
+//! Hikari EFI Graphics Types
+
+const base = @import("base.zig");
+
+pub const PixelFormat = enum(u32) {
+ rgb = 0,
+ bgr = 1,
+ bitmask = 2,
+ blt_only = 3,
+};
+
+pub const PixelBitmask = extern struct {
+ red_mask: u32,
+ green_mask: u32,
+ blue_mask: u32,
+ reserved_mask: u32,
+};
+
+pub const BltPixel = extern struct {
+ blue: u8,
+ green: u8,
+ red: u8,
+ reserved: u8,
+};
+
+pub const BltOperation = enum(u32) {
+ video_fill = 0,
+ video_to_buffer = 1,
+ buffer_to_video = 2,
+ video_to_video = 3,
+};
+
+pub const GraphicsOutputModeInformation = extern struct {
+ version: u32,
+ horizontal_resolution: u32,
+ vertical_resolution: u32,
+ pixel_format: PixelFormat,
+ pixel_information: PixelBitmask,
+ pixels_per_scan_line: u32,
+};
+
+pub const GraphicsOutputProtocolMode = extern struct {
+ max_mode: u32,
+ mode: u32,
+ info: *GraphicsOutputModeInformation,
+ size_of_info: usize,
+ framebuffer_base: base.PhysicalAddress,
+ framebuffer_size: usize,
+};
diff --git a/hikari/efi/types/input.zig b/hikari/efi/types/input.zig
new file mode 100644
index 0000000..2daa966
--- /dev/null
+++ b/hikari/efi/types/input.zig
@@ -0,0 +1,8 @@
+//! Hikari EFI Input Types
+
+const base = @import("base.zig");
+
+pub const InputKey = extern struct {
+ scan_code: u16,
+ unicode_char: base.Char16,
+};
diff --git a/hikari/efi/types/memory.zig b/hikari/efi/types/memory.zig
new file mode 100644
index 0000000..5775ad0
--- /dev/null
+++ b/hikari/efi/types/memory.zig
@@ -0,0 +1,42 @@
+//! Hikari EFI Memory Types
+
+const base = @import("base.zig");
+
+pub const MemoryType = enum(u32) {
+ reserved = 0,
+ loader_code = 1,
+ loader_data = 2,
+ boot_services_code = 3,
+ boot_services_data = 4,
+ runtime_services_code = 5,
+ runtime_services_data = 6,
+ conventional = 7,
+ unusable = 8,
+ acpi_reclaim = 9,
+ acpi_nvs = 10,
+ mmio = 11,
+ mmio_port_space = 12,
+ pal_code = 13,
+ persistent = 14,
+ unaccepted = 15,
+};
+
+pub const AllocateType = enum(u32) {
+ any_pages = 0,
+ max_address = 1,
+ address = 2,
+};
+
+pub const MemoryDescriptor = extern struct {
+ memory_type: MemoryType,
+ physical_start: base.PhysicalAddress,
+ virtual_start: base.VirtualAddress,
+ number_of_pages: u64,
+ attribute: u64,
+};
+
+pub const LocateSearchType = enum(u32) {
+ all_handles = 0,
+ by_register_notify = 1,
+ by_protocol = 2,
+};
diff --git a/hikari/efi/types/reset.zig b/hikari/efi/types/reset.zig
new file mode 100644
index 0000000..4bf206f
--- /dev/null
+++ b/hikari/efi/types/reset.zig
@@ -0,0 +1,8 @@
+//! Hikari EFI Reset Types
+
+pub const ResetType = enum(u32) {
+ cold = 0,
+ warm = 1,
+ shutdown = 2,
+ platform_specific = 3,
+};
diff --git a/hikari/efi/types/table.zig b/hikari/efi/types/table.zig
new file mode 100644
index 0000000..efcfd4b
--- /dev/null
+++ b/hikari/efi/types/table.zig
@@ -0,0 +1,16 @@
+//! Hikari EFI Table Types
+
+const base = @import("base.zig");
+
+pub const TableHeader = extern struct {
+ signature: u64,
+ revision: u32,
+ header_size: u32,
+ crc32: u32,
+ reserved: u32,
+};
+
+pub const ConfigurationTableEntry = extern struct {
+ vendor_guid: base.Guid,
+ vendor_table: *anyopaque,
+};
diff --git a/hikari/efi/types/time.zig b/hikari/efi/types/time.zig
new file mode 100644
index 0000000..6b860f5
--- /dev/null
+++ b/hikari/efi/types/time.zig
@@ -0,0 +1,26 @@
+//! Hikari EFI Time Types
+
+pub const Time = extern struct {
+ year: u16,
+ month: u8,
+ day: u8,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ pad1: u8,
+ nanosecond: u32,
+ time_zone: i16,
+ daylight: u8,
+ pad2: u8,
+};
+
+pub const TimeCapabilities = extern struct {
+ resolution: u32,
+ accuracy: u32,
+ sets_to_zero: bool,
+};
+
+pub const timezone_unspecified: i16 = 0x07FF;
+
+pub const daylight_adjust: u8 = 0x01;
+pub const daylight_time: u8 = 0x02;
diff --git a/hikari/efi/types/types.zig b/hikari/efi/types/types.zig
new file mode 100644
index 0000000..bcee99c
--- /dev/null
+++ b/hikari/efi/types/types.zig
@@ -0,0 +1,23 @@
+//! Hikari EFI Types
+
+pub const base = @import("base.zig");
+pub const memory = @import("memory.zig");
+pub const table = @import("table.zig");
+pub const time = @import("time.zig");
+pub const input = @import("input.zig");
+pub const graphics = @import("graphics.zig");
+pub const unit = @import("unit.zig");
+pub const block = @import("block.zig");
+pub const reset = @import("reset.zig");
+
+pub const Handle = base.Handle;
+pub const Event = base.Event;
+pub const Status = base.Status;
+pub const PhysicalAddress = base.PhysicalAddress;
+pub const VirtualAddress = base.VirtualAddress;
+pub const Char16 = base.Char16;
+pub const Guid = base.Guid;
+pub const Lba = base.Lba;
+
+pub const is_error = base.is_error;
+pub const is_success = base.is_success;
diff --git a/hikari/efi/types/unit.zig b/hikari/efi/types/unit.zig
new file mode 100644
index 0000000..8ad8783
--- /dev/null
+++ b/hikari/efi/types/unit.zig
@@ -0,0 +1,24 @@
+//! Hikari EFI Unit Types
+
+const base = @import("base.zig");
+const time = @import("time.zig");
+
+pub const UnitInfo = extern struct {
+ size: u64,
+ unit_size: u64,
+ physical_size: u64,
+ create_time: time.Time,
+ last_access_time: time.Time,
+ modification_time: time.Time,
+ attribute: u64,
+ unit_name: [256]base.Char16,
+};
+
+pub const UnitSystemInfo = extern struct {
+ size: u64,
+ read_only: bool,
+ volume_size: u64,
+ free_space: u64,
+ block_size: u32,
+ volume_label: [256]base.Char16,
+};
diff --git a/hikari/fs/afs/afs.zig b/hikari/fs/afs/afs.zig
new file mode 100644
index 0000000..5551b95
--- /dev/null
+++ b/hikari/fs/afs/afs.zig
@@ -0,0 +1,30 @@
+//! Hikari AFS UnitSystem
+
+pub const constants = @import("constants.zig");
+pub const types = @import("types.zig");
+pub const btree = @import("btree.zig");
+pub const reader = @import("reader.zig");
+
+pub const VolumeHeader = types.VolumeHeader;
+pub const SpanDescriptor = types.SpanDescriptor;
+pub const ChannelInfo = types.ChannelInfo;
+pub const BTreeNodeDescriptor = types.BTreeNodeDescriptor;
+pub const BTreeHeaderRecord = types.BTreeHeaderRecord;
+pub const IndexKey = types.IndexKey;
+pub const StackRecord = types.StackRecord;
+pub const UnitRecord = types.UnitRecord;
+pub const ThreadRecord = types.ThreadRecord;
+pub const SpanKey = types.SpanKey;
+pub const SpanRecord = types.SpanRecord;
+pub const Permissions = types.Permissions;
+pub const AliasInfo = types.AliasInfo;
+pub const TwinInfo = types.TwinInfo;
+pub const JournalInfoCell = types.JournalInfoCell;
+pub const JournalHeader = types.JournalHeader;
+pub const Timestamp = types.Timestamp;
+
+pub const BTree = btree.BTree;
+pub const BTreeError = btree.BTreeError;
+
+pub const Reader = reader.Reader;
+pub const ReadError = reader.ReadError;
diff --git a/hikari/fs/afs/btree.zig b/hikari/fs/afs/btree.zig
new file mode 100644
index 0000000..88b6dc0
--- /dev/null
+++ b/hikari/fs/afs/btree.zig
@@ -0,0 +1,354 @@
+//! Hikari AFS B-Tree
+
+const efi = @import("../../efi/efi.zig");
+const constants = @import("constants.zig");
+const types = @import("types.zig");
+
+pub const BTreeError = error{
+ read_failed,
+ invalid_node,
+ invalid_header,
+ key_not_found,
+ tree_empty,
+ allocation_failed,
+};
+
+pub const BTree = struct {
+ block_io: *efi.protocols.BlockIoProtocol,
+ boot_services: *efi.services.BootServices,
+ partition_start_lba: u64,
+ cell_size: u32,
+ base_span: types.SpanDescriptor,
+ node_size: u32,
+ root_node: u32,
+ first_leaf: u32,
+ last_leaf: u32,
+ depth: u16,
+ node_buffer: [*]u8,
+
+ pub fn initialize(
+ block_io: *efi.protocols.BlockIoProtocol,
+ boot_services: *efi.services.BootServices,
+ partition_start_lba: u64,
+ cell_size: u32,
+ base_span: types.SpanDescriptor,
+ header: *const types.BTreeHeaderRecord,
+ ) BTreeError!BTree {
+ var node_buffer: [*]align(8) u8 = undefined;
+ const alloc_status = boot_services.allocate_pool(
+ .loader_data,
+ header.node_size,
+ &node_buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return BTreeError.allocation_failed;
+ }
+
+ return BTree{
+ .block_io = block_io,
+ .boot_services = boot_services,
+ .partition_start_lba = partition_start_lba,
+ .cell_size = cell_size,
+ .base_span = base_span,
+ .node_size = header.node_size,
+ .root_node = header.root_node,
+ .first_leaf = header.first_leaf_node,
+ .last_leaf = header.last_leaf_node,
+ .depth = header.depth,
+ .node_buffer = node_buffer,
+ };
+ }
+
+ pub fn read_node(self: *BTree, node_number: u32) BTreeError!*types.BTreeNodeDescriptor {
+ const nodes_per_cell = self.cell_size / self.node_size;
+ const cell_offset = node_number / nodes_per_cell;
+ const node_offset_in_cell = (node_number % nodes_per_cell) * self.node_size;
+
+ const cell_lba = self.partition_start_lba +
+ ((self.base_span.start_cell + cell_offset) * self.cell_size / self.block_io.media.block_size);
+
+ var cell_buffer: [*]align(8) u8 = undefined;
+ const alloc_status = self.boot_services.allocate_pool(
+ .loader_data,
+ self.cell_size,
+ &cell_buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return BTreeError.allocation_failed;
+ }
+
+ const read_status = self.block_io.read_blocks(
+ self.block_io,
+ self.block_io.media.media_id,
+ cell_lba,
+ self.cell_size,
+ cell_buffer,
+ );
+ if (efi.types.is_error(read_status)) {
+ return BTreeError.read_failed;
+ }
+
+ const node_ptr = cell_buffer + node_offset_in_cell;
+ var i: u32 = 0;
+ while (i < self.node_size) : (i += 1) {
+ self.node_buffer[i] = node_ptr[i];
+ }
+
+ _ = self.boot_services.free_pool(cell_buffer);
+
+ return @ptrCast(@alignCast(self.node_buffer));
+ }
+
+ pub fn get_record_offset(self: *BTree, record_index: u16) u16 {
+ const offset_table_start = self.node_size - (@as(u32, record_index) + 1) * 2;
+ const offset_ptr: *align(1) const u16 = @ptrCast(self.node_buffer + offset_table_start);
+ return offset_ptr.*;
+ }
+
+ pub fn get_record_ptr(self: *BTree, record_index: u16) [*]u8 {
+ const offset = self.get_record_offset(record_index);
+ return self.node_buffer + offset;
+ }
+
+ pub fn search_index(
+ self: *BTree,
+ parent_node_id: u32,
+ identity: []const u16,
+ ) BTreeError!?*const types.UnitRecord {
+ if (self.depth == 0) {
+ return BTreeError.tree_empty;
+ }
+
+ var current_node = self.root_node;
+ var current_depth: u16 = self.depth;
+
+ while (current_depth > 0) {
+ const node_desc = try self.read_node(current_node);
+
+ if (node_desc.is_leaf()) {
+ return self.search_leaf_for_unit(node_desc, parent_node_id, identity);
+ }
+
+ const next_node = self.search_index_node(node_desc, parent_node_id, identity);
+ if (next_node) |node| {
+ current_node = node;
+ current_depth -= 1;
+ } else {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ fn search_index_node(
+ self: *BTree,
+ node_desc: *types.BTreeNodeDescriptor,
+ parent_node_id: u32,
+ identity: []const u16,
+ ) ?u32 {
+ var i: u16 = 0;
+ while (i < node_desc.record_count) : (i += 1) {
+ const record_ptr = self.get_record_ptr(i);
+ const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr));
+
+ const cmp = compare_keys(parent_node_id, identity, key);
+ if (cmp <= 0) {
+ const child_ptr: *align(1) const u32 = @ptrCast(record_ptr + key.key_length + 2);
+ return child_ptr.*;
+ }
+ }
+
+ if (node_desc.record_count > 0) {
+ const last_record = self.get_record_ptr(node_desc.record_count - 1);
+ const last_key: *const types.IndexKey = @ptrCast(@alignCast(last_record));
+ const child_ptr: *align(1) const u32 = @ptrCast(last_record + last_key.key_length + 2);
+ return child_ptr.*;
+ }
+
+ return null;
+ }
+
+ fn search_leaf_for_unit(
+ self: *BTree,
+ node_desc: *types.BTreeNodeDescriptor,
+ parent_node_id: u32,
+ identity: []const u16,
+ ) ?*const types.UnitRecord {
+ var i: u16 = 0;
+ while (i < node_desc.record_count) : (i += 1) {
+ const record_ptr = self.get_record_ptr(i);
+ const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr));
+
+ const cmp = compare_keys(parent_node_id, identity, key);
+ if (cmp == 0) {
+ const record_start = record_ptr + key.key_length + 2;
+ const record_type_ptr: *align(1) const u16 = @ptrCast(record_start);
+ const record_type = record_type_ptr.*;
+
+ if (record_type == constants.index_record_type_unit) {
+ return @ptrCast(@alignCast(record_start));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ pub fn search_index_for_stack(
+ self: *BTree,
+ parent_node_id: u32,
+ identity: []const u16,
+ ) BTreeError!?*const types.StackRecord {
+ if (self.depth == 0) {
+ return BTreeError.tree_empty;
+ }
+
+ var current_node = self.root_node;
+ var current_depth: u16 = self.depth;
+
+ while (current_depth > 0) {
+ const node_desc = try self.read_node(current_node);
+
+ if (node_desc.is_leaf()) {
+ return self.search_leaf_for_stack(node_desc, parent_node_id, identity);
+ }
+
+ const next_node = self.search_index_node(node_desc, parent_node_id, identity);
+ if (next_node) |node| {
+ current_node = node;
+ current_depth -= 1;
+ } else {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ fn search_leaf_for_stack(
+ self: *BTree,
+ node_desc: *types.BTreeNodeDescriptor,
+ parent_node_id: u32,
+ identity: []const u16,
+ ) ?*const types.StackRecord {
+ var i: u16 = 0;
+ while (i < node_desc.record_count) : (i += 1) {
+ const record_ptr = self.get_record_ptr(i);
+ const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr));
+
+ const cmp = compare_keys(parent_node_id, identity, key);
+ if (cmp == 0) {
+ const record_start = record_ptr + key.key_length + 2;
+ const record_type_ptr: *align(1) const u16 = @ptrCast(record_start);
+ const record_type = record_type_ptr.*;
+
+ if (record_type == constants.index_record_type_stack) {
+ return @ptrCast(@alignCast(record_start));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ pub fn get_thread_record(
+ self: *BTree,
+ node_id: u32,
+ ) BTreeError!?*const types.ThreadRecord {
+ var empty_identity: [0]u16 = undefined;
+ return self.search_thread(node_id, &empty_identity);
+ }
+
+ fn search_thread(
+ self: *BTree,
+ node_id: u32,
+ identity: []const u16,
+ ) BTreeError!?*const types.ThreadRecord {
+ if (self.depth == 0) {
+ return BTreeError.tree_empty;
+ }
+
+ var current_node = self.root_node;
+ var current_depth: u16 = self.depth;
+
+ while (current_depth > 0) {
+ const node_desc = try self.read_node(current_node);
+
+ if (node_desc.is_leaf()) {
+ return self.search_leaf_for_thread(node_desc, node_id, identity);
+ }
+
+ const next_node = self.search_index_node(node_desc, node_id, identity);
+ if (next_node) |node| {
+ current_node = node;
+ current_depth -= 1;
+ } else {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ fn search_leaf_for_thread(
+ self: *BTree,
+ node_desc: *types.BTreeNodeDescriptor,
+ node_id: u32,
+ identity: []const u16,
+ ) ?*const types.ThreadRecord {
+ var i: u16 = 0;
+ while (i < node_desc.record_count) : (i += 1) {
+ const record_ptr = self.get_record_ptr(i);
+ const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr));
+
+ const cmp = compare_keys(node_id, identity, key);
+ if (cmp == 0) {
+ const record_start = record_ptr + key.key_length + 2;
+ const record_type_ptr: *align(1) const u16 = @ptrCast(record_start);
+ const record_type = record_type_ptr.*;
+
+ if (record_type == constants.index_record_type_stack_thread or
+ record_type == constants.index_record_type_unit_thread)
+ {
+ return @ptrCast(@alignCast(record_start));
+ }
+ }
+ }
+
+ return null;
+ }
+};
+
+fn compare_keys(parent_node_id: u32, identity: []const u16, key: *const types.IndexKey) i32 {
+ if (parent_node_id < key.parent_node_id) {
+ return -1;
+ }
+ if (parent_node_id > key.parent_node_id) {
+ return 1;
+ }
+
+ const key_identity_len = key.get_identity_length();
+ const min_len = if (identity.len < key_identity_len) identity.len else key_identity_len;
+
+ var i: usize = 0;
+ while (i < min_len) : (i += 1) {
+ const a = identity[i];
+ const b = key.identity[i];
+ if (a < b) {
+ return -1;
+ }
+ if (a > b) {
+ return 1;
+ }
+ }
+
+ if (identity.len < key_identity_len) {
+ return -1;
+ }
+ if (identity.len > key_identity_len) {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/hikari/fs/afs/constants.zig b/hikari/fs/afs/constants.zig
new file mode 100644
index 0000000..892fef1
--- /dev/null
+++ b/hikari/fs/afs/constants.zig
@@ -0,0 +1,66 @@
+//! Hikari AFS Constants
+
+pub const signature: u64 = 0x2153464142494B41; // "AKIBAFS!"
+pub const version: u16 = 0x0001;
+
+pub const volume_header_cell: u64 = 0;
+pub const volume_header_size: u32 = 512;
+pub const alternate_volume_header_offset: u64 = 1024;
+
+pub const default_cell_size: u32 = 4096;
+pub const minimum_cell_size: u32 = 512;
+pub const maximum_cell_size: u32 = 65536;
+
+pub const max_identity_length: usize = 1024;
+
+pub const special_node_id_origin: u32 = 1;
+pub const special_node_id_origin_stack: u32 = 2;
+pub const special_node_id_span_overflow: u32 = 3;
+pub const special_node_id_index: u32 = 4;
+pub const special_node_id_attributes: u32 = 5;
+pub const special_node_id_allocation_map: u32 = 6;
+pub const special_node_id_startup: u32 = 7;
+pub const special_node_id_repair: u32 = 8;
+pub const first_user_node_id: u32 = 16;
+
+pub const index_record_type_stack: u16 = 0x0001;
+pub const index_record_type_unit: u16 = 0x0002;
+pub const index_record_type_stack_thread: u16 = 0x0003;
+pub const index_record_type_unit_thread: u16 = 0x0004;
+
+pub const btree_node_type_leaf: i8 = -1;
+pub const btree_node_type_index: i8 = 0;
+pub const btree_node_type_header: i8 = 1;
+pub const btree_node_type_map: i8 = 2;
+
+pub const btree_header_node_number: u32 = 0;
+
+pub const channel_data: u8 = 0x00;
+pub const channel_resource: u8 = 0xFF;
+
+pub const unit_flag_locked: u16 = 0x0001;
+pub const unit_flag_has_thread: u16 = 0x0002;
+pub const unit_flag_has_alias: u16 = 0x0004;
+pub const unit_flag_has_security: u16 = 0x0008;
+pub const unit_flag_has_twins: u16 = 0x0010;
+pub const unit_flag_has_resource_channel: u16 = 0x0020;
+
+pub const compression_none: u32 = 0;
+pub const compression_zlib: u32 = 1;
+pub const compression_lz4: u32 = 2;
+pub const compression_zstd: u32 = 3;
+
+pub const encryption_none: u32 = 0;
+pub const encryption_aes_128_xts: u32 = 1;
+pub const encryption_aes_256_xts: u32 = 2;
+
+pub const attribute_inline_data_max: u32 = 3802;
+
+pub const journal_signature: u32 = 0x4A4E524C; // "JNRL"
+pub const journal_header_size: u32 = 512;
+pub const journal_info_cell: u64 = 2;
+
+pub const journal_info_on_other_device: u32 = 0x00000001;
+pub const journal_info_needs_init: u32 = 0x00000002;
+
+pub const span_inline_count: usize = 8;
diff --git a/hikari/fs/afs/reader.zig b/hikari/fs/afs/reader.zig
new file mode 100644
index 0000000..1bb74d2
--- /dev/null
+++ b/hikari/fs/afs/reader.zig
@@ -0,0 +1,333 @@
+//! Hikari AFS Reader
+
+const efi = @import("../../efi/efi.zig");
+const constants = @import("constants.zig");
+const types = @import("types.zig");
+const btree = @import("btree.zig");
+
+pub const ReadError = error{
+ invalid_volume_header,
+ read_failed,
+ allocation_failed,
+ not_found,
+ not_a_stack,
+ unit_too_large,
+ invalid_span,
+ btree_error,
+};
+
+pub const Reader = struct {
+ block_io: *efi.protocols.BlockIoProtocol,
+ boot_services: *efi.services.BootServices,
+ partition_start_lba: u64,
+ volume_header: types.VolumeHeader,
+ cell_size: u32,
+ index: btree.BTree,
+ span_overflow: ?btree.BTree,
+ cell_buffer: [*]u8,
+
+ pub fn initialize(
+ block_io: *efi.protocols.BlockIoProtocol,
+ boot_services: *efi.services.BootServices,
+ partition_start_lba: u64,
+ ) ReadError!Reader {
+ const block_size = block_io.media.block_size;
+
+ var sector_buffer: [*]align(8) u8 = undefined;
+ var alloc_status = boot_services.allocate_pool(
+ .loader_data,
+ block_size,
+ &sector_buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return ReadError.allocation_failed;
+ }
+
+ const read_status = block_io.read_blocks(
+ block_io,
+ block_io.media.media_id,
+ partition_start_lba,
+ block_size,
+ sector_buffer,
+ );
+ if (efi.types.is_error(read_status)) {
+ return ReadError.read_failed;
+ }
+
+ const volume_header: *const types.VolumeHeader = @ptrCast(@alignCast(sector_buffer));
+ if (!volume_header.is_valid()) {
+ return ReadError.invalid_volume_header;
+ }
+
+ const cell_size = volume_header.cell_size;
+
+ var cell_buffer: [*]align(8) u8 = undefined;
+ alloc_status = boot_services.allocate_pool(
+ .loader_data,
+ cell_size,
+ &cell_buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return ReadError.allocation_failed;
+ }
+
+ const index_header = try read_btree_header(
+ block_io,
+ boot_services,
+ partition_start_lba,
+ cell_size,
+ volume_header.index_span,
+ );
+
+ const index = btree.BTree.initialize(
+ block_io,
+ boot_services,
+ partition_start_lba,
+ cell_size,
+ volume_header.index_span,
+ index_header,
+ ) catch {
+ return ReadError.btree_error;
+ };
+
+ var span_overflow: ?btree.BTree = null;
+ if (!volume_header.span_overflow_span.is_empty()) {
+ const span_overflow_header = try read_btree_header(
+ block_io,
+ boot_services,
+ partition_start_lba,
+ cell_size,
+ volume_header.span_overflow_span,
+ );
+
+ span_overflow = btree.BTree.initialize(
+ block_io,
+ boot_services,
+ partition_start_lba,
+ cell_size,
+ volume_header.span_overflow_span,
+ span_overflow_header,
+ ) catch null;
+ }
+
+ const header_copy = volume_header.*;
+
+ _ = boot_services.free_pool(sector_buffer);
+
+ return Reader{
+ .block_io = block_io,
+ .boot_services = boot_services,
+ .partition_start_lba = partition_start_lba,
+ .volume_header = header_copy,
+ .cell_size = cell_size,
+ .index = index,
+ .span_overflow = span_overflow,
+ .cell_buffer = cell_buffer,
+ };
+ }
+
+ pub fn open_location(self: *Reader, location: []const u8) ReadError!types.UnitRecord {
+ var current_node_id: u32 = constants.special_node_id_origin_stack;
+
+ var start: usize = 0;
+ if (location.len > 0 and (location[0] == '/' or location[0] == '\\')) {
+ start = 1;
+ }
+
+ var iter_start = start;
+ var last_unit: ?types.UnitRecord = null;
+
+ while (iter_start < location.len) {
+ var iter_end = iter_start;
+ while (iter_end < location.len and location[iter_end] != '/' and location[iter_end] != '\\') {
+ iter_end += 1;
+ }
+
+ if (iter_end == iter_start) {
+ iter_start = iter_end + 1;
+ continue;
+ }
+
+ const component = location[iter_start..iter_end];
+
+ var identity_utf16: [constants.max_identity_length]u16 = undefined;
+ var identity_len: usize = 0;
+ for (component) |byte| {
+ identity_utf16[identity_len] = byte;
+ identity_len += 1;
+ }
+ const identity = identity_utf16[0..identity_len];
+
+ const stack_record = self.index.search_index_for_stack(current_node_id, identity) catch {
+ return ReadError.btree_error;
+ };
+
+ if (stack_record) |stack| {
+ current_node_id = stack.node_id;
+ iter_start = iter_end + 1;
+ continue;
+ }
+
+ const unit_record = self.index.search_index(current_node_id, identity) catch {
+ return ReadError.btree_error;
+ };
+
+ if (unit_record) |unit| {
+ if (iter_end >= location.len) {
+ last_unit = unit.*;
+ break;
+ } else {
+ return ReadError.not_a_stack;
+ }
+ }
+
+ return ReadError.not_found;
+ }
+
+ if (last_unit) |unit| {
+ return unit;
+ }
+
+ return ReadError.not_found;
+ }
+
+ pub fn read_cell(self: *Reader, cell_number: u64) ReadError!void {
+ const cell_lba = self.partition_start_lba +
+ (cell_number * self.cell_size / self.block_io.media.block_size);
+
+ const read_status = self.block_io.read_blocks(
+ self.block_io,
+ self.block_io.media.media_id,
+ cell_lba,
+ self.cell_size,
+ self.cell_buffer,
+ );
+ if (efi.types.is_error(read_status)) {
+ return ReadError.read_failed;
+ }
+ }
+
+ pub fn read_unit(self: *Reader, unit: *const types.UnitRecord, buffer: [*]u8, max_size: u64) ReadError!u64 {
+ const channel = &unit.data_channel;
+ const unit_size = channel.logical_size;
+
+ if (unit_size > max_size) {
+ return ReadError.unit_too_large;
+ }
+
+ var bytes_read: u64 = 0;
+ var span_index: usize = 0;
+
+ while (bytes_read < unit_size and span_index < constants.span_inline_count) {
+ const span = channel.get_span(span_index);
+ if (span == null) {
+ break;
+ }
+
+ const span_bytes = span.?.byte_size(self.cell_size);
+ const bytes_remaining = unit_size - bytes_read;
+ const bytes_to_read = if (bytes_remaining < span_bytes) bytes_remaining else span_bytes;
+
+ try self.read_span_data(span.?, buffer + bytes_read, bytes_to_read);
+ bytes_read += bytes_to_read;
+ span_index += 1;
+ }
+
+ if (bytes_read < unit_size and self.span_overflow != null) {
+ bytes_read = try self.read_overflow_spans(unit, buffer, bytes_read, unit_size);
+ }
+
+ return bytes_read;
+ }
+
+ fn read_span_data(self: *Reader, span: *const types.SpanDescriptor, buffer: [*]u8, size: u64) ReadError!void {
+ var bytes_read: u64 = 0;
+ var current_cell = span.start_cell;
+
+ while (bytes_read < size) {
+ try self.read_cell(current_cell);
+
+ const bytes_remaining = size - bytes_read;
+ const bytes_to_copy = if (bytes_remaining < self.cell_size) bytes_remaining else self.cell_size;
+
+ var i: u64 = 0;
+ while (i < bytes_to_copy) : (i += 1) {
+ buffer[bytes_read + i] = self.cell_buffer[i];
+ }
+
+ bytes_read += bytes_to_copy;
+ current_cell += 1;
+ }
+ }
+
+ fn read_overflow_spans(
+ self: *Reader,
+ unit: *const types.UnitRecord,
+ buffer: [*]u8,
+ start_offset: u64,
+ total_size: u64,
+ ) ReadError!u64 {
+ _ = self;
+ _ = unit;
+ _ = buffer;
+ _ = start_offset;
+ return total_size;
+ }
+
+ pub fn read_unit_to_allocated(self: *Reader, unit: *const types.UnitRecord) ReadError!struct { buffer: [*]u8, size: u64 } {
+ const unit_size = unit.data_channel.logical_size;
+
+ var buffer: [*]align(8) u8 = undefined;
+ const alloc_status = self.boot_services.allocate_pool(
+ .loader_data,
+ unit_size,
+ &buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return ReadError.allocation_failed;
+ }
+
+ const bytes_read = try self.read_unit(unit, buffer, unit_size);
+ return .{ .buffer = buffer, .size = bytes_read };
+ }
+};
+
+fn read_btree_header(
+ block_io: *efi.protocols.BlockIoProtocol,
+ boot_services: *efi.services.BootServices,
+ partition_start_lba: u64,
+ cell_size: u32,
+ span: types.SpanDescriptor,
+) ReadError!*const types.BTreeHeaderRecord {
+ var cell_buffer: [*]align(8) u8 = undefined;
+ const alloc_status = boot_services.allocate_pool(
+ .loader_data,
+ cell_size,
+ &cell_buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return ReadError.allocation_failed;
+ }
+
+ const cell_lba = partition_start_lba +
+ (span.start_cell * cell_size / block_io.media.block_size);
+
+ const read_status = block_io.read_blocks(
+ block_io,
+ block_io.media.media_id,
+ cell_lba,
+ cell_size,
+ cell_buffer,
+ );
+ if (efi.types.is_error(read_status)) {
+ return ReadError.read_failed;
+ }
+
+ const node_desc: *const types.BTreeNodeDescriptor = @ptrCast(@alignCast(cell_buffer));
+ if (!node_desc.is_header()) {
+ return ReadError.read_failed;
+ }
+
+ const header_offset = @sizeOf(types.BTreeNodeDescriptor);
+ return @ptrCast(@alignCast(cell_buffer + header_offset));
+}
diff --git a/hikari/fs/afs/types.zig b/hikari/fs/afs/types.zig
new file mode 100644
index 0000000..bf9d1a8
--- /dev/null
+++ b/hikari/fs/afs/types.zig
@@ -0,0 +1,336 @@
+//! Hikari AFS Types
+
+const constants = @import("constants.zig");
+
+pub const VolumeHeader = extern struct {
+ signature: u64,
+ version: u16,
+ attributes: u32,
+ last_bind_timestamp: u64,
+ last_check_timestamp: u64,
+ creation_timestamp: u64,
+ modification_timestamp: u64,
+ backup_timestamp: u64,
+ checked_timestamp: u64,
+ unit_count: u32,
+ stack_count: u32,
+ cell_size: u32,
+ total_cells: u32,
+ free_cells: u32,
+ next_node_id: u32,
+ write_count: u32,
+ encoding_bitmap: u64,
+ allocation_map_size: u32,
+ allocation_map_clump: u32,
+ index_node_size: u32,
+ index_total_nodes: u32,
+ index_free_nodes: u32,
+ index_clump_size: u32,
+ index_root_node: u32,
+ index_first_leaf: u32,
+ index_last_leaf: u32,
+ index_depth: u16,
+ index_record_count: u32,
+ span_overflow_node_size: u32,
+ span_overflow_total_nodes: u32,
+ span_overflow_free_nodes: u32,
+ span_overflow_clump_size: u32,
+ span_overflow_root_node: u32,
+ span_overflow_first_leaf: u32,
+ span_overflow_last_leaf: u32,
+ span_overflow_depth: u16,
+ span_overflow_record_count: u32,
+ attributes_node_size: u32,
+ attributes_total_nodes: u32,
+ attributes_free_nodes: u32,
+ attributes_clump_size: u32,
+ attributes_root_node: u32,
+ attributes_first_leaf: u32,
+ attributes_last_leaf: u32,
+ attributes_depth: u16,
+ attributes_record_count: u32,
+ allocation_map_span: SpanDescriptor,
+ index_span: SpanDescriptor,
+ span_overflow_span: SpanDescriptor,
+ attributes_span: SpanDescriptor,
+ startup_span: SpanDescriptor,
+ journal_info_cell: u64,
+ journal_info_size: u32,
+ compression_type: u32,
+ encryption_type: u32,
+ reserved: [64]u8,
+
+ pub fn is_valid(self: *const VolumeHeader) bool {
+ if (self.signature != constants.signature) {
+ return false;
+ }
+ if (self.version != constants.version) {
+ return false;
+ }
+ if (self.cell_size < constants.minimum_cell_size or
+ self.cell_size > constants.maximum_cell_size)
+ {
+ return false;
+ }
+ return true;
+ }
+};
+
+pub const SpanDescriptor = extern struct {
+ start_cell: u64,
+ cell_count: u64,
+
+ pub fn is_empty(self: *const SpanDescriptor) bool {
+ return self.cell_count == 0;
+ }
+
+ pub fn end_cell(self: *const SpanDescriptor) u64 {
+ return self.start_cell + self.cell_count;
+ }
+
+ pub fn contains(self: *const SpanDescriptor, cell: u64) bool {
+ return cell >= self.start_cell and cell < self.end_cell();
+ }
+
+ pub fn byte_size(self: *const SpanDescriptor, cell_size: u32) u64 {
+ return self.cell_count * cell_size;
+ }
+};
+
+pub const ChannelInfo = extern struct {
+ logical_size: u64,
+ physical_size: u64,
+ clump_size: u32,
+ total_cells: u32,
+ spans: [constants.span_inline_count]SpanDescriptor,
+
+ pub fn get_span(self: *const ChannelInfo, index: usize) ?*const SpanDescriptor {
+ if (index >= constants.span_inline_count) {
+ return null;
+ }
+ if (self.spans[index].is_empty()) {
+ return null;
+ }
+ return &self.spans[index];
+ }
+};
+
+pub const BTreeNodeDescriptor = extern struct {
+ forward_link: u32,
+ backward_link: u32,
+ node_type: i8,
+ height: u8,
+ record_count: u16,
+ reserved: u16,
+
+ pub fn is_leaf(self: *const BTreeNodeDescriptor) bool {
+ return self.node_type == constants.btree_node_type_leaf;
+ }
+
+ pub fn is_index(self: *const BTreeNodeDescriptor) bool {
+ return self.node_type == constants.btree_node_type_index;
+ }
+
+ pub fn is_header(self: *const BTreeNodeDescriptor) bool {
+ return self.node_type == constants.btree_node_type_header;
+ }
+
+ pub fn is_map(self: *const BTreeNodeDescriptor) bool {
+ return self.node_type == constants.btree_node_type_map;
+ }
+};
+
+pub const BTreeHeaderRecord = extern struct {
+ depth: u16,
+ root_node: u32,
+ leaf_record_count: u32,
+ first_leaf_node: u32,
+ last_leaf_node: u32,
+ node_size: u16,
+ max_key_length: u16,
+ total_nodes: u32,
+ free_nodes: u32,
+ reserved1: u16,
+ clump_size: u32,
+ btree_type: u8,
+ key_compare_type: u8,
+ attributes: u32,
+ reserved2: [64]u8,
+};
+
+pub const IndexKey = extern struct {
+ key_length: u16,
+ parent_node_id: u32,
+ identity: [constants.max_identity_length]u16,
+
+ pub fn get_identity_length(self: *const IndexKey) usize {
+ const total_key_len = self.key_length;
+ if (total_key_len <= 6) {
+ return 0;
+ }
+ return (total_key_len - 6) / 2;
+ }
+};
+
+pub const StackRecord = extern struct {
+ record_type: u16,
+ flags: u16,
+ valence: u32,
+ node_id: u32,
+ creation_timestamp: u64,
+ modification_timestamp: u64,
+ attribute_modification_timestamp: u64,
+ access_timestamp: u64,
+ backup_timestamp: u64,
+ permissions: Permissions,
+ special: SpecialInfo,
+ text_encoding: u32,
+ reserved: u32,
+
+ pub fn is_empty(self: *const StackRecord) bool {
+ return self.valence == 0;
+ }
+};
+
+pub const UnitRecord = extern struct {
+ record_type: u16,
+ flags: u16,
+ reserved1: u32,
+ node_id: u32,
+ creation_timestamp: u64,
+ modification_timestamp: u64,
+ attribute_modification_timestamp: u64,
+ access_timestamp: u64,
+ backup_timestamp: u64,
+ permissions: Permissions,
+ special: SpecialInfo,
+ text_encoding: u32,
+ reserved2: u32,
+ data_channel: ChannelInfo,
+ resource_channel: ChannelInfo,
+
+ pub fn has_resource_channel(self: *const UnitRecord) bool {
+ return (self.flags & constants.unit_flag_has_resource_channel) != 0;
+ }
+
+ pub fn has_twins(self: *const UnitRecord) bool {
+ return (self.flags & constants.unit_flag_has_twins) != 0;
+ }
+};
+
+pub const ThreadRecord = extern struct {
+ record_type: u16,
+ reserved: u16,
+ parent_node_id: u32,
+ identity_length: u16,
+ identity: [constants.max_identity_length]u16,
+
+ pub fn get_identity(self: *const ThreadRecord) []const u16 {
+ return self.identity[0..self.identity_length];
+ }
+};
+
+pub const SpanKey = extern struct {
+ key_length: u16,
+ channel_type: u8,
+ padding: u8,
+ node_id: u32,
+ start_cell: u32,
+};
+
+pub const SpanRecord = extern struct {
+ start_cell: u64,
+ cell_count: u64,
+};
+
+pub const AttributeKey = extern struct {
+ key_length: u16,
+ padding: u16,
+ node_id: u32,
+ start_cell: u32,
+ attribute_identity_length: u16,
+ attribute_identity: [128]u16,
+};
+
+pub const AttributeInlineRecord = extern struct {
+ record_type: u32,
+ reserved: [4]u8,
+ data_size: u32,
+ data: [constants.attribute_inline_data_max]u8,
+};
+
+pub const AttributeChannelRecord = extern struct {
+ record_type: u32,
+ reserved: u32,
+ channel: ChannelInfo,
+};
+
+pub const Permissions = extern struct {
+ owner_id: u32,
+ group_id: u32,
+ admin_flags: u8,
+ owner_flags: u8,
+ mode: u16,
+ special: SpecialPermissions,
+};
+
+pub const SpecialPermissions = extern union {
+ inode_number: u32,
+ link_count: u32,
+ raw_device: u32,
+};
+
+pub const SpecialInfo = extern union {
+ raw: [16]u8,
+ alias_info: AliasInfo,
+ twin_info: TwinInfo,
+};
+
+pub const AliasInfo = extern struct {
+ target_node_id: u32,
+ target_parent_node_id: u32,
+ reserved: [8]u8,
+};
+
+pub const TwinInfo = extern struct {
+ first_twin_node_id: u32,
+ reserved: [12]u8,
+};
+
+pub const JournalInfoCell = extern struct {
+ flags: u32,
+ device_signature: [32]u32,
+ offset: u64,
+ size: u64,
+ reserved: [128]u8,
+};
+
+pub const JournalHeader = extern struct {
+ magic: u32,
+ endian: u32,
+ start: u64,
+ end: u64,
+ size: u64,
+ cell_size: u32,
+ checksum_type: u32,
+ checksum: u32,
+ sequence: u64,
+};
+
+pub const JournalCellList = extern struct {
+ max_cells: u16,
+ cell_count: u16,
+ reserved: u32,
+ cells: [1]JournalCellInfo,
+};
+
+pub const JournalCellInfo = extern struct {
+ cell_number: u64,
+ cell_size: u64,
+};
+
+pub const Timestamp = extern struct {
+ seconds: i64,
+ nanoseconds: u32,
+ reserved: u32,
+};
diff --git a/hikari/fs/fat32/constants.zig b/hikari/fs/fat32/constants.zig
new file mode 100644
index 0000000..8abc7b9
--- /dev/null
+++ b/hikari/fs/fat32/constants.zig
@@ -0,0 +1,43 @@
+//! Hikari FAT32 Constants
+
+pub const boot_signature: u16 = 0xAA55;
+pub const fs_type_fat32: [8]u8 = .{ 'F', 'A', 'T', '3', '2', ' ', ' ', ' ' };
+
+pub const attribute_read_only: u8 = 0x01;
+pub const attribute_hidden: u8 = 0x02;
+pub const attribute_system: u8 = 0x04;
+pub const attribute_volume_id: u8 = 0x08;
+pub const attribute_stack: u8 = 0x10;
+pub const attribute_archive: u8 = 0x20;
+pub const attribute_long_identity: u8 = attribute_read_only | attribute_hidden | attribute_system | attribute_volume_id;
+pub const attribute_long_identity_mask: u8 = attribute_read_only | attribute_hidden | attribute_system | attribute_volume_id | attribute_stack | attribute_archive;
+
+pub const entry_free: u8 = 0xE5;
+pub const entry_end: u8 = 0x00;
+pub const entry_kanji_lead: u8 = 0x05;
+
+pub const cluster_free: u32 = 0x00000000;
+pub const cluster_reserved_start: u32 = 0x00000001;
+pub const cluster_reserved_end: u32 = 0x00000001;
+pub const cluster_data_start: u32 = 0x00000002;
+pub const cluster_bad: u32 = 0x0FFFFFF7;
+pub const cluster_end_of_chain_start: u32 = 0x0FFFFFF8;
+pub const cluster_end_of_chain: u32 = 0x0FFFFFFF;
+pub const cluster_mask: u32 = 0x0FFFFFFF;
+
+pub const long_identity_sequence_mask: u8 = 0x1F;
+pub const long_identity_last_entry: u8 = 0x40;
+pub const long_identity_chars_per_entry: usize = 13;
+
+pub const short_identity_length: usize = 8;
+pub const short_extension_length: usize = 3;
+pub const short_identity_total_length: usize = short_identity_length + short_extension_length;
+
+pub const sector_size_minimum: u16 = 512;
+pub const sector_size_maximum: u16 = 4096;
+
+pub const fs_info_signature_1: u32 = 0x41615252;
+pub const fs_info_signature_2: u32 = 0x61417272;
+pub const fs_info_signature_3: u32 = 0xAA550000;
+pub const fs_info_unknown_free_count: u32 = 0xFFFFFFFF;
+pub const fs_info_unknown_next_free: u32 = 0xFFFFFFFF;
diff --git a/hikari/fs/fat32/fat32.zig b/hikari/fs/fat32/fat32.zig
new file mode 100644
index 0000000..295b586
--- /dev/null
+++ b/hikari/fs/fat32/fat32.zig
@@ -0,0 +1,15 @@
+//! Hikari FAT32 UnitSystem
+
+pub const constants = @import("constants.zig");
+pub const types = @import("types.zig");
+pub const reader = @import("reader.zig");
+
+pub const BootSector = types.BootSector;
+pub const FsInfo = types.FsInfo;
+pub const StackEntry = types.StackEntry;
+pub const LongIdentityEntry = types.LongIdentityEntry;
+pub const TimeFormat = types.TimeFormat;
+pub const DateFormat = types.DateFormat;
+
+pub const Reader = reader.Reader;
+pub const ReadError = reader.ReadError;
diff --git a/hikari/fs/fat32/reader.zig b/hikari/fs/fat32/reader.zig
new file mode 100644
index 0000000..bb4b63b
--- /dev/null
+++ b/hikari/fs/fat32/reader.zig
@@ -0,0 +1,303 @@
+//! Hikari FAT32 Reader
+
+const efi = @import("../../efi/efi.zig");
+const constants = @import("constants.zig");
+const types = @import("types.zig");
+
+pub const ReadError = error{
+ invalid_boot_sector,
+ invalid_fs_info,
+ read_failed,
+ allocation_failed,
+ not_found,
+ not_a_stack,
+ invalid_cluster,
+ unit_too_large,
+};
+
+pub const Reader = struct {
+ block_io: *efi.protocols.BlockIoProtocol,
+ boot_services: *efi.services.BootServices,
+ partition_start_lba: u64,
+ boot_sector: types.BootSector,
+ bytes_per_sector: u32,
+ sectors_per_cluster: u32,
+ bytes_per_cluster: u32,
+ fat_start_lba: u64,
+ data_start_lba: u64,
+ origin_cluster: u32,
+ cluster_buffer: [*]u8,
+ sector_buffer: [*]u8,
+
+ pub fn initialize(
+ block_io: *efi.protocols.BlockIoProtocol,
+ boot_services: *efi.services.BootServices,
+ partition_start_lba: u64,
+ ) ReadError!Reader {
+ const block_size = block_io.media.block_size;
+
+ var sector_buffer: [*]align(8) u8 = undefined;
+ var alloc_status = boot_services.allocate_pool(
+ .loader_data,
+ block_size,
+ &sector_buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return ReadError.allocation_failed;
+ }
+
+ const read_status = block_io.read_blocks(
+ block_io,
+ block_io.media.media_id,
+ partition_start_lba,
+ block_size,
+ sector_buffer,
+ );
+ if (efi.types.is_error(read_status)) {
+ return ReadError.read_failed;
+ }
+
+ const boot_sector: *const types.BootSector = @ptrCast(@alignCast(sector_buffer));
+ if (!boot_sector.is_valid()) {
+ return ReadError.invalid_boot_sector;
+ }
+
+ const bytes_per_sector: u32 = boot_sector.bytes_per_sector;
+ const sectors_per_cluster: u32 = boot_sector.sectors_per_cluster;
+ const bytes_per_cluster = bytes_per_sector * sectors_per_cluster;
+
+ var cluster_buffer: [*]align(8) u8 = undefined;
+ alloc_status = boot_services.allocate_pool(
+ .loader_data,
+ bytes_per_cluster,
+ &cluster_buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return ReadError.allocation_failed;
+ }
+
+ const fat_start_lba = partition_start_lba + boot_sector.get_fat_start_sector();
+ const data_start_lba = partition_start_lba + boot_sector.get_data_start_sector();
+
+ return Reader{
+ .block_io = block_io,
+ .boot_services = boot_services,
+ .partition_start_lba = partition_start_lba,
+ .boot_sector = boot_sector.*,
+ .bytes_per_sector = bytes_per_sector,
+ .sectors_per_cluster = sectors_per_cluster,
+ .bytes_per_cluster = bytes_per_cluster,
+ .fat_start_lba = fat_start_lba,
+ .data_start_lba = data_start_lba,
+ .origin_cluster = boot_sector.origin_cluster,
+ .cluster_buffer = cluster_buffer,
+ .sector_buffer = sector_buffer,
+ };
+ }
+
+ pub fn read_cluster(self: *Reader, cluster: u32) ReadError!void {
+ if (cluster < 2) {
+ return ReadError.invalid_cluster;
+ }
+
+ const cluster_lba = self.data_start_lba + (@as(u64, cluster - 2) * self.sectors_per_cluster);
+ const read_status = self.block_io.read_blocks(
+ self.block_io,
+ self.block_io.media.media_id,
+ cluster_lba,
+ self.bytes_per_cluster,
+ self.cluster_buffer,
+ );
+ if (efi.types.is_error(read_status)) {
+ return ReadError.read_failed;
+ }
+ }
+
+ pub fn get_next_cluster(self: *Reader, cluster: u32) ReadError!?u32 {
+ const fat_offset = cluster * 4;
+ const fat_sector = fat_offset / self.bytes_per_sector;
+ const fat_offset_in_sector = fat_offset % self.bytes_per_sector;
+
+ const fat_sector_lba = self.fat_start_lba + fat_sector;
+ const read_status = self.block_io.read_blocks(
+ self.block_io,
+ self.block_io.media.media_id,
+ fat_sector_lba,
+ self.bytes_per_sector,
+ self.sector_buffer,
+ );
+ if (efi.types.is_error(read_status)) {
+ return ReadError.read_failed;
+ }
+
+ const fat_entry_ptr: *align(1) const u32 = @ptrCast(self.sector_buffer + fat_offset_in_sector);
+ const next_cluster = fat_entry_ptr.* & constants.cluster_mask;
+
+ if (next_cluster >= constants.cluster_end_of_chain_start) {
+ return null;
+ }
+ if (next_cluster == constants.cluster_bad) {
+ return ReadError.invalid_cluster;
+ }
+ if (next_cluster < 2) {
+ return ReadError.invalid_cluster;
+ }
+
+ return next_cluster;
+ }
+
+ pub fn find_in_stack(self: *Reader, stack_cluster: u32, identity: []const u8) ReadError!?types.StackEntry {
+ var current_cluster = stack_cluster;
+
+ while (true) {
+ try self.read_cluster(current_cluster);
+
+ const entries_per_cluster = self.bytes_per_cluster / @sizeOf(types.StackEntry);
+ const entries: [*]const types.StackEntry = @ptrCast(@alignCast(self.cluster_buffer));
+
+ var i: usize = 0;
+ while (i < entries_per_cluster) : (i += 1) {
+ const entry = &entries[i];
+
+ if (entry.is_end()) {
+ return null;
+ }
+ if (entry.is_free()) {
+ continue;
+ }
+ if (entry.is_long_identity()) {
+ continue;
+ }
+ if (entry.is_volume_id()) {
+ continue;
+ }
+
+ var short_identity_buffer: [12]u8 = undefined;
+ const short_identity_len = entry.get_short_identity(&short_identity_buffer);
+ const short_identity = short_identity_buffer[0..short_identity_len];
+
+ if (case_insensitive_equal(short_identity, identity)) {
+ return entry.*;
+ }
+ }
+
+ const next_cluster = try self.get_next_cluster(current_cluster);
+ if (next_cluster) |next| {
+ current_cluster = next;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ pub fn open_location(self: *Reader, location: []const u8) ReadError!types.StackEntry {
+ var current_cluster = self.origin_cluster;
+ var is_stack = true;
+
+ var start: usize = 0;
+ if (location.len > 0 and (location[0] == '/' or location[0] == '\\')) {
+ start = 1;
+ }
+
+ var iter_start = start;
+ while (iter_start < location.len) {
+ if (!is_stack) {
+ return ReadError.not_a_stack;
+ }
+
+ var iter_end = iter_start;
+ while (iter_end < location.len and location[iter_end] != '/' and location[iter_end] != '\\') {
+ iter_end += 1;
+ }
+
+ if (iter_end == iter_start) {
+ iter_start = iter_end + 1;
+ continue;
+ }
+
+ const component = location[iter_start..iter_end];
+ const entry = try self.find_in_stack(current_cluster, component);
+
+ if (entry) |found| {
+ current_cluster = found.get_first_cluster();
+ is_stack = found.is_stack();
+
+ if (iter_end >= location.len) {
+ return found;
+ }
+ } else {
+ return ReadError.not_found;
+ }
+
+ iter_start = iter_end + 1;
+ }
+
+ return ReadError.not_found;
+ }
+
+ pub fn read_unit(self: *Reader, entry: *const types.StackEntry, buffer: [*]u8, max_size: u32) ReadError!u32 {
+ const unit_size = entry.unit_size;
+ if (unit_size > max_size) {
+ return ReadError.unit_too_large;
+ }
+
+ var current_cluster = entry.get_first_cluster();
+ var bytes_read: u32 = 0;
+
+ while (bytes_read < unit_size) {
+ try self.read_cluster(current_cluster);
+
+ const bytes_remaining = unit_size - bytes_read;
+ const bytes_to_copy = if (bytes_remaining < self.bytes_per_cluster) bytes_remaining else self.bytes_per_cluster;
+
+ var i: u32 = 0;
+ while (i < bytes_to_copy) : (i += 1) {
+ buffer[bytes_read + i] = self.cluster_buffer[i];
+ }
+
+ bytes_read += bytes_to_copy;
+
+ if (bytes_read < unit_size) {
+ const next_cluster = try self.get_next_cluster(current_cluster);
+ if (next_cluster) |next| {
+ current_cluster = next;
+ } else {
+ break;
+ }
+ }
+ }
+
+ return bytes_read;
+ }
+
+ pub fn read_unit_to_allocated(self: *Reader, entry: *const types.StackEntry) ReadError!struct { buffer: [*]u8, size: u32 } {
+ const unit_size = entry.unit_size;
+
+ var buffer: [*]align(8) u8 = undefined;
+ const alloc_status = self.boot_services.allocate_pool(
+ .loader_data,
+ unit_size,
+ &buffer,
+ );
+ if (efi.types.is_error(alloc_status)) {
+ return ReadError.allocation_failed;
+ }
+
+ const bytes_read = try self.read_unit(entry, buffer, unit_size);
+ return .{ .buffer = buffer, .size = bytes_read };
+ }
+};
+
+fn case_insensitive_equal(a: []const u8, b: []const u8) bool {
+ if (a.len != b.len) {
+ return false;
+ }
+ for (a, b) |char_a, char_b| {
+ const upper_a = if (char_a >= 'a' and char_a <= 'z') char_a - 32 else char_a;
+ const upper_b = if (char_b >= 'a' and char_b <= 'z') char_b - 32 else char_b;
+ if (upper_a != upper_b) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/hikari/fs/fat32/types.zig b/hikari/fs/fat32/types.zig
new file mode 100644
index 0000000..24d92a1
--- /dev/null
+++ b/hikari/fs/fat32/types.zig
@@ -0,0 +1,214 @@
+//! Hikari FAT32 Types
+
+const constants = @import("constants.zig");
+
+pub const BootSector = extern struct {
+ jump_boot: [3]u8,
+ oem_identity: [8]u8,
+ bytes_per_sector: u16 align(1),
+ sectors_per_cluster: u8,
+ reserved_sector_count: u16 align(1),
+ fat_count: u8,
+ origin_entry_count: u16 align(1),
+ total_sectors_16: u16 align(1),
+ media_type: u8,
+ fat_size_16: u16 align(1),
+ sectors_per_track: u16 align(1),
+ head_count: u16 align(1),
+ hidden_sector_count: u32 align(1),
+ total_sectors_32: u32 align(1),
+ fat_size_32: u32 align(1),
+ ext_flags: u16 align(1),
+ fs_version: u16 align(1),
+ origin_cluster: u32 align(1),
+ fs_info_sector: u16 align(1),
+ backup_boot_sector: u16 align(1),
+ reserved: [12]u8,
+ drive_number: u8,
+ reserved1: u8,
+ boot_signature: u8,
+ volume_id: u32 align(1),
+ volume_label: [11]u8,
+ fs_type: [8]u8,
+ boot_code: [420]u8,
+ signature: u16 align(1),
+
+ pub fn is_valid(self: *const BootSector) bool {
+ if (self.signature != constants.boot_signature) {
+ return false;
+ }
+ if (self.bytes_per_sector < constants.sector_size_minimum or
+ self.bytes_per_sector > constants.sector_size_maximum)
+ {
+ return false;
+ }
+ if (self.sectors_per_cluster == 0) {
+ return false;
+ }
+ if (self.fat_count == 0) {
+ return false;
+ }
+ if (self.fat_size_32 == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ pub fn get_fat_start_sector(self: *const BootSector) u32 {
+ return self.reserved_sector_count;
+ }
+
+ pub fn get_data_start_sector(self: *const BootSector) u32 {
+ return self.reserved_sector_count + (self.fat_count * self.fat_size_32);
+ }
+
+ pub fn get_total_clusters(self: *const BootSector) u32 {
+ const data_sectors = self.total_sectors_32 - self.get_data_start_sector();
+ return data_sectors / self.sectors_per_cluster;
+ }
+
+ pub fn cluster_to_sector(self: *const BootSector, cluster: u32) u32 {
+ return self.get_data_start_sector() + ((cluster - 2) * self.sectors_per_cluster);
+ }
+};
+
+pub const FsInfo = extern struct {
+ signature_1: u32 align(1),
+ reserved_1: [480]u8,
+ signature_2: u32 align(1),
+ free_cluster_count: u32 align(1),
+ next_free_cluster: u32 align(1),
+ reserved_2: [12]u8,
+ signature_3: u32 align(1),
+
+ pub fn is_valid(self: *const FsInfo) bool {
+ return self.signature_1 == constants.fs_info_signature_1 and
+ self.signature_2 == constants.fs_info_signature_2 and
+ self.signature_3 == constants.fs_info_signature_3;
+ }
+};
+
+pub const StackEntry = extern struct {
+ identity: [8]u8,
+ extension: [3]u8,
+ attributes: u8,
+ reserved_nt: u8,
+ creation_time_tenths: u8,
+ creation_time: u16 align(1),
+ creation_date: u16 align(1),
+ last_access_date: u16 align(1),
+ first_cluster_high: u16 align(1),
+ write_time: u16 align(1),
+ write_date: u16 align(1),
+ first_cluster_low: u16 align(1),
+ unit_size: u32 align(1),
+
+ pub fn is_free(self: *const StackEntry) bool {
+ return self.identity[0] == constants.entry_free;
+ }
+
+ pub fn is_end(self: *const StackEntry) bool {
+ return self.identity[0] == constants.entry_end;
+ }
+
+ pub fn is_long_identity(self: *const StackEntry) bool {
+ return (self.attributes & constants.attribute_long_identity_mask) == constants.attribute_long_identity;
+ }
+
+ pub fn is_stack(self: *const StackEntry) bool {
+ return (self.attributes & constants.attribute_stack) != 0;
+ }
+
+ pub fn is_volume_id(self: *const StackEntry) bool {
+ return (self.attributes & constants.attribute_volume_id) != 0;
+ }
+
+ pub fn get_first_cluster(self: *const StackEntry) u32 {
+ return (@as(u32, self.first_cluster_high) << 16) | self.first_cluster_low;
+ }
+
+ pub fn get_short_identity(self: *const StackEntry, buffer: *[12]u8) usize {
+ var length: usize = 0;
+
+ var identity_byte = self.identity[0];
+ if (identity_byte == constants.entry_kanji_lead) {
+ identity_byte = constants.entry_free;
+ }
+
+ var i: usize = 0;
+ while (i < 8 and self.identity[i] != ' ') : (i += 1) {
+ if (i == 0) {
+ buffer[length] = identity_byte;
+ } else {
+ buffer[length] = self.identity[i];
+ }
+ length += 1;
+ }
+
+ if (self.extension[0] != ' ') {
+ buffer[length] = '.';
+ length += 1;
+
+ var j: usize = 0;
+ while (j < 3 and self.extension[j] != ' ') : (j += 1) {
+ buffer[length] = self.extension[j];
+ length += 1;
+ }
+ }
+
+ return length;
+ }
+};
+
+pub const LongIdentityEntry = extern struct {
+ sequence_number: u8,
+ identity_1: [10]u8,
+ attributes: u8,
+ entry_type: u8,
+ checksum: u8,
+ identity_2: [12]u8,
+ first_cluster_low: u16 align(1),
+ identity_3: [4]u8,
+
+ pub fn is_last(self: *const LongIdentityEntry) bool {
+ return (self.sequence_number & constants.long_identity_last_entry) != 0;
+ }
+
+ pub fn get_sequence(self: *const LongIdentityEntry) u8 {
+ return self.sequence_number & constants.long_identity_sequence_mask;
+ }
+
+ pub fn extract_chars(self: *const LongIdentityEntry, buffer: *[13]u16) void {
+ var index: usize = 0;
+
+ var i: usize = 0;
+ while (i < 10) : (i += 2) {
+ buffer[index] = @as(u16, self.identity_1[i]) | (@as(u16, self.identity_1[i + 1]) << 8);
+ index += 1;
+ }
+
+ i = 0;
+ while (i < 12) : (i += 2) {
+ buffer[index] = @as(u16, self.identity_2[i]) | (@as(u16, self.identity_2[i + 1]) << 8);
+ index += 1;
+ }
+
+ i = 0;
+ while (i < 4) : (i += 2) {
+ buffer[index] = @as(u16, self.identity_3[i]) | (@as(u16, self.identity_3[i + 1]) << 8);
+ index += 1;
+ }
+ }
+};
+
+pub const TimeFormat = packed struct(u16) {
+ second_div_2: u5,
+ minute: u6,
+ hour: u5,
+};
+
+pub const DateFormat = packed struct(u16) {
+ day: u5,
+ month: u4,
+ year_from_1980: u7,
+};
diff --git a/hikari/fs/fs.zig b/hikari/fs/fs.zig
new file mode 100644
index 0000000..aef6e6f
--- /dev/null
+++ b/hikari/fs/fs.zig
@@ -0,0 +1,4 @@
+//! Hikari UnitSystem Subsystem
+
+pub const fat32 = @import("fat32/fat32.zig");
+pub const afs = @import("afs/afs.zig");
diff --git a/hikari/hikari.zig b/hikari/hikari.zig
new file mode 100644
index 0000000..c1c03ea
--- /dev/null
+++ b/hikari/hikari.zig
@@ -0,0 +1,322 @@
+//! Hikari UEFI Bootloader for AkibaOS
+//!
+//! Hikari loads the Mirai kernel from the AFS partition and
+//! transfers control with boot parameters.
+
+const efi = @import("efi/efi.zig");
+const disk = @import("disk/disk.zig");
+const fs = @import("fs/fs.zig");
+const loader = @import("loader/loader.zig");
+const display = @import("display/display.zig");
+const menu = @import("menu/menu.zig");
+const paging = @import("paging/paging.zig");
+const boot = @import("boot/boot.zig");
+const asm_ops = @import("asm/asm.zig");
+
+const kernel_location = "/system/akiba/mirai.kernel";
+const font_location = "/system/akiba/fonts/akiba.psf";
+
+pub fn hikari(image_handle: efi.types.Handle, system_table: *efi.services.SystemTable) callconv(.C) efi.types.Status {
+ const boot_services = system_table.boot_services;
+ const console = system_table.console_output;
+
+ _ = console.clear_screen(console);
+ print(console, "Hikari Bootloader\r\n");
+ print(console, "=================\r\n\r\n");
+
+ print(console, "Initializing graphics...\r\n");
+ const gop = get_graphics_output(boot_services) orelse {
+ print(console, "ERROR: Failed to get graphics output\r\n");
+ return efi.constants.status.unsupported;
+ };
+
+ const framebuffer = display.Framebuffer.initialize(gop);
+ _ = framebuffer;
+
+ print(console, "Locating AFS partition...\r\n");
+ const afs_partition = find_afs_partition(boot_services) orelse {
+ print(console, "ERROR: AFS partition not found\r\n");
+ return efi.constants.status.not_found;
+ };
+
+ print(console, "Initializing AFS...\r\n");
+ var afs_reader = fs.afs.Reader.initialize(
+ afs_partition.block_io,
+ boot_services,
+ afs_partition.start_lba,
+ ) catch {
+ print(console, "ERROR: Failed to initialize AFS\r\n");
+ return efi.constants.status.device_error;
+ };
+
+ print(console, "Loading kernel: ");
+ print(console, kernel_location);
+ print(console, "\r\n");
+
+ const kernel_unit = afs_reader.open_location(kernel_location) catch {
+ print(console, "ERROR: Kernel not found\r\n");
+ return efi.constants.status.not_found;
+ };
+
+ const kernel_data = afs_reader.read_unit_to_allocated(&kernel_unit) catch {
+ print(console, "ERROR: Failed to read kernel\r\n");
+ return efi.constants.status.device_error;
+ };
+
+ print(console, "Validating ELF...\r\n");
+ if (!loader.elf.validate_elf(kernel_data.buffer, kernel_data.size)) {
+ print(console, "ERROR: Invalid ELF format\r\n");
+ return efi.constants.status.invalid_parameter;
+ }
+
+ print(console, "Loading kernel into memory...\r\n");
+ var elf_loader = loader.elf.Loader.initialize(boot_services);
+ const loaded_image = elf_loader.load(kernel_data.buffer, kernel_data.size) catch {
+ print(console, "ERROR: Failed to load kernel\r\n");
+ return efi.constants.status.load_error;
+ };
+
+ print(console, "Setting up page tables...\r\n");
+ var page_setup = paging.PageTableSetup.initialize(boot_services) catch {
+ print(console, "ERROR: Failed to setup page tables\r\n");
+ return efi.constants.status.out_of_resources;
+ };
+
+ page_setup.map_identity(0, 4 * 1024 * 1024 * 1024) catch {};
+ page_setup.map_kernel(loaded_image.base_address, loaded_image.total_size()) catch {};
+ page_setup.map_physmap(16 * 1024 * 1024 * 1024) catch {};
+
+ print(console, "Allocating kernel stack...\r\n");
+ const stack_size: u64 = 64 * 1024;
+ const stack_pages = stack_size / 4096;
+ var stack_base: efi.types.PhysicalAddress = 0;
+ const stack_status = boot_services.allocate_pages(.any_pages, .loader_data, stack_pages, &stack_base);
+ if (efi.types.is_error(stack_status)) {
+ print(console, "ERROR: Failed to allocate stack\r\n");
+ return efi.constants.status.out_of_resources;
+ }
+ const stack_top = stack_base + stack_size;
+
+ print(console, "Preparing boot parameters...\r\n");
+ var params_addr: efi.types.PhysicalAddress = 0;
+ const params_status = boot_services.allocate_pages(.any_pages, .loader_data, 1, &params_addr);
+ if (efi.types.is_error(params_status)) {
+ print(console, "ERROR: Failed to allocate boot params\r\n");
+ return efi.constants.status.out_of_resources;
+ }
+
+ const boot_params: *boot.BootParams = @ptrFromInt(params_addr);
+ boot_params.* = boot.BootParams.initialize();
+
+ boot_params.framebuffer = boot.FramebufferInfo{
+ .base = gop.mode.framebuffer_base,
+ .size = gop.mode.framebuffer_size,
+ .width = gop.mode.info.horizontal_resolution,
+ .height = gop.mode.info.vertical_resolution,
+ .stride = gop.mode.info.pixels_per_scan_line,
+ .pixel_format = switch (gop.mode.info.pixel_format) {
+ .rgb => .rgb,
+ .bgr => .bgr,
+ else => .unknown,
+ },
+ .red_mask_size = 8,
+ .red_mask_shift = 16,
+ .green_mask_size = 8,
+ .green_mask_shift = 8,
+ .blue_mask_size = 8,
+ .blue_mask_shift = 0,
+ .reserved = .{ 0, 0 },
+ };
+
+ boot_params.kernel = boot.KernelInfo{
+ .physical_base = loaded_image.base_address,
+ .virtual_base = paging.constants.kernel_base,
+ .size = loaded_image.total_size(),
+ .entry_point = loaded_image.entry_point,
+ .pml4_address = page_setup.get_pml4_address(),
+ .physmap_base = paging.constants.physmap_base,
+ .physmap_size = paging.constants.physmap_size,
+ .stack_top = stack_top,
+ .stack_size = stack_size,
+ };
+
+ boot_params.acpi = find_acpi(system_table);
+
+ print(console, "Getting memory map...\r\n");
+ var memory_map_size: usize = 0;
+ var map_key: usize = 0;
+ var descriptor_size: usize = 0;
+ var descriptor_version: u32 = 0;
+ var memory_map: [*]efi.types.memory.MemoryDescriptor = undefined;
+
+ _ = boot_services.get_memory_map(
+ &memory_map_size,
+ memory_map,
+ &map_key,
+ &descriptor_size,
+ &descriptor_version,
+ );
+
+ memory_map_size += 2 * descriptor_size;
+ var map_buffer: [*]align(8) u8 = undefined;
+ _ = boot_services.allocate_pool(.loader_data, memory_map_size, &map_buffer);
+ memory_map = @ptrCast(@alignCast(map_buffer));
+
+ const map_status = boot_services.get_memory_map(
+ &memory_map_size,
+ memory_map,
+ &map_key,
+ &descriptor_size,
+ &descriptor_version,
+ );
+
+ if (efi.types.is_error(map_status)) {
+ print(console, "ERROR: Failed to get memory map\r\n");
+ return efi.constants.status.device_error;
+ }
+
+ boot_params.memory_map = boot.MemoryMapInfo{
+ .entries = @intFromPtr(memory_map),
+ .entry_count = @truncate(memory_map_size / descriptor_size),
+ .entry_size = @truncate(descriptor_size),
+ .descriptor_version = descriptor_version,
+ .reserved = 0,
+ };
+
+ print(console, "Exiting boot services...\r\n");
+ const exit_status = boot_services.exit_boot_services(image_handle, map_key);
+ if (efi.types.is_error(exit_status)) {
+ _ = boot_services.get_memory_map(
+ &memory_map_size,
+ memory_map,
+ &map_key,
+ &descriptor_size,
+ &descriptor_version,
+ );
+ _ = boot_services.exit_boot_services(image_handle, map_key);
+ }
+
+ asm_ops.jump_to_kernel(
+ loaded_image.entry_point,
+ stack_top,
+ params_addr,
+ page_setup.get_pml4_address(),
+ );
+}
+
+fn print(console: *efi.protocols.SimpleTextOutputProtocol, msg: []const u8) void {
+ for (msg) |c| {
+ var buf: [2]u16 = .{ c, 0 };
+ _ = console.output_string(console, &buf);
+ }
+}
+
+fn get_graphics_output(boot_services: *efi.services.BootServices) ?*efi.protocols.GraphicsOutputProtocol {
+ var gop: ?*anyopaque = null;
+ const status = boot_services.locate_protocol(
+ &efi.constants.guids.graphics_output_protocol,
+ null,
+ &gop,
+ );
+ if (efi.types.is_error(status)) {
+ return null;
+ }
+ return @ptrCast(@alignCast(gop));
+}
+
+const PartitionInfo = struct {
+ block_io: *efi.protocols.BlockIoProtocol,
+ start_lba: u64,
+};
+
+fn find_afs_partition(boot_services: *efi.services.BootServices) ?PartitionInfo {
+ var handle_count: usize = 0;
+ var handles: [*]efi.types.Handle = undefined;
+
+ const status = boot_services.locate_handle_buffer(
+ .by_protocol,
+ &efi.constants.guids.block_io_protocol,
+ null,
+ &handle_count,
+ &handles,
+ );
+
+ if (efi.types.is_error(status)) {
+ return null;
+ }
+
+ var i: usize = 0;
+ while (i < handle_count) : (i += 1) {
+ var block_io: ?*anyopaque = null;
+ const bio_status = boot_services.handle_protocol(
+ handles[i],
+ &efi.constants.guids.block_io_protocol,
+ &block_io,
+ );
+
+ if (efi.types.is_error(bio_status)) {
+ continue;
+ }
+
+ const bio: *efi.protocols.BlockIoProtocol = @ptrCast(@alignCast(block_io));
+
+ if (bio.media.logical_partition) {
+ continue;
+ }
+
+ var gpt_parser = disk.gpt.Parser.initialize(bio, boot_services) catch {
+ continue;
+ };
+
+ const afs_part = gpt_parser.find_partition_by_type(efi.constants.guids.gpt_partition_type_akiba_afs);
+ if (afs_part) |part| {
+ return PartitionInfo{
+ .block_io = bio,
+ .start_lba = part.starting_lba,
+ };
+ }
+ }
+
+ return null;
+}
+
+fn find_acpi(system_table: *efi.services.SystemTable) boot.AcpiInfo {
+ var i: usize = 0;
+ while (i < system_table.number_of_table_entries) : (i += 1) {
+ const entry = &system_table.configuration_table[i];
+
+ if (entry.vendor_guid.equals(efi.constants.guids.acpi_20_table)) {
+ return boot.AcpiInfo{
+ .rsdp_address = @intFromPtr(entry.vendor_table),
+ .rsdp_version = 2,
+ .reserved = 0,
+ };
+ }
+ }
+
+ i = 0;
+ while (i < system_table.number_of_table_entries) : (i += 1) {
+ const entry = &system_table.configuration_table[i];
+
+ if (entry.vendor_guid.equals(efi.constants.guids.acpi_10_table)) {
+ return boot.AcpiInfo{
+ .rsdp_address = @intFromPtr(entry.vendor_table),
+ .rsdp_version = 1,
+ .reserved = 0,
+ };
+ }
+ }
+
+ return boot.AcpiInfo{
+ .rsdp_address = 0,
+ .rsdp_version = 0,
+ .reserved = 0,
+ };
+}
+
+pub fn panic(msg: []const u8, stack_trace: ?*@import("std").builtin.StackTrace, ret_addr: ?usize) noreturn {
+ _ = msg;
+ _ = stack_trace;
+ _ = ret_addr;
+ asm_ops.halt();
+}
diff --git a/hikari/loader/elf/constants.zig b/hikari/loader/elf/constants.zig
new file mode 100644
index 0000000..1a91701
--- /dev/null
+++ b/hikari/loader/elf/constants.zig
@@ -0,0 +1,85 @@
+//! Hikari ELF Constants
+
+pub const magic: [4]u8 = .{ 0x7F, 'E', 'L', 'F' };
+
+pub const class_none: u8 = 0;
+pub const class_32: u8 = 1;
+pub const class_64: u8 = 2;
+
+pub const data_none: u8 = 0;
+pub const data_little_endian: u8 = 1;
+pub const data_big_endian: u8 = 2;
+
+pub const version_none: u8 = 0;
+pub const version_current: u8 = 1;
+
+pub const osabi_none: u8 = 0;
+pub const osabi_sysv: u8 = 0;
+pub const osabi_linux: u8 = 3;
+pub const osabi_freebsd: u8 = 9;
+pub const osabi_standalone: u8 = 255;
+
+pub const type_none: u16 = 0;
+pub const type_relocatable: u16 = 1;
+pub const type_executable: u16 = 2;
+pub const type_shared: u16 = 3;
+pub const type_core: u16 = 4;
+
+pub const machine_none: u16 = 0;
+pub const machine_386: u16 = 3;
+pub const machine_x86_64: u16 = 62;
+pub const machine_aarch64: u16 = 183;
+pub const machine_riscv: u16 = 243;
+
+pub const segment_null: u32 = 0;
+pub const segment_load: u32 = 1;
+pub const segment_dynamic: u32 = 2;
+pub const segment_interp: u32 = 3;
+pub const segment_note: u32 = 4;
+pub const segment_shlib: u32 = 5;
+pub const segment_phdr: u32 = 6;
+pub const segment_tls: u32 = 7;
+pub const segment_gnu_eh_frame: u32 = 0x6474E550;
+pub const segment_gnu_stack: u32 = 0x6474E551;
+pub const segment_gnu_relro: u32 = 0x6474E552;
+
+pub const segment_flag_execute: u32 = 0x1;
+pub const segment_flag_write: u32 = 0x2;
+pub const segment_flag_read: u32 = 0x4;
+
+pub const section_null: u32 = 0;
+pub const section_progbits: u32 = 1;
+pub const section_symtab: u32 = 2;
+pub const section_strtab: u32 = 3;
+pub const section_rela: u32 = 4;
+pub const section_hash: u32 = 5;
+pub const section_dynamic: u32 = 6;
+pub const section_note: u32 = 7;
+pub const section_nobits: u32 = 8;
+pub const section_rel: u32 = 9;
+pub const section_shlib: u32 = 10;
+pub const section_dynsym: u32 = 11;
+pub const section_init_array: u32 = 14;
+pub const section_fini_array: u32 = 15;
+pub const section_preinit_array: u32 = 16;
+pub const section_group: u32 = 17;
+pub const section_symtab_shndx: u32 = 18;
+
+pub const section_flag_write: u64 = 0x1;
+pub const section_flag_alloc: u64 = 0x2;
+pub const section_flag_execinstr: u64 = 0x4;
+pub const section_flag_merge: u64 = 0x10;
+pub const section_flag_strings: u64 = 0x20;
+pub const section_flag_info_link: u64 = 0x40;
+pub const section_flag_link_order: u64 = 0x80;
+pub const section_flag_os_nonconforming: u64 = 0x100;
+pub const section_flag_group: u64 = 0x200;
+pub const section_flag_tls: u64 = 0x400;
+
+pub const section_index_undefined: u16 = 0;
+pub const section_index_abs: u16 = 0xFFF1;
+pub const section_index_common: u16 = 0xFFF2;
+
+pub const elf64_header_size: usize = 64;
+pub const elf64_program_header_size: usize = 56;
+pub const elf64_section_header_size: usize = 64;
diff --git a/hikari/loader/elf/elf.zig b/hikari/loader/elf/elf.zig
new file mode 100644
index 0000000..83eab98
--- /dev/null
+++ b/hikari/loader/elf/elf.zig
@@ -0,0 +1,17 @@
+//! Hikari ELF
+
+pub const constants = @import("constants.zig");
+pub const types = @import("types.zig");
+pub const loader = @import("loader.zig");
+
+pub const Elf64Header = types.Elf64Header;
+pub const Elf64ProgramHeader = types.Elf64ProgramHeader;
+pub const Elf64SectionHeader = types.Elf64SectionHeader;
+pub const Elf64Symbol = types.Elf64Symbol;
+pub const Elf64Rela = types.Elf64Rela;
+pub const LoadedSegment = types.LoadedSegment;
+pub const LoadedImage = types.LoadedImage;
+
+pub const Loader = loader.Loader;
+pub const LoadError = loader.LoadError;
+pub const validate_elf = loader.validate_elf;
diff --git a/hikari/loader/elf/loader.zig b/hikari/loader/elf/loader.zig
new file mode 100644
index 0000000..4188e22
--- /dev/null
+++ b/hikari/loader/elf/loader.zig
@@ -0,0 +1,201 @@
+//! Hikari ELF Loader
+
+const efi = @import("../../efi/efi.zig");
+const constants = @import("constants.zig");
+const types = @import("types.zig");
+
+pub const LoadError = error{
+ invalid_elf_header,
+ unsupported_architecture,
+ unsupported_elf_type,
+ no_loadable_segments,
+ too_many_segments,
+ allocation_failed,
+ segment_overlap,
+};
+
+pub const Loader = struct {
+ boot_services: *efi.services.BootServices,
+
+ pub fn initialize(boot_services: *efi.services.BootServices) Loader {
+ return Loader{
+ .boot_services = boot_services,
+ };
+ }
+
+ pub fn load(self: *Loader, unit_data: [*]const u8, unit_size: u64) LoadError!types.LoadedImage {
+ if (unit_size < constants.elf64_header_size) {
+ return LoadError.invalid_elf_header;
+ }
+
+ const header: *const types.Elf64Header = @ptrCast(@alignCast(unit_data));
+
+ if (!header.is_valid()) {
+ return LoadError.invalid_elf_header;
+ }
+
+ if (!header.is_x86_64()) {
+ return LoadError.unsupported_architecture;
+ }
+
+ if (!header.is_executable()) {
+ return LoadError.unsupported_elf_type;
+ }
+
+ const program_headers = header.get_program_headers(unit_data);
+
+ var load_base: u64 = 0xFFFFFFFFFFFFFFFF;
+ var load_end: u64 = 0;
+ var loadable_count: usize = 0;
+
+ for (program_headers) |*phdr| {
+ if (!phdr.is_loadable()) {
+ continue;
+ }
+
+ loadable_count += 1;
+
+ if (phdr.virtual_address < load_base) {
+ load_base = phdr.virtual_address;
+ }
+
+ const segment_end = phdr.virtual_address + phdr.memory_size;
+ if (segment_end > load_end) {
+ load_end = segment_end;
+ }
+ }
+
+ if (loadable_count == 0) {
+ return LoadError.no_loadable_segments;
+ }
+
+ if (loadable_count > 16) {
+ return LoadError.too_many_segments;
+ }
+
+ const total_size = load_end - load_base;
+ const pages_needed = (total_size + 4095) / 4096;
+
+ var physical_base: efi.types.PhysicalAddress = load_base;
+ const alloc_status = self.boot_services.allocate_pages(
+ .address,
+ .loader_data,
+ pages_needed,
+ &physical_base,
+ );
+
+ if (efi.types.is_error(alloc_status)) {
+ physical_base = 0;
+ const fallback_status = self.boot_services.allocate_pages(
+ .any_pages,
+ .loader_data,
+ pages_needed,
+ &physical_base,
+ );
+
+ if (efi.types.is_error(fallback_status)) {
+ return LoadError.allocation_failed;
+ }
+ }
+
+ const dest_base: [*]u8 = @ptrFromInt(physical_base);
+
+ var i: u64 = 0;
+ while (i < total_size) : (i += 1) {
+ dest_base[i] = 0;
+ }
+
+ var image = types.LoadedImage{
+ .entry_point = header.entry,
+ .base_address = physical_base,
+ .end_address = physical_base + total_size,
+ .segments = undefined,
+ .segment_count = 0,
+ };
+
+ for (program_headers) |*phdr| {
+ if (!phdr.is_loadable()) {
+ continue;
+ }
+
+ const segment_offset = phdr.virtual_address - load_base;
+ const dest: [*]u8 = dest_base + segment_offset;
+ const src = unit_data + phdr.offset;
+
+ var j: u64 = 0;
+ while (j < phdr.unit_size) : (j += 1) {
+ dest[j] = src[j];
+ }
+
+ image.segments[image.segment_count] = types.LoadedSegment{
+ .virtual_address = phdr.virtual_address,
+ .physical_address = physical_base + segment_offset,
+ .memory_size = phdr.memory_size,
+ .flags = phdr.flags,
+ };
+ image.segment_count += 1;
+ }
+
+ if (physical_base != load_base) {
+ image.entry_point = (header.entry - load_base) + physical_base;
+ }
+
+ return image;
+ }
+
+ pub fn get_memory_requirements(unit_data: [*]const u8) LoadError!struct { base: u64, size: u64 } {
+ const header: *const types.Elf64Header = @ptrCast(@alignCast(unit_data));
+
+ if (!header.is_valid()) {
+ return LoadError.invalid_elf_header;
+ }
+
+ const program_headers = header.get_program_headers(unit_data);
+
+ var load_base: u64 = 0xFFFFFFFFFFFFFFFF;
+ var load_end: u64 = 0;
+
+ for (program_headers) |*phdr| {
+ if (!phdr.is_loadable()) {
+ continue;
+ }
+
+ if (phdr.virtual_address < load_base) {
+ load_base = phdr.virtual_address;
+ }
+
+ const segment_end = phdr.virtual_address + phdr.memory_size;
+ if (segment_end > load_end) {
+ load_end = segment_end;
+ }
+ }
+
+ if (load_base == 0xFFFFFFFFFFFFFFFF) {
+ return LoadError.no_loadable_segments;
+ }
+
+ return .{
+ .base = load_base,
+ .size = load_end - load_base,
+ };
+ }
+
+ pub fn get_entry_point(unit_data: [*]const u8) LoadError!u64 {
+ const header: *const types.Elf64Header = @ptrCast(@alignCast(unit_data));
+
+ if (!header.is_valid()) {
+ return LoadError.invalid_elf_header;
+ }
+
+ return header.entry;
+ }
+};
+
+pub fn validate_elf(data: [*]const u8, size: u64) bool {
+ if (size < constants.elf64_header_size) {
+ return false;
+ }
+
+ const header: *const types.Elf64Header = @ptrCast(@alignCast(data));
+ return header.is_valid() and header.is_x86_64();
+}
diff --git a/hikari/loader/elf/types.zig b/hikari/loader/elf/types.zig
new file mode 100644
index 0000000..1d4a3d4
--- /dev/null
+++ b/hikari/loader/elf/types.zig
@@ -0,0 +1,182 @@
+//! Hikari ELF Types
+
+const constants = @import("constants.zig");
+
+pub const Elf64Header = extern struct {
+ magic: [4]u8,
+ class: u8,
+ data: u8,
+ version: u8,
+ osabi: u8,
+ abi_version: u8,
+ padding: [7]u8,
+ elf_type: u16 align(1),
+ machine: u16 align(1),
+ elf_version: u32 align(1),
+ entry: u64 align(1),
+ program_header_offset: u64 align(1),
+ section_header_offset: u64 align(1),
+ flags: u32 align(1),
+ header_size: u16 align(1),
+ program_header_entry_size: u16 align(1),
+ program_header_count: u16 align(1),
+ section_header_entry_size: u16 align(1),
+ section_header_count: u16 align(1),
+ section_identity_index: u16 align(1),
+
+ pub fn is_valid(self: *const Elf64Header) bool {
+ if (self.magic[0] != constants.magic[0] or
+ self.magic[1] != constants.magic[1] or
+ self.magic[2] != constants.magic[2] or
+ self.magic[3] != constants.magic[3])
+ {
+ return false;
+ }
+ if (self.class != constants.class_64) {
+ return false;
+ }
+ if (self.data != constants.data_little_endian) {
+ return false;
+ }
+ if (self.version != constants.version_current) {
+ return false;
+ }
+ return true;
+ }
+
+ pub fn is_executable(self: *const Elf64Header) bool {
+ return self.elf_type == constants.type_executable;
+ }
+
+ pub fn is_x86_64(self: *const Elf64Header) bool {
+ return self.machine == constants.machine_x86_64;
+ }
+
+ pub fn get_program_headers(self: *const Elf64Header, base: [*]const u8) []const Elf64ProgramHeader {
+ const phdr_ptr: [*]const Elf64ProgramHeader = @ptrCast(@alignCast(base + self.program_header_offset));
+ return phdr_ptr[0..self.program_header_count];
+ }
+
+ pub fn get_section_headers(self: *const Elf64Header, base: [*]const u8) []const Elf64SectionHeader {
+ const shdr_ptr: [*]const Elf64SectionHeader = @ptrCast(@alignCast(base + self.section_header_offset));
+ return shdr_ptr[0..self.section_header_count];
+ }
+};
+
+pub const Elf64ProgramHeader = extern struct {
+ segment_type: u32 align(1),
+ flags: u32 align(1),
+ offset: u64 align(1),
+ virtual_address: u64 align(1),
+ physical_address: u64 align(1),
+ unit_size: u64 align(1),
+ memory_size: u64 align(1),
+ alignment: u64 align(1),
+
+ pub fn is_loadable(self: *const Elf64ProgramHeader) bool {
+ return self.segment_type == constants.segment_load;
+ }
+
+ pub fn is_executable(self: *const Elf64ProgramHeader) bool {
+ return (self.flags & constants.segment_flag_execute) != 0;
+ }
+
+ pub fn is_writable(self: *const Elf64ProgramHeader) bool {
+ return (self.flags & constants.segment_flag_write) != 0;
+ }
+
+ pub fn is_readable(self: *const Elf64ProgramHeader) bool {
+ return (self.flags & constants.segment_flag_read) != 0;
+ }
+
+ pub fn get_data(self: *const Elf64ProgramHeader, base: [*]const u8) []const u8 {
+ return (base + self.offset)[0..self.unit_size];
+ }
+};
+
+pub const Elf64SectionHeader = extern struct {
+ identity_offset: u32 align(1),
+ section_type: u32 align(1),
+ flags: u64 align(1),
+ address: u64 align(1),
+ offset: u64 align(1),
+ size: u64 align(1),
+ link: u32 align(1),
+ info: u32 align(1),
+ address_alignment: u64 align(1),
+ entry_size: u64 align(1),
+
+ pub fn is_allocatable(self: *const Elf64SectionHeader) bool {
+ return (self.flags & constants.section_flag_alloc) != 0;
+ }
+
+ pub fn is_writable(self: *const Elf64SectionHeader) bool {
+ return (self.flags & constants.section_flag_write) != 0;
+ }
+
+ pub fn is_executable(self: *const Elf64SectionHeader) bool {
+ return (self.flags & constants.section_flag_execinstr) != 0;
+ }
+
+ pub fn is_nobits(self: *const Elf64SectionHeader) bool {
+ return self.section_type == constants.section_nobits;
+ }
+
+ pub fn get_data(self: *const Elf64SectionHeader, base: [*]const u8) []const u8 {
+ return (base + self.offset)[0..self.size];
+ }
+};
+
+pub const Elf64Symbol = extern struct {
+ identity_offset: u32 align(1),
+ info: u8,
+ other: u8,
+ section_index: u16 align(1),
+ value: u64 align(1),
+ size: u64 align(1),
+
+ pub fn get_binding(self: *const Elf64Symbol) u8 {
+ return self.info >> 4;
+ }
+
+ pub fn get_type(self: *const Elf64Symbol) u8 {
+ return self.info & 0x0F;
+ }
+
+ pub fn get_visibility(self: *const Elf64Symbol) u8 {
+ return self.other & 0x03;
+ }
+};
+
+pub const Elf64Rela = extern struct {
+ offset: u64 align(1),
+ info: u64 align(1),
+ addend: i64 align(1),
+
+ pub fn get_symbol(self: *const Elf64Rela) u32 {
+ return @truncate(self.info >> 32);
+ }
+
+ pub fn get_type(self: *const Elf64Rela) u32 {
+ return @truncate(self.info & 0xFFFFFFFF);
+ }
+};
+
+pub const LoadedSegment = struct {
+ virtual_address: u64,
+ physical_address: u64,
+ memory_size: u64,
+ flags: u32,
+};
+
+pub const LoadedImage = struct {
+ entry_point: u64,
+ base_address: u64,
+ end_address: u64,
+ segments: [16]LoadedSegment,
+ segment_count: usize,
+
+ pub fn total_size(self: *const LoadedImage) u64 {
+ return self.end_address - self.base_address;
+ }
+};
diff --git a/hikari/loader/loader.zig b/hikari/loader/loader.zig
new file mode 100644
index 0000000..2302cd5
--- /dev/null
+++ b/hikari/loader/loader.zig
@@ -0,0 +1,3 @@
+//! Hikari Loader Subsystem
+
+pub const elf = @import("elf/elf.zig");
diff --git a/hikari/menu/input.zig b/hikari/menu/input.zig
new file mode 100644
index 0000000..34fa2a9
--- /dev/null
+++ b/hikari/menu/input.zig
@@ -0,0 +1,82 @@
+//! Hikari Menu Input
+
+const efi = @import("../efi/efi.zig");
+
+pub const InputAction = enum {
+ none,
+ up,
+ down,
+ select,
+ cancel,
+ page_up,
+ page_down,
+ home,
+ end,
+};
+
+pub const Input = struct {
+ system_table: *efi.services.SystemTable,
+
+ pub fn initialize(system_table: *efi.services.SystemTable) Input {
+ return Input{
+ .system_table = system_table,
+ };
+ }
+
+ pub fn poll(self: *Input) InputAction {
+ var key: efi.types.input.InputKey = undefined;
+ const status = self.system_table.console_input.read_key_stroke(
+ self.system_table.console_input,
+ &key,
+ );
+
+ if (efi.types.is_error(status)) {
+ return .none;
+ }
+
+ if (key.scan_code != 0) {
+ return switch (key.scan_code) {
+ efi.constants.keyboard.scan_up => .up,
+ efi.constants.keyboard.scan_down => .down,
+ efi.constants.keyboard.scan_escape => .cancel,
+ efi.constants.keyboard.scan_page_up => .page_up,
+ efi.constants.keyboard.scan_page_down => .page_down,
+ efi.constants.keyboard.scan_home => .home,
+ efi.constants.keyboard.scan_end => .end,
+ else => .none,
+ };
+ }
+
+ if (key.unicode_char != 0) {
+ return switch (key.unicode_char) {
+ '\r', ' ' => .select,
+ 'j', 'J' => .down,
+ 'k', 'K' => .up,
+ 'q', 'Q' => .cancel,
+ else => .none,
+ };
+ }
+
+ return .none;
+ }
+
+ pub fn wait_for_key(self: *Input) void {
+ var index: usize = 0;
+ const events = [_]efi.types.Event{self.system_table.console_input.wait_for_key};
+ _ = self.system_table.boot_services.wait_for_event(1, &events, &index);
+ }
+
+ pub fn wait_for_action(self: *Input) InputAction {
+ while (true) {
+ const action = self.poll();
+ if (action != .none) {
+ return action;
+ }
+ self.wait_for_key();
+ }
+ }
+
+ pub fn clear_input_buffer(self: *Input) void {
+ while (self.poll() != .none) {}
+ }
+};
diff --git a/hikari/menu/menu.zig b/hikari/menu/menu.zig
new file mode 100644
index 0000000..14ca544
--- /dev/null
+++ b/hikari/menu/menu.zig
@@ -0,0 +1,125 @@
+//! Hikari Menu Subsystem
+
+pub const theme = @import("theme.zig");
+pub const input = @import("input.zig");
+pub const renderer = @import("renderer.zig");
+
+pub const Theme = theme.Theme;
+pub const Input = input.Input;
+pub const InputAction = input.InputAction;
+pub const Renderer = renderer.Renderer;
+pub const MenuItem = renderer.MenuItem;
+
+pub const BootMenu = struct {
+ renderer: *Renderer,
+ input: *Input,
+ items: []const MenuItem,
+ selected: usize,
+ scroll_offset: usize,
+ title: []const u8,
+
+ pub fn initialize(
+ menu_renderer: *Renderer,
+ menu_input: *Input,
+ items: []const MenuItem,
+ title: []const u8,
+ ) BootMenu {
+ return BootMenu{
+ .renderer = menu_renderer,
+ .input = menu_input,
+ .items = items,
+ .selected = 0,
+ .scroll_offset = 0,
+ .title = title,
+ };
+ }
+
+ pub fn run(self: *BootMenu) ?usize {
+ self.input.clear_input_buffer();
+ self.draw();
+
+ while (true) {
+ const action = self.input.wait_for_action();
+
+ switch (action) {
+ .up => self.move_up(),
+ .down => self.move_down(),
+ .page_up => self.page_up(),
+ .page_down => self.page_down(),
+ .home => self.go_home(),
+ .end => self.go_end(),
+ .select => {
+ if (self.items[self.selected].enabled) {
+ return self.selected;
+ }
+ },
+ .cancel => return null,
+ .none => {},
+ }
+
+ self.draw();
+ }
+ }
+
+ fn draw(self: *BootMenu) void {
+ self.renderer.draw_background();
+ self.renderer.draw_title(self.title);
+ self.renderer.draw_menu_items(self.items, self.selected, self.scroll_offset);
+ self.renderer.draw_description(self.items[self.selected].description);
+ self.renderer.draw_scrollbar(self.items.len, self.renderer.visible_items, self.scroll_offset);
+ self.renderer.draw_footer();
+ }
+
+ fn move_up(self: *BootMenu) void {
+ if (self.selected > 0) {
+ self.selected -= 1;
+ if (self.selected < self.scroll_offset) {
+ self.scroll_offset = self.selected;
+ }
+ }
+ }
+
+ fn move_down(self: *BootMenu) void {
+ if (self.selected < self.items.len - 1) {
+ self.selected += 1;
+ if (self.selected >= self.scroll_offset + self.renderer.visible_items) {
+ self.scroll_offset = self.selected - self.renderer.visible_items + 1;
+ }
+ }
+ }
+
+ fn page_up(self: *BootMenu) void {
+ if (self.selected >= self.renderer.visible_items) {
+ self.selected -= self.renderer.visible_items;
+ } else {
+ self.selected = 0;
+ }
+ if (self.selected < self.scroll_offset) {
+ self.scroll_offset = self.selected;
+ }
+ }
+
+ fn page_down(self: *BootMenu) void {
+ const remaining = self.items.len - 1 - self.selected;
+ if (remaining >= self.renderer.visible_items) {
+ self.selected += self.renderer.visible_items;
+ } else {
+ self.selected = self.items.len - 1;
+ }
+ if (self.selected >= self.scroll_offset + self.renderer.visible_items) {
+ self.scroll_offset = self.selected - self.renderer.visible_items + 1;
+ }
+ }
+
+ fn go_home(self: *BootMenu) void {
+ self.selected = 0;
+ self.scroll_offset = 0;
+ }
+
+ fn go_end(self: *BootMenu) void {
+ self.selected = self.items.len - 1;
+ if (self.items.len > self.renderer.visible_items) {
+ self.scroll_offset = self.items.len - self.renderer.visible_items;
+ }
+ }
+};
diff --git a/hikari/menu/renderer.zig b/hikari/menu/renderer.zig
new file mode 100644
index 0000000..9cbf3ae
--- /dev/null
+++ b/hikari/menu/renderer.zig
@@ -0,0 +1,199 @@
+//! Hikari Menu Renderer
+
+const display = @import("../display/display.zig");
+const theme_mod = @import("theme.zig");
+
+pub const MenuItem = struct {
+ label: []const u8,
+ description: []const u8,
+ enabled: bool,
+};
+
+pub const Renderer = struct {
+ fb: *display.Framebuffer,
+ text: *display.TextRenderer,
+ theme: theme_mod.Theme,
+ menu_x: u32,
+ menu_y: u32,
+ menu_width: u32,
+ visible_items: u32,
+
+ pub fn initialize(
+ fb: *display.Framebuffer,
+ text_renderer: *display.TextRenderer,
+ theme: theme_mod.Theme,
+ ) Renderer {
+ const char_width = text_renderer.font.width;
+ const char_height = text_renderer.font.height;
+
+ const menu_width = 60;
+ const menu_x = (text_renderer.columns - menu_width) / 2;
+ const menu_y: u32 = 4;
+ const visible_items = text_renderer.rows - 10;
+
+ _ = char_width;
+ _ = char_height;
+
+ return Renderer{
+ .fb = fb,
+ .text = text_renderer,
+ .theme = theme,
+ .menu_x = menu_x,
+ .menu_y = menu_y,
+ .menu_width = menu_width,
+ .visible_items = visible_items,
+ };
+ }
+
+ pub fn draw_background(self: *Renderer) void {
+ self.fb.clear(self.theme.background);
+ }
+
+ pub fn draw_title(self: *Renderer, title: []const u8) void {
+ self.text.set_colors(self.theme.title_fg, self.theme.background);
+
+ const title_x = (self.text.columns - @as(u32, @intCast(title.len))) / 2;
+ self.text.set_cursor(title_x, 1);
+ self.text.print(title);
+
+ self.text.set_cursor(self.menu_x, 2);
+ var i: u32 = 0;
+ while (i < self.menu_width) : (i += 1) {
+ self.text.put_char('-');
+ }
+ }
+
+ pub fn draw_menu_items(
+ self: *Renderer,
+ items: []const MenuItem,
+ selected: usize,
+ scroll_offset: usize,
+ ) void {
+ var row: u32 = 0;
+ while (row < self.visible_items and (scroll_offset + row) < items.len) : (row += 1) {
+ const item_index = scroll_offset + row;
+ const item = items[item_index];
+ const is_selected = item_index == selected;
+
+ self.draw_menu_item(item, row, is_selected);
+ }
+ }
+
+ fn draw_menu_item(self: *Renderer, item: MenuItem, row: u32, selected: bool) void {
+ const y = self.menu_y + row;
+
+ if (selected) {
+ self.text.set_colors(self.theme.highlight_fg, self.theme.highlight_bg);
+ } else if (!item.enabled) {
+ self.text.set_colors(self.theme.border, self.theme.background);
+ } else {
+ self.text.set_colors(self.theme.foreground, self.theme.background);
+ }
+
+ self.text.set_cursor(self.menu_x, y);
+
+ if (selected) {
+ self.text.print("> ");
+ } else {
+ self.text.print(" ");
+ }
+
+ self.text.print(item.label);
+
+ var i: u32 = @intCast(item.label.len + 2);
+ while (i < self.menu_width) : (i += 1) {
+ self.text.put_char(' ');
+ }
+ }
+
+ pub fn draw_description(self: *Renderer, description: []const u8) void {
+ const desc_y = self.text.rows - 3;
+
+ self.text.set_colors(self.theme.foreground, self.theme.background);
+ self.text.set_cursor(self.menu_x, desc_y);
+
+ var i: u32 = 0;
+ while (i < self.menu_width) : (i += 1) {
+ self.text.put_char(' ');
+ }
+
+ self.text.set_cursor(self.menu_x, desc_y);
+ self.text.print(description);
+ }
+
+ pub fn draw_footer(self: *Renderer) void {
+ const footer_y = self.text.rows - 1;
+
+ self.text.set_colors(self.theme.border, self.theme.background);
+ self.text.set_cursor(self.menu_x, footer_y);
+ self.text.print("Up/Down: Navigate Enter: Select Esc: Cancel");
+ }
+
+ pub fn draw_scrollbar(self: *Renderer, total_items: usize, visible: usize, offset: usize) void {
+ if (total_items <= visible) {
+ return;
+ }
+
+ const scrollbar_x = self.menu_x + self.menu_width + 1;
+ const scrollbar_height = self.visible_items;
+
+ const thumb_height = (visible * scrollbar_height) / total_items;
+ const thumb_height_clamped = if (thumb_height < 1) 1 else @as(u32, @intCast(thumb_height));
+
+ const thumb_offset = (offset * scrollbar_height) / total_items;
+
+ var i: u32 = 0;
+ while (i < scrollbar_height) : (i += 1) {
+ self.text.set_cursor(scrollbar_x, self.menu_y + i);
+
+ if (i >= thumb_offset and i < thumb_offset + thumb_height_clamped) {
+ self.text.set_colors(self.theme.highlight_bg, self.theme.background);
+ self.text.put_char('#');
+ } else {
+ self.text.set_colors(self.theme.border, self.theme.background);
+ self.text.put_char('|');
+ }
+ }
+ }
+
+ pub fn draw_progress(self: *Renderer, message: []const u8, percent: u32) void {
+ const progress_y = self.text.rows / 2;
+ const progress_width: u32 = 40;
+ const progress_x = (self.text.columns - progress_width) / 2;
+
+ self.text.set_colors(self.theme.foreground, self.theme.background);
+ self.text.set_cursor((self.text.columns - @as(u32, @intCast(message.len))) / 2, progress_y - 1);
+ self.text.print(message);
+
+ self.text.set_cursor(progress_x, progress_y);
+ self.text.put_char('[');
+
+ const filled = (percent * (progress_width - 2)) / 100;
+ var i: u32 = 0;
+ while (i < progress_width - 2) : (i += 1) {
+ if (i < filled) {
+ self.text.set_colors(self.theme.success, self.theme.background);
+ self.text.put_char('=');
+ } else {
+ self.text.set_colors(self.theme.border, self.theme.background);
+ self.text.put_char('-');
+ }
+ }
+
+ self.text.set_colors(self.theme.foreground, self.theme.background);
+ self.text.put_char(']');
+ }
+
+ pub fn draw_message(self: *Renderer, message: []const u8, is_error: bool) void {
+ const msg_y = self.text.rows / 2;
+
+ if (is_error) {
+ self.text.set_colors(self.theme.error_color, self.theme.background);
+ } else {
+ self.text.set_colors(self.theme.success, self.theme.background);
+ }
+
+ self.text.set_cursor((self.text.columns - @as(u32, @intCast(message.len))) / 2, msg_y);
+ self.text.print(message);
+ }
+};
diff --git a/hikari/menu/theme.zig b/hikari/menu/theme.zig
new file mode 100644
index 0000000..b306ec0
--- /dev/null
+++ b/hikari/menu/theme.zig
@@ -0,0 +1,55 @@
+//! Hikari Menu Theme
+
+const display = @import("../display/display.zig");
+
+pub const Theme = struct {
+ background: display.Color,
+ foreground: display.Color,
+ highlight_bg: display.Color,
+ highlight_fg: display.Color,
+ title_fg: display.Color,
+ border: display.Color,
+ shadow: display.Color,
+ success: display.Color,
+ warning: display.Color,
+ error_color: display.Color,
+
+ pub const default = Theme{
+ .background = display.Color.rgb(20, 20, 30),
+ .foreground = display.Color.rgb(220, 220, 230),
+ .highlight_bg = display.Color.rgb(80, 60, 140),
+ .highlight_fg = display.Color.rgb(255, 255, 255),
+ .title_fg = display.Color.rgb(180, 140, 255),
+ .border = display.Color.rgb(60, 50, 80),
+ .shadow = display.Color.rgb(10, 10, 15),
+ .success = display.Color.rgb(80, 200, 120),
+ .warning = display.Color.rgb(255, 200, 80),
+ .error_color = display.Color.rgb(255, 80, 80),
+ };
+
+ pub const light = Theme{
+ .background = display.Color.rgb(240, 240, 245),
+ .foreground = display.Color.rgb(30, 30, 40),
+ .highlight_bg = display.Color.rgb(100, 80, 180),
+ .highlight_fg = display.Color.rgb(255, 255, 255),
+ .title_fg = display.Color.rgb(60, 40, 120),
+ .border = display.Color.rgb(180, 180, 190),
+ .shadow = display.Color.rgb(200, 200, 210),
+ .success = display.Color.rgb(40, 160, 80),
+ .warning = display.Color.rgb(200, 150, 40),
+ .error_color = display.Color.rgb(200, 60, 60),
+ };
+
+ pub const akiba = Theme{
+ .background = display.Color.rgb(15, 0, 30),
+ .foreground = display.Color.rgb(255, 120, 200),
+ .highlight_bg = display.Color.rgb(255, 0, 128),
+ .highlight_fg = display.Color.rgb(255, 255, 255),
+ .title_fg = display.Color.rgb(0, 255, 255),
+ .border = display.Color.rgb(128, 0, 255),
+ .shadow = display.Color.rgb(5, 0, 10),
+ .success = display.Color.rgb(0, 255, 128),
+ .warning = display.Color.rgb(255, 255, 0),
+ .error_color = display.Color.rgb(255, 0, 64),
+ };
+};
diff --git a/hikari/paging/constants.zig b/hikari/paging/constants.zig
new file mode 100644
index 0000000..73a4b53
--- /dev/null
+++ b/hikari/paging/constants.zig
@@ -0,0 +1,35 @@
+//! Hikari Paging Constants
+
+pub const page_size: u64 = 4096;
+pub const page_shift: u6 = 12;
+
+pub const entries_per_table: u64 = 512;
+pub const entry_shift: u6 = 9;
+
+pub const pml4_shift: u6 = 39;
+pub const pdpt_shift: u6 = 30;
+pub const pd_shift: u6 = 21;
+pub const pt_shift: u6 = 12;
+
+pub const huge_page_size_1g: u64 = 1 << 30;
+pub const huge_page_size_2m: u64 = 1 << 21;
+
+pub const flag_present: u64 = 1 << 0;
+pub const flag_writable: u64 = 1 << 1;
+pub const flag_user: u64 = 1 << 2;
+pub const flag_write_through: u64 = 1 << 3;
+pub const flag_cache_disable: u64 = 1 << 4;
+pub const flag_accessed: u64 = 1 << 5;
+pub const flag_dirty: u64 = 1 << 6;
+pub const flag_huge_page: u64 = 1 << 7;
+pub const flag_global: u64 = 1 << 8;
+pub const flag_no_execute: u64 = 1 << 63;
+
+pub const address_mask: u64 = 0x000FFFFFFFFFF000;
+
+pub const kernel_base: u64 = 0xFFFFFFFF80000000;
+pub const physmap_base: u64 = 0xFFFF800000000000;
+pub const physmap_size: u64 = 512 * huge_page_size_1g;
+
+pub const pml4_index_kernel: u64 = 511;
+pub const pml4_index_physmap: u64 = 256;
diff --git a/hikari/paging/paging.zig b/hikari/paging/paging.zig
new file mode 100644
index 0000000..0ad9321
--- /dev/null
+++ b/hikari/paging/paging.zig
@@ -0,0 +1,21 @@
+//! Hikari Paging Subsystem
+
+pub const constants = @import("constants.zig");
+pub const types = @import("types.zig");
+pub const setup = @import("setup.zig");
+
+pub const PageTableEntry = types.PageTableEntry;
+pub const PageTable = types.PageTable;
+pub const PageMapLevel4 = types.PageMapLevel4;
+pub const PageDirectoryPointerTable = types.PageDirectoryPointerTable;
+pub const PageDirectory = types.PageDirectory;
+pub const PageTableLevel1 = types.PageTableLevel1;
+
+pub const PageTableSetup = setup.PageTableSetup;
+pub const SetupError = setup.SetupError;
+
+pub const get_pml4_index = types.get_pml4_index;
+pub const get_pdpt_index = types.get_pdpt_index;
+pub const get_pd_index = types.get_pd_index;
+pub const get_pt_index = types.get_pt_index;
+pub const get_page_offset = types.get_page_offset;
diff --git a/hikari/paging/setup.zig b/hikari/paging/setup.zig
new file mode 100644
index 0000000..d897b78
--- /dev/null
+++ b/hikari/paging/setup.zig
@@ -0,0 +1,208 @@
+//! Hikari Page Table Setup
+
+const efi = @import("../efi/efi.zig");
+const constants = @import("constants.zig");
+const types = @import("types.zig");
+
+pub const SetupError = error{
+ allocation_failed,
+ invalid_mapping,
+};
+
+pub const PageTableSetup = struct {
+ boot_services: *efi.services.BootServices,
+ pml4: *types.PageMapLevel4,
+ allocated_tables: [64]*types.PageTable,
+ allocated_count: usize,
+
+ pub fn initialize(boot_services: *efi.services.BootServices) SetupError!PageTableSetup {
+ var pml4_addr: efi.types.PhysicalAddress = 0;
+ const status = boot_services.allocate_pages(
+ .any_pages,
+ .loader_data,
+ 1,
+ &pml4_addr,
+ );
+
+ if (efi.types.is_error(status)) {
+ return SetupError.allocation_failed;
+ }
+
+ const pml4: *types.PageMapLevel4 = @ptrFromInt(pml4_addr);
+ pml4.clear();
+
+ return PageTableSetup{
+ .boot_services = boot_services,
+ .pml4 = pml4,
+ .allocated_tables = undefined,
+ .allocated_count = 0,
+ };
+ }
+
+ pub fn allocate_table(self: *PageTableSetup) SetupError!*types.PageTable {
+ var table_addr: efi.types.PhysicalAddress = 0;
+ const status = self.boot_services.allocate_pages(
+ .any_pages,
+ .loader_data,
+ 1,
+ &table_addr,
+ );
+
+ if (efi.types.is_error(status)) {
+ return SetupError.allocation_failed;
+ }
+
+ const table: *types.PageTable = @ptrFromInt(table_addr);
+ table.clear();
+
+ if (self.allocated_count < 64) {
+ self.allocated_tables[self.allocated_count] = table;
+ self.allocated_count += 1;
+ }
+
+ return table;
+ }
+
+ pub fn map_identity(self: *PageTableSetup, start: u64, size: u64) SetupError!void {
+ try self.map_range(start, start, size, constants.flag_present | constants.flag_writable);
+ }
+
+ pub fn map_kernel(self: *PageTableSetup, physical: u64, size: u64) SetupError!void {
+ try self.map_range(
+ constants.kernel_base,
+ physical,
+ size,
+ constants.flag_present | constants.flag_writable | constants.flag_global,
+ );
+ }
+
+ pub fn map_physmap(self: *PageTableSetup, max_physical: u64) SetupError!void {
+ const pdpt = try self.allocate_table();
+
+ const pml4_entry = types.PageTableEntry.from_address(
+ @intFromPtr(pdpt),
+ constants.flag_present | constants.flag_writable,
+ );
+ self.pml4.set_entry(constants.pml4_index_physmap, pml4_entry);
+
+ const size_to_map = if (max_physical > constants.physmap_size)
+ constants.physmap_size
+ else
+ max_physical;
+
+ const gb_count = (size_to_map + constants.huge_page_size_1g - 1) / constants.huge_page_size_1g;
+
+ var i: u64 = 0;
+ while (i < gb_count and i < 512) : (i += 1) {
+ const physical_addr = i * constants.huge_page_size_1g;
+ const pdpt_entry = types.PageTableEntry.from_address(
+ physical_addr,
+ constants.flag_present | constants.flag_writable | constants.flag_huge_page | constants.flag_global,
+ );
+ pdpt.set_entry(@truncate(i), pdpt_entry);
+ }
+ }
+
+ pub fn map_range(self: *PageTableSetup, virtual: u64, physical: u64, size: u64, flags: u64) SetupError!void {
+ var virt = virtual & ~@as(u64, constants.page_size - 1);
+ var phys = physical & ~@as(u64, constants.page_size - 1);
+ var remaining = size;
+
+ while (remaining > 0) {
+ const pml4_idx = types.get_pml4_index(virt);
+ var pdpt: *types.PageDirectoryPointerTable = undefined;
+
+ if (self.pml4.get_entry(pml4_idx).is_present()) {
+ pdpt = @ptrFromInt(self.pml4.get_entry(pml4_idx).get_address());
+ } else {
+ pdpt = try self.allocate_table();
+ const entry = types.PageTableEntry.from_address(
+ @intFromPtr(pdpt),
+ constants.flag_present | constants.flag_writable,
+ );
+ self.pml4.set_entry(pml4_idx, entry);
+ }
+
+ const pdpt_idx = types.get_pdpt_index(virt);
+ var pd: *types.PageDirectory = undefined;
+
+ if (pdpt.get_entry(pdpt_idx).is_present()) {
+ if (pdpt.get_entry(pdpt_idx).is_huge()) {
+ virt += constants.huge_page_size_1g;
+ phys += constants.huge_page_size_1g;
+ if (remaining >= constants.huge_page_size_1g) {
+ remaining -= constants.huge_page_size_1g;
+ } else {
+ remaining = 0;
+ }
+ continue;
+ }
+ pd = @ptrFromInt(pdpt.get_entry(pdpt_idx).get_address());
+ } else {
+ pd = try self.allocate_table();
+ const entry = types.PageTableEntry.from_address(
+ @intFromPtr(pd),
+ constants.flag_present | constants.flag_writable,
+ );
+ pdpt.set_entry(pdpt_idx, entry);
+ }
+
+ const pd_idx = types.get_pd_index(virt);
+
+ if (remaining >= constants.huge_page_size_2m and
+ (virt & (constants.huge_page_size_2m - 1)) == 0 and
+ (phys & (constants.huge_page_size_2m - 1)) == 0)
+ {
+ const entry = types.PageTableEntry.from_address(
+ phys,
+ flags | constants.flag_huge_page,
+ );
+ pd.set_entry(pd_idx, entry);
+
+ virt += constants.huge_page_size_2m;
+ phys += constants.huge_page_size_2m;
+ remaining -= constants.huge_page_size_2m;
+ continue;
+ }
+
+ var pt: *types.PageTableLevel1 = undefined;
+
+ if (pd.get_entry(pd_idx).is_present()) {
+ if (pd.get_entry(pd_idx).is_huge()) {
+ virt += constants.huge_page_size_2m;
+ phys += constants.huge_page_size_2m;
+ if (remaining >= constants.huge_page_size_2m) {
+ remaining -= constants.huge_page_size_2m;
+ } else {
+ remaining = 0;
+ }
+ continue;
+ }
+ pt = @ptrFromInt(pd.get_entry(pd_idx).get_address());
+ } else {
+ pt = try self.allocate_table();
+ const entry = types.PageTableEntry.from_address(
+ @intFromPtr(pt),
+ constants.flag_present | constants.flag_writable,
+ );
+ pd.set_entry(pd_idx, entry);
+ }
+
+ const pt_idx = types.get_pt_index(virt);
+ const entry = types.PageTableEntry.from_address(phys, flags);
+ pt.set_entry(pt_idx, entry);
+
+ virt += constants.page_size;
+ phys += constants.page_size;
+ if (remaining >= constants.page_size) {
+ remaining -= constants.page_size;
+ } else {
+ remaining = 0;
+ }
+ }
+ }
+
+ pub fn get_pml4_address(self: *PageTableSetup) u64 {
+ return @intFromPtr(self.pml4);
+ }
+};
diff --git a/hikari/paging/types.zig b/hikari/paging/types.zig
new file mode 100644
index 0000000..f5e19b4
--- /dev/null
+++ b/hikari/paging/types.zig
@@ -0,0 +1,88 @@
+//! Hikari Paging Types
+
+const constants = @import("constants.zig");
+
+pub const PageTableEntry = packed struct(u64) {
+ present: bool,
+ writable: bool,
+ user: bool,
+ write_through: bool,
+ cache_disable: bool,
+ accessed: bool,
+ dirty: bool,
+ huge_page: bool,
+ global: bool,
+ available_low: u3,
+ address_bits: u40,
+ available_high: u11,
+ no_execute: bool,
+
+ pub fn empty() PageTableEntry {
+ return @bitCast(@as(u64, 0));
+ }
+
+ pub fn from_address(address: u64, flags: u64) PageTableEntry {
+ const entry: u64 = (address & constants.address_mask) | flags;
+ return @bitCast(entry);
+ }
+
+ pub fn get_address(self: PageTableEntry) u64 {
+ const raw: u64 = @bitCast(self);
+ return raw & constants.address_mask;
+ }
+
+ pub fn is_present(self: PageTableEntry) bool {
+ return self.present;
+ }
+
+ pub fn is_huge(self: PageTableEntry) bool {
+ return self.huge_page;
+ }
+
+ pub fn to_raw(self: PageTableEntry) u64 {
+ return @bitCast(self);
+ }
+};
+
+pub const PageTable = struct {
+ entries: [512]PageTableEntry,
+
+ pub fn clear(self: *PageTable) void {
+ for (&self.entries) |*entry| {
+ entry.* = PageTableEntry.empty();
+ }
+ }
+
+ pub fn get_entry(self: *PageTable, index: usize) *PageTableEntry {
+ return &self.entries[index];
+ }
+
+ pub fn set_entry(self: *PageTable, index: usize, entry: PageTableEntry) void {
+ self.entries[index] = entry;
+ }
+};
+
+pub const PageMapLevel4 = PageTable;
+pub const PageDirectoryPointerTable = PageTable;
+pub const PageDirectory = PageTable;
+pub const PageTableLevel1 = PageTable;
+
+pub fn get_pml4_index(address: u64) usize {
+ return @truncate((address >> constants.pml4_shift) & 0x1FF);
+}
+
+pub fn get_pdpt_index(address: u64) usize {
+ return @truncate((address >> constants.pdpt_shift) & 0x1FF);
+}
+
+pub fn get_pd_index(address: u64) usize {
+ return @truncate((address >> constants.pd_shift) & 0x1FF);
+}
+
+pub fn get_pt_index(address: u64) usize {
+ return @truncate((address >> constants.pt_shift) & 0x1FF);
+}
+
+pub fn get_page_offset(address: u64) usize {
+ return @truncate(address & 0xFFF);
+}