diff --git a/.gitmodules b/.gitmodules index 4470c92..e266212 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/SDL.zig"] path = lib/SDL.zig url = https://github.com/MasterQ32/SDL.zig +[submodule "lib/zig-clap"] + path = lib/zig-clap + url = https://github.com/Hejsil/zig-clap diff --git a/build.zig b/build.zig index cec93c3..c8c275d 100644 --- a/build.zig +++ b/build.zig @@ -15,7 +15,11 @@ pub fn build(b: *std.build.Builder) void { const exe = b.addExecutable("zba", "src/main.zig"); // Bitfield type from FlorenceOS: https://github.com/FlorenceOS/ - exe.addPackage(.{ .name = "bitfield", .path = .{ .path = "lib/util/bitfield.zig" } }); + // exe.addPackage(.{ .name = "bitfield", .path = .{ .path = "lib/util/bitfield.zig" } }); + exe.addPackagePath("bitfield", "lib/util/bitfield.zig"); + + // Argument Parsing Library + exe.addPackagePath("clap", "lib/zig-clap/clap.zig"); // Zig SDL Bindings: https://github.com/MasterQ32/SDL.zig const sdk = Sdk.init(b); diff --git a/lib/zig-clap b/lib/zig-clap new file mode 160000 index 0000000..a2af4a9 --- /dev/null +++ b/lib/zig-clap @@ -0,0 +1 @@ +Subproject commit a2af4a9267c547ec0f91ea1a84b2a50760ca5483 diff --git a/src/Bus.zig b/src/Bus.zig index 0c30d49..a6effec 100644 --- a/src/Bus.zig +++ b/src/Bus.zig @@ -18,10 +18,10 @@ iwram: Iwram, ewram: Ewram, io: Io, -pub fn init(alloc: Allocator, sched: *Scheduler, path: []const u8) !Self { +pub fn init(alloc: Allocator, sched: *Scheduler, rom_path: []const u8, maybe_bios: ?[]const u8) !Self { return Self{ - .pak = try GamePak.init(alloc, path), - .bios = try Bios.init(alloc, "./bin/gba_bios.bin"), // TODO: don't hardcode this + bundle open-sorce Boot ROM + .pak = try GamePak.init(alloc, rom_path), + .bios = try Bios.init(alloc, maybe_bios), .ppu = try Ppu.init(alloc, sched), .iwram = try Iwram.init(alloc), .ewram = try Ewram.init(alloc), diff --git a/src/bus/Bios.zig b/src/bus/Bios.zig index 30985d5..11a75f0 100644 --- a/src/bus/Bios.zig +++ b/src/bus/Bios.zig @@ -3,33 +3,46 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Self = @This(); -buf: []u8, +buf: ?[]u8, alloc: Allocator, -pub fn init(alloc: Allocator, path: []const u8) !Self { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); +pub fn init(alloc: Allocator, maybe_path: ?[]const u8) !Self { + var buf: ?[]u8 = null; + if (maybe_path) |path| { + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); - const len = try file.getEndPos(); + const len = try file.getEndPos(); + buf = try file.readToEndAlloc(alloc, len); + } return Self{ - .buf = try file.readToEndAlloc(alloc, len), + .buf = buf, .alloc = alloc, }; } pub fn deinit(self: Self) void { - self.alloc.free(self.buf); + if (self.buf) |buf| self.alloc.free(buf); } pub fn get32(self: *const Self, idx: usize) u32 { - return (@as(u32, self.buf[idx + 3]) << 24) | (@as(u32, self.buf[idx + 2]) << 16) | (@as(u32, self.buf[idx + 1]) << 8) | (@as(u32, self.buf[idx])); + if (self.buf) |buf| + return (@as(u32, buf[idx + 3]) << 24) | (@as(u32, buf[idx + 2]) << 16) | (@as(u32, buf[idx + 1]) << 8) | (@as(u32, buf[idx])); + + std.debug.panic("[CPU|BIOS:32] ZBA tried to read from 0x{X:0>8} but no BIOS was provided.", .{idx}); } pub fn get16(self: *const Self, idx: usize) u16 { - return (@as(u16, self.buf[idx + 1]) << 8) | @as(u16, self.buf[idx]); + if (self.buf) |buf| + return (@as(u16, buf[idx + 1]) << 8) | @as(u16, buf[idx]); + + std.debug.panic("[CPU|BIOS:16] ZBA tried to read from 0x{X:0>8} but no BIOS was provided.", .{idx}); } pub fn get8(self: *const Self, idx: usize) u8 { - return self.buf[idx]; + if (self.buf) |buf| + return buf[idx]; + + std.debug.panic("[CPU|BIOS:8] ZBA tried to read from 0x{X:0>8} but no BIOS was provided.", .{idx}); } diff --git a/src/main.zig b/src/main.zig index de2768f..0fcb3e8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const SDL = @import("sdl2"); +const clap = @import("clap"); const emu = @import("emu.zig"); const Bus = @import("Bus.zig"); @@ -25,25 +26,46 @@ pub fn main() anyerror!void { const alloc = gpa.allocator(); defer std.debug.assert(!gpa.deinit()); - // Handle CLI Arguments - const args = try std.process.argsAlloc(alloc); - defer std.process.argsFree(alloc, args); + // Parse CLI Arguments + const params = comptime [_]clap.Param(clap.Help){ + clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, + clap.parseParam("-b, --bios Optional Path to GBA BIOS ROM. ") catch unreachable, + clap.parseParam(" Path to GBA GamePak ROM ") catch unreachable, + }; - const zba_args: []const []const u8 = args[1..]; + var args = try clap.parse(clap.Help, ¶ms, .{}); + defer args.deinit(); - if (zba_args.len == 0) { - std.log.err("Expected PATH to Gameboy Advance ROM as a CLI argument", .{}); - return; - } else if (zba_args.len > 1) { - std.log.err("Too many CLI arguments were provided", .{}); - return; + if (args.flag("--help")) { + return clap.help(std.io.getStdErr().writer(), ¶ms); } + var maybe_bios: ?[]const u8 = null; + if (args.option("--bios")) |path| { + maybe_bios = path; + } + + const positionals = args.positionals(); + const stderr = std.io.getStdErr(); + defer stderr.close(); + + const rom_path = switch (positionals.len) { + 1 => positionals[0], + 0 => { + try stderr.writeAll("ZBA requires a positional path to a GamePak ROM.\n"); + return CliError.InsufficientOptions; + }, + else => { + try stderr.writeAll("ZBA received too many arguments.\n"); + return CliError.UnneededOptions; + }, + }; + // Initialize Emulator var scheduler = Scheduler.init(alloc); defer scheduler.deinit(); - var bus = try Bus.init(alloc, &scheduler, zba_args[0]); + var bus = try Bus.init(alloc, &scheduler, rom_path, maybe_bios); defer bus.deinit(); var cpu = Arm7tdmi.init(&scheduler, &bus); @@ -158,3 +180,8 @@ fn sdlPanic() noreturn { const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error"; @panic(std.mem.sliceTo(str, 0)); } + +const CliError = error{ + InsufficientOptions, + UnneededOptions, +};