2021-12-29 21:09:00 +00:00
|
|
|
const std = @import("std");
|
2022-04-21 13:43:08 +00:00
|
|
|
const builtin = @import("builtin");
|
2022-07-22 23:19:31 +00:00
|
|
|
const known_folders = @import("known_folders");
|
|
|
|
const clap = @import("clap");
|
2022-10-12 21:40:38 +00:00
|
|
|
|
|
|
|
const config = @import("config.zig");
|
2023-02-14 02:01:40 +00:00
|
|
|
const emu = @import("core/emu.zig");
|
2022-01-09 00:30:57 +00:00
|
|
|
|
2022-09-18 08:54:44 +00:00
|
|
|
const Gui = @import("platform.zig").Gui;
|
2022-07-27 16:44:24 +00:00
|
|
|
const Bus = @import("core/Bus.zig");
|
2022-07-22 23:19:31 +00:00
|
|
|
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
|
|
|
|
const Scheduler = @import("core/scheduler.zig").Scheduler;
|
2022-09-19 19:07:19 +00:00
|
|
|
const FilePaths = @import("util.zig").FilePaths;
|
2023-02-14 02:01:40 +00:00
|
|
|
const FpsTracker = @import("util.zig").FpsTracker;
|
2022-07-22 23:19:31 +00:00
|
|
|
const Allocator = std.mem.Allocator;
|
2023-02-14 02:01:40 +00:00
|
|
|
const Atomic = std.atomic.Atomic;
|
|
|
|
|
2022-09-18 08:54:44 +00:00
|
|
|
const log = std.log.scoped(.Cli);
|
2022-04-21 13:43:08 +00:00
|
|
|
pub const log_level = if (builtin.mode != .Debug) .info else std.log.default_level;
|
2022-03-22 17:41:18 +00:00
|
|
|
|
2022-05-23 15:38:44 +00:00
|
|
|
// CLI Arguments + Help Text
|
|
|
|
const params = clap.parseParamsComptime(
|
|
|
|
\\-h, --help Display this help and exit.
|
2022-10-17 20:25:04 +00:00
|
|
|
\\-s, --skip Skip BIOS.
|
2022-05-23 15:38:44 +00:00
|
|
|
\\-b, --bios <str> Optional path to a GBA BIOS ROM.
|
2022-12-15 23:37:38 +00:00
|
|
|
\\ --gdb Run ZBA from the context of a GDB Server
|
2022-10-17 20:25:04 +00:00
|
|
|
\\<str> Path to the GBA GamePak ROM.
|
2022-05-23 15:38:44 +00:00
|
|
|
\\
|
|
|
|
);
|
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
pub fn main() void {
|
2022-07-22 23:19:31 +00:00
|
|
|
// Main Allocator for ZBA
|
2021-12-29 21:09:00 +00:00
|
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
2022-01-04 02:08:55 +00:00
|
|
|
defer std.debug.assert(!gpa.deinit());
|
2022-10-17 21:13:13 +00:00
|
|
|
|
2022-07-22 23:19:31 +00:00
|
|
|
const allocator = gpa.allocator();
|
2021-12-29 21:09:00 +00:00
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
// Determine the Data Directory (stores saves)
|
2022-10-17 21:13:13 +00:00
|
|
|
const data_path = blk: {
|
|
|
|
const result = known_folders.getPath(allocator, .data);
|
2022-11-03 12:45:57 +00:00
|
|
|
const option = result catch |e| exitln("interrupted while determining the data folder: {}", .{e});
|
|
|
|
const path = option orelse exitln("no valid data folder found", .{});
|
|
|
|
ensureDataDirsExist(path) catch |e| exitln("failed to create folders under \"{s}\": {}", .{ path, e });
|
2022-10-21 07:39:16 +00:00
|
|
|
|
|
|
|
break :blk path;
|
2022-10-17 21:13:13 +00:00
|
|
|
};
|
2022-09-25 22:05:51 +00:00
|
|
|
defer allocator.free(data_path);
|
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
// Determine the Config Directory
|
|
|
|
const config_path = blk: {
|
|
|
|
const result = known_folders.getPath(allocator, .roaming_configuration);
|
|
|
|
const option = result catch |e| exitln("interreupted while determining the config folder: {}", .{e});
|
|
|
|
const path = option orelse exitln("no valid config folder found", .{});
|
|
|
|
ensureConfigDirExists(path) catch |e| exitln("failed to create required folder \"{s}\": {}", .{ path, e });
|
|
|
|
|
|
|
|
break :blk path;
|
|
|
|
};
|
|
|
|
defer allocator.free(config_path);
|
|
|
|
|
2022-10-17 21:13:13 +00:00
|
|
|
// Parse CLI
|
|
|
|
const result = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}) catch |e| exitln("failed to parse cli: {}", .{e});
|
|
|
|
defer result.deinit();
|
2022-09-25 22:05:51 +00:00
|
|
|
|
2022-10-17 21:13:13 +00:00
|
|
|
// TODO: Move config file to XDG Config directory?
|
2022-11-03 12:45:57 +00:00
|
|
|
const cfg_file_path = configFilePath(allocator, config_path) catch |e| exitln("failed to ready config file for access: {}", .{e});
|
|
|
|
defer allocator.free(cfg_file_path);
|
2022-09-25 22:05:51 +00:00
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
config.load(allocator, cfg_file_path) catch |e| exitln("failed to load config file: {}", .{e});
|
2022-01-02 05:37:21 +00:00
|
|
|
|
2022-10-17 21:13:13 +00:00
|
|
|
const paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
|
2022-07-22 23:19:31 +00:00
|
|
|
defer if (paths.save) |path| allocator.free(path);
|
2022-02-04 05:55:14 +00:00
|
|
|
|
2022-10-17 21:13:13 +00:00
|
|
|
const log_file = if (config.config().debug.cpu_trace) blk: {
|
|
|
|
break :blk std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e});
|
|
|
|
} else null;
|
2022-08-29 05:32:41 +00:00
|
|
|
defer if (log_file) |file| file.close();
|
|
|
|
|
2022-07-22 23:19:31 +00:00
|
|
|
// TODO: Take Emulator Init Code out of main.zig
|
|
|
|
var scheduler = Scheduler.init(allocator);
|
2022-01-04 02:08:55 +00:00
|
|
|
defer scheduler.deinit();
|
|
|
|
|
2022-08-29 05:32:41 +00:00
|
|
|
var bus: Bus = undefined;
|
2022-09-03 20:56:37 +00:00
|
|
|
var cpu = Arm7tdmi.init(&scheduler, &bus, log_file);
|
2022-01-28 20:33:38 +00:00
|
|
|
|
2022-10-17 21:13:13 +00:00
|
|
|
bus.init(allocator, &scheduler, &cpu, paths) catch |e| exitln("failed to init zba bus: {}", .{e});
|
2022-08-29 05:32:41 +00:00
|
|
|
defer bus.deinit();
|
|
|
|
|
2022-10-17 20:31:07 +00:00
|
|
|
if (config.config().guest.skip_bios or result.args.skip or paths.bios == null) {
|
2022-10-17 20:25:04 +00:00
|
|
|
cpu.fastBoot();
|
|
|
|
}
|
2022-10-09 17:11:18 +00:00
|
|
|
|
2023-01-01 09:42:02 +00:00
|
|
|
// TODO: Just copy the title instead of grabbing a pointer to it
|
|
|
|
var gui = Gui.init(allocator, &bus.pak.title, &bus.apu) catch |e| exitln("failed to init gui: {}", .{e});
|
2023-02-14 02:01:40 +00:00
|
|
|
defer gui.deinit();
|
|
|
|
|
2023-01-01 09:42:02 +00:00
|
|
|
var quit = Atomic(bool).init(false);
|
|
|
|
|
2022-12-15 23:37:38 +00:00
|
|
|
if (result.args.gdb) {
|
|
|
|
const Server = @import("gdbstub").Server;
|
|
|
|
const EmuThing = @import("core/emu.zig").EmuThing;
|
2022-01-28 20:33:38 +00:00
|
|
|
|
2023-01-29 13:06:06 +00:00
|
|
|
var wrapper = EmuThing.init(&cpu, &scheduler);
|
|
|
|
var emulator = wrapper.interface(allocator);
|
|
|
|
defer emulator.deinit();
|
2022-12-15 23:37:38 +00:00
|
|
|
|
|
|
|
log.info("Ready to connect", .{});
|
|
|
|
|
|
|
|
var server = Server.init(emulator) catch |e| exitln("failed to init gdb server: {}", .{e});
|
|
|
|
defer server.deinit(allocator);
|
|
|
|
|
2023-02-14 02:01:40 +00:00
|
|
|
log.info("Starting GDB Server Thread", .{});
|
|
|
|
|
|
|
|
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &quit }) catch |e| exitln("gdb server thread crashed: {}", .{e});
|
|
|
|
defer thread.join();
|
|
|
|
|
|
|
|
gui.run(.{
|
|
|
|
.cpu = &cpu,
|
|
|
|
.scheduler = &scheduler,
|
|
|
|
.quit = &quit,
|
|
|
|
}) catch |e| exitln("main thread panicked: {}", .{e});
|
2022-12-15 23:37:38 +00:00
|
|
|
} else {
|
2023-02-14 02:01:40 +00:00
|
|
|
var tracker = FpsTracker.init();
|
|
|
|
|
|
|
|
const thread = std.Thread.spawn(.{}, emu.run, .{ &quit, &scheduler, &cpu, &tracker }) catch |e| exitln("emu thread panicked: {}", .{e});
|
|
|
|
defer thread.join();
|
2022-12-15 23:37:38 +00:00
|
|
|
|
2023-02-14 02:01:40 +00:00
|
|
|
gui.run(.{
|
|
|
|
.cpu = &cpu,
|
|
|
|
.scheduler = &scheduler,
|
|
|
|
.tracker = &tracker,
|
|
|
|
.quit = &quit,
|
|
|
|
}) catch |e| exitln("main thread panicked: {}", .{e});
|
2022-12-15 23:37:38 +00:00
|
|
|
}
|
2022-04-14 03:52:21 +00:00
|
|
|
}
|
|
|
|
|
2023-02-22 05:18:37 +00:00
|
|
|
fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths {
|
2022-09-25 22:05:51 +00:00
|
|
|
const rom_path = romPath(result);
|
|
|
|
log.info("ROM path: {s}", .{rom_path});
|
|
|
|
|
|
|
|
const bios_path = result.args.bios;
|
2022-10-17 21:13:13 +00:00
|
|
|
if (bios_path) |path| log.info("BIOS path: {s}", .{path}) else log.warn("No BIOS provided", .{});
|
2022-09-25 22:05:51 +00:00
|
|
|
|
2022-10-21 07:39:16 +00:00
|
|
|
const save_path = try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "save" });
|
2022-09-25 22:05:51 +00:00
|
|
|
log.info("Save path: {s}", .{save_path});
|
|
|
|
|
2022-10-17 21:13:13 +00:00
|
|
|
return .{
|
2022-09-25 22:05:51 +00:00
|
|
|
.rom = rom_path,
|
|
|
|
.bios = bios_path,
|
|
|
|
.save = save_path,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
fn configFilePath(allocator: Allocator, config_path: []const u8) ![]const u8 {
|
|
|
|
const path = try std.fs.path.join(allocator, &[_][]const u8{ config_path, "zba", "config.toml" });
|
2022-10-17 21:13:13 +00:00
|
|
|
errdefer allocator.free(path);
|
2022-09-25 22:05:51 +00:00
|
|
|
|
|
|
|
// 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
|
2022-10-21 07:39:16 +00:00
|
|
|
std.fs.accessAbsolute(path, .{}) catch |e| {
|
|
|
|
if (e != error.FileNotFound) return e;
|
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
const config_file = std.fs.createFileAbsolute(path, .{}) catch |err| exitln("failed to create \"{s}\": {}", .{ path, err });
|
2022-10-21 07:39:16 +00:00
|
|
|
defer config_file.close();
|
2022-10-17 21:13:13 +00:00
|
|
|
|
2022-10-21 07:39:16 +00:00
|
|
|
try config_file.writeAll(@embedFile("../example.toml"));
|
2022-09-25 22:05:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
2022-05-23 15:38:44 +00:00
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
fn ensureDataDirsExist(data_path: []const u8) !void {
|
2022-09-25 22:05:51 +00:00
|
|
|
var dir = try std.fs.openDirAbsolute(data_path, .{});
|
|
|
|
defer dir.close();
|
2022-05-23 15:38:44 +00:00
|
|
|
|
2022-10-21 07:39:16 +00:00
|
|
|
// Will recursively create directories
|
2022-10-29 03:06:08 +00:00
|
|
|
try dir.makePath("zba" ++ std.fs.path.sep_str ++ "save");
|
2022-05-23 15:38:44 +00:00
|
|
|
}
|
|
|
|
|
2022-11-03 12:45:57 +00:00
|
|
|
fn ensureConfigDirExists(config_path: []const u8) !void {
|
|
|
|
var dir = try std.fs.openDirAbsolute(config_path, .{});
|
|
|
|
defer dir.close();
|
|
|
|
|
|
|
|
try dir.makePath("zba");
|
|
|
|
}
|
|
|
|
|
2022-09-25 22:05:51 +00:00
|
|
|
fn romPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) []const u8 {
|
2022-07-22 23:19:31 +00:00
|
|
|
return switch (result.positionals.len) {
|
|
|
|
1 => result.positionals[0],
|
2022-10-17 21:13:13 +00:00
|
|
|
0 => exitln("ZBA requires a path to a GamePak ROM", .{}),
|
|
|
|
else => exitln("ZBA received too many positional arguments.", .{}),
|
2022-07-22 23:19:31 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-10-17 21:13:13 +00:00
|
|
|
fn exitln(comptime format: []const u8, args: anytype) noreturn {
|
2022-09-25 22:05:51 +00:00
|
|
|
const stderr = std.io.getStdErr().writer();
|
|
|
|
stderr.print(format, args) catch {}; // Just exit already...
|
2022-10-17 21:13:13 +00:00
|
|
|
stderr.writeByte('\n') catch {};
|
2022-09-25 22:05:51 +00:00
|
|
|
std.os.exit(1);
|
2022-05-23 15:38:44 +00:00
|
|
|
}
|