Merge pull request 'Add TOML Support' (#2) from toml into main
Reviewed-on: #2
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							@@ -10,3 +10,6 @@
 | 
			
		||||
[submodule "lib/zig-datetime"]
 | 
			
		||||
	path = lib/zig-datetime
 | 
			
		||||
	url = https://github.com/frmdstryr/zig-datetime
 | 
			
		||||
[submodule "lib/zig-toml"]
 | 
			
		||||
	path = lib/zig-toml
 | 
			
		||||
	url = https://github.com/aeronavery/zig-toml
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,9 @@ pub fn build(b: *std.build.Builder) void {
 | 
			
		||||
    // Argument Parsing Library
 | 
			
		||||
    exe.addPackagePath("clap", "lib/zig-clap/clap.zig");
 | 
			
		||||
 | 
			
		||||
    // TOML Library
 | 
			
		||||
    exe.addPackagePath("toml", "lib/zig-toml/src/toml.zig");
 | 
			
		||||
 | 
			
		||||
    // Zig SDL Bindings: https://github.com/MasterQ32/SDL.zig
 | 
			
		||||
    const sdk = Sdk.init(b);
 | 
			
		||||
    sdk.link(exe, .dynamic);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								lib/zig-toml
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								lib/zig-toml
									
									
									
									
									
										Submodule
									
								
							 Submodule lib/zig-toml added at 5dfa919e03
									
								
							
							
								
								
									
										74
									
								
								src/config.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/config.zig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const toml = @import("toml");
 | 
			
		||||
 | 
			
		||||
const Allocator = std.mem.Allocator;
 | 
			
		||||
 | 
			
		||||
var state: Config = .{};
 | 
			
		||||
 | 
			
		||||
const Config = struct {
 | 
			
		||||
    host: Host = .{},
 | 
			
		||||
    guest: Guest = .{},
 | 
			
		||||
    debug: Debug = .{},
 | 
			
		||||
 | 
			
		||||
    /// Settings related to the Computer the Emulator is being run on
 | 
			
		||||
    const Host = struct {
 | 
			
		||||
        /// Using Nearest-Neighbor, multiply the resolution of the GBA Window
 | 
			
		||||
        win_scale: i64 = 3,
 | 
			
		||||
        /// Enable Vsync
 | 
			
		||||
        ///
 | 
			
		||||
        /// Note: This does not affect whether Emulation is synced to 59Hz
 | 
			
		||||
        vsync: bool = true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Settings realted to the emulation itself
 | 
			
		||||
    const Guest = struct {
 | 
			
		||||
        /// Whether Emulation thread to sync to Audio Callbacks
 | 
			
		||||
        audio_sync: bool = true,
 | 
			
		||||
        /// Whether Emulation thread should sync to 59Hz
 | 
			
		||||
        video_sync: bool = true,
 | 
			
		||||
        /// Whether RTC I/O should always be enabled
 | 
			
		||||
        force_rtc: bool = false,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /// Settings related to debugging ZBA
 | 
			
		||||
    const Debug = struct {
 | 
			
		||||
        /// Enable CPU Trace logs
 | 
			
		||||
        cpu_trace: bool = false,
 | 
			
		||||
        /// If false and ZBA is build in debug mode, ZBA will panic on unhandled I/O
 | 
			
		||||
        unhandled_io: bool = true,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn config() *const Config {
 | 
			
		||||
    return &state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Reads a config file and then loads it into the global state
 | 
			
		||||
pub fn load(allocator: Allocator, config_path: []const u8) !void {
 | 
			
		||||
    var config_file = try std.fs.cwd().openFile(config_path, .{});
 | 
			
		||||
    defer config_file.close();
 | 
			
		||||
 | 
			
		||||
    const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos());
 | 
			
		||||
    defer allocator.free(contents);
 | 
			
		||||
 | 
			
		||||
    const table = try toml.parseContents(allocator, contents, null);
 | 
			
		||||
    defer table.deinit();
 | 
			
		||||
 | 
			
		||||
    // TODO: Report unknown config options
 | 
			
		||||
 | 
			
		||||
    if (table.keys.get("Host")) |host| {
 | 
			
		||||
        if (host.Table.keys.get("win_scale")) |scale| state.host.win_scale = scale.Integer;
 | 
			
		||||
        if (host.Table.keys.get("vsync")) |vsync| state.host.vsync = vsync.Boolean;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (table.keys.get("Guest")) |guest| {
 | 
			
		||||
        if (guest.Table.keys.get("audio_sync")) |sync| state.guest.audio_sync = sync.Boolean;
 | 
			
		||||
        if (guest.Table.keys.get("video_sync")) |sync| state.guest.video_sync = sync.Boolean;
 | 
			
		||||
        if (guest.Table.keys.get("force_rtc")) |forced| state.guest.force_rtc = forced.Boolean;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (table.keys.get("Debug")) |debug| {
 | 
			
		||||
        if (debug.Table.keys.get("cpu_trace")) |trace| state.debug.cpu_trace = trace.Boolean;
 | 
			
		||||
        if (debug.Table.keys.get("unhandled_io")) |unhandled| state.debug.unhandled_io = unhandled.Boolean;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const config = @import("../../config.zig");
 | 
			
		||||
 | 
			
		||||
const Bit = @import("bitfield").Bit;
 | 
			
		||||
const Bitfield = @import("bitfield").Bitfield;
 | 
			
		||||
const DateTime = @import("datetime").datetime.Datetime;
 | 
			
		||||
@@ -8,7 +10,6 @@ const Backup = @import("backup.zig").Backup;
 | 
			
		||||
const Gpio = @import("gpio.zig").Gpio;
 | 
			
		||||
const Allocator = std.mem.Allocator;
 | 
			
		||||
 | 
			
		||||
const force_rtc = @import("../emu.zig").force_rtc;
 | 
			
		||||
const log = std.log.scoped(.GamePak);
 | 
			
		||||
 | 
			
		||||
const Self = @This();
 | 
			
		||||
@@ -190,7 +191,7 @@ pub fn init(allocator: Allocator, cpu: *Arm7tdmi, rom_path: []const u8, save_pat
 | 
			
		||||
    const file_buf = try file.readToEndAlloc(allocator, try file.getEndPos());
 | 
			
		||||
    const title = file_buf[0xA0..0xAC].*;
 | 
			
		||||
    const kind = Backup.guess(file_buf);
 | 
			
		||||
    const device = if (force_rtc) .Rtc else guessDevice(file_buf);
 | 
			
		||||
    const device = if (config.config().guest.force_rtc) .Rtc else guessDevice(file_buf);
 | 
			
		||||
 | 
			
		||||
    logHeader(file_buf, &title);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -236,7 +236,6 @@ pub const thumb = struct {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cpu_logging = @import("emu.zig").cpu_logging;
 | 
			
		||||
const log = std.log.scoped(.Arm7Tdmi);
 | 
			
		||||
 | 
			
		||||
pub const Arm7tdmi = struct {
 | 
			
		||||
@@ -428,12 +427,12 @@ pub const Arm7tdmi = struct {
 | 
			
		||||
    pub fn step(self: *Self) void {
 | 
			
		||||
        if (self.cpsr.t.read()) {
 | 
			
		||||
            const opcode = self.fetch(u16);
 | 
			
		||||
            if (cpu_logging) self.logger.?.mgbaLog(self, opcode);
 | 
			
		||||
            if (self.logger) |*trace| trace.mgbaLog(self, opcode);
 | 
			
		||||
 | 
			
		||||
            thumb.lut[thumb.idx(opcode)](self, self.bus, opcode);
 | 
			
		||||
        } else {
 | 
			
		||||
            const opcode = self.fetch(u32);
 | 
			
		||||
            if (cpu_logging) self.logger.?.mgbaLog(self, opcode);
 | 
			
		||||
            if (self.logger) |*trace| trace.mgbaLog(self, opcode);
 | 
			
		||||
 | 
			
		||||
            if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) {
 | 
			
		||||
                arm.lut[arm.idx(opcode)](self, self.bus, opcode);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										148
									
								
								src/core/emu.zig
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								src/core/emu.zig
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const SDL = @import("sdl2");
 | 
			
		||||
const config = @import("../config.zig");
 | 
			
		||||
 | 
			
		||||
const Bus = @import("Bus.zig");
 | 
			
		||||
const Scheduler = @import("scheduler.zig").Scheduler;
 | 
			
		||||
@@ -12,14 +13,6 @@ const Thread = std.Thread;
 | 
			
		||||
const Atomic = std.atomic.Atomic;
 | 
			
		||||
const Allocator = std.mem.Allocator;
 | 
			
		||||
 | 
			
		||||
// TODO: Move these to a TOML File
 | 
			
		||||
const sync_audio = false; // Enable Audio Sync
 | 
			
		||||
const sync_video: RunKind = .LimitedFPS; // Configure Video Sync
 | 
			
		||||
pub const win_scale = 4; // 1x, 2x, 3x, etc. Window Scaling
 | 
			
		||||
pub const cpu_logging = false; // Enable detailed CPU logging
 | 
			
		||||
pub const allow_unhandled_io = true; // Only relevant in Debug Builds
 | 
			
		||||
pub const force_rtc = false;
 | 
			
		||||
 | 
			
		||||
// 228 Lines which consist of 308 dots (which are 4 cycles long)
 | 
			
		||||
const cycles_per_frame: u64 = 228 * (308 * 4); //280896
 | 
			
		||||
const clock_rate: u64 = 1 << 24; // 16.78MHz
 | 
			
		||||
@@ -40,18 +33,57 @@ const RunKind = enum {
 | 
			
		||||
    UnlimitedFPS,
 | 
			
		||||
    Limited,
 | 
			
		||||
    LimitedFPS,
 | 
			
		||||
    LimitedBusy,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn run(quit: *Atomic(bool), fps: *FpsTracker, sched: *Scheduler, cpu: *Arm7tdmi) void {
 | 
			
		||||
    if (sync_audio) log.info("Audio sync enabled", .{});
 | 
			
		||||
pub fn run(quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: *FpsTracker) void {
 | 
			
		||||
    const audio_sync = config.config().guest.audio_sync;
 | 
			
		||||
    if (audio_sync) log.info("Audio sync enabled", .{});
 | 
			
		||||
 | 
			
		||||
    switch (sync_video) {
 | 
			
		||||
        .Unlimited => runUnsynchronized(quit, sched, cpu, null),
 | 
			
		||||
        .Limited => runSynchronized(quit, sched, cpu, null),
 | 
			
		||||
        .UnlimitedFPS => runUnsynchronized(quit, sched, cpu, fps),
 | 
			
		||||
        .LimitedFPS => runSynchronized(quit, sched, cpu, fps),
 | 
			
		||||
        .LimitedBusy => runBusyLoop(quit, sched, cpu),
 | 
			
		||||
    if (config.config().guest.video_sync) {
 | 
			
		||||
        inner(.LimitedFPS, audio_sync, quit, scheduler, cpu, tracker);
 | 
			
		||||
    } else {
 | 
			
		||||
        inner(.UnlimitedFPS, audio_sync, quit, scheduler, cpu, tracker);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: ?*FpsTracker) void {
 | 
			
		||||
    if (kind == .UnlimitedFPS or kind == .LimitedFPS) {
 | 
			
		||||
        std.debug.assert(tracker != null);
 | 
			
		||||
        log.info("FPS tracking enabled", .{});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (kind) {
 | 
			
		||||
        .Unlimited, .UnlimitedFPS => {
 | 
			
		||||
            log.info("Emulation w/out video sync", .{});
 | 
			
		||||
 | 
			
		||||
            while (!quit.load(.SeqCst)) {
 | 
			
		||||
                runFrame(scheduler, cpu);
 | 
			
		||||
                audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
			
		||||
 | 
			
		||||
                if (kind == .UnlimitedFPS) tracker.?.tick();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        .Limited, .LimitedFPS => {
 | 
			
		||||
            log.info("Emulation w/ video sync", .{});
 | 
			
		||||
            var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer");
 | 
			
		||||
            var wake_time: u64 = frame_period;
 | 
			
		||||
 | 
			
		||||
            while (!quit.load(.SeqCst)) {
 | 
			
		||||
                runFrame(scheduler, cpu);
 | 
			
		||||
                const new_wake_time = videoSync(&timer, wake_time);
 | 
			
		||||
 | 
			
		||||
                // Spin to make up the difference of OS scheduler innacuracies
 | 
			
		||||
                // If we happen to also be syncing to audio, we choose to spin on
 | 
			
		||||
                // the amount of time needed for audio to catch up rather than
 | 
			
		||||
                // our expected wake-up time
 | 
			
		||||
 | 
			
		||||
                audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
			
		||||
                if (!audio_sync) spinLoop(&timer, wake_time);
 | 
			
		||||
                wake_time = new_wake_time;
 | 
			
		||||
 | 
			
		||||
                if (kind == .LimitedFPS) tracker.?.tick();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +104,7 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn syncToAudio(stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
 | 
			
		||||
fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
 | 
			
		||||
    const sample_size = 2 * @sizeOf(u16);
 | 
			
		||||
    const max_buf_size: c_int = 0x400;
 | 
			
		||||
 | 
			
		||||
@@ -85,90 +117,20 @@ fn syncToAudio(stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
        still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1;
 | 
			
		||||
        if (!sync_audio or !still_full) break;
 | 
			
		||||
        if (!audio_sync or !still_full) break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn runUnsynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, fps: ?*FpsTracker) void {
 | 
			
		||||
    log.info("Emulation thread w/out video sync", .{});
 | 
			
		||||
 | 
			
		||||
    if (fps) |tracker| {
 | 
			
		||||
        log.info("FPS Tracking Enabled", .{});
 | 
			
		||||
 | 
			
		||||
        while (!quit.load(.SeqCst)) {
 | 
			
		||||
            runFrame(sched, cpu);
 | 
			
		||||
            syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
			
		||||
 | 
			
		||||
            tracker.tick();
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        while (!quit.load(.SeqCst)) {
 | 
			
		||||
            runFrame(sched, cpu);
 | 
			
		||||
            syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn runSynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, fps: ?*FpsTracker) void {
 | 
			
		||||
    log.info("Emulation thread w/ video sync", .{});
 | 
			
		||||
    var timer = Timer.start() catch std.debug.panic("Failed to initialize std.timer.Timer", .{});
 | 
			
		||||
    var wake_time: u64 = frame_period;
 | 
			
		||||
 | 
			
		||||
    if (fps) |tracker| {
 | 
			
		||||
        log.info("FPS Tracking Enabled", .{});
 | 
			
		||||
 | 
			
		||||
        while (!quit.load(.SeqCst)) {
 | 
			
		||||
            runFrame(sched, cpu);
 | 
			
		||||
            const new_wake_time = blockOnVideo(&timer, wake_time);
 | 
			
		||||
 | 
			
		||||
            // Spin to make up the difference of OS scheduler innacuracies
 | 
			
		||||
            // If we happen to also be syncing to audio, we choose to spin on
 | 
			
		||||
            // the amount of time needed for audio to catch up rather than
 | 
			
		||||
            // our expected wake-up time
 | 
			
		||||
            syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
			
		||||
            if (!sync_audio) spinLoop(&timer, wake_time);
 | 
			
		||||
            wake_time = new_wake_time;
 | 
			
		||||
 | 
			
		||||
            tracker.tick();
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        while (!quit.load(.SeqCst)) {
 | 
			
		||||
            runFrame(sched, cpu);
 | 
			
		||||
            const new_wake_time = blockOnVideo(&timer, wake_time);
 | 
			
		||||
 | 
			
		||||
            // see above comment
 | 
			
		||||
            syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
			
		||||
            if (!sync_audio) spinLoop(&timer, wake_time);
 | 
			
		||||
            wake_time = new_wake_time;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fn blockOnVideo(timer: *Timer, wake_time: u64) u64 {
 | 
			
		||||
fn videoSync(timer: *Timer, wake_time: u64) u64 {
 | 
			
		||||
    // Use the OS scheduler to put the emulation thread to sleep
 | 
			
		||||
    const maybe_recalc_wake_time = sleep(timer, wake_time);
 | 
			
		||||
    const recalculated = sleep(timer, wake_time);
 | 
			
		||||
 | 
			
		||||
    // If sleep() determined we need to adjust our wake up time, do so
 | 
			
		||||
    // otherwise predict our next wake up time according to the frame period
 | 
			
		||||
    return if (maybe_recalc_wake_time) |recalc| recalc else wake_time + frame_period;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void {
 | 
			
		||||
    log.info("Emulation thread with video sync using busy loop", .{});
 | 
			
		||||
    var timer = Timer.start() catch unreachable;
 | 
			
		||||
    var wake_time: u64 = frame_period;
 | 
			
		||||
 | 
			
		||||
    while (!quit.load(.SeqCst)) {
 | 
			
		||||
        runFrame(sched, cpu);
 | 
			
		||||
        spinLoop(&timer, wake_time);
 | 
			
		||||
 | 
			
		||||
        syncToAudio(cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
 | 
			
		||||
 | 
			
		||||
        // Update to the new wake time
 | 
			
		||||
        wake_time += frame_period;
 | 
			
		||||
    }
 | 
			
		||||
    return recalculated orelse wake_time + frame_period;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Better sleep impl?
 | 
			
		||||
fn sleep(timer: *Timer, wake_time: u64) ?u64 {
 | 
			
		||||
    // const step = std.time.ns_per_ms * 10; // 10ms
 | 
			
		||||
    const timestamp = timer.read();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										98
									
								
								src/main.zig
									
									
									
									
									
								
							@@ -1,9 +1,10 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const builtin = @import("builtin");
 | 
			
		||||
 | 
			
		||||
const known_folders = @import("known_folders");
 | 
			
		||||
const clap = @import("clap");
 | 
			
		||||
 | 
			
		||||
const config = @import("config.zig");
 | 
			
		||||
 | 
			
		||||
const Gui = @import("platform.zig").Gui;
 | 
			
		||||
const Bus = @import("core/Bus.zig");
 | 
			
		||||
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
 | 
			
		||||
@@ -14,11 +15,8 @@ const Allocator = std.mem.Allocator;
 | 
			
		||||
const log = std.log.scoped(.Cli);
 | 
			
		||||
const width = @import("core/ppu.zig").width;
 | 
			
		||||
const height = @import("core/ppu.zig").height;
 | 
			
		||||
const cpu_logging = @import("core/emu.zig").cpu_logging;
 | 
			
		||||
pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level;
 | 
			
		||||
 | 
			
		||||
// TODO: Reimpl Logging
 | 
			
		||||
 | 
			
		||||
// CLI Arguments + Help Text
 | 
			
		||||
const params = clap.parseParamsComptime(
 | 
			
		||||
    \\-h, --help            Display this help and exit.
 | 
			
		||||
@@ -33,14 +31,27 @@ pub fn main() anyerror!void {
 | 
			
		||||
    defer std.debug.assert(!gpa.deinit());
 | 
			
		||||
    const allocator = gpa.allocator();
 | 
			
		||||
 | 
			
		||||
    // TODO: Make Error message not Linux Specific
 | 
			
		||||
    const data_path = try known_folders.getPath(allocator, .data) orelse exit("Unable to Determine XDG Data Path", .{});
 | 
			
		||||
    defer allocator.free(data_path);
 | 
			
		||||
 | 
			
		||||
    const config_path = try configFilePath(allocator, data_path);
 | 
			
		||||
    defer allocator.free(config_path);
 | 
			
		||||
 | 
			
		||||
    const save_path = try savePath(allocator, data_path);
 | 
			
		||||
    defer allocator.free(save_path);
 | 
			
		||||
 | 
			
		||||
    try config.load(allocator, config_path);
 | 
			
		||||
 | 
			
		||||
    // Handle CLI Input
 | 
			
		||||
    const result = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{});
 | 
			
		||||
    defer result.deinit();
 | 
			
		||||
 | 
			
		||||
    const paths = try handleArguments(allocator, &result);
 | 
			
		||||
    const paths = try handleArguments(allocator, data_path, &result);
 | 
			
		||||
    defer if (paths.save) |path| allocator.free(path);
 | 
			
		||||
 | 
			
		||||
    const log_file: ?std.fs.File = if (cpu_logging) try std.fs.cwd().createFile("zba.log", .{}) else null;
 | 
			
		||||
    const cpu_trace = config.config().debug.cpu_trace;
 | 
			
		||||
    const log_file: ?std.fs.File = if (cpu_trace) try std.fs.cwd().createFile("zba.log", .{}) else null;
 | 
			
		||||
    defer if (log_file) |file| file.close();
 | 
			
		||||
 | 
			
		||||
    // TODO: Take Emulator Init Code out of main.zig
 | 
			
		||||
@@ -60,39 +71,15 @@ pub fn main() anyerror!void {
 | 
			
		||||
    try gui.run(&cpu, &scheduler);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn getSavePath(allocator: Allocator) !?[]const u8 {
 | 
			
		||||
    const save_subpath = "zba" ++ [_]u8{std.fs.path.sep} ++ "save";
 | 
			
		||||
 | 
			
		||||
    const maybe_data_path = try known_folders.getPath(allocator, .data);
 | 
			
		||||
    defer if (maybe_data_path) |path| allocator.free(path);
 | 
			
		||||
 | 
			
		||||
    const save_path = if (maybe_data_path) |base| try std.fs.path.join(allocator, &[_][]const u8{ base, "zba", "save" }) else null;
 | 
			
		||||
 | 
			
		||||
    if (save_path) |_| {
 | 
			
		||||
        // If we've determined what our save path should be, ensure the prereq directories
 | 
			
		||||
        // are present so that we can successfully write to the path when necessary
 | 
			
		||||
        const maybe_data_dir = try known_folders.open(allocator, .data, .{});
 | 
			
		||||
        if (maybe_data_dir) |data_dir| try data_dir.makePath(save_subpath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return save_path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn getRomPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) ![]const u8 {
 | 
			
		||||
    return switch (result.positionals.len) {
 | 
			
		||||
        1 => result.positionals[0],
 | 
			
		||||
        0 => std.debug.panic("ZBA requires a positional path to a GamePak ROM.\n", .{}),
 | 
			
		||||
        else => std.debug.panic("ZBA received too many arguments.\n", .{}),
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn handleArguments(allocator: Allocator, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths {
 | 
			
		||||
    const rom_path = try getRomPath(result);
 | 
			
		||||
pub fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths {
 | 
			
		||||
    const rom_path = romPath(result);
 | 
			
		||||
    log.info("ROM path: {s}", .{rom_path});
 | 
			
		||||
 | 
			
		||||
    const bios_path = result.args.bios;
 | 
			
		||||
    if (bios_path) |path| log.info("BIOS path: {s}", .{path}) else log.info("No BIOS provided", .{});
 | 
			
		||||
    const save_path = try getSavePath(allocator);
 | 
			
		||||
    if (save_path) |path| log.info("Save path: {s}", .{path});
 | 
			
		||||
 | 
			
		||||
    const save_path = try savePath(allocator, data_path);
 | 
			
		||||
    log.info("Save path: {s}", .{save_path});
 | 
			
		||||
 | 
			
		||||
    return FilePaths{
 | 
			
		||||
        .rom = rom_path,
 | 
			
		||||
@@ -100,3 +87,42 @@ pub fn handleArguments(allocator: Allocator, result: *const clap.Result(clap.Hel
 | 
			
		||||
        .save = save_path,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn configFilePath(allocator: Allocator, data_path: []const u8) ![]const u8 {
 | 
			
		||||
    const path = try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "config.toml" });
 | 
			
		||||
 | 
			
		||||
    // We try to create the file exclusively, meaning that we err out if the file already exists.
 | 
			
		||||
    // All we care about is a file being there so we can just ignore that error in particular and
 | 
			
		||||
    // continue down the happy pathj
 | 
			
		||||
    std.fs.accessAbsolute(path, .{}) catch {
 | 
			
		||||
        const file_handle = try std.fs.createFileAbsolute(path, .{});
 | 
			
		||||
        file_handle.close();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn savePath(allocator: Allocator, data_path: []const u8) ![]const u8 {
 | 
			
		||||
    var dir = try std.fs.openDirAbsolute(data_path, .{});
 | 
			
		||||
    defer dir.close();
 | 
			
		||||
 | 
			
		||||
    // Will either make the path recursively, or just exit early since it already exists
 | 
			
		||||
    try dir.makePath("zba" ++ [_]u8{std.fs.path.sep} ++ "save");
 | 
			
		||||
 | 
			
		||||
    // FIXME: Do we have to allocate? :sad:
 | 
			
		||||
    return try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "save" });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn romPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) []const u8 {
 | 
			
		||||
    return switch (result.positionals.len) {
 | 
			
		||||
        1 => result.positionals[0],
 | 
			
		||||
        0 => exit("ZBA requires a path to a GamePak ROM\n", .{}),
 | 
			
		||||
        else => exit("ZBA received too many positional arguments. \n", .{}),
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn exit(comptime format: []const u8, args: anytype) noreturn {
 | 
			
		||||
    const stderr = std.io.getStdErr().writer();
 | 
			
		||||
    stderr.print(format, args) catch {}; // Just exit already...
 | 
			
		||||
    std.os.exit(1);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const SDL = @import("sdl2");
 | 
			
		||||
const emu = @import("core/emu.zig");
 | 
			
		||||
const config = @import("config.zig");
 | 
			
		||||
 | 
			
		||||
const Apu = @import("core/apu.zig").Apu;
 | 
			
		||||
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
 | 
			
		||||
@@ -10,8 +11,6 @@ const FpsTracker = @import("util.zig").FpsTracker;
 | 
			
		||||
const span = @import("util.zig").span;
 | 
			
		||||
 | 
			
		||||
const pitch = @import("core/ppu.zig").framebuf_pitch;
 | 
			
		||||
const scale = @import("core/emu.zig").win_scale;
 | 
			
		||||
 | 
			
		||||
const default_title: []const u8 = "ZBA";
 | 
			
		||||
 | 
			
		||||
pub const Gui = struct {
 | 
			
		||||
@@ -28,16 +27,19 @@ pub const Gui = struct {
 | 
			
		||||
        const ret = SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER);
 | 
			
		||||
        if (ret < 0) panic();
 | 
			
		||||
 | 
			
		||||
        const win_scale = @intCast(c_int, config.config().host.win_scale);
 | 
			
		||||
 | 
			
		||||
        const window = SDL.SDL_CreateWindow(
 | 
			
		||||
            default_title.ptr,
 | 
			
		||||
            SDL.SDL_WINDOWPOS_CENTERED,
 | 
			
		||||
            SDL.SDL_WINDOWPOS_CENTERED,
 | 
			
		||||
            @as(c_int, width * scale),
 | 
			
		||||
            @as(c_int, height * scale),
 | 
			
		||||
            @as(c_int, width * win_scale),
 | 
			
		||||
            @as(c_int, height * win_scale),
 | 
			
		||||
            SDL.SDL_WINDOW_SHOWN,
 | 
			
		||||
        ) orelse panic();
 | 
			
		||||
 | 
			
		||||
        const renderer = SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RENDERER_ACCELERATED | SDL.SDL_RENDERER_PRESENTVSYNC) orelse panic();
 | 
			
		||||
        const renderer_flags = SDL.SDL_RENDERER_ACCELERATED | if (config.config().host.vsync) SDL.SDL_RENDERER_PRESENTVSYNC else 0;
 | 
			
		||||
        const renderer = SDL.SDL_CreateRenderer(window, -1, @bitCast(u32, renderer_flags)) orelse panic();
 | 
			
		||||
 | 
			
		||||
        const texture = SDL.SDL_CreateTexture(
 | 
			
		||||
            renderer,
 | 
			
		||||
@@ -58,9 +60,9 @@ pub const Gui = struct {
 | 
			
		||||
 | 
			
		||||
    pub fn run(self: *Self, cpu: *Arm7tdmi, scheduler: *Scheduler) !void {
 | 
			
		||||
        var quit = std.atomic.Atomic(bool).init(false);
 | 
			
		||||
        var frame_rate = FpsTracker.init();
 | 
			
		||||
        var tracker = FpsTracker.init();
 | 
			
		||||
 | 
			
		||||
        const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, &frame_rate, scheduler, cpu });
 | 
			
		||||
        const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker });
 | 
			
		||||
        defer thread.join();
 | 
			
		||||
 | 
			
		||||
        var title_buf: [0x100]u8 = [_]u8{0} ** 0x100;
 | 
			
		||||
@@ -127,7 +129,7 @@ pub const Gui = struct {
 | 
			
		||||
            _ = SDL.SDL_RenderCopy(self.renderer, self.texture, null, null);
 | 
			
		||||
            SDL.SDL_RenderPresent(self.renderer);
 | 
			
		||||
 | 
			
		||||
            const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, frame_rate.value() }) catch unreachable;
 | 
			
		||||
            const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable;
 | 
			
		||||
            SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/util.zig
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/util.zig
									
									
									
									
									
								
							@@ -1,10 +1,10 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const builtin = @import("builtin");
 | 
			
		||||
const config = @import("config.zig");
 | 
			
		||||
 | 
			
		||||
const Log2Int = std.math.Log2Int;
 | 
			
		||||
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
 | 
			
		||||
 | 
			
		||||
const allow_unhandled_io = @import("core/emu.zig").allow_unhandled_io;
 | 
			
		||||
 | 
			
		||||
// Sign-Extend value of type `T` to type `U`
 | 
			
		||||
pub fn sext(comptime T: type, comptime U: type, value: T) T {
 | 
			
		||||
    // U must have less bits than T
 | 
			
		||||
@@ -144,8 +144,10 @@ pub const io = struct {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn undef(comptime T: type, log: anytype, comptime format: []const u8, args: anytype) ?T {
 | 
			
		||||
            const unhandled_io = config.config().debug.unhandled_io;
 | 
			
		||||
 | 
			
		||||
            log.warn(format, args);
 | 
			
		||||
            if (builtin.mode == .Debug and !allow_unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
 | 
			
		||||
            if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
@@ -153,8 +155,10 @@ pub const io = struct {
 | 
			
		||||
 | 
			
		||||
    pub const write = struct {
 | 
			
		||||
        pub fn undef(log: anytype, comptime format: []const u8, args: anytype) void {
 | 
			
		||||
            const unhandled_io = config.config().debug.unhandled_io;
 | 
			
		||||
 | 
			
		||||
            log.warn(format, args);
 | 
			
		||||
            if (builtin.mode == .Debug and !allow_unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
 | 
			
		||||
            if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user