Add TOML Support #2
							
								
								
									
										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