2021-12-29 21:09:00 +00:00
|
|
|
const std = @import("std");
|
2022-06-15 04:08:43 +00:00
|
|
|
const builtin = @import("builtin");
|
2022-10-12 21:40:38 +00:00
|
|
|
const config = @import("config.zig");
|
|
|
|
|
2022-03-17 01:28:24 +00:00
|
|
|
const Log2Int = std.math.Log2Int;
|
2022-09-19 19:07:19 +00:00
|
|
|
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
|
2021-12-29 21:09:00 +00:00
|
|
|
|
2022-09-18 10:41:37 +00:00
|
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
|
2022-05-23 15:38:44 +00:00
|
|
|
pub const FpsTracker = struct {
|
2022-03-14 11:54:48 +00:00
|
|
|
const Self = @This();
|
|
|
|
|
2022-05-17 11:55:23 +00:00
|
|
|
fps: u32,
|
|
|
|
count: std.atomic.Atomic(u32),
|
|
|
|
timer: std.time.Timer,
|
2022-03-14 11:54:48 +00:00
|
|
|
|
|
|
|
pub fn init() Self {
|
2022-05-17 11:55:23 +00:00
|
|
|
return .{
|
|
|
|
.fps = 0,
|
|
|
|
.count = std.atomic.Atomic(u32).init(0),
|
|
|
|
.timer = std.time.Timer.start() catch unreachable,
|
|
|
|
};
|
2022-03-14 11:54:48 +00:00
|
|
|
}
|
|
|
|
|
2022-05-28 00:50:16 +00:00
|
|
|
pub fn tick(self: *Self) void {
|
2022-05-17 11:55:23 +00:00
|
|
|
_ = self.count.fetchAdd(1, .Monotonic);
|
2022-03-14 11:54:48 +00:00
|
|
|
}
|
|
|
|
|
2022-05-17 11:55:23 +00:00
|
|
|
pub fn value(self: *Self) u32 {
|
2022-05-28 00:50:16 +00:00
|
|
|
if (self.timer.read() >= std.time.ns_per_s) {
|
2022-11-30 04:21:02 +00:00
|
|
|
self.fps = self.count.swap(0, .Monotonic);
|
2022-05-17 11:55:23 +00:00
|
|
|
self.timer.reset();
|
|
|
|
}
|
2022-03-14 11:54:48 +00:00
|
|
|
|
2022-05-17 11:55:23 +00:00
|
|
|
return self.fps;
|
2022-03-14 11:54:48 +00:00
|
|
|
}
|
|
|
|
};
|
2022-03-18 09:27:37 +00:00
|
|
|
|
2022-09-18 09:23:30 +00:00
|
|
|
/// Creates a copy of a title with all Filesystem-invalid characters replaced
|
2022-03-22 17:41:18 +00:00
|
|
|
///
|
2022-03-22 17:54:37 +00:00
|
|
|
/// e.g. POKEPIN R/S to POKEPIN R_S
|
2022-04-27 23:08:44 +00:00
|
|
|
pub fn escape(title: [12]u8) [12]u8 {
|
2022-09-18 09:23:30 +00:00
|
|
|
var ret: [12]u8 = title;
|
2022-03-22 17:41:18 +00:00
|
|
|
|
2022-09-18 09:23:30 +00:00
|
|
|
//TODO: Add more replacements
|
|
|
|
std.mem.replaceScalar(u8, &ret, '/', '_');
|
|
|
|
std.mem.replaceScalar(u8, &ret, '\\', '_');
|
2022-03-22 17:41:18 +00:00
|
|
|
|
2022-09-18 09:23:30 +00:00
|
|
|
return ret;
|
2022-03-22 17:41:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-10 07:28:05 +00:00
|
|
|
pub const FilePaths = struct {
|
2023-03-11 03:13:38 +00:00
|
|
|
rom: ?[]const u8,
|
2022-04-10 07:28:05 +00:00
|
|
|
bios: ?[]const u8,
|
|
|
|
save: ?[]const u8,
|
|
|
|
};
|
2022-06-15 04:08:43 +00:00
|
|
|
|
2022-09-13 02:01:41 +00:00
|
|
|
pub const io = struct {
|
|
|
|
pub const read = struct {
|
|
|
|
pub fn todo(comptime log: anytype, comptime format: []const u8, args: anytype) u8 {
|
|
|
|
log.debug(format, args);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-10-29 05:37:33 +00:00
|
|
|
pub fn undef(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
2022-10-29 04:08:58 +00:00
|
|
|
@setCold(true);
|
|
|
|
|
2022-10-12 21:40:38 +00:00
|
|
|
const unhandled_io = config.config().debug.unhandled_io;
|
|
|
|
|
2022-09-13 02:01:41 +00:00
|
|
|
log.warn(format, args);
|
2022-10-12 21:40:38 +00:00
|
|
|
if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
|
2022-09-13 02:01:41 +00:00
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
2022-10-29 05:37:33 +00:00
|
|
|
|
|
|
|
pub fn err(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
|
|
|
@setCold(true);
|
|
|
|
|
|
|
|
log.err(format, args);
|
|
|
|
return null;
|
|
|
|
}
|
2022-09-13 02:01:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pub const write = struct {
|
|
|
|
pub fn undef(log: anytype, comptime format: []const u8, args: anytype) void {
|
2022-10-12 21:40:38 +00:00
|
|
|
const unhandled_io = config.config().debug.unhandled_io;
|
|
|
|
|
2022-09-13 02:01:41 +00:00
|
|
|
log.warn(format, args);
|
2022-10-12 21:40:38 +00:00
|
|
|
if (builtin.mode == .Debug and !unhandled_io) std.debug.panic("TODO: Implement I/O Register", .{});
|
2022-09-13 02:01:41 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
2022-07-27 17:49:55 +00:00
|
|
|
|
|
|
|
pub const Logger = struct {
|
|
|
|
const Self = @This();
|
2022-09-18 10:41:37 +00:00
|
|
|
const FmtArgTuple = std.meta.Tuple(&.{ u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 });
|
2022-07-27 17:49:55 +00:00
|
|
|
|
|
|
|
buf: std.io.BufferedWriter(4096 << 2, std.fs.File.Writer),
|
|
|
|
|
|
|
|
pub fn init(file: std.fs.File) Self {
|
|
|
|
return .{
|
|
|
|
.buf = .{ .unbuffered_writer = file.writer() },
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn print(self: *Self, comptime format: []const u8, args: anytype) !void {
|
|
|
|
try self.buf.writer().print(format, args);
|
2022-08-18 22:51:46 +00:00
|
|
|
try self.buf.flush(); // FIXME: On panics, whatever is in the buffer isn't written to file
|
2022-07-27 17:49:55 +00:00
|
|
|
}
|
|
|
|
|
2022-09-03 20:56:37 +00:00
|
|
|
pub fn mgbaLog(self: *Self, cpu: *const Arm7tdmi, opcode: u32) void {
|
2022-07-27 17:49:55 +00:00
|
|
|
const fmt_base = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | ";
|
|
|
|
const thumb_fmt = fmt_base ++ "{X:0>4}:\n";
|
|
|
|
const arm_fmt = fmt_base ++ "{X:0>8}:\n";
|
|
|
|
|
2022-09-03 20:56:37 +00:00
|
|
|
if (cpu.cpsr.t.read()) {
|
2022-07-27 17:49:55 +00:00
|
|
|
if (opcode >> 11 == 0x1E) {
|
|
|
|
// Instruction 1 of a BL Opcode, print in ARM mode
|
2022-11-20 19:36:40 +00:00
|
|
|
const low = cpu.bus.dbgRead(u16, cpu.r[15] - 2);
|
2022-07-27 17:49:55 +00:00
|
|
|
const bl_opcode = @as(u32, opcode) << 16 | low;
|
|
|
|
|
2022-09-03 20:56:37 +00:00
|
|
|
self.print(arm_fmt, Self.fmtArgs(cpu, bl_opcode)) catch @panic("failed to write to log file");
|
2022-07-27 17:49:55 +00:00
|
|
|
} else {
|
2022-09-03 20:56:37 +00:00
|
|
|
self.print(thumb_fmt, Self.fmtArgs(cpu, opcode)) catch @panic("failed to write to log file");
|
2022-07-27 17:49:55 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-09-03 20:56:37 +00:00
|
|
|
self.print(arm_fmt, Self.fmtArgs(cpu, opcode)) catch @panic("failed to write to log file");
|
2022-07-27 17:49:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-03 20:56:37 +00:00
|
|
|
fn fmtArgs(cpu: *const Arm7tdmi, opcode: u32) FmtArgTuple {
|
2022-07-27 17:49:55 +00:00
|
|
|
return .{
|
2022-09-03 20:56:37 +00:00
|
|
|
cpu.r[0],
|
|
|
|
cpu.r[1],
|
|
|
|
cpu.r[2],
|
|
|
|
cpu.r[3],
|
|
|
|
cpu.r[4],
|
|
|
|
cpu.r[5],
|
|
|
|
cpu.r[6],
|
|
|
|
cpu.r[7],
|
|
|
|
cpu.r[8],
|
|
|
|
cpu.r[9],
|
|
|
|
cpu.r[10],
|
|
|
|
cpu.r[11],
|
|
|
|
cpu.r[12],
|
|
|
|
cpu.r[13],
|
|
|
|
cpu.r[14],
|
2022-07-29 00:03:32 +00:00
|
|
|
cpu.r[15] - if (cpu.cpsr.t.read()) 2 else @as(u32, 4),
|
2022-09-03 20:56:37 +00:00
|
|
|
cpu.cpsr.raw,
|
2022-07-27 17:49:55 +00:00
|
|
|
opcode,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-10 23:31:12 +00:00
|
|
|
pub const audio = struct {
|
|
|
|
const _io = @import("core/bus/io.zig");
|
|
|
|
|
|
|
|
const ToneSweep = @import("core/apu/ToneSweep.zig");
|
|
|
|
const Tone = @import("core/apu/Tone.zig");
|
|
|
|
const Wave = @import("core/apu/Wave.zig");
|
|
|
|
const Noise = @import("core/apu/Noise.zig");
|
|
|
|
|
|
|
|
pub const length = struct {
|
|
|
|
const FrameSequencer = @import("core/apu.zig").FrameSequencer;
|
|
|
|
|
|
|
|
/// Update State of Ch1, Ch2 and Ch3 length timer
|
|
|
|
pub fn update(comptime T: type, self: *T, fs: *const FrameSequencer, nrx34: _io.Frequency) void {
|
|
|
|
comptime std.debug.assert(T == ToneSweep or T == Tone or T == Wave);
|
|
|
|
|
|
|
|
// Write to NRx4 when FS's next step is not one that clocks the length counter
|
|
|
|
if (!fs.isLengthNext()) {
|
|
|
|
// If length_enable was disabled but is now enabled and length timer is not 0 already,
|
|
|
|
// decrement the length timer
|
|
|
|
|
|
|
|
if (!self.freq.length_enable.read() and nrx34.length_enable.read() and self.len_dev.timer != 0) {
|
|
|
|
self.len_dev.timer -= 1;
|
|
|
|
|
|
|
|
// If Length Timer is now 0 and trigger is clear, disable the channel
|
|
|
|
if (self.len_dev.timer == 0 and !nrx34.trigger.read()) self.enabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub const ch4 = struct {
|
|
|
|
/// update state of ch4 length timer
|
|
|
|
pub fn update(self: *Noise, fs: *const FrameSequencer, nr44: _io.NoiseControl) void {
|
|
|
|
// Write to NRx4 when FS's next step is not one that clocks the length counter
|
|
|
|
if (!fs.isLengthNext()) {
|
|
|
|
// If length_enable was disabled but is now enabled and length timer is not 0 already,
|
|
|
|
// decrement the length timer
|
|
|
|
|
|
|
|
if (!self.cnt.length_enable.read() and nr44.length_enable.read() and self.len_dev.timer != 0) {
|
|
|
|
self.len_dev.timer -= 1;
|
|
|
|
|
|
|
|
// If Length Timer is now 0 and trigger is clear, disable the channel
|
|
|
|
if (self.len_dev.timer == 0 and !nr44.trigger.read()) self.enabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2022-10-13 01:57:44 +00:00
|
|
|
|
2022-10-30 03:40:57 +00:00
|
|
|
/// Sets a quarter (8) of the bits of the u32 `left` to the value of u8 `right`
|
|
|
|
pub inline fn setQuart(left: u32, addr: u8, right: u8) u32 {
|
|
|
|
const offset = @truncate(u2, addr);
|
|
|
|
|
|
|
|
return switch (offset) {
|
|
|
|
0b00 => (left & 0xFFFF_FF00) | right,
|
|
|
|
0b01 => (left & 0xFFFF_00FF) | @as(u32, right) << 8,
|
|
|
|
0b10 => (left & 0xFF00_FFFF) | @as(u32, right) << 16,
|
|
|
|
0b11 => (left & 0x00FF_FFFF) | @as(u32, right) << 24,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-10-29 04:30:12 +00:00
|
|
|
/// Calculates the correct shift offset for an aligned/unaligned u8 read
|
|
|
|
///
|
2022-10-30 05:36:46 +00:00
|
|
|
/// TODO: Support u16 reads of u32 values?
|
|
|
|
pub inline fn getHalf(byte: u8) u4 {
|
2022-10-29 04:30:12 +00:00
|
|
|
return @truncate(u4, byte & 1) << 3;
|
|
|
|
}
|
|
|
|
|
2022-10-30 03:40:57 +00:00
|
|
|
pub inline fn setHalf(comptime T: type, left: T, addr: u8, right: HalfInt(T)) T {
|
|
|
|
const offset = @truncate(u1, addr >> if (T == u32) 1 else 0);
|
|
|
|
|
|
|
|
return switch (T) {
|
|
|
|
u32 => switch (offset) {
|
|
|
|
0b0 => (left & 0xFFFF_0000) | right,
|
|
|
|
0b1 => (left & 0x0000_FFFF) | @as(u32, right) << 16,
|
|
|
|
},
|
|
|
|
u16 => switch (offset) {
|
|
|
|
0b0 => (left & 0xFF00) | right,
|
2022-10-30 07:12:58 +00:00
|
|
|
0b1 => (left & 0x00FF) | @as(u16, right) << 8,
|
2022-10-30 03:40:57 +00:00
|
|
|
},
|
|
|
|
else => @compileError("unsupported type"),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-10-13 01:57:44 +00:00
|
|
|
/// The Integer type which corresponds to T with exactly half the amount of bits
|
|
|
|
fn HalfInt(comptime T: type) type {
|
|
|
|
const type_info = @typeInfo(T);
|
|
|
|
comptime std.debug.assert(type_info == .Int); // Type must be an integer
|
|
|
|
comptime std.debug.assert(type_info.Int.bits % 2 == 0); // Type must have an even amount of bits
|
|
|
|
|
|
|
|
return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1);
|
|
|
|
}
|
2022-09-18 10:41:37 +00:00
|
|
|
|
|
|
|
/// Double Buffering Implementation
|
|
|
|
pub const FrameBuffer = struct {
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
layers: [2][]u8,
|
|
|
|
buf: []u8,
|
2023-03-10 08:27:31 +00:00
|
|
|
current: u1 = 0,
|
2022-09-18 10:41:37 +00:00
|
|
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
// TODO: Rename
|
|
|
|
const Device = enum { Emulator, Renderer };
|
|
|
|
|
|
|
|
pub fn init(allocator: Allocator, comptime len: comptime_int) !Self {
|
|
|
|
const buf = try allocator.alloc(u8, len * 2);
|
|
|
|
std.mem.set(u8, buf, 0);
|
|
|
|
|
|
|
|
return .{
|
|
|
|
// Front and Back Framebuffers
|
|
|
|
.layers = [_][]u8{ buf[0..][0..len], buf[len..][0..len] },
|
|
|
|
.buf = buf,
|
|
|
|
|
|
|
|
.allocator = allocator,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-10 08:27:31 +00:00
|
|
|
pub fn reset(self: *Self) void {
|
|
|
|
std.mem.set(u8, self.buf, 0);
|
|
|
|
self.current = 0;
|
|
|
|
}
|
|
|
|
|
2022-09-18 10:41:37 +00:00
|
|
|
pub fn deinit(self: *Self) void {
|
|
|
|
self.allocator.free(self.buf);
|
|
|
|
self.* = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn swap(self: *Self) void {
|
|
|
|
self.current = ~self.current;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get(self: *Self, comptime dev: Device) []u8 {
|
|
|
|
return self.layers[if (dev == .Emulator) self.current else ~self.current];
|
|
|
|
}
|
|
|
|
};
|